diff --git a/.gitignore b/.gitignore index 2f37a33f1..627d3c630 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,4 @@ compile_flags.txt # CMake Files cmake-build-relwithdebinfo/* +skill-caps.diff diff --git a/CHANGELOG.md b/CHANGELOG.md index dcfa1f1bc..7f735023c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,969 @@ +## [23.10.3] 9/16/2025 + +### Hotfix + +* Hotfix crashes occurring in #4987. @Akkadius 2025-09-17 + +## [23.10.2] 9/16/2025 + +### Hotfix + +* Revert #4996 as it was causing critical issues with spells that needs to be further investigated. @Akkadius 2025-09-17 + +## [23.10.1] 9/16/2025 + +### Hotfix +* Fixed Mail Key Bug ([#5015](https://github.com/EQEmu/Server/pull/5015)) @Kinglykrab 2025-09-16 + +## [23.10.0] 9/15/2025 + +### Build + +* Fix Linking with GCC ([#4969](https://github.com/EQEmu/Server/pull/4969)) @solar984 2025-08-03 + +### Code + +* Add #npcedit npc_tint_id Help Message ([#4982](https://github.com/EQEmu/Server/pull/4982)) @Kinglykrab 2025-08-17 +* Cleanup #show ip_lookup Message ([#5005](https://github.com/EQEmu/Server/pull/5005)) @Kinglykrab 2025-08-30 +* Fix #set race 0 Message ([#5004](https://github.com/EQEmu/Server/pull/5004)) @Kinglykrab 2025-08-30 +* Fix Issues with Strings::Commify and Mob::SendStatsWindow ([#4984](https://github.com/EQEmu/Server/pull/4984)) @Kinglykrab 2025-08-17 +* Remove Attributions ([#4988](https://github.com/EQEmu/Server/pull/4988)) @KimLS 2025-08-16 +* Remove Unused errorname Variable ([#5001](https://github.com/EQEmu/Server/pull/5001)) @Kinglykrab 2025-08-29 + +### Commands + +* Add #find account Subcommand ([#4981](https://github.com/EQEmu/Server/pull/4981)) @Kinglykrab 2025-08-17 +* Add #show keyring Subcommand ([#4973](https://github.com/EQEmu/Server/pull/4973)) @Kinglykrab 2025-08-03 +* Add #task complete Saylink to #task show ([#4985](https://github.com/EQEmu/Server/pull/4985)) @Kinglykrab 2025-08-17 + +### Constants + +* Change Race Changes to Race Namespace ([#5000](https://github.com/EQEmu/Server/pull/5000)) @Kinglykrab 2025-08-30 +* Convert SE Defines to SpellEffect Namespace ([#4999](https://github.com/EQEmu/Server/pull/4999)) @Kinglykrab 2025-08-30 + +### Database + +* Add `heal_amount` to `character_stats_record` ([#4986](https://github.com/EQEmu/Server/pull/4986)) @Kinglykrab 2025-08-17 + +### Fixes + +* Fix #show recipe uint16 Cap ([#4978](https://github.com/EQEmu/Server/pull/4978)) @Kinglykrab 2025-08-11 +* Fix Race 474 for Titanium ([#4979](https://github.com/EQEmu/Server/pull/4979)) @regneq 2025-08-11 +* Fix Recipe Inspect Bug ([#4994](https://github.com/EQEmu/Server/pull/4994)) @Kinglykrab 2025-08-30 +* Fix Several Evolving Item Bugs ([#4992](https://github.com/EQEmu/Server/pull/4992)) @neckkola 2025-09-08 +* Fix Task Reloading ([#5002](https://github.com/EQEmu/Server/pull/5002)) @Kinglykrab 2025-08-29 + +### Loginserver + +* Fix Legacy World When Using Local DB ([#4970](https://github.com/EQEmu/Server/pull/4970)) @solar984 2025-08-03 + +### Pets + +* Add Pet Constants and Methods ([#4987](https://github.com/EQEmu/Server/pull/4987)) @Kinglykrab 2025-08-17 + +### Quest API + +* Add EVENT_CHARM_START and EVENT_CHARM_END ([#5013](https://github.com/EQEmu/Server/pull/5013)) @Kinglykrab 2025-09-15 +* Add GetKeyRing() to Perl/Lua ([#4980](https://github.com/EQEmu/Server/pull/4980)) @Kinglykrab 2025-08-17 +* Add GetNPCTintIndex() to Perl/Lua ([#4983](https://github.com/EQEmu/Server/pull/4983)) @Kinglykrab 2025-08-17 +* Add GetTimers() and GetPausedTimers() to Perl/Lua ([#4965](https://github.com/EQEmu/Server/pull/4965)) @Kinglykrab 2025-08-03 +* Add Identifiers to Get/Modify NPC Stat Methods ([#5012](https://github.com/EQEmu/Server/pull/5012)) @Kinglykrab 2025-09-15 + +### Repositories + +* Convert Character Inspect Messages to Repositories ([#4997](https://github.com/EQEmu/Server/pull/4997)) @Kinglykrab 2025-08-30 +* Convert Damage Shield Types to Repositories ([#4995](https://github.com/EQEmu/Server/pull/4995)) @Kinglykrab 2025-08-30 +* Convert Item Loading to Repositories ([#4998](https://github.com/EQEmu/Server/pull/4998)) @Kinglykrab 2025-08-30 +* Convert Mail Key to Repositories ([#5007](https://github.com/EQEmu/Server/pull/5007)) @Kinglykrab 2025-09-15 +* Convert Shared Bank Platinum to Repositories ([#5006](https://github.com/EQEmu/Server/pull/5006)) @Kinglykrab 2025-09-02 +* Convert Spell Loading to Repositories ([#4996](https://github.com/EQEmu/Server/pull/4996)) @Kinglykrab 2025-08-30 +* Convert Total Time Played to Repositories ([#5008](https://github.com/EQEmu/Server/pull/5008)) @Kinglykrab 2025-09-15 + +## [23.9.1] 8/2/2025 + +### Hotfix + +* Fix Quest Ownership Edge Case ([#4977](https://github.com/EQEmu/Server/pull/4977)) @Kinglykrab 2025-08-02 + +## [23.9.0] 8/2/2025 + +### Bots + +* Fix FinishBuffing rule ([#4961](https://github.com/EQEmu/Server/pull/4961)) @nytmyr 2025-07-01 +* Fix ^cast resurrects ([#4958](https://github.com/EQEmu/Server/pull/4958)) @nytmyr 2025-06-29 + +### Build + +* Fix Linking with GCC ([#4969](https://github.com/EQEmu/Server/pull/4969)) @solar984 2025-08-03 +* More Build Speed Improvements ([#4959](https://github.com/EQEmu/Server/pull/4959)) @Akkadius 2025-06-30 + +### Commands + +* Add #show keyring Subcommand ([#4973](https://github.com/EQEmu/Server/pull/4973)) @Kinglykrab 2025-08-03 + +### Database + +* Add Indexes to NPC's Spawns Loot ([#4972](https://github.com/EQEmu/Server/pull/4972)) @Akkadius 2025-07-30 + +### Feature + +* Zone Scripting ([#4908](https://github.com/EQEmu/Server/pull/4908)) @Kinglykrab 2025-07-10 + +### Fixes + +* Add a missing Froglok starting area for Titanium Startzone. ([#4962](https://github.com/EQEmu/Server/pull/4962)) @regneq 2025-07-04 +* Fix Hero's Forge Ingame and Character Select ([#4966](https://github.com/EQEmu/Server/pull/4966)) @Kinglykrab 2025-07-30 +* Show player count on the server list status. ([#4971](https://github.com/EQEmu/Server/pull/4971)) @regneq 2025-07-30 + +### Loginserver + +* Fix Legacy World When Using Local DB ([#4970](https://github.com/EQEmu/Server/pull/4970)) @solar984 2025-08-03 + +### Performance + +* Clear Wearchange Deduplication Cache ([#4960](https://github.com/EQEmu/Server/pull/4960)) @Akkadius 2025-06-30 + +### Quest API + +* Add GetMemberRole() to Perl/Lua ([#4963](https://github.com/EQEmu/Server/pull/4963)) @Barathos 2025-07-10 +* Add GetTimers() and GetPausedTimers() to Perl/Lua ([#4965](https://github.com/EQEmu/Server/pull/4965)) @Kinglykrab 2025-08-03 + +## [23.8.1] 6/28/2025 + +### Crash Fix + +* Fix Possible Crashes with Raid Methods ([#4955](https://github.com/EQEmu/Server/pull/4955)) @Kinglykrab 2025-06-26 + +### Databuckets + +* Revert Caching Changes of #4917 ([#4957](https://github.com/EQEmu/Server/pull/4957)) @Akkadius 2025-06-28 + +### Fixes + +* Fix FindCharacter Using content_db ([#4956](https://github.com/EQEmu/Server/pull/4956)) @Kinglykrab 2025-06-26 +* Fix Hero Forge on Character Select ([#4954](https://github.com/EQEmu/Server/pull/4954)) @Kinglykrab 2025-06-26 + +## [23.8.0] 6/25/2025 + +### API + +* Add wwmarquee ([#4919](https://github.com/EQEmu/Server/pull/4919)) @Akkadius 2025-06-23 + +### Build + +* Significantly Improve Build Times Using Unity Builds ([#4948](https://github.com/EQEmu/Server/pull/4948)) @Akkadius 2025-06-22 + +### Code + +* AdventureManager Global to Singleton Cleanup ([#4931](https://github.com/EQEmu/Server/pull/4931)) @Kinglykrab 2025-06-25 +* Cleanup Strings Header ([#4950](https://github.com/EQEmu/Server/pull/4950)) @Akkadius 2025-06-22 +* ClientList Global to Singleton Cleanup ([#4942](https://github.com/EQEmu/Server/pull/4942)) @Kinglykrab 2025-06-25 +* DatabaseUpdate Global to Singleton Cleanup ([#4943](https://github.com/EQEmu/Server/pull/4943)) @Kinglykrab 2025-06-25 +* DiscordManager Global to Singleton Cleanup ([#4926](https://github.com/EQEmu/Server/pull/4926)) @Kinglykrab 2025-06-25 +* EQ::Random Global to Singleton Cleanup ([#4936](https://github.com/EQEmu/Server/pull/4936)) @Kinglykrab 2025-06-22 +* EQEmuLogSys Global to Singleton Cleanup ([#4925](https://github.com/EQEmu/Server/pull/4925)) @Akkadius 2025-06-25 +* EvolvingItemsManager Global to Singleton Cleanup ([#4929](https://github.com/EQEmu/Server/pull/4929)) @Kinglykrab 2025-06-25 +* LFGuildManager Global to Singleton Cleanup ([#4927](https://github.com/EQEmu/Server/pull/4927)) @Kinglykrab 2025-06-25 +* LoginServerList Global to Singleton Cleanup ([#4941](https://github.com/EQEmu/Server/pull/4941)) @Kinglykrab 2025-06-25 +* Optimize PCH Config (Faster Builds) ([#4951](https://github.com/EQEmu/Server/pull/4951)) @Akkadius 2025-06-22 +* PCH Cleanup ([#4952](https://github.com/EQEmu/Server/pull/4952)) @Akkadius 2025-06-25 +* PathManager Global to Singleton Cleanup ([#4924](https://github.com/EQEmu/Server/pull/4924)) @Akkadius 2025-06-22 +* PetitionList Global to Singleton Cleanup ([#4944](https://github.com/EQEmu/Server/pull/4944)) @Kinglykrab 2025-06-25 +* PlayerEventLogs Global to Singleton Cleanup ([#4928](https://github.com/EQEmu/Server/pull/4928)) @Kinglykrab 2025-06-25 +* QueryServConnection Global to Singleton Cleanup ([#4938](https://github.com/EQEmu/Server/pull/4938)) @Kinglykrab 2025-06-25 +* Remove Lua Rule Constants ([#4949](https://github.com/EQEmu/Server/pull/4949)) @Akkadius 2025-06-22 +* Remove Regex Compile Bloat ([#4947](https://github.com/EQEmu/Server/pull/4947)) @Akkadius 2025-06-22 +* Remove Unused MZoneShutdown Mutex ([#4946](https://github.com/EQEmu/Server/pull/4946)) @Kinglykrab 2025-06-22 +* SharedTaskManager Global to Singleton Cleanup ([#4930](https://github.com/EQEmu/Server/pull/4930)) @Kinglykrab 2025-06-25 +* SkillCaps Global to Singleton Cleanup ([#4933](https://github.com/EQEmu/Server/pull/4933)) @Kinglykrab 2025-06-22 +* TaskManager Global to Singleton Cleanup ([#4945](https://github.com/EQEmu/Server/pull/4945)) @Kinglykrab 2025-06-22 +* UCSConnection Global to Singleton Cleanup ([#4937](https://github.com/EQEmu/Server/pull/4937)) @Kinglykrab 2025-06-25 +* WebInterfaceList Global to Singleton Cleanup ([#4935](https://github.com/EQEmu/Server/pull/4935)) @Kinglykrab 2025-06-25 +* WorldContentServer Global to Singleton Cleanup ([#4939](https://github.com/EQEmu/Server/pull/4939)) @Kinglykrab 2025-06-25 +* WorldEventScheduler and ZoneEventScheduler Global to Singleton Cleanup ([#4932](https://github.com/EQEmu/Server/pull/4932)) @Kinglykrab 2025-06-25 +* ZSList Global to Singleton Cleanup ([#4940](https://github.com/EQEmu/Server/pull/4940)) @Kinglykrab 2025-06-25 +* ZoneStore Global to Singleton Cleanup ([#4934](https://github.com/EQEmu/Server/pull/4934)) @Kinglykrab 2025-06-23 + +### Corpses + +* Add corpse entity variables to DB ([#4911](https://github.com/EQEmu/Server/pull/4911)) @fryguy503 2025-06-25 + +### Databuckets + +* Move Databuckets to Common ([#4918](https://github.com/EQEmu/Server/pull/4918)) @Akkadius 2025-06-16 +* Nested Caching ([#4917](https://github.com/EQEmu/Server/pull/4917)) @Akkadius 2025-06-25 + +### Doors + +* Fix door saving for versions ([#4905](https://github.com/EQEmu/Server/pull/4905)) @nytmyr 2025-06-09 + +### Fixes + +* Bulk Send Corpses after Idle State ([#4910](https://github.com/EQEmu/Server/pull/4910)) @Akkadius 2025-06-09 +* Fix ClientList Singleton Shortcomings of #4942 @Akkadius 2025-06-25 +* Fix breaking changes to Titanium, SoF, SoD patches causes by big bags update ([#4922](https://github.com/EQEmu/Server/pull/4922)) @regneq 2025-06-25 + +### Logs + +* Fix output for tasks in logs ([#4907](https://github.com/EQEmu/Server/pull/4907)) @joligario 2025-06-09 + +### Performance + +* Auto Idle / AFK ([#4903](https://github.com/EQEmu/Server/pull/4903)) @Akkadius 2025-05-22 +* Wearchange Packet Send Deduplication ([#4916](https://github.com/EQEmu/Server/pull/4916)) @Akkadius 2025-06-25 + +### Player Event Logs + +* Don't Clear Event Data on ETL Events ([#4913](https://github.com/EQEmu/Server/pull/4913)) @Akkadius 2025-06-25 + +### Player Events + +* Don't Write Empty Data ([#4912](https://github.com/EQEmu/Server/pull/4912)) @Akkadius 2025-06-25 +* Zone Fetch Settings from QS if Enabled ([#4915](https://github.com/EQEmu/Server/pull/4915)) @Akkadius 2025-06-25 + +### Quest API + +* Add CompleteTask and UncompleteTask methods to Perl/Lua ([#4921](https://github.com/EQEmu/Server/pull/4921)) @Barathos 2025-06-25 +* Lua Client Scoped EnableTitleSets ([#4914](https://github.com/EQEmu/Server/pull/4914)) @fryguy503 2025-06-25 + +### Quests + +* Support Multiple Quest, Plugin, and Lua Module Paths ([#4906](https://github.com/EQEmu/Server/pull/4906)) @Akkadius 2025-06-09 + +### World API + +* Input Validation ([#4904](https://github.com/EQEmu/Server/pull/4904)) @Akkadius 2025-05-22 + +## [23.7.0] 5/19/2025 + +### CLI + +* Add custom database version output ([#4901](https://github.com/EQEmu/Server/pull/4901)) @joligario 2025-05-18 +* Fix MySQL check in database dumper ([#4897](https://github.com/EQEmu/Server/pull/4897)) @joligario 2025-05-16 + +### Commands + +* Add #zonevariable Command ([#4882](https://github.com/EQEmu/Server/pull/4882)) @Kinglykrab 2025-05-15 + +### Database + +* Add Custom Database Migrations for Operators ([#4892](https://github.com/EQEmu/Server/pull/4892)) @Akkadius 2025-05-16 +* Remove Transaction Wrapped Character Save ([#4894](https://github.com/EQEmu/Server/pull/4894)) @Akkadius 2025-05-16 + +### Fixes + +* Deadlock on failed #copycharacter commands ([#4887](https://github.com/EQEmu/Server/pull/4887)) @Akkadius 2025-05-16 + +### Logging + +* Auto Update Log Category Names ([#4890](https://github.com/EQEmu/Server/pull/4890)) @Akkadius 2025-05-16 + +### Netcode + +* Resend Logic Adjustments ([#4900](https://github.com/EQEmu/Server/pull/4900)) @Akkadius 2025-05-18 + +### Player Events + +* Add rule to ignore configured GM commands ([#4888](https://github.com/EQEmu/Server/pull/4888)) @Akkadius 2025-05-16 + +### Rules + +* Auto Update Rule Notes from Source ([#4891](https://github.com/EQEmu/Server/pull/4891)) @Akkadius 2025-05-16 + +### World + +* Fix Rarer Reload Deadlock ([#4893](https://github.com/EQEmu/Server/pull/4893)) @Akkadius 2025-05-16 + +### Zone State + +* Load New Spawn2 Data When Present ([#4889](https://github.com/EQEmu/Server/pull/4889)) @Akkadius 2025-05-16 + +## [23.6.0] 5/14/2025 + +### Bots + +* Correct ^pull logic and add checks for Enchanter pets ([#4827](https://github.com/EQEmu/Server/pull/4827)) @nytmyr 2025-05-15 +* Fix creation limit, spawn limit, level requirement checks ([#4868](https://github.com/EQEmu/Server/pull/4868)) @nytmyr 2025-05-15 +* Move all spell_id instances to uint16 ([#4876](https://github.com/EQEmu/Server/pull/4876)) @nytmyr 2025-05-15 +* Prevent non-taunters from potentially fleeing mob on TargetReflection ([#4859](https://github.com/EQEmu/Server/pull/4859)) @nytmyr 2025-04-28 + +### CLI + +* ETL Settings Output ([#4873](https://github.com/EQEmu/Server/pull/4873)) @joligario 2025-05-15 + +### Code + +* Fix typo in QueryNameAvailablity ([#4869](https://github.com/EQEmu/Server/pull/4869)) @nytmyr 2025-04-28 + +### Crash + +* Fix crash bug with pbae and quest scripts spawning mobs ([#4884](https://github.com/EQEmu/Server/pull/4884)) @carolus21rex 2025-05-15 + +### Feature + +* Add Character:TradeskillUpMinChance rule ([#4867](https://github.com/EQEmu/Server/pull/4867)) @zrix-eq 2025-05-15 +* Enable spawn attribute for NPCTintID ([#4871](https://github.com/EQEmu/Server/pull/4871)) @neckkola 2025-05-15 + +### Fixes + +* Add trader/buyer cleanup actions ([#4843](https://github.com/EQEmu/Server/pull/4843)) @neckkola 2025-05-15 +* Fix #copycharacter command ([#4860](https://github.com/EQEmu/Server/pull/4860)) @nytmyr 2025-04-28 +* Fix Crash with #task ([#4874](https://github.com/EQEmu/Server/pull/4874)) @Kinglykrab 2025-04-30 +* Fix Object Name Init, User Refs, and Client Sync on Close ([#4861](https://github.com/EQEmu/Server/pull/4861)) @zimp-wow 2025-05-15 +* Fix breaking change to UF patches caused by Big Bags update ([#4883](https://github.com/EQEmu/Server/pull/4883)) @hbingram 2025-05-15 +* Prevent Ranged Attack from being triggered at arbitrary rate ([#4879](https://github.com/EQEmu/Server/pull/4879)) @catapultam-habeo 2025-05-15 + +### Performance + +* Store Player Title Sets in Client Memory ([#4836](https://github.com/EQEmu/Server/pull/4836)) @Kinglykrab 2025-05-15 + +### Quest API + +* Add Last Login and First Login Flags to EVENT_CONNECT ([#4866](https://github.com/EQEmu/Server/pull/4866)) @Kinglykrab 2025-05-15 + +## [23.5.0] 4/10/2025 + +### API + +* World API Optimizations ([#4850](https://github.com/EQEmu/Server/pull/4850)) @Akkadius 2025-04-10 + +### Bots + +* Add valid state checks to ^clickitem ([#4830](https://github.com/EQEmu/Server/pull/4830)) @nytmyr 2025-04-10 +* Flag all buffs with SE_DamageShield as Damage Shield ([#4833](https://github.com/EQEmu/Server/pull/4833)) @nytmyr 2025-04-10 +* Positioning rewrite ([#4856](https://github.com/EQEmu/Server/pull/4856)) @nytmyr 2025-04-10 +* Restore old buff overwrite blocking ([#4832](https://github.com/EQEmu/Server/pull/4832)) @nytmyr 2025-04-10 + +### Bugfix + +* Load zone variables before encounter_load. ([#4846](https://github.com/EQEmu/Server/pull/4846)) @zimp-wow 2025-04-10 +* Prevent depops from blocking new spawns. ([#4841](https://github.com/EQEmu/Server/pull/4841)) @zimp-wow 2025-04-10 +* Prevent final shutdown from persisting incomplete state. ([#4849](https://github.com/EQEmu/Server/pull/4849)) @zimp-wow 2025-04-10 + +### Code + +* Remove queryserv dump flag ([#4842](https://github.com/EQEmu/Server/pull/4842)) @joligario 2025-04-10 +* Update link for legacy EQEmu loginserver account setup ([#4826](https://github.com/EQEmu/Server/pull/4826)) @joligario 2025-04-10 + +### Crash + +* Fix rarer exception crash issue in PlayerEventLogs::ProcessBatchQueue ([#4835](https://github.com/EQEmu/Server/pull/4835)) @Akkadius 2025-04-03 + +### Database + +* Fix manifest for `helmtexture` in `horses` table ([#4852](https://github.com/EQEmu/Server/pull/4852)) @joligario 2025-04-10 + +### Feature + +* Add rule to consume command text from any channel ([#4839](https://github.com/EQEmu/Server/pull/4839)) @catapultam-habeo 2025-04-10 + +### Fixes + +* Add the bazaar search limit to query ([#4829](https://github.com/EQEmu/Server/pull/4829)) @neckkola 2025-04-10 +* Backfill expire_at (not sure why this didn't make it in there to begin with) @Akkadius 2025-03-31 +* Bazaar Search window not working in a DZ ([#4828](https://github.com/EQEmu/Server/pull/4828)) @neckkola 2025-04-10 +* Databuckets Account Cache Loading ([#4855](https://github.com/EQEmu/Server/pull/4855)) @Akkadius 2025-04-10 +* Fix missing timer_name check on Mob::StopTimer ([#4840](https://github.com/EQEmu/Server/pull/4840)) @zimp-wow 2025-04-04 +* FixHeading Infinite Loop Fix ([#4854](https://github.com/EQEmu/Server/pull/4854)) @KimLS 2025-04-10 +* Make sure we don't expire default value instances @Akkadius 2025-03-31 +* Regression in World SendEmoteMessageRaw ([#4837](https://github.com/EQEmu/Server/pull/4837)) @Akkadius 2025-04-03 +* Remove QS Tables From Export @Akkadius 2025-04-10 +* Zone State Spawn2 Location Restore ([#4844](https://github.com/EQEmu/Server/pull/4844)) @Akkadius 2025-04-10 + +### Netcode + +* Fix Stale Client Edge Case ([#4853](https://github.com/EQEmu/Server/pull/4853)) @Akkadius 2025-04-10 + +### Performance + +* Character Save Optimizations ([#4851](https://github.com/EQEmu/Server/pull/4851)) @Akkadius 2025-04-10 +* Network Ring Buffers ([#4857](https://github.com/EQEmu/Server/pull/4857)) @Akkadius 2025-04-10 +* Pre-Compute CLE Server Lists ([#4838](https://github.com/EQEmu/Server/pull/4838)) @Akkadius 2025-04-10 + +### Spells + +* Fear resistance effects edge case fixes and support for SPA 102 as an AA ([#4848](https://github.com/EQEmu/Server/pull/4848)) @KayenEQ 2025-04-10 +* Update to SPA 180 SE_ResistSpellChance to not block unresistable spells. ([#4847](https://github.com/EQEmu/Server/pull/4847)) @KayenEQ 2025-04-10 +* Update to SPA 378 SE_SpellEffectResistChance ([#4845](https://github.com/EQEmu/Server/pull/4845)) @KayenEQ 2025-04-10 + +## [23.4.0] 3/30/2025 + +### API + +* Expose Zoneserver Compile Metadata ([#4815](https://github.com/EQEmu/Server/pull/4815)) @Akkadius 2025-03-29 + +### Bots + +* Charmed Pets were breaking Mob respawns ([#4780](https://github.com/EQEmu/Server/pull/4780)) @nytmyr 2025-03-16 +* Enraged positioning ([#4789](https://github.com/EQEmu/Server/pull/4789)) @nytmyr 2025-03-29 +* Fix IsValidSpellTypeBySpellID to account for all types ([#4764](https://github.com/EQEmu/Server/pull/4764)) @nytmyr 2025-03-19 +* Fix Rule ZonesWithSpawnLimits/ZonesWithForcedSpawnLimits errors ([#4791](https://github.com/EQEmu/Server/pull/4791)) @nytmyr 2025-03-29 +* Fix rule Bots:FinishBuffing ([#4788](https://github.com/EQEmu/Server/pull/4788)) @nytmyr 2025-03-29 +* Line of Sight and Mez optimizations and cleanup ([#4746](https://github.com/EQEmu/Server/pull/4746)) @nytmyr 2025-03-29 +* Prevent bot pets from despawning on #repop ([#4790](https://github.com/EQEmu/Server/pull/4790)) @nytmyr 2025-03-29 + +### Code + +* Control flow defaults missed in recent bot updates ([#4817](https://github.com/EQEmu/Server/pull/4817)) @joligario 2025-03-30 +* Remove Extraneous Time Type in ShowZoneData ([#4806](https://github.com/EQEmu/Server/pull/4806)) @Kinglykrab 2025-03-29 +* Remove Unused Command Methods ([#4805](https://github.com/EQEmu/Server/pull/4805)) @Kinglykrab 2025-03-29 +* UCS Member Count ([#4819](https://github.com/EQEmu/Server/pull/4819)) @joligario 2025-03-30 + +### Commands + +* Add #show zone_variables ([#4812](https://github.com/EQEmu/Server/pull/4812)) @Akkadius 2025-03-29 +* Add Instance Support to #zoneshutdown ([#4807](https://github.com/EQEmu/Server/pull/4807)) @Kinglykrab 2025-03-29 + +### Crash + +* Fix Rarer World Crash with Player Event Thread Processor ([#4800](https://github.com/EQEmu/Server/pull/4800)) @Akkadius 2025-03-29 +* Fix Repop Race Condition Crash ([#4814](https://github.com/EQEmu/Server/pull/4814)) @Akkadius 2025-03-29 + +### Database + +* Fix Respawn Times Table ([#4802](https://github.com/EQEmu/Server/pull/4802)) @Akkadius 2025-03-29 +* Wrap PurgeExpiredInstances in a Transaction ([#4824](https://github.com/EQEmu/Server/pull/4824)) @Akkadius 2025-03-30 + +### Feature + +* Implement /changename & related script bindings. Clean up #set name ([#4770](https://github.com/EQEmu/Server/pull/4770)) @catapultam-habeo 2025-03-20 + +### Fixes + +* AllowFVNoDrop Flag trades ([#4809](https://github.com/EQEmu/Server/pull/4809)) @neckkola 2025-03-27 +* Fix Instance Creation Race Condition ([#4803](https://github.com/EQEmu/Server/pull/4803)) @Akkadius 2025-03-29 +* Fix zone crash when attempting to add a disappearing client to hate list. ([#4782](https://github.com/EQEmu/Server/pull/4782)) @zimp-wow 2025-03-19 +* Globally Reloading Quests when not loaded ([#4813](https://github.com/EQEmu/Server/pull/4813)) @Akkadius 2025-03-29 +* Instance DZ Creation ([#4823](https://github.com/EQEmu/Server/pull/4823)) @Akkadius 2025-03-30 +* Zone State Entity Variable Load Pre-Spawn ([#4785](https://github.com/EQEmu/Server/pull/4785)) @Akkadius 2025-03-19 +* Zone State Position Fix ([#4784](https://github.com/EQEmu/Server/pull/4784)) @Akkadius 2025-03-19 +* Zone State Variables Load First ([#4798](https://github.com/EQEmu/Server/pull/4798)) @Akkadius 2025-03-29 +* Zone state edge case with 0 hp ([#4787](https://github.com/EQEmu/Server/pull/4787)) @Akkadius 2025-03-29 + +### Instance + +* Clear Respawn Timers on Creation ([#4801](https://github.com/EQEmu/Server/pull/4801)) @Akkadius 2025-03-29 + +### Instances + +* Add `expire_at` Column ([#4820](https://github.com/EQEmu/Server/pull/4820)) @Akkadius 2025-03-30 + +### Performance + +* Add several database indexes ([#4811](https://github.com/EQEmu/Server/pull/4811)) @Akkadius 2025-03-29 +* Have World Send Smarter Guild Updates ([#4796](https://github.com/EQEmu/Server/pull/4796)) @Akkadius 2025-03-29 +* Improve Character Select DB Performance ([#4799](https://github.com/EQEmu/Server/pull/4799)) @Akkadius 2025-03-29 +* Reduce Adventure S2S chatter ([#4793](https://github.com/EQEmu/Server/pull/4793)) @Akkadius 2025-03-29 +* Reduce CorpseOwnerOnline S2S Chatter to World ([#4795](https://github.com/EQEmu/Server/pull/4795)) @Akkadius 2025-03-29 +* Reduce LFGuild Chatter ([#4794](https://github.com/EQEmu/Server/pull/4794)) @Akkadius 2025-03-29 +* Reduce UpdateWho S2S Chatter to World ([#4792](https://github.com/EQEmu/Server/pull/4792)) @Akkadius 2025-03-29 +* Send Smarter Emote Packets ([#4818](https://github.com/EQEmu/Server/pull/4818)) @Akkadius 2025-03-30 + +### Quest API + +* Add Support for NPC ID and NPC Name Specificity ([#4781](https://github.com/EQEmu/Server/pull/4781)) @Kinglykrab 2025-03-19 + +### Reload + +* Add Reload for Maps / Navs ([#4816](https://github.com/EQEmu/Server/pull/4816)) @Akkadius 2025-03-29 + +### Zone + +* Zone State Automated Testing and Improvements ([#4808](https://github.com/EQEmu/Server/pull/4808)) @Akkadius 2025-03-30 +* Zone State Improvements Part 3 ([#4773](https://github.com/EQEmu/Server/pull/4773)) @Akkadius 2025-03-13 + +## [23.3.4] 3/14/2025 + +### Fixes + +* 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 + +* Database SetMutex crash fix ([#4741](https://github.com/EQEmu/Server/pull/4741)) @Akkadius 2025-03-03 +* Fix Aura process crash with bots ([#4743](https://github.com/EQEmu/Server/pull/4743)) @Akkadius 2025-03-03 +* Fix crash in add loot code path ([#4745](https://github.com/EQEmu/Server/pull/4745)) @Akkadius 2025-03-03 +* Fix world repop crash ([#4742](https://github.com/EQEmu/Server/pull/4742)) @Akkadius 2025-03-03 +* Potential crash fix in scan close mobs ([#4744](https://github.com/EQEmu/Server/pull/4744)) @Akkadius 2025-03-03 + +### Fixes + +* Cleanup zone buckets on instance purge. ([#4739](https://github.com/EQEmu/Server/pull/4739)) @zimp-wow 2025-03-02 +* Fix an error causing Endurance Regen to not be applied by items. ([#4738](https://github.com/EQEmu/Server/pull/4738)) @catapultam-habeo 2025-03-02 + +### World + +* Check if port in use to avoid double booting mistakes ([#4740](https://github.com/EQEmu/Server/pull/4740)) @Akkadius 2025-03-03 + +## [23.1.0] 3/1/2025 + +### Bots + +* Fix unresponsive bots in groups upon group wipe ([#4712](https://github.com/EQEmu/Server/pull/4712)) @nytmyr 2025-02-28 + +### Code + +* More login <-> world code cleanup ([#4724](https://github.com/EQEmu/Server/pull/4724)) @Akkadius 2025-02-28 + +### Crash + +* Check for directory existence before traversing in CheckForCompatibleQuestPlugins ([#4730](https://github.com/EQEmu/Server/pull/4730)) @Akkadius 2025-03-02 +* Fix filesystem crash / exception in DatabaseDumpService::RemoveCredentialsFile() ([#4731](https://github.com/EQEmu/Server/pull/4731)) @Akkadius 2025-03-01 +* Fix large file size crash in File::GetContents for windows ([#4735](https://github.com/EQEmu/Server/pull/4735)) @Akkadius 2025-03-02 +* Fix reload concurrency crash when ran from Spire ([#4733](https://github.com/EQEmu/Server/pull/4733)) @Akkadius 2025-03-02 +* Validate item in SE_SummonItemIntoBag ([#4734](https://github.com/EQEmu/Server/pull/4734)) @Akkadius 2025-03-02 +* World CLI validation ([#4728](https://github.com/EQEmu/Server/pull/4728)) @Akkadius 2025-03-01 + +### Database + +* Remove force_interactive from big bag updates ([#4727](https://github.com/EQEmu/Server/pull/4727)) @Akkadius 2025-03-01 + +### Feature + +* Add a rule for spells to bypass stacking rules ([#4716](https://github.com/EQEmu/Server/pull/4716)) @catapultam-habeo 2025-02-28 +* Evolving items Additions ([#4725](https://github.com/EQEmu/Server/pull/4725)) @neckkola 2025-03-01 + +### Fixes + +* Add character_pet_name to player tables schema @Akkadius 2025-03-02 +* Add client packets to questmanager:setguild ([#4732](https://github.com/EQEmu/Server/pull/4732)) @neckkola 2025-03-01 +* Clear m_completed_shared_tasks before reloading @Akkadius 2025-02-24 +* Fix AA Reset Error Message ([#4720](https://github.com/EQEmu/Server/pull/4720)) @Kinglykrab 2025-02-28 +* Fix Issue with Suffixes/Prefixes ([#4723](https://github.com/EQEmu/Server/pull/4723)) @Kinglykrab 2025-02-28 +* Fix Trading Items to Bot Pets ([#4721](https://github.com/EQEmu/Server/pull/4721)) @MortimerGreenwald 2025-02-28 +* Refactor ApplyItemBonuses to fix double-counting of ATK and recommended levels not correctly applying ([#4713](https://github.com/EQEmu/Server/pull/4713)) @catapultam-habeo 2025-03-01 + +### Loginserver + +* Minor cleanup ([#4729](https://github.com/EQEmu/Server/pull/4729)) @Akkadius 2025-03-01 + +### Quest API + +* Add Key Ring Methods to Perl and Lua ([#4719](https://github.com/EQEmu/Server/pull/4719)) @Kinglykrab 2025-02-28 +* Implement eq.handin() and quest::handin() ([#4718](https://github.com/EQEmu/Server/pull/4718)) @Akkadius 2025-02-28 + +### Tasks + +* Extend IsTaskCompleted to also be aware of shared task completion ([#4714](https://github.com/EQEmu/Server/pull/4714)) @Akkadius 2025-02-24 + +### Zone + +* Implement Zone State Saving on Shutdown ([#4715](https://github.com/EQEmu/Server/pull/4715)) @Akkadius 2025-02-28 + +### Zone State + +* Wrap all serialization/deserialization in try/catch ([#4726](https://github.com/EQEmu/Server/pull/4726)) @Akkadius 2025-03-01 + +## [23.0.2] 2/21/2025 + +### Bots + +* Add checks to ensure bots and pets do not engage on ^pull ([#4708](https://github.com/EQEmu/Server/pull/4708)) @nytmyr 2025-02-22 +* Improve positioning ([#4709](https://github.com/EQEmu/Server/pull/4709)) @nytmyr 2025-02-22 +* Prevent medding in combat if any mob has bot targeted ([#4707](https://github.com/EQEmu/Server/pull/4707)) @nytmyr 2025-02-22 + +### Client Mod + +* Adds a hacked fast camp rule for GMs ([#4697](https://github.com/EQEmu/Server/pull/4697)) @KimLS 2025-02-20 + +### Fixes + +* Fix Lua Zone ID Exports ([#4700](https://github.com/EQEmu/Server/pull/4700)) @Kinglykrab 2025-02-22 +* Fix bad Mob reference in QuestManager::resumetimer() ([#4710](https://github.com/EQEmu/Server/pull/4710)) @zimp-wow 2025-02-22 +* Fix cursor load on zone ([#4704](https://github.com/EQEmu/Server/pull/4704)) @nytmyr 2025-02-22 +* Fix infinite loop in QuestManager::stoptimer() ([#4703](https://github.com/EQEmu/Server/pull/4703)) @zimp-wow 2025-02-21 + +### Quest API + +* Add GetSpawn() to Perl and Lua ([#4702](https://github.com/EQEmu/Server/pull/4702)) @Kinglykrab 2025-02-22 + +## [23.0.1] 2/20/2025 + +### Fixes + +* Player event ordering merge fix ([#4699](https://github.com/EQEmu/Server/pull/4699)) @Akkadius 2025-02-20 + +### Quest API + +* Add DisableRespawnTimers to Perl and Lua ([#4691](https://github.com/EQEmu/Server/pull/4691)) @Kinglykrab 2025-02-20 + +## [23.0.0] 2/19/2025 + +### Bots + +* Add AEHateLine to HateLine ParentType ([#4678](https://github.com/EQEmu/Server/pull/4678)) @nytmyr 2025-02-15 +* Add IsInRaidOrGroup checks to ^attack and ^pull ([#4654](https://github.com/EQEmu/Server/pull/4654)) @nytmyr 2025-02-07 +* Add missing stance options ([#4681](https://github.com/EQEmu/Server/pull/4681)) @nytmyr 2025-02-15 +* Bot Overhaul ([#4580](https://github.com/EQEmu/Server/pull/4580)) @nytmyr 2025-02-03 +* Command Cleanup ([#4676](https://github.com/EQEmu/Server/pull/4676)) @nytmyr 2025-02-15 +* Correct camp count on ^camp ([#4650](https://github.com/EQEmu/Server/pull/4650)) @nytmyr 2025-02-06 +* Correct helper message for forced casts ([#4656](https://github.com/EQEmu/Server/pull/4656)) @nytmyr 2025-02-07 +* Crash fixes related to GetNumberNeedingHealedInGroup ([#4684](https://github.com/EQEmu/Server/pull/4684)) @nytmyr 2025-02-15 +* Fix AE range calculation ([#4683](https://github.com/EQEmu/Server/pull/4683)) @nytmyr 2025-02-15 +* Fix Bards not casting ([#4638](https://github.com/EQEmu/Server/pull/4638)) @nytmyr 2025-02-03 +* Fix a couple potential crashes with GetNumberNeedingHealedInGroup ([#4652](https://github.com/EQEmu/Server/pull/4652)) @nytmyr 2025-02-07 +* Fix crash related to GetTempSpellType() ([#4649](https://github.com/EQEmu/Server/pull/4649)) @nytmyr 2025-02-06 +* Fix pets causing aggro ([#4677](https://github.com/EQEmu/Server/pull/4677)) @nytmyr 2025-02-15 +* Fix spell priority commands ([#4660](https://github.com/EQEmu/Server/pull/4660)) @nytmyr 2025-02-08 +* Fix typo in positioning ([#4659](https://github.com/EQEmu/Server/pull/4659)) @nytmyr 2025-02-08 +* Move BotGetSpellsByType to cache ([#4655](https://github.com/EQEmu/Server/pull/4655)) @nytmyr 2025-02-07 +* Prevents casting on ineligible targets due to target type, stacking, etc. ([#4680](https://github.com/EQEmu/Server/pull/4680)) @nytmyr 2025-02-15 +* Sanity checks for spell type updates ([#4641](https://github.com/EQEmu/Server/pull/4641)) @nytmyr 2025-02-05 + +### Bug + +* Item Purchase Offset when multiple buyers are buying at the same time. ([#4628](https://github.com/EQEmu/Server/pull/4628)) @fryguy503 2025-02-06 + +### CI + +* Fix database race condition ([#4646](https://github.com/EQEmu/Server/pull/4646)) @Akkadius 2025-02-06 + +### Client Mod + +* Adds a hacked fast camp rule for GMs ([#4697](https://github.com/EQEmu/Server/pull/4697)) @KimLS 2025-02-20 + +### Code + +* Bot RaidGroupSay ([#4653](https://github.com/EQEmu/Server/pull/4653)) @nytmyr 2025-02-07 +* Cleanup logic in cursor bag check ([#4642](https://github.com/EQEmu/Server/pull/4642)) @nytmyr 2025-02-04 +* Use Repositories for Titles ([#4608](https://github.com/EQEmu/Server/pull/4608)) @Kinglykrab 2025-02-07 + +### Commands + +* Fix #goto not accepting proper heading ([#4685](https://github.com/EQEmu/Server/pull/4685)) @nytmyr 2025-02-15 +* Fix Illusion Block ([#4666](https://github.com/EQEmu/Server/pull/4666)) @nytmyr 2025-02-12 + +### Crash + +* Fix raid/group crash regression ([#4671](https://github.com/EQEmu/Server/pull/4671)) @Akkadius 2025-02-12 +* Fix zone crash caused by NPC::MoveTo ([#4639](https://github.com/EQEmu/Server/pull/4639)) @catapultam-habeo 2025-02-03 + +### Databuckets + +* Add Zone Scoped Databuckets ([#4690](https://github.com/EQEmu/Server/pull/4690)) @Akkadius 2025-02-18 + +### Expeditions + +* Move expedition code into DynamicZone ([#4672](https://github.com/EQEmu/Server/pull/4672)) @hgtw 2025-02-16 + +### Feature + +* Add Support for Tradeskill Recipe Inspect ([#4648](https://github.com/EQEmu/Server/pull/4648)) @Kinglykrab 2025-02-06 +* Add rule to allow /changepetname to function without being enabled by scripts. @catapultam-habeo 2025-02-05 +* GuildBank Updates ([#4674](https://github.com/EQEmu/Server/pull/4674)) @neckkola 2025-02-15 +* Implement Big Bags ([#4606](https://github.com/EQEmu/Server/pull/4606)) @Kinglykrab 2025-02-03 + +### Fixes + +* #rq and #reload quest alias ([#4694](https://github.com/EQEmu/Server/pull/4694)) @Akkadius 2025-02-18 +* Always spawn zone controller first ([#4669](https://github.com/EQEmu/Server/pull/4669)) @Akkadius 2025-02-12 +* Big Bag Cleanup ([#4643](https://github.com/EQEmu/Server/pull/4643)) @fryguy503 2025-02-05 +* Big Bag additional fixes ([#4644](https://github.com/EQEmu/Server/pull/4644)) @fryguy503 2025-02-05 +* Change logging level for no items found in a bazaar search to reduce spam logs. ([#4675](https://github.com/EQEmu/Server/pull/4675)) @neckkola 2025-02-13 +* Find Zone - Expansion Settings ([#4692](https://github.com/EQEmu/Server/pull/4692)) @MortimerGreenwald 2025-02-18 +* Fix Beastlord Warder Size Modifier ([#4665](https://github.com/EQEmu/Server/pull/4665)) @Kinglykrab 2025-02-12 +* Fix CI since hand-ins are merged @Akkadius 2025-02-03 +* Fix Illusion Fade Texture Bug ([#4673](https://github.com/EQEmu/Server/pull/4673)) @Kinglykrab 2025-02-14 +* Fix Item Discovery ([#4663](https://github.com/EQEmu/Server/pull/4663)) @Kinglykrab 2025-02-10 +* Fix ST_GroupNoPets and ST_GroupClientAndPet ([#4667](https://github.com/EQEmu/Server/pull/4667)) @nytmyr 2025-02-10 +* Fix SendStatsWindow Mod2 Value Display ([#4658](https://github.com/EQEmu/Server/pull/4658)) @Kinglykrab 2025-02-07 +* Fix Tradeskill Queries ([#4661](https://github.com/EQEmu/Server/pull/4661)) @Kinglykrab 2025-02-09 +* Fix error in update manifest ([#4637](https://github.com/EQEmu/Server/pull/4637)) @nytmyr 2025-02-03 +* Fix issue with getting an unset nested databucket ([#4693](https://github.com/EQEmu/Server/pull/4693)) @Akkadius 2025-02-19 +* Fix non-error in player_event_logs ([#4695](https://github.com/EQEmu/Server/pull/4695)) @Akkadius 2025-02-18 +* GMMove Update Edge Case With Clients ([#4686](https://github.com/EQEmu/Server/pull/4686)) @Akkadius 2025-02-15 +* Item Handins to Pets ([#4687](https://github.com/EQEmu/Server/pull/4687)) @Akkadius 2025-02-15 +* Parcel Delivery Updates ([#4688](https://github.com/EQEmu/Server/pull/4688)) @neckkola 2025-02-16 +* Prevent zone from loading ETL ID's on bootup ([#4696](https://github.com/EQEmu/Server/pull/4696)) @Akkadius 2025-02-18 +* Update pre big bag corpse slot_id's to support big bags ([#4679](https://github.com/EQEmu/Server/pull/4679)) @nytmyr 2025-02-15 + +### Inventory + +* Fix cursor bag saving to invalid slot_ids ([#4640](https://github.com/EQEmu/Server/pull/4640)) @nytmyr 2025-02-04 + +### Items + +* Overhaul Item Hand-in System ([#4593](https://github.com/EQEmu/Server/pull/4593)) @Akkadius 2025-02-03 + +### Loginserver + +* Fix iterator crash ([#4670](https://github.com/EQEmu/Server/pull/4670)) @Akkadius 2025-02-12 +* Modernize codebase ([#4647](https://github.com/EQEmu/Server/pull/4647)) @Akkadius 2025-02-06 + +### NPC Handins + +* Fix MultiQuest Handins ([#4651](https://github.com/EQEmu/Server/pull/4651)) @Akkadius 2025-02-07 + +### Performance + +* Server Reload Overhaul ([#4689](https://github.com/EQEmu/Server/pull/4689)) @Akkadius 2025-02-18 + +### Player Event Logs + +* Migrate and Deprecate QS Legacy Logging ([#4542](https://github.com/EQEmu/Server/pull/4542)) @neckkola 2025-02-05 + +### Quest API + +* Add Bandolier Methods ([#4635](https://github.com/EQEmu/Server/pull/4635)) @Kinglykrab 2025-02-03 +* Add Potion Belt Methods ([#4634](https://github.com/EQEmu/Server/pull/4634)) @Kinglykrab 2025-02-04 +* Add Zone Support to Perl and Lua ([#4662](https://github.com/EQEmu/Server/pull/4662)) @Kinglykrab 2025-02-09 + +### Spells + +* Add all types to checks for max_targets_allowed rule for AEs ([#4682](https://github.com/EQEmu/Server/pull/4682)) @nytmyr 2025-02-15 + +## [22.62.2] 2/1/2025 + +### Fixes + +* Add price change check to the Bazaar Search Window purchase mechanics ([#4632](https://github.com/EQEmu/Server/pull/4632)) @neckkola 2025-02-01 +* NewBazaar Search Consumables ([#4631](https://github.com/EQEmu/Server/pull/4631)) @neckkola 2025-02-01 +* Update the shard bazaar search feature ([#4630](https://github.com/EQEmu/Server/pull/4630)) @neckkola 2025-02-01 + +### Memory Leak + +* Revert " Change raw pointer to unique_ptr to avoid potential leak in dbg stream " ([#4616](https://github.com/EQEmu/Server/pull/4616)) @Akkadius 2025-01-27 + +### Performance + +* Significantly Improve Client Network Resends ([#4629](https://github.com/EQEmu/Server/pull/4629)) @Akkadius 2025-02-01 + +## [22.62.1] 1/27/2025 + +### Memory Leak + +* Revert "Change raw pointer to unique_ptr to avoid potential leak in dbg stream" ([#4616](https://github.com/EQEmu/Server/pull/4616)) @Akkadius 2025-01-27 + +### Performance + +* Re-use ClientUpdate packet memory ([#4619](https://github.com/EQEmu/Server/pull/4619)) @Akkadius 2025-01-27 +* Re-use OP_Animation packet ([#4621](https://github.com/EQEmu/Server/pull/4621)) @Akkadius 2025-01-27 +* Re-use OP_Damage packet memory ([#4625](https://github.com/EQEmu/Server/pull/4625)) @Akkadius 2025-01-27 +* Re-use OP_HPUpdate packet memory ([#4622](https://github.com/EQEmu/Server/pull/4622)) @Akkadius 2025-01-27 +* Re-use OP_PlayerStateAdd packet memory ([#4626](https://github.com/EQEmu/Server/pull/4626)) @Akkadius 2025-01-27 +* Re-use OP_SendFindableNPCs packet memory ([#4623](https://github.com/EQEmu/Server/pull/4623)) @Akkadius 2025-01-27 + +### Repop + +* Make #repop instant ([#4620](https://github.com/EQEmu/Server/pull/4620)) @Akkadius 2025-01-27 + +## [22.62.0] 1/26/2025 + +### Bazaar + +* Improve Bazaar Search Performance ([#4615](https://github.com/EQEmu/Server/pull/4615)) @neckkola 2025-01-27 + +### CLI + +* Add --skip-backup to world database:updates ([#4605](https://github.com/EQEmu/Server/pull/4605)) @Akkadius 2025-01-22 + +### Database + +* Change npc_types walkspeed to be of type float ([#4589](https://github.com/EQEmu/Server/pull/4589)) @Akkadius 2025-01-07 + +### Databuckets + +* Add Account Scoped Databuckets ([#4603](https://github.com/EQEmu/Server/pull/4603)) @Akkadius 2025-01-21 +* Implement Nested Databuckets ([#4604](https://github.com/EQEmu/Server/pull/4604)) @Akkadius 2025-01-27 + +### Feature + +* Add Alternate Bazaar Search Approach ([#4600](https://github.com/EQEmu/Server/pull/4600)) @neckkola 2025-01-20 +* Add Support for Item Previews ([#4599](https://github.com/EQEmu/Server/pull/4599)) @Kinglykrab 2025-01-20 +* Evolving Item Support for RoF2 ([#4496](https://github.com/EQEmu/Server/pull/4496)) @neckkola 2025-01-20 +* Implement Custom Pet Names ([#4594](https://github.com/EQEmu/Server/pull/4594)) @catapultam-habeo 2025-01-22 + +### Fixes + +* Add Bazaar BulkSendTrader Limit for RoF2 ([#4590](https://github.com/EQEmu/Server/pull/4590)) @neckkola 2025-01-08 +* CLI help menu from parsing correctly in World @Akkadius 2025-01-22 +* Delete later in RemoveItem second case @Akkadius 2025-01-25 +* Fix query error in character_evolving_items @Akkadius 2025-01-21 +* Repair a memory leak in #summonitem ([#4591](https://github.com/EQEmu/Server/pull/4591)) @neckkola 2025-01-08 +* Repair an incorrect safe_delete call memory leak. ([#4588](https://github.com/EQEmu/Server/pull/4588)) @neckkola 2025-01-07 +* Repair levers opening the Evolving XP Transfer Window ([#4607](https://github.com/EQEmu/Server/pull/4607)) @neckkola 2025-01-23 +* Update a few Bazaar RoF2 routines for memory leaks ([#4592](https://github.com/EQEmu/Server/pull/4592)) @neckkola 2025-01-08 +* Update database version to match manifest @Akkadius 2025-01-21 +* Update trader add/remove packets to limits for RoF2 ([#4595](https://github.com/EQEmu/Server/pull/4595)) @neckkola 2025-01-19 + +### Linux + +* Implement KSM Kernel Samepage Merging with Maps ([#4601](https://github.com/EQEmu/Server/pull/4601)) @Akkadius 2025-01-21 + +### Memory Leak + +* Change raw pointer to unique_ptr to avoid potential leak in dbg stream ([#4616](https://github.com/EQEmu/Server/pull/4616)) @KimLS 2025-01-27 +* Fix leak in BuyTraderItemOutsideBazaar ([#4609](https://github.com/EQEmu/Server/pull/4609)) @Akkadius 2025-01-24 +* Fix leak in Client::RemoveDuplicateLore ([#4614](https://github.com/EQEmu/Server/pull/4614)) @Akkadius 2025-01-24 +* Fix leak in NPC::RemoveItem ([#4611](https://github.com/EQEmu/Server/pull/4611)) @Akkadius 2025-01-24 +* Fix leak in QuestManager::varlink ([#4610](https://github.com/EQEmu/Server/pull/4610)) @Akkadius 2025-01-24 +* Fix leaks in Client::Handle_OP_AugmentItem ([#4612](https://github.com/EQEmu/Server/pull/4612)) @Akkadius 2025-01-24 +* Fix memory leak in Client::Handle_OP_MoveMultipleItems ([#4613](https://github.com/EQEmu/Server/pull/4613)) @Akkadius 2025-01-24 + +### Performance + +* Client / NPC Position Update Optimizations ([#4602](https://github.com/EQEmu/Server/pull/4602)) @Akkadius 2025-01-21 + +### Quest API + +* Add SetAAEXPPercentage to Perl/Lua ([#4597](https://github.com/EQEmu/Server/pull/4597)) @Kinglykrab 2025-01-19 + +### Zone + +* Implement zone player count sharding ([#4536](https://github.com/EQEmu/Server/pull/4536)) @Akkadius 2025-01-08 + +## [22.61.0] 1/6/2025 + +### Bots + +* Fix AA ranks to account for level ([#4567](https://github.com/EQEmu/Server/pull/4567)) @nytmyr 2024-12-07 + +### Code + +* Convert Event Parses to Single Line ([#4569](https://github.com/EQEmu/Server/pull/4569)) @Kinglykrab 2024-12-12 +* Fix GM Flag Spell Restriction Bypasses ([#4571](https://github.com/EQEmu/Server/pull/4571)) @Kinglykrab 2025-01-06 +* Remove Unused Group Methods ([#4559](https://github.com/EQEmu/Server/pull/4559)) @Kinglykrab 2024-12-12 + +### Commands + +* Add #find bot Subcommand ([#4563](https://github.com/EQEmu/Server/pull/4563)) @Kinglykrab 2024-12-12 +* Add #find ldon_theme Subcommand ([#4564](https://github.com/EQEmu/Server/pull/4564)) @Kinglykrab 2024-12-12 +* Fix #copycharacter ([#4582](https://github.com/EQEmu/Server/pull/4582)) @Akkadius 2025-01-06 + +### Databuckets + +* Improved Reliability and Performance of Databuckets ([#4562](https://github.com/EQEmu/Server/pull/4562)) @Akkadius 2024-12-12 + +### Feature + +* Enable bazaar window 'Find Trader' functionality ([#4560](https://github.com/EQEmu/Server/pull/4560)) @neckkola 2024-12-12 + +### Filesystem + +* Path Manager Improvements ([#4557](https://github.com/EQEmu/Server/pull/4557)) @Akkadius 2025-01-06 + +### Fixes + +* Allow Items in ROF2 to Stack to 32,767 ([#4556](https://github.com/EQEmu/Server/pull/4556)) @Kinglykrab 2024-12-12 +* Fix EVENT_COMBAT on NPC Death ([#4558](https://github.com/EQEmu/Server/pull/4558)) @Kinglykrab 2024-11-28 +* Guild Membership Update Fix ([#4581](https://github.com/EQEmu/Server/pull/4581)) @neckkola 2025-01-06 +* Guild creation to propagate across zones ([#4575](https://github.com/EQEmu/Server/pull/4575)) @neckkola 2025-01-06 +* Repair a EQEMUConfig Memory Leak ([#4584](https://github.com/EQEmu/Server/pull/4584)) @neckkola 2025-01-06 +* Repair a LoadNPCEmote MemoryLeak ([#4586](https://github.com/EQEmu/Server/pull/4586)) @neckkola 2025-01-06 +* Repair a memory leak in GuildsList ([#4585](https://github.com/EQEmu/Server/pull/4585)) @neckkola 2025-01-06 +* Resolve a client crash when logging in or zoning ([#4572](https://github.com/EQEmu/Server/pull/4572)) @neckkola 2024-12-14 + +### Groups + +* Fix AmIMainAssist incorrectly checking for MainTankName ([#4565](https://github.com/EQEmu/Server/pull/4565)) @nytmyr 2024-12-04 + +### Inventory + +* Add GetInventorySlots() Method ([#4566](https://github.com/EQEmu/Server/pull/4566)) @Kinglykrab 2025-01-06 + +### Logs + +* Improve Crash log defaults ([#4579](https://github.com/EQEmu/Server/pull/4579)) @Akkadius 2025-01-06 + +### Maps + +* Fix broken Map MMFS implementation ([#4576](https://github.com/EQEmu/Server/pull/4576)) @Akkadius 2025-01-06 + +### Network + +* Prune / disconnect TCP connections gracefully ([#4574](https://github.com/EQEmu/Server/pull/4574)) @Akkadius 2025-01-06 + +### Rules + +* Add rules for requiring custom files from client ([#4561](https://github.com/EQEmu/Server/pull/4561)) @knervous 2024-12-12 + ## [22.60.0] 11/25/2024 ### Bazaar diff --git a/CMakeLists.txt b/CMakeLists.txt index 83dacc20f..59279caa8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,8 +37,12 @@ IF(EQEMU_ADD_PROFILER) SET(CMAKE_EXE_LINKER_FLAGS "-Wl,--no-as-needed,-lprofiler,--as-needed") ENDIF(EQEMU_ADD_PROFILER) +IF(USE_MAP_MMFS) + ADD_DEFINITIONS(-DUSE_MAP_MMFS) +ENDIF (USE_MAP_MMFS) IF(MSVC) + add_compile_options(/bigobj) ADD_DEFINITIONS(-D_CRT_SECURE_NO_WARNINGS) ADD_DEFINITIONS(-DNOMINMAX) ADD_DEFINITIONS(-DCRASH_LOGGING) @@ -359,6 +363,8 @@ MESSAGE(STATUS "**************************************************") #setup server libs and headers SET(SERVER_LIBS common ${DATABASE_LIBRARY_LIBS} ${ZLIB_LIBRARY_LIBS} ${Boost_LIBRARIES} uv_a fmt RecastNavigation::Detour) +set(FMT_HEADER_ONLY OFF) + INCLUDE_DIRECTORIES(SYSTEM "${DATABASE_LIBRARY_INCLUDE}") INCLUDE_DIRECTORIES(SYSTEM "${ZLIB_LIBRARY_INCLUDE}") INCLUDE_DIRECTORIES(SYSTEM "${Boost_INCLUDE_DIRS}") diff --git a/README.md b/README.md index 59e525af6..033fcccab 100644 --- a/README.md +++ b/README.md @@ -1,83 +1,150 @@ -# EQEmulator Core Server -| Drone (Linux x64) | Drone (Windows x64) | -|:---:|:---:| -|[![Build Status](http://drone.akkadius.com/api/badges/EQEmu/Server/status.svg)](http://drone.akkadius.com/EQEmu/Server) |[![Build Status](http://drone.akkadius.com/api/badges/EQEmu/Server/status.svg)](http://drone.akkadius.com/EQEmu/Server) | +

EQEmulator Server Platform

+ +

+ +

+ +

+EQEmulator - A Fan-Made Project Honoring a Legendary MMORPG +

+ +

+Contributors +Discord +Docs +License +Latest Release +Release Date +Github All Releases +Build Status +GitHub Issues or Pull Requests +Docker Pulls +Build Status Official + +

*** -**EQEmulator is a custom completely from-scratch open source server implementation for EverQuest built mostly on C++** - * MySQL/MariaDB is used as the database engine (over 200+ tables) - * Perl and LUA are both supported scripting languages for NPC/Player/Quest oriented events - * Open source database (Project EQ) has content up to expansion OoW (included in server installs) - * Game server environments and databases can be heavily customized to create all new experiences - * Hundreds of Quests/events created and maintained by Project EQ +

+EQEmulator is a passion-driven, open source server emulator project dedicated to preserving and celebrating the legacy of a groundbreaking classic MMORPG. +

-## Server Installs -| |Windows|Linux| -|:---:|:---:|:---:| -|**Install Count**|![Windows Install Count](http://analytics.akkadius.com/?install_count&windows_count)|![Linux Install Count](http://analytics.akkadius.com/?install_count&linux_count)| -### > Windows +

+For over two decades and continuing, EQEmulator has served as a fan tribute, providing tools and technology that allow players to explore, customize, and experience the legendary game's iconic gameplay in new ways. This project exists solely out of deep admiration for the original developers, artists, designers, and visionaries who created one of the most influential online worlds of all time. +

-* [Install Guide](https://docs.eqemu.io/server/installation/server-installation-windows/) +

+We do not claim ownership of the original game or its assets. All credit and respect belong to the original creators and Daybreak Game Company, whose work continues to inspire generations of players and developers alike. +

-### > Debian/Ubuntu/CentOS/Fedora +

+EQEmulator has for over 20 years and always will be a fan-based, non-commercial open-source effort made by players, for players—preserving the legacy of the game while empowering community-driven creativity, learning and joy that the game and its creators has so strongly inspired in us all. +

-* [Install Guide](https://docs.eqemu.io/server/installation/server-installation-linux/) +*** -* You can use curl or wget to kick off the installer (whichever your OS has) -> curl -O https://raw.githubusercontent.com/EQEmu/Server/master/utils/scripts/linux_installer/install.sh install.sh && chmod 755 install.sh && ./install.sh +

+ Technical Overview & Reverse Engineering Effort +

-> wget --no-check-certificate https://raw.githubusercontent.com/EQEmu/Server/master/utils/scripts/linux_installer/install.sh -O install.sh && chmod 755 install.sh && ./install.sh +

EQEmulator represents over two decades of collaborative reverse engineering, building the server from the ground up without access to the original source code. This effort was achieved entirely through community-driven analysis, network protocol decoding, and in-game behavioral research.

-## Supported Clients +

+ 💡 How We Did It +

+ +

+ +

+ + +

+ Reverse Engineering +Every system, packet, opcode, and game mechanic has been reconstructed through countless hours of live packet sniffing, client disassembly, and in-game experimentation by dedicated contributors over the years. +

+ +

+ No proprietary code or server sources were ever used. +

+ +

+ All implementations are the result of clean-room engineering. +

+ + +

+ 🛠️ Technology Stack +

+ +

+ +

+ +**C++ Core Engine** + +* High-performance networking and gameplay logic built in C++ +* Cross-platform support for Linux and Windows + +**MySQL / MariaDB Backend** + +* Fully structured schema with over 200+ tables +* Supports content customization, expansions, and custom worlds + +**Scripting Engine** + +* Native support for **Perl** and **Lua** scripting +* Powerfully extendable for quests, NPC behaviors, and custom events + +**Open Source Content Database** + +* Includes ProjectEQ’s world data up through *Dragons of Norrath* +* 100% customizable to create entirely new game worlds + +

+ 🚀 Why It Matters +

+ +

🧬 EQEmulator stands as a technical preservation project, ensuring that the magic of classic and custom servers lives on for future generations of players, tinkerers, and game designers. +

+ +> We humbly acknowledge and thank the original developers for creating one of the most influential online experiences in gaming history. + +

+ 🧑‍💻🖥️ Supported Clients +

|Titanium Edition|Secrets of Faydwer|Seeds of Destruction|Underfoot|Rain of Fear| |:---:|:---:|:---:|:---:|:---:| |||||| -## Bug Reports -* Please use the [issue tracker](https://github.com/EQEmu/Server/issues) provided by GitHub to send us bug -reports or feature requests. -* The [EQEmu Forums](http://www.eqemulator.org/forums/) are also a place to submit and get help with bugs. +## 📚 Resources -## Contributions +| Resource | Badges | Link | +|---------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------| +| **EQEmulator Docs** | [![Docs](https://img.shields.io/badge/docs-MkDocs%20Powered-blueviolet)](https://docs.eqemu.io) | [docs.eqemu.io](https://docs.eqemu.io/) | +| **Discord Community**| [![Discord](https://img.shields.io/discord/212663220849213441?label=Discord&logo=discord&color=7289DA)](https://discord.gg/QHsm7CD) | [Join Discord](https://discord.gg/QHsm7CD) | +| **Latest Release** | [![Latest Release](https://img.shields.io/github/v/release/eqemu/server)](https://github.com/eqemu/server/releases)
[![Release Date](https://img.shields.io/github/release-date/EQEmu/Server)](https://github.com/EQEmu/Server/releases)
[![All Releases](https://img.shields.io/github/downloads/eqemu/server/total.svg)](https://github.com/eqemu/server/releases) | [View Releases](https://github.com/eqemu/server/releases) | +| **License** | [![License](https://img.shields.io/github/license/EQEmu/Server)](./LICENSE) | [View License](./LICENSE) | +| **Build Status** | [![Build Status](http://drone.akkadius.com/api/badges/EQEmu/Server/status.svg)](http://drone.akkadius.com/EQEmu/Server) | [View Build Status](http://drone.akkadius.com/EQEmu/Server) | +| **Docker Pulls** | [![Docker Pulls](https://img.shields.io/docker/pulls/akkadius/eqemu-server)](https://hub.docker.com/r/akkadius/eqemu-server) | [Docker Hub](https://hub.docker.com/r/akkadius/eqemu-server) | +| **Contributions** | [![GitHub PRs](https://img.shields.io/github/issues-pr-closed/eqemu/server)](https://github.com/eqemu/server/pulls?q=is%3Apr+is%3Aclosed) | [Closed PRs & Issues](https://github.com/eqemu/server/pulls?q=is%3Apr+is%3Aclosed) | -* The preferred way to contribute is to fork the repo and submit a pull request on -GitHub. If you need help with your changes, you can always post on the forums or -try Discord. You can also post unified diffs (`git diff` should do the trick) on the -[Server Code Submissions](http://www.eqemulator.org/forums/forumdisplay.php?f=669) -forum, although pull requests will be much quicker and easier on all parties. +## 🛠️ Getting Started -## Contact +If you want to set up your own EQEmulator server, please refer to the current [server installation guides](https://docs.eqemu.io/#server-installation). We've had 100,000s of players and developers use our guides to set up their own servers, and we hope you will too! - - Discord Channel: https://discord.gg/QHsm7CD - - **User Discord Channel**: `#general` - - **Developer Discord Channel**: `#eqemucoders` +## 🗂️ Related Repositories -## Resources -- [EQEmulator Forums](http://www.eqemulator.org/forums) -- [EQEmulator Wiki](https://docs.eqemu.io/) +| Repository | Description | +|--------------------|----------------------------------------------------------------------------------| +| [ProjectEQ Quests](https://github.com/ProjectEQ/projecteqquests) | Official quests and event scripts for ProjectEQ | +| [Maps](https://github.com/Akkadius/EQEmuMaps) | EQEmu-compatible zone maps | +| [Installer Resources](https://github.com/Akkadius/EQEmuInstall) | Scripts and assets for setting up EQEmu servers | +| [Zone Utilities](https://github.com/EQEmu/zone-utilities) | Utilities for parsing, rendering, and manipulating EQ zone files | -## Related Repositories -* [ProjectEQ Quests](https://github.com/ProjectEQ/projecteqquests) -* [Maps](https://github.com/Akkadius/EQEmuMaps) -* [Installer Resources](https://github.com/Akkadius/EQEmuInstall) -* [Zone Utilities](https://github.com/EQEmu/zone-utilities) - Various utilities and libraries for parsing, rendering and manipulating EQ Zone files. - -## Other License Info - -* The server code and utilities are released under **GPLv3** -* We also include some small libraries for convienence that may be under different licensing - * SocketLib - GPL LibXML - * zlib - zlib license - * MariaDB/MySQL - GPL - * GPL Perl - GPL / ActiveState (under the assumption that this is a free project) - * CPPUnit - GLP StringUtilities - Apache - * LUA - MIT ## Contributors - diff --git a/client_files/export/main.cpp b/client_files/export/main.cpp index e90e6c76c..8d0a27a3c 100644 --- a/client_files/export/main.cpp +++ b/client_files/export/main.cpp @@ -36,12 +36,7 @@ #include "../../common/file.h" #include "../../common/events/player_event_logs.h" #include "../../common/skill_caps.h" - -EQEmuLogSys LogSys; -WorldContentService content_service; -ZoneStore zone_store; -PathManager path; -PlayerEventLogs player_event_logs; +#include "../../common/evolving_items.h" void ExportSpells(SharedDatabase *db); void ExportSkillCaps(SharedDatabase *db); @@ -51,10 +46,10 @@ void ExportDBStrings(SharedDatabase *db); int main(int argc, char **argv) { RegisterExecutablePlatform(ExePlatformClientExport); - LogSys.LoadLogSettingsDefaults(); + EQEmuLogSys::Instance()->LoadLogSettingsDefaults(); set_exception_handler(); - path.LoadPaths(); + PathManager::Instance()->Init(); LogInfo("Client Files Export Utility"); if (!EQEmuConfig::LoadConfig()) { @@ -97,8 +92,8 @@ int main(int argc, char **argv) content_db.SetMySQL(database); } - LogSys.SetDatabase(&database) - ->SetLogPath(path.GetLogPath()) + EQEmuLogSys::Instance()->SetDatabase(&database) + ->SetLogPath(PathManager::Instance()->GetLogPath()) ->LoadLogDatabaseSettings() ->StartFileLogs(); @@ -127,14 +122,14 @@ int main(int argc, char **argv) ExportBaseData(&content_db); ExportDBStrings(&database); - LogSys.CloseFileLogs(); + EQEmuLogSys::Instance()->CloseFileLogs(); return 0; } void ExportSpells(SharedDatabase* db) { - std::ofstream file(fmt::format("{}/export/spells_us.txt", path.GetServerPath())); + std::ofstream file(fmt::format("{}/export/spells_us.txt", PathManager::Instance()->GetServerPath())); if (!file || !file.is_open()) { LogError("Unable to open export/spells_us.txt to write, skipping."); return; @@ -153,7 +148,7 @@ void ExportSpells(SharedDatabase* db) void ExportSkillCaps(SharedDatabase* db) { - std::ofstream file(fmt::format("{}/export/SkillCaps.txt", path.GetServerPath())); + std::ofstream file(fmt::format("{}/export/SkillCaps.txt", PathManager::Instance()->GetServerPath())); if (!file || !file.is_open()) { LogError("Unable to open export/SkillCaps.txt to write, skipping."); return; @@ -172,7 +167,7 @@ void ExportSkillCaps(SharedDatabase* db) void ExportBaseData(SharedDatabase *db) { - std::ofstream file(fmt::format("{}/export/BaseData.txt", path.GetServerPath())); + std::ofstream file(fmt::format("{}/export/BaseData.txt", PathManager::Instance()->GetServerPath())); if (!file || !file.is_open()) { LogError("Unable to open export/BaseData.txt to write, skipping."); return; @@ -191,7 +186,7 @@ void ExportBaseData(SharedDatabase *db) void ExportDBStrings(SharedDatabase *db) { - std::ofstream file(fmt::format("{}/export/dbstr_us.txt", path.GetServerPath())); + std::ofstream file(fmt::format("{}/export/dbstr_us.txt", PathManager::Instance()->GetServerPath())); if (!file || !file.is_open()) { LogError("Unable to open export/dbstr_us.txt to write, skipping."); return; diff --git a/client_files/import/main.cpp b/client_files/import/main.cpp index 0d8010221..b7be706d7 100644 --- a/client_files/import/main.cpp +++ b/client_files/import/main.cpp @@ -30,12 +30,7 @@ #include "../../common/repositories/base_data_repository.h" #include "../../common/file.h" #include "../../common/events/player_event_logs.h" - -EQEmuLogSys LogSys; -WorldContentService content_service; -ZoneStore zone_store; -PathManager path; -PlayerEventLogs player_event_logs; +#include "../../common/evolving_items.h" void ImportSpells(SharedDatabase *db); void ImportSkillCaps(SharedDatabase *db); @@ -44,10 +39,10 @@ void ImportDBStrings(SharedDatabase *db); int main(int argc, char **argv) { RegisterExecutablePlatform(ExePlatformClientImport); - LogSys.LoadLogSettingsDefaults(); + EQEmuLogSys::Instance()->LoadLogSettingsDefaults(); set_exception_handler(); - path.LoadPaths(); + PathManager::Instance()->Init(); LogInfo("Client Files Import Utility"); if(!EQEmuConfig::LoadConfig()) { @@ -90,8 +85,8 @@ int main(int argc, char **argv) { content_db.SetMySQL(database); } - LogSys.SetDatabase(&database) - ->SetLogPath(path.GetLogPath()) + EQEmuLogSys::Instance()->SetDatabase(&database) + ->SetLogPath(PathManager::Instance()->GetLogPath()) ->LoadLogDatabaseSettings() ->StartFileLogs(); @@ -100,7 +95,7 @@ int main(int argc, char **argv) { ImportBaseData(&content_db); ImportDBStrings(&database); - LogSys.CloseFileLogs(); + EQEmuLogSys::Instance()->CloseFileLogs(); return 0; } @@ -136,7 +131,7 @@ bool IsStringField(int i) { void ImportSpells(SharedDatabase *db) { LogInfo("Importing Spells"); - std::string file = fmt::format("{}/import/spells_us.txt", path.GetServerPath()); + std::string file = fmt::format("{}/import/spells_us.txt", PathManager::Instance()->GetServerPath()); FILE *f = fopen(file.c_str(), "r"); if(!f) { LogError("Unable to open {} to read, skipping.", file); @@ -226,7 +221,7 @@ void ImportSpells(SharedDatabase *db) { void ImportSkillCaps(SharedDatabase *db) { LogInfo("Importing Skill Caps"); - std::string file = fmt::format("{}/import/SkillCaps.txt", path.GetServerPath()); + std::string file = fmt::format("{}/import/SkillCaps.txt", PathManager::Instance()->GetServerPath()); FILE *f = fopen(file.c_str(), "r"); if(!f) { LogError("Unable to open {} to read, skipping.", file); @@ -263,7 +258,7 @@ void ImportBaseData(SharedDatabase *db) { LogInfo("Importing Base Data"); - const std::string& file_name = fmt::format("{}/import/BaseData.txt", path.GetServerPath()); + const std::string& file_name = fmt::format("{}/import/BaseData.txt", PathManager::Instance()->GetServerPath()); const auto& file_contents = File::GetContents(file_name); if (!file_contents.error.empty()) { @@ -303,7 +298,7 @@ void ImportBaseData(SharedDatabase *db) void ImportDBStrings(SharedDatabase *db) { LogInfo("Importing DB Strings"); - std::string file = fmt::format("{}/import/dbstr_us.txt", path.GetServerPath()); + std::string file = fmt::format("{}/import/dbstr_us.txt", PathManager::Instance()->GetServerPath()); FILE *f = fopen(file.c_str(), "r"); if(!f) { LogError("Unable to open {} to read, skipping.", file); diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 0a1766fc8..1a9028eb7 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -17,11 +17,13 @@ SET(common_sources database.cpp database_instances.cpp database/database_update_manifest.cpp + database/database_update_manifest_custom.cpp database/database_update_manifest_bots.cpp database/database_update.cpp dbcore.cpp deity.cpp dynamic_zone_base.cpp + dynamic_zone_lockout.cpp emu_constants.cpp emu_limits.cpp emu_opcodes.cpp @@ -39,7 +41,7 @@ SET(common_sources event_sub.cpp events/player_event_logs.cpp events/player_event_discord_formatter.cpp - expedition_lockout_timer.cpp + evolving_items.cpp extprofile.cpp discord/discord_manager.cpp faction.cpp @@ -88,6 +90,7 @@ SET(common_sources skills.cpp skill_caps.cpp spdat.cpp + spdat_bot.cpp strings.cpp struct_strategy.cpp textures.cpp @@ -97,12 +100,13 @@ SET(common_sources json/json.hpp json/jsoncpp.cpp zone_store.cpp + memory/ksm.hpp net/console_server.cpp net/console_server_connection.cpp net/crc32.cpp - net/daybreak_connection.cpp net/eqstream.cpp net/packet.cpp + net/reliable_stream_connection.cpp net/servertalk_client_connection.cpp net/servertalk_legacy_client_connection.cpp net/servertalk_server.cpp @@ -172,6 +176,7 @@ SET(repositories repositories/base/base_character_currency_repository.h repositories/base/base_character_data_repository.h repositories/base/base_character_disciplines_repository.h + repositories/base/base_character_evolving_items_repository.h repositories/base/base_character_expedition_lockouts_repository.h repositories/base/base_character_exp_modifiers_repository.h repositories/base/base_character_inspect_messages_repository.h @@ -209,10 +214,9 @@ SET(repositories repositories/base/base_discovered_items_repository.h repositories/base/base_doors_repository.h repositories/base/base_dynamic_zones_repository.h + repositories/base/base_dynamic_zone_lockouts_repository.h repositories/base/base_dynamic_zone_members_repository.h repositories/base/base_dynamic_zone_templates_repository.h - repositories/base/base_expeditions_repository.h - repositories/base/base_expedition_lockouts_repository.h repositories/base/base_faction_association_repository.h repositories/base/base_faction_base_data_repository.h repositories/base/base_faction_list_repository.h @@ -240,6 +244,7 @@ SET(repositories repositories/base/base_inventory_snapshots_repository.h repositories/base/base_ip_exemptions_repository.h repositories/base/base_items_repository.h + repositories/base/base_items_evolving_details_repository.h repositories/base/base_ldon_trap_entries_repository.h repositories/base/base_ldon_trap_templates_repository.h repositories/base/base_level_exp_mods_repository.h @@ -277,8 +282,20 @@ SET(repositories repositories/base/base_pets_equipmentset_repository.h repositories/base/base_pets_equipmentset_entries_repository.h repositories/base/base_player_titlesets_repository.h + repositories/base/base_player_event_aa_purchase_repository.h + repositories/base/base_player_event_killed_npc_repository.h + repositories/base/base_player_event_killed_named_npc_repository.h + repositories/base/base_player_event_killed_raid_npc_repository.h repositories/base/base_player_event_log_settings_repository.h repositories/base/base_player_event_logs_repository.h + repositories/base/base_player_event_loot_items_repository.h + repositories/base/base_player_event_merchant_purchase_repository.h + repositories/base/base_player_event_merchant_sell_repository.h + repositories/base/base_player_event_npc_handin_repository.h + repositories/base/base_player_event_npc_handin_entries_repository.h + repositories/base/base_player_event_speech_repository.h + repositories/base/base_player_event_trade_repository.h + repositories/base/base_player_event_trade_entries_repository.h repositories/base/base_quest_globals_repository.h repositories/base/base_raid_details_repository.h repositories/base/base_raid_members_repository.h @@ -355,6 +372,7 @@ SET(repositories repositories/character_currency_repository.h repositories/character_data_repository.h repositories/character_disciplines_repository.h + repositories/character_evolving_items_repository.h repositories/character_expedition_lockouts_repository.h repositories/character_exp_modifiers_repository.h repositories/character_inspect_messages_repository.h @@ -392,10 +410,9 @@ SET(repositories repositories/discovered_items_repository.h repositories/doors_repository.h repositories/dynamic_zones_repository.h + repositories/dynamic_zone_lockouts_repository.h repositories/dynamic_zone_members_repository.h repositories/dynamic_zone_templates_repository.h - repositories/expeditions_repository.h - repositories/expedition_lockouts_repository.h repositories/faction_association_repository.h repositories/faction_base_data_repository.h repositories/faction_list_repository.h @@ -423,6 +440,7 @@ SET(repositories repositories/inventory_snapshots_repository.h repositories/ip_exemptions_repository.h repositories/items_repository.h + repositories/items_evolving_details_repository.h repositories/ldon_trap_entries_repository.h repositories/ldon_trap_templates_repository.h repositories/level_exp_mods_repository.h @@ -460,8 +478,20 @@ SET(repositories repositories/pets_equipmentset_repository.h repositories/pets_equipmentset_entries_repository.h repositories/player_titlesets_repository.h + repositories/player_event_aa_purchase_repository.h + repositories/player_event_killed_npc_repository.h + repositories/player_event_killed_named_npc_repository.h + repositories/player_event_killed_raid_npc_repository.h repositories/player_event_log_settings_repository.h repositories/player_event_logs_repository.h + repositories/player_event_loot_items_repository.h + repositories/player_event_merchant_purchase_repository.h + repositories/player_event_merchant_sell_repository.h + repositories/player_event_npc_handin_repository.h + repositories/player_event_npc_handin_entries_repository.h + repositories/player_event_speech_repository.h + repositories/player_event_trade_repository.h + repositories/player_event_trade_entries_repository.h repositories/quest_globals_repository.h repositories/raid_details_repository.h repositories/raid_members_repository.h @@ -530,6 +560,7 @@ SET(common_headers discord/discord.h discord/discord_manager.h dynamic_zone_base.h + dynamic_zone_lockout.h emu_constants.h emu_limits.h emu_opcodes.h @@ -555,7 +586,7 @@ SET(common_headers events/player_event_discord_formatter.h events/player_events.h event_sub.h - expedition_lockout_timer.h + evolving_items.h extprofile.h faction.h file.h @@ -572,7 +603,6 @@ SET(common_headers ipc_mutex.h ip_util.h item_data.h - item_fieldlist.h item_instance.h json_config.h light_source.h @@ -615,6 +645,7 @@ SET(common_headers server_event_scheduler.h serverinfo.h servertalk.h + server_reload_types.h shared_tasks.h shareddb.h skills.h @@ -639,18 +670,20 @@ SET(common_headers net/console_server.h net/console_server_connection.h net/crc32.h - net/daybreak_connection.h - net/daybreak_structs.h net/dns.h net/endian.h net/eqstream.h net/packet.h + net/reliable_stream_connection.h + net/reliable_stream_pooling.h + net/reliable_stream_structs.h net/servertalk_client_connection.h net/servertalk_legacy_client_connection.h net/servertalk_common.h net/servertalk_server.h net/servertalk_server_connection.h net/tcp_connection.h + net/tcp_connection_pooling.h net/tcp_server.h net/websocket_server.h net/websocket_server_connection.h @@ -709,9 +742,6 @@ SOURCE_GROUP(Net FILES net/console_server_connection.h net/crc32.cpp net/crc32.h - net/daybreak_connection.cpp - net/daybreak_connection.h - net/daybreak_structs.h net/dns.h net/endian.h net/eqmq.cpp @@ -720,6 +750,10 @@ SOURCE_GROUP(Net FILES net/eqstream.h net/packet.cpp net/packet.h + net/reliable_stream_connection.cpp + net/reliable_stream_connection.h + net/reliable_stream_pooling.h + net/reliable_stream_structs.h net/servertalk_client_connection.cpp net/servertalk_client_connection.h net/servertalk_legacy_client_connection.cpp @@ -731,6 +765,7 @@ SOURCE_GROUP(Net FILES net/servertalk_server_connection.h net/tcp_connection.cpp net/tcp_connection.h + net/tcp_connection_pooling.h net/tcp_server.cpp net/tcp_server.h net/websocket_server.cpp @@ -805,8 +840,8 @@ IF (UNIX) SET_SOURCE_FILES_PROPERTIES("patches/sod.cpp" "patches/sof.cpp" "patches/rof.cpp" "patches/rof2.cpp" "patches/uf.cpp" PROPERTIES COMPILE_FLAGS -O0) ENDIF (UNIX) -IF (WIN32 AND EQEMU_BUILD_PCH) - TARGET_PRECOMPILE_HEADERS(common PRIVATE pch/pch.h) +IF (EQEMU_BUILD_PCH) + TARGET_PRECOMPILE_HEADERS(common PRIVATE pch/std-pch.h) ENDIF () SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) diff --git a/common/bazaar.cpp b/common/bazaar.cpp index 789182821..8244d5c52 100644 --- a/common/bazaar.cpp +++ b/common/bazaar.cpp @@ -6,15 +6,17 @@ std::vector Bazaar::GetSearchResults( - SharedDatabase &db, + Database &db, + Database &content_db, BazaarSearchCriteria_Struct search, - uint32 char_zone_id + uint32 char_zone_id, + int32 char_zone_instance_id ) { LogTrading( "Searching for items with search criteria - item_name [{}] min_cost [{}] max_cost [{}] min_level [{}] " "max_level [{}] max_results [{}] prestige [{}] augment [{}] trader_entity_id [{}] trader_id [{}] " - "search_scope [{}] char_zone_id [{}]", + "search_scope [{}] char_zone_id [{}], char_zone_instance_id [{}]", search.item_name, search.min_cost, search.max_cost, @@ -26,26 +28,173 @@ Bazaar::GetSearchResults( search.trader_entity_id, search.trader_id, search.search_scope, - char_zone_id + char_zone_id, + char_zone_instance_id ); - std::string search_criteria_trader("TRUE "); + static std::map item_slot_searches_new = { + {EQ::invslot::slotCharm, 1}, + {EQ::invslot::slotEar1, 2}, + {EQ::invslot::slotHead, 4}, + {EQ::invslot::slotFace, 8}, + {EQ::invslot::slotEar2, 16}, + {EQ::invslot::slotNeck, 32}, + {EQ::invslot::slotShoulders, 64}, + {EQ::invslot::slotArms, 128}, + {EQ::invslot::slotBack, 256}, + {EQ::invslot::slotWrist1, 512}, + {EQ::invslot::slotWrist2, 1024}, + {EQ::invslot::slotRange, 2048}, + {EQ::invslot::slotHands, 4096}, + {EQ::invslot::slotPrimary, 8192}, + {EQ::invslot::slotSecondary, 16384}, + {EQ::invslot::slotFinger1, 32768}, + {EQ::invslot::slotFinger2, 65536}, + {EQ::invslot::slotChest, 131072}, + {EQ::invslot::slotLegs, 262144}, + {EQ::invslot::slotFeet, 524288}, + {EQ::invslot::slotWaist, 1048576}, + {EQ::invslot::slotPowerSource, 2097152}, + {EQ::invslot::slotAmmo, 4194304}, + }; + + struct ItemSearchType { + EQ::item::ItemType type; + std::string condition; + }; + + std::vector item_search_types_new = { + {EQ::item::ItemType::ItemTypeBook, " AND (items.itemclass = 2 or items.itemclass = 31)"}, + {EQ::item::ItemType::ItemTypeContainer, " AND (items.itemclass = 1 or items.itemclass = 67)"}, + {EQ::item::ItemType::ItemTypeAllEffects, " AND (items.scrolleffect > 0 && items.scrolleffect < 65000)"}, + {EQ::item::ItemType::ItemTypeUnknown9, " AND items.worneffect = 998"}, + {EQ::item::ItemType::ItemTypeUnknown10, " AND (items.worneffect >= 1298 && items.worneffect <= 1307)"}, + {EQ::item::ItemType::ItemTypeFocusEffect, " AND items.focuseffect > 0"}, + {EQ::item::ItemType::ItemTypeArmor, " AND items.itemtype = 10"}, + {EQ::item::ItemType::ItemType1HBlunt, " AND items.itemtype = 3"}, + {EQ::item::ItemType::ItemType1HPiercing, " AND items.itemtype = 2"}, + {EQ::item::ItemType::ItemType1HSlash, " AND items.itemtype = 0"}, + {EQ::item::ItemType::ItemType2HBlunt, " AND items.itemtype = 4"}, + {EQ::item::ItemType::ItemType2HSlash, " AND items.itemtype = 1"}, + {EQ::item::ItemType::ItemTypeBow, " AND items.itemtype = 5"}, + {EQ::item::ItemType::ItemTypeShield, " AND items.itemtype = 8"}, + {EQ::item::ItemType::ItemTypeMisc, " AND items.itemtype = 11"}, + {EQ::item::ItemType::ItemTypeFood, " AND items.itemtype = 14"}, + {EQ::item::ItemType::ItemTypeDrink, " AND items.itemtype = 15"}, + {EQ::item::ItemType::ItemTypeLight, " AND items.itemtype = 16"}, + {EQ::item::ItemType::ItemTypeCombinable, " AND items.itemtype = 17"}, + {EQ::item::ItemType::ItemTypeBandage, " AND items.itemtype = 18"}, + {EQ::item::ItemType::ItemTypeSmallThrowing, " AND (items.itemtype = 19 OR items.itemtype = 7)"}, + {EQ::item::ItemType::ItemTypeSpell, " AND items.itemtype = 20"}, + {EQ::item::ItemType::ItemTypePotion, " AND items.itemtype = 21"}, + {EQ::item::ItemType::ItemTypeBrassInstrument, " AND items.itemtype = 25"}, + {EQ::item::ItemType::ItemTypeWindInstrument, " AND items.itemtype = 23"}, + {EQ::item::ItemType::ItemTypeStringedInstrument, " AND items.itemtype = 24"}, + {EQ::item::ItemType::ItemTypePercussionInstrument, " AND items.itemtype = 26"}, + {EQ::item::ItemType::ItemTypeArrow, " AND items.itemtype = 27"}, + {EQ::item::ItemType::ItemTypeJewelry, " AND items.itemtype = 29"}, + {EQ::item::ItemType::ItemTypeNote, " AND items.itemtype = 32"}, + {EQ::item::ItemType::ItemTypeKey, " AND items.itemtype = 33"}, + {EQ::item::ItemType::ItemType2HPiercing, " AND items.itemtype = 35"}, + {EQ::item::ItemType::ItemTypeAlcohol, " AND items.itemtype = 38"}, + {EQ::item::ItemType::ItemTypeMartial, " AND items.itemtype = 45"}, + {EQ::item::ItemType::ItemTypeAugmentation, " AND items.itemtype = 54"}, + {EQ::item::ItemType::ItemTypeAlternateAbility, " AND items.itemtype = 57"}, + {EQ::item::ItemType::ItemTypeCount, " AND items.itemtype = 65"}, + {EQ::item::ItemType::ItemTypeCollectible, " AND items.itemtype = 66"} + }; + + // item stat searches + struct ItemStatSearch { + std::string query_string; + EQ::skills::SkillType skill_type; + }; + + std::map item_stat_searches_new = { + {STAT_AC, {" items.ac" , static_cast(0)} }, + {STAT_AGI, {" items.aagi", static_cast(0)} }, + {STAT_CHA, {" items.acha", static_cast(0)} }, + {STAT_DEX, {" items.adex", static_cast(0)} }, + {STAT_INT, {" items.aint", static_cast(0)} }, + {STAT_STA, {" items.asta", static_cast(0)} }, + {STAT_STR, {" items.astr", static_cast(0)} }, + {STAT_WIS, {" items.awis", static_cast(0)} }, + {STAT_COLD, {" items.cr", static_cast(0)} }, + {STAT_DISEASE, {" items.dr", static_cast(0)} }, + {STAT_FIRE, {" items.fr", static_cast(0)} }, + {STAT_MAGIC, {" items.mr", static_cast(0)} }, + {STAT_POISON, {" items.pr", static_cast(0)} }, + {STAT_HP, {" items.hp", static_cast(0)} }, + {STAT_MANA, {" items.mana", static_cast(0)} }, + {STAT_ENDURANCE, {" items.endur", static_cast(0)} }, + {STAT_ATTACK, {" items.attack", static_cast(0)} }, + {STAT_HP_REGEN, {" items.regen", static_cast(0)} }, + {STAT_MANA_REGEN, {" items.manaregen", static_cast(0)} }, + {STAT_HASTE, {" items.haste", static_cast(0)} }, + {STAT_DAMAGE_SHIELD, {" items.damageshield", static_cast(0)} }, + {STAT_DS_MITIGATION, {" items.dsmitigation", static_cast(0)} }, + {STAT_HEAL_AMOUNT, {" items.healamt", static_cast(0)} }, + {STAT_SPELL_DAMAGE, {" items.spelldmg", static_cast(0)} }, + {STAT_CLAIRVOYANCE, {" items.clairvoyance", static_cast(0)} }, + {STAT_HEROIC_AGILITY, {" items.heroic_agi", static_cast(0)} }, + {STAT_HEROIC_CHARISMA, {" items.heroic_cha", static_cast(0)} }, + {STAT_HEROIC_DEXTERITY, {" items.heroic_dex", static_cast(0)} }, + {STAT_HEROIC_INTELLIGENCE, {" items.heroic_int", static_cast(0)} }, + {STAT_HEROIC_STAMINA, {" items.heroic_sta", static_cast(0)} }, + {STAT_HEROIC_STRENGTH, {" items.heroic_str", static_cast(0)} }, + {STAT_HEROIC_WISDOM, {" items.heroic_wis", static_cast(0)} }, + {STAT_BASH, {" items.skillmodvalue", EQ::skills::SkillBash} }, + {STAT_BACKSTAB, {" items.backstabdmg", EQ::skills::SkillBackstab} }, + {STAT_DRAGON_PUNCH, {" items.skillmodvalue", EQ::skills::SkillDragonPunch} }, + {STAT_EAGLE_STRIKE, {" items.skillmodvalue", EQ::skills::SkillEagleStrike} }, + {STAT_FLYING_KICK, {" items.skillmodvalue", EQ::skills::SkillFlyingKick} }, + {STAT_KICK, {" items.skillmodvalue", EQ::skills::SkillKick} }, + {STAT_ROUND_KICK, {" items.skillmodvalue", EQ::skills::SkillRoundKick} }, + {STAT_TIGER_CLAW, {" items.skillmodvalue", EQ::skills::SkillTigerClaw} }, + {STAT_FRENZY, {" items.skillmodvalue", EQ::skills::SkillFrenzy} }, + }; + + bool convert = false; + std::string search_criteria_trader("TRUE"); + std::string field_criteria_items("FALSE"); + std::string where_criteria_items(" TRUE "); if (search.search_scope == NonRoFBazaarSearchScope) { search_criteria_trader.append( fmt::format( - " AND trader.char_entity_id = {} AND trader.char_zone_id = {}", + " AND trader.char_entity_id = {} AND trader.char_zone_id = {} AND trader.char_zone_instance_id = {}", search.trader_entity_id, - Zones::BAZAAR + Zones::BAZAAR, + char_zone_instance_id ) ); } else if (search.search_scope == Local_Scope) { - search_criteria_trader.append(fmt::format(" AND trader.char_zone_id = {}", char_zone_id)); + search_criteria_trader.append(fmt::format( + " AND trader.char_zone_id = {} AND trader.char_zone_instance_id = {}", + char_zone_id, + char_zone_instance_id) + ); } else if (search.trader_id > 0) { - search_criteria_trader.append(fmt::format(" AND trader.char_id = {}", search.trader_id)); + if (RuleB(Bazaar, UseAlternateBazaarSearch)) { + if (search.trader_id >= TraderRepository::TRADER_CONVERT_ID) { + convert = true; + search_criteria_trader.append(fmt::format( + " AND trader.char_zone_id = {} AND trader.char_zone_instance_id = {}", + Zones::BAZAAR, + search.trader_id - TraderRepository::TRADER_CONVERT_ID) + ); + } + else { + search_criteria_trader.append(fmt::format(" AND trader.char_id = {}", search.trader_id)); + } + } + else { + search_criteria_trader.append(fmt::format(" AND trader.char_id = {}", search.trader_id)); + } } + if (search.min_cost != 0) { search_criteria_trader.append(fmt::format(" AND trader.item_cost >= {}", search.min_cost * 1000)); } @@ -53,300 +202,125 @@ Bazaar::GetSearchResults( search_criteria_trader.append(fmt::format(" AND trader.item_cost <= {}", (uint64) search.max_cost * 1000)); } - // not yet implemented - // if (search.prestige != 0) { - // 0xffffffff prestige only, 0xfffffffe non-prestige, 0 all - // search_criteria.append(fmt::format(" AND items.type = {} ", search.prestige)); - // } + if (search.slot != std::numeric_limits::max()) { + if (item_slot_searches_new.contains(search.slot)) { + where_criteria_items.append( + fmt::format(" AND items.slots & {0} = {0}", item_slot_searches_new[search.slot])); + } + } - std::string query = fmt::format( - "SELECT COUNT(item_id), trader.char_id, trader.item_id, trader.item_sn, trader.item_charges, trader.item_cost, " - "trader.slot_id, SUM(trader.item_charges), trader.char_zone_id, trader.char_entity_id, character_data.name, " - "aug_slot_1, aug_slot_2, aug_slot_3, aug_slot_4, aug_slot_5, aug_slot_6 " - "FROM trader, character_data " - "WHERE {} AND trader.char_id = character_data.id " - "GROUP BY trader.item_sn, trader.item_charges, trader.char_id", - search_criteria_trader.c_str() - ); + if (search.type != std::numeric_limits::max()) { + for (auto const &[type, condition]: item_search_types_new) { + if (type == search.type) { + where_criteria_items.append(condition); + break; + } + } + } + + if (search.race != std::numeric_limits::max()) { + where_criteria_items.append( + fmt::format(" AND items.races & {0} = {0}", GetPlayerRaceBit(GetRaceIDFromPlayerRaceValue(search.race)))); + } + + if (search._class != std::numeric_limits::max()) { + where_criteria_items.append(fmt::format(" AND items.classes & {0} = {0}", GetPlayerClassBit(search._class))); + } + + if (search.item_stat != std::numeric_limits::max()) { + if (item_stat_searches_new.contains(search.item_stat)) { + field_criteria_items = fmt::format("{}", item_stat_searches_new[search.item_stat].query_string); + if (item_stat_searches_new[search.item_stat].skill_type) { + where_criteria_items.append( + fmt::format(" AND items.skillmodtype = {} ", item_stat_searches_new[search.item_stat].skill_type)); + } + else { + where_criteria_items.append( + fmt::format(" AND {} > 0 ", item_stat_searches_new[search.item_stat].query_string)); + } + } + } + + if (search.augment) { + where_criteria_items.append(fmt::format( + " AND (items.augslot1type = {0} OR " + "items.augslot2type = {0} OR " + "items.augslot3type = {0} OR " + "items.augslot4type = {0} OR " + "items.augslot5type = {0} OR " + "items.augslot6type = {0})", + search.augment) + ); + } + + if (search.min_level != 1) { + where_criteria_items.append(fmt::format(" AND items.reclevel >= {}", search.min_level)); + } + + if (search.max_level != 100) { + where_criteria_items.append(fmt::format(" AND items.reclevel <= {}", search.max_level)); + } std::vector all_entries; + std::vector trader_items_ids{}; - auto results = db.QueryDatabase(query); - - if (!results.Success()) { + auto const trader_results = TraderRepository::GetBazaarTraderDetails(db, search_criteria_trader); + if (trader_results.empty()) { + LogTradingDetail("Bazaar - No traders found in bazaar search."); return all_entries; } - struct ItemSearchType { - EQ::item::ItemType type; - bool condition; - }; + for (auto const &i: trader_results) { + trader_items_ids.push_back(std::to_string(i.trader.item_id)); + } - struct AddititiveSearchCriteria { - bool should_check; - bool condition; - }; + auto const item_results = ItemsRepository::GetItemsForBazaarSearch( + content_db, + trader_items_ids, + std::string(search.item_name), + field_criteria_items, + where_criteria_items, + search.max_results + ); + + if (item_results.empty()) { + LogTradingDetail("Bazaar - No items found in bazaar search."); + return all_entries; + } + + all_entries.reserve(trader_results.size()); + + for (auto const& t:trader_results) { + if (!item_results.contains(t.trader.item_id)) { + continue; + } - for (auto row: results) { BazaarSearchResultsFromDB_Struct r{}; + r.count = 1; + r.trader_id = t.trader.char_id; + r.serial_number = t.trader.item_sn; + r.cost = t.trader.item_cost; + r.slot_id = t.trader.slot_id; + r.charges = t.trader.item_charges; + r.stackable = item_results.at(t.trader.item_id).stackable; + r.icon_id = item_results.at(t.trader.item_id).icon; + r.trader_zone_id = t.trader.char_zone_id; + r.trader_zone_instance_id = t.trader.char_zone_instance_id; + r.trader_entity_id = t.trader.char_entity_id; + r.serial_number_RoF = fmt::format("{:016}\0", t.trader.item_sn); + r.item_name = fmt::format("{:.63}\0", item_results.at(t.trader.item_id).name); + r.trader_name = fmt::format("{:.63}\0", t.trader_name); + r.item_stat = item_results.at(t.trader.item_id).stats; - r.item_id = Strings::ToInt(row[2]); - r.charges = Strings::ToInt(row[4]); - - auto item = db.GetItem(r.item_id); - if (!item) { - continue; - } - - uint32 aug_slot_1 = Strings::ToUnsignedInt(row[11]); - uint32 aug_slot_2 = Strings::ToUnsignedInt(row[12]); - uint32 aug_slot_3 = Strings::ToUnsignedInt(row[13]); - uint32 aug_slot_4 = Strings::ToUnsignedInt(row[14]); - uint32 aug_slot_5 = Strings::ToUnsignedInt(row[15]); - uint32 aug_slot_6 = Strings::ToUnsignedInt(row[16]); - - std::unique_ptr inst( - db.CreateItem( - item, - r.charges, - aug_slot_1, - aug_slot_2, - aug_slot_3, - aug_slot_4, - aug_slot_5, - aug_slot_6 - ) - ); - - if (!inst->GetItem()) { - continue; - } - - r.count = Strings::ToInt(row[0]); - r.trader_id = Strings::ToInt(row[1]); - r.serial_number = Strings::ToInt(row[3]); - r.cost = Strings::ToInt(row[5]); - r.slot_id = Strings::ToInt(row[6]); - r.sum_charges = Strings::ToInt(row[7]); - r.stackable = item->Stackable; - r.icon_id = item->Icon; - r.trader_zone_id = Strings::ToInt(row[8]); - r.trader_entity_id = Strings::ToInt(row[9]); - r.serial_number_RoF = fmt::format("{:016}\0", Strings::ToInt(row[3])); - r.item_name = fmt::format("{:.63}\0", item->Name); - r.trader_name = fmt::format("{:.63}\0", std::string(row[10]).c_str()); - - LogTradingDetail( - "Searching against item [{}] ({}) for trader [{}]", - item->Name, - item->ID, - r.trader_name - ); - - // item stat searches - std::map item_stat_searches = { - - {STAT_AC, inst->GetItemArmorClass(true)}, - {STAT_AGI, static_cast(inst->GetItemAgi(true))}, - {STAT_CHA, static_cast(inst->GetItemCha(true))}, - {STAT_DEX, static_cast(inst->GetItemDex(true))}, - {STAT_INT, static_cast(inst->GetItemInt(true))}, - {STAT_STA, static_cast(inst->GetItemSta(true))}, - {STAT_STR, static_cast(inst->GetItemStr(true))}, - {STAT_WIS, static_cast(inst->GetItemWis(true))}, - {STAT_COLD, static_cast(inst->GetItemCR(true))}, - {STAT_DISEASE, static_cast(inst->GetItemDR(true))}, - {STAT_FIRE, static_cast(inst->GetItemFR(true))}, - {STAT_MAGIC, static_cast(inst->GetItemMR(true))}, - {STAT_POISON, static_cast(inst->GetItemPR(true))}, - {STAT_HP, static_cast(inst->GetItemHP(true))}, - {STAT_MANA, static_cast(inst->GetItemMana(true))}, - {STAT_ENDURANCE, static_cast(inst->GetItemEndur(true))}, - {STAT_ATTACK, static_cast(inst->GetItemAttack(true))}, - {STAT_HP_REGEN, static_cast(inst->GetItemRegen(true))}, - {STAT_MANA_REGEN, static_cast(inst->GetItemManaRegen(true))}, - {STAT_HASTE, static_cast(inst->GetItemHaste(true))}, - {STAT_DAMAGE_SHIELD, static_cast(inst->GetItemDamageShield(true))}, - {STAT_DS_MITIGATION, static_cast(inst->GetItemDSMitigation(true))}, - {STAT_HEAL_AMOUNT, static_cast(inst->GetItemHealAmt(true))}, - {STAT_SPELL_DAMAGE, static_cast(inst->GetItemSpellDamage(true))}, - {STAT_CLAIRVOYANCE, static_cast(inst->GetItemClairvoyance(true))}, - {STAT_HEROIC_AGILITY, static_cast(inst->GetItemHeroicAgi(true))}, - {STAT_HEROIC_CHARISMA, static_cast(inst->GetItemHeroicCha(true))}, - {STAT_HEROIC_DEXTERITY, static_cast(inst->GetItemHeroicDex(true))}, - {STAT_HEROIC_INTELLIGENCE, static_cast(inst->GetItemHeroicInt(true))}, - {STAT_HEROIC_STAMINA, static_cast(inst->GetItemHeroicSta(true))}, - {STAT_HEROIC_STRENGTH, static_cast(inst->GetItemHeroicStr(true))}, - {STAT_HEROIC_WISDOM, static_cast(inst->GetItemHeroicWis(true))}, - {STAT_BASH, static_cast(inst->GetItemSkillsStat(EQ::skills::SkillBash, true))}, - {STAT_BACKSTAB, static_cast(inst->GetItemBackstabDamage(true))}, - {STAT_DRAGON_PUNCH, static_cast(inst->GetItemSkillsStat(EQ::skills::SkillDragonPunch, true))}, - {STAT_EAGLE_STRIKE, static_cast(inst->GetItemSkillsStat(EQ::skills::SkillEagleStrike, true))}, - {STAT_FLYING_KICK, static_cast(inst->GetItemSkillsStat(EQ::skills::SkillFlyingKick, true))}, - {STAT_KICK, static_cast(inst->GetItemSkillsStat(EQ::skills::SkillKick, true))}, - {STAT_ROUND_KICK, static_cast(inst->GetItemSkillsStat(EQ::skills::SkillRoundKick, true))}, - {STAT_TIGER_CLAW, static_cast(inst->GetItemSkillsStat(EQ::skills::SkillTigerClaw, true))}, - {STAT_FRENZY, static_cast(inst->GetItemSkillsStat(EQ::skills::SkillFrenzy, true))}, - }; - - r.item_stat = item_stat_searches.contains(search.item_stat) ? item_stat_searches[search.item_stat] : 0; - if (item_stat_searches.contains(search.item_stat) && item_stat_searches[search.item_stat] <= 0) { - continue; - } - - static std::map item_slot_searches = { - {EQ::invslot::slotCharm, 1}, - {EQ::invslot::slotEar1, 2}, - {EQ::invslot::slotHead, 4}, - {EQ::invslot::slotFace, 8}, - {EQ::invslot::slotEar2, 16}, - {EQ::invslot::slotNeck, 32}, - {EQ::invslot::slotShoulders, 64}, - {EQ::invslot::slotArms, 128}, - {EQ::invslot::slotBack, 256}, - {EQ::invslot::slotWrist1, 512}, - {EQ::invslot::slotWrist2, 1024}, - {EQ::invslot::slotRange, 2048}, - {EQ::invslot::slotHands, 4096}, - {EQ::invslot::slotPrimary, 8192}, - {EQ::invslot::slotSecondary, 16384}, - {EQ::invslot::slotFinger1, 32768}, - {EQ::invslot::slotFinger2, 65536}, - {EQ::invslot::slotChest, 131072}, - {EQ::invslot::slotLegs, 262144}, - {EQ::invslot::slotFeet, 524288}, - {EQ::invslot::slotWaist, 1048576}, - {EQ::invslot::slotPowerSource, 2097152}, - {EQ::invslot::slotAmmo, 4194304}, - }; - - auto GetEquipmentSlotBit = [&](uint32 slot) -> uint32 { - return item_slot_searches.contains(slot) ? item_slot_searches[slot] : 0; - }; - - auto FindItemAugSlot = [&]() -> bool { - for (auto const &s: inst->GetItem()->AugSlotType) { - return s == search.augment; - } - return false; - }; - - // item type searches - std::vector item_search_types = { - {EQ::item::ItemType::ItemTypeAll, true}, - {EQ::item::ItemType::ItemTypeBook, item->ItemClass == EQ::item::ItemType::ItemTypeBook}, - {EQ::item::ItemType::ItemTypeContainer, item->ItemClass == EQ::item::ItemType::ItemTypeContainer || - item->IsClassBag()}, - {EQ::item::ItemType::ItemTypeAllEffects, item->Scroll.Effect > 0 && item->Scroll.Effect < 65000}, - {EQ::item::ItemType::ItemTypeUnknown9, item->Worn.Effect == 998}, - {EQ::item::ItemType::ItemTypeUnknown10, item->Worn.Effect >= 1298 && item->Worn.Effect <= 1307}, - {EQ::item::ItemType::ItemTypeFocusEffect, item->Focus.Effect > 0}, - {EQ::item::ItemType::ItemTypeArmor, item->ItemType == EQ::item::ItemType::ItemTypeArmor}, - {EQ::item::ItemType::ItemType1HBlunt, item->ItemType == EQ::item::ItemType::ItemType1HBlunt}, - {EQ::item::ItemType::ItemType1HPiercing, item->ItemType == EQ::item::ItemType::ItemType1HPiercing}, - {EQ::item::ItemType::ItemType1HSlash, item->ItemType == EQ::item::ItemType::ItemType1HSlash}, - {EQ::item::ItemType::ItemType2HBlunt, item->ItemType == EQ::item::ItemType::ItemType2HBlunt}, - {EQ::item::ItemType::ItemType2HSlash, item->ItemType == EQ::item::ItemType::ItemType2HSlash}, - {EQ::item::ItemType::ItemTypeBow, item->ItemType == EQ::item::ItemType::ItemTypeBow}, - {EQ::item::ItemType::ItemTypeShield, item->ItemType == EQ::item::ItemType::ItemTypeShield}, - {EQ::item::ItemType::ItemTypeMisc, item->ItemType == EQ::item::ItemType::ItemTypeMisc}, - {EQ::item::ItemType::ItemTypeFood, item->ItemType == EQ::item::ItemType::ItemTypeFood}, - {EQ::item::ItemType::ItemTypeDrink, item->ItemType == EQ::item::ItemType::ItemTypeDrink}, - {EQ::item::ItemType::ItemTypeLight, item->ItemType == EQ::item::ItemType::ItemTypeLight}, - {EQ::item::ItemType::ItemTypeCombinable, item->ItemType == EQ::item::ItemType::ItemTypeCombinable}, - {EQ::item::ItemType::ItemTypeBandage, item->ItemType == EQ::item::ItemType::ItemTypeBandage}, - {EQ::item::ItemType::ItemTypeSmallThrowing, item->ItemType == EQ::item::ItemType::ItemTypeSmallThrowing || - item->ItemType == EQ::item::ItemType::ItemTypeLargeThrowing}, - {EQ::item::ItemType::ItemTypeSpell, item->ItemType == EQ::item::ItemType::ItemTypeSpell}, - {EQ::item::ItemType::ItemTypePotion, item->ItemType == EQ::item::ItemType::ItemTypePotion}, - {EQ::item::ItemType::ItemTypeBrassInstrument, item->ItemType == EQ::item::ItemType::ItemTypeBrassInstrument}, - {EQ::item::ItemType::ItemTypeWindInstrument, item->ItemType == EQ::item::ItemType::ItemTypeWindInstrument}, - {EQ::item::ItemType::ItemTypeStringedInstrument, item->ItemType == EQ::item::ItemType::ItemTypeStringedInstrument}, - {EQ::item::ItemType::ItemTypePercussionInstrument, item->ItemType == EQ::item::ItemType::ItemTypePercussionInstrument}, - {EQ::item::ItemType::ItemTypeArrow, item->ItemType == EQ::item::ItemType::ItemTypeArrow}, - {EQ::item::ItemType::ItemTypeJewelry, item->ItemType == EQ::item::ItemType::ItemTypeJewelry}, - {EQ::item::ItemType::ItemTypeNote, item->ItemType == EQ::item::ItemType::ItemTypeNote}, - {EQ::item::ItemType::ItemTypeKey, item->ItemType == EQ::item::ItemType::ItemTypeKey}, - {EQ::item::ItemType::ItemType2HPiercing, item->ItemType == EQ::item::ItemType::ItemType2HPiercing}, - {EQ::item::ItemType::ItemTypeAlcohol, item->ItemType == EQ::item::ItemType::ItemTypeAlcohol}, - {EQ::item::ItemType::ItemTypeMartial, item->ItemType == EQ::item::ItemType::ItemTypeMartial}, - {EQ::item::ItemType::ItemTypeAugmentation, item->ItemType == EQ::item::ItemType::ItemTypeAugmentation}, - {EQ::item::ItemType::ItemTypeAlternateAbility, item->ItemType == EQ::item::ItemType::ItemTypeAlternateAbility}, - {EQ::item::ItemType::ItemTypeCount, item->ItemType == EQ::item::ItemType::ItemTypeCount}, - {EQ::item::ItemType::ItemTypeCollectible, item->ItemType == EQ::item::ItemType::ItemTypeCollectible} - }; - - bool met_filter = false; - bool has_filter = false; - - for (auto &i: item_search_types) { - if (i.type == search.type) { - has_filter = true; - if (i.condition) { - LogTradingDetail("Item [{}] met search criteria for type [{}]", item->Name, uint8(i.type)); - met_filter = true; - break; + if (RuleB(Bazaar, UseAlternateBazaarSearch)) { + if (convert || + char_zone_id != Zones::BAZAAR || + (char_zone_id == Zones::BAZAAR && r.trader_zone_instance_id != char_zone_instance_id) + ) { + r.trader_id = TraderRepository::TRADER_CONVERT_ID + r.trader_zone_instance_id; } - } - } - if (has_filter && !met_filter) { - continue; } - // TODO: Add catch-all item type filter for specific item types - - // item additive searches - std::vector item_additive_searches = { - { - .should_check = search.min_level != 1 && inst->GetItemRequiredLevel(true) > 0, - .condition = inst->GetItemRequiredLevel(true) >= search.min_level - }, - { - .should_check = search.max_level != 1 && inst->GetItemRequiredLevel(true) > 0, - .condition = inst->GetItemRequiredLevel(true) <= search.max_level - }, - { - .should_check = !std::string(search.item_name).empty(), - .condition = Strings::ContainsLower(item->Name, search.item_name) - }, - { - .should_check = search._class != 0xFFFFFFFF, - .condition = static_cast(item->Classes & GetPlayerClassBit(search._class)) - }, - { - .should_check = search.race != 0xFFFFFFFF, - .condition = static_cast(item->Races & GetPlayerRaceBit(GetRaceIDFromPlayerRaceValue(search.race))) - }, - { - .should_check = search.augment != 0, - .condition = FindItemAugSlot() - }, - { - .should_check = search.slot != 0xFFFFFFFF, - .condition = static_cast(item->Slots & GetEquipmentSlotBit(search.slot)) - }, - }; - - bool should_add = true; - - for (auto &i: item_additive_searches) { - LogTradingDetail( - "Checking item [{}] for search criteria - should_check [{}] condition [{}]", - item->Name, - i.should_check, - i.condition - ); - if (i.should_check && !i.condition) { - should_add = false; - continue; - } - } - - if (!should_add) { - continue; - } - - LogTradingDetail("Found item [{}] meeting search criteria.", r.item_name); all_entries.push_back(r); } diff --git a/common/bazaar.h b/common/bazaar.h index 6ce906724..bec1ae264 100644 --- a/common/bazaar.h +++ b/common/bazaar.h @@ -3,11 +3,13 @@ #include #include "shareddb.h" +#include "../../common/item_instance.h" class Bazaar { public: static std::vector - GetSearchResults(SharedDatabase &db, BazaarSearchCriteria_Struct search, unsigned int char_zone_id); + GetSearchResults(Database &content_db, Database &db, BazaarSearchCriteria_Struct search, unsigned int char_zone_id, int char_zone_instance_id); + }; diff --git a/common/classes.h b/common/classes.h index 96e5cad26..dc586ceeb 100644 --- a/common/classes.h +++ b/common/classes.h @@ -131,6 +131,8 @@ static std::map class_names = { #define ARMOR_TYPE_LAST ARMOR_TYPE_PLATE #define ARMOR_TYPE_COUNT 5 +#define BOT_CLASS_BASE_ID_PREFIX 3000 + const char* GetClassIDName(uint8 class_id, uint8 level = 0); diff --git a/common/content/world_content_service.cpp b/common/content/world_content_service.cpp index 7d23c9d13..d6511e710 100644 --- a/common/content/world_content_service.cpp +++ b/common/content/world_content_service.cpp @@ -27,7 +27,7 @@ WorldContentService *WorldContentService::SetExpansionContext() // pull expansion from rules int expansion = RuleI(Expansion, CurrentExpansion); if (expansion >= Expansion::Classic && expansion <= Expansion::MaxId) { - content_service.SetCurrentExpansion(expansion); + WorldContentService::Instance()->SetCurrentExpansion(expansion); } LogInfo( @@ -41,12 +41,12 @@ WorldContentService *WorldContentService::SetExpansionContext() std::string WorldContentService::GetCurrentExpansionName() { - if (content_service.GetCurrentExpansion() == Expansion::EXPANSION_ALL) { + if (WorldContentService::Instance()->GetCurrentExpansion() == Expansion::EXPANSION_ALL) { return "All Expansions"; } if (current_expansion >= Expansion::Classic && current_expansion <= Expansion::MaxId) { - return Expansion::ExpansionName[content_service.GetCurrentExpansion()]; + return Expansion::ExpansionName[WorldContentService::Instance()->GetCurrentExpansion()]; } return "Unknown Expansion"; @@ -185,7 +185,7 @@ void WorldContentService::ReloadContentFlags() SetContentFlags(set_content_flags); LoadStaticGlobalZoneInstances(); - zone_store.LoadZones(*m_content_database); + ZoneStore::Instance()->LoadZones(*m_content_database); } Database *WorldContentService::GetDatabase() const @@ -291,7 +291,7 @@ WorldContentService *WorldContentService::LoadStaticGlobalZoneInstances() // instance_list table entry for lavastorm has version = 1, is_global = 1, never_expires = 1 WorldContentService::FindZoneResult WorldContentService::FindZone(uint32 zone_id, uint32 instance_id) { - for (const auto &z: zone_store.GetZones()) { + for (const auto &z: ZoneStore::Instance()->GetZones()) { for (auto &i: m_zone_static_instances) { if ( z.zoneidnumber == zone_id && diff --git a/common/content/world_content_service.h b/common/content/world_content_service.h index 2afc89181..0f84682d7 100644 --- a/common/content/world_content_service.h +++ b/common/content/world_content_service.h @@ -181,6 +181,12 @@ public: FindZoneResult FindZone(uint32 zone_id, uint32 instance_id); bool IsInPublicStaticInstance(uint32 instance_id); + static WorldContentService* Instance() + { + static WorldContentService instance; + return &instance; + } + private: int current_expansion{}; std::vector content_flags; @@ -194,6 +200,4 @@ private: std::vector m_zone_static_instances; }; -extern WorldContentService content_service; - #endif //EQEMU_WORLD_CONTENT_SERVICE_H diff --git a/common/crash.cpp b/common/crash.cpp index cb02a0bb4..968a7c5ff 100644 --- a/common/crash.cpp +++ b/common/crash.cpp @@ -27,6 +27,8 @@ void SendCrashReport(const std::string &crash_report) // "http://localhost:3010/api/v1/analytics/server-crash-report", // development }; + EQEmuLogSys* log = EQEmuLogSys::Instance(); + auto config = EQEmuConfig::get(); for (auto &e: endpoints) { uri u(e); @@ -68,12 +70,12 @@ void SendCrashReport(const std::string &crash_report) p["cpus"] = cpus.size(); p["origination_info"] = ""; - if (!LogSys.origination_info.zone_short_name.empty()) { + if (!log->origination_info.zone_short_name.empty()) { p["origination_info"] = fmt::format( "{} ({}) instance_id [{}]", - LogSys.origination_info.zone_short_name, - LogSys.origination_info.zone_long_name, - LogSys.origination_info.instance_id + log->origination_info.zone_short_name, + log->origination_info.zone_long_name, + log->origination_info.instance_id ); } @@ -294,6 +296,8 @@ void print_trace() SendCrashReport(crash_report); } + EQEmuLogSys::Instance()->CloseFileLogs(); + exit(1); } diff --git a/common/data_bucket.cpp b/common/data_bucket.cpp new file mode 100644 index 000000000..bef06a124 --- /dev/null +++ b/common/data_bucket.cpp @@ -0,0 +1,846 @@ +#include "../common/data_bucket.h" +#include "database.h" +#include +#include +#include "../common/json/json.hpp" + +using json = nlohmann::json; + +const std::string NESTED_KEY_DELIMITER = "."; +std::vector g_data_bucket_cache = {}; + +#if defined(ZONE) +#include "../zone/zonedb.h" +extern ZoneDatabase database; +#elif defined(WORLD) +#include "../world/worlddb.h" +extern WorldDatabase database; +#else +#error "You must define either ZONE or WORLD" +#endif + +void DataBucket::SetData(const std::string &bucket_key, const std::string &bucket_value, std::string expires_time) +{ + auto k = DataBucketKey{ + .key = bucket_key, + .value = bucket_value, + .expires = expires_time, + }; + + DataBucket::SetData(k); +} + +void DataBucket::SetData(const DataBucketKey &k_) +{ + DataBucketKey k = k_; // copy the key so we can modify it + 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(); + } + + auto b = DataBucketsRepository::NewEntity(); + auto r = GetData(k, true); + // if we have an entry, use it + if (r.id > 0) { + b = r; + } + + // add scoping to bucket + if (k.character_id > 0) { + b.character_id = k.character_id; + } + else if (k.account_id > 0) { + b.account_id = k.account_id; + } + else if (k.npc_id > 0) { + b.npc_id = k.npc_id; + } + else if (k.bot_id > 0) { + b.bot_id = k.bot_id; + } else if (k.zone_id > 0) { + b.zone_id = k.zone_id; + b.instance_id = k.instance_id; + } + + const uint64 bucket_id = b.id; + int64 expires_time_unix = 0; + + if (!k.expires.empty()) { + expires_time_unix = static_cast(std::time(nullptr)) + Strings::ToInt(k.expires); + if (isalpha(k.expires[0]) || isalpha(k.expires[k.expires.length() - 1])) { + expires_time_unix = static_cast(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; + b.value = k.value; + b.key_ = k.key; + + // Check for nested keys (keys with dots) + if (k_.key.find(NESTED_KEY_DELIMITER) != std::string::npos) { + // Retrieve existing JSON or create a new one + std::string existing_value = r.id > 0 ? r.value : "{}"; + json json_value = json::object(); + + // 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(); + } + current = &(*current)[key_part]; + } + } + + // Serialize JSON back to string + b.value = json_value.dump(); + b.key_ = top_key; // Use the top-level key + } + + if (bucket_id) { + // update the cache if it exists + if (CanCache(k)) { + for (auto &e: g_data_bucket_cache) { + if (CheckBucketMatch(e, k)) { + e = b; + break; + } + } + } + + DataBucketsRepository::UpdateOne(database, b); + } + else { + b = DataBucketsRepository::InsertOne(database, b); + + // add to cache if it doesn't exist + if (CanCache(k) && !ExistsInCache(b)) { + DeleteFromMissesCache(b); + g_data_bucket_cache.emplace_back(b); + } + } +} + +std::string DataBucket::GetData(const std::string &bucket_key) +{ + return GetData(DataBucketKey{.key = bucket_key}).value; +} + +DataBucketsRepository::DataBuckets DataBucket::ExtractNestedValue( + const DataBucketsRepository::DataBuckets &bucket, + 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) { + LogDataBuckets("Failed to parse JSON for key [{}] [{}]", bucket.key_, ex.what()); + return DataBucketsRepository::NewEntity(); // Return empty entity on parse error + } + + // Start from the top-level key (e.g., "progression") + json *current = &json_value; + + // Traverse the JSON structure + for (const auto &key_part: nested_keys) { + LogDataBuckets("Looking for key part [{}] in JSON", key_part); + + if (!current->contains(key_part)) { + LogDataBuckets("Key part [{}] not found in JSON for [{}]", key_part, full_key); + return DataBucketsRepository::NewEntity(); + } + + current = &(*current)[key_part]; + } + + // Create a new entity with the extracted value + DataBucketsRepository::DataBuckets result = bucket; // Copy the original bucket + result.value = current->is_string() ? current->get() : current->dump(); + return result; +} + +// GetData fetches bucket data from the database or cache if it exists +// if the bucket doesn't exist, it will be added to the cache as a miss +// if ignore_misses_cache is true, the bucket will not be added to the cache as a miss +// the only place we should be ignoring the misses cache is on the initial read during SetData +DataBucketsRepository::DataBuckets DataBucket::GetData(const DataBucketKey &k_, bool ignore_misses_cache) +{ + DataBucketKey k = k_; // Copy the key so we can modify it + + bool is_nested_key = k.key.find(NESTED_KEY_DELIMITER) != std::string::npos; + + // Extract the top-level key for nested keys + if (is_nested_key) { + k.key = Strings::Split(k.key, NESTED_KEY_DELIMITER).front(); + } + + LogDataBuckets( + "Getting bucket key [{}] bot_id [{}] account_id [{}] character_id [{}] npc_id [{}] zone_id [{}] instance_id [{}]", + k.key, + k.bot_id, + k.account_id, + k.character_id, + k.npc_id, + k.zone_id, + k.instance_id + ); + + bool can_cache = CanCache(k); + + // Attempt to retrieve the value from the cache + if (can_cache) { + for (const auto &e : g_data_bucket_cache) { + if (CheckBucketMatch(e, k)) { + if (e.expires > 0 && e.expires < std::time(nullptr)) { + LogDataBuckets("Attempted to read expired key [{}] removing from cache", e.key_); + DeleteData(k); + return DataBucketsRepository::NewEntity(); + } + + LogDataBuckets("Returning key [{}] value [{}] from cache", e.key_, e.value); + + if (is_nested_key && !k_.key.empty()) { + return ExtractNestedValue(e, k_.key); + } + + return e; + } + } + } + + // Fetch the value from the database + auto r = DataBucketsRepository::GetWhere( + database, + fmt::format( + " {} `key` = '{}' LIMIT 1", + DataBucket::GetScopedDbFilters(k), + k.key + ) + ); + + if (r.empty()) { + // Handle cache misses + if (!ignore_misses_cache && can_cache) { + size_t size_before = g_data_bucket_cache.size(); + + g_data_bucket_cache.emplace_back( + DataBucketsRepository::DataBuckets{ + .id = 0, + .key_ = k.key, + .value = "", + .expires = 0, + .account_id = k.account_id, + .character_id = k.character_id, + .npc_id = k.npc_id, + .bot_id = k.bot_id, + .zone_id = k.zone_id, + .instance_id = k.instance_id + } + ); + + LogDataBuckets( + "Key [{}] not found in database, adding to cache as a miss account_id [{}] character_id [{}] npc_id [{}] bot_id [{}] zone_id [{}] instance_id [{}] cache size before [{}] after [{}]", + k.key, + 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() + ); + } + + return DataBucketsRepository::NewEntity(); + } + + auto bucket = r.front(); + + // If the entry has expired, delete it + if (bucket.expires > 0 && bucket.expires < static_cast(std::time(nullptr))) { + DeleteData(k); + return DataBucketsRepository::NewEntity(); + } + + // Add the value to the cache if it doesn't exist + if (can_cache) { + bool has_cache = false; + for (const auto &e : g_data_bucket_cache) { + if (e.id == bucket.id) { + has_cache = true; + break; + } + } + + if (!has_cache) { + g_data_bucket_cache.emplace_back(bucket); + } + } + + // Handle nested key extraction + if (is_nested_key && !k_.key.empty()) { + return ExtractNestedValue(bucket, k_.key); + } + + return bucket; +} + +std::string DataBucket::GetDataExpires(const std::string &bucket_key) +{ + return GetDataExpires(DataBucketKey{.key = bucket_key}); +} + +std::string DataBucket::GetDataRemaining(const std::string &bucket_key) +{ + return GetDataRemaining(DataBucketKey{.key = bucket_key}); +} + +bool DataBucket::DeleteData(const std::string &bucket_key) +{ + return DeleteData(DataBucketKey{.key = bucket_key}); +} + +bool DataBucket::DeleteData(const DataBucketKey &k) +{ + bool is_nested_key = k.key.find(NESTED_KEY_DELIMITER) != std::string::npos; + + 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() + ); + } + + // Regular key deletion, no nesting involved + 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) +{ + LogDataBuckets( + "Getting bucket expiration key [{}] bot_id [{}] account_id [{}] character_id [{}] npc_id [{}]", + k.key, + k.bot_id, + k.account_id, + k.character_id, + k.npc_id + ); + + auto r = GetData(k); + if (r.id == 0) { + return {}; + } + + return std::to_string(r.expires); +} + +std::string DataBucket::GetDataRemaining(const DataBucketKey &k) +{ + LogDataBuckets( + "Getting bucket remaining key [{}] bot_id [{}] account_id [{}] character_id [{}] npc_id [{}] bot_id [{}] zone_id [{}] instance_id [{}]", + k.key, + k.bot_id, + k.account_id, + k.character_id, + k.npc_id, + k.bot_id, + k.zone_id, + k.instance_id + ); + + auto r = GetData(k); + if (r.id == 0) { + return "0"; + } + + return fmt::format("{}", r.expires - (long long) std::time(nullptr)); +} + +std::string DataBucket::GetScopedDbFilters(const DataBucketKey &k) +{ + std::vector q = {}; + if (k.character_id > 0) { + q.emplace_back(fmt::format("character_id = {}", k.character_id)); + } + else { + q.emplace_back("character_id = 0"); + } + + if (k.account_id > 0) { + q.emplace_back(fmt::format("account_id = {}", k.account_id)); + } + else { + q.emplace_back("account_id = 0"); + } + + if (k.npc_id > 0) { + q.emplace_back(fmt::format("npc_id = {}", k.npc_id)); + } + else { + q.emplace_back("npc_id = 0"); + } + + if (k.bot_id > 0) { + q.emplace_back(fmt::format("bot_id = {}", k.bot_id)); + } + else { + q.emplace_back("bot_id = 0"); + } + + if (k.zone_id > 0) { + q.emplace_back(fmt::format("zone_id = {} AND instance_id = {}", k.zone_id, k.instance_id)); + } + else { + q.emplace_back("zone_id = 0 AND instance_id = 0"); + } + + return fmt::format( + "{} {}", + Strings::Join(q, " AND "), + !q.empty() ? "AND" : "" + ); +} + +bool DataBucket::CheckBucketMatch(const DataBucketsRepository::DataBuckets &dbe, const DataBucketKey &k) +{ + return ( + dbe.key_ == k.key && + dbe.bot_id == k.bot_id && + dbe.account_id == k.account_id && + dbe.character_id == k.character_id && + dbe.npc_id == k.npc_id && + dbe.zone_id == k.zone_id && + dbe.instance_id == k.instance_id + ); +} + +void DataBucket::LoadZoneCache(uint16 zone_id, uint16 instance_id) +{ + const auto &l = DataBucketsRepository::GetWhere( + database, + fmt::format( + "zone_id = {} AND instance_id = {} AND (`expires` > {} OR `expires` = 0)", + zone_id, + instance_id, + (long long) std::time(nullptr) + ) + ); + + if (l.empty()) { + return; + } + + LogDataBucketsDetail("cache size before [{}] l size [{}]", g_data_bucket_cache.size(), l.size()); + + uint32 added_count = 0; + + for (const auto &e: l) { + if (!ExistsInCache(e)) { + added_count++; + } + } + + for (const auto &e: l) { + if (!ExistsInCache(e)) { + LogDataBucketsDetail("bucket id [{}] bucket key [{}] bucket value [{}]", e.id, e.key_, e.value); + + g_data_bucket_cache.emplace_back(e); + } + } + + LogDataBucketsDetail("cache size after [{}]", g_data_bucket_cache.size()); + + LogDataBuckets( + "Loaded [{}] zone keys new cache size is [{}]", + l.size(), + g_data_bucket_cache.size() + ); +} + +void DataBucket::BulkLoadEntitiesToCache(DataBucketLoadType::Type t, std::vector ids) +{ + if (ids.empty()) { + return; + } + + if (ids.size() == 1) { + bool has_cache = false; + + for (const auto &e: g_data_bucket_cache) { + if (t == DataBucketLoadType::Bot) { + has_cache = e.bot_id == ids[0]; + } + else if (t == DataBucketLoadType::Account) { + has_cache = e.account_id == ids[0]; + } + else if (t == DataBucketLoadType::Client) { + has_cache = e.character_id == ids[0]; + } + } + + if (has_cache) { + LogDataBucketsDetail("LoadType [{}] ID [{}] has cache", DataBucketLoadType::Name[t], ids[0]); + return; + } + } + + std::string column; + + switch (t) { + case DataBucketLoadType::Bot: + column = "bot_id"; + break; + case DataBucketLoadType::Client: + column = "character_id"; + break; + case DataBucketLoadType::Account: + column = "account_id"; + break; + default: + LogError("Incorrect LoadType [{}]", static_cast(t)); + break; + } + + const auto &l = DataBucketsRepository::GetWhere( + database, + fmt::format( + "{} IN ({}) AND (`expires` > {} OR `expires` = 0)", + column, + Strings::Join(ids, ", "), + (long long) std::time(nullptr) + ) + ); + + if (l.empty()) { + return; + } + + LogDataBucketsDetail("cache size before [{}] l size [{}]", g_data_bucket_cache.size(), l.size()); + + uint32 added_count = 0; + + for (const auto &e: l) { + if (!ExistsInCache(e)) { + added_count++; + } + } + + for (const auto &e: l) { + if (!ExistsInCache(e)) { + LogDataBucketsDetail("bucket id [{}] bucket key [{}] bucket value [{}]", e.id, e.key_, e.value); + + g_data_bucket_cache.emplace_back(e); + } + } + + LogDataBucketsDetail("cache size after [{}]", g_data_bucket_cache.size()); + + LogDataBuckets( + "Bulk Loaded ids [{}] column [{}] new cache size is [{}]", + ids.size(), + column, + g_data_bucket_cache.size() + ); +} + +void DataBucket::DeleteCachedBuckets(DataBucketLoadType::Type type, uint32 id, uint32 secondary_id) +{ + size_t size_before = g_data_bucket_cache.size(); + + g_data_bucket_cache.erase( + std::remove_if( + g_data_bucket_cache.begin(), + g_data_bucket_cache.end(), + [&](DataBucketsRepository::DataBuckets &e) { + return ( + (type == DataBucketLoadType::Bot && e.bot_id == id) || + (type == DataBucketLoadType::Account && e.account_id == id) || + (type == DataBucketLoadType::Client && e.character_id == id) || + (type == DataBucketLoadType::Zone && e.zone_id == id && e.instance_id == secondary_id) + ); + } + ), + g_data_bucket_cache.end() + ); + + LogDataBuckets( + "LoadType [{}] id [{}] cache size before [{}] after [{}]", + DataBucketLoadType::Name[type], + id, + size_before, + g_data_bucket_cache.size() + ); +} + +bool DataBucket::ExistsInCache(const DataBucketsRepository::DataBuckets &entry) +{ + for (const auto &e: g_data_bucket_cache) { + if (e.id == entry.id) { + return true; + } + } + + return false; +} + +void DataBucket::DeleteFromMissesCache(DataBucketsRepository::DataBuckets e) +{ + // delete from cache where there might have been a written bucket miss to the cache + // this is to prevent the cache from growing too large + size_t size_before = g_data_bucket_cache.size(); + + g_data_bucket_cache.erase( + std::remove_if( + g_data_bucket_cache.begin(), + g_data_bucket_cache.end(), + [&](DataBucketsRepository::DataBuckets &ce) { + return ce.id == 0 && ce.key_ == e.key_ && + ce.account_id == e.account_id && + ce.character_id == e.character_id && + ce.npc_id == e.npc_id && + ce.bot_id == e.bot_id && + ce.zone_id == e.zone_id && + ce.instance_id == e.instance_id; + } + ), + g_data_bucket_cache.end() + ); + LogDataBucketsDetail( + "Deleted bucket misses from cache where key [{}] size before [{}] after [{}]", + e.key_, + size_before, + g_data_bucket_cache.size() + ); +} + +void DataBucket::ClearCache() +{ + g_data_bucket_cache.clear(); + LogInfo("Cleared data buckets cache"); +} + +void DataBucket::DeleteFromCache(uint64 id, DataBucketLoadType::Type type) +{ + size_t size_before = g_data_bucket_cache.size(); + + g_data_bucket_cache.erase( + std::remove_if( + g_data_bucket_cache.begin(), + g_data_bucket_cache.end(), + [&](DataBucketsRepository::DataBuckets &e) { + switch (type) { + case DataBucketLoadType::Bot: + return e.bot_id == id; + case DataBucketLoadType::Client: + return e.character_id == id; + case DataBucketLoadType::Account: + return e.account_id == id; + default: + return false; + } + } + ), + g_data_bucket_cache.end() + ); + + LogDataBuckets( + "Deleted [{}] id [{}] from cache size before [{}] after [{}]", + DataBucketLoadType::Name[type], + id, + size_before, + g_data_bucket_cache.size() + ); +} + +void DataBucket::DeleteZoneFromCache(uint16 zone_id, uint16 instance_id, DataBucketLoadType::Type type) +{ + size_t size_before = g_data_bucket_cache.size(); + + g_data_bucket_cache.erase( + std::remove_if( + g_data_bucket_cache.begin(), + g_data_bucket_cache.end(), + [&](DataBucketsRepository::DataBuckets &e) { + switch (type) { + case DataBucketLoadType::Zone: + return e.zone_id == zone_id && e.instance_id == instance_id; + default: + return false; + } + } + ), + g_data_bucket_cache.end() + ); + + LogDataBuckets( + "Deleted zone [{}] instance [{}] from cache size before [{}] after [{}]", + zone_id, + instance_id, + size_before, + g_data_bucket_cache.size() + ); +} + +// CanCache returns whether a bucket can be cached or not +// characters are only in one zone at a time so we can cache locally to the zone +// bots (not implemented) are only in one zone at a time so we can cache locally to the zone +// npcs (ids) can be in multiple zones so we can't cache locally to the zone +bool DataBucket::CanCache(const DataBucketKey &key) +{ + if (key.character_id > 0 || key.account_id > 0 || key.bot_id > 0 || key.zone_id > 0) { + return true; + } + + return false; +} \ No newline at end of file diff --git a/zone/data_bucket.h b/common/data_bucket.h similarity index 55% rename from zone/data_bucket.h rename to common/data_bucket.h index 4ae270e44..6f5c1bcc6 100644 --- a/zone/data_bucket.h +++ b/common/data_bucket.h @@ -1,59 +1,37 @@ -// -// Created by Akkadius on 7/7/18. -// - #ifndef EQEMU_DATABUCKET_H #define EQEMU_DATABUCKET_H #include -#include "../common/types.h" -#include "../common/repositories/data_buckets_repository.h" -#include "mob.h" -#include "../common/json/json_archive_single_line.h" -#include "../common/servertalk.h" - -enum DataBucketCacheUpdateAction : uint8 { - Upsert, - Delete -}; - -struct DataBucketCacheEntry { - DataBucketsRepository::DataBuckets e; - int64_t updated_time{}; - DataBucketCacheUpdateAction update_action{}; - - template - void serialize(Archive &ar) - { - ar( - CEREAL_NVP(e), - CEREAL_NVP(updated_time), - CEREAL_NVP(update_action) - ); - } -}; +#include "types.h" +#include "repositories/data_buckets_repository.h" +#include "json/json_archive_single_line.h" struct DataBucketKey { std::string key; std::string value; std::string expires; - int64_t character_id; - int64_t npc_id; - int64_t bot_id; + uint64_t account_id = 0; + uint64_t character_id = 0; + uint32_t npc_id = 0; + uint32_t bot_id = 0; + uint16_t zone_id = 0; + uint16_t instance_id = 0; }; namespace DataBucketLoadType { enum Type : uint8 { Bot, + Account, Client, - NPC, + Zone, MaxType }; static const std::string Name[Type::MaxType] = { "Bot", + "Account", "Client", - "NPC", + "Zone" }; } @@ -66,29 +44,29 @@ public: static std::string GetDataExpires(const std::string &bucket_key); static std::string GetDataRemaining(const std::string &bucket_key); - static bool GetDataBuckets(Mob *mob); - - static int64_t GetCurrentTimeUNIX(); - // scoped bucket methods - static void SetData(const DataBucketKey &k); + static void SetData(const DataBucketKey &k_); static bool DeleteData(const DataBucketKey &k); - static DataBucketsRepository::DataBuckets GetData(const DataBucketKey &k, bool ignore_misses_cache = false); + static DataBucketsRepository::DataBuckets GetData(const DataBucketKey &k_, bool ignore_misses_cache = false); static std::string GetDataExpires(const DataBucketKey &k); static std::string GetDataRemaining(const DataBucketKey &k); static std::string GetScopedDbFilters(const DataBucketKey &k); // bucket repository versus key matching static bool CheckBucketMatch(const DataBucketsRepository::DataBuckets &dbe, const DataBucketKey &k); - static bool ExistsInCache(const DataBucketsRepository::DataBuckets &e); + static bool ExistsInCache(const DataBucketsRepository::DataBuckets &entry); - static void BulkLoadEntities(DataBucketLoadType::Type t, std::vector ids); - static void DeleteCachedBuckets(DataBucketLoadType::Type t, uint32 id); + static void LoadZoneCache(uint16 zone_id, uint16 instance_id); + static void BulkLoadEntitiesToCache(DataBucketLoadType::Type t, std::vector ids); + static void DeleteCachedBuckets(DataBucketLoadType::Type type, uint32 id, uint32 secondary_id = 0); - static bool SendDataBucketCacheUpdate(const DataBucketCacheEntry &e); - static void HandleWorldMessage(ServerPacket *p); static void DeleteFromMissesCache(DataBucketsRepository::DataBuckets e); static void ClearCache(); + static void DeleteFromCache(uint64 id, DataBucketLoadType::Type type); + static void DeleteZoneFromCache(uint16 zone_id, uint16 instance_id, DataBucketLoadType::Type type); + static bool CanCache(const DataBucketKey &key); + static DataBucketsRepository::DataBuckets + ExtractNestedValue(const DataBucketsRepository::DataBuckets &bucket, const std::string &full_key); }; #endif //EQEMU_DATABUCKET_H diff --git a/common/database.cpp b/common/database.cpp index c0193b927..3052fd722 100644 --- a/common/database.cpp +++ b/common/database.cpp @@ -50,6 +50,7 @@ #include "../common/repositories/raid_members_repository.h" #include "../common/repositories/reports_repository.h" #include "../common/repositories/variables_repository.h" +#include "../common/repositories/character_pet_name_repository.h" #include "../common/events/player_event_logs.h" // Disgrace: for windows compile @@ -244,7 +245,7 @@ uint32 Database::CreateAccount( e.password = password; } - LogInfo("Account Attempting to be created: [{}:{}] status: {}", loginserver, name, status); + LogInfo("Account attempting to be created loginserver [{}] name [{}] status [{}]", loginserver, name, status); e = AccountRepository::InsertOne(*this, e); @@ -313,6 +314,12 @@ bool Database::ReserveName(uint32 account_id, const std::string& name) return false; } + const auto& p = CharacterPetNameRepository::GetWhere(*this, where_filter); + if (!p.empty()) { + LogInfo("Account [{}] requested name [{}] but name is already taken by an Pet", account_id, name); + return false; + } + auto e = CharacterDataRepository::NewEntity(); e.account_id = account_id; @@ -701,6 +708,20 @@ const std::string Database::GetNPCNameByID(uint32 npc_id) return e.id ? e.name : std::string(); } +template +inline auto CleanMobName(InputIterator first, InputIterator last, OutputIterator result) +{ + for (; first != last; ++first) { + if (*first == '_') { + *result = ' '; + } + else if (isalpha(*first) || *first == '`') { + *result = *first; + } + } + return result; +} + const std::string Database::GetCleanNPCNameByID(uint32 npc_id) { const auto& e = NpcTypesRepository::FindOne(*this, npc_id); @@ -948,6 +969,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)) { @@ -975,6 +1019,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"); @@ -1051,13 +1109,13 @@ void Database::SetLFP(uint32 character_id, bool is_lfp) CharacterDataRepository::UpdateOne(*this, e); } -void Database::SetLoginFlags(uint32 character_id, bool is_lfp, bool is_lfg, uint8 first_logon) +void Database::SetLoginFlags(uint32 character_id, bool is_lfp, bool is_lfg, uint8 ingame) { auto e = CharacterDataRepository::FindOne(*this, character_id); - e.firstlogon = first_logon; - e.lfg = is_lfg ? 1 : 0; - e.lfp = is_lfp ? 1 : 0; + e.ingame = ingame; + e.lfg = is_lfg ? 1 : 0; + e.lfp = is_lfp ? 1 : 0; CharacterDataRepository::UpdateOne(*this, e); } @@ -1071,11 +1129,11 @@ void Database::SetLFG(uint32 character_id, bool is_lfg) CharacterDataRepository::UpdateOne(*this, e); } -void Database::SetFirstLogon(uint32 character_id, uint8 first_logon) +void Database::SetIngame(uint32 character_id, uint8 ingame) { auto e = CharacterDataRepository::FindOne(*this, character_id); - e.firstlogon = first_logon; + e.ingame = ingame; CharacterDataRepository::UpdateOne(*this, e); } @@ -1860,7 +1918,36 @@ bool Database::CopyCharacter( const int64 new_character_id = (CharacterDataRepository::GetMaxId(*this) + 1); - std::vector tables_to_zero_id = { "keyring", "data_buckets", "character_instance_safereturns" }; + // validate destination name doesn't exist already + const auto& destination_characters = CharacterDataRepository::GetWhere( + *this, + fmt::format( + "`name` = '{}' AND `deleted_at` IS NULL LIMIT 1", + Strings::Escape(destination_character_name) + ) + ); + if (!destination_characters.empty()) { + LogError("Character [{}] already exists", destination_character_name); + return false; + } + + std::vector tables_to_zero_id = { + "keyring", + "data_buckets", + "character_evolving_items", + "character_instance_safereturns", + "character_expedition_lockouts", + "character_instance_lockouts", + "character_parcels", + "character_tribute", + "player_titlesets", + }; + + std::vector ignore_tables = { + "guilds", + }; + + size_t total_rows_copied = 0; TransactionBegin(); @@ -1868,6 +1955,10 @@ bool Database::CopyCharacter( const std::string& table_name = t.first; const std::string& character_id_column_name = t.second; + if (Strings::Contains(ignore_tables, table_name)) { + continue; + } + auto results = QueryDatabase( fmt::format( "SHOW COLUMNS FROM {}", @@ -1875,6 +1966,12 @@ bool Database::CopyCharacter( ) ); + if (!results.Success()) { + LogError("Transaction failed [{}] rolling back", results.ErrorMessage()); + TransactionRollback(); + return false; + } + std::vector columns = {}; int column_count = 0; @@ -1893,6 +1990,12 @@ bool Database::CopyCharacter( ) ); + if (!results.Success()) { + LogError("Transaction failed [{}] rolling back", results.ErrorMessage()); + TransactionRollback(); + return false; + } + std::vector> new_rows; for (auto row : results) { @@ -1918,6 +2021,10 @@ bool Database::CopyCharacter( value = std::to_string(destination_account_id); } + if (!Strings::IsNumber(value)) { + value = Strings::Escape(value); + } + new_values.emplace_back(value); } @@ -1950,14 +2057,31 @@ bool Database::CopyCharacter( ) ); + size_t rows_copied = insert_rows.size(); // Rows copied for this table + total_rows_copied += rows_copied; // Increment grand total + + LogInfo("Copying table [{}] rows [{}]", table_name, Strings::Commify(rows_copied)); + if (!insert.ErrorMessage().empty()) { + LogError("Error copying table [{}] [{}]", table_name, insert.ErrorMessage()); TransactionRollback(); return false; } } } - TransactionCommit(); + auto r = TransactionCommit(); + if (!r.Success()) { + LogError("Transaction failed [{}] rolling back", r.ErrorMessage()); + return false; + } + + LogInfo( + "Character [{}] copied to [{}] total rows [{}]", + source_character_name, + destination_character_name, + Strings::Commify(total_rows_copied) + ); return true; } @@ -2100,18 +2224,18 @@ void Database::PurgeCharacterParcels() pel.event_type_name = PlayerEvent::EventName[pel.event_type_id]; std::stringstream ss; for (auto const &r: results) { - pd.from_name = r.from_name; - pd.item_id = r.item_id; - pd.aug_slot_1 = r.aug_slot_1; - pd.aug_slot_2 = r.aug_slot_2; - pd.aug_slot_3 = r.aug_slot_3; - pd.aug_slot_4 = r.aug_slot_4; - pd.aug_slot_5 = r.aug_slot_5; - pd.aug_slot_6 = r.aug_slot_6; - pd.note = r.note; - pd.quantity = r.quantity; - pd.sent_date = r.sent_date; - pd.char_id = r.char_id; + pd.from_name = r.from_name; + pd.item_id = r.item_id; + pd.augment_1_id = r.aug_slot_1; + pd.augment_2_id = r.aug_slot_2; + pd.augment_3_id = r.aug_slot_3; + pd.augment_4_id = r.aug_slot_4; + pd.augment_5_id = r.aug_slot_5; + pd.augment_6_id = r.aug_slot_6; + pd.note = r.note; + pd.quantity = r.quantity; + pd.sent_date = r.sent_date; + pd.char_id = r.char_id; { cereal::JSONOutputArchiveSingleLine ar(ss); pd.serialize(ar); @@ -2120,7 +2244,7 @@ void Database::PurgeCharacterParcels() pel.event_data = ss.str(); pel.created_at = std::time(nullptr); - player_event_logs.AddToQueue(pel); + PlayerEventLogs::Instance()->AddToQueue(pel); ss.str(""); ss.clear(); @@ -2147,3 +2271,20 @@ void Database::ClearBuyerDetails() { BuyerRepository::DeleteBuyer(*this, 0); } + +uint64_t Database::GetNextTableId(const std::string &table_name) +{ + auto results = QueryDatabase(fmt::format("SHOW TABLE STATUS LIKE '{}'", table_name)); + + for (auto row: results) { + for (int row_index = 0; row_index < results.ColumnCount(); row_index++) { + std::string field_name = Strings::ToLower(results.FieldName(row_index)); + if (field_name == "auto_increment") { + std::string value = row[row_index] ? row[row_index] : "null"; + return Strings::ToUnsignedBigInt(value, 1); + } + } + } + + return 1; +} diff --git a/common/database.h b/common/database.h index b33a6acc9..9b81f66a0 100644 --- a/common/database.h +++ b/common/database.h @@ -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); @@ -260,7 +263,7 @@ public: bool SaveTime(int8 minute, int8 hour, int8 day, int8 month, int16 year); void ClearMerchantTemp(); void ClearPTimers(uint32 character_id); - void SetFirstLogon(uint32 character_id, uint8 first_logon); + void SetIngame(uint32 character_id, uint8 ingame); void SetLFG(uint32 character_id, bool is_lfg); void SetLFP(uint32 character_id, bool is_lfp); void SetLoginFlags(uint32 character_id, bool is_lfp, bool is_lfg, uint8 first_logon); @@ -274,6 +277,8 @@ public: void Encode(std::string &in); void Decode(std::string &in); + uint64_t GetNextTableId(const std::string& table_name); + private: Mutex Mvarcache; VarCache_Struct varcache; diff --git a/common/database/database_dump_service.cpp b/common/database/database_dump_service.cpp index 62d0e070a..6cbea2d48 100644 --- a/common/database/database_dump_service.cpp +++ b/common/database/database_dump_service.cpp @@ -50,7 +50,7 @@ bool DatabaseDumpService::IsMySQLInstalled() { std::string version_output = GetMySQLVersion(); - return version_output.find("mysql") != std::string::npos && version_output.find("Ver") != std::string::npos; + return version_output.find("mysql") != std::string::npos && (version_output.find("Ver") != std::string::npos || version_output.find("from") != std::string::npos); } /** @@ -136,11 +136,6 @@ std::string DatabaseDumpService::GetLoginTableList() return Strings::Join(DatabaseSchema::GetLoginTables(), " "); } -std::string DatabaseDumpService::GetQueryServTables() -{ - return Strings::Join(DatabaseSchema::GetQueryServerTables(), " "); -} - std::string DatabaseDumpService::GetSystemTablesList() { auto system_tables = DatabaseSchema::GetServerTables(); @@ -209,7 +204,7 @@ void DatabaseDumpService::DatabaseDump() } if (IsDumpOutputToConsole()) { - LogSys.SilenceConsoleLogging(); + EQEmuLogSys::Instance()->SilenceConsoleLogging(); } LogInfo("MySQL installed [{}]", GetMySQLVersion()); @@ -272,11 +267,6 @@ void DatabaseDumpService::DatabaseDump() tables_to_dump += GetLoginTableList() + " "; dump_descriptor += "-login"; } - - if (IsDumpQueryServerTables()) { - tables_to_dump += GetQueryServTables(); - dump_descriptor += "-queryserv"; - } } if (IsDumpStaticInstanceData()) { @@ -334,7 +324,7 @@ void DatabaseDumpService::DatabaseDump() } if (!IsDumpOutputToConsole()) { - LogSys.LoadLogSettingsDefaults(); + EQEmuLogSys::Instance()->LoadLogSettingsDefaults(); } if (!pipe_file.empty()) { @@ -401,7 +391,6 @@ void DatabaseDumpService::DatabaseDump() // LogDebug("[{}] dump-to-console", IsDumpOutputToConsole()); // LogDebug("[{}] dump-path", GetSetDumpPath()); // LogDebug("[{}] compression", (IsDumpWithCompression() ? "true" : "false")); -// LogDebug("[{}] query-serv", (IsDumpQueryServerTables() ? "true" : "false")); // LogDebug("[{}] has-compression-binary", (HasCompressionBinary() ? "true" : "false")); // LogDebug("[{}] content", (IsDumpContentTables() ? "true" : "false")); // LogDebug("[{}] no-data", (IsDumpWithNoData() ? "true" : "false")); @@ -511,16 +500,6 @@ const std::string &DatabaseDumpService::GetDumpFileName() const return dump_file_name; } -bool DatabaseDumpService::IsDumpQueryServerTables() const -{ - return dump_query_server_tables; -} - -void DatabaseDumpService::SetDumpQueryServerTables(bool dump_query_server_tables) -{ - DatabaseDumpService::dump_query_server_tables = dump_query_server_tables; -} - bool DatabaseDumpService::IsDumpOutputToConsole() const { return dump_output_to_console; @@ -617,7 +596,12 @@ void DatabaseDumpService::BuildCredentialsFile() void DatabaseDumpService::RemoveCredentialsFile() { if (File::Exists(CREDENTIALS_FILE)) { - std::filesystem::remove(CREDENTIALS_FILE); + try { + std::filesystem::remove(CREDENTIALS_FILE); + } + catch (std::exception &e) { + LogError("std::filesystem::remove err [{}]", e.what()); + } } } diff --git a/common/database/database_dump_service.h b/common/database/database_dump_service.h index 568eac07f..fde5dc218 100644 --- a/common/database/database_dump_service.h +++ b/common/database/database_dump_service.h @@ -45,8 +45,6 @@ public: void SetDumpPath(const std::string &dump_path); const std::string &GetDumpFileName() const; void SetDumpFileName(const std::string &dump_file_name); - bool IsDumpQueryServerTables() const; - void SetDumpQueryServerTables(bool dump_query_server_tables); bool IsDumpOutputToConsole() const; void SetDumpOutputToConsole(bool dump_output_to_console); bool IsDumpDropTableSyntaxOnly() const; @@ -67,7 +65,6 @@ private: bool dump_system_tables = false; bool dump_content_tables = false; bool dump_player_tables = false; - bool dump_query_server_tables = false; bool dump_login_server_tables = false; bool dump_with_no_data = false; bool dump_table_lock = false; @@ -96,7 +93,6 @@ private: bool HasCompressionBinary(); std::string GetDumpFileNameWithPath(); std::string GetSetDumpPath(); - std::string GetQueryServTables(); void RemoveSqlBackup(); void BuildCredentialsFile(); void RemoveCredentialsFile(); diff --git a/common/database/database_update.cpp b/common/database/database_update.cpp index cd33d2abf..d296aa92b 100644 --- a/common/database/database_update.cpp +++ b/common/database/database_update.cpp @@ -7,6 +7,7 @@ #include "../http/httplib.h" #include "database_update_manifest.cpp" +#include "database_update_manifest_custom.cpp" #include "database_update_manifest_bots.cpp" #include "database_dump_service.h" @@ -14,7 +15,7 @@ constexpr int BREAK_LENGTH = 70; DatabaseVersion DatabaseUpdate::GetDatabaseVersions() { - auto results = m_database->QueryDatabase("SELECT `version`, `bots_version` FROM `db_version` LIMIT 1"); + auto results = m_database->QueryDatabase("SELECT `version`, `bots_version`, `custom_version` FROM `db_version` LIMIT 1"); if (!results.Success() || !results.RowCount()) { LogError("Failed to read from [db_version] table!"); return DatabaseVersion{}; @@ -25,6 +26,7 @@ DatabaseVersion DatabaseUpdate::GetDatabaseVersions() return DatabaseVersion{ .server_database_version = Strings::ToInt(r[0]), .bots_database_version = Strings::ToInt(r[1]), + .custom_database_version = Strings::ToInt(r[2]), }; } @@ -33,6 +35,7 @@ DatabaseVersion DatabaseUpdate::GetBinaryDatabaseVersions() return DatabaseVersion{ .server_database_version = CURRENT_BINARY_DATABASE_VERSION, .bots_database_version = (RuleB(Bots, Enabled) ? CURRENT_BINARY_BOTS_DATABASE_VERSION : 0), + .custom_database_version = CUSTOM_BINARY_DATABASE_VERSION, }; } @@ -43,6 +46,7 @@ constexpr int LOOK_BACK_AMOUNT = 10; // this check will take action void DatabaseUpdate::CheckDbUpdates() { + InjectCustomVersionColumn(); InjectBotsVersionColumn(); auto v = GetDatabaseVersions(); auto b = GetBinaryDatabaseVersions(); @@ -59,6 +63,15 @@ void DatabaseUpdate::CheckDbUpdates() m_database->QueryDatabase(fmt::format("UPDATE `db_version` SET `version` = {}", b.server_database_version)); } + if (UpdateManifest(manifest_entries_custom, v.custom_database_version, b.custom_database_version)) { + LogInfo( + "Updates ran successfully, setting database version to [{}] from [{}]", + b.custom_database_version, + v.custom_database_version + ); + m_database->QueryDatabase(fmt::format("UPDATE `db_version` SET `custom_version` = {}", b.custom_database_version)); + } + if (b.bots_database_version > 0) { if (UpdateManifest(bot_manifest_entries, v.bots_database_version, b.bots_database_version)) { LogInfo( @@ -141,7 +154,8 @@ bool DatabaseUpdate::UpdateManifest( std::vector missing_migrations = {}; if (version_low != version_high) { - LogSys.DisableMySQLErrorLogs(); + EQEmuLogSys::Instance()->DisableMySQLErrorLogs(); + bool force_interactive = false; for (int version = version_low + 1; version <= version_high; ++version) { for (auto &e: entries) { if (e.version == version) { @@ -163,13 +177,20 @@ bool DatabaseUpdate::UpdateManifest( prefix, e.description ); + + if (!has_migration && e.force_interactive) { + force_interactive = true; + } } } } - LogSys.EnableMySQLErrorLogs(); + EQEmuLogSys::Instance()->EnableMySQLErrorLogs(); LogInfo("{}", Strings::Repeat("-", BREAK_LENGTH)); - if (!missing_migrations.empty()) { + if (!missing_migrations.empty() && m_skip_backup) { + LogInfo("Skipping database backup"); + } + else if (!missing_migrations.empty()) { LogInfo("Automatically backing up database before applying updates"); LogInfo("{}", Strings::Repeat("-", BREAK_LENGTH)); auto s = DatabaseDumpService(); @@ -184,6 +205,42 @@ bool DatabaseUpdate::UpdateManifest( LogInfo("{}", Strings::Repeat("-", BREAK_LENGTH)); } + if (force_interactive && !std::getenv("FORCE_INTERACTIVE")) { + LogInfo("{}", Strings::Repeat("-", BREAK_LENGTH)); + LogInfo("Some migrations require user input. Running interactively"); + LogInfo("This is usually due to a major change that could cause data loss"); + LogInfo("Your server is automatically backed up before these updates are applied"); + LogInfo("but you should also make sure you take a backup prior to running this update"); + LogInfo("Would you like to run this update? [y/n] (Timeout 60s)"); + LogInfo("{}", Strings::Repeat("-", BREAK_LENGTH)); + + // user input + std::string input; + bool gave_input = false; + time_t start_time = time(nullptr); + time_t wait_time_seconds = 60; + + // spawn a concurrent thread that waits for input from std::cin + std::thread t1( + [&]() { + std::cin >> input; + gave_input = true; + } + ); + t1.detach(); + + // check the inputReceived flag once every 50ms for 10 seconds + while (time(nullptr) < start_time + wait_time_seconds && !gave_input) { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + + // prompt for user skip + if (Strings::Trim(input) != "y") { + LogInfo("Exiting due to user input"); + std::exit(1); + } + } + for (auto &m: missing_migrations) { for (auto &e: entries) { if (e.version == m) { @@ -271,6 +328,13 @@ DatabaseUpdate *DatabaseUpdate::SetContentDatabase(Database *db) return this; } +DatabaseUpdate *DatabaseUpdate::SetSkipBackup(bool skip) +{ + m_skip_backup = skip; + + return this; +} + bool DatabaseUpdate::CheckVersionsUpToDate(DatabaseVersion v, DatabaseVersion b) { LogInfo("{}", Strings::Repeat("-", BREAK_LENGTH)); @@ -293,6 +357,16 @@ bool DatabaseUpdate::CheckVersionsUpToDate(DatabaseVersion v, DatabaseVersion b) ); } + if (b.custom_database_version > 0) { + LogInfo( + "{:>8} | database [{}] binary [{}] {}", + "Custom", + v.custom_database_version, + b.custom_database_version, + (v.custom_database_version == b.custom_database_version) ? "up to date" : "checking updates" + ); + } + LogInfo("{:>8} | [server.auto_database_updates] [true]", "Config"); LogInfo("{}", Strings::Repeat("-", BREAK_LENGTH)); @@ -302,7 +376,10 @@ bool DatabaseUpdate::CheckVersionsUpToDate(DatabaseVersion v, DatabaseVersion b) // bots database version is optional, if not enabled then it is always up-to-date bool bots_up_to_date = RuleB(Bots, Enabled) ? v.bots_database_version >= b.bots_database_version : true; - return server_up_to_date && bots_up_to_date; + // custom database version is optional, if not enabled then it is always up-to-date + bool custom_up_to_date = v.custom_database_version >= b.custom_database_version; + + return server_up_to_date && bots_up_to_date && custom_up_to_date; } // checks to see if there are pending updates @@ -322,3 +399,12 @@ void DatabaseUpdate::InjectBotsVersionColumn() m_database->QueryDatabase("ALTER TABLE db_version ADD bots_version int(11) DEFAULT '0' AFTER version"); } } + +void DatabaseUpdate::InjectCustomVersionColumn() +{ + auto results = m_database->QueryDatabase("SHOW COLUMNS FROM `db_version` LIKE 'custom_version'"); + if (!results.Success() || results.RowCount() == 0) { + LogInfo("Adding custom_version column to db_version table"); + m_database->QueryDatabase("ALTER TABLE `db_version` ADD COLUMN `custom_version` INT(11) UNSIGNED NOT NULL DEFAULT 0"); + } +} diff --git a/common/database/database_update.h b/common/database/database_update.h index 0ba7b8df5..a445422bc 100644 --- a/common/database/database_update.h +++ b/common/database/database_update.h @@ -11,11 +11,13 @@ struct ManifestEntry { std::string match{}; // match field that is not always used, but works in conjunction with "condition" values [missing|match|contains] std::string sql{}; // the SQL DDL that gets ran when the condition is true bool content_schema_update{}; // if true, this migration is a content schema update and should be ran against the content database + bool force_interactive; // if true, this migration will always be run interactively }; struct DatabaseVersion { int server_database_version; int bots_database_version; + int custom_database_version; }; class DatabaseUpdate { @@ -29,12 +31,23 @@ public: DatabaseUpdate *SetDatabase(Database *db); DatabaseUpdate *SetContentDatabase(Database *db); + DatabaseUpdate *SetSkipBackup(bool skip); bool HasPendingUpdates(); + + static DatabaseUpdate* Instance() + { + static DatabaseUpdate instance; + return &instance; + } + private: + bool m_skip_backup = false; Database *m_database; Database *m_content_database; static bool CheckVersionsUpToDate(DatabaseVersion v, DatabaseVersion b); void InjectBotsVersionColumn(); + void InjectCustomVersionColumn(); + }; #endif //EQEMU_DATABASE_UPDATE_H diff --git a/common/database/database_update_manifest.cpp b/common/database/database_update_manifest.cpp index 636899094..15dc4a27f 100644 --- a/common/database/database_update_manifest.cpp +++ b/common/database/database_update_manifest.cpp @@ -5771,7 +5771,1421 @@ CREATE INDEX idx_character_expires ON data_buckets (character_id, expires); CREATE INDEX idx_npc_expires ON data_buckets (npc_id, expires); CREATE INDEX idx_bot_expires ON data_buckets (bot_id, expires); )" - } + }, + ManifestEntry{ + .version = 9286, + .description = "2024_11_26_bazaar_find_trader.sql", + .check = "SHOW COLUMNS FROM `trader` LIKE 'char_zone_instance_id'", + .condition = "empty", + .match = "", + .sql = R"( +ALTER TABLE `trader` + ADD COLUMN `char_zone_instance_id` INT NULL DEFAULT '0' AFTER `char_zone_id`; +)" + }, + ManifestEntry{ + .version = 9287, + .description = "2024_11_26_bazaar_find_trader.sql", + .check = "SHOW COLUMNS FROM `npc_types` LIKE 'walkspeed'", + .condition = "missing", + .match = "float", + .sql = R"( +ALTER TABLE `npc_types` MODIFY COLUMN `walkspeed` float NOT NULL DEFAULT 0; +)", + .content_schema_update = true + }, + ManifestEntry{ + .version = 9288, + .description = "2024_11_10_zone_player_partitioning.sql", + .check = "SHOW CREATE TABLE `zone`", + .condition = "missing", + .match = "shard_at_player_count", + .sql = R"( +ALTER TABLE `zone` +ADD COLUMN `shard_at_player_count` int(11) NULL DEFAULT 0 AFTER `seconds_before_idle`; +)", + .content_schema_update = true + }, + ManifestEntry{ + .version = 9289, + .description = "2025_01_19_evolving_items__character_evolving_items", + .check = "SHOW TABLES LIKE 'character_evolving_items'", + .condition = "empty", + .match = "", + .sql = R"( +CREATE TABLE `character_evolving_items` ( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `character_id` INT(10) UNSIGNED NULL DEFAULT '0', + `item_id` INT(10) UNSIGNED NULL DEFAULT '0', + `activated` TINYINT(1) UNSIGNED NULL DEFAULT '0', + `equipped` TINYINT(3) UNSIGNED NULL DEFAULT '0', + `current_amount` BIGINT(20) NULL DEFAULT '0', + `progression` DOUBLE(22,0) NULL DEFAULT '0', + `final_item_id` INT(10) UNSIGNED NULL DEFAULT '0', + `deleted_at` DATETIME NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) +COLLATE='latin1_swedish_ci' +ENGINE=InnoDB +AUTO_INCREMENT=1 +; +)" + }, + ManifestEntry{ + .version = 9290, + .description = "2025_01_19_evolving_items__items_evolving_details", + .check = "SHOW TABLES LIKE 'items_evolving_details'", + .condition = "empty", + .match = "", + .sql = R"( +CREATE TABLE `items_evolving_details` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `item_evo_id` int(10) unsigned DEFAULT 0 COMMENT 'items.evoid', + `item_evolve_level` int(10) unsigned DEFAULT 0 COMMENT 'items.evolvinglevel', + `item_id` int(10) unsigned DEFAULT 0 COMMENT 'items.id', + `type` int(10) unsigned DEFAULT 0, + `sub_type` int(10) unsigned DEFAULT 0, + `required_amount` bigint(20) DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; +INSERT INTO `items_evolving_details` VALUES +(1,1000,1,86900,99,1,0), +(2,1000,2,86901,99,1,0), +(3,1000,3,86902,99,1,0), +(4,1000,4,86903,99,1,0), +(5,1000,5,86904,99,1,0), +(6,1001,1,86910,99,1,0), +(7,1001,2,86911,99,1,0), +(8,1001,3,86912,99,1,0), +(9,1001,4,86913,99,1,0), +(10,1001,5,86914,99,1,0), +(11,1001,6,86915,99,1,0), +(12,1001,7,86916,99,1,0), +(13,1002,1,86920,99,1,0), +(14,1002,2,86921,99,1,0), +(15,1002,3,86922,99,1,0), +(16,1002,4,86923,99,1,0), +(17,1002,5,86924,99,1,0), +(18,1002,6,86925,99,1,0), +(19,1002,7,86926,99,1,0), +(20,1003,1,86930,99,1,0), +(21,1003,2,86931,99,1,0), +(22,1003,3,86932,99,1,0), +(23,1003,4,86933,99,1,0), +(24,1003,5,86934,99,1,0), +(25,1003,6,86935,99,1,0), +(26,1004,1,86940,99,1,0), +(27,1004,2,86941,99,1,0), +(28,1004,3,86942,99,1,0), +(29,1004,4,86943,99,1,0), +(30,1004,5,86944,99,1,0), +(31,1004,6,86945,99,1,0), +(32,1005,1,86950,99,1,0), +(33,1005,2,86951,99,1,0), +(34,1005,3,86952,99,1,0), +(35,1005,4,86953,99,1,0), +(36,1005,5,86954,99,1,0), +(37,1005,6,86955,99,1,0), +(38,1005,7,86956,99,1,0), +(39,1006,1,86960,99,1,0), +(40,1006,2,86961,99,1,0), +(41,1006,3,86962,99,1,0), +(42,1006,4,86963,99,1,0), +(43,1006,5,86964,99,1,0), +(44,1006,6,86965,99,1,0), +(45,1006,7,86966,99,1,0), +(46,1007,1,86970,99,1,0), +(47,1007,2,86971,99,1,0), +(48,1007,3,86972,99,1,0), +(49,1007,4,86973,99,1,0), +(50,1007,5,86974,99,1,0), +(51,1007,6,86975,99,1,0), +(52,1007,7,86976,99,1,0), +(53,1008,1,86980,99,1,0), +(54,1008,2,86981,99,1,0), +(55,1008,3,86982,99,1,0), +(56,1008,4,86983,99,1,0), +(57,1008,5,86984,99,1,0), +(58,1009,1,86990,99,1,0), +(59,1009,2,86991,99,1,0), +(60,1009,3,86992,99,1,0), +(61,1009,4,86993,99,1,0), +(62,1009,5,86994,99,1,0), +(63,1009,6,86995,99,1,0), +(64,1009,7,86996,99,1,0), +(65,1009,8,86997,99,1,0), +(66,1009,9,86998,99,1,0), +(67,1009,10,86999,99,1,0), +(68,1010,1,90001,99,1,0), +(69,1010,2,90002,99,1,0), +(70,1010,3,90003,99,1,0), +(71,1010,6,90006,99,1,0), +(72,1010,7,90007,99,1,0), +(73,1010,8,90008,99,1,0), +(74,1010,10,90010,99,1,0), +(75,1050,1,89270,99,1,0), +(76,1050,2,89271,99,1,0), +(77,1050,3,89272,99,1,0), +(78,1050,4,89273,99,1,0), +(79,1050,5,89274,99,1,0), +(80,1050,6,89275,99,1,0), +(81,1051,1,89280,99,1,0), +(82,1051,2,89281,99,1,0), +(83,1051,3,89282,99,1,0), +(84,1051,4,89283,99,1,0), +(85,1051,5,89284,99,1,0), +(86,1052,1,89290,99,1,0), +(87,1052,2,89291,99,1,0), +(88,1052,3,89292,99,1,0), +(89,1052,4,89293,99,1,0), +(90,1052,5,89294,99,1,0), +(91,1052,6,89295,99,1,0), +(92,1052,7,89296,99,1,0), +(93,1053,1,89300,99,1,0), +(94,1053,2,89301,99,1,0), +(95,1053,3,89302,99,1,0), +(96,1053,4,89303,99,1,0), +(97,1053,5,89304,99,1,0), +(98,1053,6,89305,99,1,0), +(99,1053,7,89306,99,1,0), +(100,1053,8,89307,99,1,0), +(101,1053,9,89308,99,1,0), +(102,1054,1,89310,99,1,0), +(103,1054,2,89311,99,1,0), +(104,1054,3,89312,99,1,0), +(105,1054,4,89313,99,1,0), +(106,1054,5,89314,99,1,0), +(107,1054,6,89315,99,1,0), +(108,1055,1,89320,99,1,0), +(109,1055,2,89321,99,1,0), +(110,1055,3,89322,99,1,0), +(111,1055,4,89323,99,1,0), +(112,1055,5,89324,99,1,0), +(113,1055,6,89325,99,1,0), +(114,1055,7,89326,99,1,0), +(115,1056,1,89330,99,1,0), +(116,1056,2,89331,99,1,0), +(117,1056,3,89332,99,1,0), +(118,1056,4,89333,99,1,0), +(119,1056,5,89334,99,1,0), +(120,1057,1,89340,99,1,0), +(121,1057,2,89341,99,1,0), +(122,1057,3,89342,99,1,0), +(123,1057,4,89343,99,1,0), +(124,1057,5,89344,99,1,0), +(125,1057,6,89345,99,1,0), +(126,1057,7,89346,99,1,0), +(127,1057,8,89347,99,1,0), +(128,1058,1,89350,99,1,0), +(129,1058,2,89351,99,1,0), +(130,1058,3,89352,99,1,0), +(131,1058,4,89353,99,1,0), +(132,1058,5,89354,99,1,0), +(133,1058,6,89355,99,1,0), +(134,1058,7,89356,99,1,0), +(135,1059,1,89360,99,1,0), +(136,1059,2,89361,99,1,0), +(137,1059,3,89362,99,1,0), +(138,1060,1,89490,99,1,0), +(139,1060,2,89491,99,1,0), +(140,1060,3,89492,99,1,0), +(141,1061,1,89500,99,1,0), +(142,1061,2,89501,99,1,0), +(143,1061,3,89502,99,1,0), +(144,1062,1,89510,99,1,0), +(145,1062,2,89511,99,1,0), +(146,1062,3,89512,99,1,0), +(147,1063,1,89520,99,1,0), +(148,1063,2,89521,99,1,0), +(149,1063,3,89522,99,1,0), +(150,1064,1,89530,99,1,0), +(151,1064,2,89531,99,1,0), +(152,1064,3,89532,99,1,0), +(153,1065,1,89540,99,1,0), +(154,1065,2,89541,99,1,0), +(155,1065,3,89542,99,1,0), +(156,1066,1,89550,3,274,500), +(157,1066,2,89551,3,274,1000), +(158,1066,3,89552,3,274,2000), +(159,1067,1,89560,99,1,0), +(160,1067,2,89561,99,1,0), +(161,1067,3,89562,99,1,0), +(162,1069,1,85571,99,1,0), +(163,1069,2,85572,99,1,0), +(164,1069,3,85573,99,1,0), +(165,1200,1,95001,99,1,0), +(166,1200,2,95002,99,1,0), +(167,1200,3,95003,99,1,0), +(168,1200,4,95004,99,1,0), +(169,1200,5,95005,99,1,0), +(170,1200,6,95006,99,1,0), +(171,1200,7,95007,99,1,0), +(172,1201,1,95008,99,1,0), +(173,1201,2,95009,99,1,0), +(174,1201,3,95010,99,1,0), +(175,1201,4,95011,99,1,0), +(176,1201,5,95012,99,1,0), +(177,1201,6,95013,99,1,0), +(178,1201,7,95014,99,1,0), +(179,1202,1,95015,99,1,0), +(180,1202,2,95016,99,1,0), +(181,1202,3,95017,99,1,0), +(182,1202,4,95018,99,1,0), +(183,1202,5,95019,99,1,0), +(184,1202,6,95020,99,1,0), +(185,1202,7,95021,99,1,0), +(186,1203,1,95022,99,1,0), +(187,1203,2,95023,99,1,0), +(188,1203,3,95024,99,1,0), +(189,1203,4,95025,99,1,0), +(190,1203,5,95026,99,1,0), +(191,1203,6,95027,99,1,0), +(192,1203,7,95028,99,1,0), +(193,1204,1,95029,99,1,0), +(194,1204,2,95030,99,1,0), +(195,1204,3,95031,99,1,0), +(196,1204,4,95032,99,1,0), +(197,1204,5,95033,99,1,0), +(198,1204,6,95034,99,1,0), +(199,1204,7,95035,99,1,0), +(200,1205,1,95036,99,1,0), +(201,1205,2,95037,99,1,0), +(202,1205,3,95038,99,1,0), +(203,1205,4,95039,99,1,0), +(204,1205,5,95040,99,1,0), +(205,1205,6,95041,99,1,0), +(206,1205,7,95042,99,1,0), +(207,1206,1,95043,99,1,0), +(208,1206,2,95044,99,1,0), +(209,1206,3,95045,99,1,0), +(210,1206,4,95046,99,1,0), +(211,1206,5,95047,99,1,0), +(212,1206,6,95048,99,1,0), +(213,1206,7,95049,99,1,0), +(214,1207,1,95050,99,1,0), +(215,1207,2,95051,99,1,0), +(216,1207,3,95052,99,1,0), +(217,1207,4,95053,99,1,0), +(218,1207,5,95054,99,1,0), +(219,1207,6,95055,99,1,0), +(220,1207,7,95056,99,1,0), +(221,1208,1,95057,99,1,0), +(222,1208,2,95058,99,1,0), +(223,1208,3,95059,99,1,0), +(224,1208,4,95060,99,1,0), +(225,1208,5,95061,99,1,0), +(226,1208,6,95062,99,1,0), +(227,1208,7,95063,99,1,0), +(228,1209,1,95064,99,1,0), +(229,1209,2,95065,99,1,0), +(230,1209,3,95066,99,1,0), +(231,1209,4,95067,99,1,0), +(232,1209,5,95068,99,1,0), +(233,1209,6,95069,99,1,0), +(234,1209,7,95070,99,1,0), +(235,1210,1,95071,99,1,0), +(236,1210,2,95072,99,1,0), +(237,1210,3,95073,99,1,0), +(238,1210,4,95074,99,1,0), +(239,1210,5,95075,99,1,0), +(240,1210,6,95076,99,1,0), +(241,1210,7,95077,99,1,0), +(242,1211,1,85612,1,1,100000), +(243,1211,2,85613,1,1,200000), +(244,1211,3,85614,1,1,300000), +(245,1214,1,80035,99,1,0), +(246,1301,1,102700,99,1,0), +(247,1301,4,102703,99,1,0), +(248,1302,1,102704,99,1,0), +(249,1302,2,102705,99,1,0), +(250,1303,1,102706,99,1,0), +(251,1303,2,102707,99,1,0), +(252,1303,3,102708,99,1,0), +(253,1304,1,102709,99,1,0), +(254,1304,5,102713,99,1,0), +(255,1305,1,102714,99,1,0), +(256,1306,1,102716,99,1,0), +(257,1306,5,102720,99,1,0), +(258,1307,1,102721,99,1,0), +(259,1307,3,102723,99,1,0), +(260,1308,1,102724,99,1,0), +(261,1309,1,102727,99,1,0), +(262,1309,2,102728,99,1,0), +(263,1310,1,102729,99,1,0), +(264,1310,3,102731,99,1,0), +(265,1311,1,102732,99,1,0), +(266,1311,4,102735,99,1,0), +(267,1312,1,102736,99,1,0), +(268,1312,3,102738,99,1,0), +(269,1313,1,102739,99,1,0), +(270,1314,1,102743,99,1,0), +(271,1314,2,102744,99,1,0), +(272,1314,3,102745,99,1,0), +(273,1315,1,102746,99,1,0), +(274,1315,2,102747,99,1,0), +(275,1316,1,102748,99,1,0), +(276,1316,5,102752,99,1,0), +(277,1317,1,102753,99,1,0), +(278,1318,1,102756,99,1,0), +(279,1319,1,102759,99,1,0), +(280,1319,3,102761,99,1,0), +(281,1320,1,102762,99,1,0), +(282,1321,1,102765,99,1,0), +(283,1321,2,102766,99,1,0), +(284,1321,3,102767,99,1,0), +(285,1322,1,102768,99,1,0), +(286,1322,2,102769,99,1,0), +(287,1322,3,102770,99,1,0), +(288,1323,1,102771,99,1,0), +(289,1324,1,102774,99,1,0), +(290,1400,1,102800,99,1,0), +(291,1401,1,102807,99,1,0), +(292,1401,7,102813,99,1,0), +(293,1402,1,102814,99,1,0), +(294,1402,7,102820,99,1,0), +(295,1403,1,102821,99,1,0), +(296,1404,1,102828,99,1,0), +(297,1405,1,102835,99,1,0), +(298,1406,1,102842,99,1,0), +(299,1408,1,109310,99,1,0), +(300,1408,5,109314,99,1,0), +(301,1409,1,109315,99,1,0), +(302,1409,5,109319,99,1,0), +(303,1410,1,109320,99,1,0), +(304,1410,5,109324,99,1,0), +(305,1411,1,109325,99,1,0), +(306,1411,5,109329,99,1,0), +(307,1412,1,109330,99,1,0), +(308,1412,5,109334,99,1,0), +(309,1413,1,109335,99,1,0), +(310,1413,5,109339,99,1,0), +(311,1414,1,109340,99,1,0), +(312,1414,5,109344,99,1,0), +(313,1415,1,109345,99,1,0), +(314,1415,5,109349,99,1,0), +(315,1416,1,109350,99,1,0), +(316,1416,2,109351,99,1,0), +(317,1416,5,109354,99,1,0), +(318,1417,1,109355,99,1,0), +(319,1417,5,109359,99,1,0), +(320,1418,1,109360,99,1,0), +(321,1418,5,109364,99,1,0), +(322,1419,1,109365,99,1,0), +(323,1419,3,109367,99,1,0), +(324,1419,5,109369,99,1,0), +(325,1420,1,109370,99,1,0), +(326,1420,5,109374,99,1,0), +(327,1421,1,109375,99,1,0), +(328,1421,5,109379,99,1,0), +(329,1422,1,109380,99,1,0), +(330,1422,5,109384,99,1,0), +(331,1423,1,109385,99,1,0), +(332,1423,2,109386,99,1,0), +(333,1423,5,109389,99,1,0), +(334,1436,1,120378,99,1,0), +(335,1436,2,120379,99,1,0), +(336,1436,3,120380,99,1,0), +(337,1436,4,120381,99,1,0), +(338,1436,5,120382,99,1,0), +(339,1436,6,120383,99,1,0), +(340,1436,7,120384,99,1,0), +(341,1436,8,120385,99,1,0), +(342,1436,9,120386,99,1,0), +(343,1436,10,120387,99,1,0), +(344,1436,11,120388,99,1,0), +(345,1436,12,120389,99,1,0), +(346,1436,13,120390,99,1,0), +(347,1436,14,120391,99,1,0), +(348,1436,15,120392,99,1,0), +(349,1436,16,120393,99,1,0), +(350,1436,17,120394,99,1,0), +(351,1436,18,120395,99,1,0), +(352,1436,19,120396,99,1,0), +(353,1436,20,120397,99,1,0), +(354,1440,1,56992,99,1,0), +(355,1440,2,56993,99,1,0), +(356,1440,3,56994,99,1,0), +(357,1440,4,56995,99,1,0), +(358,1440,5,56996,99,1,0), +(359,1441,1,132787,99,1,0), +(360,1441,2,132788,99,1,0), +(361,1441,3,132789,99,1,0), +(362,1441,4,132790,99,1,0), +(363,1441,5,132791,99,1,0), +(364,1441,6,132792,99,1,0), +(365,1441,7,132793,99,1,0), +(366,1441,8,132794,99,1,0), +(367,1441,9,132795,99,1,0), +(368,1441,10,132796,99,1,0), +(369,1441,11,132797,99,1,0), +(370,1441,13,132799,99,1,0), +(371,1441,14,132800,99,1,0), +(372,1441,15,132801,99,1,0), +(373,1441,16,132802,99,1,0), +(374,1441,17,132803,99,1,0), +(375,1441,18,132804,99,1,0), +(376,1441,19,132805,99,1,0), +(377,1441,20,132806,99,1,0), +(378,1442,1,133137,99,1,0), +(379,1442,2,133138,99,1,0), +(380,1442,3,133139,99,1,0), +(381,1442,4,133140,99,1,0), +(382,1442,10,133146,99,1,0), +(383,1442,11,133147,99,1,0), +(384,1442,12,133148,99,1,0), +(385,1442,13,133149,99,1,0), +(386,1442,14,133150,99,1,0), +(387,1442,15,133151,99,1,0), +(388,1442,16,133152,99,1,0), +(389,1442,17,133153,99,1,0), +(390,1442,18,133154,99,1,0), +(391,1442,19,133155,99,1,0), +(392,1442,20,133156,99,1,0), +(393,1443,1,133406,99,1,0), +(394,1443,2,133407,99,1,0), +(395,1443,3,133408,99,1,0), +(396,1443,4,133409,99,1,0), +(397,1443,5,133410,99,1,0), +(398,1443,6,133411,99,1,0), +(399,1443,7,133412,99,1,0), +(400,1443,8,133413,99,1,0), +(401,1443,9,133414,99,1,0), +(402,1443,10,133415,99,1,0), +(403,1443,11,133416,99,1,0), +(404,1443,12,133417,99,1,0), +(405,1443,13,133418,99,1,0), +(406,1443,14,133419,99,1,0), +(407,1443,15,133420,99,1,0), +(408,1443,16,133421,99,1,0), +(409,1443,17,133422,99,1,0), +(410,1443,18,133423,99,1,0), +(411,1443,19,133424,99,1,0), +(412,1443,20,133425,99,1,0), +(413,1444,1,94938,99,1,0), +(414,1444,2,94939,99,1,0), +(415,1444,3,94940,99,1,0), +(416,1444,4,94941,99,1,0), +(417,1444,5,94942,99,1,0), +(418,1444,6,94943,99,1,0), +(419,1444,7,94944,99,1,0), +(420,1444,8,94945,99,1,0), +(421,1444,9,94946,99,1,0), +(422,1444,10,94947,99,1,0), +(423,1444,11,94948,99,1,0), +(424,1444,12,94949,99,1,0), +(425,1444,13,94950,99,1,0), +(426,1444,14,94951,99,1,0), +(427,1444,15,94952,99,1,0), +(428,1444,16,94953,99,1,0), +(429,1444,17,94954,99,1,0), +(430,1444,18,94955,99,1,0), +(431,1444,19,94956,99,1,0), +(432,1444,20,94957,99,1,0), +(433,1445,1,98858,99,1,0), +(434,1445,2,98859,99,1,0), +(435,1445,3,98860,99,1,0), +(436,1445,4,98861,99,1,0), +(437,1445,5,98862,99,1,0); + +)", + .content_schema_update = true + }, + ManifestEntry{ + .version = 9291, + .description = "2025_01_21_add_remove_zone_fields", + .check = "SHOW COLUMNS FROM `zone` LIKE 'client_update_range'", + .condition = "empty", + .match = "", + .sql = R"( +ALTER TABLE zone DROP COLUMN IF EXISTS npc_update_range; +ALTER TABLE zone DROP COLUMN IF EXISTS max_movement_update_range; +ALTER TABLE `zone` ADD COLUMN `client_update_range` int(11) NOT NULL DEFAULT 600 AFTER `npc_max_aggro_dist`; +)", + .content_schema_update = true, + }, + ManifestEntry{ + .version = 9292, + .description = "2025_01_21_data_buckets_account_id", + .check = "SHOW COLUMNS FROM `data_buckets` LIKE 'account_id'", + .condition = "empty", + .match = "", + .sql = R"( +ALTER TABLE `data_buckets` +ADD COLUMN `account_id` bigint(11) NULL DEFAULT 0 AFTER `expires`, +DROP INDEX `keys`, +ADD UNIQUE INDEX `keys` (`key`, `character_id`, `npc_id`, `bot_id`, `account_id`) USING BTREE; + +-- Add the INDEX for character_id and key +ALTER TABLE `data_buckets` ADD KEY `idx_account_id_key` (`account_id`, `key`); +)", + .content_schema_update = false + }, + ManifestEntry{ + .version = 9293, + .description = "2025_01_10_create_pet_names_table.sql", + .check = "SHOW TABLES LIKE 'character_pet_name'", + .condition = "empty", + .match = "", + .sql = R"( +CREATE TABLE `character_pet_name` ( + `character_id` INT(11) NOT NULL PRIMARY KEY, + `name` VARCHAR(64) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +)", + }, + ManifestEntry{ + .version = 9294, + .description = "2025_01_26_items_table_bazaar_search_indexes.sql", + .check = "SHOW CREATE TABLE `items`", + .condition = "missing", + .match = "idx_slots_reclevel", + .sql = R"( +-- indexes for the `items` table +CREATE INDEX idx_slots_reclevel ON items (slots, reclevel); +CREATE INDEX idx_itemclass_itemtype ON items (itemclass, itemtype); +CREATE INDEX idx_augment_slots ON items ( + augslot1type, + augslot2type, + augslot3type, + augslot4type, + augslot5type, + augslot6type +); +CREATE INDEX idx_races_classes ON items (races, classes); + +-- common stat fields +CREATE INDEX idx_item_ac ON items (ac); +CREATE INDEX idx_item_hp ON items (hp); +CREATE INDEX idx_item_mana ON items (mana); +CREATE INDEX idx_item_reclevel ON items (reclevel); +CREATE INDEX idx_item_type_skill ON items (itemtype, skillmodtype); +)", + .content_schema_update = true + }, + ManifestEntry{ + .version = 9295, + .description = "2025_01_26_trader_table_bazaar_search_indexes.sql", + .check = "SHOW CREATE TABLE `trader`", + .condition = "missing", + .match = "idx_trader_item", + .sql = R"( +-- indexes for the `trader` table +CREATE INDEX idx_trader_item ON trader (item_id, item_cost); +CREATE INDEX idx_trader_char ON trader (char_id, char_zone_id, char_zone_instance_id); +CREATE INDEX idx_trader_item_sn ON trader (item_sn); +CREATE INDEX idx_trader_item_cost ON trader (item_cost); +CREATE INDEX idx_trader_active_transaction ON trader (active_transaction); +)", + .content_schema_update = false + }, + ManifestEntry{ + .version = 9296, + .description = "2025_02_01_trader_table_listing_date.sql", + .check = "SHOW CREATE TABLE `trader`", + .condition = "missing", + .match = "listing_date", + .sql = R"( +ALTER TABLE `trader` + ADD COLUMN `listing_date` DATETIME NULL DEFAULT NULL AFTER `active_transaction`, + ADD INDEX `idx_trader_listing_date` (`listing_date`); +)", + .content_schema_update = false + }, + ManifestEntry{ + .version = 9297, + .description = "2024_01_22_sharedbank_guid_primary_key.sql", + .check = "SHOW COLUMNS FROM `sharedbank` LIKE 'guid'", + .condition = "empty", + .match = "", + .sql = R"( +ALTER TABLE `sharedbank` +CHANGE COLUMN `acctid` `account_id` int(11) UNSIGNED NOT NULL DEFAULT 0 FIRST, +CHANGE COLUMN `slotid` `slot_id` mediumint(7) UNSIGNED NOT NULL DEFAULT 0 AFTER `account_id`, +CHANGE COLUMN `itemid` `item_id` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `slot_id`, +CHANGE COLUMN `augslot1` `augment_one` mediumint(7) UNSIGNED NOT NULL DEFAULT 0 AFTER `charges`, +CHANGE COLUMN `augslot2` `augment_two` mediumint(7) UNSIGNED NOT NULL DEFAULT 0 AFTER `augment_one`, +CHANGE COLUMN `augslot3` `augment_three` mediumint(7) UNSIGNED NOT NULL DEFAULT 0 AFTER `augment_two`, +CHANGE COLUMN `augslot4` `augment_four` mediumint(7) UNSIGNED NOT NULL DEFAULT 0 AFTER `augment_three`, +CHANGE COLUMN `augslot5` `augment_five` mediumint(7) UNSIGNED NOT NULL DEFAULT 0 AFTER `augment_four`, +CHANGE COLUMN `augslot6` `augment_six` mediumint(7) UNSIGNED NOT NULL DEFAULT 0 AFTER `augment_five`, +MODIFY COLUMN `charges` smallint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `item_id`, +ADD COLUMN `color` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `charges`, +ADD COLUMN `ornament_icon` int(11) UNSIGNED NOT NULL AFTER `custom_data`, +ADD COLUMN `ornament_idfile` int(11) UNSIGNED NOT NULL AFTER `ornament_icon`, +ADD COLUMN `ornament_hero_model` int(11) NOT NULL AFTER `ornament_idfile`, +ADD COLUMN `guid` bigint(20) UNSIGNED NOT NULL DEFAULT 0 AFTER `ornament_hero_model`, +ADD PRIMARY KEY (`account_id`, `slot_id`); +)", + .content_schema_update = false, + .force_interactive = false + }, + ManifestEntry{ + .version = 9298, + .description = "2024_10_24_inventory_changes.sql", + .check = "SHOW COLUMNS FROM `inventory` LIKE 'character_id'", + .condition = "empty", + .match = "", + .sql = R"( +ALTER TABLE `inventory` +CHANGE COLUMN `charid` `character_id` int(11) UNSIGNED NOT NULL DEFAULT 0 FIRST, +CHANGE COLUMN `slotid` `slot_id` mediumint(7) UNSIGNED NOT NULL DEFAULT 0 AFTER `character_id`, +CHANGE COLUMN `itemid` `item_id` int(11) UNSIGNED NULL DEFAULT 0 AFTER `slot_id`, +CHANGE COLUMN `augslot1` `augment_one` mediumint(7) UNSIGNED NOT NULL DEFAULT 0 AFTER `color`, +CHANGE COLUMN `augslot2` `augment_two` mediumint(7) UNSIGNED NOT NULL DEFAULT 0 AFTER `augment_one`, +CHANGE COLUMN `augslot3` `augment_three` mediumint(7) UNSIGNED NOT NULL DEFAULT 0 AFTER `augment_two`, +CHANGE COLUMN `augslot4` `augment_four` mediumint(7) UNSIGNED NOT NULL DEFAULT 0 AFTER `augment_three`, +CHANGE COLUMN `augslot5` `augment_five` mediumint(7) UNSIGNED NOT NULL DEFAULT 0 AFTER `augment_four`, +CHANGE COLUMN `augslot6` `augment_six` mediumint(7) UNSIGNED NOT NULL DEFAULT 0 AFTER `augment_five`, +CHANGE COLUMN `ornamenticon` `ornament_icon` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `custom_data`, +CHANGE COLUMN `ornamentidfile` `ornament_idfile` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `ornament_icon`, +DROP PRIMARY KEY, +ADD PRIMARY KEY (`character_id`, `slot_id`) USING BTREE; +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 251) + 4010) WHERE `slot_id` BETWEEN 251 AND 260; -- Bag 1 +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 261) + 4210) WHERE `slot_id` BETWEEN 261 AND 270; -- Bag 2 +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 271) + 4410) WHERE `slot_id` BETWEEN 271 AND 280; -- Bag 3 +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 281) + 4610) WHERE `slot_id` BETWEEN 281 AND 290; -- Bag 4 +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 291) + 4810) WHERE `slot_id` BETWEEN 291 AND 300; -- Bag 5 +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 301) + 5010) WHERE `slot_id` BETWEEN 301 AND 310; -- Bag 6 +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 311) + 5210) WHERE `slot_id` BETWEEN 311 AND 320; -- Bag 7 +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 321) + 5410) WHERE `slot_id` BETWEEN 321 AND 330; -- Bag 8 +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 331) + 5610) WHERE `slot_id` BETWEEN 331 AND 340; -- Bag 9 +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 341) + 5810) WHERE `slot_id` BETWEEN 341 AND 350; -- Bag 10 +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 8000) + 6010) WHERE `slot_id` BETWEEN 8000 AND 8199; -- Cursor Overflow +DELETE FROM `inventory` WHERE `slot_id` BETWEEN 8200 AND 8999; -- Extreme Cursor Overflow +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 351) + 6010) WHERE `slot_id` BETWEEN 351 AND 360; -- Cursor Bag +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 2031) + 6210) WHERE `slot_id` BETWEEN 2031 AND 2040; -- Bank Bag 1 +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 2041) + 6410) WHERE `slot_id` BETWEEN 2041 AND 2050; -- Bank Bag 2 +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 2051) + 6610) WHERE `slot_id` BETWEEN 2051 AND 2060; -- Bank Bag 3 +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 2061) + 6810) WHERE `slot_id` BETWEEN 2061 AND 2070; -- Bank Bag 4 +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 2071) + 7010) WHERE `slot_id` BETWEEN 2071 AND 2080; -- Bank Bag 5 +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 2081) + 7210) WHERE `slot_id` BETWEEN 2081 AND 2090; -- Bank Bag 6 +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 2091) + 7410) WHERE `slot_id` BETWEEN 2091 AND 2100; -- Bank Bag 7 +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 2101) + 7610) WHERE `slot_id` BETWEEN 2101 AND 2110; -- Bank Bag 8 +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 2111) + 7810) WHERE `slot_id` BETWEEN 2111 AND 2120; -- Bank Bag 9 +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 2121) + 8010) WHERE `slot_id` BETWEEN 2121 AND 2130; -- Bank Bag 10 +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 2131) + 8210) WHERE `slot_id` BETWEEN 2131 AND 2140; -- Bank Bag 11 +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 2141) + 8410) WHERE `slot_id` BETWEEN 2141 AND 2150; -- Bank Bag 12 +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 2151) + 8610) WHERE `slot_id` BETWEEN 2151 AND 2160; -- Bank Bag 13 +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 2161) + 8810) WHERE `slot_id` BETWEEN 2161 AND 2170; -- Bank Bag 14 +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 2171) + 9010) WHERE `slot_id` BETWEEN 2171 AND 2180; -- Bank Bag 15 +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 2181) + 9210) WHERE `slot_id` BETWEEN 2181 AND 2190; -- Bank Bag 16 +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 2191) + 9410) WHERE `slot_id` BETWEEN 2191 AND 2200; -- Bank Bag 17 +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 2201) + 9610) WHERE `slot_id` BETWEEN 2201 AND 2210; -- Bank Bag 18 +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 2211) + 9810) WHERE `slot_id` BETWEEN 2211 AND 2220; -- Bank Bag 19 +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 2221) + 10010) WHERE `slot_id` BETWEEN 2221 AND 2230; -- Bank Bag 20 +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 2231) + 10210) WHERE `slot_id` BETWEEN 2231 AND 2240; -- Bank Bag 21 +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 2241) + 10410) WHERE `slot_id` BETWEEN 2241 AND 2250; -- Bank Bag 22 +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 2251) + 10610) WHERE `slot_id` BETWEEN 2251 AND 2260; -- Bank Bag 23 +UPDATE `inventory` SET `slot_id` = ((`slot_id` - 2261) + 10810) WHERE `slot_id` BETWEEN 2261 AND 2270; -- Bank Bag 24 +UPDATE `sharedbank` SET `slot_id` = ((`slot_id` - 2531) + 11010) WHERE `slot_id` BETWEEN 2531 AND 2540; -- Shared Bank Bag 1 +UPDATE `sharedbank` SET `slot_id` = ((`slot_id` - 2541) + 11210) WHERE `slot_id` BETWEEN 2541 AND 2550; -- Shared Bank Bag 2 +)", + .content_schema_update = false, + .force_interactive = false + }, + ManifestEntry{ + .version = 9299, + .description = "2024_10_24_merchantlist_temp_uncap.sql", + .check = "SHOW CREATE TABLE `merchantlist_temp`", + .condition = "contains", + .match = "`slot` tinyint(3)", + .sql = R"( +ALTER TABLE `merchantlist_temp` +MODIFY COLUMN `slot` int UNSIGNED NOT NULL DEFAULT 0 AFTER `npcid`; +)", + .content_schema_update = false + }, + ManifestEntry{ + .version = 9300, + .description = "2024_10_15_npc_types_multiquest_enabled.sql", + .check = "SHOW COLUMNS FROM `npc_types` LIKE 'multiquest_enabled'", + .condition = "empty", + .match = "", + .sql = R"( +ALTER TABLE `npc_types` +ADD COLUMN `multiquest_enabled` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 AFTER `is_parcel_merchant`; +)", + .content_schema_update = true + }, + ManifestEntry{ + .version = 9301, + .description = "2024_10_08_add_detail_player_event_logging.sql", + .check = "SHOW COLUMNS FROM `player_event_log_settings` LIKE 'etl_enabled'", + .condition = "empty", + .match = "", + .sql = R"( +ALTER TABLE `player_event_log_settings` + ADD COLUMN `etl_enabled` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0' AFTER `discord_webhook_id`; +ALTER TABLE `player_event_logs` + ADD COLUMN `etl_table_id` BIGINT(20) NOT NULL DEFAULT '0' AFTER `event_data`; +UPDATE `player_event_log_settings` SET `etl_enabled` = 1 WHERE `id` = 14; +CREATE TABLE `player_event_loot_items` ( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `item_id` INT(10) UNSIGNED NULL DEFAULT NULL, + `item_name` VARCHAR(64) NULL DEFAULT NULL COLLATE 'latin1_swedish_ci', + `charges` INT(11) NULL DEFAULT NULL, + `augment_1_id` INT UNSIGNED NULL DEFAULT '0', + `augment_2_id` INT UNSIGNED NULL DEFAULT '0', + `augment_3_id` INT UNSIGNED NULL DEFAULT '0', + `augment_4_id` INT UNSIGNED NULL DEFAULT '0', + `augment_5_id` INT UNSIGNED NULL DEFAULT '0', + `augment_6_id` INT UNSIGNED NULL DEFAULT '0', + `npc_id` INT(10) UNSIGNED NULL DEFAULT NULL, + `corpse_name` VARCHAR(64) NULL DEFAULT NULL COLLATE 'latin1_swedish_ci', + `created_at` DATETIME NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + INDEX `item_id_npc_id` (`item_id`, `npc_id`) USING BTREE, + INDEX `created_at` (`created_at`) USING BTREE +) +COLLATE='latin1_swedish_ci' +ENGINE=InnoDB +AUTO_INCREMENT=1; +UPDATE `player_event_log_settings` SET `etl_enabled` = 1 WHERE `id` = 16; +CREATE TABLE `player_event_merchant_sell` ( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `npc_id` INT(10) UNSIGNED NULL DEFAULT '0', + `merchant_name` VARCHAR(64) NULL DEFAULT NULL COLLATE 'latin1_swedish_ci', + `merchant_type` INT(10) UNSIGNED NULL DEFAULT '0', + `item_id` INT(10) UNSIGNED NULL DEFAULT '0', + `item_name` VARCHAR(64) NULL DEFAULT NULL COLLATE 'latin1_swedish_ci', + `charges` INT(11) NULL DEFAULT '0', + `cost` INT(10) UNSIGNED NULL DEFAULT '0', + `alternate_currency_id` INT(10) UNSIGNED NULL DEFAULT '0', + `player_money_balance` BIGINT(20) UNSIGNED NULL DEFAULT '0', + `player_currency_balance` BIGINT(20) UNSIGNED NULL DEFAULT '0', + `created_at` DATETIME NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + INDEX `item_id_npc_id` (`item_id`, `npc_id`) USING BTREE, + INDEX `created_at` (`created_at`) USING BTREE +) +COLLATE='latin1_swedish_ci' +ENGINE=InnoDB +AUTO_INCREMENT=1; +UPDATE `player_event_log_settings` SET `etl_enabled` = 1 WHERE `id` = 15; +CREATE TABLE `player_event_merchant_purchase` ( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `npc_id` INT(10) UNSIGNED NULL DEFAULT '0', + `merchant_name` VARCHAR(64) NULL DEFAULT NULL COLLATE 'latin1_swedish_ci', + `merchant_type` INT(10) UNSIGNED NULL DEFAULT '0', + `item_id` INT(10) UNSIGNED NULL DEFAULT '0', + `item_name` VARCHAR(64) NULL DEFAULT NULL COLLATE 'latin1_swedish_ci', + `charges` INT(11) NULL DEFAULT '0', + `cost` INT(10) UNSIGNED NULL DEFAULT '0', + `alternate_currency_id` INT(10) UNSIGNED NULL DEFAULT '0', + `player_money_balance` BIGINT(20) UNSIGNED NULL DEFAULT '0', + `player_currency_balance` BIGINT(20) UNSIGNED NULL DEFAULT '0', + `created_at` DATETIME NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + INDEX `item_id_npc_id` (`item_id`, `npc_id`) USING BTREE, + INDEX `created_at` (`created_at`) USING BTREE +) +COLLATE='latin1_swedish_ci' +ENGINE=InnoDB +AUTO_INCREMENT=1; +UPDATE `player_event_log_settings` SET `etl_enabled` = 1 WHERE `id` = 22; +CREATE TABLE `player_event_npc_handin` ( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `npc_id` INT(10) UNSIGNED NULL DEFAULT '0', + `npc_name` VARCHAR(64) NULL DEFAULT NULL COLLATE 'latin1_swedish_ci', + `handin_copper` BIGINT(20) UNSIGNED NULL DEFAULT '0', + `handin_silver` BIGINT(20) UNSIGNED NULL DEFAULT '0', + `handin_gold` BIGINT(20) UNSIGNED NULL DEFAULT '0', + `handin_platinum` BIGINT(20) UNSIGNED NULL DEFAULT '0', + `return_copper` BIGINT(20) UNSIGNED NULL DEFAULT '0', + `return_silver` BIGINT(20) UNSIGNED NULL DEFAULT '0', + `return_gold` BIGINT(20) UNSIGNED NULL DEFAULT '0', + `return_platinum` BIGINT(20) UNSIGNED NULL DEFAULT '0', + `is_quest_handin` TINYINT(3) UNSIGNED NULL DEFAULT '0', + `created_at` DATETIME NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + INDEX `npc_id_is_quest_handin` (`npc_id`, `is_quest_handin`) USING BTREE, + INDEX `created_at` (`created_at`) USING BTREE +) +COLLATE='latin1_swedish_ci' +ENGINE=InnoDB +AUTO_INCREMENT=1; +CREATE TABLE `player_event_npc_handin_entries` ( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `player_event_npc_handin_id` BIGINT(20) UNSIGNED NOT NULL DEFAULT '0', + `type` INT(10) UNSIGNED NULL DEFAULT NULL, + `item_id` INT(10) UNSIGNED NOT NULL DEFAULT '0', + `charges` INT(11) NOT NULL DEFAULT '0', + `evolve_level` INT(10) UNSIGNED NOT NULL DEFAULT '0', + `evolve_amount` BIGINT(20) UNSIGNED NOT NULL DEFAULT '0', + `augment_1_id` INT(10) UNSIGNED NOT NULL DEFAULT '0', + `augment_2_id` INT(10) UNSIGNED NOT NULL DEFAULT '0', + `augment_3_id` INT(10) UNSIGNED NOT NULL DEFAULT '0', + `augment_4_id` INT(10) UNSIGNED NOT NULL DEFAULT '0', + `augment_5_id` INT(10) UNSIGNED NOT NULL DEFAULT '0', + `augment_6_id` INT(10) UNSIGNED NOT NULL DEFAULT '0', + `created_at` DATETIME NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + INDEX `type_item_id` (`type`, `item_id`) USING BTREE, + INDEX `player_event_npc_handin_id` (`player_event_npc_handin_id`) USING BTREE, + INDEX `created_at` (`created_at`) USING BTREE +) +COLLATE='latin1_swedish_ci' +ENGINE=InnoDB +AUTO_INCREMENT=1; +UPDATE `player_event_log_settings` SET `etl_enabled` = 1 WHERE `id` = 27; +CREATE TABLE `player_event_trade` ( + `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `char1_id` INT(10) UNSIGNED NULL DEFAULT '0', + `char2_id` INT(10) UNSIGNED NULL DEFAULT '0', + `char1_copper` BIGINT(20) UNSIGNED NULL DEFAULT '0', + `char1_silver` BIGINT(20) UNSIGNED NULL DEFAULT '0', + `char1_gold` BIGINT(20) UNSIGNED NULL DEFAULT '0', + `char1_platinum` BIGINT(20) UNSIGNED NULL DEFAULT '0', + `char2_copper` BIGINT(20) UNSIGNED NULL DEFAULT '0', + `char2_silver` BIGINT(20) UNSIGNED NULL DEFAULT '0', + `char2_gold` BIGINT(20) UNSIGNED NULL DEFAULT '0', + `char2_platinum` BIGINT(20) UNSIGNED NULL DEFAULT '0', + `created_at` DATETIME NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + INDEX `char1_id_char2_id` (`char1_id`, `char2_id`) USING BTREE, + INDEX `created_at` (`created_at`) USING BTREE +) +COLLATE='latin1_swedish_ci' +ENGINE=InnoDB +AUTO_INCREMENT=1; +CREATE TABLE `player_event_trade_entries` ( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `player_event_trade_id` BIGINT(20) UNSIGNED NULL DEFAULT '0', + `char_id` INT(10) UNSIGNED NULL DEFAULT '0', + `slot` SMALLINT(6) NULL DEFAULT '0', + `item_id` INT(10) UNSIGNED NULL DEFAULT '0', + `charges` SMALLINT(6) NULL DEFAULT '0', + `augment_1_id` INT(10) UNSIGNED NULL DEFAULT '0', + `augment_2_id` INT(10) UNSIGNED NULL DEFAULT '0', + `augment_3_id` INT(10) UNSIGNED NULL DEFAULT '0', + `augment_4_id` INT(10) UNSIGNED NULL DEFAULT '0', + `augment_5_id` INT(10) UNSIGNED NULL DEFAULT '0', + `augment_6_id` INT(10) UNSIGNED NULL DEFAULT '0', + `in_bag` TINYINT(4) NULL DEFAULT '0', + `created_at` DATETIME NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + INDEX `player_event_trade_id` (`player_event_trade_id`) USING BTREE, + INDEX `created_at` (`created_at`) USING BTREE +) +COLLATE='latin1_swedish_ci' +ENGINE=InnoDB +AUTO_INCREMENT=1; +UPDATE `player_event_log_settings` SET `etl_enabled` = 0 WHERE `id` = 54; +CREATE TABLE `player_event_speech` ( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `to_char_id` VARCHAR(64) NULL DEFAULT NULL COLLATE 'latin1_swedish_ci', + `from_char_id` VARCHAR(64) NULL DEFAULT NULL COLLATE 'latin1_swedish_ci', + `guild_id` INT(10) UNSIGNED NULL DEFAULT '0', + `type` INT(10) UNSIGNED NULL DEFAULT '0', + `min_status` INT(10) UNSIGNED NULL DEFAULT '0', + `message` LONGTEXT NULL DEFAULT NULL COLLATE 'latin1_swedish_ci', + `created_at` DATETIME NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + INDEX `to_char_id_from_char_id` (`to_char_id`, `from_char_id`) USING BTREE, + INDEX `created_at` (`created_at`) USING BTREE +) +COLLATE='latin1_swedish_ci' +ENGINE=InnoDB +AUTO_INCREMENT=1; +UPDATE `player_event_log_settings` SET `etl_enabled` = 1 WHERE `id` = 44; +CREATE TABLE `player_event_killed_npc` ( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `npc_id` INT(10) UNSIGNED NULL DEFAULT '0', + `npc_name` VARCHAR(64) NULL DEFAULT NULL COLLATE 'latin1_swedish_ci', + `combat_time_seconds` INT(10) UNSIGNED NULL DEFAULT '0', + `total_damage_per_second_taken` BIGINT(20) UNSIGNED NULL DEFAULT '0', + `total_heal_per_second_taken` BIGINT(20) UNSIGNED NULL DEFAULT '0', + `created_at` DATETIME NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + INDEX `npc_id` (`npc_id`) USING BTREE, + INDEX `created_at` (`created_at`) USING BTREE +) +COLLATE='latin1_swedish_ci' +ENGINE=InnoDB; +UPDATE `player_event_log_settings` SET `etl_enabled` = 1 WHERE `id` = 45; +CREATE TABLE `player_event_killed_named_npc` ( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `npc_id` INT(10) UNSIGNED NULL DEFAULT '0', + `npc_name` VARCHAR(64) NULL DEFAULT NULL COLLATE 'latin1_swedish_ci', + `combat_time_seconds` INT(10) UNSIGNED NULL DEFAULT '0', + `total_damage_per_second_taken` BIGINT(20) UNSIGNED NULL DEFAULT '0', + `total_heal_per_second_taken` BIGINT(20) UNSIGNED NULL DEFAULT '0', + `created_at` DATETIME NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + INDEX `npc_id` (`npc_id`) USING BTREE, + INDEX `created_at` (`created_at`) USING BTREE +) +COLLATE='latin1_swedish_ci' +ENGINE=InnoDB; +UPDATE `player_event_log_settings` SET `etl_enabled` = 1 WHERE `id` = 46; +CREATE TABLE `player_event_killed_raid_npc` ( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `npc_id` INT(10) UNSIGNED NULL DEFAULT '0', + `npc_name` VARCHAR(64) NULL DEFAULT NULL COLLATE 'latin1_swedish_ci', + `combat_time_seconds` INT(10) UNSIGNED NULL DEFAULT '0', + `total_damage_per_second_taken` BIGINT(20) UNSIGNED NULL DEFAULT '0', + `total_heal_per_second_taken` BIGINT(20) UNSIGNED NULL DEFAULT '0', + `created_at` DATETIME NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + INDEX `npc_id` (`npc_id`) USING BTREE, + INDEX `created_at` (`created_at`) USING BTREE +) +COLLATE='latin1_swedish_ci' +ENGINE=InnoDB; +UPDATE `player_event_log_settings` SET `etl_enabled` = 1 WHERE `id` = 4; +CREATE TABLE `player_event_aa_purchase` ( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `aa_ability_id` INT(11) NULL DEFAULT '0', + `cost` INT(11) NULL DEFAULT '0', + `previous_id` INT(11) NULL DEFAULT '0', + `next_id` INT(11) NULL DEFAULT '0', + `created_at` DATETIME NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + INDEX `created_at` (`created_at`) USING BTREE +) +COLLATE='latin1_swedish_ci' +ENGINE=InnoDB +; +)" + }, + ManifestEntry{ + .version = 9302, + .description = "2025_02_09_illusion_block.sql", + .check = "SHOW COLUMNS FROM `character_data` LIKE 'illusion_block'", + .condition = "empty", + .match = "", + .sql = R"( +ALTER TABLE `character_data` + ADD COLUMN `illusion_block` TINYINT(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `deleted_at`; + +UPDATE `command_settings` +SET `aliases` = + CASE + WHEN LENGTH(`aliases`) > 0 AND `aliases` NOT LIKE '%|ib%' + THEN CONCAT(`aliases`, '|ib') + WHEN LENGTH(`aliases`) = 0 + THEN 'ib' + ELSE `aliases` + END +WHERE `command` = 'illusionblock' +AND `aliases` NOT LIKE '%ib%'; +)", + }, + ManifestEntry{ + .version = 9303, + .description = "2025_02_13_corpse_slot_fix.sql", + .check = "SELECT * FROM `character_corpse_items` WHERE `equip_slot` BETWEEN 251 AND 350", + .condition = "not_empty", + .match = "", + .sql = R"( +UPDATE `character_corpse_items` SET `equip_slot` = ((`equip_slot` - 251) + 4010) WHERE `equip_slot` BETWEEN 251 AND 260; -- Bag 1 +UPDATE `character_corpse_items` SET `equip_slot` = ((`equip_slot` - 261) + 4210) WHERE `equip_slot` BETWEEN 261 AND 270; -- Bag 2 +UPDATE `character_corpse_items` SET `equip_slot` = ((`equip_slot` - 271) + 4410) WHERE `equip_slot` BETWEEN 271 AND 280; -- Bag 3 +UPDATE `character_corpse_items` SET `equip_slot` = ((`equip_slot` - 281) + 4610) WHERE `equip_slot` BETWEEN 281 AND 290; -- Bag 4 +UPDATE `character_corpse_items` SET `equip_slot` = ((`equip_slot` - 291) + 4810) WHERE `equip_slot` BETWEEN 291 AND 300; -- Bag 5 +UPDATE `character_corpse_items` SET `equip_slot` = ((`equip_slot` - 301) + 5010) WHERE `equip_slot` BETWEEN 301 AND 310; -- Bag 6 +UPDATE `character_corpse_items` SET `equip_slot` = ((`equip_slot` - 311) + 5210) WHERE `equip_slot` BETWEEN 311 AND 320; -- Bag 7 +UPDATE `character_corpse_items` SET `equip_slot` = ((`equip_slot` - 321) + 5410) WHERE `equip_slot` BETWEEN 321 AND 330; -- Bag 8 +UPDATE `character_corpse_items` SET `equip_slot` = ((`equip_slot` - 331) + 5610) WHERE `equip_slot` BETWEEN 331 AND 340; -- Bag 9 +UPDATE `character_corpse_items` SET `equip_slot` = ((`equip_slot` - 341) + 5810) WHERE `equip_slot` BETWEEN 341 AND 350; -- Bag 10 +)", + }, + ManifestEntry{ + .version = 9304, + .description = "2024_12_01_update_guild_bank", + .check = "SHOW COLUMNS FROM `guild_bank` LIKE 'augment_one_id'", + .condition = "empty", + .match = "", + .sql = R"( +ALTER TABLE `guild_bank` + DROP INDEX `guildid`, + CHANGE COLUMN `guildid` `guild_id` INT(10) UNSIGNED NOT NULL DEFAULT '0' AFTER `id`, + CHANGE COLUMN `itemid` `item_id` INT(10) UNSIGNED NOT NULL DEFAULT '0' AFTER `slot`, + CHANGE COLUMN `whofor` `who_for` VARCHAR(64) NULL DEFAULT NULL COLLATE 'utf8_general_ci' AFTER `permissions`, + ADD COLUMN `augment_one_id` INT UNSIGNED NULL DEFAULT '0' AFTER `item_id`, + ADD COLUMN `augment_two_id` INT UNSIGNED NULL DEFAULT '0' AFTER `augment_one_id`, + ADD COLUMN `augment_three_id` INT UNSIGNED NULL DEFAULT '0' AFTER `augment_two_id`, + ADD COLUMN `augment_four_id` INT UNSIGNED NULL DEFAULT '0' AFTER `augment_three_id`, + ADD COLUMN `augment_five_id` INT UNSIGNED NULL DEFAULT '0' AFTER `augment_four_id`, + ADD COLUMN `augment_six_id` INT UNSIGNED NULL DEFAULT '0' AFTER `augment_five_id`, + CHANGE COLUMN `qty` `quantity` INT(10) NOT NULL DEFAULT '0' AFTER `augment_six_id`; +ALTER TABLE `guild_bank` + ADD INDEX `guild_id` (`guild_id`); +)" + }, + ManifestEntry{ + .version = 9305, + .description = "2024_12_01_expedition_dz_merge.sql", + .check = "SHOW COLUMNS FROM `dynamic_zones` LIKE 'is_locked'", + .condition = "empty", + .match = "", + .sql = R"( +ALTER TABLE `dynamic_zones` + ADD COLUMN `is_locked` TINYINT NOT NULL DEFAULT '0' AFTER `has_zone_in`, + ADD COLUMN `add_replay` TINYINT NOT NULL DEFAULT '1' AFTER `is_locked`; + +ALTER TABLE `expedition_lockouts` + CHANGE COLUMN `expedition_id` `dynamic_zone_id` INT(10) UNSIGNED NOT NULL AFTER `id`, + DROP INDEX `expedition_id_event_name`, + ADD UNIQUE INDEX `dz_id_event_name` (`dynamic_zone_id`, `event_name`) USING BTREE; + +UPDATE expedition_lockouts lockouts + INNER JOIN expeditions ON lockouts.dynamic_zone_id = expeditions.id + SET lockouts.dynamic_zone_id = expeditions.dynamic_zone_id; + +DROP TABLE `expeditions`; + +RENAME TABLE `expedition_lockouts` TO `dynamic_zone_lockouts`; +)" + }, + ManifestEntry{ + .version = 9306, + .description = "2025_02_16_data_buckets_zone_id_instance_id.sql", + .check = "SHOW COLUMNS FROM `data_buckets` LIKE 'zone_id'", + .condition = "empty", + .match = "", + .sql = R"( +-- 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`; + +-- Add zone_id, instance_id +ALTER TABLE `data_buckets` + MODIFY COLUMN `npc_id` int(11) NOT NULL DEFAULT 0 AFTER `character_id`, + MODIFY COLUMN `bot_id` int(11) NOT NULL DEFAULT 0 AFTER `npc_id`, + ADD COLUMN `zone_id` smallint(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `bot_id`, + ADD COLUMN `instance_id` smallint(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `zone_id`; + +ALTER TABLE `data_buckets` + MODIFY COLUMN `account_id` bigint(11) UNSIGNED NULL DEFAULT 0 AFTER `expires`, + MODIFY COLUMN `character_id` bigint(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `account_id`, + MODIFY COLUMN `npc_id` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `character_id`, + MODIFY COLUMN `bot_id` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `npc_id`; + +-- 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 INDEX idx_instance_id ON data_buckets (instance_id); +)", + .content_schema_update = false + }, + ManifestEntry{ + .version = 9307, + .description = "2025_02_17_zone_state_spawns.sql", + .check = "SHOW TABLES LIKE 'zone_state_spawns'", + .condition = "empty", + .match = "", + .sql = R"( +CREATE TABLE `zone_state_spawns` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `zone_id` int(11) unsigned DEFAULT NULL, + `instance_id` int(11) unsigned DEFAULT NULL, + `is_corpse` tinyint(11) DEFAULT 0, + `decay_in_seconds` int(11) DEFAULT 0, + `npc_id` int(10) unsigned DEFAULT NULL, + `spawn2_id` int(10) unsigned NOT NULL, + `spawngroup_id` int(10) unsigned NOT NULL, + `x` float NOT NULL, + `y` float NOT NULL, + `z` float NOT NULL, + `heading` float NOT NULL, + `respawn_time` int(10) unsigned NOT NULL, + `variance` int(10) unsigned NOT NULL, + `grid` int(10) unsigned DEFAULT 0, + `current_waypoint` int(11) DEFAULT 0, + `path_when_zone_idle` smallint(6) DEFAULT 0, + `condition_id` smallint(5) unsigned DEFAULT 0, + `condition_min_value` smallint(6) DEFAULT 0, + `enabled` smallint(6) DEFAULT 1, + `anim` smallint(5) unsigned DEFAULT 0, + `loot_data` text DEFAULT NULL, + `entity_variables` text DEFAULT NULL, + `buffs` text DEFAULT NULL, + `hp` bigint(20) DEFAULT 0, + `mana` bigint(20) DEFAULT 0, + `endurance` bigint(20) DEFAULT 0, + `created_at` datetime DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 +)", + .content_schema_update = false + }, + ManifestEntry{ + .version = 9308, + .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)", + .sql = R"( +ALTER TABLE `items_evolving_details` + CHANGE COLUMN `sub_type` `sub_type` VARCHAR(200) NULL DEFAULT '0' AFTER `type`; +)", + .content_schema_update = true + }, + // this one got missed being added to PEQ dumps so adding it again so it gets added when folks take a new release + ManifestEntry{ + .version = 9309, + .description = "2025_03_1_create_pet_names_table_if_not_exist.sql", + .check = "SHOW TABLES LIKE 'character_pet_name'", + .condition = "empty", + .match = "", + .sql = R"( +CREATE TABLE `character_pet_name` ( + `character_id` INT(11) NOT NULL PRIMARY KEY, + `name` VARCHAR(64) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +)", + }, + ManifestEntry{ + .version = 9310, + .description = "2025_03_7_expand_horse_def.sql", + .check = "SHOW COLUMNS FROM `horses` LIKE 'helmtexture'", + .condition = "empty", + .match = "", + .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 + }, + ManifestEntry{ + .version = 9322, + .description = "2025_04_24_add_npc_tint_id.sql", + .check = "SHOW COLUMNS FROM `npc_types` LIKE 'npc_tint_id'", + .condition = "empty", + .match = "", + .sql = R"( +ALTER TABLE `npc_types` + ADD COLUMN `npc_tint_id` SMALLINT UNSIGNED NULL DEFAULT '0' AFTER `multiquest_enabled`; +)", + .content_schema_update = true + }, + ManifestEntry{ + .version = 9323, + .description = "2025_04_16_character_data_first_login.sql", + .check = "SHOW COLUMNS FROM `character_data` LIKE 'first_login'", + .condition = "empty", + .match = "", + .sql = R"( +ALTER TABLE `character_data` +CHANGE COLUMN `firstlogon` `ingame` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 AFTER `xtargets`, +ADD COLUMN `first_login` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `xtargets`; +)", + .content_schema_update = false + }, + ManifestEntry{ + .version = 9324, + .description = "2025_06_11_player_event_logs_table.sql", + .check = "SHOW CREATE TABLE `player_event_logs`", + .condition = "missing", + .match = "COMPRESS", + .sql = R"( +ALTER TABLE player_event_logs ROW_FORMAT=COMPRESSED; +CREATE INDEX idx_event_type_char_id ON player_event_logs (event_type_id, character_id); +)", + .content_schema_update = false + }, + ManifestEntry{ + .version = 9325, + .description = "2025_06_10_character_corpses_entity_variables.sql", + .check = "SHOW COLUMNS FROM `character_corpses` LIKE 'entity_variables'", + .condition = "empty", + .match = "", + .sql = R"( +ALTER TABLE `character_corpses` +ADD COLUMN `entity_variables` TEXT DEFAULT NULL AFTER `rezzable`; +)", + .content_schema_update = false + }, + ManifestEntry{ + .version = 9326, + .description = "2025_07_27_add_indexes_npc_spawns_loot.sql", + .check = "SHOW INDEX FROM npc_types", + .condition = "missing", + .match = "idx_npc_types_loottable_id", + .sql = R"( +ALTER TABLE npc_types + ADD INDEX idx_npc_types_loottable_id (loottable_id); + +ALTER TABLE spawnentry + ADD INDEX idx_spawnentry_spawngroup_id (spawngroupID), + ADD INDEX idx_spawnentry_npc_id (npcID); + +ALTER TABLE lootdrop_entries + ADD INDEX idx_lootdrop_entries_lootdrop_id (lootdrop_id), + ADD INDEX idx_lootdrop_entries_item_id (item_id); + +ALTER TABLE loottable_entries + ADD INDEX idx_loottable_entries_lootdrop_id (lootdrop_id), + ADD INDEX idx_loottable_entries_loottable_id (loottable_id); +)", + .content_schema_update = true + }, + ManifestEntry{ + .version = 9327, + .description = "2025_08_13_character_stats_record_heal_amount.sql", + .check = "SHOW COLUMNS FROM `character_stats_record` LIKE 'heal_amount'", + .condition = "empty", + .match = "", + .sql = R"( +ALTER TABLE `character_stats_record` +ADD COLUMN `heal_amount` int(11) NULL DEFAULT 0 AFTER `spell_damage`; +)" + }, + ManifestEntry{ + .version = 9328, + .description = "2025_08_22_character_parcel_updates.sql", + .check = "SHOW COLUMNS FROM `character_parcels` LIKE 'evolve_amount'", + .condition = "empty", + .match = "", + .sql = R"( +ALTER TABLE `character_parcels` + ADD COLUMN `evolve_amount` INT UNSIGNED NOT NULL DEFAULT '0' AFTER `quantity`; + +ALTER TABLE `character_parcels_containers` + ADD COLUMN `evolve_amount` INT UNSIGNED NOT NULL DEFAULT '0' AFTER `quantity`; +)", + .content_schema_update = false + }, // -- template; copy/paste this when you need to create a new entry // ManifestEntry{ // .version = 9228, diff --git a/common/database/database_update_manifest_bots.cpp b/common/database/database_update_manifest_bots.cpp index 48c9aa7f8..c2da0fd70 100644 --- a/common/database/database_update_manifest_bots.cpp +++ b/common/database/database_update_manifest_bots.cpp @@ -104,7 +104,7 @@ ALTER TABLE `bot_timers` DROP PRIMARY KEY; ALTER TABLE `bot_timers` ADD PRIMARY KEY (`bot_id`, `timer_id`, `spell_id`, `item_id`); -)" +)", }, ManifestEntry{ .version = 9042, @@ -123,7 +123,7 @@ ALTER TABLE `bot_pets` DROP FOREIGN KEY `FK_bot_pets_1`; ALTER TABLE `bot_pet_buffs` DROP FOREIGN KEY `FK_bot_pet_buffs_1`; ALTER TABLE `bot_pet_inventories` DROP FOREIGN KEY `FK_bot_pet_inventories_1`; ALTER TABLE `bot_stances` DROP FOREIGN KEY `FK_bot_stances_1`; -)" +)", }, ManifestEntry{ .version = 9043, @@ -139,7 +139,7 @@ ADD COLUMN `augment_three` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `augment_tw ADD COLUMN `augment_four` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `augment_three`, ADD COLUMN `augment_five` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `augment_four`, ADD COLUMN `augment_six` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `augment_five`; -)" +)", }, ManifestEntry{ .version = 9044, @@ -150,7 +150,7 @@ ADD COLUMN `augment_six` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `augment_five .sql = R"( ALTER TABLE `bot_data` ADD COLUMN `extra_haste` mediumint(8) NOT NULL DEFAULT 0 AFTER `wis`; -)" +)", }, ManifestEntry{ .version = 9045, @@ -161,7 +161,1967 @@ ADD COLUMN `extra_haste` mediumint(8) NOT NULL DEFAULT 0 AFTER `wis`; .sql = R"( ALTER TABLE `bot_spells_entries` CHANGE COLUMN `spellid` `spell_id` smallint(5) UNSIGNED NOT NULL DEFAULT 0 AFTER `npc_spells_id`; -)" +)", + }, + ManifestEntry{ + .version = 9046, + .description = "2024_05_18_bot_settings.sql", + .check = "SHOW TABLES LIKE 'bot_settings'", + .condition = "empty", + .match = "", + .sql = R"( +CREATE TABLE `bot_settings` ( + `character_id` INT(10) UNSIGNED NOT NULL, + `bot_id` INT(10) UNSIGNED NOT NULL, + `stance` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0', + `setting_id` SMALLINT(5) UNSIGNED NOT NULL DEFAULT '0', + `setting_type` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0', + `value` BIGINT(19) NOT NULL DEFAULT '0', + `category_name` VARCHAR(64) NULL DEFAULT '' COLLATE 'utf8mb4_general_ci', + `setting_name` VARCHAR(64) NULL DEFAULT '' COLLATE 'utf8mb4_general_ci', + PRIMARY KEY (`character_id`, `bot_id`, `stance`, `setting_id`, `setting_type`) USING BTREE +) +COLLATE='utf8mb4_general_ci' +ENGINE=InnoDB +; + +INSERT INTO bot_settings SELECT 0, bd.`bot_id`, (SELECT bs.`stance_id` FROM bot_stances bs WHERE bs.`bot_id` = bd.`bot_id`) AS stance_id, 0, 0, bd.`expansion_bitmask`, 'BaseSetting', 'ExpansionBitmask' FROM bot_data bd +JOIN rule_values rv +WHERE rv.rule_name LIKE 'Bots:BotExpansionSettings' +AND bd.expansion_bitmask != rv.rule_value; + +INSERT INTO bot_settings +SELECT 0, bd.bot_id, bs.stance_id, 1, 0, bd.show_helm, 'BaseSetting', 'ShowHelm' +FROM bot_data bd +INNER JOIN bot_stances bs ON bd.bot_id = bs.bot_id +WHERE bd.show_helm != 1 +GROUP BY bd.bot_id; + +INSERT INTO bot_settings +SELECT 0, bd.bot_id, bs.stance_id, 2, 0, SQRT(bd.follow_distance), 'BaseSetting', 'FollowDistance' +FROM bot_data bd +INNER JOIN bot_stances bs ON bd.bot_id = bs.bot_id +WHERE bd.follow_distance != 184 +GROUP BY bd.bot_id; + +INSERT INTO bot_settings +SELECT 0, bd.bot_id, bs.stance_id, 3, 0, bd.stop_melee_level, 'BaseSetting', 'StopMeleeLevel' +FROM bot_data bd +INNER JOIN bot_stances bs ON bd.bot_id = bs.bot_id +WHERE (CASE + WHEN bd.class IN (2, 6, 10, 11, 12, 13, 14) THEN 13 + ELSE 255 +END) != bd.stop_melee_level +GROUP BY bd.bot_id; + +INSERT INTO bot_settings +SELECT 0, bd.bot_id, bs.stance_id, 4, 0, bd.enforce_spell_settings, 'BaseSetting', 'EnforceSpellSettings' +FROM bot_data bd +INNER JOIN bot_stances bs ON bd.bot_id = bs.bot_id +WHERE bd.enforce_spell_settings != 0 +GROUP BY bd.bot_id; + +INSERT INTO bot_settings +SELECT 0, bd.bot_id, bs.stance_id, 5, 0, bd.archery_setting, 'BaseSetting', 'RangedSetting' +FROM bot_data bd +INNER JOIN bot_stances bs ON bd.bot_id = bs.bot_id +WHERE bd.archery_setting != 0 +GROUP BY bd.bot_id; + +INSERT INTO bot_settings +SELECT 0, bd.bot_id, bs.stance_id, 8, 0, bd.caster_range, 'BaseSetting', 'DistanceRanged' +FROM bot_data bd +INNER JOIN bot_stances bs ON bd.bot_id = bs.bot_id +WHERE (CASE + WHEN bd.class IN (1, 7, 9, 16) THEN 0 + WHEN bd.class = 8 THEN 30 + ELSE 90 +END) != bd.caster_range +GROUP BY bd.bot_id; + +ALTER TABLE `bot_data` + DROP COLUMN `show_helm`; +ALTER TABLE `bot_data` + DROP COLUMN `follow_distance`; +ALTER TABLE `bot_data` + DROP COLUMN `stop_melee_level`; +ALTER TABLE `bot_data` + DROP COLUMN `expansion_bitmask`; +ALTER TABLE `bot_data` + DROP COLUMN `enforce_spell_settings`; +ALTER TABLE `bot_data` + DROP COLUMN `archery_setting`; +ALTER TABLE `bot_data` + DROP COLUMN `caster_range`; + +UPDATE `bot_command_settings` SET `aliases`= 'bh' WHERE `bot_command`='behindmob'; +UPDATE `bot_command_settings` SET `aliases`= 'bs|settings' WHERE `bot_command`='botsettings'; +UPDATE `bot_command_settings` SET `aliases`= CASE WHEN LENGTH(`aliases`) > 0 THEN CONCAT(`aliases`, '|followdistance') ELSE 'followd||followdistance' END WHERE `bot_command`='botfollowdistance' AND `aliases` NOT LIKE '%followdistance%'; +UPDATE `bot_command_settings` SET `aliases`= CASE WHEN LENGTH(`aliases`) > 0 THEN CONCAT(`aliases`, '|ranged|toggleranged|btr') ELSE 'ranged|toggleranged|btr' END WHERE `bot_command`='bottoggleranged' AND `aliases` NOT LIKE '%ranged|toggleranged|btr%'; +UPDATE `bot_command_settings` SET `aliases`= 'distranged|dr' WHERE `bot_command`='distanceranged'; +UPDATE `bot_command_settings` SET `aliases`= 'copy' WHERE `bot_command`='copysettings'; +UPDATE `bot_command_settings` SET `aliases`= 'default' WHERE `bot_command`='defaultsettings'; +UPDATE `bot_command_settings` SET `aliases`= CASE WHEN LENGTH(`aliases`) > 0 THEN CONCAT(`aliases`, '|enforce') ELSE 'enforce' END WHERE `bot_command`='enforcespellsettings' AND `aliases` NOT LIKE '%enforce%'; +UPDATE `bot_command_settings` SET `aliases`= CASE WHEN LENGTH(`aliases`) > 0 THEN CONCAT(`aliases`, '|ib') ELSE 'ib' END WHERE `bot_command`='illusionblock' AND `aliases` NOT LIKE '%ib%'; +UPDATE `bot_command_settings` SET `aliases`= CASE WHEN LENGTH(`aliases`) > 0 THEN CONCAT(`aliases`, '|ig') ELSE 'invgive|ig' END WHERE `bot_command`='inventorygive' AND `aliases` NOT LIKE '%ig%'; +UPDATE `bot_command_settings` SET `aliases`= CASE WHEN LENGTH(`aliases`) > 0 THEN CONCAT(`aliases`, '|il') ELSE 'invlist|il' END WHERE `bot_command`='inventorylist' AND `aliases` NOT LIKE '%il%'; +UPDATE `bot_command_settings` SET `aliases`= CASE WHEN LENGTH(`aliases`) > 0 THEN CONCAT(`aliases`, '|ir') ELSE 'invremove|ir' END WHERE `bot_command`='inventoryremove' AND `aliases` NOT LIKE '%ir%'; +UPDATE `bot_command_settings` SET `aliases`= CASE WHEN LENGTH(`aliases`) > 0 THEN CONCAT(`aliases`, '|iw') ELSE 'invwindow|iw' END WHERE `bot_command`='inventorywindow' AND `aliases` NOT LIKE '%iw%'; +UPDATE `bot_command_settings` SET `aliases`= CASE WHEN LENGTH(`aliases`) > 0 THEN CONCAT(`aliases`, '|iu') ELSE 'iu' END WHERE `bot_command`='itemuse' AND `aliases` NOT LIKE '%iu%'; +UPDATE `bot_command_settings` SET `aliases`= CASE WHEN LENGTH(`aliases`) > 0 THEN CONCAT(`aliases`, '|pst') ELSE 'pset||pst' END WHERE `bot_command`='petsettype' AND `aliases` NOT LIKE '%pst%'; +UPDATE `bot_command_settings` SET `aliases`= 'mmr' WHERE `bot_command`='maxmeleerange'; +UPDATE `bot_command_settings` SET `aliases`= CASE WHEN LENGTH(`aliases`) > 0 THEN CONCAT(`aliases`, '|pp') ELSE 'pp' END WHERE `bot_command`='pickpocket' AND `aliases` NOT LIKE '%pp%'; +UPDATE `bot_command_settings` SET `aliases`= 'sithp' WHERE `bot_command`='sithppercent'; +UPDATE `bot_command_settings` SET `aliases`= 'sitcombat' WHERE `bot_command`='sitincombat'; +UPDATE `bot_command_settings` SET `aliases`= 'sitmana' WHERE `bot_command`='sitmanapercent'; +UPDATE `bot_command_settings` SET `aliases`= 'aggrochecks' WHERE `bot_command`='spellaggrochecks'; +UPDATE `bot_command_settings` SET `aliases`= 'delays' WHERE `bot_command`='spelldelays'; +UPDATE `bot_command_settings` SET `aliases`= 'engagedpriority' WHERE `bot_command`='spellengagedpriority'; +UPDATE `bot_command_settings` SET `aliases`= 'holds' WHERE `bot_command`='spellholds'; +UPDATE `bot_command_settings` SET `aliases`= 'idlepriority' WHERE `bot_command`='spellidlepriority'; +UPDATE `bot_command_settings` SET `aliases`= 'maxhp' WHERE `bot_command`='spellmaxhppct'; +UPDATE `bot_command_settings` SET `aliases`= 'maxmana' WHERE `bot_command`='spellmaxmanapct'; +UPDATE `bot_command_settings` SET `aliases`= 'maxthresholds' WHERE `bot_command`='spellmaxthresholds'; +UPDATE `bot_command_settings` SET `aliases`= 'minhp' WHERE `bot_command`='spellminhppct'; +UPDATE `bot_command_settings` SET `aliases`= 'minmana' WHERE `bot_command`='spellminmanapct'; +UPDATE `bot_command_settings` SET `aliases`= 'minthresholds' WHERE `bot_command`='spellminthresholds'; +UPDATE `bot_command_settings` SET `aliases`= 'pursuepriority' WHERE `bot_command`='spellpursuepriority'; +UPDATE `bot_command_settings` SET `aliases`= 'resistlimits' WHERE `bot_command`='spellresistlimits'; +UPDATE `bot_command_settings` SET `aliases`= 'targetcount' WHERE `bot_command`='spelltargetcount'; +UPDATE `bot_command_settings` SET `aliases`= 'disc' WHERE `bot_command`='discipline'; +UPDATE `bot_command_settings` SET `aliases`= CASE WHEN LENGTH(`aliases`) > 0 THEN CONCAT(`aliases`, '|vc') ELSE 'vc' END WHERE `bot_command`='viewcombos' AND `aliases` NOT LIKE '%vc%'; +UPDATE `bot_command_settings` SET `aliases`= 'announcecasts' WHERE `bot_command`='spellannouncecasts'; +)", + }, + ManifestEntry{ + .version = 9047, + .description = "2024_05_18_bot_update_spell_types.sql", + .check = "SELECT * FROM `bot_spells_entries` WHERE `type` >= 1024", + .condition = "not_empty", + .match = "", + .sql = R"( +UPDATE `bot_spells_entries` SET `type` = 0 WHERE `type` = 1; +UPDATE `bot_spells_entries` SET `type` = 1 WHERE `type` = 2; +UPDATE `bot_spells_entries` SET `type` = 2 WHERE `type` = 4; +UPDATE `bot_spells_entries` SET `type` = 3 WHERE `type` = 8; +UPDATE `bot_spells_entries` SET `type` = 4 WHERE `type` = 16; +UPDATE `bot_spells_entries` SET `type` = 5 WHERE `type` = 32; +UPDATE `bot_spells_entries` SET `type` = 6 WHERE `type` = 64; +UPDATE `bot_spells_entries` SET `type` = 7 WHERE `type` = 128; +UPDATE `bot_spells_entries` SET `type` = 8 WHERE `type` = 256; +UPDATE `bot_spells_entries` SET `type` = 9 WHERE `type` = 512; +UPDATE `bot_spells_entries` SET `type` = 10 WHERE `type` = 1024; +UPDATE `bot_spells_entries` SET `type` = 11 WHERE `type` = 2048; +UPDATE `bot_spells_entries` SET `type` = 12 WHERE `type` = 4096; +UPDATE `bot_spells_entries` SET `type` = 13 WHERE `type` = 8192; +UPDATE `bot_spells_entries` SET `type` = 14 WHERE `type` = 16384; +UPDATE `bot_spells_entries` SET `type` = 15 WHERE `type` = 32768; +UPDATE `bot_spells_entries` SET `type` = 16 WHERE `type` = 65536; +UPDATE `bot_spells_entries` SET `type` = 17 WHERE `type` = 131072; +UPDATE `bot_spells_entries` SET `type` = 18 WHERE `type` = 262144; +UPDATE `bot_spells_entries` SET `type` = 19 WHERE `type` = 524288; +UPDATE `bot_spells_entries` SET `type` = 20 WHERE `type` = 1048576; +UPDATE `bot_spells_entries` SET `type` = 21 WHERE `type` = 2097152; +)", + }, + ManifestEntry{ + .version = 9048, + .description = "2024_05_18_bot_fear_spell_type.sql", + .check = "SELECT * FROM `bot_spells_entries` where `type` = 22", + .condition = "empty", + .match = "", + .sql = R"( +UPDATE bot_spells_entries b, spells_new s +SET b.`type` = 22 +WHERE b.spell_id = s.id +AND ( + s.`effectid1` = 23 OR + s.`effectid2` = 23 OR + s.`effectid3` = 23 OR + s.`effectid4` = 23 OR + s.`effectid5` = 23 OR + s.`effectid6` = 23 OR + s.`effectid7` = 23 OR + s.`effectid8` = 23 OR + s.`effectid9` = 23 OR + s.`effectid10` = 23 OR + s.`effectid11` = 23 OR + s.`effectid12` = 23 + ); +)", + }, + ManifestEntry{ + .version = 9049, + .description = "2024_05_18_correct_bot_spell_entries_types.sql", + .check = "SELECT * FROM bot_spells_entries WHERE `spell_id` = 14312 AND `npc_spells_id` != 3002", + .condition = "not_empty", + .match = "", + .sql = R"( +-- Class fixes +UPDATE bot_spells_entries SET `npc_spells_id` = 3002 WHERE `spell_id` = 14312 AND `npc_spells_id` != 3002; +UPDATE bot_spells_entries SET `npc_spells_id` = 3002 WHERE `spell_id` = 14313 AND `npc_spells_id` != 3002; +UPDATE bot_spells_entries SET `npc_spells_id` = 3002 WHERE `spell_id` = 14314 AND `npc_spells_id` != 3002; +UPDATE bot_spells_entries SET `npc_spells_id` = 3003 WHERE `spell_id` = 14955 AND `npc_spells_id` != 3003; +UPDATE bot_spells_entries SET `npc_spells_id` = 3003 WHERE `spell_id` = 14956 AND `npc_spells_id` != 3003; +UPDATE bot_spells_entries SET `npc_spells_id` = 3005 WHERE `spell_id` = 15186 AND `npc_spells_id` != 3005; +UPDATE bot_spells_entries SET `npc_spells_id` = 3005 WHERE `spell_id` = 15187 AND `npc_spells_id` != 3005; +UPDATE bot_spells_entries SET `npc_spells_id` = 3005 WHERE `spell_id` = 15188 AND `npc_spells_id` != 3005; +UPDATE bot_spells_entries SET `npc_spells_id` = 3006 WHERE `spell_id` = 14446 AND `npc_spells_id` != 3006; +UPDATE bot_spells_entries SET `npc_spells_id` = 3006 WHERE `spell_id` = 14447 AND `npc_spells_id` != 3006; +UPDATE bot_spells_entries SET `npc_spells_id` = 3006 WHERE `spell_id` = 14467 AND `npc_spells_id` != 3006; +UPDATE bot_spells_entries SET `npc_spells_id` = 3006 WHERE `spell_id` = 14468 AND `npc_spells_id` != 3006; +UPDATE bot_spells_entries SET `npc_spells_id` = 3006 WHERE `spell_id` = 14469 AND `npc_spells_id` != 3006; +UPDATE bot_spells_entries SET `npc_spells_id` = 3006 WHERE `spell_id` = 14387 AND `npc_spells_id` != 3006; +UPDATE bot_spells_entries SET `npc_spells_id` = 3006 WHERE `spell_id` = 14388 AND `npc_spells_id` != 3006; +UPDATE bot_spells_entries SET `npc_spells_id` = 3006 WHERE `spell_id` = 14389 AND `npc_spells_id` != 3006; + +-- Minlevel fixes +UPDATE bot_spells_entries b, spells_new s SET b.`minlevel` = s.classes2 WHERE b.`spell_id` = 1445 AND b.`npc_spells_id` = 3002 AND b.`minlevel` != s.classes2 AND s.classes2 < 254; +UPDATE bot_spells_entries b, spells_new s SET b.`minlevel` = s.classes2 WHERE b.`spell_id` = 14267 AND b.`npc_spells_id` = 3002 AND b.`minlevel` != s.classes2 AND s.classes2 < 254; +UPDATE bot_spells_entries b, spells_new s SET b.`minlevel` = s.classes2 WHERE b.`spell_id` = 14268 AND b.`npc_spells_id` = 3002 AND b.`minlevel` != s.classes2 AND s.classes2 < 254; +UPDATE bot_spells_entries b, spells_new s SET b.`minlevel` = s.classes2 WHERE b.`spell_id` = 14269 AND b.`npc_spells_id` = 3002 AND b.`minlevel` != s.classes2 AND s.classes2 < 254; +UPDATE bot_spells_entries b, spells_new s SET b.`minlevel` = s.classes2 WHERE b.`spell_id` = 14312 AND b.`npc_spells_id` = 3002 AND b.`minlevel` != s.classes2 AND s.classes2 < 254; +UPDATE bot_spells_entries b, spells_new s SET b.`minlevel` = s.classes2 WHERE b.`spell_id` = 14313 AND b.`npc_spells_id` = 3002 AND b.`minlevel` != s.classes2 AND s.classes2 < 254; +UPDATE bot_spells_entries b, spells_new s SET b.`minlevel` = s.classes2 WHERE b.`spell_id` = 14314 AND b.`npc_spells_id` = 3002 AND b.`minlevel` != s.classes2 AND s.classes2 < 254; +UPDATE bot_spells_entries b, spells_new s SET b.`minlevel` = s.classes3 WHERE b.`spell_id` = 2589 AND b.`npc_spells_id` = 3003 AND b.`minlevel` != s.classes3 AND s.classes3 < 254; +UPDATE bot_spells_entries b, spells_new s SET b.`minlevel` = s.classes3 WHERE b.`spell_id` = 14955 AND b.`npc_spells_id` = 3003 AND b.`minlevel` != s.classes3 AND s.classes3 < 254; +UPDATE bot_spells_entries b, spells_new s SET b.`minlevel` = s.classes3 WHERE b.`spell_id` = 14956 AND b.`npc_spells_id` = 3003 AND b.`minlevel` != s.classes3 AND s.classes3 < 254; +UPDATE bot_spells_entries b, spells_new s SET b.`minlevel` = s.classes4 WHERE b.`spell_id` = 5305 AND b.`npc_spells_id` = 3004 AND b.`minlevel` != s.classes4 AND s.classes4 < 254; +UPDATE bot_spells_entries b, spells_new s SET b.`minlevel` = s.classes5 WHERE b.`spell_id` = 15186 AND b.`npc_spells_id` = 3005 AND b.`minlevel` != s.classes5 AND s.classes5 < 254; +UPDATE bot_spells_entries b, spells_new s SET b.`minlevel` = s.classes5 WHERE b.`spell_id` = 15187 AND b.`npc_spells_id` = 3005 AND b.`minlevel` != s.classes5 AND s.classes5 < 254; +UPDATE bot_spells_entries b, spells_new s SET b.`minlevel` = s.classes5 WHERE b.`spell_id` = 15188 AND b.`npc_spells_id` = 3005 AND b.`minlevel` != s.classes5 AND s.classes5 < 254; +UPDATE bot_spells_entries b, spells_new s SET b.`minlevel` = s.classes6 WHERE b.`spell_id` = 14387 AND b.`npc_spells_id` = 3006 AND b.`minlevel` != s.classes6 AND s.classes6 < 254; +UPDATE bot_spells_entries b, spells_new s SET b.`minlevel` = s.classes6 WHERE b.`spell_id` = 14388 AND b.`npc_spells_id` = 3006 AND b.`minlevel` != s.classes6 AND s.classes6 < 254; +UPDATE bot_spells_entries b, spells_new s SET b.`minlevel` = s.classes6 WHERE b.`spell_id` = 14389 AND b.`npc_spells_id` = 3006 AND b.`minlevel` != s.classes6 AND s.classes6 < 254; +UPDATE bot_spells_entries b, spells_new s SET b.`minlevel` = s.classes6 WHERE b.`spell_id` = 14446 AND b.`npc_spells_id` = 3006 AND b.`minlevel` != s.classes6 AND s.classes6 < 254; +UPDATE bot_spells_entries b, spells_new s SET b.`minlevel` = s.classes6 WHERE b.`spell_id` = 14447 AND b.`npc_spells_id` = 3006 AND b.`minlevel` != s.classes6 AND s.classes6 < 254; +UPDATE bot_spells_entries b, spells_new s SET b.`minlevel` = s.classes6 WHERE b.`spell_id` = 14467 AND b.`npc_spells_id` = 3006 AND b.`minlevel` != s.classes6 AND s.classes6 < 254; +UPDATE bot_spells_entries b, spells_new s SET b.`minlevel` = s.classes6 WHERE b.`spell_id` = 14468 AND b.`npc_spells_id` = 3006 AND b.`minlevel` != s.classes6 AND s.classes6 < 254; +UPDATE bot_spells_entries b, spells_new s SET b.`minlevel` = s.classes6 WHERE b.`spell_id` = 14469 AND b.`npc_spells_id` = 3006 AND b.`minlevel` != s.classes6 AND s.classes6 < 254; +UPDATE bot_spells_entries b, spells_new s SET b.`minlevel` = s.classes8 WHERE b.`spell_id` = 717 AND b.`npc_spells_id` = 3008 AND b.`minlevel` != s.classes8 AND s.classes8 < 254; +UPDATE bot_spells_entries b, spells_new s SET b.`minlevel` = s.classes8 WHERE b.`spell_id` = 734 AND b.`npc_spells_id` = 3008 AND b.`minlevel` != s.classes8 AND s.classes8 < 254; +UPDATE bot_spells_entries b, spells_new s SET b.`minlevel` = s.classes8 WHERE b.`spell_id` = 738 AND b.`npc_spells_id` = 3008 AND b.`minlevel` != s.classes8 AND s.classes8 < 254; +UPDATE bot_spells_entries b, spells_new s SET b.`minlevel` = s.classes8 WHERE b.`spell_id` = 1751 AND b.`npc_spells_id` = 3008 AND b.`minlevel` != s.classes8 AND s.classes8 < 254; +UPDATE bot_spells_entries b, spells_new s SET b.`minlevel` = s.classes10 WHERE b.`spell_id` = 754 AND b.`npc_spells_id` = 3010 AND b.`minlevel` != s.classes10 AND s.classes10 < 254; +UPDATE bot_spells_entries b, spells_new s SET b.`minlevel` = s.classes11 WHERE b.`spell_id` = 229 AND b.`npc_spells_id` = 3011 AND b.`minlevel` != s.classes11 AND s.classes11 < 254; +UPDATE bot_spells_entries b, spells_new s SET b.`minlevel` = s.classes13 WHERE b.`spell_id` = 333 AND b.`npc_spells_id` = 3013 AND b.`minlevel` != s.classes13 AND s.classes13 < 254; + +-- Maxlevel fixes +UPDATE bot_spells_entries SET `maxlevel` = 83 WHERE `spell_id` = 14267 AND `npc_spells_id` = 3002 AND `maxlevel` < 83; +UPDATE bot_spells_entries SET `maxlevel` = 83 WHERE `spell_id` = 14268 AND `npc_spells_id` = 3002 AND `maxlevel` < 83; +UPDATE bot_spells_entries SET `maxlevel` = 83 WHERE `spell_id` = 14269 AND `npc_spells_id` = 3002 AND `maxlevel` < 83; +UPDATE bot_spells_entries SET `maxlevel` = 84 WHERE `spell_id` = 14446 AND `npc_spells_id` = 3006 AND `maxlevel` < 84; +UPDATE bot_spells_entries SET `maxlevel` = 84 WHERE `spell_id` = 14447 AND `npc_spells_id` = 3006 AND `maxlevel` < 84; +UPDATE bot_spells_entries SET `maxlevel` = 84 WHERE `spell_id` = 14467 AND `npc_spells_id` = 3006 AND `maxlevel` < 84; +UPDATE bot_spells_entries SET `maxlevel` = 84 WHERE `spell_id` = 14468 AND `npc_spells_id` = 3006 AND `maxlevel` < 84; +UPDATE bot_spells_entries SET `maxlevel` = 84 WHERE `spell_id` = 14469 AND `npc_spells_id` = 3006 AND `maxlevel` < 84; +UPDATE bot_spells_entries SET `maxlevel` = 81 WHERE `spell_id` = 14312 AND `npc_spells_id` = 3002 AND `maxlevel` < 81; +UPDATE bot_spells_entries SET `maxlevel` = 81 WHERE `spell_id` = 14313 AND `npc_spells_id` = 3002 AND `maxlevel` < 81; +UPDATE bot_spells_entries SET `maxlevel` = 81 WHERE `spell_id` = 14314 AND `npc_spells_id` = 3002 AND `maxlevel` < 81; + +-- Type fixes + +UPDATE bot_spells_entries SET `type` = 0 WHERE `spell_id` = 14267; +UPDATE bot_spells_entries SET `type` = 0 WHERE `spell_id` = 14268; +UPDATE bot_spells_entries SET `type` = 0 WHERE `spell_id` = 14269; +UPDATE bot_spells_entries SET `type` = 1 WHERE `spell_id` = 1576; +UPDATE bot_spells_entries SET `type` = 1 WHERE `spell_id` = 5416; +UPDATE bot_spells_entries SET `type` = 1 WHERE `spell_id` = 14446; +UPDATE bot_spells_entries SET `type` = 1 WHERE `spell_id` = 14447; +UPDATE bot_spells_entries SET `type` = 3 WHERE `spell_id` = 4074; +UPDATE bot_spells_entries SET `type` = 3 WHERE `spell_id` = 8008; +UPDATE bot_spells_entries SET `type` = 3 WHERE `spell_id` = 14467; +UPDATE bot_spells_entries SET `type` = 3 WHERE `spell_id` = 14468; +UPDATE bot_spells_entries SET `type` = 3 WHERE `spell_id` = 14469; +UPDATE bot_spells_entries SET `type` = 3 WHERE `spell_id` = 14388; +UPDATE bot_spells_entries SET `type` = 3 WHERE `spell_id` = 14389; +UPDATE bot_spells_entries SET `type` = 3 WHERE `spell_id` = 15187; +UPDATE bot_spells_entries SET `type` = 3 WHERE `spell_id` = 15188; +UPDATE bot_spells_entries SET `type` = 4 WHERE `spell_id` = 10436; +UPDATE bot_spells_entries SET `type` = 4 WHERE `spell_id` = 14312; +UPDATE bot_spells_entries SET `type` = 4 WHERE `spell_id` = 14313; +UPDATE bot_spells_entries SET `type` = 4 WHERE `spell_id` = 14314; +UPDATE bot_spells_entries SET `type` = 6 WHERE `spell_id` = 2115; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spell_id` = 195; +UPDATE bot_spells_entries SET `type` = 9 WHERE `spell_id` = 289; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spell_id` = 294; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spell_id` = 450; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spell_id` = 521; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spell_id` = 1403; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spell_id` = 1405; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spell_id` = 1703; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spell_id` = 3345; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spell_id` = 5348; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spell_id` = 5509; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spell_id` = 18392; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spell_id` = 18393; +UPDATE bot_spells_entries SET `type` = 8 WHERE `spell_id` = 18394; +UPDATE bot_spells_entries SET `type` = 10 WHERE `spell_id` = 14955; +UPDATE bot_spells_entries SET `type` = 10 WHERE `spell_id` = 14956; +UPDATE bot_spells_entries SET `type` = 10 WHERE `spell_id` = 15186; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 185; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 186; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 270; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 302; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 505; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 506; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 507; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 1588; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 1712; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 2542; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 2544; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 2634; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 2942; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 3462; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 4900; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 6826; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 6827; +UPDATE bot_spells_entries SET `type` = 13 WHERE `spell_id` = 6828; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 110; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 111; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 112; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 162; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 163; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 201; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 281; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 370; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 526; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 527; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 1437; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 1436; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 1573; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 1592; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 1577; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 1578; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 2571; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 3386; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 3387; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 3395; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 5394; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 5392; +UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 14387; +UPDATE bot_spells_entries SET `type` = 17 WHERE `spell_id` = 752; +UPDATE bot_spells_entries SET `type` = 17 WHERE `spell_id` = 1741; +UPDATE bot_spells_entries SET `type` = 17 WHERE `spell_id` = 1296; +UPDATE bot_spells_entries SET `type` = 17 WHERE `spell_id` = 2117; +UPDATE bot_spells_entries SET `type` = 17 WHERE `spell_id` = 3229; + +-- UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 3440; -- Ro's Illumination [#3440] from DoT [#8] to Debuff [#14] [Should be 0] +-- UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 303; -- Whirl till you hurl [#303] from Nuke [#0] to Debuff [#14] [Should be 0] +-- UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 619; -- Dyn's Dizzying Draught [#619] from Nuke [#0] to Debuff [#14] [Should be 0] + +-- UPDATE bot_spells_entries SET `type` = 14 WHERE `spell_id` = 74; -- Mana Sieve [#74] from Nuke [#0] to Debuff [#14] +-- UPDATE bot_spells_entries SET `type` = 6 WHERE `spell_id` = 1686; -- Theft of Thought [#1686] from Nuke [#0] to Lifetap [#6] + +-- UPDATE bot_spells_entries SET `type` = 1 WHERE `spell_id` = 3694; -- Stoicism [#3694] from In-Combat Buff [#10] to Regular Heal [#1] +-- UPDATE bot_spells_entries SET `type` = 1 WHERE `spell_id` = 4899; -- Breath of Trushar [#4899] from In-Combat Buff [#10] to Regular Heal [#1] + +-- UPDATE bot_spells_entries SET `type` = 7 WHERE `spell_id` = 738; -- Selo's Consonant Chain [#738] from Slow [#13] to Snare [#7] +-- UPDATE bot_spells_entries SET `type` = 7 WHERE `spell_id` = 1751; -- Largo's Assonant Binding [#1751] from Slow [#13] to Snare [#7] +-- UPDATE bot_spells_entries SET `type` = 8 WHERE `spell_id` = 1748; -- Angstlich's Assonance [#1748] from Slow [#13] to DoT [#8] +-- UPDATE bot_spells_entries SET `type` = 7 WHERE `spell_id` = 738; -- Selo's Consonant Chain [#738] from Slow [#13] to Snare [#7] +-- UPDATE bot_spells_entries SET `type` = 7 WHERE `spell_id` = 1751; -- Largo's Assonant Binding [#1751] from Slow [#13] to Snare [#7] +-- UPDATE bot_spells_entries SET `type` = 7 WHERE `spell_id` = 738; -- Selo's Consonant Chain [#738] from Slow [#13] to Snare [#7] + +)", + }, + ManifestEntry{ + .version = 9050, + .description = "2024_11_26_add_commanded_spelltypes.sql", + .check = "SELECT * FROM `bot_spells_entries` where `type` = 100", + .condition = "empty", + .match = "", + .sql = R"( +INSERT INTO `bot_spells_entries` (`npc_spells_id`, `spell_id`, `type`, `minlevel`, `maxlevel`) +VALUES +(3006, 9957, 100, 20, 254), +(3006, 9956, 100, 20, 254), +(3006, 552, 100, 25, 254), +(3006, 550, 100, 25, 254), +(3006, 553, 100, 25, 254), +(3006, 2432, 100, 26, 254), +(3006, 2020, 100, 26, 254), +(3006, 551, 100, 27, 254), +(3006, 3792, 100, 28, 254), +(3006, 2419, 100, 29, 254), +(3006, 554, 100, 30, 254), +(3006, 557, 100, 31, 254), +(3006, 1434, 100, 32, 254), +(3006, 555, 100, 32, 254), +(3006, 25898, 100, 32, 254), +(3006, 25904, 100, 32, 254), +(3006, 556, 100, 32, 254), +(3006, 25698, 100, 33, 254), +(3006, 1517, 100, 33, 254), +(3006, 2424, 100, 33, 254), +(3006, 25689, 100, 33, 254), +(3006, 25899, 100, 34, 254), +(3006, 25690, 100, 35, 254), +(3006, 25903, 100, 35, 254), +(3006, 25900, 100, 35, 254), +(3006, 558, 100, 36, 254), +(3006, 2429, 100, 37, 254), +(3006, 1438, 100, 38, 254), +(3006, 3184, 100, 38, 254), +(3006, 25697, 100, 38, 254), +(3006, 25902, 100, 39, 254), +(3006, 25695, 100, 39, 254), +(3006, 25901, 100, 40, 254), +(3006, 25694, 100, 40, 254), +(3006, 1398, 100, 40, 254), +(3006, 25905, 100, 41, 254), +(3006, 25696, 100, 42, 254), +(3006, 1440, 100, 42, 254), +(3006, 25906, 100, 43, 254), +(3006, 25693, 100, 44, 254), +(3006, 25699, 100, 45, 254), +(3006, 24773, 100, 46, 254), +(3006, 8965, 100, 52, 254), +(3006, 24771, 100, 52, 254), +(3006, 8235, 100, 52, 254), +(3006, 24775, 100, 52, 254), +(3006, 4966, 100, 54, 254), +(3006, 6184, 100, 55, 254), +(3006, 5731, 100, 55, 254), +(3006, 24776, 100, 56, 254), +(3006, 25700, 100, 56, 254), +(3006, 25691, 100, 57, 254), +(3006, 24772, 100, 57, 254), +(3006, 25692, 100, 57, 254), +(3006, 11981, 100, 59, 254), +(3006, 9953, 100, 60, 254), +(3006, 9954, 100, 60, 254), +(3006, 11980, 100, 64, 254), +(3006, 6179, 100, 64, 254), +(3006, 24774, 100, 67, 254), +(3006, 9950, 100, 70, 254), +(3006, 9951, 100, 70, 254), +(3006, 15886, 100, 75, 254), +(3006, 15887, 100, 75, 254), +(3006, 21989, 100, 80, 254), +(3006, 20539, 100, 80, 254), +(3006, 21984, 100, 80, 254), +(3006, 20538, 100, 80, 254), +(3006, 17883, 100, 85, 254), +(3006, 17884, 100, 85, 254), +(3006, 28997, 100, 90, 254), +(3006, 28998, 100, 90, 254), +(3006, 29000, 100, 92, 254), +(3006, 29001, 100, 92, 254), +(3006, 34832, 100, 95, 254), +(3006, 40217, 100, 95, 254), +(3006, 34833, 100, 95, 254), +(3006, 40216, 100, 95, 254), +(3012, 10881, 100, 20, 254), +(3012, 10880, 100, 20, 254), +(3012, 562, 100, 25, 254), +(3012, 563, 100, 27, 254), +(3012, 3793, 100, 27, 254), +(3012, 561, 100, 28, 254), +(3012, 2420, 100, 29, 254), +(3012, 2944, 100, 29, 254), +(3012, 564, 100, 32, 254), +(3012, 565, 100, 33, 254), +(3012, 1418, 100, 33, 254), +(3012, 2425, 100, 33, 254), +(3012, 1516, 100, 34, 254), +(3012, 1338, 100, 35, 254), +(3012, 3833, 100, 35, 254), +(3012, 566, 100, 35, 254), +(3012, 1336, 100, 36, 254), +(3012, 2943, 100, 36, 254), +(3012, 1423, 100, 36, 254), +(3012, 567, 100, 36, 254), +(3012, 568, 100, 37, 254), +(3012, 1337, 100, 37, 254), +(3012, 3180, 100, 38, 254), +(3012, 1339, 100, 38, 254), +(3012, 2421, 100, 39, 254), +(3012, 2430, 100, 39, 254), +(3012, 1372, 100, 40, 254), +(3012, 2426, 100, 41, 254), +(3012, 1371, 100, 41, 254), +(3012, 1399, 100, 42, 254), +(3012, 1374, 100, 42, 254), +(3012, 1373, 100, 43, 254), +(3012, 1425, 100, 43, 254), +(3012, 1375, 100, 44, 254), +(3012, 3181, 100, 45, 254), +(3012, 2022, 100, 45, 254), +(3012, 666, 100, 46, 254), +(3012, 3849, 100, 46, 254), +(3012, 674, 100, 46, 254), +(3012, 2023, 100, 46, 254), +(3012, 2024, 100, 47, 254), +(3012, 2025, 100, 48, 254), +(3012, 2431, 100, 49, 254), +(3012, 8966, 100, 51, 254), +(3012, 8236, 100, 51, 254), +(3012, 4965, 100, 54, 254), +(3012, 8969, 100, 55, 254), +(3012, 8239, 100, 55, 254), +(3012, 6183, 100, 55, 254), +(3012, 5732, 100, 55, 254), +(3012, 4964, 100, 57, 254), +(3012, 6182, 100, 58, 254), +(3012, 5735, 100, 60, 254), +(3012, 10877, 100, 60, 254), +(3012, 10878, 100, 60, 254), +(3012, 6178, 100, 64, 254), +(3012, 6177, 100, 67, 254), +(3012, 11984, 100, 69, 254), +(3012, 10874, 100, 70, 254), +(3012, 10875, 100, 70, 254), +(3012, 11983, 100, 74, 254), +(3012, 15889, 100, 75, 254), +(3012, 15890, 100, 75, 254), +(3012, 21988, 100, 80, 254), +(3012, 20542, 100, 80, 254), +(3012, 21985, 100, 80, 254), +(3012, 20541, 100, 80, 254), +(3012, 17886, 100, 85, 254), +(3012, 17887, 100, 85, 254), +(3012, 29840, 100, 90, 254), +(3012, 29841, 100, 90, 254), +(3012, 29843, 100, 92, 254), +(3012, 29844, 100, 92, 254), +(3012, 40443, 100, 95, 254), +(3012, 35715, 100, 95, 254), +(3012, 40442, 100, 95, 254), +(3012, 35714, 100, 95, 254), +(3002, 208, 101, 1, 4), +(3002, 501, 101, 5, 14), +(3002, 47, 101, 15, 35), +(3002, 45, 101, 36, 64), +(3002, 1541, 101, 55, 254), +(3002, 3197, 101, 65, 254), +(3002, 5274, 101, 70, 70), +(3002, 9798, 101, 71, 75), +(3002, 9799, 101, 71, 75), +(3002, 9797, 101, 71, 75), +(3002, 14288, 101, 76, 80), +(3002, 14289, 101, 76, 80), +(3002, 14290, 101, 76, 80), +(3002, 18309, 101, 81, 85), +(3002, 18310, 101, 81, 85), +(3002, 18311, 101, 81, 85), +(3002, 25103, 101, 86, 90), +(3002, 25101, 101, 86, 90), +(3002, 25102, 101, 86, 90), +(3002, 28102, 101, 91, 95), +(3002, 28100, 101, 91, 95), +(3002, 28101, 101, 91, 95), +(3002, 34096, 101, 96, 254), +(3002, 34094, 101, 96, 254), +(3002, 34095, 101, 96, 254), +(3003, 208, 101, 10, 24), +(3003, 501, 101, 25, 42), +(3003, 47, 101, 43, 48), +(3003, 45, 101, 49, 254), +(3003, 25294, 101, 86, 90), +(3003, 25295, 101, 86, 90), +(3003, 25296, 101, 86, 90), +(3003, 28340, 101, 91, 95), +(3003, 28338, 101, 91, 95), +(3003, 28339, 101, 91, 95), +(3003, 34346, 101, 96, 254), +(3003, 34344, 101, 96, 254), +(3003, 34345, 101, 96, 254), +(3004, 240, 101, 4, 30), +(3004, 250, 101, 22, 254), +(3004, 513, 101, 31, 254), +(3004, 3601, 101, 39, 254), +(3004, 5316, 101, 68, 70), +(3004, 10112, 101, 71, 75), +(3004, 10110, 101, 71, 75), +(3004, 10111, 101, 71, 75), +(3004, 15037, 101, 76, 80), +(3004, 15035, 101, 76, 80), +(3004, 15036, 101, 76, 80), +(3004, 19168, 101, 81, 85), +(3004, 19169, 101, 81, 85), +(3004, 19167, 101, 81, 85), +(3004, 25417, 101, 86, 87), +(3004, 25418, 101, 86, 90), +(3004, 25419, 101, 86, 90), +(3004, 25466, 101, 88, 90), +(3004, 25467, 101, 88, 90), +(3004, 25465, 101, 88, 90), +(3004, 28479, 101, 91, 92), +(3004, 28480, 101, 91, 95), +(3004, 28481, 101, 91, 95), +(3004, 28542, 101, 93, 95), +(3004, 28543, 101, 93, 95), +(3004, 28544, 101, 93, 95), +(3004, 34500, 101, 96, 97), +(3004, 34502, 101, 96, 254), +(3004, 34501, 101, 96, 254), +(3004, 34565, 101, 98, 254), +(3004, 34563, 101, 98, 254), +(3004, 34564, 101, 98, 254), +(3005, 347, 101, 9, 51), +(3005, 448, 101, 52, 254), +(3006, 240, 101, 1, 14), +(3006, 250, 101, 5, 254), +(3006, 513, 101, 15, 254), +(3006, 3601, 101, 29, 254), +(3006, 5347, 101, 67, 70), +(3006, 9851, 101, 71, 75), +(3006, 9852, 101, 71, 75), +(3006, 9853, 101, 71, 75), +(3006, 14369, 101, 76, 80), +(3006, 14367, 101, 76, 80), +(3006, 14368, 101, 76, 80), +(3006, 18409, 101, 81, 85), +(3006, 18407, 101, 81, 85), +(3006, 18408, 101, 81, 85), +(3006, 25736, 101, 86, 90), +(3006, 25734, 101, 86, 90), +(3006, 25735, 101, 86, 90), +(3006, 28831, 101, 91, 95), +(3006, 28832, 101, 91, 95), +(3006, 28830, 101, 91, 95), +(3006, 34863, 101, 96, 254), +(3006, 34864, 101, 96, 254), +(3006, 34862, 101, 96, 254), +(3008, 728, 101, 8, 60), +(3008, 3361, 101, 61, 254), +(3008, 5370, 101, 66, 70), +(3008, 10403, 101, 71, 75), +(3008, 10401, 101, 71, 75), +(3008, 10402, 101, 71, 75), +(3008, 14002, 101, 76, 80), +(3008, 14000, 101, 76, 80), +(3008, 14001, 101, 76, 80), +(3008, 18001, 101, 81, 85), +(3008, 18002, 101, 81, 85), +(3008, 18000, 101, 81, 85), +(3008, 25978, 101, 86, 90), +(3008, 25979, 101, 86, 90), +(3008, 25977, 101, 86, 90), +(3008, 29079, 101, 91, 95), +(3008, 29080, 101, 91, 95), +(3008, 29078, 101, 91, 95), +(3008, 35131, 101, 96, 254), +(3008, 35132, 101, 96, 254), +(3008, 35133, 101, 96, 254), +(3011, 347, 101, 2, 22), +(3011, 448, 101, 23, 254), +(3014, 208, 101, 1, 5), +(3014, 501, 101, 6, 17), +(3014, 47, 101, 18, 34), +(3014, 45, 101, 35, 61), +(3014, 1541, 101, 51, 254), +(3014, 3197, 101, 62, 254), +(3014, 5506, 101, 67, 71), +(3014, 10601, 101, 72, 76), +(3014, 10599, 101, 72, 76), +(3014, 10600, 101, 72, 76), +(3014, 14510, 101, 77, 81), +(3014, 14511, 101, 77, 81), +(3014, 14509, 101, 77, 81), +(3014, 18568, 101, 82, 86), +(3014, 18569, 101, 82, 86), +(3014, 18567, 101, 82, 86), +(3014, 26921, 101, 87, 91), +(3014, 26922, 101, 87, 91), +(3014, 26920, 101, 87, 91), +(3014, 30054, 101, 92, 96), +(3014, 30055, 101, 92, 96), +(3014, 30056, 101, 92, 96), +(3014, 36116, 101, 97, 254), +(3014, 36117, 101, 97, 254), +(3014, 36118, 101, 97, 254), +(3006, 2183, 102, 18, 56), +(3006, 1567, 102, 57, 254), +(3012, 2184, 102, 18, 56), +(3012, 2558, 102, 56, 64), +(3012, 1628, 102, 57, 254), +(3012, 3244, 102, 65, 254), +(3002, 35, 103, 10, 254), +(3006, 35, 103, 12, 254), +(3010, 35, 103, 14, 254), +(3011, 35, 103, 12, 254), +(3012, 35, 103, 12, 254), +(3013, 35, 103, 12, 254), +(3014, 35, 103, 12, 254), +(3008, 737, 104, 14, 254), +(3011, 305, 104, 17, 254), +(3012, 305, 104, 14, 254), +(3013, 305, 104, 13, 254), +(3014, 305, 104, 15, 254), +(3004, 261, 105, 35, 64), +(3004, 2517, 105, 65, 254), +(3006, 261, 105, 14, 49), +(3006, 2894, 105, 50, 53), +(3006, 2517, 105, 54, 254), +(3006, 3185, 105, 62, 254), +(3008, 718, 105, 31, 50), +(3008, 1750, 105, 51, 254), +(3010, 261, 105, 10, 50), +(3010, 2894, 105, 51, 254), +(3011, 457, 105, 41, 254), +(3011, 1391, 105, 45, 254), +(3012, 261, 105, 22, 49), +(3012, 2894, 105, 50, 254), +(3014, 261, 105, 15, 50), +(3014, 2894, 105, 51, 254), +(3015, 261, 105, 32, 254), +(3003, 1743, 106, 55, 254), +(3008, 714, 106, 41, 254), +(3008, 748, 106, 47, 57), +(3008, 1450, 106, 49, 254), +(3008, 1752, 106, 52, 254), +(3008, 1763, 106, 58, 72), +(3008, 11881, 106, 73, 77), +(3008, 11879, 106, 73, 77), +(3008, 11880, 106, 73, 77), +(3008, 14055, 106, 78, 82), +(3008, 14056, 106, 78, 82), +(3008, 14054, 106, 78, 82), +(3008, 18040, 106, 83, 87), +(3008, 18041, 106, 83, 87), +(3008, 18039, 106, 83, 87), +(3008, 26026, 106, 88, 92), +(3008, 26027, 106, 88, 92), +(3008, 26025, 106, 88, 92), +(3008, 29120, 106, 93, 97), +(3008, 29121, 106, 93, 97), +(3008, 29122, 106, 93, 97), +(3008, 35170, 106, 98, 254), +(3008, 35171, 106, 98, 254), +(3008, 35172, 106, 98, 254), +(3012, 2559, 106, 58, 254), +(3014, 481, 106, 13, 21), +(3014, 21, 106, 19, 21), +(3014, 482, 106, 22, 32), +(3014, 483, 106, 33, 39), +(3014, 648, 106, 38, 39), +(3014, 484, 106, 40, 57), +(3014, 176, 106, 47, 57), +(3014, 1689, 106, 52, 57), +(3014, 1713, 106, 58, 60), +(3014, 3343, 106, 61, 66), +(3014, 6739, 106, 61, 68), +(3014, 3351, 106, 63, 66), +(3014, 5504, 106, 67, 70), +(3014, 5514, 106, 69, 70), +(3014, 6671, 106, 69, 254), +(3014, 10598, 106, 71, 75), +(3014, 10596, 106, 71, 75), +(3014, 10597, 106, 71, 75), +(3014, 10643, 106, 74, 75), +(3014, 10641, 106, 74, 75), +(3014, 10642, 106, 74, 75), +(3014, 11887, 106, 74, 78), +(3014, 11885, 106, 74, 78), +(3014, 11886, 106, 74, 78), +(3014, 14507, 106, 76, 80), +(3014, 14508, 106, 76, 80), +(3014, 14506, 106, 76, 80), +(3014, 14543, 106, 79, 80), +(3014, 14544, 106, 79, 80), +(3014, 14542, 106, 79, 80), +(3014, 14582, 106, 79, 83), +(3014, 14583, 106, 79, 83), +(3014, 14581, 106, 79, 83), +(3014, 18564, 106, 81, 85), +(3014, 18565, 106, 81, 85), +(3014, 18566, 106, 81, 85), +(3014, 18600, 106, 84, 85), +(3014, 18601, 106, 84, 85), +(3014, 18602, 106, 84, 85), +(3014, 18641, 106, 84, 88), +(3014, 18639, 106, 84, 88), +(3014, 18640, 106, 84, 88), +(3014, 26901, 106, 86, 90), +(3014, 26899, 106, 86, 90), +(3014, 26900, 106, 86, 90), +(3014, 26999, 106, 89, 90), +(3014, 26997, 106, 89, 90), +(3014, 26998, 106, 89, 90), +(3014, 27025, 106, 89, 93), +(3014, 27026, 106, 89, 93), +(3014, 27024, 106, 89, 93), +(3014, 30028, 106, 91, 95), +(3014, 30029, 106, 91, 95), +(3014, 30027, 106, 91, 95), +(3014, 30132, 106, 93, 95), +(3014, 30133, 106, 93, 95), +(3014, 30131, 106, 93, 95), +(3014, 30140, 106, 94, 95), +(3014, 30141, 106, 94, 95), +(3014, 30142, 106, 94, 95), +(3014, 30167, 106, 94, 98), +(3014, 30168, 106, 94, 98), +(3014, 30169, 106, 94, 98), +(3014, 36091, 106, 96, 254), +(3014, 36089, 106, 96, 254), +(3014, 36090, 106, 96, 254), +(3014, 36189, 106, 98, 254), +(3014, 36187, 106, 98, 254), +(3014, 36188, 106, 98, 254), +(3014, 36216, 106, 99, 254), +(3014, 36196, 106, 99, 254), +(3014, 36217, 106, 99, 254), +(3014, 36194, 106, 99, 254), +(3014, 36215, 106, 99, 254), +(3014, 36195, 106, 99, 254), +(3004, 86, 107, 20, 254), +(3006, 86, 107, 6, 49), +(3006, 2881, 107, 50, 254), +(3008, 729, 107, 16, 254), +(3010, 86, 107, 12, 50), +(3010, 2881, 107, 51, 254), +(3011, 457, 107, 41, 254), +(3011, 1391, 107, 45, 254), +(3014, 86, 107, 12, 43), +(3014, 3696, 107, 44, 50), +(3014, 2881, 107, 51, 254), +(3015, 86, 107, 25, 254), +(3010, 345, 108, 15, 254), +(3010, 2522, 108, 16, 254), +(3015, 345, 108, 23, 254), +(3002, 235, 109, 11, 254), +(3002, 1726, 109, 51, 254), +(3002, 6125, 109, 66, 254), +(3002, 14348, 109, 77, 81), +(3002, 14346, 109, 77, 81), +(3002, 14347, 109, 77, 81), +(3002, 18369, 109, 82, 254), +(3002, 18367, 109, 82, 254), +(3002, 18368, 109, 82, 254), +(3003, 235, 109, 17, 254), +(3004, 247, 109, 14, 46), +(3004, 80, 109, 32, 64), +(3004, 34, 109, 47, 254), +(3004, 2517, 109, 65, 254), +(3005, 235, 109, 4, 254), +(3006, 247, 109, 4, 17), +(3006, 255, 109, 8, 254), +(3006, 80, 109, 13, 53), +(3006, 34, 109, 18, 254), +(3006, 2516, 109, 52, 254), +(3006, 4058, 109, 52, 254), +(3006, 2517, 109, 54, 254), +(3006, 3185, 109, 62, 254), +(3006, 6123, 109, 68, 254), +(3008, 719, 109, 19, 50), +(3008, 735, 109, 24, 254), +(3008, 1750, 109, 51, 254), +(3010, 79, 109, 7, 55), +(3010, 255, 109, 10, 254), +(3010, 42, 109, 27, 254), +(3010, 1575, 109, 56, 254), +(3010, 2886, 109, 58, 254), +(3011, 235, 109, 1, 254), +(3011, 457, 109, 41, 254), +(3011, 1391, 109, 45, 254), +(3011, 6124, 109, 68, 254), +(3012, 80, 109, 4, 39), +(3012, 42, 109, 16, 254), +(3012, 3811, 109, 40, 254), +(3012, 6120, 109, 67, 254), +(3012, 15513, 109, 76, 80), +(3012, 15511, 109, 76, 80), +(3012, 15512, 109, 76, 80), +(3012, 19701, 109, 81, 254), +(3012, 19699, 109, 81, 254), +(3012, 19700, 109, 81, 254), +(3013, 42, 109, 8, 254), +(3013, 80, 109, 16, 254), +(3014, 42, 109, 4, 254), +(3014, 80, 109, 6, 43), +(3014, 235, 109, 14, 254), +(3014, 3696, 109, 44, 254), +(3014, 6122, 109, 66, 254), +(3015, 79, 109, 29, 64), +(3015, 42, 109, 43, 254), +(3015, 1575, 109, 65, 254), +(3004, 278, 110, 28, 64), +(3004, 4054, 110, 41, 64), +(3004, 4055, 110, 49, 254), +(3004, 2517, 110, 65, 254), +(3006, 278, 110, 10, 53), +(3006, 424, 110, 26, 254), +(3006, 4054, 110, 30, 53), +(3006, 169, 110, 35, 61), +(3006, 4055, 110, 35, 254), +(3006, 3579, 110, 45, 254), +(3006, 4058, 110, 52, 254), +(3006, 1554, 110, 53, 254), +(3006, 2517, 110, 54, 254), +(3006, 3185, 110, 62, 254), +(3008, 717, 110, 5, 48), +(3008, 4395, 110, 25, 48), +(3008, 2605, 110, 49, 50), +(3008, 1750, 110, 51, 254), +(3010, 278, 110, 9, 254), +(3010, 424, 110, 22, 254), +(3010, 4054, 110, 29, 254), +(3010, 4055, 110, 34, 254), +(3010, 2524, 110, 36, 254), +(3010, 1554, 110, 52, 254), +(3015, 278, 110, 24, 254), +(3015, 4054, 110, 39, 254), +(3015, 4055, 110, 44, 254), +(3012, 1422, 111, 50, 254), +(3012, 1334, 111, 52, 254), +(3005, 2213, 112, 12, 34), +(3005, 3, 112, 35, 56), +(3005, 1773, 112, 57, 70), +(3005, 10042, 112, 71, 75), +(3005, 14823, 112, 76, 80), +(3005, 18928, 112, 81, 85), +(3005, 25555, 112, 86, 90), +(3005, 28632, 112, 91, 95), +(3005, 34662, 112, 96, 254), +(3011, 2213, 112, 12, 34), +(3011, 3, 112, 35, 56), +(3011, 1773, 112, 57, 70), +(3011, 10042, 112, 71, 75), +(3011, 14823, 112, 76, 80), +(3011, 18928, 112, 81, 85), +(3011, 25555, 112, 86, 90), +(3011, 28632, 112, 91, 95), +(3011, 34662, 112, 96, 254); + +DELETE +FROM bot_spells_entries +WHERE NOT EXISTS +(SELECT * +FROM spells_new +WHERE bot_spells_entries.spell_id = spells_new.id); +)", + }, + ManifestEntry{ + .version = 9051, + .description = "2024_11_26_remove_sk_icb.sql", + .check = "SELECT * FROM `bot_spells_entries` where `type` = 24", + .condition = "empty", + .match = "", + .sql = R"( +DELETE +FROM bot_spells_entries +WHERE `npc_spells_id` = 3005 +AND `type` = 10; + +INSERT INTO `bot_spells_entries` (`npc_spells_id`, `spell_id`, `type`, `minlevel`, `maxlevel`, `priority`) +VALUES +(3003, 10173, 24, 72, 76, 3), +(3003, 10174, 24, 72, 76, 2), +(3003, 10175, 24, 72, 76, 1), +(3003, 14954, 24, 77, 81, 3), +(3003, 14955, 24, 77, 81, 2), +(3003, 14956, 24, 77, 81, 1), +(3003, 19068, 24, 82, 86, 3), +(3003, 19069, 24, 82, 86, 2), +(3003, 19070, 24, 82, 86, 1), +(3003, 25297, 24, 87, 91, 3), +(3003, 25298, 24, 87, 91, 2), +(3003, 25299, 24, 87, 91, 1), +(3003, 28347, 24, 92, 96, 3), +(3003, 28348, 24, 92, 96, 2), +(3003, 28349, 24, 92, 96, 1), +(3003, 34350, 24, 97, 254, 3), +(3003, 34351, 24, 97, 254, 2), +(3003, 34352, 24, 97, 254, 1), +(3003, 40078, 24, 98, 254, 3), +(3003, 40079, 24, 98, 254, 2), +(3003, 40080, 24, 98, 254, 1), +(3005, 1221, 24, 33, 41, 1), +(3005, 1222, 24, 42, 52, 1), +(3005, 1223, 24, 53, 58, 1), +(3005, 1224, 24, 59, 62, 1), +(3005, 3405, 24, 63, 66, 1), +(3005, 5329, 24, 67, 70, 5), +(3005, 5336, 24, 69, 73, 4), +(3005, 10257, 24, 71, 71, 3), +(3005, 10258, 24, 71, 71, 2), +(3005, 10259, 24, 71, 71, 1), +(3005, 10260, 24, 72, 76, 3), +(3005, 10261, 24, 72, 76, 2), +(3005, 10262, 24, 72, 76, 1), +(3005, 10291, 24, 74, 78, 3), +(3005, 10292, 24, 74, 78, 2), +(3005, 10293, 24, 74, 78, 1), +(3005, 15160, 24, 76, 76, 3), +(3005, 15161, 24, 76, 76, 2), +(3005, 15162, 24, 76, 76, 1), +(3005, 15163, 24, 77, 81, 3), +(3005, 15164, 24, 77, 81, 2), +(3005, 15165, 24, 77, 81, 1), +(3005, 15184, 24, 79, 83, 3), +(3005, 15185, 24, 79, 83, 2), +(3005, 15186, 24, 79, 83, 1), +(3005, 19313, 24, 81, 81, 3), +(3005, 19314, 24, 81, 81, 2), +(3005, 19315, 24, 81, 81, 1), +(3005, 19316, 24, 82, 86, 3), +(3005, 19317, 24, 82, 86, 2), +(3005, 19318, 24, 82, 86, 1), +(3005, 19337, 24, 84, 88, 3), +(3005, 19338, 24, 84, 88, 2), +(3005, 19339, 24, 84, 88, 1), +(3005, 25580, 24, 86, 86, 3), +(3005, 25581, 24, 86, 86, 2), +(3005, 25582, 24, 86, 86, 1), +(3005, 25586, 24, 87, 91, 3), +(3005, 25587, 24, 87, 91, 2), +(3005, 25588, 24, 87, 91, 1), +(3005, 25641, 24, 89, 93, 3), +(3005, 25642, 24, 89, 93, 2), +(3005, 25643, 24, 89, 93, 1), +(3005, 28657, 24, 91, 91, 3), +(3005, 28658, 24, 91, 91, 2), +(3005, 28659, 24, 91, 91, 1), +(3005, 28663, 24, 92, 96, 3), +(3005, 28664, 24, 92, 96, 2), +(3005, 28665, 24, 92, 96, 1), +(3005, 28733, 24, 94, 98, 3), +(3005, 28734, 24, 94, 98, 2), +(3005, 28735, 24, 94, 98, 1), +(3005, 34687, 24, 96, 96, 3), +(3005, 34688, 24, 96, 96, 2), +(3005, 34689, 24, 96, 96, 1), +(3005, 34693, 24, 97, 254, 3), +(3005, 34694, 24, 97, 254, 2), +(3005, 34695, 24, 97, 254, 1), +(3005, 34751, 24, 99, 254, 3), +(3005, 34752, 24, 99, 254, 2), +(3005, 34753, 24, 99, 254, 1); + +DELETE +FROM bot_spells_entries +WHERE NOT EXISTS +(SELECT * +FROM spells_new +WHERE bot_spells_entries.spell_id = spells_new.id); +)", + }, + ManifestEntry{ + .version = 9052, + .description = "2024_12_15_bot_blocked_buffs.sql", + .check = "SHOW TABLES LIKE 'bot_blocked_buffs'", + .condition = "empty", + .match = "", + .sql = R"( +CREATE TABLE `bot_blocked_buffs` ( + `bot_id` INT(11) UNSIGNED NOT NULL, + `spell_id` INT(11) UNSIGNED NOT NULL, + `blocked` TINYINT(4) UNSIGNED NULL DEFAULT '0', + `blocked_pet` TINYINT(4) UNSIGNED NULL DEFAULT '0', + PRIMARY KEY (`bot_id`, `spell_id`) USING BTREE +) +COLLATE='latin1_swedish_ci' +ENGINE=InnoDB +; +)", + }, + ManifestEntry{ + .version = 9053, + .description = "2024_12_26_discipline_inserts.sql", + .check = "SELECT * FROM `bot_spells_entries` where `type` = 200", + .condition = "empty", + .match = "", + .sql = R"( +INSERT INTO `bot_spells_entries` (`npc_spells_id`, `spell_id`, `type`, `minlevel`, `maxlevel`) +VALUES +(3001, 5225, 200, 1, 254), +(3001, 25060, 200, 5, 254), +(3001, 4721, 200, 10, 62), +(3001, 4608, 200, 20, 51), +(3001, 4585, 200, 30, 254), +(3001, 4587, 200, 40, 254), +(3001, 4681, 200, 52, 55), +(3001, 4503, 200, 52, 254), +(3001, 4672, 200, 53, 254), +(3001, 4514, 200, 54, 254), +(3001, 4499, 200, 55, 64), +(3001, 8921, 200, 55, 69), +(3001, 4682, 200, 56, 62), +(3001, 4674, 200, 56, 254), +(3001, 4501, 200, 57, 254), +(3001, 4675, 200, 58, 59), +(3001, 4670, 200, 59, 254), +(3001, 4498, 200, 60, 254), +(3001, 6750, 200, 61, 68), +(3001, 4689, 200, 61, 254), +(3001, 4697, 200, 63, 64), +(3001, 4687, 200, 63, 254), +(3001, 4688, 200, 65, 71), +(3001, 5015, 200, 65, 254), +(3001, 5016, 200, 65, 254), +(3001, 6191, 200, 66, 70), +(3001, 6192, 200, 68, 73), +(3001, 8000, 200, 68, 254), +(3001, 6725, 200, 69, 73), +(3001, 6173, 200, 69, 80), +(3001, 8467, 200, 70, 74), +(3001, 8468, 200, 70, 254), +(3001, 6190, 200, 70, 254), +(3001, 10959, 200, 71, 75), +(3001, 10960, 200, 71, 75), +(3001, 10961, 200, 71, 75), +(3001, 11913, 200, 72, 76), +(3001, 11914, 200, 72, 76), +(3001, 11915, 200, 72, 76), +(3001, 10965, 200, 72, 254), +(3001, 10966, 200, 72, 254), +(3001, 10967, 200, 72, 254), +(3001, 10970, 200, 73, 254), +(3001, 10968, 200, 73, 254), +(3001, 10969, 200, 73, 254), +(3001, 10971, 200, 74, 78), +(3001, 10972, 200, 74, 78), +(3001, 10973, 200, 74, 78), +(3001, 11917, 200, 74, 254), +(3001, 11918, 200, 74, 254), +(3001, 11916, 200, 74, 254), +(3001, 10974, 200, 75, 79), +(3001, 10975, 200, 75, 79), +(3001, 10976, 200, 75, 79), +(3001, 15345, 200, 76, 80), +(3001, 15346, 200, 76, 80), +(3001, 15347, 200, 76, 80), +(3001, 14192, 200, 77, 81), +(3001, 14193, 200, 77, 81), +(3001, 14194, 200, 77, 81), +(3001, 15369, 200, 77, 254), +(3001, 15370, 200, 77, 254), +(3001, 15371, 200, 77, 254), +(3001, 15375, 200, 78, 82), +(3001, 15376, 200, 78, 82), +(3001, 15377, 200, 78, 82), +(3001, 15359, 200, 79, 83), +(3001, 15357, 200, 79, 83), +(3001, 15358, 200, 79, 83), +(3001, 15379, 200, 80, 84), +(3001, 15360, 200, 80, 84), +(3001, 15380, 200, 80, 84), +(3001, 15361, 200, 80, 84), +(3001, 15362, 200, 80, 84), +(3001, 15378, 200, 80, 84), +(3001, 19537, 200, 81, 254), +(3001, 19538, 200, 81, 254), +(3001, 19516, 200, 81, 254), +(3001, 19539, 200, 81, 254), +(3001, 19517, 200, 81, 254), +(3001, 19518, 200, 81, 254), +(3001, 18213, 200, 82, 254), +(3001, 18214, 200, 82, 254), +(3001, 18215, 200, 82, 254), +(3001, 19555, 200, 83, 254), +(3001, 19556, 200, 83, 254), +(3001, 19557, 200, 83, 254), +(3001, 19914, 200, 83, 254), +(3001, 19915, 200, 83, 254), +(3001, 19916, 200, 83, 254), +(3001, 19553, 200, 84, 254), +(3001, 19554, 200, 84, 254), +(3001, 19528, 200, 84, 254), +(3001, 19529, 200, 84, 254), +(3001, 19552, 200, 84, 254), +(3001, 19530, 200, 84, 254), +(3001, 22556, 200, 85, 254), +(3001, 19531, 200, 85, 254), +(3001, 22557, 200, 85, 254), +(3001, 19532, 200, 85, 254), +(3001, 22558, 200, 85, 254), +(3001, 19533, 200, 85, 254), +(3001, 19549, 200, 85, 254), +(3001, 19550, 200, 85, 254), +(3001, 19917, 200, 85, 254), +(3001, 19551, 200, 85, 254), +(3001, 19918, 200, 85, 254), +(3001, 19919, 200, 85, 254), +(3002, 33000, 200, 1, 254), +(3003, 4585, 200, 51, 254), +(3003, 4587, 200, 54, 254), +(3003, 4500, 200, 55, 254), +(3003, 7004, 200, 56, 60), +(3003, 4590, 200, 59, 254), +(3003, 4518, 200, 60, 254), +(3003, 6731, 200, 61, 68), +(3003, 6663, 200, 69, 72), +(3003, 11854, 200, 73, 77), +(3003, 11855, 200, 73, 77), +(3003, 11856, 200, 73, 77), +(3003, 14987, 200, 78, 82), +(3003, 14988, 200, 78, 82), +(3003, 14989, 200, 78, 82), +(3003, 19103, 200, 83, 254), +(3003, 19131, 200, 83, 254), +(3003, 19132, 200, 83, 254), +(3003, 19133, 200, 83, 254), +(3003, 22665, 200, 83, 254), +(3003, 22666, 200, 83, 254), +(3003, 22667, 200, 83, 254), +(3003, 19101, 200, 83, 254), +(3003, 19102, 200, 83, 254), +(3004, 33000, 200, 1, 254), +(3004, 4585, 200, 51, 254), +(3004, 4587, 200, 54, 254), +(3004, 4506, 200, 55, 79), +(3004, 4519, 200, 60, 254), +(3004, 8019, 200, 69, 254), +(3004, 10086, 200, 72, 76), +(3004, 10087, 200, 72, 76), +(3004, 10088, 200, 72, 76), +(3004, 15020, 200, 77, 81), +(3004, 15021, 200, 77, 81), +(3004, 15022, 200, 77, 81), +(3004, 15091, 200, 80, 84), +(3004, 15092, 200, 80, 84), +(3004, 15093, 200, 80, 84), +(3004, 19153, 200, 82, 254), +(3004, 19154, 200, 82, 254), +(3004, 19152, 200, 82, 254), +(3004, 19223, 200, 85, 254), +(3004, 19224, 200, 85, 254), +(3004, 19225, 200, 85, 254), +(3005, 4585, 200, 51, 254), +(3005, 4587, 200, 54, 254), +(3005, 4520, 200, 55, 254), +(3005, 7005, 200, 56, 60), +(3005, 4590, 200, 59, 254), +(3005, 4504, 200, 60, 254), +(3005, 6741, 200, 61, 68), +(3005, 6673, 200, 69, 72), +(3005, 11866, 200, 73, 77), +(3005, 11867, 200, 73, 77), +(3005, 11868, 200, 73, 77), +(3005, 10306, 200, 75, 77), +(3005, 10307, 200, 75, 77), +(3005, 10308, 200, 75, 77), +(3005, 15223, 200, 78, 79), +(3005, 15224, 200, 78, 79), +(3005, 15225, 200, 78, 79), +(3005, 15211, 200, 78, 82), +(3005, 15212, 200, 78, 82), +(3005, 15213, 200, 78, 82), +(3005, 15191, 200, 80, 84), +(3005, 15192, 200, 80, 84), +(3005, 15190, 200, 80, 84), +(3005, 19364, 200, 83, 254), +(3005, 19365, 200, 83, 254), +(3005, 19366, 200, 83, 254), +(3005, 22662, 200, 83, 254), +(3005, 22663, 200, 83, 254), +(3005, 19131, 200, 83, 254), +(3005, 22664, 200, 83, 254), +(3005, 19132, 200, 83, 254), +(3005, 19133, 200, 83, 254), +(3005, 19343, 200, 85, 254), +(3005, 19344, 200, 85, 254), +(3005, 19345, 200, 85, 254), +(3007, 5225, 200, 1, 254), +(3007, 25060, 200, 5, 60), +(3007, 4721, 200, 10, 62), +(3007, 4585, 200, 30, 254), +(3007, 4614, 200, 35, 49), +(3007, 4587, 200, 40, 254), +(3007, 4683, 200, 50, 56), +(3007, 4510, 200, 51, 64), +(3007, 4511, 200, 52, 59), +(3007, 4509, 200, 53, 254), +(3007, 4502, 200, 54, 254), +(3007, 8923, 200, 55, 69), +(3007, 4512, 200, 56, 78), +(3007, 4684, 200, 57, 63), +(3007, 4513, 200, 57, 254), +(3007, 4507, 200, 59, 254), +(3007, 4508, 200, 60, 73), +(3007, 4692, 200, 61, 65), +(3007, 6752, 200, 61, 68), +(3007, 4687, 200, 63, 254), +(3007, 4691, 200, 63, 254), +(3007, 4698, 200, 64, 64), +(3007, 4690, 200, 65, 71), +(3007, 5019, 200, 65, 254), +(3007, 5020, 200, 65, 254), +(3007, 6193, 200, 66, 254), +(3007, 6195, 200, 68, 254), +(3007, 8002, 200, 68, 254), +(3007, 6175, 200, 69, 70), +(3007, 6727, 200, 69, 254), +(3007, 6194, 200, 70, 254), +(3007, 8473, 200, 70, 254), +(3007, 8474, 200, 70, 254), +(3007, 10947, 200, 71, 75), +(3007, 10948, 200, 71, 75), +(3007, 10949, 200, 71, 75), +(3007, 11913, 200, 72, 76), +(3007, 11914, 200, 72, 76), +(3007, 11915, 200, 72, 76), +(3007, 10938, 200, 72, 254), +(3007, 10939, 200, 72, 254), +(3007, 10940, 200, 72, 254), +(3007, 10933, 200, 73, 77), +(3007, 10934, 200, 73, 77), +(3007, 10932, 200, 73, 77), +(3007, 10944, 200, 74, 78), +(3007, 10945, 200, 74, 78), +(3007, 10946, 200, 74, 78), +(3007, 11922, 200, 74, 254), +(3007, 11923, 200, 74, 254), +(3007, 11924, 200, 74, 254), +(3007, 10950, 200, 75, 79), +(3007, 10951, 200, 75, 79), +(3007, 10952, 200, 75, 79), +(3007, 14799, 200, 76, 80), +(3007, 14800, 200, 76, 80), +(3007, 14801, 200, 76, 80), +(3007, 14194, 200, 77, 81), +(3007, 14811, 200, 77, 81), +(3007, 14812, 200, 77, 81), +(3007, 14813, 200, 77, 81), +(3007, 14192, 200, 77, 81), +(3007, 14193, 200, 77, 81), +(3007, 14784, 200, 78, 82), +(3007, 14785, 200, 78, 82), +(3007, 14786, 200, 78, 82), +(3007, 14796, 200, 79, 80), +(3007, 14797, 200, 79, 80), +(3007, 14798, 200, 79, 80), +(3007, 14822, 200, 79, 254), +(3007, 14820, 200, 79, 254), +(3007, 14821, 200, 79, 254), +(3007, 14814, 200, 80, 84), +(3007, 14815, 200, 80, 84), +(3007, 14816, 200, 80, 84), +(3007, 14802, 200, 80, 254), +(3007, 14803, 200, 80, 254), +(3007, 14804, 200, 80, 254), +(3007, 18895, 200, 81, 83), +(3007, 18896, 200, 81, 83), +(3007, 18897, 200, 81, 83), +(3007, 18904, 200, 81, 254), +(3007, 18905, 200, 81, 254), +(3007, 18906, 200, 81, 254), +(3007, 18918, 200, 82, 254), +(3007, 18213, 200, 82, 254), +(3007, 18214, 200, 82, 254), +(3007, 18215, 200, 82, 254), +(3007, 18916, 200, 82, 254), +(3007, 18917, 200, 82, 254), +(3007, 18889, 200, 83, 254), +(3007, 18890, 200, 83, 254), +(3007, 18891, 200, 83, 254), +(3007, 19826, 200, 83, 254), +(3007, 19827, 200, 83, 254), +(3007, 19828, 200, 83, 254), +(3007, 18902, 200, 84, 254), +(3007, 18903, 200, 84, 254), +(3007, 19552, 200, 84, 254), +(3007, 19553, 200, 84, 254), +(3007, 19554, 200, 84, 254), +(3007, 18901, 200, 84, 254), +(3007, 19830, 200, 85, 254), +(3007, 18919, 200, 85, 254), +(3007, 19831, 200, 85, 254), +(3007, 18920, 200, 85, 254), +(3007, 22525, 200, 85, 254), +(3007, 18921, 200, 85, 254), +(3007, 22526, 200, 85, 254), +(3007, 22527, 200, 85, 254), +(3007, 18907, 200, 85, 254), +(3007, 18908, 200, 85, 254), +(3007, 18909, 200, 85, 254), +(3007, 19829, 200, 85, 254), +(3008, 4585, 200, 51, 254), +(3008, 4587, 200, 54, 254), +(3008, 4516, 200, 55, 254), +(3008, 4586, 200, 60, 254), +(3008, 8030, 200, 69, 254), +(3009, 5225, 200, 1, 254), +(3009, 25060, 200, 5, 82), +(3009, 4721, 200, 10, 62), +(3009, 4659, 200, 20, 51), +(3009, 4585, 200, 30, 254), +(3009, 4587, 200, 40, 254), +(3009, 4685, 200, 52, 62), +(3009, 4673, 200, 53, 254), +(3009, 4505, 200, 54, 62), +(3009, 8922, 200, 55, 69), +(3009, 4515, 200, 55, 254), +(3009, 4517, 200, 57, 254), +(3009, 4677, 200, 58, 254), +(3009, 4676, 200, 59, 74), +(3009, 6751, 200, 61, 68), +(3009, 4696, 200, 61, 254), +(3009, 4686, 200, 63, 64), +(3009, 4687, 200, 63, 254), +(3009, 4694, 200, 63, 254), +(3009, 5017, 200, 65, 68), +(3009, 5018, 200, 65, 68), +(3009, 4695, 200, 65, 254), +(3009, 6198, 200, 66, 254), +(3009, 8001, 200, 68, 254), +(3009, 6196, 200, 68, 254), +(3009, 6174, 200, 69, 69), +(3009, 6726, 200, 69, 70), +(3009, 8470, 200, 70, 79), +(3009, 6197, 200, 70, 254), +(3009, 8471, 200, 70, 254), +(3009, 10883, 200, 71, 75), +(3009, 10884, 200, 71, 75), +(3009, 10885, 200, 71, 75), +(3009, 11913, 200, 72, 76), +(3009, 11914, 200, 72, 76), +(3009, 11915, 200, 72, 76), +(3009, 10889, 200, 72, 254), +(3009, 10890, 200, 72, 254), +(3009, 10891, 200, 72, 254), +(3009, 10892, 200, 73, 77), +(3009, 10893, 200, 73, 77), +(3009, 10894, 200, 73, 77), +(3009, 11925, 200, 74, 78), +(3009, 11926, 200, 74, 78), +(3009, 11927, 200, 74, 78), +(3009, 10895, 200, 74, 254), +(3009, 10896, 200, 74, 254), +(3009, 10897, 200, 74, 254), +(3009, 10899, 200, 75, 254), +(3009, 10900, 200, 75, 254), +(3009, 10898, 200, 75, 254), +(3009, 15097, 200, 76, 80), +(3009, 15120, 200, 76, 80), +(3009, 15098, 200, 76, 80), +(3009, 15099, 200, 76, 80), +(3009, 15118, 200, 76, 80), +(3009, 15119, 200, 76, 80), +(3009, 15121, 200, 77, 81), +(3009, 15122, 200, 77, 81), +(3009, 15123, 200, 77, 81), +(3009, 14192, 200, 77, 81), +(3009, 14193, 200, 77, 81), +(3009, 14194, 200, 77, 81), +(3009, 15103, 200, 78, 82), +(3009, 15104, 200, 78, 82), +(3009, 15105, 200, 78, 82), +(3009, 15115, 200, 79, 83), +(3009, 15116, 200, 79, 83), +(3009, 15117, 200, 79, 83), +(3009, 15127, 200, 80, 84), +(3009, 15128, 200, 80, 84), +(3009, 15129, 200, 80, 84), +(3009, 15133, 200, 80, 84), +(3009, 15134, 200, 80, 84), +(3009, 15135, 200, 80, 84), +(3009, 19244, 200, 81, 254), +(3009, 19245, 200, 81, 254), +(3009, 19265, 200, 81, 254), +(3009, 19246, 200, 81, 254), +(3009, 19266, 200, 81, 254), +(3009, 19267, 200, 81, 254), +(3009, 18213, 200, 82, 254), +(3009, 18214, 200, 82, 254), +(3009, 18215, 200, 82, 254), +(3009, 19268, 200, 82, 254), +(3009, 19269, 200, 82, 254), +(3009, 19270, 200, 82, 254), +(3009, 19873, 200, 83, 84), +(3009, 19871, 200, 83, 84), +(3009, 19872, 200, 83, 84), +(3009, 19252, 200, 83, 254), +(3009, 19247, 200, 83, 254), +(3009, 19248, 200, 83, 254), +(3009, 19249, 200, 83, 254), +(3009, 19250, 200, 83, 254), +(3009, 19251, 200, 83, 254), +(3009, 19262, 200, 84, 254), +(3009, 19263, 200, 84, 254), +(3009, 19264, 200, 84, 254), +(3009, 19283, 200, 84, 254), +(3009, 19284, 200, 84, 254), +(3009, 19285, 200, 84, 254), +(3009, 19275, 200, 85, 254), +(3009, 19874, 200, 85, 254), +(3009, 19276, 200, 85, 254), +(3009, 19875, 200, 85, 254), +(3009, 19280, 200, 85, 254), +(3009, 19876, 200, 85, 254), +(3009, 19281, 200, 85, 254), +(3009, 22540, 200, 85, 254), +(3009, 19282, 200, 85, 254), +(3009, 22541, 200, 85, 254), +(3009, 22542, 200, 85, 254), +(3009, 19274, 200, 85, 254), +(3015, 4585, 200, 51, 254), +(3015, 4587, 200, 54, 254), +(3015, 4671, 200, 55, 254), +(3015, 4678, 200, 60, 68), +(3015, 8233, 200, 69, 254), +(3015, 8782, 200, 70, 79), +(3015, 14158, 200, 80, 84), +(3015, 14159, 200, 80, 84), +(3015, 14160, 200, 80, 84), +(3015, 18170, 200, 85, 254), +(3015, 18171, 200, 85, 254), +(3015, 18172, 200, 85, 254), +(3016, 5225, 200, 1, 254), +(3016, 4937, 200, 1, 4), +(3016, 4938, 200, 5, 9), +(3016, 25060, 200, 5, 254), +(3016, 4928, 200, 8, 31), +(3016, 4939, 200, 10, 14), +(3016, 4721, 200, 10, 62), +(3016, 4940, 200, 15, 19), +(3016, 4931, 200, 16, 39), +(3016, 4941, 200, 20, 24), +(3016, 4934, 200, 24, 47), +(3016, 4942, 200, 25, 29), +(3016, 4943, 200, 30, 34), +(3016, 5027, 200, 30, 49), +(3016, 4585, 200, 30, 254), +(3016, 4929, 200, 32, 53), +(3016, 4944, 200, 35, 39), +(3016, 4945, 200, 40, 44), +(3016, 4932, 200, 40, 59), +(3016, 4587, 200, 40, 254), +(3016, 4946, 200, 45, 49), +(3016, 4935, 200, 48, 64), +(3016, 4947, 200, 50, 54), +(3016, 5028, 200, 50, 56), +(3016, 5039, 200, 53, 254), +(3016, 4930, 200, 54, 66), +(3016, 5037, 200, 54, 254), +(3016, 4948, 200, 55, 59), +(3016, 8924, 200, 55, 69), +(3016, 5040, 200, 56, 254), +(3016, 5029, 200, 57, 63), +(3016, 5035, 200, 57, 254), +(3016, 5041, 200, 58, 59), +(3016, 5038, 200, 59, 254), +(3016, 4949, 200, 60, 64), +(3016, 4933, 200, 60, 67), +(3016, 5034, 200, 60, 74), +(3016, 6754, 200, 61, 68), +(3016, 5044, 200, 61, 254), +(3016, 4687, 200, 63, 254), +(3016, 5042, 200, 63, 254), +(3016, 5030, 200, 64, 64), +(3016, 4950, 200, 65, 65), +(3016, 5107, 200, 65, 65), +(3016, 4936, 200, 65, 68), +(3016, 5043, 200, 65, 254), +(3016, 5031, 200, 65, 254), +(3016, 5032, 200, 65, 254), +(3016, 6172, 200, 66, 70), +(3016, 6200, 200, 66, 254), +(3016, 6169, 200, 67, 70), +(3016, 6170, 200, 68, 72), +(3016, 6201, 200, 68, 254), +(3016, 8003, 200, 68, 254), +(3016, 6171, 200, 69, 73), +(3016, 6729, 200, 69, 73), +(3016, 8476, 200, 70, 254), +(3016, 8477, 200, 70, 254), +(3016, 6199, 200, 70, 254), +(3016, 10907, 200, 71, 75), +(3016, 10908, 200, 71, 75), +(3016, 10909, 200, 71, 75), +(3016, 10910, 200, 71, 75), +(3016, 11915, 200, 72, 76), +(3016, 10914, 200, 72, 76), +(3016, 10915, 200, 72, 76), +(3016, 11913, 200, 72, 76), +(3016, 10916, 200, 72, 76), +(3016, 11914, 200, 72, 76), +(3016, 10918, 200, 73, 77), +(3016, 10919, 200, 73, 77), +(3016, 10917, 200, 73, 77), +(3016, 11928, 200, 74, 78), +(3016, 11929, 200, 74, 78), +(3016, 10920, 200, 74, 78), +(3016, 11930, 200, 74, 78), +(3016, 10921, 200, 74, 78), +(3016, 10922, 200, 74, 78), +(3016, 10923, 200, 75, 254), +(3016, 10924, 200, 75, 254), +(3016, 10925, 200, 75, 254), +(3016, 14176, 200, 76, 80), +(3016, 14177, 200, 76, 80), +(3016, 14178, 200, 76, 80), +(3016, 14179, 200, 76, 80), +(3016, 14192, 200, 77, 81), +(3016, 14193, 200, 77, 81), +(3016, 14180, 200, 77, 81), +(3016, 14194, 200, 77, 81), +(3016, 14181, 200, 77, 81), +(3016, 14182, 200, 77, 81), +(3016, 14183, 200, 78, 82), +(3016, 14184, 200, 78, 82), +(3016, 14185, 200, 78, 82), +(3016, 14197, 200, 79, 83), +(3016, 14186, 200, 79, 83), +(3016, 14187, 200, 79, 83), +(3016, 14188, 200, 79, 83), +(3016, 14195, 200, 79, 83), +(3016, 14196, 200, 79, 83), +(3016, 14198, 200, 80, 84), +(3016, 14199, 200, 80, 84), +(3016, 14200, 200, 80, 84), +(3016, 18200, 200, 81, 254), +(3016, 16918, 200, 81, 254), +(3016, 16919, 200, 81, 254), +(3016, 16920, 200, 81, 254), +(3016, 18197, 200, 81, 254), +(3016, 18198, 200, 81, 254), +(3016, 18199, 200, 81, 254), +(3016, 18201, 200, 82, 254), +(3016, 18202, 200, 82, 254), +(3016, 18213, 200, 82, 254), +(3016, 18203, 200, 82, 254), +(3016, 18214, 200, 82, 254), +(3016, 18215, 200, 82, 254), +(3016, 18211, 200, 83, 254), +(3016, 18212, 200, 83, 254), +(3016, 19753, 200, 83, 254), +(3016, 18204, 200, 83, 254), +(3016, 19754, 200, 83, 254), +(3016, 18205, 200, 83, 254), +(3016, 19755, 200, 83, 254), +(3016, 18206, 200, 83, 254), +(3016, 18210, 200, 83, 254), +(3016, 18216, 200, 84, 254), +(3016, 18217, 200, 84, 254), +(3016, 18207, 200, 84, 254), +(3016, 18218, 200, 84, 254), +(3016, 18208, 200, 84, 254), +(3016, 18209, 200, 84, 254), +(3016, 19741, 200, 85, 254), +(3016, 19742, 200, 85, 254), +(3016, 19743, 200, 85, 254), +(3016, 22506, 200, 85, 254), +(3016, 22507, 200, 85, 254), +(3016, 18219, 200, 85, 254), +(3016, 22508, 200, 85, 254), +(3016, 18220, 200, 85, 254), +(3016, 18221, 200, 85, 254); + +DELETE +FROM bot_spells_entries +WHERE NOT EXISTS +(SELECT * +FROM spells_new +WHERE bot_spells_entries.spell_id = spells_new.id); +)", + }, + ManifestEntry{ + .version = 9054, + .description = "2024_12_29_discipline_subtypes_inserts.sql", + .check = "SELECT * FROM `bot_spells_entries` where `type` = 201", + .condition = "empty", + .match = "", + .sql = R"( +INSERT INTO `bot_spells_entries` (`npc_spells_id`, `spell_id`, `type`, `minlevel`, `maxlevel`, `priority`) +VALUES +(3001, 4672, 201, 53, 254, 3), +(3001, 4514, 201, 54, 254, 3), +(3001, 4501, 201, 57, 254, 3), +(3001, 4675, 201, 58, 59, 3), +(3001, 4498, 201, 60, 254, 3), +(3001, 6750, 201, 61, 68, 3), +(3001, 6192, 201, 68, 73, 3), +(3001, 6725, 201, 69, 73, 3), +(3001, 10970, 201, 73, 254, 1), +(3001, 10968, 201, 73, 254, 3), +(3001, 10969, 201, 73, 254, 2), +(3001, 10971, 201, 74, 78, 3), +(3001, 10972, 201, 74, 78, 2), +(3001, 10973, 201, 74, 78, 1), +(3001, 11917, 201, 74, 254, 2), +(3001, 11918, 201, 74, 254, 1), +(3001, 11916, 201, 74, 254, 3), +(3001, 15369, 201, 77, 254, 3), +(3001, 15370, 201, 77, 254, 2), +(3001, 15371, 201, 77, 254, 1), +(3001, 15375, 201, 78, 82, 3), +(3001, 15376, 201, 78, 82, 2), +(3001, 15377, 201, 78, 82, 1), +(3001, 15359, 201, 79, 83, 1), +(3001, 15357, 201, 79, 83, 3), +(3001, 15358, 201, 79, 83, 2), +(3001, 15379, 201, 80, 84, 2), +(3001, 15380, 201, 80, 84, 1), +(3001, 15378, 201, 80, 84, 3), +(3001, 19555, 201, 83, 254, 3), +(3001, 19556, 201, 83, 254, 2), +(3001, 19557, 201, 83, 254, 1), +(3001, 19553, 201, 84, 254, 2), +(3001, 19554, 201, 84, 254, 1), +(3001, 19528, 201, 84, 254, 3), +(3001, 19529, 201, 84, 254, 2), +(3001, 19552, 201, 84, 254, 3), +(3001, 19530, 201, 84, 254, 1), +(3001, 19549, 201, 85, 254, 3), +(3001, 19550, 201, 85, 254, 2), +(3001, 19551, 201, 85, 254, 1), +(3003, 4500, 201, 55, 254, 3), +(3003, 4590, 201, 59, 254, 3), +(3004, 4506, 201, 55, 79, 3), +(3004, 8019, 201, 69, 254, 3), +(3004, 10086, 201, 72, 76, 3), +(3004, 10087, 201, 72, 76, 2), +(3004, 10088, 201, 72, 76, 1), +(3004, 15020, 201, 77, 81, 3), +(3004, 15021, 201, 77, 81, 2), +(3004, 15022, 201, 77, 81, 1), +(3004, 15091, 201, 80, 84, 3), +(3004, 15092, 201, 80, 84, 2), +(3004, 15093, 201, 80, 84, 1), +(3004, 19153, 201, 82, 254, 2), +(3004, 19154, 201, 82, 254, 1), +(3004, 19152, 201, 82, 254, 3), +(3004, 19223, 201, 85, 254, 3), +(3004, 19224, 201, 85, 254, 2), +(3004, 19225, 201, 85, 254, 1), +(3005, 4520, 201, 55, 254, 3), +(3005, 4590, 201, 59, 254, 3), +(3007, 4511, 201, 52, 59, 3), +(3007, 4512, 201, 56, 78, 3), +(3007, 4513, 201, 57, 254, 3), +(3007, 4507, 201, 59, 254, 3), +(3007, 4508, 201, 60, 73, 3), +(3007, 4691, 201, 63, 254, 3), +(3007, 6194, 201, 70, 254, 3), +(3007, 8473, 201, 70, 254, 3), +(3007, 10944, 201, 74, 78, 3), +(3007, 10945, 201, 74, 78, 2), +(3007, 10946, 201, 74, 78, 1), +(3007, 11922, 201, 74, 254, 3), +(3007, 11923, 201, 74, 254, 2), +(3007, 11924, 201, 74, 254, 1), +(3007, 14796, 201, 79, 80, 3), +(3007, 14797, 201, 79, 80, 2), +(3007, 14798, 201, 79, 80, 1), +(3007, 14822, 201, 79, 254, 1), +(3007, 14820, 201, 79, 254, 3), +(3007, 14821, 201, 79, 254, 2), +(3007, 14814, 201, 80, 84, 3), +(3007, 14815, 201, 80, 84, 2), +(3007, 14816, 201, 80, 84, 1), +(3007, 18895, 201, 81, 83, 3), +(3007, 18896, 201, 81, 83, 2), +(3007, 18897, 201, 81, 83, 1), +(3007, 18902, 201, 84, 254, 2), +(3007, 18903, 201, 84, 254, 1), +(3007, 19552, 201, 84, 254, 3), +(3007, 19553, 201, 84, 254, 2), +(3007, 19554, 201, 84, 254, 1), +(3007, 18901, 201, 84, 254, 3), +(3007, 18919, 201, 85, 254, 3), +(3007, 18920, 201, 85, 254, 2), +(3007, 22525, 201, 85, 254, 3), +(3007, 18921, 201, 85, 254, 1), +(3007, 22526, 201, 85, 254, 2), +(3007, 22527, 201, 85, 254, 1), +(3008, 4586, 201, 60, 254, 3), +(3008, 8030, 201, 69, 254, 3), +(3009, 4659, 201, 20, 51, 3), +(3009, 4685, 201, 52, 62, 3), +(3009, 4505, 201, 54, 62, 3), +(3009, 4517, 201, 57, 254, 3), +(3009, 4677, 201, 58, 254, 3), +(3009, 4676, 201, 59, 74, 3), +(3009, 4696, 201, 61, 254, 3), +(3009, 4686, 201, 63, 64, 3), +(3009, 4694, 201, 63, 254, 3), +(3009, 5017, 201, 65, 68, 3), +(3009, 5018, 201, 65, 68, 3), +(3009, 4695, 201, 65, 254, 3), +(3009, 8001, 201, 68, 254, 3), +(3009, 6196, 201, 68, 254, 3), +(3009, 6174, 201, 69, 69, 3), +(3009, 8470, 201, 70, 79, 3), +(3009, 6197, 201, 70, 254, 3), +(3009, 10892, 201, 73, 77, 3), +(3009, 10893, 201, 73, 77, 2), +(3009, 10894, 201, 73, 77, 1), +(3009, 11925, 201, 74, 78, 3), +(3009, 11926, 201, 74, 78, 2), +(3009, 11927, 201, 74, 78, 1), +(3009, 10899, 201, 75, 254, 2), +(3009, 10900, 201, 75, 254, 1), +(3009, 10898, 201, 75, 254, 3), +(3009, 15103, 201, 78, 82, 3), +(3009, 15104, 201, 78, 82, 2), +(3009, 15105, 201, 78, 82, 1), +(3009, 15115, 201, 79, 83, 3), +(3009, 15116, 201, 79, 83, 2), +(3009, 15117, 201, 79, 83, 1), +(3009, 15133, 201, 80, 84, 3), +(3009, 15134, 201, 80, 84, 2), +(3009, 15135, 201, 80, 84, 1), +(3009, 19252, 201, 83, 254, 1), +(3009, 19250, 201, 83, 254, 3), +(3009, 19251, 201, 83, 254, 2), +(3009, 19262, 201, 84, 254, 3), +(3009, 19263, 201, 84, 254, 2), +(3009, 19264, 201, 84, 254, 1), +(3009, 19280, 201, 85, 254, 3), +(3009, 19281, 201, 85, 254, 2), +(3009, 19282, 201, 85, 254, 1), +(3015, 4678, 201, 60, 68, 3), +(3015, 8233, 201, 69, 254, 3), +(3015, 8782, 201, 70, 79, 3), +(3015, 14158, 201, 80, 84, 3), +(3015, 14159, 201, 80, 84, 2), +(3015, 14160, 201, 80, 84, 1), +(3015, 18170, 201, 85, 254, 3), +(3015, 18171, 201, 85, 254, 2), +(3015, 18172, 201, 85, 254, 1), +(3016, 5027, 201, 30, 49, 3), +(3016, 5028, 201, 50, 56, 3), +(3016, 5039, 201, 53, 254, 3), +(3016, 5037, 201, 54, 254, 3), +(3016, 5029, 201, 57, 63, 3), +(3016, 5035, 201, 57, 254, 3), +(3016, 5041, 201, 58, 59, 3), +(3016, 5034, 201, 60, 74, 3), +(3016, 6754, 201, 61, 68, 3), +(3016, 5030, 201, 64, 64, 3), +(3016, 5043, 201, 65, 254, 3), +(3016, 5031, 201, 65, 254, 3), +(3016, 5032, 201, 65, 254, 3), +(3016, 8003, 201, 68, 254, 3), +(3016, 6729, 201, 69, 73, 3), +(3016, 6199, 201, 70, 254, 3), +(3016, 10914, 201, 72, 76, 3), +(3016, 10915, 201, 72, 76, 2), +(3016, 10916, 201, 72, 76, 1), +(3016, 11928, 201, 74, 78, 3), +(3016, 11929, 201, 74, 78, 2), +(3016, 11930, 201, 74, 78, 1), +(3016, 10923, 201, 75, 254, 3), +(3016, 10924, 201, 75, 254, 2), +(3016, 10925, 201, 75, 254, 1), +(3016, 14180, 201, 77, 81, 3), +(3016, 14181, 201, 77, 81, 2), +(3016, 14182, 201, 77, 81, 1), +(3016, 14197, 201, 79, 83, 1), +(3016, 14195, 201, 79, 83, 3), +(3016, 14196, 201, 79, 83, 2), +(3016, 14198, 201, 80, 84, 3), +(3016, 14199, 201, 80, 84, 2), +(3016, 14200, 201, 80, 84, 1), +(3016, 16918, 201, 81, 254, 3), +(3016, 16919, 201, 81, 254, 2), +(3016, 16920, 201, 81, 254, 1), +(3016, 18201, 201, 82, 254, 3), +(3016, 18202, 201, 82, 254, 2), +(3016, 18203, 201, 82, 254, 1), +(3016, 18216, 201, 84, 254, 3), +(3016, 18217, 201, 84, 254, 2), +(3016, 18218, 201, 84, 254, 1), +(3016, 19741, 201, 85, 254, 3), +(3016, 19742, 201, 85, 254, 2), +(3016, 19743, 201, 85, 254, 1), +(3016, 22506, 201, 85, 254, 3), +(3016, 22507, 201, 85, 254, 2), +(3016, 18219, 201, 85, 254, 3), +(3016, 22508, 201, 85, 254, 1), +(3016, 18220, 201, 85, 254, 2), +(3016, 18221, 201, 85, 254, 1); + +INSERT INTO `bot_spells_entries` (`npc_spells_id`, `spell_id`, `type`, `minlevel`, `maxlevel`, `priority`) +VALUES +(3001, 4587, 202, 40, 254, 3), +(3001, 4503, 202, 52, 254, 3), +(3001, 4499, 202, 55, 64, 3), +(3001, 4674, 202, 56, 254, 3), +(3001, 4670, 202, 59, 254, 3), +(3001, 4688, 202, 65, 71, 3), +(3001, 8000, 202, 68, 254, 3), +(3001, 6190, 202, 70, 254, 3), +(3001, 10965, 202, 72, 254, 3), +(3001, 10966, 202, 72, 254, 2), +(3001, 10967, 202, 72, 254, 1), +(3003, 4587, 202, 54, 254, 3), +(3003, 7004, 202, 56, 60, 3), +(3003, 4518, 202, 60, 254, 3), +(3003, 6731, 202, 61, 68, 3), +(3003, 6663, 202, 69, 72, 3), +(3003, 11854, 202, 73, 77, 3), +(3003, 11855, 202, 73, 77, 2), +(3003, 11856, 202, 73, 77, 1), +(3003, 14987, 202, 78, 82, 3), +(3003, 14988, 202, 78, 82, 2), +(3003, 14989, 202, 78, 82, 1), +(3003, 19103, 202, 83, 254, 1), +(3003, 19131, 202, 83, 254, 3), +(3003, 19132, 202, 83, 254, 2), +(3003, 19133, 202, 83, 254, 1), +(3003, 22665, 202, 83, 254, 3), +(3003, 22666, 202, 83, 254, 2), +(3003, 22667, 202, 83, 254, 1), +(3003, 19101, 202, 83, 254, 3), +(3003, 19102, 202, 83, 254, 2), +(3004, 4587, 202, 54, 254, 3), +(3004, 4519, 202, 60, 254, 3), +(3005, 4587, 202, 54, 254, 3), +(3005, 7005, 202, 56, 60, 3), +(3005, 4504, 202, 60, 254, 3), +(3005, 6741, 202, 61, 68, 3), +(3005, 6673, 202, 69, 72, 3), +(3005, 11866, 202, 73, 77, 3), +(3005, 11867, 202, 73, 77, 2), +(3005, 11868, 202, 73, 77, 1), +(3005, 15211, 202, 78, 82, 3), +(3005, 15212, 202, 78, 82, 2), +(3005, 15213, 202, 78, 82, 1), +(3005, 19364, 202, 83, 254, 3), +(3005, 19365, 202, 83, 254, 2), +(3005, 19366, 202, 83, 254, 1), +(3005, 22662, 202, 83, 254, 3), +(3005, 22663, 202, 83, 254, 2), +(3005, 19131, 202, 83, 254, 3), +(3005, 22664, 202, 83, 254, 1), +(3005, 19132, 202, 83, 254, 2), +(3005, 19133, 202, 83, 254, 1), +(3007, 4587, 202, 40, 254, 3), +(3007, 4510, 202, 51, 64, 3), +(3007, 4509, 202, 53, 254, 3), +(3007, 4502, 202, 54, 254, 3), +(3007, 4690, 202, 65, 71, 3), +(3007, 6195, 202, 68, 254, 3), +(3007, 10938, 202, 72, 254, 3), +(3007, 10939, 202, 72, 254, 2), +(3007, 10940, 202, 72, 254, 1), +(3007, 19830, 202, 85, 254, 2), +(3007, 19831, 202, 85, 254, 1), +(3007, 19829, 202, 85, 254, 3), +(3008, 4587, 202, 54, 254, 3), +(3008, 4516, 202, 55, 254, 3), +(3009, 4587, 202, 40, 254, 3), +(3009, 4673, 202, 53, 254, 3), +(3009, 4515, 202, 55, 254, 3), +(3009, 10889, 202, 72, 254, 3), +(3009, 10890, 202, 72, 254, 2), +(3009, 10891, 202, 72, 254, 1), +(3009, 10895, 202, 74, 254, 3), +(3009, 10896, 202, 74, 254, 2), +(3009, 10897, 202, 74, 254, 1), +(3015, 4587, 202, 54, 254, 3), +(3015, 4671, 202, 55, 254, 3), +(3016, 4587, 202, 40, 254, 3), +(3016, 5040, 202, 56, 254, 3), +(3016, 5038, 202, 59, 254, 3), +(3016, 5043, 202, 65, 254, 3), +(3016, 6200, 202, 66, 254, 3), +(3016, 6201, 202, 68, 254, 3), +(3016, 8003, 202, 68, 254, 3), +(3016, 8476, 202, 70, 254, 3); + +DELETE +FROM bot_spells_entries +WHERE NOT EXISTS +(SELECT * +FROM spells_new +WHERE bot_spells_entries.spell_id = spells_new.id); +)", } // -- template; copy/paste this when you need to create a new entry // ManifestEntry{ diff --git a/common/database/database_update_manifest_custom.cpp b/common/database/database_update_manifest_custom.cpp new file mode 100644 index 000000000..e441d600c --- /dev/null +++ b/common/database/database_update_manifest_custom.cpp @@ -0,0 +1,55 @@ +#include "database_update.h" + +std::vector manifest_entries_custom = { + ManifestEntry{ + .version = 1, + .description = "2025_05_16_new_database_check_test", + .check = "SHOW TABLES LIKE 'new_table'", + .condition = "empty", + .match = "", + .sql = R"( +CREATE TABLE `new_table` ( + `id` int NOT NULL AUTO_INCREMENT, + PRIMARY KEY (`id`) +); +)", + .content_schema_update = false, + }, +// Used for testing +// ManifestEntry{ +// .version = 9229, +// .description = "new_database_check_test", +// .check = "SHOW TABLES LIKE 'new_table'", +// .condition = "empty", +// .match = "", +// .sql = R"( +//CREATE TABLE `new_table` ( +// `id` int NOT NULL AUTO_INCREMENT, +// PRIMARY KEY (`id`) +//); +//CREATE TABLE `new_table1` ( +// `id` int NOT NULL AUTO_INCREMENT, +// PRIMARY KEY (`id`) +//); +//CREATE TABLE `new_table2` ( +// `id` int NOT NULL AUTO_INCREMENT, +// PRIMARY KEY (`id`) +//); +//CREATE TABLE `new_table3` ( +// `id` int NOT NULL AUTO_INCREMENT, +// PRIMARY KEY (`id`) +//); +//)", +// } + +}; + +// see struct definitions for what each field does +// struct ManifestEntry { +// int version{}; // database version of the migration +// std::string description{}; // description of the migration ex: "add_new_table" or "add_index_to_table" +// std::string check{}; // query that checks against the condition +// std::string condition{}; // condition or "match_type" - Possible values [contains|match|missing|empty|not_empty] +// std::string match{}; // match field that is not always used, but works in conjunction with "condition" values [missing|match|contains] +// std::string sql{}; // the SQL DDL that gets ran when the condition is true +// }; diff --git a/common/database_instances.cpp b/common/database_instances.cpp index e9068a534..e32720f38 100644 --- a/common/database_instances.cpp +++ b/common/database_instances.cpp @@ -30,8 +30,8 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "../common/repositories/respawn_times_repository.h" #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 @@ -114,7 +114,9 @@ bool Database::CheckInstanceExpired(uint16 instance_id) timeval tv{}; gettimeofday(&tv, nullptr); - return (i.start_time + i.duration) <= tv.tv_sec; + // Use uint64_t for the addition to prevent overflow + uint64_t expiration_time = static_cast(i.start_time) + static_cast(i.duration); + return expiration_time <= tv.tv_sec; } bool Database::CreateInstance(uint16 instance_id, uint32 zone_id, uint32 version, uint32 duration) @@ -126,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; @@ -469,16 +495,18 @@ void Database::AssignRaidToInstance(uint32 raid_id, uint32 instance_id) void Database::DeleteInstance(uint16 instance_id) { + // I'm not sure why this isn't in here but we should add it in a later change and make sure it's tested + // InstanceListRepository::DeleteWhere(*this, fmt::format("id = {}", instance_id)); InstanceListPlayerRepository::DeleteWhere(*this, fmt::format("id = {}", instance_id)); - RespawnTimesRepository::DeleteWhere(*this, fmt::format("instance_id = {}", instance_id)); - SpawnConditionValuesRepository::DeleteWhere(*this, fmt::format("instance_id = {}", instance_id)); - DynamicZoneMembersRepository::DeleteByInstance(*this, 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) @@ -533,14 +561,12 @@ void Database::GetCharactersInInstance(uint16 instance_id, std::list &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; @@ -551,16 +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)); + 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) @@ -572,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); } diff --git a/common/database_schema.h b/common/database_schema.h index e0b87b94d..de6f8472f 100644 --- a/common/database_schema.h +++ b/common/database_schema.h @@ -51,6 +51,7 @@ namespace DatabaseSchema { {"character_enabledtasks", "charid"}, {"character_expedition_lockouts", "character_id"}, {"character_exp_modifiers", "character_id"}, + {"character_evolving_items", "character_id"}, {"character_inspect_messages", "id"}, {"character_instance_safereturns", "character_id"}, {"character_item_recast", "id"}, @@ -63,6 +64,7 @@ namespace DatabaseSchema { {"character_pet_buffs", "char_id"}, {"character_pet_info", "char_id"}, {"character_pet_inventory", "char_id"}, + {"character_pet_name", "character_id"}, {"character_peqzone_flags", "id"}, {"character_potionbelt", "id"}, {"character_skills", "id"}, @@ -78,7 +80,7 @@ namespace DatabaseSchema { {"guild_members", "char_id"}, {"guilds", "id"}, {"instance_list_player", "id"}, - {"inventory", "charid"}, + {"inventory", "character_id"}, {"inventory_snapshots", "charid"}, {"keyring", "char_id"}, {"mail", "charid"}, @@ -124,6 +126,7 @@ namespace DatabaseSchema { "character_enabledtasks", "character_expedition_lockouts", "character_exp_modifiers", + "character_evolving_items", "character_inspect_messages", "character_instance_safereturns", "character_item_recast", @@ -136,6 +139,7 @@ namespace DatabaseSchema { "character_pet_buffs", "character_pet_info", "character_pet_inventory", + "character_pet_name", "character_peqzone_flags", "character_potionbelt", "character_skills", @@ -212,6 +216,7 @@ namespace DatabaseSchema { "ground_spawns", "horses", "items", + "items_evolving_details", "ldon_trap_entries", "ldon_trap_templates", "lootdrop", @@ -287,32 +292,6 @@ namespace DatabaseSchema { }; } - /** - * Gets QueryServer tables - * - * @return - */ - static std::vector GetQueryServerTables() - { - return { - "qs_merchant_transaction_record", - "qs_merchant_transaction_record_entries", - "qs_player_aa_rate_hourly", - "qs_player_delete_record", - "qs_player_delete_record_entries", - "qs_player_events", - "qs_player_handin_record", - "qs_player_handin_record_entries", - "qs_player_move_record", - "qs_player_move_record_entries", - "qs_player_npc_kill_record", - "qs_player_npc_kill_record_entries", - "qs_player_speech", - "qs_player_trade_record", - "qs_player_trade_record_entries", - }; - } - /** * Gets state tables * Tables that keep track of server state @@ -333,10 +312,9 @@ namespace DatabaseSchema { "completed_shared_task_members", "completed_shared_tasks", "discord_webhooks", + "dynamic_zone_lockouts", "dynamic_zone_members", "dynamic_zones", - "expedition_lockouts", - "expeditions", "gm_ips", "group_id", "group_leaders", @@ -355,12 +333,25 @@ namespace DatabaseSchema { "saylink", "server_scheduled_events", "spawn2_disabled", + "player_event_aa_purchase", + "player_event_killed_npc", + "player_event_killed_named_npc", + "player_event_killed_raid_npc", "player_event_log_settings", "player_event_logs", + "player_event_loot_items", + "player_event_merchant_purchase", + "player_event_merchant_sell", + "player_event_npc_handin", + "player_event_npc_handin_entries", + "player_event_speech", + "player_event_trade", + "player_event_trade_entries", "shared_task_activity_state", "shared_task_dynamic_zones", "shared_task_members", "shared_tasks", + "zone_state_spawns", }; } @@ -402,6 +393,7 @@ namespace DatabaseSchema { static std::vector GetBotTables() { return { + "bot_blocked_buffs", "bot_buffs", "bot_command_settings", "bot_create_combinations", @@ -415,6 +407,7 @@ namespace DatabaseSchema { "bot_pet_buffs", "bot_pet_inventories", "bot_pets", + "bot_settings", "bot_spell_casting_chances", "bot_spell_settings", "bot_spells_entries", diff --git a/common/dbcore.cpp b/common/dbcore.cpp index 82a9bc396..ff0afac3a 100644 --- a/common/dbcore.cpp +++ b/common/dbcore.cpp @@ -160,7 +160,7 @@ MySQLRequestResult DBcore::QueryDatabase(const char *query, uint32 querylen, boo (uint32) mysql_insert_id(mysql) ); - if (LogSys.log_settings[Logs::MySQLQuery].is_category_enabled == 1) { + if (EQEmuLogSys::Instance()->log_settings[Logs::MySQLQuery].is_category_enabled == 1) { if ((strncasecmp(query, "select", 6) == 0)) { LogMySQLQuery( "{0} -- ({1} row{2} returned) ({3}s)", @@ -189,9 +189,9 @@ void DBcore::TransactionBegin() QueryDatabase("START TRANSACTION"); } -void DBcore::TransactionCommit() +MySQLRequestResult DBcore::TransactionCommit() { - QueryDatabase("COMMIT"); + return QueryDatabase("COMMIT"); } void DBcore::TransactionRollback() @@ -302,7 +302,9 @@ std::string DBcore::Escape(const std::string& s) void DBcore::SetMutex(Mutex *mutex) { - safe_delete(m_mutex); + if (m_mutex && m_mutex != mutex) { + safe_delete(m_mutex); + } DBcore::m_mutex = mutex; } diff --git a/common/dbcore.h b/common/dbcore.h index cefdc6522..6d07f1bdb 100644 --- a/common/dbcore.h +++ b/common/dbcore.h @@ -32,7 +32,7 @@ public: MySQLRequestResult QueryDatabase(const std::string& query, bool retryOnFailureOnce = true); MySQLRequestResult QueryDatabaseMulti(const std::string &query); void TransactionBegin(); - void TransactionCommit(); + MySQLRequestResult TransactionCommit(); void TransactionRollback(); std::string Escape(const std::string& s); uint32 DoEscapeString(char *tobuf, const char *frombuf, uint32 fromlen); diff --git a/common/discord/discord.h b/common/discord/discord.h index 5cc59202a..8b8a19290 100644 --- a/common/discord/discord.h +++ b/common/discord/discord.h @@ -4,7 +4,6 @@ #include #include "../types.h" -#include "../http/httplib.h" #include "../repositories/player_event_logs_repository.h" #include "../events/player_events.h" diff --git a/common/discord/discord_manager.cpp b/common/discord/discord_manager.cpp index a425d4c34..fef6debb4 100644 --- a/common/discord/discord_manager.cpp +++ b/common/discord/discord_manager.cpp @@ -26,7 +26,7 @@ void DiscordManager::ProcessMessageQueue() continue; } - auto webhook = LogSys.GetDiscordWebhooks()[q.first]; + auto webhook = EQEmuLogSys::Instance()->GetDiscordWebhooks()[q.first]; std::string message; for (auto &m: q.second) { @@ -68,7 +68,7 @@ void DiscordManager::ProcessMessageQueue() void DiscordManager::QueuePlayerEventMessage(const PlayerEvent::PlayerEventContainer& e) { - auto w = player_event_logs.GetDiscordWebhookUrlFromEventType(e.player_event_log.event_type_id); + auto w = PlayerEventLogs::Instance()->GetDiscordWebhookUrlFromEventType(e.player_event_log.event_type_id); if (!w.empty()) { Discord::SendPlayerEventMessage(e, w); } diff --git a/common/discord/discord_manager.h b/common/discord/discord_manager.h index f577d994f..d02180859 100644 --- a/common/discord/discord_manager.h +++ b/common/discord/discord_manager.h @@ -13,6 +13,12 @@ public: void QueueWebhookMessage(uint32 webhook_id, const std::string& message); void ProcessMessageQueue(); void QueuePlayerEventMessage(const PlayerEvent::PlayerEventContainer& e); + + static DiscordManager* Instance() + { + static DiscordManager instance; + return &instance; + } private: std::mutex webhook_queue_lock{}; std::map> webhook_message_queue{}; diff --git a/common/dynamic_zone_base.cpp b/common/dynamic_zone_base.cpp index 78b38de21..4ad08c2db 100644 --- a/common/dynamic_zone_base.cpp +++ b/common/dynamic_zone_base.cpp @@ -1,11 +1,13 @@ #include "dynamic_zone_base.h" #include "database.h" #include "eqemu_logsys.h" -#include "repositories/instance_list_repository.h" -#include "repositories/instance_list_player_repository.h" #include "rulesys.h" #include "servertalk.h" #include "util/uuid.h" +#include "repositories/character_expedition_lockouts_repository.h" +#include "repositories/dynamic_zone_lockouts_repository.h" +#include "repositories/instance_list_repository.h" +#include "repositories/instance_list_player_repository.h" DynamicZoneBase::DynamicZoneBase(DynamicZonesRepository::DynamicZoneInstance&& entry) { @@ -56,15 +58,16 @@ uint32_t DynamicZoneBase::CreateInstance() insert_instance.start_time = static_cast(std::chrono::system_clock::to_time_t(m_start_time)); insert_instance.duration = static_cast(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; } @@ -93,13 +96,15 @@ void DynamicZoneBase::LoadRepositoryResult(DynamicZonesRepository::DynamicZoneIn m_zonein.y = dz_entry.zone_in_y; m_zonein.z = dz_entry.zone_in_z; m_zonein.heading = dz_entry.zone_in_heading; - m_has_zonein = (dz_entry.has_zone_in != 0); + m_has_zonein = dz_entry.has_zone_in != 0; + m_is_locked = dz_entry.is_locked; + m_add_replay = dz_entry.add_replay; // instance_list portion m_zone_id = dz_entry.zone; m_zone_version = dz_entry.version; m_start_time = std::chrono::system_clock::from_time_t(dz_entry.start_time); m_duration = std::chrono::seconds(dz_entry.duration); - m_never_expires = (dz_entry.never_expires != 0); + m_never_expires = dz_entry.never_expires != 0; m_expire_time = m_start_time + m_duration; } @@ -119,37 +124,40 @@ void DynamicZoneBase::AddMemberFromRepositoryResult( uint32_t DynamicZoneBase::SaveToDatabase() { LogDynamicZonesDetail("Saving dz instance [{}] to database", m_instance_id); - - if (m_instance_id != 0) + if (m_instance_id == 0) { - auto insert_dz = DynamicZonesRepository::NewEntity(); - insert_dz.uuid = m_uuid; - insert_dz.name = m_name; - insert_dz.leader_id = m_leader.id; - insert_dz.min_players = m_min_players; - insert_dz.max_players = m_max_players; - insert_dz.instance_id = m_instance_id, - insert_dz.type = static_cast(m_type); - insert_dz.dz_switch_id = m_dz_switch_id; - insert_dz.compass_zone_id = m_compass.zone_id; - insert_dz.compass_x = m_compass.x; - insert_dz.compass_y = m_compass.y; - insert_dz.compass_z = m_compass.z; - insert_dz.safe_return_zone_id = m_safereturn.zone_id; - insert_dz.safe_return_x = m_safereturn.x; - insert_dz.safe_return_y = m_safereturn.y; - insert_dz.safe_return_z = m_safereturn.z; - insert_dz.safe_return_heading = m_safereturn.heading; - insert_dz.zone_in_x = m_zonein.x; - insert_dz.zone_in_y = m_zonein.y; - insert_dz.zone_in_z = m_zonein.z; - insert_dz.zone_in_heading = m_zonein.heading; - insert_dz.has_zone_in = m_has_zonein; - - auto inserted_dz = DynamicZonesRepository::InsertOne(GetDatabase(), insert_dz); - return inserted_dz.id; + return 0; } - return 0; + + auto dz = DynamicZonesRepository::NewEntity(); + dz.uuid = m_uuid; + dz.name = m_name; + dz.leader_id = m_leader.id; + dz.min_players = m_min_players; + dz.max_players = m_max_players; + dz.instance_id = static_cast(m_instance_id), + dz.type = static_cast(m_type); + dz.dz_switch_id = m_dz_switch_id; + dz.compass_zone_id = m_compass.zone_id; + dz.compass_x = m_compass.x; + dz.compass_y = m_compass.y; + dz.compass_z = m_compass.z; + dz.safe_return_zone_id = m_safereturn.zone_id; + dz.safe_return_x = m_safereturn.x; + dz.safe_return_y = m_safereturn.y; + dz.safe_return_z = m_safereturn.z; + dz.safe_return_heading = m_safereturn.heading; + dz.zone_in_x = m_zonein.x; + dz.zone_in_y = m_zonein.y; + dz.zone_in_z = m_zonein.z; + dz.zone_in_heading = m_zonein.heading; + dz.has_zone_in = static_cast(m_has_zonein); + dz.is_locked = static_cast(m_is_locked); + dz.add_replay = static_cast(m_add_replay); + + dz = DynamicZonesRepository::InsertOne(GetDatabase(), std::move(dz)); + + return dz.id; } bool DynamicZoneBase::AddMember(const DynamicZoneMember& add_member) @@ -196,10 +204,9 @@ bool DynamicZoneBase::RemoveMember(const DynamicZoneMember& remove_member) return true; } -bool DynamicZoneBase::SwapMember( - const DynamicZoneMember& add_member, const std::string& remove_char_name) +bool DynamicZoneBase::SwapMember(const DynamicZoneMember& add_member, const std::string& remove_name) { - auto remove_member = GetMemberData(remove_char_name); + auto remove_member = GetMemberData(remove_name); if (!add_member.IsValid() || !remove_member.IsValid()) { return false; @@ -230,9 +237,18 @@ void DynamicZoneBase::RemoveAllMembers() void DynamicZoneBase::SaveMembers(const std::vector& members) { + if (members.empty()) + { + return; + } + LogDynamicZonesDetail("Saving [{}] member(s) for dz [{}]", members.size(), m_id); m_members = members; + if (m_members.size() > m_max_players) + { + m_members.resize(m_max_players); + } // the lower level instance_list_players needs to be kept updated as well std::vector insert_members; @@ -242,12 +258,12 @@ void DynamicZoneBase::SaveMembers(const std::vector& members) DynamicZoneMembersRepository::DynamicZoneMembers member_entry{}; member_entry.dynamic_zone_id = m_id; member_entry.character_id = member.id; - insert_members.emplace_back(member_entry); + insert_members.push_back(member_entry); - InstanceListPlayerRepository::InstanceListPlayer player_entry; - player_entry.id = static_cast(m_instance_id); - player_entry.charid = static_cast(member.id); - insert_players.emplace_back(player_entry); + InstanceListPlayerRepository::InstanceListPlayer player_entry{}; + player_entry.id = m_instance_id; + player_entry.charid = member.id; + insert_players.push_back(player_entry); } DynamicZoneMembersRepository::InsertOrUpdateMany(GetDatabase(), insert_members); @@ -339,6 +355,44 @@ void DynamicZoneBase::SetLeader(const DynamicZoneMember& new_leader, bool update } } +void DynamicZoneBase::SetLocked(bool lock, bool update_db, DzLockMsg lock_msg, uint32_t color) +{ + m_is_locked = lock; + + if (update_db) + { + DynamicZonesRepository::UpdateLocked(GetDatabase(), m_id, lock); + + ServerPacket pack(ServerOP_DzLock, sizeof(ServerDzLock_Struct)); + auto buf = reinterpret_cast(pack.pBuffer); + buf->dz_id = GetID(); + buf->sender_zone_id = GetCurrentZoneID(); + buf->sender_instance_id = GetCurrentInstanceID(); + buf->lock = m_is_locked; + buf->lock_msg = static_cast(lock_msg); + buf->color = color; + SendServerPacket(&pack); + } +} + +void DynamicZoneBase::SetReplayOnJoin(bool enabled, bool update_db) +{ + m_add_replay = enabled; + + if (update_db) + { + DynamicZonesRepository::UpdateReplayOnJoin(GetDatabase(), m_id, enabled); + + ServerPacket pack(ServerOP_DzReplayOnJoin, sizeof(ServerDzBool_Struct)); + auto buf = reinterpret_cast(pack.pBuffer); + buf->dz_id = GetID(); + buf->sender_zone_id = GetCurrentZoneID(); + buf->sender_instance_id = GetCurrentInstanceID(); + buf->enabled = enabled; + SendServerPacket(&pack); + } +} + uint32_t DynamicZoneBase::GetSecondsRemaining() const { auto remaining = std::chrono::duration_cast(GetDurationRemaining()).count(); @@ -478,13 +532,13 @@ void DynamicZoneBase::RemoveInternalMember(uint32_t character_id) ), m_members.end()); } -bool DynamicZoneBase::HasMember(uint32_t character_id) +bool DynamicZoneBase::HasMember(uint32_t character_id) const { return std::any_of(m_members.begin(), m_members.end(), [&](const DynamicZoneMember& member) { return member.id == character_id; }); } -bool DynamicZoneBase::HasMember(const std::string& character_name) +bool DynamicZoneBase::HasMember(const std::string& character_name) const { return std::any_of(m_members.begin(), m_members.end(), [&](const DynamicZoneMember& member) { @@ -590,35 +644,34 @@ std::string DynamicZoneBase::GetDynamicZoneTypeName(DynamicZoneType dz_type) } } -EQ::Net::DynamicPacket DynamicZoneBase::GetSerializedDzPacket() +std::unique_ptr DynamicZoneBase::CreateServerPacket(uint16_t zone_id, uint16_t instance_id) { - EQ::Net::DynamicPacket dyn_pack; - dyn_pack.PutSerialize(0, *this); + std::ostringstream ss = GetSerialized(); + std::string_view sv = ss.view(); - LogDynamicZonesDetail("Serialized server dz size [{}]", dyn_pack.Length()); - return dyn_pack; -} - -std::unique_ptr DynamicZoneBase::CreateServerDzCreatePacket( - uint16_t origin_zone_id, uint16_t origin_instance_id) -{ - EQ::Net::DynamicPacket dyn_pack = GetSerializedDzPacket(); - - auto pack_size = sizeof(ServerDzCreateSerialized_Struct) + dyn_pack.Length(); + auto pack_size = sizeof(ServerDzCreate_Struct) + sv.size(); auto pack = std::make_unique(ServerOP_DzCreated, static_cast(pack_size)); - auto buf = reinterpret_cast(pack->pBuffer); - buf->origin_zone_id = origin_zone_id; - buf->origin_instance_id = origin_instance_id; - buf->cereal_size = static_cast(dyn_pack.Length()); - memcpy(buf->cereal_data, dyn_pack.Data(), dyn_pack.Length()); + auto buf = reinterpret_cast(pack->pBuffer); + buf->origin_zone_id = zone_id; + buf->origin_instance_id = instance_id; + buf->dz_id = GetID(); + buf->cereal_size = static_cast(sv.size()); + memcpy(buf->cereal_data, sv.data(), sv.size()); return pack; } -void DynamicZoneBase::LoadSerializedDzPacket(char* cereal_data, uint32_t cereal_size) +std::ostringstream DynamicZoneBase::GetSerialized() { - LogDynamicZonesDetail("Deserializing server dz size [{}]", cereal_size); - EQ::Util::MemoryStreamReader ss(cereal_data, cereal_size); + std::ostringstream ss; + cereal::BinaryOutputArchive archive(ss); + archive(*this); + return ss; +} + +void DynamicZoneBase::Unserialize(std::span buf) +{ + EQ::Util::MemoryStreamReader ss(buf.data(), buf.size()); cereal::BinaryInputArchive archive(ss); archive(*this); } @@ -647,3 +700,180 @@ void DynamicZoneBase::LoadTemplate(const DynamicZoneTemplatesRepository::Dynamic m_zonein.z = dz_template.zone_in_z; m_zonein.heading = dz_template.zone_in_h; } + +std::vector DynamicZoneBase::GetMemberIds() +{ + std::vector ids; + ids.reserve(m_members.size()); + for (const auto& member : m_members) + { + ids.push_back(member.id); + } + return ids; +} + +bool DynamicZoneBase::HasLockout(const std::string& event) +{ + return std::ranges::any_of(m_lockouts, [&](const auto& l) { return l.IsEvent(event); }); +} + +bool DynamicZoneBase::HasReplayLockout() +{ + return HasLockout(DzLockout::ReplayTimer); +} + +void DynamicZoneBase::AddLockout(const std::string& event, uint32_t seconds) +{ + auto lockout = DzLockout::Create(m_name, event, seconds, m_uuid); + AddLockout(lockout); +} + +void DynamicZoneBase::AddLockout(const DzLockout& lockout, bool members_only) +{ + if (!members_only) + { + DynamicZoneLockoutsRepository::InsertLockouts(GetDatabase(), GetID(), { lockout }); + } + + CharacterExpeditionLockoutsRepository::InsertLockout(GetDatabase(), GetMemberIds(), lockout); + + HandleLockoutUpdate(lockout, false, members_only); + SendServerPacket(CreateLockoutPacket(lockout, false, members_only).get()); +} + +void DynamicZoneBase::AddLockoutDuration(const std::string& event, int seconds, bool members_only) +{ + auto lockout = DzLockout::Create(m_name, event, std::max(0, seconds), m_uuid); + + // lockout has unsigned duration, pass original seconds to support reducing existing timers + int secs = static_cast(seconds * RuleR(Expedition, LockoutDurationMultiplier)); + CharacterExpeditionLockoutsRepository::AddLockoutDuration(GetDatabase(), GetMemberIds(), lockout, secs); + + HandleLockoutDuration(lockout, seconds, members_only, true); + SendServerPacket(CreateLockoutDurationPacket(lockout, seconds, members_only).get()); +} + +void DynamicZoneBase::UpdateLockoutDuration(const std::string& event, uint32_t seconds, bool members_only) +{ + // some live expeditions update existing lockout timers during progression + auto it = std::ranges::find_if(m_lockouts, [&](const auto& l) { return l.IsEvent(event); }); + if (it != m_lockouts.end()) + { + seconds = static_cast(seconds * RuleR(Expedition, LockoutDurationMultiplier)); + DzLockout lockout(m_uuid, m_name, event, it->GetStartTime() + seconds, seconds); + AddLockout(lockout, members_only); + } +} + +void DynamicZoneBase::RemoveLockout(const std::string& event) +{ + DynamicZoneLockoutsRepository::DeleteWhere(GetDatabase(), fmt::format( + "dynamic_zone_id = {} AND event_name = '{}'", GetID(), Strings::Escape(event))); + + CharacterExpeditionLockoutsRepository::DeleteWhere(GetDatabase(), fmt::format( + "character_id IN ({}) AND expedition_name = '{}' AND event_name = '{}'", + fmt::join(GetMemberIds(), ","), Strings::Escape(m_name), Strings::Escape(event))); + + DzLockout lockout{m_uuid, m_name, event, 0, 0}; + HandleLockoutUpdate(lockout, true, false); + SendServerPacket(CreateLockoutPacket(lockout, true).get()); +} + +void DynamicZoneBase::HandleLockoutUpdate(const DzLockout& lockout, bool remove, bool members_only) +{ + if (!members_only) + { + std::erase_if(m_lockouts, [&](const auto& l) { return l.IsEvent(lockout.Event()); }); + if (!remove) + { + m_lockouts.push_back(lockout); + } + } +} + +void DynamicZoneBase::HandleLockoutDuration(const DzLockout& lockout, int seconds, bool members_only, bool insert_db) +{ + if (!members_only) + { + auto it = std::ranges::find_if(m_lockouts, [&](const auto& l) { return l.IsEvent(lockout.Event()); }); + if (it != m_lockouts.end()) + { + it->AddLockoutTime(seconds); + } + else + { + it = m_lockouts.insert(m_lockouts.end(), lockout); + } + + if (insert_db) + { + DynamicZoneLockoutsRepository::InsertLockouts(GetDatabase(), GetID(), { *it }); + } + } +} + +std::unique_ptr DynamicZoneBase::CreateLockoutPacket(const DzLockout& lockout, bool remove, bool members_only) const +{ + uint32_t pack_size = sizeof(ServerDzLockout_Struct); + auto pack = std::make_unique(ServerOP_DzLockout, pack_size); + auto buf = reinterpret_cast(pack->pBuffer); + buf->dz_id = GetID(); + buf->expire_time = lockout.GetExpireTime(); + buf->duration = lockout.GetDuration(); + buf->sender_zone_id = GetCurrentZoneID(); + buf->sender_instance_id = GetCurrentInstanceID(); + buf->remove = remove; + buf->members_only = members_only; + strn0cpy(buf->event_name, lockout.Event().c_str(), sizeof(buf->event_name)); + return pack; +} + +std::unique_ptr DynamicZoneBase::CreateLockoutDurationPacket(const DzLockout& lockout, int seconds, bool members_only) const +{ + uint32_t pack_size = sizeof(ServerDzLockout_Struct); + auto pack = std::make_unique(ServerOP_DzLockoutDuration, pack_size); + auto buf = reinterpret_cast(pack->pBuffer); + buf->dz_id = GetID(); + buf->expire_time = lockout.GetExpireTime(); + buf->duration = lockout.GetDuration(); + buf->sender_zone_id = GetCurrentZoneID(); + buf->sender_instance_id = GetCurrentInstanceID(); + buf->members_only = members_only; + buf->seconds = seconds; + strn0cpy(buf->event_name, lockout.Event().c_str(), sizeof(buf->event_name)); + return pack; +} + +void DynamicZoneBase::SyncCharacterLockouts(uint32_t char_id, std::vector& lockouts) +{ + // adds missing event lockouts to client for this expedition and updates + // client timers that are both shorter and from another expedition + bool modified = false; + + for (const auto& lockout : m_lockouts) + { + if (lockout.IsReplay() || lockout.IsExpired() || lockout.UUID() != m_uuid) + { + continue; + } + + auto it = std::find_if(lockouts.begin(), lockouts.end(), [&](const DzLockout& l) { return l.IsSame(lockout); }); + if (it == lockouts.end()) + { + modified = true; + lockouts.push_back(lockout); // insert missing + } + else if (it->GetSecondsRemaining() < lockout.GetSecondsRemaining() && it->UUID() != m_uuid) + { + // only update lockout timer not uuid so loot event apis still work + modified = true; + it->SetDuration(lockout.GetDuration()); + it->SetExpireTime(lockout.GetExpireTime()); + } + } + + if (modified) + { + CharacterExpeditionLockoutsRepository::InsertLockouts(GetDatabase(), char_id, lockouts); + } +} diff --git a/common/dynamic_zone_base.h b/common/dynamic_zone_base.h index b92f18ec2..6a43449f4 100644 --- a/common/dynamic_zone_base.h +++ b/common/dynamic_zone_base.h @@ -1,8 +1,8 @@ #ifndef COMMON_DYNAMIC_ZONE_BASE_H #define COMMON_DYNAMIC_ZONE_BASE_H +#include "dynamic_zone_lockout.h" #include "eq_constants.h" -#include "net/packet.h" #include "repositories/dynamic_zones_repository.h" #include "repositories/dynamic_zone_members_repository.h" #include "repositories/dynamic_zone_templates_repository.h" @@ -10,12 +10,40 @@ #include #include #include +#include #include #include class Database; class ServerPacket; +// message string 8312 added in September 08 2020 Test patch (used by both dz and shared tasks) +inline constexpr char DzNotAllAdded[] = "Not all players in your {0} were added to the {1}. The {1} can take a maximum of {2} players, and your {0} has {3}."; + +enum class DzLockMsg : uint8_t +{ + None = 0, Close, Begin +}; + +enum class DynamicZoneType +{ + None = 0, + Expedition, + Tutorial, + Task, + Mission, // Shared Task + Quest +}; + +enum class DynamicZoneMemberStatus +{ + Unknown = 0, + Online, + Offline, + InDynamicZone, + LinkDead +}; + struct DynamicZoneMember { uint32_t id = 0; @@ -93,6 +121,7 @@ public: const std::string& GetName() const { return m_name; } const std::string& GetUUID() const { return m_uuid; } const DynamicZoneMember& GetLeader() const { return m_leader; } + const std::vector& GetLockouts() const { return m_lockouts; } const std::vector& GetMembers() const { return m_members; } const DynamicZoneLocation& GetCompassLocation() const { return m_compass; } const DynamicZoneLocation& GetSafeReturnLocation() const { return m_safereturn; } @@ -104,31 +133,34 @@ public: uint32_t GetDatabaseMemberCount(); DynamicZoneMember GetMemberData(uint32_t character_id); DynamicZoneMember GetMemberData(const std::string& character_name); - EQ::Net::DynamicPacket GetSerializedDzPacket(); + std::vector GetMemberIds(); + std::ostringstream GetSerialized(); bool HasDatabaseMember(uint32_t character_id); - bool HasMember(uint32_t character_id); - bool HasMember(const std::string& character_name); + bool HasMember(uint32_t character_id) const; + bool HasMember(const std::string& character_name) const; bool HasMembers() const { return !m_members.empty(); } bool HasZoneInLocation() const { return m_has_zonein; } + bool IsExpedition() const { return m_type == DynamicZoneType::Expedition; } bool IsExpired() const { return m_expire_time < std::chrono::system_clock::now(); } bool IsInstanceID(uint32_t instance_id) const { return (m_instance_id != 0 && m_instance_id == instance_id); } + bool IsLocked() const { return m_is_locked; } bool IsValid() const { return m_instance_id != 0; } bool IsSameDz(uint32_t zone_id, uint32_t instance_id) const { return zone_id == m_zone_id && instance_id == m_instance_id; } - void LoadSerializedDzPacket(char* cereal_data, uint32_t cereal_size); void LoadTemplate(const DynamicZoneTemplatesRepository::DynamicZoneTemplates& dz_template); void RemoveAllMembers(); bool RemoveMember(uint32_t character_id); bool RemoveMember(const std::string& character_name); bool RemoveMember(const DynamicZoneMember& remove_member); - void SaveMembers(const std::vector& members); void SetCompass(const DynamicZoneLocation& location, bool update_db = false); void SetCompass(uint32_t zone_id, float x, float y, float z, bool update_db = false); void SetDuration(uint32_t seconds) { m_duration = std::chrono::seconds(seconds); } void SetLeader(const DynamicZoneMember& leader, bool update_db = false); + void SetLocked(bool lock, bool update_db = false, DzLockMsg lock_msg = DzLockMsg::None, uint32_t color = Chat::Yellow); void SetMaxPlayers(uint32_t max_players) { m_max_players = max_players; } void SetMemberStatus(uint32_t character_id, DynamicZoneMemberStatus status); void SetMinPlayers(uint32_t min_players) { m_min_players = min_players; } void SetName(const std::string& name) { m_name = name; } + void SetReplayOnJoin(bool enabled, bool update_db = false); void SetSafeReturn(const DynamicZoneLocation& location, bool update_db = false); void SetSafeReturn(uint32_t zone_id, float x, float y, float z, float heading, bool update_db = false); void SetSwitchID(int dz_switch_id, bool update_db = false); @@ -136,34 +168,48 @@ public: void SetUUID(std::string uuid) { m_uuid = std::move(uuid); } void SetZoneInLocation(const DynamicZoneLocation& location, bool update_db = false); void SetZoneInLocation(float x, float y, float z, float heading, bool update_db = false); - bool SwapMember(const DynamicZoneMember& add_member, const std::string& remove_char_name); + bool SwapMember(const DynamicZoneMember& add_member, const std::string& remove_name); + + void AddLockout(const std::string& event, uint32_t seconds); + void AddLockoutDuration(const std::string& event, int seconds, bool members_only = true); + bool HasLockout(const std::string& event); + bool HasReplayLockout(); + void RemoveLockout(const std::string& event); + void SyncCharacterLockouts(uint32_t char_id, std::vector& lockouts); + void UpdateLockoutDuration(const std::string& event, uint32_t seconds, bool members_only = true); protected: - virtual uint16_t GetCurrentInstanceID() { return 0; } - virtual uint16_t GetCurrentZoneID() { return 0; } + virtual uint16_t GetCurrentInstanceID() const { return 0; } + virtual uint16_t GetCurrentZoneID() const { return 0; } virtual Database& GetDatabase() = 0; + virtual void HandleLockoutDuration(const DzLockout& lockout, int seconds, bool members_only, bool insert_db); + virtual void HandleLockoutUpdate(const DzLockout& lockout, bool remove, bool members_only); virtual void ProcessCompassChange(const DynamicZoneLocation& location) { m_compass = location; } virtual void ProcessMemberAddRemove(const DynamicZoneMember& member, bool removed); - virtual bool ProcessMemberStatusChange(uint32_t member_id, DynamicZoneMemberStatus status); - virtual void ProcessRemoveAllMembers(bool silent = false) { m_members.clear(); } + virtual bool ProcessMemberStatusChange(uint32_t character_id, DynamicZoneMemberStatus status); + virtual void ProcessRemoveAllMembers() { m_members.clear(); } virtual void ProcessSetSwitchID(int dz_switch_id) { m_dz_switch_id = dz_switch_id; } virtual bool SendServerPacket(ServerPacket* packet) = 0; + void AddLockout(const DzLockout& lockout, bool members_only = false); void AddInternalMember(const DynamicZoneMember& member); uint32_t Create(); uint32_t CreateInstance(); void LoadRepositoryResult(DynamicZonesRepository::DynamicZoneInstance&& dz_entry); void RemoveInternalMember(uint32_t character_id); + void SaveMembers(const std::vector& members); uint32_t SaveToDatabase(); bool SetInternalMemberStatus(uint32_t character_id, DynamicZoneMemberStatus status); - std::unique_ptr CreateServerDzCreatePacket(uint16_t origin_zone_id, uint16_t origin_instance_id); + std::unique_ptr CreateServerPacket(uint16_t zone_id, uint16_t instance_id); std::unique_ptr CreateServerDzLocationPacket(uint16_t server_opcode, const DynamicZoneLocation& location); std::unique_ptr CreateServerDzSwitchIDPacket(); std::unique_ptr CreateServerMemberAddRemovePacket(const DynamicZoneMember& member, bool removed); std::unique_ptr CreateServerMemberStatusPacket(uint32_t character_id, DynamicZoneMemberStatus status); std::unique_ptr CreateServerMemberSwapPacket(const DynamicZoneMember& remove_member, const DynamicZoneMember& add_member); std::unique_ptr CreateServerRemoveAllMembersPacket(); + std::unique_ptr CreateLockoutPacket(const DzLockout& lockout, bool remove, bool members_only = false) const; + std::unique_ptr CreateLockoutDurationPacket(const DzLockout& lockout, int seconds, bool members_only = false) const; uint32_t m_id = 0; uint32_t m_zone_id = 0; @@ -175,6 +221,8 @@ protected: bool m_never_expires = false; bool m_has_zonein = false; bool m_has_member_statuses = false; + bool m_is_locked = false; + bool m_add_replay = true; std::string m_name; std::string m_uuid; DynamicZoneMember m_leader; @@ -182,12 +230,15 @@ protected: DynamicZoneLocation m_compass; DynamicZoneLocation m_safereturn; DynamicZoneLocation m_zonein; - std::chrono::seconds m_duration; + std::chrono::seconds m_duration = {}; std::chrono::time_point m_start_time; std::chrono::time_point m_expire_time; std::vector m_members; + std::vector m_lockouts; public: + void Unserialize(std::span buf); + template void serialize(Archive& archive) { @@ -202,6 +253,8 @@ public: m_never_expires, m_has_zonein, m_has_member_statuses, + m_is_locked, + m_add_replay, m_name, m_uuid, m_leader, @@ -212,7 +265,8 @@ public: m_duration, m_start_time, m_expire_time, - m_members + m_members, + m_lockouts ); } }; diff --git a/common/dynamic_zone_lockout.cpp b/common/dynamic_zone_lockout.cpp new file mode 100644 index 000000000..a47a9e205 --- /dev/null +++ b/common/dynamic_zone_lockout.cpp @@ -0,0 +1,92 @@ +#include "dynamic_zone_lockout.h" +#include "strings.h" +#include "rulesys.h" +#include "util/uuid.h" +#include +#include + +DzLockout::DzLockout(std::string uuid, std::string expedition, std::string event, uint64_t expire_time, uint32_t duration) + : m_uuid(std::move(uuid)) + , m_name(std::move(expedition)) + , m_event(std::move(event)) + , m_expire_time(std::chrono::system_clock::from_time_t(expire_time)) + , m_duration(duration) +{ + m_is_replay = m_event == ReplayTimer; +} + +DzLockout::DzLockout(std::string_view name, BaseDynamicZoneLockoutsRepository::DynamicZoneLockouts&& lockout) + : m_uuid(std::move(lockout.from_expedition_uuid)) + , m_name(name) + , m_event(std::move(lockout.event_name)) + , m_expire_time(std::chrono::system_clock::from_time_t(lockout.expire_time)) + , m_duration(lockout.duration) +{ + m_is_replay = m_event == ReplayTimer; +} + +DzLockout DzLockout::Create(const std::string& expedition, const std::string& event, uint32_t seconds, std::string uuid) +{ + seconds = static_cast(seconds * RuleR(Expedition, LockoutDurationMultiplier)); + + if (uuid.empty()) + { + uuid = EQ::Util::UUID::Generate().ToString(); + } + + DzLockout lockout{uuid, expedition, event, 0, seconds}; + lockout.Reset(); // sets expire time + return lockout; +} + +uint32_t DzLockout::GetSecondsRemaining() const +{ + auto now = std::chrono::system_clock::now(); + if (m_expire_time > now) + { + auto remaining = m_expire_time - now; + return static_cast(std::chrono::duration_cast(remaining).count()); + } + return 0; +} + +DzLockout::TimeStrings DzLockout::GetTimeRemainingStrs() const +{ + auto seconds = GetSecondsRemaining(); + return DzLockout::TimeStrings{ + fmt::format_int(seconds / 86400).str(), // days + fmt::format_int(seconds / 3600 % 24).str(), // hours + fmt::format_int(seconds / 60 % 60).str(), // minutes + fmt::format_int(seconds % 60).str() // seconds + }; +} + +bool DzLockout::IsSame(const DzLockout& other) const +{ + return other.IsSame(m_name, m_event); +} + +bool DzLockout::IsSame(const std::string& expedition, const std::string& event) const +{ + return m_name == expedition && m_event == event; +} + +void DzLockout::AddLockoutTime(int seconds) +{ + seconds = static_cast(seconds * RuleR(Expedition, LockoutDurationMultiplier)); + + auto new_duration = std::max(0, static_cast(m_duration.count()) + seconds); + + auto start_time = m_expire_time - m_duration; + m_duration = std::chrono::seconds(new_duration); + m_expire_time = start_time + m_duration; +} + +template +void DzLockout::serialize(T& archive) +{ + archive(m_is_replay, m_uuid, m_name, m_event, m_duration, m_expire_time); +} + +template void DzLockout::serialize(cereal::BinaryOutputArchive&); +template void DzLockout::serialize(cereal::BinaryInputArchive&); diff --git a/common/dynamic_zone_lockout.h b/common/dynamic_zone_lockout.h new file mode 100644 index 000000000..72dc132bd --- /dev/null +++ b/common/dynamic_zone_lockout.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include +#include "repositories/base/base_dynamic_zone_lockouts_repository.h" + +class DzLockout +{ +public: + DzLockout() = default; + DzLockout(std::string uuid, std::string expedition, std::string event, uint64_t expire_time, uint32_t duration); + DzLockout(std::string_view name, BaseDynamicZoneLockoutsRepository::DynamicZoneLockouts&& lockout); + + static constexpr char ReplayTimer[] = "Replay Timer"; + + static DzLockout Create(const std::string& expedition, const std::string& event, uint32_t seconds, std::string uuid = {}); + + struct TimeStrings + { + std::string days; + std::string hours; + std::string mins; + std::string secs; + }; + + void AddLockoutTime(int seconds); + uint32_t GetDuration() const { return static_cast(m_duration.count()); } + uint64_t GetExpireTime() const { return std::chrono::system_clock::to_time_t(m_expire_time); } + uint64_t GetStartTime() const { return std::chrono::system_clock::to_time_t(m_expire_time - m_duration); } + uint32_t GetSecondsRemaining() const; + TimeStrings GetTimeRemainingStrs() const; + const std::string& DzName() const { return m_name; } + const std::string& Event() const { return m_event; } + const std::string& UUID() const { return m_uuid; } + bool IsEvent(std::string_view event) const { return m_event == event; } + bool IsExpired() const { return GetSecondsRemaining() == 0; } + bool IsReplay() const { return m_is_replay; } + bool IsSame(const DzLockout& other) const; + bool IsSame(const std::string& expedition, const std::string& event) const; + bool IsUUID(const std::string& uuid) const { return uuid == m_uuid; } + void Reset() { m_expire_time = std::chrono::system_clock::now() + m_duration; } + void SetDuration(uint32_t seconds) { m_duration = std::chrono::seconds(seconds); } + void SetExpireTime(uint64_t expire_time) { m_expire_time = std::chrono::system_clock::from_time_t(expire_time); } + void SetUUID(const std::string& uuid) { m_uuid = uuid; } + + template + void serialize(T& archive); + +private: + bool m_is_replay = false; + std::string m_uuid; // dz received in + std::string m_name; + std::string m_event; + std::chrono::seconds m_duration = {}; + std::chrono::time_point m_expire_time; +}; diff --git a/common/emu_constants.cpp b/common/emu_constants.cpp index fd4983216..ecb1e4c58 100644 --- a/common/emu_constants.cpp +++ b/common/emu_constants.cpp @@ -140,29 +140,6 @@ std::string EQ::constants::GetLanguageName(uint8 language_id) return EQ::constants::GetLanguageMap().find(language_id)->second; } -const std::map& EQ::constants::GetLDoNThemeMap() -{ - static const std::map ldon_theme_map = { - { LDoNThemes::Unused, "Unused" }, - { LDoNThemes::GUK, "Deepest Guk" }, - { LDoNThemes::MIR, "Miragul's Menagerie" }, - { LDoNThemes::MMC, "Mistmoore Catacombs" }, - { LDoNThemes::RUJ, "Rujarkian Hills" }, - { LDoNThemes::TAK, "Takish-Hiz" }, - }; - - return ldon_theme_map; -} - -std::string EQ::constants::GetLDoNThemeName(uint32 theme_id) -{ - if (!EQ::ValueWithin(theme_id, LDoNThemes::Unused, LDoNThemes::TAK)) { - return std::string(); - } - - return EQ::constants::GetLDoNThemeMap().find(theme_id)->second; -} - const std::map& EQ::constants::GetFlyModeMap() { static const std::map flymode_map = { @@ -459,3 +436,38 @@ bool ComparisonType::IsValid(uint8 type) { return comparison_types.find(type) != comparison_types.end(); } + +uint32 LDoNTheme::GetBitmask(uint32 theme_id) +{ + return IsValid(theme_id) ? ldon_theme_names[theme_id].second : LDoNTheme::UnusedBit; +} + +std::string LDoNTheme::GetName(uint32 theme_id) +{ + return IsValid(theme_id) ? ldon_theme_names[theme_id].first : "UNKNOWN LDON THEME"; +} + +bool LDoNTheme::IsValid(uint32 theme_id) +{ + return ldon_theme_names.find(theme_id) != ldon_theme_names.end(); +} + +std::string PetCommand::GetName(uint8 pet_command) +{ + return IsValid(pet_command) ? pet_commands[pet_command] : "UNKNOWN PET COMMAND"; +} + +bool PetCommand::IsValid(uint8 pet_command) +{ + return pet_commands.find(pet_command) != pet_commands.end(); +} + +std::string PetType::GetName(uint8 pet_type) +{ + return IsValid(pet_type) ? pet_types[pet_type] : "UNKNOWN PET TYPE"; +} + +bool PetType::IsValid(uint8 pet_type) +{ + return pet_types.find(pet_type) != pet_types.end(); +} diff --git a/common/emu_constants.h b/common/emu_constants.h index 83056cfb2..0c762ffb8 100644 --- a/common/emu_constants.h +++ b/common/emu_constants.h @@ -130,9 +130,11 @@ namespace EQ using RoF2::invtype::MAIL_SIZE; using RoF2::invtype::GUILD_TROPHY_TRIBUTE_SIZE; using RoF2::invtype::KRONO_SIZE; + using RoF2::invtype::GUILD_BANK_MAIN_SIZE; + using RoF2::invtype::GUILD_BANK_DEPOSIT_SIZE; using RoF2::invtype::OTHER_SIZE; - using Titanium::invtype::TRADE_NPC_SIZE; + using RoF2::invtype::TRADE_NPC_SIZE; using RoF2::invtype::TYPE_INVALID; using RoF2::invtype::TYPE_BEGIN; @@ -159,7 +161,7 @@ namespace EQ using RoF2::invslot::SLOT_INVALID; using RoF2::invslot::SLOT_BEGIN; - using Titanium::invslot::SLOT_TRADESKILL_EXPERIMENT_COMBINE; + using RoF2::invslot::SLOT_TRADESKILL_EXPERIMENT_COMBINE; const int16 SLOT_AUGMENT_GENERIC_RETURN = 1001; // clients don't appear to use this method... (internal inventory return value) @@ -179,28 +181,28 @@ namespace EQ using RoF2::invslot::BONUS_STAT_END; using RoF2::invslot::BONUS_SKILL_END; - using Titanium::invslot::BANK_BEGIN; - using SoF::invslot::BANK_END; + using RoF2::invslot::BANK_BEGIN; + using RoF2::invslot::BANK_END; - using Titanium::invslot::SHARED_BANK_BEGIN; - using Titanium::invslot::SHARED_BANK_END; + using RoF2::invslot::SHARED_BANK_BEGIN; + using RoF2::invslot::SHARED_BANK_END; - using Titanium::invslot::TRADE_BEGIN; - using Titanium::invslot::TRADE_END; + using RoF2::invslot::TRADE_BEGIN; + using RoF2::invslot::TRADE_END; - using Titanium::invslot::TRADE_NPC_END; + using RoF2::invslot::TRADE_NPC_END; - using Titanium::invslot::WORLD_BEGIN; - using Titanium::invslot::WORLD_END; + using RoF2::invslot::WORLD_BEGIN; + using RoF2::invslot::WORLD_END; - using Titanium::invslot::TRIBUTE_BEGIN; - using Titanium::invslot::TRIBUTE_END; + using RoF2::invslot::TRIBUTE_BEGIN; + using RoF2::invslot::TRIBUTE_END; - using Titanium::invslot::GUILD_TRIBUTE_BEGIN; - using Titanium::invslot::GUILD_TRIBUTE_END; + using RoF2::invslot::GUILD_TRIBUTE_BEGIN; + using RoF2::invslot::GUILD_TRIBUTE_END; const int16 CORPSE_BEGIN = invslot::slotGeneral1; - const int16 CORPSE_END = CORPSE_BEGIN + invslot::slotCursor; + const int16 CORPSE_END = CORPSE_BEGIN + invslot::slotCursor; using RoF2::invslot::EQUIPMENT_BITMASK; using RoF2::invslot::GENERAL_BITMASK; @@ -214,38 +216,40 @@ namespace EQ } // namespace invslot namespace invbag { - using Titanium::invbag::SLOT_INVALID; - using Titanium::invbag::SLOT_BEGIN; - using Titanium::invbag::SLOT_END; - using Titanium::invbag::SLOT_COUNT; + using RoF2::invbag::SLOT_INVALID; + using RoF2::invbag::SLOT_BEGIN; + using RoF2::invbag::SLOT_END; + using RoF2::invbag::SLOT_COUNT; - using Titanium::invbag::GENERAL_BAGS_BEGIN; + using RoF2::invslot::WORLD_END; + + const int16 GENERAL_BAGS_BEGIN = WORLD_END + 1; const int16 GENERAL_BAGS_COUNT = invslot::GENERAL_COUNT * SLOT_COUNT; - const int16 GENERAL_BAGS_END = (GENERAL_BAGS_BEGIN + GENERAL_BAGS_COUNT) - 1; + const int16 GENERAL_BAGS_END = (GENERAL_BAGS_BEGIN + GENERAL_BAGS_COUNT) - 1; const int16 GENERAL_BAGS_8_COUNT = 8 * SLOT_COUNT; - const int16 GENERAL_BAGS_8_END = (GENERAL_BAGS_BEGIN + GENERAL_BAGS_8_COUNT) - 1; + const int16 GENERAL_BAGS_8_END = (GENERAL_BAGS_BEGIN + GENERAL_BAGS_8_COUNT) - 1; - const int16 CURSOR_BAG_BEGIN = 351; + const int16 CURSOR_BAG_BEGIN = GENERAL_BAGS_END + 1; const int16 CURSOR_BAG_COUNT = SLOT_COUNT; - const int16 CURSOR_BAG_END = (CURSOR_BAG_BEGIN + CURSOR_BAG_COUNT) - 1; + const int16 CURSOR_BAG_END = (CURSOR_BAG_BEGIN + CURSOR_BAG_COUNT) - 1; - using Titanium::invbag::BANK_BAGS_BEGIN; + const int16 BANK_BAGS_BEGIN = CURSOR_BAG_END + 1; const int16 BANK_BAGS_COUNT = (invtype::BANK_SIZE * SLOT_COUNT); - const int16 BANK_BAGS_END = (BANK_BAGS_BEGIN + BANK_BAGS_COUNT) - 1; + const int16 BANK_BAGS_END = (BANK_BAGS_BEGIN + BANK_BAGS_COUNT) - 1; const int16 BANK_BAGS_16_COUNT = 16 * SLOT_COUNT; - const int16 BANK_BAGS_16_END = (BANK_BAGS_BEGIN + BANK_BAGS_16_COUNT) - 1; + const int16 BANK_BAGS_16_END = (BANK_BAGS_BEGIN + BANK_BAGS_16_COUNT) - 1; - using Titanium::invbag::SHARED_BANK_BAGS_BEGIN; + const int16 SHARED_BANK_BAGS_BEGIN = BANK_BAGS_END + 1; const int16 SHARED_BANK_BAGS_COUNT = invtype::SHARED_BANK_SIZE * SLOT_COUNT; - const int16 SHARED_BANK_BAGS_END = (SHARED_BANK_BAGS_BEGIN + SHARED_BANK_BAGS_COUNT) - 1; + const int16 SHARED_BANK_BAGS_END = (SHARED_BANK_BAGS_BEGIN + SHARED_BANK_BAGS_COUNT) - 1; - using Titanium::invbag::TRADE_BAGS_BEGIN; + const int16 TRADE_BAGS_BEGIN = SHARED_BANK_BAGS_END + 1; const int16 TRADE_BAGS_COUNT = invtype::TRADE_SIZE * SLOT_COUNT; - const int16 TRADE_BAGS_END = (TRADE_BAGS_BEGIN + TRADE_BAGS_COUNT) - 1; + const int16 TRADE_BAGS_END = (TRADE_BAGS_BEGIN + TRADE_BAGS_COUNT) - 1; - using Titanium::invbag::GetInvBagIndexName; + using RoF2::invbag::GetInvBagIndexName; } // namespace invbag @@ -352,9 +356,6 @@ namespace EQ extern const std::map& GetLanguageMap(); std::string GetLanguageName(uint8 language_id); - extern const std::map& GetLDoNThemeMap(); - std::string GetLDoNThemeName(uint32 theme_id); - extern const std::map& GetFlyModeMap(); std::string GetFlyModeName(int8 flymode_id); @@ -751,6 +752,35 @@ static std::map stance_names = { { Stance::AEBurn, "AE Burn" } }; +namespace LDoNTheme { + constexpr uint32 Unused = 0; + constexpr uint32 GUK = 1; + constexpr uint32 MIR = 2; + constexpr uint32 MMC = 3; + constexpr uint32 RUJ = 4; + constexpr uint32 TAK = 5; + + constexpr uint32 UnusedBit = 0; + constexpr uint32 GUKBit = 1; + constexpr uint32 MIRBit = 2; + constexpr uint32 MMCBit = 4; + constexpr uint32 RUJBit = 8; + constexpr uint32 TAKBit = 16; + + uint32 GetBitmask(uint32 theme_id); + std::string GetName(uint32 theme_id); + bool IsValid(uint32 theme_id); +} + +static std::map> ldon_theme_names = { + { LDoNTheme::Unused, { "Unused", LDoNTheme::UnusedBit }, }, + { LDoNTheme::GUK, { "Deepest Guk", LDoNTheme::GUKBit }, }, + { LDoNTheme::MIR, { "Miragul's Menagerie", LDoNTheme::MIRBit }, }, + { LDoNTheme::MMC, { "Mistmoore Catacombs", LDoNTheme::MMCBit }, }, + { LDoNTheme::RUJ, { "Rujarkian Hills", LDoNTheme::RUJBit }, }, + { LDoNTheme::TAK, { "Takish-Hiz", LDoNTheme::TAKBit }, }, +}; + namespace PCNPCOnlyFlagType { constexpr int PC = 1; constexpr int NPC = 2; @@ -762,4 +792,131 @@ namespace BookType { constexpr uint8 ItemInfo = 2; } +namespace PetButton { + constexpr uint8 Sit = 0; + constexpr uint8 Stop = 1; + constexpr uint8 Regroup = 2; + constexpr uint8 Follow = 3; + constexpr uint8 Guard = 4; + constexpr uint8 Taunt = 5; + constexpr uint8 Hold = 6; + constexpr uint8 GreaterHold = 7; + constexpr uint8 Focus = 8; + constexpr uint8 SpellHold = 9; +} + +namespace PetButtonState { + constexpr uint8 Off = 0; + constexpr uint8 On = 1; +} + +namespace PetCommand { + constexpr uint8 HealthReport = 0; // /pet health or Pet Window + constexpr uint8 Leader = 1; // /pet leader or Pet Window + constexpr uint8 Attack = 2; // /pet attack or Pet Window + constexpr uint8 QAttack = 3; // /pet qattack or Pet Window + constexpr uint8 FollowMe = 4; // /pet follow or Pet Window + constexpr uint8 GuardHere = 5; // /pet guard or Pet Window + constexpr uint8 Sit = 6; // /pet sit or Pet Window + constexpr uint8 SitDown = 7; // /pet sit on + constexpr uint8 StandUp = 8; // /pet sit off + constexpr uint8 Stop = 9; // /pet stop or Pet Window - Not implemented + constexpr uint8 StopOn = 10; // /pet stop on - Not implemented + constexpr uint8 StopOff = 11; // /pet stop off - Not implemented + constexpr uint8 Taunt = 12; // /pet taunt or Pet Window + constexpr uint8 TauntOn = 13; // /pet taunt on + constexpr uint8 TauntOff = 14; // /pet taunt off + constexpr uint8 Hold = 15; // /pet hold or Pet Window, won't add to hate list unless attacking + constexpr uint8 HoldOn = 16; // /pet hold on + constexpr uint8 HoldOff = 17; // /pet hold off + constexpr uint8 GreaterHold = 18; // /pet ghold, will never add to hate list unless told to + constexpr uint8 GreaterHoldOn = 19; // /pet ghold on + constexpr uint8 GreaterHoldOff = 20; // /pet ghold off + constexpr uint8 SpellHold = 21; // /pet no cast or /pet spellhold or Pet Window + constexpr uint8 SpellHoldOn = 22; // /pet spellhold on + constexpr uint8 SpellHoldOff = 23; // /pet spellhold off + constexpr uint8 Focus = 24; // /pet focus or Pet Window + constexpr uint8 FocusOn = 25; // /pet focus on + constexpr uint8 FocusOff = 26; // /pet focus off + constexpr uint8 Feign = 27; // /pet feign + constexpr uint8 BackOff = 28; // /pet back off + constexpr uint8 GetLost = 29; // /pet get lost + constexpr uint8 GuardMe = 30; // Same as /pet follow, but different message in older clients + constexpr uint8 Regroup = 31; // /pet regroup, acts like classic hold + constexpr uint8 RegroupOn = 32; // /pet regroup on + constexpr uint8 RegroupOff = 33; // /pet regroup off + constexpr uint8 Max = 34; + + static std::map pet_commands = { + { PetCommand::HealthReport, "Health Report" }, + { PetCommand::Leader, "Leader" }, + { PetCommand::Attack, "Attack" }, + { PetCommand::QAttack, "QAttack" }, + { PetCommand::FollowMe, "Follow Me" }, + { PetCommand::GuardHere, "Guard Here" }, + { PetCommand::Sit, "Sit" }, + { PetCommand::SitDown, "Sit Down" }, + { PetCommand::StandUp, "Stand Up" }, + { PetCommand::Stop, "Stop" }, + { PetCommand::StopOn, "Stop On" }, + { PetCommand::StopOff, "Stop Off" }, + { PetCommand::Taunt, "Taunt" }, + { PetCommand::TauntOn, "Taunt On" }, + { PetCommand::TauntOff, "Taunt Off" }, + { PetCommand::Hold, "Hold" }, + { PetCommand::HoldOn, "Hold On" }, + { PetCommand::HoldOff, "Hold Off" }, + { PetCommand::GreaterHold, "Greater Hold" }, + { PetCommand::GreaterHoldOn, "Greater Hold On" }, + { PetCommand::GreaterHoldOff, "Greater Hold Off" }, + { PetCommand::SpellHold, "Spell Hold" }, + { PetCommand::SpellHoldOn, "Spell Hold On" }, + { PetCommand::SpellHoldOff, "Spell Hold Off" }, + { PetCommand::Focus, "Focus" }, + { PetCommand::FocusOn, "Focus On" }, + { PetCommand::FocusOff, "Focus Off" }, + { PetCommand::Feign, "Feign" }, + { PetCommand::BackOff, "Back Off" }, + { PetCommand::GetLost, "Get Lost" }, + { PetCommand::GuardMe, "Guard Me" }, + { PetCommand::Regroup, "Regroup" }, + { PetCommand::RegroupOn, "Regroup On" }, + { PetCommand::RegroupOff, "Regroup Off" }, + { PetCommand::Max, "Max" } + }; + + std::string GetName(uint8 pet_command); + bool IsValid(uint8 pet_command); +} + +namespace PetOrder { + constexpr uint8 Follow = 0; + constexpr uint8 Sit = 1; + constexpr uint8 Guard = 2; + constexpr uint8 Feign = 3; +} + +namespace PetType { + constexpr uint8 Familiar = 0; + constexpr uint8 Animation = 1; + constexpr uint8 Normal = 2; + constexpr uint8 Charmed = 3; + constexpr uint8 Follow = 4; + constexpr uint8 TargetLock = 5; + constexpr uint8 None = 255; + + static std::map pet_types = { + { PetType::Familiar, "Familiar" }, + { PetType::Animation, "Animation" }, + { PetType::Normal, "Normal" }, + { PetType::Charmed, "Charmed" }, + { PetType::Follow, "Follow" }, + { PetType::TargetLock, "Target Lock" }, + { PetType::None, "None" } + }; + + std::string GetName(uint8 pet_type); + bool IsValid(uint8 pet_type); +} + #endif /*COMMON_EMU_CONSTANTS_H*/ diff --git a/common/emu_oplist.h b/common/emu_oplist.h index aa8190720..268fa97cd 100644 --- a/common/emu_oplist.h +++ b/common/emu_oplist.h @@ -35,7 +35,7 @@ N(OP_AltCurrencyMerchantRequest), N(OP_AltCurrencyPurchase), N(OP_AltCurrencyReclaim), N(OP_AltCurrencySell), -N(OP_AltCurrencySellSelection), // Used by eqstr_us.txt 8066, 8068, 8069 +N(OP_AltCurrencySellSelection), // Used by eqstr_us.txt 8066, 8068, 8069 N(OP_Animation), N(OP_AnnoyingZoneUnknown), N(OP_ApplyPoison), @@ -77,6 +77,7 @@ N(OP_CashReward), N(OP_CastSpell), N(OP_ChangeSize), N(OP_ChannelMessage), +N(OP_ChangePetName), N(OP_CharacterCreate), N(OP_CharacterCreateRequest), N(OP_CharInventory), @@ -162,6 +163,7 @@ N(OP_EnduranceUpdate), N(OP_EnterChat), N(OP_EnterWorld), N(OP_EnvDamage), +N(OP_EvolveItem), N(OP_ExpansionInfo), N(OP_ExpUpdate), N(OP_FaceChange), @@ -283,12 +285,17 @@ N(OP_InspectMessageUpdate), N(OP_InspectRequest), 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), N(OP_ItemName), N(OP_ItemPacket), N(OP_ItemPreview), +N(OP_ItemPreviewRequest), N(OP_ItemRecastDelay), N(OP_ItemVerifyReply), N(OP_ItemVerifyRequest), @@ -400,6 +407,8 @@ N(OP_PetitionSearchText), N(OP_PetitionUnCheckout), N(OP_PetitionUpdate), N(OP_PickPocket), +N(OP_PickZone), +N(OP_PickZoneWindow), N(OP_PlayerProfile), N(OP_PlayerStateAdd), N(OP_PlayerStateRemove), @@ -514,7 +523,7 @@ N(OP_ShopPlayerSell), N(OP_ShopSendParcel), N(OP_ShopDeleteParcel), N(OP_ShopRespondParcel), -N(OP_ShopRetrieveParcel), +N(OP_ShopRetrieveParcel), N(OP_ShopParcelIcon), N(OP_ShopRequest), N(OP_SimpleMessage), @@ -567,6 +576,7 @@ N(OP_TradeRequestAck), N(OP_TraderItemUpdate), N(OP_TraderShop), N(OP_TradeSkillCombine), +N(OP_TradeSkillRecipeInspect), N(OP_Translocate), N(OP_TributeInfo), N(OP_TributeItem), diff --git a/common/eq_constants.h b/common/eq_constants.h index 44beffd25..b577b9fb3 100644 --- a/common/eq_constants.h +++ b/common/eq_constants.h @@ -974,43 +974,6 @@ namespace ZoneBlockedSpellTypes { const uint8 Region = 2; }; -enum class DynamicZoneType -{ - None = 0, - Expedition, - Tutorial, - Task, - Mission, // Shared Task - Quest -}; - -enum class DynamicZoneMemberStatus : uint8_t -{ - Unknown = 0, - Online, - Offline, - InDynamicZone, - LinkDead -}; - -enum LDoNThemes { - Unused = 0, - GUK, - MIR, - MMC, - RUJ, - TAK -}; - -enum LDoNThemeBits { - UnusedBit = 0, - GUKBit = 1, - MIRBit = 2, - MMCBit = 4, - RUJBit = 8, - TAKBit = 16 -}; - enum StartZoneIndex { Odus = 0, Qeynos, @@ -1025,7 +988,8 @@ enum StartZoneIndex { Felwithe, Akanon, Cabilis, - SharVahl + SharVahl, + RatheMtn }; enum FVNoDropFlagRule @@ -1132,4 +1096,5 @@ enum ExpSource namespace DoorType { constexpr uint32 BuyerStall = 155; } + #endif /*COMMON_EQ_CONSTANTS_H*/ diff --git a/common/eq_limits.cpp b/common/eq_limits.cpp index 051857681..1bfbb6d29 100644 --- a/common/eq_limits.cpp +++ b/common/eq_limits.cpp @@ -47,6 +47,7 @@ static const EQ::constants::LookupEntry constants_static_lookup_entries[EQ::vers ClientUnknown::constants::EXPANSION_BIT, ClientUnknown::constants::EXPANSIONS_MASK, ClientUnknown::INULL, + ClientUnknown::INULL, ClientUnknown::INULL ), /*[ClientVersion::Client62] =*/ @@ -55,6 +56,7 @@ static const EQ::constants::LookupEntry constants_static_lookup_entries[EQ::vers Client62::constants::EXPANSION_BIT, Client62::constants::EXPANSIONS_MASK, Client62::INULL, + Client62::INULL, Client62::INULL ), /*[ClientVersion::Titanium] =*/ @@ -63,7 +65,8 @@ static const EQ::constants::LookupEntry constants_static_lookup_entries[EQ::vers Titanium::constants::EXPANSION_BIT, Titanium::constants::EXPANSIONS_MASK, Titanium::constants::CHARACTER_CREATION_LIMIT, - Titanium::constants::SAY_LINK_BODY_SIZE + Titanium::constants::SAY_LINK_BODY_SIZE, + Titanium::INULL ), /*[ClientVersion::SoF] =*/ EQ::constants::LookupEntry( @@ -71,7 +74,8 @@ static const EQ::constants::LookupEntry constants_static_lookup_entries[EQ::vers SoF::constants::EXPANSION_BIT, SoF::constants::EXPANSIONS_MASK, SoF::constants::CHARACTER_CREATION_LIMIT, - SoF::constants::SAY_LINK_BODY_SIZE + SoF::constants::SAY_LINK_BODY_SIZE, + SoF::INULL ), /*[ClientVersion::SoD] =*/ EQ::constants::LookupEntry( @@ -79,7 +83,8 @@ static const EQ::constants::LookupEntry constants_static_lookup_entries[EQ::vers SoD::constants::EXPANSION_BIT, SoD::constants::EXPANSIONS_MASK, SoD::constants::CHARACTER_CREATION_LIMIT, - SoD::constants::SAY_LINK_BODY_SIZE + SoD::constants::SAY_LINK_BODY_SIZE, + SoD::INULL ), /*[ClientVersion::UF] =*/ EQ::constants::LookupEntry( @@ -87,7 +92,8 @@ static const EQ::constants::LookupEntry constants_static_lookup_entries[EQ::vers UF::constants::EXPANSION_BIT, UF::constants::EXPANSIONS_MASK, UF::constants::CHARACTER_CREATION_LIMIT, - UF::constants::SAY_LINK_BODY_SIZE + UF::constants::SAY_LINK_BODY_SIZE, + UF::INULL ), /*[ClientVersion::RoF] =*/ EQ::constants::LookupEntry( @@ -95,7 +101,8 @@ static const EQ::constants::LookupEntry constants_static_lookup_entries[EQ::vers RoF::constants::EXPANSION_BIT, RoF::constants::EXPANSIONS_MASK, RoF::constants::CHARACTER_CREATION_LIMIT, - RoF::constants::SAY_LINK_BODY_SIZE + RoF::constants::SAY_LINK_BODY_SIZE, + RoF::INULL ), /*[ClientVersion::RoF2] =*/ EQ::constants::LookupEntry( @@ -103,7 +110,8 @@ static const EQ::constants::LookupEntry constants_static_lookup_entries[EQ::vers RoF2::constants::EXPANSION_BIT, RoF2::constants::EXPANSIONS_MASK, RoF2::constants::CHARACTER_CREATION_LIMIT, - RoF2::constants::SAY_LINK_BODY_SIZE + RoF2::constants::SAY_LINK_BODY_SIZE, + RoF2::constants::MAX_BAZAAR_TRADERS ) }; @@ -165,7 +173,7 @@ static const EQ::inventory::LookupEntry inventory_static_lookup_entries[EQ::vers ClientUnknown::INULL, ClientUnknown::INULL, ClientUnknown::INULL, ClientUnknown::INULL, ClientUnknown::INULL, ClientUnknown::INULL, ClientUnknown::INULL, ClientUnknown::INULL, ClientUnknown::INULL, - ClientUnknown::INULL + ClientUnknown::INULL, ClientUnknown::INULL, ClientUnknown::INULL ), ClientUnknown::INULL, @@ -192,7 +200,7 @@ static const EQ::inventory::LookupEntry inventory_static_lookup_entries[EQ::vers Client62::INULL, Client62::INULL, Client62::INULL, Client62::INULL, Client62::INULL, Client62::INULL, Client62::INULL, Client62::INULL, Client62::INULL, - Client62::INULL + Client62::INULL, Client62::INULL, Client62::INULL ), Client62::INULL, @@ -219,7 +227,7 @@ static const EQ::inventory::LookupEntry inventory_static_lookup_entries[EQ::vers Titanium::invtype::VIEW_MOD_PC_SIZE, Titanium::invtype::VIEW_MOD_BANK_SIZE, Titanium::invtype::VIEW_MOD_SHARED_BANK_SIZE, Titanium::invtype::VIEW_MOD_LIMBO_SIZE, Titanium::invtype::ALT_STORAGE_SIZE, Titanium::invtype::ARCHIVED_SIZE, Titanium::INULL, Titanium::INULL, Titanium::INULL, - Titanium::invtype::OTHER_SIZE + Titanium::INULL, Titanium::INULL, Titanium::invtype::OTHER_SIZE ), Titanium::invslot::EQUIPMENT_BITMASK, @@ -246,7 +254,7 @@ static const EQ::inventory::LookupEntry inventory_static_lookup_entries[EQ::vers SoF::invtype::VIEW_MOD_PC_SIZE, SoF::invtype::VIEW_MOD_BANK_SIZE, SoF::invtype::VIEW_MOD_SHARED_BANK_SIZE, SoF::invtype::VIEW_MOD_LIMBO_SIZE, SoF::invtype::ALT_STORAGE_SIZE, SoF::invtype::ARCHIVED_SIZE, SoF::INULL, SoF::INULL, SoF::INULL, - SoF::invtype::OTHER_SIZE + SoF::INULL, SoF::INULL, SoF::invtype::OTHER_SIZE ), SoF::invslot::EQUIPMENT_BITMASK, @@ -273,7 +281,7 @@ static const EQ::inventory::LookupEntry inventory_static_lookup_entries[EQ::vers SoD::invtype::VIEW_MOD_PC_SIZE, SoD::invtype::VIEW_MOD_BANK_SIZE, SoD::invtype::VIEW_MOD_SHARED_BANK_SIZE, SoD::invtype::VIEW_MOD_LIMBO_SIZE, SoD::invtype::ALT_STORAGE_SIZE, SoD::invtype::ARCHIVED_SIZE, SoD::INULL, SoD::INULL, SoD::INULL, - SoD::invtype::OTHER_SIZE + SoD::INULL, SoD::INULL, SoD::invtype::OTHER_SIZE ), SoD::invslot::EQUIPMENT_BITMASK, @@ -300,7 +308,7 @@ static const EQ::inventory::LookupEntry inventory_static_lookup_entries[EQ::vers UF::invtype::VIEW_MOD_PC_SIZE, UF::invtype::VIEW_MOD_BANK_SIZE, UF::invtype::VIEW_MOD_SHARED_BANK_SIZE, UF::invtype::VIEW_MOD_LIMBO_SIZE, UF::invtype::ALT_STORAGE_SIZE, UF::invtype::ARCHIVED_SIZE, UF::INULL, UF::INULL, UF::INULL, - UF::invtype::OTHER_SIZE + UF::INULL, UF::INULL, UF::invtype::OTHER_SIZE ), UF::invslot::EQUIPMENT_BITMASK, @@ -327,7 +335,7 @@ static const EQ::inventory::LookupEntry inventory_static_lookup_entries[EQ::vers RoF::invtype::VIEW_MOD_PC_SIZE, RoF::invtype::VIEW_MOD_BANK_SIZE, RoF::invtype::VIEW_MOD_SHARED_BANK_SIZE, RoF::invtype::VIEW_MOD_LIMBO_SIZE, RoF::invtype::ALT_STORAGE_SIZE, RoF::invtype::ARCHIVED_SIZE, RoF::invtype::MAIL_SIZE, RoF::invtype::GUILD_TROPHY_TRIBUTE_SIZE, RoF::INULL, - RoF::invtype::OTHER_SIZE + RoF::INULL,RoF::INULL,RoF::invtype::OTHER_SIZE ), RoF::invslot::EQUIPMENT_BITMASK, @@ -354,7 +362,7 @@ static const EQ::inventory::LookupEntry inventory_static_lookup_entries[EQ::vers RoF2::invtype::VIEW_MOD_PC_SIZE, RoF2::invtype::VIEW_MOD_BANK_SIZE, RoF2::invtype::VIEW_MOD_SHARED_BANK_SIZE, RoF2::invtype::VIEW_MOD_LIMBO_SIZE, RoF2::invtype::ALT_STORAGE_SIZE, RoF2::invtype::ARCHIVED_SIZE, RoF2::invtype::MAIL_SIZE, RoF2::invtype::GUILD_TROPHY_TRIBUTE_SIZE, RoF2::invtype::KRONO_SIZE, - RoF2::invtype::OTHER_SIZE + RoF2::invtype::GUILD_BANK_MAIN_SIZE,RoF2::invtype::GUILD_BANK_DEPOSIT_SIZE, RoF2::invtype::OTHER_SIZE ), RoF2::invslot::EQUIPMENT_BITMASK, @@ -381,7 +389,7 @@ static const EQ::inventory::LookupEntry inventory_static_lookup_entries[EQ::vers EntityLimits::NPC::INULL, EntityLimits::NPC::INULL, EntityLimits::NPC::INULL, EntityLimits::NPC::INULL, EntityLimits::NPC::INULL, EntityLimits::NPC::INULL, EntityLimits::NPC::INULL, EntityLimits::NPC::INULL, EntityLimits::NPC::INULL, - EntityLimits::NPC::INULL + EntityLimits::NPC::INULL, EntityLimits::NPC::INULL,EntityLimits::NPC::INULL ), EntityLimits::NPC::INULL, @@ -408,7 +416,7 @@ static const EQ::inventory::LookupEntry inventory_static_lookup_entries[EQ::vers EntityLimits::NPCMerchant::INULL, EntityLimits::NPCMerchant::INULL, EntityLimits::NPCMerchant::INULL, EntityLimits::NPCMerchant::INULL, EntityLimits::NPCMerchant::INULL, EntityLimits::NPCMerchant::INULL, EntityLimits::NPCMerchant::INULL, EntityLimits::NPCMerchant::INULL, EntityLimits::NPCMerchant::INULL, - EntityLimits::NPCMerchant::INULL + EntityLimits::NPCMerchant::INULL, EntityLimits::NPCMerchant::INULL, EntityLimits::NPCMerchant::INULL ), EntityLimits::NPCMerchant::INULL, @@ -435,7 +443,7 @@ static const EQ::inventory::LookupEntry inventory_static_lookup_entries[EQ::vers EntityLimits::Merc::INULL, EntityLimits::Merc::INULL, EntityLimits::Merc::INULL, EntityLimits::Merc::INULL, EntityLimits::Merc::INULL, EntityLimits::Merc::INULL, EntityLimits::Merc::INULL, EntityLimits::Merc::INULL, EntityLimits::Merc::INULL, - EntityLimits::Merc::INULL + EntityLimits::Merc::INULL, EntityLimits::Merc::INULL, EntityLimits::Merc::INULL ), EntityLimits::Merc::INULL, @@ -462,7 +470,7 @@ static const EQ::inventory::LookupEntry inventory_static_lookup_entries[EQ::vers EntityLimits::Bot::INULL, EntityLimits::Bot::INULL, EntityLimits::Bot::INULL, EntityLimits::Bot::INULL, EntityLimits::Bot::INULL, EntityLimits::Bot::INULL, EntityLimits::Bot::INULL, EntityLimits::Bot::INULL, EntityLimits::Bot::INULL, - EntityLimits::Bot::INULL + EntityLimits::Bot::INULL, EntityLimits::Bot::INULL, EntityLimits::Bot::INULL ), EntityLimits::Bot::invslot::EQUIPMENT_BITMASK, @@ -489,7 +497,7 @@ static const EQ::inventory::LookupEntry inventory_static_lookup_entries[EQ::vers EntityLimits::ClientPet::INULL, EntityLimits::ClientPet::INULL, EntityLimits::ClientPet::INULL, EntityLimits::ClientPet::INULL, EntityLimits::ClientPet::INULL, EntityLimits::ClientPet::INULL, EntityLimits::ClientPet::INULL, EntityLimits::ClientPet::INULL, EntityLimits::ClientPet::INULL, - EntityLimits::ClientPet::INULL + EntityLimits::ClientPet::INULL, EntityLimits::ClientPet::INULL, EntityLimits::ClientPet::INULL ), EntityLimits::ClientPet::INULL, @@ -516,7 +524,7 @@ static const EQ::inventory::LookupEntry inventory_static_lookup_entries[EQ::vers EntityLimits::NPCPet::INULL, EntityLimits::NPCPet::INULL, EntityLimits::NPCPet::INULL, EntityLimits::NPCPet::INULL, EntityLimits::NPCPet::INULL, EntityLimits::NPCPet::INULL, EntityLimits::NPCPet::INULL, EntityLimits::NPCPet::INULL, EntityLimits::NPCPet::INULL, - EntityLimits::NPCPet::INULL + EntityLimits::NPCPet::INULL, EntityLimits::NPCPet::INULL, EntityLimits::NPCPet::INULL ), EntityLimits::NPCPet::INULL, @@ -543,7 +551,7 @@ static const EQ::inventory::LookupEntry inventory_static_lookup_entries[EQ::vers EntityLimits::MercPet::INULL, EntityLimits::MercPet::INULL, EntityLimits::MercPet::INULL, EntityLimits::MercPet::INULL, EntityLimits::MercPet::INULL, EntityLimits::MercPet::INULL, EntityLimits::MercPet::INULL, EntityLimits::MercPet::INULL, EntityLimits::MercPet::INULL, - EntityLimits::MercPet::INULL + EntityLimits::MercPet::INULL, EntityLimits::MercPet::INULL, EntityLimits::MercPet::INULL ), EntityLimits::MercPet::INULL, @@ -570,7 +578,7 @@ static const EQ::inventory::LookupEntry inventory_static_lookup_entries[EQ::vers EntityLimits::BotPet::INULL, EntityLimits::BotPet::INULL, EntityLimits::BotPet::INULL, EntityLimits::BotPet::INULL, EntityLimits::BotPet::INULL, EntityLimits::BotPet::INULL, EntityLimits::BotPet::INULL, EntityLimits::BotPet::INULL, EntityLimits::BotPet::INULL, - EntityLimits::BotPet::INULL + EntityLimits::BotPet::INULL, EntityLimits::BotPet::INULL, EntityLimits::BotPet::INULL ), EntityLimits::BotPet::INULL, @@ -597,7 +605,7 @@ static const EQ::inventory::LookupEntry inventory_static_lookup_entries[EQ::vers Titanium::invtype::VIEW_MOD_PC_SIZE, Titanium::invtype::VIEW_MOD_BANK_SIZE, Titanium::invtype::VIEW_MOD_SHARED_BANK_SIZE, Titanium::invtype::VIEW_MOD_LIMBO_SIZE, Titanium::INULL, Titanium::INULL, Titanium::INULL, Titanium::INULL, Titanium::INULL, - Titanium::INULL + Titanium::INULL, Titanium::INULL, Titanium::INULL ), Titanium::INULL, @@ -624,7 +632,7 @@ static const EQ::inventory::LookupEntry inventory_static_lookup_entries[EQ::vers SoF::invtype::VIEW_MOD_PC_SIZE, SoF::invtype::VIEW_MOD_BANK_SIZE, SoF::invtype::VIEW_MOD_SHARED_BANK_SIZE, SoF::invtype::VIEW_MOD_LIMBO_SIZE, SoF::INULL, SoF::INULL, SoF::INULL, SoF::INULL, SoF::INULL, - SoF::INULL + SoF::INULL, SoF::INULL, SoF::INULL ), SoF::INULL, @@ -651,7 +659,7 @@ static const EQ::inventory::LookupEntry inventory_static_lookup_entries[EQ::vers SoD::invtype::VIEW_MOD_PC_SIZE, SoD::invtype::VIEW_MOD_BANK_SIZE, SoD::invtype::VIEW_MOD_SHARED_BANK_SIZE, SoD::invtype::VIEW_MOD_LIMBO_SIZE, SoD::INULL, SoD::INULL, SoD::INULL, SoD::INULL, SoD::INULL, - SoD::INULL + SoD::INULL, SoD::INULL, SoD::INULL ), SoD::INULL, @@ -678,7 +686,7 @@ static const EQ::inventory::LookupEntry inventory_static_lookup_entries[EQ::vers UF::invtype::VIEW_MOD_PC_SIZE, UF::invtype::VIEW_MOD_BANK_SIZE, UF::invtype::VIEW_MOD_SHARED_BANK_SIZE, UF::invtype::VIEW_MOD_LIMBO_SIZE, UF::INULL, UF::INULL, UF::INULL, UF::INULL, UF::INULL, - UF::INULL + UF::INULL, UF::INULL, UF::INULL ), UF::INULL, @@ -705,7 +713,7 @@ static const EQ::inventory::LookupEntry inventory_static_lookup_entries[EQ::vers RoF::invtype::VIEW_MOD_PC_SIZE, RoF::invtype::VIEW_MOD_BANK_SIZE, RoF::invtype::VIEW_MOD_SHARED_BANK_SIZE, RoF::invtype::VIEW_MOD_LIMBO_SIZE, RoF::INULL, RoF::INULL, RoF::INULL, RoF::INULL, RoF::INULL, - RoF::INULL + RoF::INULL, RoF::INULL, RoF::INULL ), RoF::INULL, @@ -732,7 +740,7 @@ static const EQ::inventory::LookupEntry inventory_static_lookup_entries[EQ::vers RoF2::invtype::VIEW_MOD_PC_SIZE, RoF2::invtype::VIEW_MOD_BANK_SIZE, RoF2::invtype::VIEW_MOD_SHARED_BANK_SIZE, RoF2::invtype::VIEW_MOD_LIMBO_SIZE, RoF2::INULL, RoF2::INULL, RoF2::INULL, RoF2::INULL, RoF2::INULL, - RoF2::INULL + RoF2::INULL, RoF2::INULL, RoF2::INULL ), RoF2::INULL, diff --git a/common/eq_limits.h b/common/eq_limits.h index 40b095b15..6c00e411e 100644 --- a/common/eq_limits.h +++ b/common/eq_limits.h @@ -42,6 +42,7 @@ namespace EQ uint32 ExpansionsMask; int16 CharacterCreationLimit; size_t SayLinkBodySize; + uint32 BazaarTraderLimit; LookupEntry(const LookupEntry *lookup_entry) { } LookupEntry( @@ -49,13 +50,15 @@ namespace EQ uint32 ExpansionBit, uint32 ExpansionsMask, int16 CharacterCreationLimit, - size_t SayLinkBodySize + size_t SayLinkBodySize, + uint32 BazaarTraderLimit ) : Expansion(Expansion), ExpansionBit(ExpansionBit), ExpansionsMask(ExpansionsMask), CharacterCreationLimit(CharacterCreationLimit), - SayLinkBodySize(SayLinkBodySize) + SayLinkBodySize(SayLinkBodySize), + BazaarTraderLimit(BazaarTraderLimit) { } }; @@ -84,7 +87,7 @@ namespace EQ int16 ViewMODPC, ViewMODBank, ViewMODSharedBank; int16 ViewMODLimbo, AltStorage, Archived; int16 Mail, GuildTrophyTribute, Krono; - int16 Other; + int16 GuildBankMain,GuildBankDeposit, Other; InventoryTypeSize_Struct( int16 Possessions, int16 Bank, int16 SharedBank, @@ -95,7 +98,7 @@ namespace EQ int16 ViewMODPC, int16 ViewMODBank, int16 ViewMODSharedBank, int16 ViewMODLimbo, int16 AltStorage, int16 Archived, int16 Mail, int16 GuildTrophyTribute, int16 Krono, - int16 Other + int16 GuildBankMain,int16 GuildBankDeposit, int16 Other ) : Possessions(Possessions), Bank(Bank), SharedBank(SharedBank), Trade(Trade), World(World), Limbo(Limbo), @@ -105,7 +108,7 @@ namespace EQ ViewMODPC(ViewMODPC), ViewMODBank(ViewMODBank), ViewMODSharedBank(ViewMODSharedBank), ViewMODLimbo(ViewMODLimbo), AltStorage(AltStorage), Archived(Archived), Mail(Mail), GuildTrophyTribute(GuildTrophyTribute), Krono(Krono), - Other(Other) + GuildBankMain(GuildBankMain), GuildBankDeposit(GuildBankDeposit), Other(Other) { } }; diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index 322bdf639..0a1349254 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -19,17 +19,17 @@ #ifndef EQ_PACKET_STRUCTS_H #define EQ_PACKET_STRUCTS_H -#include "types.h" +#include #include #include -#include #include -#include "../common/version.h" -#include "emu_constants.h" -#include "textures.h" #include "../cereal/include/cereal/archives/binary.hpp" #include "../cereal/include/cereal/types/string.hpp" #include "../cereal/include/cereal/types/vector.hpp" +#include "../common/version.h" +#include "emu_constants.h" +#include "textures.h" +#include "types.h" static const uint32 BUFF_COUNT = 42; static const uint32 PET_BUFF_COUNT = 30; @@ -47,7 +47,7 @@ static const uint32 ADVANCED_LORE_LENGTH = 8192; */ #pragma pack(1) -struct LoginInfo_Struct { +struct LoginInfo { /*000*/ char login_info[64]; /*064*/ uint8 unknown064[124]; /*188*/ uint8 zoning; // 01 if zoning, 00 if not @@ -324,6 +324,8 @@ union bool guild_show; bool trader; bool buyer; + bool untargetable; + uint32 npc_tint_id; }; struct PlayerState_Struct { @@ -3742,7 +3744,8 @@ struct GetItems_Struct{ struct BecomeTrader_Struct { uint32 action; - uint32 zone_id; + uint16 zone_id; + uint16 zone_instance_id; uint32 trader_id; uint32 entity_id; char trader_name[64]; @@ -4282,6 +4285,10 @@ struct NewCombine_Struct { /*04*/ }; +struct TradeSkillRecipeInspect_Struct { + uint32 recipe_id; + uint32 padding[17]; // unknown +}; //client requesting favorite recipies struct TradeskillFavorites_Struct { @@ -5524,56 +5531,65 @@ struct GuildBankWithdrawItem_Struct struct GuildBankItemUpdate_Struct { - void Init(uint32 inAction, uint32 inUnknown004, uint16 inSlotID, uint16 inArea, uint16 inUnknown012, uint32 inItemID, uint32 inIcon, uint32 inQuantity, - uint32 inPermissions, uint32 inAllowMerge, bool inUseable) + void Init( + uint32 inAction, + uint32 inUnknown004, + uint16 inSlotID, + uint16 inArea, + uint16 inUnknown012, + uint32 inItemID, + uint32 inIcon, + uint32 inQuantity, + uint32 inPermissions, + uint32 inAllowMerge, + bool inUseable) { - Action = inAction; - Unknown004 = inUnknown004; - SlotID = inSlotID; - Area = inArea; - Unknown012 = inUnknown012; - ItemID = inItemID; - Icon = inIcon; - Quantity = inQuantity; - Permissions = inPermissions; - AllowMerge = inAllowMerge; - Useable = inUseable; - ItemName[0] = '\0'; - Donator[0] = '\0'; - WhoFor[0] = '\0'; + action = inAction; + unknown004 = inUnknown004; + slot_id = inSlotID; + area = inArea; + display = inUnknown012; + item_id = inItemID; + icon_id = inIcon; + quantity = inQuantity; + permissions = inPermissions; + allow_merge = inAllowMerge; + is_useable = inUseable; + item_name[0] = '\0'; + donator[0] = '\0'; + who_for[0] = '\0'; }; -/*000*/ uint32 Action; -/*004*/ uint32 Unknown004; -/*008*/ uint16 SlotID; -/*010*/ uint16 Area; -/*012*/ uint32 Unknown012; -/*016*/ uint32 ItemID; -/*020*/ uint32 Icon; -/*024*/ uint32 Quantity; -/*028*/ uint32 Permissions; -/*032*/ uint8 AllowMerge; -/*033*/ uint8 Useable; // Used in conjunction with the Public-if-useable permission. -/*034*/ char ItemName[64]; -/*098*/ char Donator[64]; -/*162*/ char WhoFor[64]; -/*226*/ uint16 Unknown226; +/*000*/ uint32 action; +/*004*/ uint32 unknown004; +/*008*/ uint16 slot_id; +/*010*/ uint16 area; +/*012*/ uint32 display; +/*016*/ uint32 item_id; +/*020*/ uint32 icon_id; +/*024*/ uint32 quantity; +/*028*/ uint32 permissions; +/*032*/ uint8 allow_merge; +/*033*/ uint8 is_useable; // Used in conjunction with the Public-if-useable permission. +/*034*/ char item_name[64]; +/*098*/ char donator[64]; +/*162*/ char who_for[64]; +/*226*/ uint16 unknown226; }; // newer clients (RoF+) send a list that contains 240 entries // The packets don't actually use all 64 chars in the strings, but we'll just overallocate for these -struct GuildBankItemListEntry_Struct -{ - uint8 vaild; +struct GuildBankItemListEntry_Struct { + uint8 vaild; uint32 permissions; - char whofor[64]; - char donator[64]; + char whofor[64]; + char donator[64]; uint32 item_id; uint32 item_icon; uint32 quantity; - uint8 allow_merge; // 1 here for non-full stacks - uint8 usable; - char item_name[64]; + uint8 allow_merge; // 1 here for non-full stacks + uint8 usable; + char item_name[64]; }; struct GuildBankClear_Struct @@ -5818,6 +5834,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; +}; + // New OpCode/Struct for SoD+ struct GroupMakeLeader_Struct { @@ -6347,6 +6385,7 @@ enum BazaarTraderBarterActions { TraderAck2 = 22, AddTraderToBazaarWindow = 24, RemoveTraderFromBazaarWindow = 25, + FirstOpenSearch = 26, ClickTrader = 28, DeliveryCostUpdate = 29 }; @@ -6386,6 +6425,7 @@ struct BazaarSearchResultsFromDB_Struct { uint32 icon_id; uint32 sum_charges; uint32 trader_zone_id; + int32 trader_zone_instance_id; uint32 trader_entity_id; uint32 item_stat; bool stackable; @@ -6407,6 +6447,7 @@ struct BazaarSearchResultsFromDB_Struct { CEREAL_NVP(icon_id), CEREAL_NVP(sum_charges), CEREAL_NVP(trader_zone_id), + CEREAL_NVP(trader_zone_instance_id), CEREAL_NVP(trader_entity_id), CEREAL_NVP(item_stat), CEREAL_NVP(stackable), @@ -6436,6 +6477,90 @@ struct BuylineItemDetails_Struct { uint32 item_quantity; }; +struct PickZoneEntry_Struct { + int16 zone_id; + int16 unknown; + int32 player_count; + int32 instance_id; +}; + +struct PickZoneWindow_Struct { + char padding000[64]; + int64 session_id; + int8 option_count; + char padding073[23]; + PickZoneEntry_Struct entries[10]; +}; + +struct PickZone_Struct { + int64 session_id; + int32 selection_id; +}; + +struct EvolveItemToggle { + uint32 action; + uint32 unknown_004; + uint64 unique_id; + uint32 percentage; + uint32 activated; +}; + +struct EvolveXPWindowReceive { + uint32 action; + uint32 unknown_004; + uint64 item1_unique_id; + uint64 item2_unique_id; +}; + +struct EvolveItemMessaging { + uint32 action; + char serialized_data[]; +}; + +struct EvolveXPWindowSend { + /*000*/ uint32 action; + /*004*/ uint64 item1_unique_id; + /*012*/ uint64 item2_unique_id; + /*020*/ uint32 compatibility; + /*024*/ uint32 max_transfer_level; + /*028*/ uint8 item1_present; + /*029*/ uint8 item2_present; + /*030*/ std::string serialize_item_1; + /*034*/ std::string serialize_item_2; + + template + void serialize(Archive &archive) + { + archive( + CEREAL_NVP(action), + CEREAL_NVP(item1_unique_id), + CEREAL_NVP(item2_unique_id), + CEREAL_NVP(compatibility), + CEREAL_NVP(max_transfer_level), + CEREAL_NVP(item1_present), + CEREAL_NVP(item2_present), + CEREAL_NVP(serialize_item_1), + CEREAL_NVP(serialize_item_2) + ); + } +}; + +struct EvolveTransfer { + uint32 item_from_id; + uint32 item_from_current_amount; + uint32 item_to_id; + uint32 item_to_current_amount; + uint32 compatibility; + uint32 max_transfer_level; +}; + +struct EvolveGetNextItem { + uint32 new_item_id; + uint64 new_current_amount; + uint64 from_current_amount; + uint32 max_transfer_level; +}; + // Restore structure packing to default #pragma pack() diff --git a/common/eq_stream.cpp b/common/eq_stream.cpp index 0182c53f4..d17ed54c0 100644 --- a/common/eq_stream.cpp +++ b/common/eq_stream.cpp @@ -545,13 +545,13 @@ void EQStream::SendPacket(uint16 opcode, EQApplicationPacket *p) uint32 chunksize, used; uint32 length; - if (LogSys.log_settings[Logs::Server_Client_Packet].is_category_enabled == 1){ + if (EQEmuLogSys::Instance()->log_settings[Logs::Server_Client_Packet].is_category_enabled == 1){ if (p->GetOpcode() != OP_SpecialMesg){ Log(Logs::General, Logs::Server_Client_Packet, "[%s - 0x%04x] [Size: %u]", OpcodeManager::EmuToName(p->GetOpcode()), p->GetOpcode(), p->Size()); } } - if (LogSys.log_settings[Logs::Server_Client_Packet_With_Dump].is_category_enabled == 1){ + if (EQEmuLogSys::Instance()->log_settings[Logs::Server_Client_Packet_With_Dump].is_category_enabled == 1){ if (p->GetOpcode() != OP_SpecialMesg){ Log(Logs::General, Logs::Server_Client_Packet_With_Dump, "[%s - 0x%04x] [Size: %u] %s", OpcodeManager::EmuToName(p->GetOpcode()), p->GetOpcode(), p->Size(), DumpPacketToString(p).c_str()); } diff --git a/common/eq_stream_intf.h b/common/eq_stream_intf.h index e2b7b21c7..5b2d24c82 100644 --- a/common/eq_stream_intf.h +++ b/common/eq_stream_intf.h @@ -6,7 +6,7 @@ #include #include "emu_versions.h" #include "eq_packet.h" -#include "net/daybreak_connection.h" +#include "net/reliable_stream_connection.h" typedef enum { ESTABLISHED, @@ -33,18 +33,18 @@ struct EQStreamManagerInterfaceOptions //Login I had trouble getting to recognize compression at all //but that might be because it was still a bit buggy when i was testing that. if (compressed) { - daybreak_options.encode_passes[0] = EQ::Net::EncodeCompression; + reliable_stream_options.encode_passes[0] = EQ::Net::EncodeCompression; } else if (encoded) { - daybreak_options.encode_passes[0] = EQ::Net::EncodeXOR; + reliable_stream_options.encode_passes[0] = EQ::Net::EncodeXOR; } - daybreak_options.port = port; + reliable_stream_options.port = port; } int opcode_size; bool track_opcode_stats; - EQ::Net::DaybreakConnectionManagerOptions daybreak_options; + EQ::Net::ReliableStreamConnectionManagerOptions reliable_stream_options; }; class EQStreamManagerInterface @@ -80,7 +80,7 @@ public: struct Stats { - EQ::Net::DaybreakConnectionStats DaybreakStats; + EQ::Net::ReliableStreamConnectionStats ReliableStreamStats; int RecvCount[_maxEmuOpcode]; int SentCount[_maxEmuOpcode]; }; diff --git a/common/eqemu_config.cpp b/common/eqemu_config.cpp index 40374de8b..e2f35106c 100644 --- a/common/eqemu_config.cpp +++ b/common/eqemu_config.cpp @@ -147,6 +147,8 @@ void EQEmuConfig::parse_config() QSDatabaseUsername = _root["server"]["qsdatabase"].get("username", "eq").asString(); QSDatabasePassword = _root["server"]["qsdatabase"].get("password", "eq").asString(); QSDatabaseDB = _root["server"]["qsdatabase"].get("db", "eq").asString(); + QSHost = _root["server"]["queryserver"].get("host", "localhost").asString(); + QSPort = Strings::ToUnsignedInt(_root["server"]["queryserver"].get("port", "9500").asString()); /** * Zones @@ -175,6 +177,21 @@ void EQEmuConfig::parse_config() SharedMemDir = _root["server"]["directories"].get("shared_memory", "shared/").asString(); LogDir = _root["server"]["directories"].get("logs", "logs/").asString(); + auto load_paths = [&](const std::string& key, std::vector& target) { + const auto& paths = _root["server"]["directories"][key]; + if (paths.isArray()) { + for (const auto& dir : paths) { + if (dir.isString()) { + target.push_back(dir.asString()); + } + } + } + }; + + load_paths("quest_paths", m_quest_directories); + load_paths("plugin_paths", m_plugin_directories); + load_paths("lua_module_paths", m_lua_module_directories); + /** * Logs */ @@ -419,11 +436,11 @@ void EQEmuConfig::CheckUcsConfigConversion() LogInfo("Migrating old [eqemu_config] UCS configuration to new configuration"); std::string config_file_path = std::filesystem::path{ - path.GetServerPath() + "/eqemu_config.json" + PathManager::Instance()->GetServerPath() + "/eqemu_config.json" }.string(); std::string config_file_bak_path = std::filesystem::path{ - path.GetServerPath() + "/eqemu_config.ucs-migrate-json.bak" + PathManager::Instance()->GetServerPath() + "/eqemu_config.ucs-migrate-json.bak" }.string(); // copy eqemu_config.json to eqemu_config.json.bak diff --git a/common/eqemu_config.h b/common/eqemu_config.h index 7e9e3b341..57a0543c8 100644 --- a/common/eqemu_config.h +++ b/common/eqemu_config.h @@ -81,7 +81,9 @@ class EQEmuConfig std::string QSDatabaseUsername; std::string QSDatabasePassword; std::string QSDatabaseDB; - uint16 QSDatabasePort; + uint16 QSDatabasePort; + std::string QSHost; + int QSPort; // From std::string SpellsFile; @@ -118,6 +120,22 @@ class EQEmuConfig const std::string &GetUCSHost() const; uint16 GetUCSPort() const; + std::vector GetQuestDirectories() const + { + return m_quest_directories; + } + + std::vector GetPluginsDirectories() const + { + return m_plugin_directories; + } + + std::vector GetLuaModuleDirectories() const + { + return m_lua_module_directories; + } + + // uint16 DynamicCount; // map StaticZones; @@ -131,15 +149,20 @@ class EQEmuConfig Json::Value _root; static std::string ConfigFile; + std::vector m_quest_directories = {}; + std::vector m_plugin_directories = {}; + std::vector m_lua_module_directories = {}; + + protected: void parse_config(); EQEmuConfig() { } - virtual ~EQEmuConfig() {} public: + virtual ~EQEmuConfig() {} // Produce a const singleton static const EQEmuConfig *get() @@ -168,7 +191,7 @@ class EQEmuConfig std::string file = fmt::format( "{}/{}", - (file_path.empty() ? path.GetServerPath() : file_path), + (file_path.empty() ? PathManager::Instance()->GetServerPath() : file_path), EQEmuConfig::ConfigFile ); diff --git a/common/eqemu_logsys.cpp b/common/eqemu_logsys.cpp index c4c14f82f..0d735b407 100644 --- a/common/eqemu_logsys.cpp +++ b/common/eqemu_logsys.cpp @@ -25,6 +25,8 @@ #include "repositories/discord_webhooks_repository.h" #include "repositories/logsys_categories_repository.h" #include "termcolor/rang.hpp" +#include "path_manager.h" +#include "file.h" #include #include @@ -85,6 +87,7 @@ EQEmuLogSys *EQEmuLogSys::LoadLogSettingsDefaults() * Set Defaults */ log_settings[Logs::Crash].log_to_console = static_cast(Logs::General); + log_settings[Logs::Crash].log_to_file = static_cast(Logs::General); log_settings[Logs::MySQLError].log_to_console = static_cast(Logs::General); log_settings[Logs::NPCScaling].log_to_gmsay = static_cast(Logs::General); log_settings[Logs::HotReload].log_to_gmsay = static_cast(Logs::General); @@ -102,6 +105,8 @@ EQEmuLogSys *EQEmuLogSys::LoadLogSettingsDefaults() log_settings[Logs::QuestErrors].log_to_console = static_cast(Logs::General); log_settings[Logs::EqTime].log_to_console = static_cast(Logs::General); log_settings[Logs::EqTime].log_to_gmsay = static_cast(Logs::General); + log_settings[Logs::NpcHandin].log_to_console = static_cast(Logs::General); + log_settings[Logs::NpcHandin].log_to_gmsay = static_cast(Logs::General); /** * RFC 5424 @@ -532,6 +537,11 @@ void EQEmuLogSys::StartFileLogs(const std::string &log_name) { EQEmuLogSys::CloseFileLogs(); + if (!File::Exists(PathManager::Instance()->GetLogPath())) { + LogInfo("Logs directory not found, creating [{}]", PathManager::Instance()->GetLogPath()); + File::Makedir(PathManager::Instance()->GetLogPath()); + } + /** * When loading settings, we must have been given a reason in category based logging to output to a file in order to even create or open one... */ @@ -591,6 +601,8 @@ void EQEmuLogSys::SilenceConsoleLogging() log_settings[log_index].is_category_enabled = 0; } + log_settings[Logs::MySQLError].log_to_console = static_cast(Logs::MySQLError); + log_settings[Logs::Error].log_to_console = static_cast(Logs::Error); log_settings[Logs::Crash].log_to_console = static_cast(Logs::General); } @@ -602,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(); @@ -644,7 +656,7 @@ EQEmuLogSys *EQEmuLogSys::LoadLogDatabaseSettings() // If we go through this whole loop and nothing is set to any debug level, there // is no point to create a file or keep anything open if (log_settings[c.log_category_id].log_to_file > 0) { - LogSys.m_file_logs_enabled = true; + m_file_logs_enabled = true; } db_categories.emplace_back(c.log_category_id); @@ -670,14 +682,33 @@ EQEmuLogSys *EQEmuLogSys::LoadLogDatabaseSettings() if (is_missing_in_database && !is_deprecated_category) { LogInfo("Automatically adding new log category [{}] ({})", Logs::LogCategoryName[i], i); - auto new_category = LogsysCategoriesRepository::NewEntity(); - new_category.log_category_id = i; - new_category.log_category_description = Strings::Escape(Logs::LogCategoryName[i]); - new_category.log_to_console = log_settings[i].log_to_console; - new_category.log_to_gmsay = log_settings[i].log_to_gmsay; - new_category.log_to_file = log_settings[i].log_to_file; - new_category.log_to_discord = log_settings[i].log_to_discord; - db_categories_to_add.emplace_back(new_category); + auto e = LogsysCategoriesRepository::NewEntity(); + e.log_category_id = i; + e.log_category_description = Strings::Escape(Logs::LogCategoryName[i]); + e.log_to_console = log_settings[i].log_to_console; + e.log_to_gmsay = log_settings[i].log_to_gmsay; + e.log_to_file = log_settings[i].log_to_file; + e.log_to_discord = log_settings[i].log_to_discord; + db_categories_to_add.emplace_back(e); + } + + // look to see if the category name is different in the database + auto it = std::find_if( + categories.begin(), + categories.end(), + [i](const auto &c) { return c.log_category_id == i; } + ); + if (it != categories.end()) { + if (it->log_category_description != Logs::LogCategoryName[i]) { + LogInfo( + "Updating log category [{}] ({}) to new name [{}]", + it->log_category_description, + i, + Logs::LogCategoryName[i] + ); + it->log_category_description = Logs::LogCategoryName[i]; + LogsysCategoriesRepository::ReplaceOne(*m_database, *it); + } } } @@ -687,6 +718,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)); @@ -704,6 +739,10 @@ EQEmuLogSys *EQEmuLogSys::LoadLogDatabaseSettings() log_settings[Logs::Info].log_to_file = static_cast(Logs::General); log_settings[Logs::Info].log_to_console = static_cast(Logs::General); + if (silent_load) { + SilenceConsoleLogging(); + } + return this; } diff --git a/common/eqemu_logsys.h b/common/eqemu_logsys.h index 8bf474f34..0757b3e98 100644 --- a/common/eqemu_logsys.h +++ b/common/eqemu_logsys.h @@ -74,7 +74,7 @@ namespace Logs { Spawns, Spells, Status, // deprecated - TCPConnection, + TCPConnection, // deprecated Tasks, Tradeskills, Trading, @@ -142,6 +142,16 @@ namespace Logs { EqTime, Corpses, XTargets, + EvolveItem, + PositionUpdate, + KSM, + BotSettings, + BotSpellChecks, + BotSpellTypeChecks, + NpcHandin, + ZoneState, + NetClient, + NetTCP, MaxCategoryID /* Don't Remove this */ }; @@ -175,7 +185,7 @@ namespace Logs { "Spawns", "Spells", "Status (Deprecated)", - "TCP Connection", + "TCP Connection (Deprecated)", "Tasks", "Tradeskills", "Trading", @@ -184,8 +194,8 @@ namespace Logs { "Web Interface (Deprecated)", "World Server (Deprecated)", "Zone Server (Deprecated)", - "QueryErr", - "Query", + "MySQL Error", + "MySQL Query", "Mercenaries", "Quest Debug", "Legacy Packet Logging (Deprecated)", @@ -201,15 +211,15 @@ namespace Logs { "Traps", "NPC Roam Box", "NPC Scaling", - "MobAppearance", + "Mob Appearance", "Info", "Warning", "Critical (Deprecated)", "Emergency (Deprecated)", "Alert (Deprecated)", "Notice (Deprecated)", - "AI Scan", - "AI Yell", + "AI Scan Close", + "AI Yell For Help", "AI CastBeneficial", "AOE Cast", "Entity Management", @@ -227,7 +237,7 @@ namespace Logs { "DialogueWindow", "HTTP", "Saylink", - "ChecksumVer", + "Checksum Verification", "CombatRecord", "Hate", "Discord", @@ -242,12 +252,20 @@ namespace Logs { "Zoning", "EqTime", "Corpses", - "XTargets" + "XTargets", + "EvolveItem", + "PositionUpdate", + "KSM", // Kernel Samepage Merging + "Bot Settings", + "Bot Spell Checks", + "Bot Spell Type Checks", + "NpcHandin", + "ZoneState", + "Net Server <-> Client", + "Net TCP" }; } -#include "eqemu_logsys_log_aliases.h" - class Database; constexpr uint16 MAX_DISCORD_WEBHOOK_ID = 300; @@ -263,7 +281,13 @@ public: */ void CloseFileLogs(); EQEmuLogSys *LoadLogSettingsDefaults(); - EQEmuLogSys *LoadLogDatabaseSettings(); + EQEmuLogSys *LoadLogDatabaseSettings(bool silent_load = false); + + static EQEmuLogSys *Instance() + { + static EQEmuLogSys instance; + return &instance; + } /** * @param directory_name @@ -330,7 +354,7 @@ public: /** * Internally used memory reference for all log settings per category * These are loaded via DB and have defaults loaded in LoadLogSettingsDefaults - * Database loaded via LogSys.SetDatabase(&database)->LoadLogDatabaseSettings(); + * Database loaded via EQEmuLogSys::Instance()->SetDatabase(&database)->LoadLogDatabaseSettings(); */ LogSettings log_settings[Logs::LogCategory::MaxCategoryID]{}; @@ -414,7 +438,7 @@ private: void InjectTablesIfNotExist(); }; -extern EQEmuLogSys LogSys; +#include "eqemu_logsys_log_aliases.h" /** template @@ -436,7 +460,7 @@ void OutF( #define OutF(ls, debug_level, log_category, file, func, line, formatStr, ...) \ do { \ - ls.Out(debug_level, log_category, file, func, line, fmt::format(formatStr, ##__VA_ARGS__).c_str()); \ + ls->Out(debug_level, log_category, file, func, line, fmt::format(formatStr, ##__VA_ARGS__).c_str()); \ } while(0) #endif diff --git a/common/eqemu_logsys_log_aliases.h b/common/eqemu_logsys_log_aliases.h index 10c9cd98a..a2c88a03f 100644 --- a/common/eqemu_logsys_log_aliases.h +++ b/common/eqemu_logsys_log_aliases.h @@ -21,837 +21,919 @@ #ifndef EQEMU_EQEMU_LOGSYS_LOG_ALIASES_H #define EQEMU_EQEMU_LOGSYS_LOG_ALIASES_H +inline auto logsys = EQEmuLogSys::Instance(); + #define LogAA(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::AA))\ - OutF(LogSys, Logs::General, Logs::AA, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::AA))\ + OutF(logsys, Logs::General, Logs::AA, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogAADetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::AA))\ - OutF(LogSys, Logs::Detail, Logs::AA, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::AA))\ + OutF(logsys, Logs::Detail, Logs::AA, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogAI(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::AI))\ - OutF(LogSys, Logs::General, Logs::AI, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::AI))\ + OutF(logsys, Logs::General, Logs::AI, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogAIDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::AI))\ - OutF(LogSys, Logs::Detail, Logs::AI, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::AI))\ + OutF(logsys, Logs::Detail, Logs::AI, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogAggro(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Aggro))\ - OutF(LogSys, Logs::General, Logs::Aggro, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Aggro))\ + OutF(logsys, Logs::General, Logs::Aggro, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogAggroDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Aggro))\ - OutF(LogSys, Logs::Detail, Logs::Aggro, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Aggro))\ + OutF(logsys, Logs::Detail, Logs::Aggro, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogAttack(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Attack))\ - OutF(LogSys, Logs::General, Logs::Attack, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Attack))\ + OutF(logsys, Logs::General, Logs::Attack, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogAttackDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Attack))\ - OutF(LogSys, Logs::Detail, Logs::Attack, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Attack))\ + OutF(logsys, Logs::Detail, Logs::Attack, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogPacketClientServer(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::PacketClientServer))\ - OutF(LogSys, Logs::General, Logs::PacketClientServer, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::PacketClientServer))\ + OutF(logsys, Logs::General, Logs::PacketClientServer, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogPacketClientServerDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::PacketClientServer))\ - OutF(LogSys, Logs::Detail, Logs::PacketClientServer, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::PacketClientServer))\ + OutF(logsys, Logs::Detail, Logs::PacketClientServer, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogCombat(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Combat))\ - OutF(LogSys, Logs::General, Logs::Combat, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Combat))\ + OutF(logsys, Logs::General, Logs::Combat, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogCombatDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Combat))\ - OutF(LogSys, Logs::Detail, Logs::Combat, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Combat))\ + OutF(logsys, Logs::Detail, Logs::Combat, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogCommands(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Commands))\ - OutF(LogSys, Logs::General, Logs::Commands, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Commands))\ + OutF(logsys, Logs::General, Logs::Commands, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogCommandsDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Commands))\ - OutF(LogSys, Logs::Detail, Logs::Commands, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Commands))\ + OutF(logsys, Logs::Detail, Logs::Commands, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogCrash(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Crash))\ - OutF(LogSys, Logs::General, Logs::Crash, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Crash))\ + OutF(logsys, Logs::General, Logs::Crash, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogCrashDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Crash))\ - OutF(LogSys, Logs::Detail, Logs::Crash, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Crash))\ + OutF(logsys, Logs::Detail, Logs::Crash, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogDebug(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Debug))\ - OutF(LogSys, Logs::General, Logs::Debug, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Debug))\ + OutF(logsys, Logs::General, Logs::Debug, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogDebugDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Debug))\ - OutF(LogSys, Logs::Detail, Logs::Debug, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Debug))\ + OutF(logsys, Logs::Detail, Logs::Debug, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogDoors(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Doors))\ - OutF(LogSys, Logs::General, Logs::Doors, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Doors))\ + OutF(logsys, Logs::General, Logs::Doors, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogDoorsDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Doors))\ - OutF(LogSys, Logs::Detail, Logs::Doors, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Doors))\ + OutF(logsys, Logs::Detail, Logs::Doors, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogError(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Error))\ - OutF(LogSys, Logs::General, Logs::Error, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Error))\ + OutF(logsys, Logs::General, Logs::Error, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogErrorDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Error))\ - OutF(LogSys, Logs::Detail, Logs::Error, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Error))\ + OutF(logsys, Logs::Detail, Logs::Error, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogEvolveItem(message, ...) do {\ + if (logsys->IsLogEnabled(Logs::General, Logs::EvolveItem))\ + OutF(logsys, Logs::General, Logs::EvolveItem, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogEvolveItemDetail(message, ...) do {\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::EvolveItem))\ + OutF(logsys, Logs::Detail, Logs::EvolveItem, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogGuilds(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Guilds))\ - OutF(LogSys, Logs::General, Logs::Guilds, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Guilds))\ + OutF(logsys, Logs::General, Logs::Guilds, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogGuildsDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Guilds))\ - OutF(LogSys, Logs::Detail, Logs::Guilds, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Guilds))\ + OutF(logsys, Logs::Detail, Logs::Guilds, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogInventory(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Inventory))\ - OutF(LogSys, Logs::General, Logs::Inventory, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Inventory))\ + OutF(logsys, Logs::General, Logs::Inventory, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogInventoryDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Inventory))\ - OutF(LogSys, Logs::Detail, Logs::Inventory, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Inventory))\ + OutF(logsys, Logs::Detail, Logs::Inventory, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogLauncher(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Launcher))\ - OutF(LogSys, Logs::General, Logs::Launcher, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Launcher))\ + OutF(logsys, Logs::General, Logs::Launcher, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogLauncherDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Launcher))\ - OutF(LogSys, Logs::Detail, Logs::Launcher, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Launcher))\ + OutF(logsys, Logs::Detail, Logs::Launcher, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogNetcode(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Netcode))\ - OutF(LogSys, Logs::General, Logs::Netcode, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Netcode))\ + OutF(logsys, Logs::General, Logs::Netcode, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogNetcodeDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Netcode))\ - OutF(LogSys, Logs::Detail, Logs::Netcode, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Netcode))\ + OutF(logsys, Logs::Detail, Logs::Netcode, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogNormal(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Normal))\ - OutF(LogSys, Logs::General, Logs::Normal, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Normal))\ + OutF(logsys, Logs::General, Logs::Normal, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogNormalDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Normal))\ - OutF(LogSys, Logs::Detail, Logs::Normal, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Normal))\ + OutF(logsys, Logs::Detail, Logs::Normal, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogObject(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Object))\ - OutF(LogSys, Logs::General, Logs::Object, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Object))\ + OutF(logsys, Logs::General, Logs::Object, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogObjectDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Object))\ - OutF(LogSys, Logs::Detail, Logs::Object, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Object))\ + OutF(logsys, Logs::Detail, Logs::Object, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogPathing(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Pathing))\ - OutF(LogSys, Logs::General, Logs::Pathing, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Pathing))\ + OutF(logsys, Logs::General, Logs::Pathing, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogPathingDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Pathing))\ - OutF(LogSys, Logs::Detail, Logs::Pathing, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Pathing))\ + OutF(logsys, Logs::Detail, Logs::Pathing, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogQuests(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Quests))\ - OutF(LogSys, Logs::General, Logs::Quests, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Quests))\ + OutF(logsys, Logs::General, Logs::Quests, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogQuestsDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Quests))\ - OutF(LogSys, Logs::Detail, Logs::Quests, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Quests))\ + OutF(logsys, Logs::Detail, Logs::Quests, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogRules(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Rules))\ - OutF(LogSys, Logs::General, Logs::Rules, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Rules))\ + OutF(logsys, Logs::General, Logs::Rules, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogRulesDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Rules))\ - OutF(LogSys, Logs::Detail, Logs::Rules, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Rules))\ + OutF(logsys, Logs::Detail, Logs::Rules, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogSkills(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Skills))\ - OutF(LogSys, Logs::General, Logs::Skills, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Skills))\ + OutF(logsys, Logs::General, Logs::Skills, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogSkillsDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Skills))\ - OutF(LogSys, Logs::Detail, Logs::Skills, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Skills))\ + OutF(logsys, Logs::Detail, Logs::Skills, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogSpawns(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Spawns))\ - OutF(LogSys, Logs::General, Logs::Spawns, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Spawns))\ + OutF(logsys, Logs::General, Logs::Spawns, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogSpawnsDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Spawns))\ - OutF(LogSys, Logs::Detail, Logs::Spawns, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Spawns))\ + OutF(logsys, Logs::Detail, Logs::Spawns, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogSpells(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Spells))\ - OutF(LogSys, Logs::General, Logs::Spells, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Spells))\ + OutF(logsys, Logs::General, Logs::Spells, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogSpellsDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Spells))\ - OutF(LogSys, Logs::Detail, Logs::Spells, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ -} while (0) - -#define LogStatus(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Status))\ - OutF(LogSys, Logs::General, Logs::Status, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ -} while (0) - -#define LogStatusDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Status))\ - OutF(LogSys, Logs::Detail, Logs::Status, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ -} while (0) - -#define LogTCPConnection(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::TCPConnection))\ - OutF(LogSys, Logs::General, Logs::TCPConnection, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ -} while (0) - -#define LogTCPConnectionDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::TCPConnection))\ - OutF(LogSys, Logs::Detail, Logs::TCPConnection, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Spells))\ + OutF(logsys, Logs::Detail, Logs::Spells, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogTasks(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Tasks))\ - OutF(LogSys, Logs::General, Logs::Tasks, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Tasks))\ + OutF(logsys, Logs::General, Logs::Tasks, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogTasksDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Tasks))\ - OutF(LogSys, Logs::Detail, Logs::Tasks, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Tasks))\ + OutF(logsys, Logs::Detail, Logs::Tasks, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogTradeskills(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Tradeskills))\ - OutF(LogSys, Logs::General, Logs::Tradeskills, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Tradeskills))\ + OutF(logsys, Logs::General, Logs::Tradeskills, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogTradeskillsDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Tradeskills))\ - OutF(LogSys, Logs::Detail, Logs::Tradeskills, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Tradeskills))\ + OutF(logsys, Logs::Detail, Logs::Tradeskills, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogTrading(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Trading))\ - OutF(LogSys, Logs::General, Logs::Trading, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Trading))\ + OutF(logsys, Logs::General, Logs::Trading, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogTradingDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Trading))\ - OutF(LogSys, Logs::Detail, Logs::Trading, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Trading))\ + OutF(logsys, Logs::Detail, Logs::Trading, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogTribute(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Tribute))\ - OutF(LogSys, Logs::General, Logs::Tribute, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Tribute))\ + OutF(logsys, Logs::General, Logs::Tribute, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogTributeDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Tribute))\ - OutF(LogSys, Logs::Detail, Logs::Tribute, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Tribute))\ + OutF(logsys, Logs::Detail, Logs::Tribute, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogMySQLError(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::MySQLError))\ - OutF(LogSys, Logs::General, Logs::MySQLError, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::MySQLError))\ + OutF(logsys, Logs::General, Logs::MySQLError, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogMySQLErrorDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::MySQLError))\ - OutF(LogSys, Logs::Detail, Logs::MySQLError, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::MySQLError))\ + OutF(logsys, Logs::Detail, Logs::MySQLError, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogMySQLQuery(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::MySQLQuery))\ - OutF(LogSys, Logs::General, Logs::MySQLQuery, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::MySQLQuery))\ + OutF(logsys, Logs::General, Logs::MySQLQuery, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogMySQLQueryDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::MySQLQuery))\ - OutF(LogSys, Logs::Detail, Logs::MySQLQuery, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::MySQLQuery))\ + OutF(logsys, Logs::Detail, Logs::MySQLQuery, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogMercenaries(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Mercenaries))\ - OutF(LogSys, Logs::General, Logs::Mercenaries, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Mercenaries))\ + OutF(logsys, Logs::General, Logs::Mercenaries, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogMercenariesDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Mercenaries))\ - OutF(LogSys, Logs::Detail, Logs::Mercenaries, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Mercenaries))\ + OutF(logsys, Logs::Detail, Logs::Mercenaries, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogQuestDebug(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::QuestDebug))\ - OutF(LogSys, Logs::General, Logs::QuestDebug, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::QuestDebug))\ + OutF(logsys, Logs::General, Logs::QuestDebug, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogQuestDebugDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::QuestDebug))\ - OutF(LogSys, Logs::Detail, Logs::QuestDebug, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::QuestDebug))\ + OutF(logsys, Logs::Detail, Logs::QuestDebug, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogPacketServerClient(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::PacketServerClient))\ - OutF(LogSys, Logs::General, Logs::PacketServerClient, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::PacketServerClient))\ + OutF(logsys, Logs::General, Logs::PacketServerClient, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogPacketServerClientDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::PacketServerClient))\ - OutF(LogSys, Logs::Detail, Logs::PacketServerClient, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::PacketServerClient))\ + OutF(logsys, Logs::Detail, Logs::PacketServerClient, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogLoginserver(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Loginserver))\ - OutF(LogSys, Logs::General, Logs::Loginserver, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Loginserver))\ + OutF(logsys, Logs::General, Logs::Loginserver, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogLoginserverDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Loginserver))\ - OutF(LogSys, Logs::Detail, Logs::Loginserver, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Loginserver))\ + OutF(logsys, Logs::Detail, Logs::Loginserver, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogClientLogin(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::ClientLogin))\ - OutF(LogSys, Logs::General, Logs::ClientLogin, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::ClientLogin))\ + OutF(logsys, Logs::General, Logs::ClientLogin, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogClientLoginDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::ClientLogin))\ - OutF(LogSys, Logs::Detail, Logs::ClientLogin, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::ClientLogin))\ + OutF(logsys, Logs::Detail, Logs::ClientLogin, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogHPUpdate(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::HPUpdate))\ - OutF(LogSys, Logs::General, Logs::HPUpdate, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::HPUpdate))\ + OutF(logsys, Logs::General, Logs::HPUpdate, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogHPUpdateDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::HPUpdate))\ - OutF(LogSys, Logs::Detail, Logs::HPUpdate, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::HPUpdate))\ + OutF(logsys, Logs::Detail, Logs::HPUpdate, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogFixZ(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::FixZ))\ - OutF(LogSys, Logs::General, Logs::FixZ, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::FixZ))\ + OutF(logsys, Logs::General, Logs::FixZ, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogFixZDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::FixZ))\ - OutF(LogSys, Logs::Detail, Logs::FixZ, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::FixZ))\ + OutF(logsys, Logs::Detail, Logs::FixZ, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogFood(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Food))\ - OutF(LogSys, Logs::General, Logs::Food, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Food))\ + OutF(logsys, Logs::General, Logs::Food, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogFoodDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Food))\ - OutF(LogSys, Logs::Detail, Logs::Food, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Food))\ + OutF(logsys, Logs::Detail, Logs::Food, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogTraps(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Traps))\ - OutF(LogSys, Logs::General, Logs::Traps, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Traps))\ + OutF(logsys, Logs::General, Logs::Traps, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogTrapsDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Traps))\ - OutF(LogSys, Logs::Detail, Logs::Traps, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Traps))\ + OutF(logsys, Logs::Detail, Logs::Traps, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogNPCRoamBox(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::NPCRoamBox))\ - OutF(LogSys, Logs::General, Logs::NPCRoamBox, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::NPCRoamBox))\ + OutF(logsys, Logs::General, Logs::NPCRoamBox, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogNPCRoamBoxDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::NPCRoamBox))\ - OutF(LogSys, Logs::Detail, Logs::NPCRoamBox, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::NPCRoamBox))\ + OutF(logsys, Logs::Detail, Logs::NPCRoamBox, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogNPCScaling(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::NPCScaling))\ - OutF(LogSys, Logs::General, Logs::NPCScaling, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::NPCScaling))\ + OutF(logsys, Logs::General, Logs::NPCScaling, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogNPCScalingDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::NPCScaling))\ - OutF(LogSys, Logs::Detail, Logs::NPCScaling, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::NPCScaling))\ + OutF(logsys, Logs::Detail, Logs::NPCScaling, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogMobAppearance(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::MobAppearance))\ - OutF(LogSys, Logs::General, Logs::MobAppearance, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::MobAppearance))\ + OutF(logsys, Logs::General, Logs::MobAppearance, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogMobAppearanceDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::MobAppearance))\ - OutF(LogSys, Logs::Detail, Logs::MobAppearance, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::MobAppearance))\ + OutF(logsys, Logs::Detail, Logs::MobAppearance, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogInfo(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Info))\ - OutF(LogSys, Logs::General, Logs::Info, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Info))\ + OutF(logsys, Logs::General, Logs::Info, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogInfoDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Info))\ - OutF(LogSys, Logs::Detail, Logs::Info, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Info))\ + OutF(logsys, Logs::Detail, Logs::Info, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogWarning(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Warning))\ - OutF(LogSys, Logs::General, Logs::Warning, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Warning))\ + OutF(logsys, Logs::General, Logs::Warning, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogWarningDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Warning))\ - OutF(LogSys, Logs::Detail, Logs::Warning, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Warning))\ + OutF(logsys, Logs::Detail, Logs::Warning, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogCritical(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Critical))\ - OutF(LogSys, Logs::General, Logs::Critical, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Critical))\ + OutF(logsys, Logs::General, Logs::Critical, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogCriticalDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Critical))\ - OutF(LogSys, Logs::Detail, Logs::Critical, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Critical))\ + OutF(logsys, Logs::Detail, Logs::Critical, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogEmergency(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Emergency))\ - OutF(LogSys, Logs::General, Logs::Emergency, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Emergency))\ + OutF(logsys, Logs::General, Logs::Emergency, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogEmergencyDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Emergency))\ - OutF(LogSys, Logs::Detail, Logs::Emergency, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Emergency))\ + OutF(logsys, Logs::Detail, Logs::Emergency, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogAlert(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Alert))\ - OutF(LogSys, Logs::General, Logs::Alert, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Alert))\ + OutF(logsys, Logs::General, Logs::Alert, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogAlertDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Alert))\ - OutF(LogSys, Logs::Detail, Logs::Alert, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Alert))\ + OutF(logsys, Logs::Detail, Logs::Alert, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogNotice(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Notice))\ - OutF(LogSys, Logs::General, Logs::Notice, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Notice))\ + OutF(logsys, Logs::General, Logs::Notice, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogNoticeDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Notice))\ - OutF(LogSys, Logs::Detail, Logs::Notice, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Notice))\ + OutF(logsys, Logs::Detail, Logs::Notice, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogAIScanClose(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::AIScanClose))\ - OutF(LogSys, Logs::General, Logs::AIScanClose, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::AIScanClose))\ + OutF(logsys, Logs::General, Logs::AIScanClose, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogAIScanCloseDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::AIScanClose))\ - OutF(LogSys, Logs::Detail, Logs::AIScanClose, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::AIScanClose))\ + OutF(logsys, Logs::Detail, Logs::AIScanClose, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogAIYellForHelp(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::AIYellForHelp))\ - OutF(LogSys, Logs::General, Logs::AIYellForHelp, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::AIYellForHelp))\ + OutF(logsys, Logs::General, Logs::AIYellForHelp, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogAIYellForHelpDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::AIYellForHelp))\ - OutF(LogSys, Logs::Detail, Logs::AIYellForHelp, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::AIYellForHelp))\ + OutF(logsys, Logs::Detail, Logs::AIYellForHelp, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogAICastBeneficialClose(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::AICastBeneficialClose))\ - OutF(LogSys, Logs::General, Logs::AICastBeneficialClose, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::AICastBeneficialClose))\ + OutF(logsys, Logs::General, Logs::AICastBeneficialClose, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogAICastBeneficialCloseDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::AICastBeneficialClose))\ - OutF(LogSys, Logs::Detail, Logs::AICastBeneficialClose, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::AICastBeneficialClose))\ + OutF(logsys, Logs::Detail, Logs::AICastBeneficialClose, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogAoeCast(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::AoeCast))\ - OutF(LogSys, Logs::General, Logs::AoeCast, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::AoeCast))\ + OutF(logsys, Logs::General, Logs::AoeCast, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogAoeCastDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::AoeCast))\ - OutF(LogSys, Logs::Detail, Logs::AoeCast, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::AoeCast))\ + OutF(logsys, Logs::Detail, Logs::AoeCast, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogEntityManagement(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::EntityManagement))\ - OutF(LogSys, Logs::General, Logs::EntityManagement, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::EntityManagement))\ + OutF(logsys, Logs::General, Logs::EntityManagement, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogEntityManagementDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::EntityManagement))\ - OutF(LogSys, Logs::Detail, Logs::EntityManagement, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::EntityManagement))\ + OutF(logsys, Logs::Detail, Logs::EntityManagement, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogFlee(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Flee))\ - OutF(LogSys, Logs::General, Logs::Flee, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Flee))\ + OutF(logsys, Logs::General, Logs::Flee, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogFleeDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Flee))\ - OutF(LogSys, Logs::Detail, Logs::Flee, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Flee))\ + OutF(logsys, Logs::Detail, Logs::Flee, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogAura(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Aura))\ - OutF(LogSys, Logs::General, Logs::Aura, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Aura))\ + OutF(logsys, Logs::General, Logs::Aura, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogAuraDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Aura))\ - OutF(LogSys, Logs::Detail, Logs::Aura, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Aura))\ + OutF(logsys, Logs::Detail, Logs::Aura, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogHotReload(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::HotReload))\ - OutF(LogSys, Logs::General, Logs::HotReload, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::HotReload))\ + OutF(logsys, Logs::General, Logs::HotReload, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogHotReloadDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::HotReload))\ - OutF(LogSys, Logs::Detail, Logs::HotReload, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::HotReload))\ + OutF(logsys, Logs::Detail, Logs::HotReload, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogMerchants(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Merchants))\ - OutF(LogSys, Logs::General, Logs::Merchants, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Merchants))\ + OutF(logsys, Logs::General, Logs::Merchants, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogMerchantsDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Merchants))\ - OutF(LogSys, Logs::Detail, Logs::Merchants, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Merchants))\ + OutF(logsys, Logs::Detail, Logs::Merchants, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogZonePoints(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::ZonePoints))\ - OutF(LogSys, Logs::General, Logs::ZonePoints, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::ZonePoints))\ + OutF(logsys, Logs::General, Logs::ZonePoints, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogZonePointsDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::ZonePoints))\ - OutF(LogSys, Logs::Detail, Logs::ZonePoints, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::ZonePoints))\ + OutF(logsys, Logs::Detail, Logs::ZonePoints, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogLoot(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Loot))\ - OutF(LogSys, Logs::General, Logs::Loot, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Loot))\ + OutF(logsys, Logs::General, Logs::Loot, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogLootDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Loot))\ - OutF(LogSys, Logs::Detail, Logs::Loot, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Loot))\ + OutF(logsys, Logs::Detail, Logs::Loot, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogExpeditions(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Expeditions))\ - OutF(LogSys, Logs::General, Logs::Expeditions, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Expeditions))\ + OutF(logsys, Logs::General, Logs::Expeditions, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogExpeditionsDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Expeditions))\ - OutF(LogSys, Logs::Detail, Logs::Expeditions, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Expeditions))\ + OutF(logsys, Logs::Detail, Logs::Expeditions, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogDynamicZones(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::DynamicZones))\ - OutF(LogSys, Logs::General, Logs::DynamicZones, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::DynamicZones))\ + OutF(logsys, Logs::General, Logs::DynamicZones, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogDynamicZonesDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::DynamicZones))\ - OutF(LogSys, Logs::Detail, Logs::DynamicZones, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::DynamicZones))\ + OutF(logsys, Logs::Detail, Logs::DynamicZones, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogScheduler(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Scheduler))\ - OutF(LogSys, Logs::General, Logs::Scheduler, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Scheduler))\ + OutF(logsys, Logs::General, Logs::Scheduler, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogSchedulerDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Scheduler))\ - OutF(LogSys, Logs::Detail, Logs::Scheduler, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Scheduler))\ + OutF(logsys, Logs::Detail, Logs::Scheduler, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogCheat(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Cheat))\ - OutF(LogSys, Logs::General, Logs::Cheat, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Cheat))\ + OutF(logsys, Logs::General, Logs::Cheat, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogCheatDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Cheat))\ - OutF(LogSys, Logs::Detail, Logs::Cheat, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Cheat))\ + OutF(logsys, Logs::Detail, Logs::Cheat, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogClientList(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::ClientList))\ - OutF(LogSys, Logs::General, Logs::ClientList, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::ClientList))\ + OutF(logsys, Logs::General, Logs::ClientList, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogClientListDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::ClientList))\ - OutF(LogSys, Logs::Detail, Logs::ClientList, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::ClientList))\ + OutF(logsys, Logs::Detail, Logs::ClientList, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogDiaWind(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::DiaWind))\ - OutF(LogSys, Logs::General, Logs::DiaWind, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::DiaWind))\ + OutF(logsys, Logs::General, Logs::DiaWind, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogDiaWindDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::DiaWind))\ - OutF(LogSys, Logs::Detail, Logs::DiaWind, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::DiaWind))\ + OutF(logsys, Logs::Detail, Logs::DiaWind, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogHTTP(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::HTTP))\ - OutF(LogSys, Logs::General, Logs::HTTP, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::HTTP))\ + OutF(logsys, Logs::General, Logs::HTTP, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogHTTPDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::HTTP))\ - OutF(LogSys, Logs::Detail, Logs::HTTP, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::HTTP))\ + OutF(logsys, Logs::Detail, Logs::HTTP, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogSaylink(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Saylink))\ - OutF(LogSys, Logs::General, Logs::Saylink, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Saylink))\ + OutF(logsys, Logs::General, Logs::Saylink, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogSaylinkDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Saylink))\ - OutF(LogSys, Logs::Detail, Logs::Saylink, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Saylink))\ + OutF(logsys, Logs::Detail, Logs::Saylink, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogChecksumVerification(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::ChecksumVerification))\ - OutF(LogSys, Logs::General, Logs::ChecksumVerification, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::ChecksumVerification))\ + OutF(logsys, Logs::General, Logs::ChecksumVerification, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogChecksumVerificationDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::ChecksumVerification))\ - OutF(LogSys, Logs::Detail, Logs::ChecksumVerification, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::ChecksumVerification))\ + OutF(logsys, Logs::Detail, Logs::ChecksumVerification, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogCombatRecord(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::CombatRecord))\ - OutF(LogSys, Logs::General, Logs::CombatRecord, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::CombatRecord))\ + OutF(logsys, Logs::General, Logs::CombatRecord, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogCombatRecordDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::CombatRecord))\ - OutF(LogSys, Logs::Detail, Logs::CombatRecord, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::CombatRecord))\ + OutF(logsys, Logs::Detail, Logs::CombatRecord, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogHate(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Hate))\ - OutF(LogSys, Logs::General, Logs::Hate, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Hate))\ + OutF(logsys, Logs::General, Logs::Hate, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogHateDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Hate))\ - OutF(LogSys, Logs::Detail, Logs::Hate, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Hate))\ + OutF(logsys, Logs::Detail, Logs::Hate, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogDiscord(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Discord))\ - OutF(LogSys, Logs::General, Logs::Discord, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Discord))\ + OutF(logsys, Logs::General, Logs::Discord, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogDiscordDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Discord))\ - OutF(LogSys, Logs::Detail, Logs::Discord, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Discord))\ + OutF(logsys, Logs::Detail, Logs::Discord, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogFaction(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Faction))\ - OutF(LogSys, Logs::General, Logs::Faction, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Faction))\ + OutF(logsys, Logs::General, Logs::Faction, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogFactionDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Faction))\ - OutF(LogSys, Logs::Detail, Logs::Faction, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Faction))\ + OutF(logsys, Logs::Detail, Logs::Faction, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogPacketServerToServer(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::PacketServerToServer))\ - OutF(LogSys, Logs::General, Logs::PacketServerToServer, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::PacketServerToServer))\ + OutF(logsys, Logs::General, Logs::PacketServerToServer, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogPacketServerToServerDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::PacketServerToServer))\ - OutF(LogSys, Logs::Detail, Logs::PacketServerToServer, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::PacketServerToServer))\ + OutF(logsys, Logs::Detail, Logs::PacketServerToServer, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) // manually created #define LogBugs(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Bugs))\ - OutF(LogSys, Logs::General, Logs::Bugs, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Bugs))\ + OutF(logsys, Logs::General, Logs::Bugs, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogBugsDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Bugs))\ - OutF(LogSys, Logs::Detail, Logs::Bugs, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Bugs))\ + OutF(logsys, Logs::Detail, Logs::Bugs, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogQuestErrors(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::QuestErrors))\ - OutF(LogSys, Logs::General, Logs::QuestErrors, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::QuestErrors))\ + OutF(logsys, Logs::General, Logs::QuestErrors, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogQuestErrorsDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::QuestErrors))\ - OutF(LogSys, Logs::Detail, Logs::QuestErrors, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::QuestErrors))\ + OutF(logsys, Logs::Detail, Logs::QuestErrors, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogPlayerEvents(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::PlayerEvents))\ - OutF(LogSys, Logs::General, Logs::PlayerEvents, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::PlayerEvents))\ + OutF(logsys, Logs::General, Logs::PlayerEvents, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogPlayerEventsDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::PlayerEvents))\ - OutF(LogSys, Logs::Detail, Logs::PlayerEvents, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::PlayerEvents))\ + OutF(logsys, Logs::Detail, Logs::PlayerEvents, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogDataBuckets(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::DataBuckets))\ - OutF(LogSys, Logs::General, Logs::DataBuckets, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::DataBuckets))\ + OutF(logsys, Logs::General, Logs::DataBuckets, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogDataBucketsDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::DataBuckets))\ - OutF(LogSys, Logs::Detail, Logs::DataBuckets, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::DataBuckets))\ + OutF(logsys, Logs::Detail, Logs::DataBuckets, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogZoning(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Zoning))\ - OutF(LogSys, Logs::General, Logs::Zoning, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Zoning))\ + OutF(logsys, Logs::General, Logs::Zoning, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogZoningDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Zoning))\ - OutF(LogSys, Logs::Detail, Logs::Zoning, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Zoning))\ + OutF(logsys, Logs::Detail, Logs::Zoning, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogEqTime(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::EqTime))\ - OutF(LogSys, Logs::General, Logs::EqTime, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::EqTime))\ + OutF(logsys, Logs::General, Logs::EqTime, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogEqTimeDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::EqTime))\ - OutF(LogSys, Logs::Detail, Logs::EqTime, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::EqTime))\ + OutF(logsys, Logs::Detail, Logs::EqTime, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogCorpses(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::Corpses))\ - OutF(LogSys, Logs::General, Logs::Corpses, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::Corpses))\ + OutF(logsys, Logs::General, Logs::Corpses, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogCorpsesDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::Corpses))\ - OutF(LogSys, Logs::Detail, Logs::Corpses, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::Corpses))\ + OutF(logsys, Logs::Detail, Logs::Corpses, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogXTargets(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::General, Logs::XTargets))\ - OutF(LogSys, Logs::General, Logs::XTargets, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::General, Logs::XTargets))\ + OutF(logsys, Logs::General, Logs::XTargets, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogXTargetsDetail(message, ...) do {\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::XTargets))\ - OutF(LogSys, Logs::Detail, Logs::XTargets, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::XTargets))\ + OutF(logsys, Logs::Detail, Logs::XTargets, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogPositionUpdate(message, ...) do {\ + if (logsys->IsLogEnabled(Logs::General, Logs::PositionUpdate))\ + OutF(logsys, Logs::General, Logs::PositionUpdate, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogPositionUpdateDetail(message, ...) do {\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::PositionUpdate))\ + OutF(logsys, Logs::Detail, Logs::PositionUpdate, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__); \ +} while (0) + +#define LogKSM(message, ...) do {\ + if (logsys->IsLogEnabled(Logs::General, Logs::KSM))\ + OutF(logsys, Logs::General, Logs::KSM, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogKSMDetail(message, ...) do {\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::KSM))\ + OutF(logsys, Logs::Detail, Logs::KSM, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogBotSettings(message, ...) do {\ + if (logsys->IsLogEnabled(Logs::General, Logs::BotSettings))\ + OutF(logsys, Logs::General, Logs::BotSettings, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogBotSettingsDetail(message, ...) do {\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::BotSettings))\ + OutF(logsys, Logs::Detail, Logs::BotSettings, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogBotSpellChecks(message, ...) do {\ + if (logsys->IsLogEnabled(Logs::General, Logs::BotSpellChecks))\ + OutF(logsys, Logs::General, Logs::BotSpellChecks, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogBotSpellChecksDetail(message, ...) do {\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::BotSpellChecks))\ + OutF(logsys, Logs::Detail, Logs::BotSpellChecks, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogBotSpellTypeChecks(message, ...) do {\ + if (logsys->IsLogEnabled(Logs::General, Logs::BotSpellTypeChecks))\ + OutF(logsys, Logs::General, Logs::BotSpellTypeChecks, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogBotSpellTypeChecksDetail(message, ...) do {\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::BotSpellTypeChecks))\ + OutF(logsys, Logs::Detail, Logs::BotSpellTypeChecks, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogNpcHandin(message, ...) do {\ + if (logsys->IsLogEnabled(Logs::General, Logs::NpcHandin))\ + OutF(logsys, Logs::General, Logs::NpcHandin, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogNpcHandinDetail(message, ...) do {\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::NpcHandin))\ + OutF(logsys, Logs::Detail, Logs::NpcHandin, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogZoneState(message, ...) do {\ + if (logsys->IsLogEnabled(Logs::General, Logs::ZoneState))\ + OutF(logsys, Logs::General, Logs::ZoneState, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogZoneStateDetail(message, ...) do {\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::ZoneState))\ + OutF(logsys, Logs::Detail, Logs::ZoneState, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogNetClient(message, ...) do {\ + if (logsys->IsLogEnabled(Logs::General, Logs::NetClient))\ + OutF(logsys, Logs::General, Logs::NetClient, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogNetClientDetail(message, ...) do {\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::NetClient))\ + OutF(logsys, Logs::Detail, Logs::NetClient, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogNetTCP(message, ...) do {\ + if (logsys->IsLogEnabled(Logs::General, Logs::NetTCP))\ + OutF(logsys, Logs::General, Logs::NetTCP, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogNetTCPDetail(message, ...) do {\ + if (logsys->IsLogEnabled(Logs::Detail, Logs::NetTCP))\ + OutF(logsys, Logs::Detail, Logs::NetTCP, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define Log(debug_level, log_category, message, ...) do {\ - if (LogSys.IsLogEnabled(debug_level, log_category))\ - LogSys.Out(debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(debug_level, log_category))\ + logsys->Out(debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) #define LogF(debug_level, log_category, message, ...) do {\ - if (LogSys.IsLogEnabled(debug_level, log_category))\ - OutF(LogSys, debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ + if (logsys->IsLogEnabled(debug_level, log_category))\ + OutF(logsys, debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) diff --git a/common/events/player_event_discord_formatter.cpp b/common/events/player_event_discord_formatter.cpp index 578ec672b..5b068f507 100644 --- a/common/events/player_event_discord_formatter.cpp +++ b/common/events/player_event_discord_formatter.cpp @@ -714,6 +714,18 @@ std::string PlayerEventDiscordFormatter::FormatNPCHandinEvent( h.charges > 1 ? fmt::format(" Charges: {}", h.charges) : "", h.attuned ? " (Attuned)" : "" ); + + for (int i = 0; i < h.augment_ids.size(); i++) { + if (!h.augment_names[i].empty()) { + const uint8 slot_id = (i + 1); + handin_items_info += fmt::format( + "Augment {}: {} ({})\n", + slot_id, + h.augment_names[i], + h.augment_ids[i] + ); + } + } } } @@ -727,6 +739,18 @@ std::string PlayerEventDiscordFormatter::FormatNPCHandinEvent( r.charges > 1 ? fmt::format(" Charges: {}", r.charges) : "", r.attuned ? " (Attuned)" : "" ); + + for (int i = 0; i < r.augment_ids.size(); i++) { + if (!r.augment_names[i].empty()) { + const uint8 slot_id = (i + 1); + return_items_info += fmt::format( + "Augment {}: {} ({})\n", + slot_id, + r.augment_names[i], + r.augment_ids[i] + ); + } + } } } @@ -1057,51 +1081,51 @@ std::string PlayerEventDiscordFormatter::FormatTradeEvent( if (!e.character_1_give_items.empty()) { for (const auto &i: e.character_1_give_items) { std::string augment_info; - if (i.aug_1_item_id > 0) { + if (i.augment_1_id > 0) { augment_info += fmt::format( "Augment 1: {} ({})", - i.aug_1_item_name, - i.aug_1_item_id + i.augment_1_name, + i.augment_1_id ); } - if (i.aug_2_item_id > 0) { + if (i.augment_2_id > 0) { augment_info += fmt::format( "Augment 2: {} ({})", - i.aug_2_item_name, - i.aug_2_item_id + i.augment_2_name, + i.augment_2_id ); } - if (i.aug_3_item_id > 0) { + if (i.augment_3_id > 0) { augment_info += fmt::format( "Augment 3: {} ({})", - i.aug_3_item_name, - i.aug_3_item_id + i.augment_3_name, + i.augment_3_id ); } - if (i.aug_4_item_id > 0) { + if (i.augment_4_id > 0) { augment_info += fmt::format( "Augment 4: {} ({})\n", - i.aug_4_item_name, - i.aug_4_item_id + i.augment_4_name, + i.augment_4_id ); } - if (i.aug_5_item_id > 0) { + if (i.augment_5_id > 0) { augment_info += fmt::format( "Augment 5: {} ({})\n", - i.aug_5_item_name, - i.aug_5_item_id + i.augment_5_name, + i.augment_5_id ); } - if (i.aug_6_item_id > 0) { + if (i.augment_6_id > 0) { augment_info += fmt::format( "Augment 6: {} ({})", - i.aug_6_item_name, - i.aug_6_item_id + i.augment_6_name, + i.augment_6_id ); } @@ -1122,51 +1146,51 @@ std::string PlayerEventDiscordFormatter::FormatTradeEvent( if (!e.character_2_give_items.empty()) { for (const auto &i: e.character_2_give_items) { std::string augment_info; - if (i.aug_1_item_id > 0) { + if (i.augment_1_id > 0) { augment_info += fmt::format( "Augment 1: {} ({})", - i.aug_1_item_name, - i.aug_1_item_id + i.augment_1_name, + i.augment_1_id ); } - if (i.aug_2_item_id > 0) { + if (i.augment_2_id > 0) { augment_info += fmt::format( "Augment 2: {} ({})", - i.aug_2_item_name, - i.aug_2_item_id + i.augment_2_name, + i.augment_2_id ); } - if (i.aug_3_item_id > 0) { + if (i.augment_3_id > 0) { augment_info += fmt::format( "Augment 3: {} ({})", - i.aug_3_item_name, - i.aug_3_item_id + i.augment_3_name, + i.augment_3_id ); } - if (i.aug_4_item_id > 0) { + if (i.augment_4_id > 0) { augment_info += fmt::format( "Augment 4: {} ({})\n", - i.aug_4_item_name, - i.aug_4_item_id + i.augment_4_name, + i.augment_4_id ); } - if (i.aug_5_item_id > 0) { + if (i.augment_5_id > 0) { augment_info += fmt::format( "Augment 5: {} ({})\n", - i.aug_5_item_name, - i.aug_5_item_id + i.augment_5_name, + i.augment_5_id ); } - if (i.aug_6_item_id > 0) { + if (i.augment_6_id > 0) { augment_info += fmt::format( "Augment 6: {} ({})", - i.aug_6_item_name, - i.aug_6_item_id + i.augment_6_name, + i.augment_6_id ); } diff --git a/common/events/player_event_logs.cpp b/common/events/player_event_logs.cpp index 5baa3e548..daa8cd17c 100644 --- a/common/events/player_event_logs.cpp +++ b/common/events/player_event_logs.cpp @@ -1,8 +1,14 @@ -#include #include "player_event_logs.h" -#include "player_event_discord_formatter.h" +#include + #include "../platform.h" #include "../rulesys.h" +#include "player_event_discord_formatter.h" +#include "../repositories/player_event_loot_items_repository.h" +#include "../repositories/player_event_merchant_sell_repository.h" +#include "../repositories/player_event_merchant_purchase_repository.h" +#include "../repositories/player_event_npc_handin_repository.h" +#include "../repositories/player_event_npc_handin_entries_repository.h" const uint32 PROCESS_RETENTION_TRUNCATION_TIMER_INTERVAL = 60 * 60 * 1000; // 1 hour @@ -11,6 +17,7 @@ void PlayerEventLogs::Init() { m_process_batch_events_timer.SetTimer(RuleI(Logging, BatchPlayerEventProcessIntervalSeconds) * 1000); m_process_retention_truncation_timer.SetTimer(PROCESS_RETENTION_TRUNCATION_TIMER_INTERVAL); + m_database_ping_timer.SetTimer(10 * 1000); // 10 seconds ValidateDatabaseConnection(); @@ -21,6 +28,7 @@ void PlayerEventLogs::Init() m_settings[i].event_enabled = 1; m_settings[i].retention_days = 0; m_settings[i].discord_webhook_id = 0; + m_settings[i].etl_enabled = false; } SetSettingsDefaults(); @@ -65,6 +73,7 @@ void PlayerEventLogs::Init() c.event_name = PlayerEvent::EventName[i]; c.event_enabled = m_settings[i].event_enabled; c.retention_days = m_settings[i].retention_days; + c.etl_enabled = false; settings_to_insert.emplace_back(c); } } @@ -78,6 +87,7 @@ void PlayerEventLogs::Init() // on initial boot process truncation if (processing_in_world || processing_in_qs) { + LoadEtlIds(); ProcessRetentionTruncation(); } } @@ -121,23 +131,328 @@ void PlayerEventLogs::ProcessBatchQueue() return; } + static std::map counter{}; + counter.clear(); + for (auto const &e: m_record_batch_queue) { + counter[e.event_type_id]++; + } + BenchTimer benchmark; + EtlQueues etl_queues{}; + for (const auto &[type, count]: counter) { + if (count > 0) { + switch (type) { + case PlayerEvent::TRADE: + etl_queues.trade.reserve(count); + break; + case PlayerEvent::SPEECH: + etl_queues.speech.reserve(count); + break; + case PlayerEvent::LOOT_ITEM: + etl_queues.loot_items.reserve(count); + break; + case PlayerEvent::KILLED_NPC: + etl_queues.killed_npc.reserve(count); + break; + case PlayerEvent::NPC_HANDIN: + etl_queues.npc_handin.reserve(count); + break; + case PlayerEvent::AA_PURCHASE: + etl_queues.aa_purchase.reserve(count); + break; + case PlayerEvent::MERCHANT_SELL: + etl_queues.merchant_sell.reserve(count); + break; + case PlayerEvent::KILLED_RAID_NPC: + etl_queues.killed_raid_npc.reserve(count); + break; + case PlayerEvent::KILLED_NAMED_NPC: + etl_queues.killed_named_npc.reserve(count); + break; + case PlayerEvent::MERCHANT_PURCHASE: + etl_queues.merchant_purchase.reserve(count); + break; + default: + break; + } + } + } + + // Helper to deserialize event data + auto Deserialize = [](const std::string &data, auto &out) { + if (!Strings::IsValidJson(data)) { + return; + } + + // cpp exceptions are terrible, don't ever use them + try { + std::stringstream ss(data); + cereal::JSONInputArchive ar(ss); + out.serialize(ar); + } + catch (const std::exception &e) {} + }; + + // Helper to assign ETL table ID + + auto AssignEtlId = [&]( + PlayerEventLogsRepository::PlayerEventLogs& r, + PlayerEvent::EventType type + ) + { + if (m_etl_settings.contains(type)) { + r.etl_table_id = m_etl_settings.at(type).next_id++; + } + }; + + // Define event processors + std::unordered_map> event_processors = { + { + PlayerEvent::EventType::LOOT_ITEM, [&](PlayerEventLogsRepository::PlayerEventLogs &r) { + PlayerEvent::LootItemEvent in{}; + PlayerEventLootItemsRepository::PlayerEventLootItems out{}; + Deserialize(r.event_data, in); + + out.charges = in.charges; + out.corpse_name = in.corpse_name; + out.item_id = in.item_id; + out.item_name = in.item_name; + out.augment_1_id = in.augment_1_id; + out.augment_2_id = in.augment_2_id; + out.augment_3_id = in.augment_3_id; + out.augment_4_id = in.augment_4_id; + out.augment_5_id = in.augment_5_id; + out.augment_6_id = in.augment_6_id; + out.npc_id = in.npc_id; + out.created_at = r.created_at; + + AssignEtlId(r, PlayerEvent::EventType::LOOT_ITEM); + etl_queues.loot_items.push_back(out); + } + }, + { + PlayerEvent::EventType::MERCHANT_SELL, [&](PlayerEventLogsRepository::PlayerEventLogs &r) { + PlayerEvent::MerchantSellEvent in{}; + PlayerEventMerchantSellRepository::PlayerEventMerchantSell out{}; + Deserialize(r.event_data, in); + + out.npc_id = in.npc_id; + out.merchant_name = in.merchant_name; + out.merchant_type = in.merchant_type; + out.item_id = in.item_id; + out.item_name = in.item_name; + out.charges = in.charges; + out.cost = in.cost; + out.alternate_currency_id = in.alternate_currency_id; + out.player_money_balance = in.player_money_balance; + out.player_currency_balance = in.player_currency_balance; + out.created_at = r.created_at; + + AssignEtlId(r, PlayerEvent::EventType::MERCHANT_SELL); + etl_queues.merchant_sell.push_back(out); + }}, + { + PlayerEvent::EventType::MERCHANT_PURCHASE, [&](PlayerEventLogsRepository::PlayerEventLogs &r) { + PlayerEvent::MerchantPurchaseEvent in{}; + PlayerEventMerchantPurchaseRepository::PlayerEventMerchantPurchase out{}; + Deserialize(r.event_data, in); + + out.npc_id = in.npc_id; + out.merchant_name = in.merchant_name; + out.merchant_type = in.merchant_type; + out.item_id = in.item_id; + out.item_name = in.item_name; + out.charges = in.charges; + out.cost = in.cost; + out.alternate_currency_id = in.alternate_currency_id; + out.player_money_balance = in.player_money_balance; + out.player_currency_balance = in.player_currency_balance; + out.created_at = r.created_at; + + AssignEtlId(r, PlayerEvent::EventType::MERCHANT_PURCHASE); + etl_queues.merchant_purchase.push_back(out); + }}, + { + PlayerEvent::EventType::NPC_HANDIN, [&](PlayerEventLogsRepository::PlayerEventLogs &r) { + PlayerEvent::HandinEvent in{}; + PlayerEventNpcHandinRepository::PlayerEventNpcHandin out{}; + Deserialize(r.event_data, in); + + out.npc_id = in.npc_id; + out.npc_name = in.npc_name; + out.handin_copper = in.handin_money.copper; + out.handin_silver = in.handin_money.silver; + out.handin_gold = in.handin_money.gold; + out.handin_platinum = in.handin_money.platinum; + out.return_copper = in.return_money.copper; + out.return_silver = in.return_money.silver; + out.return_gold = in.return_money.gold; + out.return_platinum = in.return_money.platinum; + out.is_quest_handin = in.is_quest_handin; + out.created_at = r.created_at; + + AssignEtlId(r, PlayerEvent::EventType::NPC_HANDIN); + etl_queues.npc_handin.push_back(out); + + for (const auto &i: in.handin_items) { + PlayerEventNpcHandinEntriesRepository::PlayerEventNpcHandinEntries entry{}; + entry.player_event_npc_handin_id = r.etl_table_id; + entry.item_id = i.item_id; + entry.charges = i.charges; + entry.type = 1; + etl_queues.npc_handin_entries.push_back(entry); + } + for (const auto &i: in.return_items) { + PlayerEventNpcHandinEntriesRepository::PlayerEventNpcHandinEntries entry{}; + entry.player_event_npc_handin_id = r.etl_table_id; + entry.item_id = i.item_id; + entry.charges = i.charges; + entry.type = 2; + etl_queues.npc_handin_entries.push_back(entry); + } + }}, + { + PlayerEvent::EventType::TRADE, [&](PlayerEventLogsRepository::PlayerEventLogs &r) { + PlayerEvent::TradeEvent in{}; + PlayerEventTradeRepository::PlayerEventTrade out{}; + Deserialize(r.event_data, in); + + out.char1_id = in.character_1_id; + out.char2_id = in.character_2_id; + out.char1_copper = in.character_1_give_money.copper; + out.char1_silver = in.character_1_give_money.silver; + out.char1_gold = in.character_1_give_money.gold; + out.char1_platinum = in.character_1_give_money.platinum; + out.char2_copper = in.character_2_give_money.copper; + out.char2_silver = in.character_2_give_money.silver; + out.char2_gold = in.character_2_give_money.gold; + out.char2_platinum = in.character_2_give_money.platinum; + out.created_at = r.created_at; + + AssignEtlId(r, PlayerEvent::EventType::TRADE); + etl_queues.trade.push_back(out); + + for (const auto &i: in.character_1_give_items) { + PlayerEventTradeEntriesRepository::PlayerEventTradeEntries entry{}; + entry.player_event_trade_id = r.etl_table_id; + entry.char_id = in.character_1_id; + entry.item_id = i.item_id; + entry.charges = i.charges; + entry.slot = i.slot; + etl_queues.trade_entries.push_back(entry); + } + for (const auto &i: in.character_2_give_items) { + PlayerEventTradeEntriesRepository::PlayerEventTradeEntries entry{}; + entry.player_event_trade_id = r.etl_table_id; + entry.char_id = in.character_2_id; + entry.item_id = i.item_id; + entry.charges = i.charges; + entry.slot = i.slot; + etl_queues.trade_entries.push_back(entry); + } + }}, + { + PlayerEvent::EventType::SPEECH, [&](PlayerEventLogsRepository::PlayerEventLogs &r) { + PlayerEvent::PlayerSpeech in{}; + PlayerEventSpeechRepository::PlayerEventSpeech out{}; + Deserialize(r.event_data, in); + + out.from_char_id = in.from; + out.to_char_id = in.to; + out.type = in.type; + out.min_status = in.min_status; + out.message = in.message; + out.guild_id = in.guild_id; + out.created_at = r.created_at; + + AssignEtlId(r, PlayerEvent::EventType::SPEECH); + etl_queues.speech.push_back(out); + }}, + { + PlayerEvent::EventType::KILLED_NPC, [&](PlayerEventLogsRepository::PlayerEventLogs &r) { + PlayerEvent::KilledNPCEvent in{}; + PlayerEventKilledNpcRepository::PlayerEventKilledNpc out{}; + Deserialize(r.event_data, in); + + out.npc_id = in.npc_id; + out.npc_name = in.npc_name; + out.combat_time_seconds = in.combat_time_seconds; + out.total_damage_per_second_taken = in.total_damage_per_second_taken; + out.total_heal_per_second_taken = in.total_heal_per_second_taken; + out.created_at = r.created_at; + + AssignEtlId(r, PlayerEvent::EventType::KILLED_NPC); + etl_queues.killed_npc.push_back(out); + }}, + { + PlayerEvent::EventType::AA_PURCHASE, [&](PlayerEventLogsRepository::PlayerEventLogs &r) { + PlayerEvent::AAPurchasedEvent in{}; + PlayerEventAaPurchaseRepository::PlayerEventAaPurchase out{}; + Deserialize(r.event_data, in); + + out.aa_ability_id = in.aa_id; + out.cost = in.aa_cost; + out.previous_id = in.aa_previous_id; + out.next_id = in.aa_next_id; + out.created_at = r.created_at; + + AssignEtlId(r, PlayerEvent::EventType::AA_PURCHASE); + etl_queues.aa_purchase.push_back(out); + }}, + }; + + // Process the batch queue + for (auto &r: m_record_batch_queue) { + if (m_settings[r.event_type_id].etl_enabled) { + auto it = event_processors.find(static_cast(r.event_type_id)); + if (it != event_processors.end()) { + it->second(r); // Call the appropriate lambda + } + else { + LogPlayerEventsDetail("Non-Implemented ETL routing [{}]", r.event_type_id); + } + } + } + + // Helper to flush and clear queues + auto flush_queue = [&](auto insert_many, auto &queue) { + if (!queue.empty()) { + insert_many(*m_database, queue); + queue.clear(); + } + }; + // flush many PlayerEventLogsRepository::InsertMany(*m_database, m_record_batch_queue); - LogPlayerEventsDetail( + + // flush etl queues + flush_queue(PlayerEventLootItemsRepository::InsertMany, etl_queues.loot_items); + flush_queue(PlayerEventMerchantSellRepository::InsertMany, etl_queues.merchant_sell); + flush_queue(PlayerEventMerchantPurchaseRepository::InsertMany, etl_queues.merchant_purchase); + flush_queue(PlayerEventNpcHandinRepository::InsertMany, etl_queues.npc_handin); + flush_queue(PlayerEventNpcHandinEntriesRepository::InsertMany, etl_queues.npc_handin_entries); + flush_queue(PlayerEventTradeRepository::InsertMany, etl_queues.trade); + flush_queue(PlayerEventTradeEntriesRepository::InsertMany, etl_queues.trade_entries); + flush_queue(PlayerEventSpeechRepository::InsertMany, etl_queues.speech); + flush_queue(PlayerEventKilledNpcRepository::InsertMany, etl_queues.killed_npc); + flush_queue(PlayerEventKilledNamedNpcRepository::InsertMany, etl_queues.killed_named_npc); + flush_queue(PlayerEventKilledRaidNpcRepository::InsertMany, etl_queues.killed_raid_npc); + flush_queue(PlayerEventAaPurchaseRepository::InsertMany, etl_queues.aa_purchase); + + LogPlayerEvents( "Processing batch player event log queue of [{}] took [{}]", m_record_batch_queue.size(), benchmark.elapsed() ); // empty - m_record_batch_queue = {}; + m_record_batch_queue.clear(); m_batch_queue_lock.unlock(); } // adds a player event to the queue -void PlayerEventLogs::AddToQueue(const PlayerEventLogsRepository::PlayerEventLogs &log) +void PlayerEventLogs::AddToQueue(PlayerEventLogsRepository::PlayerEventLogs &log) { m_batch_queue_lock.lock(); m_record_batch_queue.emplace_back(log); @@ -194,7 +509,7 @@ bool PlayerEventLogs::IsEventDiscordEnabled(int32_t event_type_id) } // ensure there is a matching webhook to begin with - if (!LogSys.GetDiscordWebhooks()[m_settings[event_type_id].discord_webhook_id].webhook_url.empty()) { + if (!EQEmuLogSys::Instance()->GetDiscordWebhooks()[m_settings[event_type_id].discord_webhook_id].webhook_url.empty()) { return true; } @@ -214,13 +529,27 @@ std::string PlayerEventLogs::GetDiscordWebhookUrlFromEventType(int32_t event_typ } // ensure there is a matching webhook to begin with - if (!LogSys.GetDiscordWebhooks()[m_settings[event_type_id].discord_webhook_id].webhook_url.empty()) { - return LogSys.GetDiscordWebhooks()[m_settings[event_type_id].discord_webhook_id].webhook_url; + if (!EQEmuLogSys::Instance()->GetDiscordWebhooks()[m_settings[event_type_id].discord_webhook_id].webhook_url.empty()) { + return EQEmuLogSys::Instance()->GetDiscordWebhooks()[m_settings[event_type_id].discord_webhook_id].webhook_url; } return ""; } +void PlayerEventLogs::LoadPlayerEventSettingsFromQS( + const std::vector &settings +) +{ + for (const auto &e : settings) { + if (e.id >= PlayerEvent::MAX || e.id < 0) { + continue; + } + m_settings[e.id] = e; + } + + LogInfo("Applied [{}] player event log settings from QS", settings.size()); +} + // GM_COMMAND | [x] Implemented Formatter // ZONING | [x] Implemented Formatter // AA_GAIN | [x] Implemented Formatter @@ -588,7 +917,7 @@ std::string PlayerEventLogs::GetDiscordPayloadFromEvent(const PlayerEvent::Playe break; } default: { - LogInfo( + LogPlayerEventsDetail( "Player event [{}] ({}) Discord formatter not implemented", e.player_event_log.event_type_name, e.player_event_log.event_type_id @@ -602,7 +931,12 @@ std::string PlayerEventLogs::GetDiscordPayloadFromEvent(const PlayerEvent::Playe // general process function, used in world or QS depending on rule Logging:PlayerEventsQSProcess void PlayerEventLogs::Process() { - if (m_process_batch_events_timer.Check() || m_record_batch_queue.size() >= RuleI(Logging, BatchPlayerEventProcessChunkSize)) { + if (m_database_ping_timer.Check()) { + m_database->ping(); + } + + if (m_process_batch_events_timer.Check() || + m_record_batch_queue.size() >= RuleI(Logging, BatchPlayerEventProcessChunkSize)) { ProcessBatchQueue(); } @@ -613,29 +947,115 @@ void PlayerEventLogs::Process() void PlayerEventLogs::ProcessRetentionTruncation() { - LogPlayerEvents("Running truncation"); + LogPlayerEventsDetail("Running truncation"); - for (int i = PlayerEvent::GM_COMMAND; i != PlayerEvent::MAX; i++) { + // Map of repository-specific deletion functions + std::unordered_map> repository_deleters = { + { + PlayerEvent::LOOT_ITEM, [&](const std::string &condition) { + return PlayerEventLootItemsRepository::DeleteWhere(*m_database, condition); + }}, + { + PlayerEvent::MERCHANT_SELL, [&](const std::string &condition) { + return PlayerEventMerchantSellRepository::DeleteWhere(*m_database, condition); + }}, + { + PlayerEvent::MERCHANT_PURCHASE, [&](const std::string &condition) { + return PlayerEventMerchantPurchaseRepository::DeleteWhere(*m_database, condition); + }}, + { + PlayerEvent::NPC_HANDIN, [&](const std::string &condition) { + uint32 deleted_count = PlayerEventNpcHandinRepository::DeleteWhere(*m_database, condition); + deleted_count += PlayerEventNpcHandinEntriesRepository::DeleteWhere(*m_database, condition); + return deleted_count; + }}, + { + PlayerEvent::TRADE, [&](const std::string &condition) { + uint32 deleted_count = PlayerEventTradeRepository::DeleteWhere(*m_database, condition); + deleted_count += PlayerEventTradeEntriesRepository::DeleteWhere(*m_database, condition); + return deleted_count; + }}, + { + PlayerEvent::SPEECH, [&](const std::string &condition) { + return PlayerEventSpeechRepository::DeleteWhere(*m_database, condition); + }}, + { + PlayerEvent::KILLED_NPC, [&](const std::string &condition) { + return PlayerEventKilledNpcRepository::DeleteWhere(*m_database, condition); + }}, + { + PlayerEvent::KILLED_NAMED_NPC, [&](const std::string &condition) { + return PlayerEventKilledNamedNpcRepository::DeleteWhere(*m_database, condition); + }}, + { + PlayerEvent::KILLED_RAID_NPC, [&](const std::string &condition) { + return PlayerEventKilledRaidNpcRepository::DeleteWhere(*m_database, condition); + }}, + { + PlayerEvent::AA_PURCHASE, [&](const std::string &condition) { + return PlayerEventAaPurchaseRepository::DeleteWhere(*m_database, condition); + }} + }; + + // Group event types by retention interval + std::unordered_map> retention_groups; + for (int i = PlayerEvent::GM_COMMAND; i != PlayerEvent::MAX; i++) { if (m_settings[i].retention_days > 0) { - int deleted_count = PlayerEventLogsRepository::DeleteWhere( - *m_database, - fmt::format( - "event_type_id = {} AND created_at < (NOW() - INTERVAL {} DAY)", - i, - m_settings[i].retention_days - ) - ); + retention_groups[m_settings[i].retention_days].push_back(i); + } + } - if (deleted_count > 0) { - LogInfo( - "Truncated [{}] events of type [{}] ({}) older than [{}] days", - deleted_count, - PlayerEvent::EventName[i], - i, - m_settings[i].retention_days - ); + for (const auto &[retention_days, event_types]: retention_groups) { + std::string condition = fmt::format( + "created_at < (NOW() - INTERVAL {} DAY)", + retention_days + ); + + // Handle ETL deletions for each event type in the group + uint32 total_deleted_count = 0; + for (int event_type_id: event_types) { + if (m_settings[event_type_id].etl_enabled) { + auto it = repository_deleters.find(static_cast(m_settings[event_type_id].id)); + if (it != repository_deleters.end()) { + total_deleted_count += it->second(condition); + } + else { + LogError("Non-Implemented ETL Event Type [{}]", static_cast(m_settings[event_type_id].id)); + } } } + + if (total_deleted_count > 0) { + LogInfo( + "Truncated [{}] ETL events older than [{}] days", + total_deleted_count, + retention_days + ); + } + + // Batch deletion for player_event_logs + std::string event_type_ids = fmt::format( + "({})", + fmt::join(event_types, ", ") + ); + + uint32 deleted_count = PlayerEventLogsRepository::DeleteWhere( + *m_database, + fmt::format( + "event_type_id IN {} AND {}", + event_type_ids, + condition + ) + ); + + if (deleted_count > 0) { + LogInfo( + "Truncated [{}] events of types [{}] older than [{}] days", + deleted_count, + event_type_ids, + retention_days + ); + } } } @@ -707,8 +1127,144 @@ void PlayerEventLogs::SetSettingsDefaults() m_settings[PlayerEvent::PARCEL_RETRIEVE].event_enabled = 1; m_settings[PlayerEvent::PARCEL_DELETE].event_enabled = 1; m_settings[PlayerEvent::BARTER_TRANSACTION].event_enabled = 1; + m_settings[PlayerEvent::EVOLVE_ITEM].event_enabled = 1; + m_settings[PlayerEvent::SPEECH].event_enabled = 0; for (int i = PlayerEvent::GM_COMMAND; i != PlayerEvent::MAX; i++) { m_settings[i].retention_days = RETENTION_DAYS_DEFAULT; } } + +void PlayerEventLogs::LoadEtlIds() +{ + auto e = [&](auto p) -> bool { + for (PlayerEventLogSettingsRepository::PlayerEventLogSettings const &c: m_settings) { + if (c.id == p) { + return c.etl_enabled ? true : false; + } + } + + return false; + }; + + m_etl_settings.clear(); + m_etl_settings = { + { + PlayerEvent::LOOT_ITEM, + { + .enabled = e(PlayerEvent::LOOT_ITEM), + .table_name = "player_event_loot_items", + .next_id = static_cast(m_database->GetNextTableId(PlayerEventLootItemsRepository::TableName())) + } + }, + { + PlayerEvent::MERCHANT_SELL, + { + .enabled = e(PlayerEvent::MERCHANT_SELL), + .table_name = "player_event_merchant_sell", + .next_id = static_cast(m_database->GetNextTableId(PlayerEventMerchantSellRepository::TableName())) + } + }, + { + PlayerEvent::MERCHANT_PURCHASE, + { + .enabled = e(PlayerEvent::MERCHANT_PURCHASE), + .table_name = "player_event_merchant_purchase", + .next_id = static_cast(m_database->GetNextTableId(PlayerEventMerchantPurchaseRepository::TableName())) + } + }, + { + PlayerEvent::NPC_HANDIN, + { + .enabled = e(PlayerEvent::NPC_HANDIN), + .table_name = "player_event_npc_handin", + .next_id = static_cast(m_database->GetNextTableId(PlayerEventNpcHandinRepository::TableName())) + + } + }, + { + PlayerEvent::TRADE, + { + .enabled = e(PlayerEvent::TRADE), + .table_name = "player_event_trade", + .next_id = static_cast(m_database->GetNextTableId(PlayerEventTradeRepository::TableName())) + } + }, + { + PlayerEvent::SPEECH, + { + .enabled = e(PlayerEvent::SPEECH), + .table_name = "player_event_speech", + .next_id = static_cast(m_database->GetNextTableId(PlayerEventSpeechRepository::TableName())) + } + }, + { + PlayerEvent::KILLED_NPC, + { + .enabled = e(PlayerEvent::KILLED_NPC), + .table_name = "player_event_killed_npc", + .next_id = static_cast(m_database->GetNextTableId(PlayerEventKilledNpcRepository::TableName())) + } + }, + { + PlayerEvent::KILLED_NAMED_NPC, + { + .enabled = e(PlayerEvent::KILLED_NAMED_NPC), + .table_name = "player_event_killed_named_npc", + .next_id = static_cast(m_database->GetNextTableId(PlayerEventKilledNamedNpcRepository::TableName())) + } + }, + { + PlayerEvent::KILLED_RAID_NPC, + { + .enabled = e(PlayerEvent::KILLED_RAID_NPC), + .table_name = "player_event_killed_raid_npc", + .next_id = static_cast(m_database->GetNextTableId(PlayerEventKilledRaidNpcRepository::TableName())) + } + }, + { + PlayerEvent::AA_PURCHASE, + { + .enabled = e(PlayerEvent::AA_PURCHASE), + .table_name = "player_event_aa_purchase", + .next_id = static_cast(m_database->GetNextTableId(PlayerEventAaPurchaseRepository::TableName())) + } + } + }; + + for (auto &e: m_etl_settings) { + LogPlayerEventsDetail( + "ETL Settings [{}] Enabled [{}] Table [{}] NextId [{}]", + PlayerEvent::EventName[e.first], + e.second.enabled, + e.second.table_name, + e.second.next_id + ); + } +} + +bool PlayerEventLogs::LoadDatabaseConnection() +{ + const auto c = EQEmuConfig::get(); + + LogInfo( + "Connecting to MySQL for PlayerEvents [{}]@[{}]:[{}]", + c->DatabaseUsername.c_str(), + c->DatabaseHost.c_str(), + c->DatabasePort + ); + + if (!player_event_database.Connect( + c->DatabaseHost.c_str(), + c->DatabaseUsername.c_str(), + c->DatabasePassword.c_str(), + c->DatabaseDB.c_str(), + c->DatabasePort + )) { + LogError("Cannot continue without a database connection for player events."); + return false; + } + + SetDatabase(&player_event_database); + return true; +} diff --git a/common/events/player_event_logs.h b/common/events/player_event_logs.h index 814f075ba..3c22de4c0 100644 --- a/common/events/player_event_logs.h +++ b/common/events/player_event_logs.h @@ -1,19 +1,38 @@ #ifndef EQEMU_PLAYER_EVENT_LOGS_H #define EQEMU_PLAYER_EVENT_LOGS_H -#include "../repositories/player_event_log_settings_repository.h" -#include "player_events.h" -#include "../servertalk.h" -#include "../repositories/player_event_logs_repository.h" -#include "../timer.h" -#include "../json/json_archive_single_line.h" #include #include +#include "../json/json_archive_single_line.h" +#include "../servertalk.h" +#include "../timer.h" +#include "../eqemu_config.h" + +#include "../repositories/player_event_log_settings_repository.h" +#include "../repositories/player_event_logs_repository.h" +#include "../repositories/player_event_loot_items_repository.h" +#include "../repositories/player_event_merchant_purchase_repository.h" +#include "../repositories/player_event_merchant_sell_repository.h" +#include "../repositories/player_event_npc_handin_repository.h" +#include "../repositories/player_event_npc_handin_entries_repository.h" +#include "../repositories/player_event_trade_repository.h" +#include "../repositories/player_event_trade_entries_repository.h" +#include "../repositories/player_event_speech_repository.h" +#include "../repositories/player_event_killed_npc_repository.h" +#include "../repositories/player_event_killed_named_npc_repository.h" +#include "../repositories/player_event_killed_raid_npc_repository.h" +#include "../repositories/player_event_aa_purchase_repository.h" + + class PlayerEventLogs { public: + Database player_event_database{}; + void Init(); + bool LoadDatabaseConnection(); void ReloadSettings(); + void LoadEtlIds(); PlayerEventLogs *SetDatabase(Database *db); bool ValidateDatabaseConnection(); bool IsEventEnabled(PlayerEvent::EventType event); @@ -21,7 +40,7 @@ public: void Process(); // batch queue - void AddToQueue(const PlayerEventLogsRepository::PlayerEventLogs &logs); + void AddToQueue(PlayerEventLogsRepository::PlayerEventLogs &logs); // main event record generic function // can ingest any struct event types @@ -54,12 +73,42 @@ public: return BuildPlayerEventPacket(c); } - [[nodiscard]] const PlayerEventLogSettingsRepository::PlayerEventLogSettings *GetSettings() const; - bool IsEventDiscordEnabled(int32_t event_type_id); - std::string GetDiscordWebhookUrlFromEventType(int32_t event_type_id); + [[nodiscard]] const PlayerEventLogSettingsRepository::PlayerEventLogSettings * GetSettings() const; + bool IsEventDiscordEnabled(int32_t event_type_id); + std::string GetDiscordWebhookUrlFromEventType(int32_t event_type_id); + + void LoadPlayerEventSettingsFromQS(const std::vector& settings); static std::string GetDiscordPayloadFromEvent(const PlayerEvent::PlayerEventContainer &e); + + struct EtlQueues { + std::vector loot_items; + std::vector merchant_purchase; + std::vector merchant_sell; + std::vector npc_handin; + std::vector npc_handin_entries; + std::vector trade; + std::vector trade_entries; + std::vector speech; + std::vector killed_npc; + std::vector killed_named_npc; + std::vector killed_raid_npc; + std::vector aa_purchase; + }; + + static PlayerEventLogs* Instance() + { + static PlayerEventLogs instance; + return &instance; + } + private: + struct EtlSettings { + bool enabled; + std::string table_name; + int64 next_id; + }; + Database *m_database; // reference to database PlayerEventLogSettingsRepository::PlayerEventLogSettings m_settings[PlayerEvent::EventType::MAX]{}; @@ -69,7 +118,10 @@ private: static std::unique_ptr BuildPlayerEventPacket(const PlayerEvent::PlayerEventContainer &e); + std::map m_etl_settings{}; + // timers + Timer m_database_ping_timer; // database ping timer Timer m_process_batch_events_timer; // events processing timer Timer m_process_retention_truncation_timer; // timer for truncating events based on retention settings @@ -78,8 +130,9 @@ private: void ProcessBatchQueue(); void ProcessRetentionTruncation(); void SetSettingsDefaults(); + +public: + std::map &GetEtlSettings() { return m_etl_settings;} }; -extern PlayerEventLogs player_event_logs; - #endif //EQEMU_PLAYER_EVENT_LOGS_H diff --git a/common/events/player_events.h b/common/events/player_events.h index 6384f8e0e..02ea2a26b 100644 --- a/common/events/player_events.h +++ b/common/events/player_events.h @@ -4,10 +4,19 @@ #include #include #include "../types.h" +#include "../rulesys.h" #include "../repositories/player_event_logs_repository.h" -namespace PlayerEvent { +#define CEREAL_NVP_IF_NONZERO(ar, name) \ +if ((name) != 0) ar(cereal::make_nvp(#name, name)) +#define CEREAL_NVP_IF_NOT_EMPTY(ar, name) \ +if (!(name).empty()) ar(cereal::make_nvp(#name, name)) + +#define CEREAL_NVP_IF_TRUE(ar, name) \ +if ((name)) ar(cereal::make_nvp(#name, name)) + +namespace PlayerEvent { enum EventType { GM_COMMAND = 1, ZONING, @@ -25,10 +34,10 @@ namespace PlayerEvent { LOOT_ITEM, MERCHANT_PURCHASE, MERCHANT_SELL, - GROUP_JOIN, // unimplemented + GROUP_JOIN, // unimplemented GROUP_LEAVE, // unimplemented - RAID_JOIN, // unimplemented - RAID_LEAVE, // unimplemented + RAID_JOIN, // unimplemented + RAID_LEAVE, // unimplemented GROUNDSPAWN_PICKUP, NPC_HANDIN, SKILL_UP, @@ -44,12 +53,12 @@ namespace PlayerEvent { COMBINE_SUCCESS, DROPPED_ITEM, SPLIT_MONEY, - DZ_JOIN, // unimplemented + DZ_JOIN, // unimplemented DZ_LEAVE, // unimplemented TRADER_PURCHASE, TRADER_SELL, BANDOLIER_CREATE, // unimplemented - BANDOLIER_SWAP, // unimplemented + BANDOLIER_SWAP, // unimplemented DISCOVER_ITEM, POSSIBLE_HACK, KILLED_NPC, @@ -62,6 +71,11 @@ namespace PlayerEvent { PARCEL_RETRIEVE, PARCEL_DELETE, BARTER_TRANSACTION, + SPEECH, + EVOLVE_ITEM, + GUILD_BANK_DEPOSIT, + GUILD_BANK_WITHDRAWAL, + GUILD_BANK_MOVE_TO_BANK_AREA, MAX // dont remove }; @@ -70,7 +84,7 @@ namespace PlayerEvent { // If event is unimplemented just tag (Unimplemented) in the name // Events don't get saved to the database if unimplemented or deprecated // Events tagged as deprecated will get automatically removed - static const char *EventName[EventType::MAX] = { + static const char* EventName[EventType::MAX] = { "None", "GM Command", "Zoning", @@ -124,7 +138,12 @@ namespace PlayerEvent { "Parcel Item Sent", "Parcel Item Retrieved", "Parcel Prune Routine", - "Barter Transaction" + "Barter Transaction", + "Player Speech", + "Evolve Item Update", + "Guild Bank Item Deposit", + "Guild Bank Item Withdrawal", + "Guild Bank Move From Deposit Area to Bank Area" }; // Generic struct used by all events @@ -145,8 +164,8 @@ namespace PlayerEvent { float heading; // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) { ar( CEREAL_NVP(account_id), @@ -174,8 +193,8 @@ namespace PlayerEvent { PlayerEventLogsRepository::PlayerEventLogs player_event_log; // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) { ar( CEREAL_NVP(player_event), @@ -189,8 +208,8 @@ namespace PlayerEvent { std::string noop; // noop, gets discard upstream // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) { ar( CEREAL_NVP(noop) @@ -204,86 +223,154 @@ namespace PlayerEvent { std::string item_name; uint16 to_slot; int16 charges; - uint32 aug1; - uint32 aug2; - uint32 aug3; - uint32 aug4; - uint32 aug5; - uint32 aug6; + uint32 augment_1_id; + uint32 augment_2_id; + uint32 augment_3_id; + uint32 augment_4_id; + uint32 augment_5_id; + uint32 augment_6_id; bool attuned; // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) + { + if constexpr (Archive::is_saving::value) { + save(ar); + } + else { + load(ar); + } + } + + template + void save(Archive& ar) const + { + ar( + CEREAL_NVP(item_id), + CEREAL_NVP(item_name), + CEREAL_NVP(to_slot), + CEREAL_NVP(charges) + ); + + CEREAL_NVP_IF_NONZERO(ar, augment_1_id); + CEREAL_NVP_IF_NONZERO(ar, augment_2_id); + CEREAL_NVP_IF_NONZERO(ar, augment_3_id); + CEREAL_NVP_IF_NONZERO(ar, augment_4_id); + CEREAL_NVP_IF_NONZERO(ar, augment_5_id); + CEREAL_NVP_IF_NONZERO(ar, augment_6_id); + CEREAL_NVP_IF_TRUE(ar, attuned); + } + + template + void load(Archive& ar) { ar( CEREAL_NVP(item_id), CEREAL_NVP(item_name), CEREAL_NVP(to_slot), CEREAL_NVP(charges), - CEREAL_NVP(aug1), - CEREAL_NVP(aug2), - CEREAL_NVP(aug3), - CEREAL_NVP(aug4), - CEREAL_NVP(aug5), - CEREAL_NVP(aug6), + CEREAL_NVP(augment_1_id), + CEREAL_NVP(augment_2_id), + CEREAL_NVP(augment_3_id), + CEREAL_NVP(augment_4_id), + CEREAL_NVP(augment_5_id), + CEREAL_NVP(augment_6_id), CEREAL_NVP(attuned) ); } }; // used in Trade event - struct TradeItem { - int64 item_id; - std::string item_name; - int32 slot; - - // cereal - template - void serialize(Archive &ar) - { - ar( - CEREAL_NVP(item_id), - CEREAL_NVP(item_name), - CEREAL_NVP(slot) - ); - } - }; + // struct TradeItem { + // int64 item_id; + // std::string item_name; + // int32 slot; + // + // // cereal + // template + // void serialize(Archive &ar) + // { + // ar( + // CEREAL_NVP(item_id), + // CEREAL_NVP(item_name), + // CEREAL_NVP(slot) + // ); + // } + // }; // used in Trade event class TradeItemEntry { public: uint16 slot; uint32 item_id; + uint32 augment_1_id; + std::string augment_1_name; + uint32 augment_2_id; + std::string augment_2_name; + uint32 augment_3_id; + std::string augment_3_name; + uint32 augment_4_id; + std::string augment_4_name; + uint32 augment_5_id; + std::string augment_5_name; + uint32 augment_6_id; + std::string augment_6_name; std::string item_name; uint16 charges; - uint32 aug_1_item_id; - std::string aug_1_item_name; - uint32 aug_2_item_id; - std::string aug_2_item_name; - uint32 aug_3_item_id; - std::string aug_3_item_name; - uint32 aug_4_item_id; - std::string aug_4_item_name; - uint32 aug_5_item_id; - std::string aug_5_item_name; - uint32 aug_6_item_id; - std::string aug_6_item_name; bool in_bag; + // cereal - template - void serialize(Archive &ar) + template + void save(Archive& ar) const + { + ar( + CEREAL_NVP(slot), + CEREAL_NVP(item_id) + ); + + CEREAL_NVP_IF_NONZERO(ar, augment_1_id); + CEREAL_NVP_IF_NOT_EMPTY(ar, augment_1_name); + CEREAL_NVP_IF_NONZERO(ar, augment_2_id); + CEREAL_NVP_IF_NOT_EMPTY(ar, augment_2_name); + CEREAL_NVP_IF_NONZERO(ar, augment_3_id); + CEREAL_NVP_IF_NOT_EMPTY(ar, augment_3_name); + CEREAL_NVP_IF_NONZERO(ar, augment_4_id); + CEREAL_NVP_IF_NOT_EMPTY(ar, augment_4_name); + CEREAL_NVP_IF_NONZERO(ar, augment_5_id); + CEREAL_NVP_IF_NOT_EMPTY(ar, augment_5_name); + CEREAL_NVP_IF_NONZERO(ar, augment_6_id); + CEREAL_NVP_IF_NOT_EMPTY(ar, augment_6_name); + + ar( + CEREAL_NVP(item_name), + CEREAL_NVP(charges) + ); + + CEREAL_NVP_IF_TRUE(ar, in_bag); + } + + template + void load(Archive& ar) { ar( CEREAL_NVP(slot), CEREAL_NVP(item_id), + CEREAL_NVP(augment_1_id), + CEREAL_NVP(augment_1_name), + CEREAL_NVP(augment_2_id), + CEREAL_NVP(augment_2_name), + CEREAL_NVP(augment_3_id), + CEREAL_NVP(augment_3_name), + CEREAL_NVP(augment_4_id), + CEREAL_NVP(augment_4_name), + CEREAL_NVP(augment_5_id), + CEREAL_NVP(augment_5_name), + CEREAL_NVP(augment_6_id), + CEREAL_NVP(augment_6_name), + CEREAL_NVP(item_name), CEREAL_NVP(charges), - CEREAL_NVP(aug_1_item_id), - CEREAL_NVP(aug_2_item_id), - CEREAL_NVP(aug_3_item_id), - CEREAL_NVP(aug_4_item_id), - CEREAL_NVP(aug_5_item_id), CEREAL_NVP(in_bag) ); } @@ -299,8 +386,8 @@ namespace PlayerEvent { int32 copper; // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) { ar( CEREAL_NVP(platinum), @@ -322,8 +409,8 @@ namespace PlayerEvent { std::vector character_2_give_items; // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) { ar( CEREAL_NVP(character_1_id), @@ -343,8 +430,8 @@ namespace PlayerEvent { std::string target; // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) { ar( CEREAL_NVP(message), @@ -366,8 +453,8 @@ namespace PlayerEvent { int32 to_instance_version; // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) { ar( CEREAL_NVP(from_zone_long_name), @@ -388,8 +475,8 @@ namespace PlayerEvent { uint32 aa_gained; // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) { ar(CEREAL_NVP(aa_gained)); } @@ -397,13 +484,13 @@ namespace PlayerEvent { struct AAPurchasedEvent { uint32 aa_id; - int32 aa_cost; - int32 aa_previous_id; - int32 aa_next_id; + int32 aa_cost; + int32 aa_previous_id; + int32 aa_next_id; // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) { ar( CEREAL_NVP(aa_id), @@ -416,14 +503,51 @@ namespace PlayerEvent { struct ForageSuccessEvent { uint32 item_id; + uint32 augment_1_id; + uint32 augment_2_id; + uint32 augment_3_id; + uint32 augment_4_id; + uint32 augment_5_id; + uint32 augment_6_id; std::string item_name; - // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) + { + if constexpr (Archive::is_saving::value) { + save(ar); + } + else { + load(ar); + } + } + + template + void save(Archive& ar) const + { + ar(CEREAL_NVP(item_id)); + + CEREAL_NVP_IF_NONZERO(ar, augment_1_id); + CEREAL_NVP_IF_NONZERO(ar, augment_2_id); + CEREAL_NVP_IF_NONZERO(ar, augment_3_id); + CEREAL_NVP_IF_NONZERO(ar, augment_4_id); + CEREAL_NVP_IF_NONZERO(ar, augment_5_id); + CEREAL_NVP_IF_NONZERO(ar, augment_6_id); + + ar(CEREAL_NVP(item_name)); + } + + template + void load(Archive& ar) { ar( CEREAL_NVP(item_id), + CEREAL_NVP(augment_1_id), + CEREAL_NVP(augment_2_id), + CEREAL_NVP(augment_3_id), + CEREAL_NVP(augment_4_id), + CEREAL_NVP(augment_5_id), + CEREAL_NVP(augment_6_id), CEREAL_NVP(item_name) ); } @@ -431,14 +555,51 @@ namespace PlayerEvent { struct FishSuccessEvent { uint32 item_id; + uint32 augment_1_id; + uint32 augment_2_id; + uint32 augment_3_id; + uint32 augment_4_id; + uint32 augment_5_id; + uint32 augment_6_id; std::string item_name; - // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) + { + if constexpr (Archive::is_saving::value) { + save(ar); + } + else { + load(ar); + } + } + + template + void save(Archive& ar) const + { + ar(CEREAL_NVP(item_id)); + + CEREAL_NVP_IF_NONZERO(ar, augment_1_id); + CEREAL_NVP_IF_NONZERO(ar, augment_2_id); + CEREAL_NVP_IF_NONZERO(ar, augment_3_id); + CEREAL_NVP_IF_NONZERO(ar, augment_4_id); + CEREAL_NVP_IF_NONZERO(ar, augment_5_id); + CEREAL_NVP_IF_NONZERO(ar, augment_6_id); + + ar(CEREAL_NVP(item_name)); + } + + template + void load(Archive& ar) { ar( CEREAL_NVP(item_id), + CEREAL_NVP(augment_1_id), + CEREAL_NVP(augment_2_id), + CEREAL_NVP(augment_3_id), + CEREAL_NVP(augment_4_id), + CEREAL_NVP(augment_5_id), + CEREAL_NVP(augment_6_id), CEREAL_NVP(item_name) ); } @@ -448,29 +609,76 @@ namespace PlayerEvent { uint32 item_id; std::string item_name; int16 charges; + uint32 augment_1_id; + uint32 augment_2_id; + uint32 augment_3_id; + uint32 augment_4_id; + uint32 augment_5_id; + uint32 augment_6_id; + bool attuned; std::string reason; - // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) + { + if constexpr (Archive::is_saving::value) { + save(ar); + } + else { + load(ar); + } + } + + template + void save(Archive& ar) const { ar( CEREAL_NVP(item_id), CEREAL_NVP(item_name), - CEREAL_NVP(reason), CEREAL_NVP(charges) ); + + CEREAL_NVP_IF_NONZERO(ar, augment_1_id); + CEREAL_NVP_IF_NONZERO(ar, augment_2_id); + CEREAL_NVP_IF_NONZERO(ar, augment_3_id); + CEREAL_NVP_IF_NONZERO(ar, augment_4_id); + CEREAL_NVP_IF_NONZERO(ar, augment_5_id); + CEREAL_NVP_IF_NONZERO(ar, augment_6_id); + + ar( + CEREAL_NVP(attuned), + CEREAL_NVP(reason) + ); + } + + template + void load(Archive& ar) + { + ar( + CEREAL_NVP(item_id), + CEREAL_NVP(item_name), + CEREAL_NVP(charges), + CEREAL_NVP(augment_1_id), + CEREAL_NVP(augment_2_id), + CEREAL_NVP(augment_3_id), + CEREAL_NVP(augment_4_id), + CEREAL_NVP(augment_5_id), + CEREAL_NVP(augment_6_id), + CEREAL_NVP(attuned), + CEREAL_NVP(reason) + ); } }; + struct LevelGainedEvent { uint32 from_level; uint8 to_level; int levels_gained; // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) { ar( CEREAL_NVP(from_level), @@ -486,8 +694,8 @@ namespace PlayerEvent { int levels_lost; // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) { ar( CEREAL_NVP(from_level), @@ -501,17 +709,62 @@ namespace PlayerEvent { uint32 item_id; std::string item_name; int16 charges; + uint32 augment_1_id; + uint32 augment_2_id; + uint32 augment_3_id; + uint32 augment_4_id; + uint32 augment_5_id; + uint32 augment_6_id; uint32 npc_id; std::string corpse_name; // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) + { + if constexpr (Archive::is_saving::value) { + save(ar); + } + else { + load(ar); + } + } + + template + void save(Archive& ar) const + { + ar( + CEREAL_NVP(item_id), + CEREAL_NVP(item_name), + CEREAL_NVP(charges) + ); + + CEREAL_NVP_IF_NONZERO(ar, augment_1_id); + CEREAL_NVP_IF_NONZERO(ar, augment_2_id); + CEREAL_NVP_IF_NONZERO(ar, augment_3_id); + CEREAL_NVP_IF_NONZERO(ar, augment_4_id); + CEREAL_NVP_IF_NONZERO(ar, augment_5_id); + CEREAL_NVP_IF_NONZERO(ar, augment_6_id); + + ar( + CEREAL_NVP(npc_id), + CEREAL_NVP(corpse_name) + ); + } + + template + void load(Archive& ar) { ar( CEREAL_NVP(item_id), CEREAL_NVP(item_name), CEREAL_NVP(charges), + CEREAL_NVP(augment_1_id), + CEREAL_NVP(augment_2_id), + CEREAL_NVP(augment_3_id), + CEREAL_NVP(augment_4_id), + CEREAL_NVP(augment_5_id), + CEREAL_NVP(augment_6_id), CEREAL_NVP(npc_id), CEREAL_NVP(corpse_name) ); @@ -531,8 +784,8 @@ namespace PlayerEvent { uint64 player_currency_balance; // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) { ar( CEREAL_NVP(npc_id), @@ -562,8 +815,8 @@ namespace PlayerEvent { uint64 player_currency_balance; // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) { ar( CEREAL_NVP(npc_id), @@ -587,8 +840,8 @@ namespace PlayerEvent { std::string against_who; // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) { ar( CEREAL_NVP(skill_id), @@ -606,8 +859,8 @@ namespace PlayerEvent { std::string task_name; // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) { ar( CEREAL_NVP(npc_id), @@ -625,8 +878,8 @@ namespace PlayerEvent { uint32 done_count; // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) { ar( CEREAL_NVP(task_id), @@ -644,8 +897,8 @@ namespace PlayerEvent { uint32 done_count; // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) { ar( CEREAL_NVP(task_id), @@ -661,8 +914,8 @@ namespace PlayerEvent { std::string item_name; // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) { ar( CEREAL_NVP(item_id), @@ -676,8 +929,8 @@ namespace PlayerEvent { std::string target; // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) { ar( CEREAL_NVP(message), @@ -692,8 +945,8 @@ namespace PlayerEvent { uint32 spell_id; // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) { ar( CEREAL_NVP(resurrecter_name), @@ -710,8 +963,8 @@ namespace PlayerEvent { uint32 tradeskill_id; // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) { ar( CEREAL_NVP(recipe_id), @@ -724,16 +977,58 @@ namespace PlayerEvent { struct DroppedItemEvent { uint32 item_id; + uint32 augment_1_id; + uint32 augment_2_id; + uint32 augment_3_id; + uint32 augment_4_id; + uint32 augment_5_id; + uint32 augment_6_id; std::string item_name; int16 slot_id; uint32 charges; // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) + { + if constexpr (Archive::is_saving::value) { + save(ar); + } + else { + load(ar); + } + } + + template + void save(Archive& ar) const + { + ar(CEREAL_NVP(item_id)); + + CEREAL_NVP_IF_NONZERO(ar, augment_1_id); + CEREAL_NVP_IF_NONZERO(ar, augment_2_id); + CEREAL_NVP_IF_NONZERO(ar, augment_3_id); + CEREAL_NVP_IF_NONZERO(ar, augment_4_id); + CEREAL_NVP_IF_NONZERO(ar, augment_5_id); + CEREAL_NVP_IF_NONZERO(ar, augment_6_id); + + ar( + CEREAL_NVP(item_name), + CEREAL_NVP(slot_id), + CEREAL_NVP(charges) + ); + } + + template + void load(Archive& ar) { ar( CEREAL_NVP(item_id), + CEREAL_NVP(augment_1_id), + CEREAL_NVP(augment_2_id), + CEREAL_NVP(augment_3_id), + CEREAL_NVP(augment_4_id), + CEREAL_NVP(augment_5_id), + CEREAL_NVP(augment_6_id), CEREAL_NVP(item_name), CEREAL_NVP(slot_id), CEREAL_NVP(charges) @@ -751,8 +1046,8 @@ namespace PlayerEvent { std::string skill_name; // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) { ar( CEREAL_NVP(killer_id), @@ -774,8 +1069,8 @@ namespace PlayerEvent { uint64 player_money_balance; // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) { ar( CEREAL_NVP(copper), @@ -789,25 +1084,73 @@ namespace PlayerEvent { struct TraderPurchaseEvent { uint32 item_id; + uint32 augment_1_id; + uint32 augment_2_id; + uint32 augment_3_id; + uint32 augment_4_id; + uint32 augment_5_id; + uint32 augment_6_id; std::string item_name; uint32 trader_id; std::string trader_name; uint32 price; - uint32 charges; - uint32 total_cost; + uint32 quantity; + int32 charges; + uint64 total_cost; uint64 player_money_balance; - // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) { + if constexpr (Archive::is_saving::value) { + save(ar); + } + else { + load(ar); + } + } + + template + void save(Archive& ar) const + { + ar(CEREAL_NVP(item_id)); + + CEREAL_NVP_IF_NONZERO(ar, augment_1_id); + CEREAL_NVP_IF_NONZERO(ar, augment_2_id); + CEREAL_NVP_IF_NONZERO(ar, augment_3_id); + CEREAL_NVP_IF_NONZERO(ar, augment_4_id); + CEREAL_NVP_IF_NONZERO(ar, augment_5_id); + CEREAL_NVP_IF_NONZERO(ar, augment_6_id); + ar( - CEREAL_NVP(item_id), CEREAL_NVP(item_name), CEREAL_NVP(trader_id), CEREAL_NVP(trader_name), CEREAL_NVP(price), + CEREAL_NVP(quantity), + CEREAL_NVP(charges), + CEREAL_NVP(total_cost), + CEREAL_NVP(player_money_balance) + ); + } + + template + void load(Archive& ar) + { + ar( + CEREAL_NVP(item_id), + CEREAL_NVP(augment_1_id), + CEREAL_NVP(augment_2_id), + CEREAL_NVP(augment_3_id), + CEREAL_NVP(augment_4_id), + CEREAL_NVP(augment_5_id), + CEREAL_NVP(augment_6_id), + CEREAL_NVP(item_name), + CEREAL_NVP(trader_id), + CEREAL_NVP(trader_name), + CEREAL_NVP(price), + CEREAL_NVP(quantity), CEREAL_NVP(charges), CEREAL_NVP(total_cost), CEREAL_NVP(player_money_balance) @@ -817,25 +1160,72 @@ namespace PlayerEvent { struct TraderSellEvent { uint32 item_id; + uint32 augment_1_id; + uint32 augment_2_id; + uint32 augment_3_id; + uint32 augment_4_id; + uint32 augment_5_id; + uint32 augment_6_id; std::string item_name; uint32 buyer_id; std::string buyer_name; uint32 price; - uint32 charges; - uint32 total_cost; + uint32 quantity; + int32 charges; + uint64 total_cost; uint64 player_money_balance; - - // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) { + if constexpr (Archive::is_saving::value) { + save(ar); + } + else { + load(ar); + } + } + + template + void save(Archive& ar) const + { + ar(CEREAL_NVP(item_id)); + + CEREAL_NVP_IF_NONZERO(ar, augment_1_id); + CEREAL_NVP_IF_NONZERO(ar, augment_2_id); + CEREAL_NVP_IF_NONZERO(ar, augment_3_id); + CEREAL_NVP_IF_NONZERO(ar, augment_4_id); + CEREAL_NVP_IF_NONZERO(ar, augment_5_id); + CEREAL_NVP_IF_NONZERO(ar, augment_6_id); + ar( - CEREAL_NVP(item_id), CEREAL_NVP(item_name), CEREAL_NVP(buyer_id), CEREAL_NVP(buyer_name), CEREAL_NVP(price), + CEREAL_NVP(quantity), + CEREAL_NVP(charges), + CEREAL_NVP(total_cost), + CEREAL_NVP(player_money_balance) + ); + } + + template + void load(Archive& ar) + { + ar( + CEREAL_NVP(item_id), + CEREAL_NVP(augment_1_id), + CEREAL_NVP(augment_2_id), + CEREAL_NVP(augment_3_id), + CEREAL_NVP(augment_4_id), + CEREAL_NVP(augment_5_id), + CEREAL_NVP(augment_6_id), + CEREAL_NVP(item_name), + CEREAL_NVP(buyer_id), + CEREAL_NVP(buyer_name), + CEREAL_NVP(price), + CEREAL_NVP(quantity), CEREAL_NVP(charges), CEREAL_NVP(total_cost), CEREAL_NVP(player_money_balance) @@ -848,8 +1238,8 @@ namespace PlayerEvent { std::string item_name; // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) { ar( CEREAL_NVP(item_id), @@ -868,8 +1258,8 @@ namespace PlayerEvent { bool attuned; // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) { ar( CEREAL_NVP(item_id), @@ -890,8 +1280,8 @@ namespace PlayerEvent { uint32 platinum; // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) { ar( CEREAL_NVP(copper), @@ -912,8 +1302,8 @@ namespace PlayerEvent { bool is_quest_handin; // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) { ar( CEREAL_NVP(npc_id), @@ -931,8 +1321,8 @@ namespace PlayerEvent { std::string message; // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) { ar( CEREAL_NVP(message) @@ -948,8 +1338,8 @@ namespace PlayerEvent { uint64 total_heal_per_second_taken; // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) { ar( CEREAL_NVP(npc_id), @@ -962,27 +1352,70 @@ namespace PlayerEvent { }; struct GuildTributeDonateItem { - uint32 item_id; - uint32 guild_favor; + uint32 item_id; + uint32 augment_1_id; + uint32 augment_2_id; + uint32 augment_3_id; + uint32 augment_4_id; + uint32 augment_5_id; + uint32 augment_6_id; + int16 charges; + bool attuned; + uint32 guild_favor; - // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) + { + if constexpr (Archive::is_saving::value) { + save(ar); + } + else { + load(ar); + } + } + + template + void save(Archive& ar) const + { + ar(CEREAL_NVP(item_id)); + + CEREAL_NVP_IF_NONZERO(ar, augment_1_id); + CEREAL_NVP_IF_NONZERO(ar, augment_2_id); + CEREAL_NVP_IF_NONZERO(ar, augment_3_id); + CEREAL_NVP_IF_NONZERO(ar, augment_4_id); + CEREAL_NVP_IF_NONZERO(ar, augment_5_id); + CEREAL_NVP_IF_NONZERO(ar, augment_6_id); + CEREAL_NVP_IF_NONZERO(ar, charges); + CEREAL_NVP_IF_TRUE(ar, attuned); + + ar(CEREAL_NVP(guild_favor)); + } + + template + void load(Archive& ar) { ar( CEREAL_NVP(item_id), + CEREAL_NVP(augment_1_id), + CEREAL_NVP(augment_2_id), + CEREAL_NVP(augment_3_id), + CEREAL_NVP(augment_4_id), + CEREAL_NVP(augment_5_id), + CEREAL_NVP(augment_6_id), + CEREAL_NVP(charges), + CEREAL_NVP(attuned), CEREAL_NVP(guild_favor) ); } }; struct GuildTributeDonatePlat { - uint32 plat; - uint32 guild_favor; + uint32 plat; + uint32 guild_favor; // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) { ar( CEREAL_NVP(plat), @@ -993,100 +1426,193 @@ namespace PlayerEvent { struct ParcelRetrieve { uint32 item_id; + uint32 augment_1_id; + uint32 augment_2_id; + uint32 augment_3_id; + uint32 augment_4_id; + uint32 augment_5_id; + uint32 augment_6_id; uint32 quantity; std::string from_player_name; uint32 sent_date; - uint32 aug_slot_1; - uint32 aug_slot_2; - uint32 aug_slot_3; - uint32 aug_slot_4; - uint32 aug_slot_5; - uint32 aug_slot_6; - // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) + { + if constexpr (Archive::is_saving::value) { + save(ar); + } + else { + load(ar); + } + } + + template + void save(Archive& ar) const + { + ar(CEREAL_NVP(item_id)); + + CEREAL_NVP_IF_NONZERO(ar, augment_1_id); + CEREAL_NVP_IF_NONZERO(ar, augment_2_id); + CEREAL_NVP_IF_NONZERO(ar, augment_3_id); + CEREAL_NVP_IF_NONZERO(ar, augment_4_id); + CEREAL_NVP_IF_NONZERO(ar, augment_5_id); + CEREAL_NVP_IF_NONZERO(ar, augment_6_id); + + ar( + CEREAL_NVP(quantity), + CEREAL_NVP(from_player_name), + CEREAL_NVP(sent_date) + ); + } + + template + void load(Archive& ar) { ar( CEREAL_NVP(item_id), + CEREAL_NVP(augment_1_id), + CEREAL_NVP(augment_2_id), + CEREAL_NVP(augment_3_id), + CEREAL_NVP(augment_4_id), + CEREAL_NVP(augment_5_id), + CEREAL_NVP(augment_6_id), CEREAL_NVP(quantity), CEREAL_NVP(from_player_name), - CEREAL_NVP(sent_date), - CEREAL_NVP(aug_slot_1), - CEREAL_NVP(aug_slot_2), - CEREAL_NVP(aug_slot_3), - CEREAL_NVP(aug_slot_4), - CEREAL_NVP(aug_slot_5), - CEREAL_NVP(aug_slot_6) + CEREAL_NVP(sent_date) ); } }; struct ParcelSend { uint32 item_id; + uint32 augment_1_id; + uint32 augment_2_id; + uint32 augment_3_id; + uint32 augment_4_id; + uint32 augment_5_id; + uint32 augment_6_id; uint32 quantity; + int32 charges; std::string from_player_name; std::string to_player_name; uint32 sent_date; - uint32 aug_slot_1; - uint32 aug_slot_2; - uint32 aug_slot_3; - uint32 aug_slot_4; - uint32 aug_slot_5; - uint32 aug_slot_6; - // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) { + if constexpr (Archive::is_saving::value) { + save(ar); + } + else { + load(ar); + } + } + + template + void save(Archive& ar) const + { + ar(CEREAL_NVP(item_id)); + + CEREAL_NVP_IF_NONZERO(ar, augment_1_id); + CEREAL_NVP_IF_NONZERO(ar, augment_2_id); + CEREAL_NVP_IF_NONZERO(ar, augment_3_id); + CEREAL_NVP_IF_NONZERO(ar, augment_4_id); + CEREAL_NVP_IF_NONZERO(ar, augment_5_id); + CEREAL_NVP_IF_NONZERO(ar, augment_6_id); + CEREAL_NVP_IF_NONZERO(ar, charges); + ar( - CEREAL_NVP(item_id), CEREAL_NVP(quantity), CEREAL_NVP(from_player_name), CEREAL_NVP(to_player_name), - CEREAL_NVP(sent_date), - CEREAL_NVP(aug_slot_1), - CEREAL_NVP(aug_slot_2), - CEREAL_NVP(aug_slot_3), - CEREAL_NVP(aug_slot_4), - CEREAL_NVP(aug_slot_5), - CEREAL_NVP(aug_slot_6) + CEREAL_NVP(sent_date) + ); + } + + template + void load(Archive& ar) + { + ar( + CEREAL_NVP(item_id), + CEREAL_NVP(augment_1_id), + CEREAL_NVP(augment_2_id), + CEREAL_NVP(augment_3_id), + CEREAL_NVP(augment_4_id), + CEREAL_NVP(augment_5_id), + CEREAL_NVP(augment_6_id), + CEREAL_NVP(quantity), + CEREAL_NVP(charges), + CEREAL_NVP(from_player_name), + CEREAL_NVP(to_player_name), + CEREAL_NVP(sent_date) ); } }; struct ParcelDelete { - uint32 item_id; - uint32 quantity; uint32 char_id; + uint32 item_id; + uint32 augment_1_id; + uint32 augment_2_id; + uint32 augment_3_id; + uint32 augment_4_id; + uint32 augment_5_id; + uint32 augment_6_id; + uint32 quantity; + uint32 sent_date; std::string from_name; std::string note; - uint32 sent_date; - uint32 aug_slot_1; - uint32 aug_slot_2; - uint32 aug_slot_3; - uint32 aug_slot_4; - uint32 aug_slot_5; - uint32 aug_slot_6; - // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) { + if constexpr (Archive::is_saving::value) { + save(ar); + } + else { + load(ar); + } + } + + template + void save(Archive& ar) const + { + ar(CEREAL_NVP(item_id)); + + CEREAL_NVP_IF_NONZERO(ar, augment_1_id); + CEREAL_NVP_IF_NONZERO(ar, augment_2_id); + CEREAL_NVP_IF_NONZERO(ar, augment_3_id); + CEREAL_NVP_IF_NONZERO(ar, augment_4_id); + CEREAL_NVP_IF_NONZERO(ar, augment_5_id); + CEREAL_NVP_IF_NONZERO(ar, augment_6_id); + ar( - CEREAL_NVP(item_id), CEREAL_NVP(quantity), CEREAL_NVP(char_id), CEREAL_NVP(from_name), CEREAL_NVP(note), - CEREAL_NVP(sent_date), - CEREAL_NVP(aug_slot_1), - CEREAL_NVP(aug_slot_2), - CEREAL_NVP(aug_slot_3), - CEREAL_NVP(aug_slot_4), - CEREAL_NVP(aug_slot_5), - CEREAL_NVP(aug_slot_6) - ); + CEREAL_NVP(sent_date) + ); + } + + template + void load(Archive& ar) + { + ar( + CEREAL_NVP(item_id), + CEREAL_NVP(augment_1_id), + CEREAL_NVP(augment_2_id), + CEREAL_NVP(augment_3_id), + CEREAL_NVP(augment_4_id), + CEREAL_NVP(augment_5_id), + CEREAL_NVP(augment_6_id), + CEREAL_NVP(quantity), + CEREAL_NVP(char_id), + CEREAL_NVP(from_name), + CEREAL_NVP(note), + CEREAL_NVP(sent_date) + ); } }; @@ -1100,8 +1626,8 @@ namespace PlayerEvent { std::string seller_name; uint64 total_cost; // cereal - template - void serialize(Archive &ar) + template + void serialize(Archive& ar) { ar( CEREAL_NVP(status), @@ -1115,30 +1641,162 @@ namespace PlayerEvent { ); } }; + + struct EvolveItem { + std::string status; + uint32 item_id; + uint64 unique_id; + std::string item_name; + uint32 level; + double progression; + + // cereal + template + void serialize(Archive& ar) + { + ar( + CEREAL_NVP(status), + CEREAL_NVP(item_id), + CEREAL_NVP(unique_id), + CEREAL_NVP(item_name), + CEREAL_NVP(level), + CEREAL_NVP(progression) + ); + } + }; + + struct PlayerSpeech { + std::string to; + std::string from; + uint32 guild_id; + int16 min_status; + uint32 type; + std::string message; + + template + void serialize(Archive& ar) + { + ar( + CEREAL_NVP(to), + CEREAL_NVP(from), + CEREAL_NVP(guild_id), + CEREAL_NVP(min_status), + CEREAL_NVP(type), + CEREAL_NVP(message) + ); + } + }; + + struct GuildBankTransaction { + uint32 char_id; + uint32 guild_id; + uint32 item_id; + uint32 aug_slot_one; + uint32 aug_slot_two; + uint32 aug_slot_three; + uint32 aug_slot_four; + uint32 aug_slot_five; + uint32 aug_slot_six; + uint32 quantity; + uint32 permission; + + template + void serialize(Archive& ar) + { + if constexpr (Archive::is_saving::value) { + save(ar); + } + else { + load(ar); + } + } + + template + void save(Archive& ar) const + { + ar( + CEREAL_NVP(char_id), + CEREAL_NVP(guild_id), + CEREAL_NVP(item_id) + ); + + CEREAL_NVP_IF_NONZERO(ar, aug_slot_one); + CEREAL_NVP_IF_NONZERO(ar, aug_slot_two); + CEREAL_NVP_IF_NONZERO(ar, aug_slot_three); + CEREAL_NVP_IF_NONZERO(ar, aug_slot_four); + CEREAL_NVP_IF_NONZERO(ar, aug_slot_five); + CEREAL_NVP_IF_NONZERO(ar, aug_slot_six); + + ar( + CEREAL_NVP(quantity), + CEREAL_NVP(permission) + ); + } + + template + void load(Archive& ar) + { + ar( + CEREAL_NVP(char_id), + CEREAL_NVP(guild_id), + CEREAL_NVP(item_id), + CEREAL_NVP(aug_slot_one), + CEREAL_NVP(aug_slot_two), + CEREAL_NVP(aug_slot_three), + CEREAL_NVP(aug_slot_four), + CEREAL_NVP(aug_slot_five), + CEREAL_NVP(aug_slot_six), + CEREAL_NVP(quantity), + CEREAL_NVP(permission) + ); + } + }; } #endif //EQEMU_PLAYER_EVENTS_H #define RecordPlayerEventLog(event_type, event_data) do {\ - if (player_event_logs.IsEventEnabled(event_type)) {\ - worldserver.SendPacket(\ - player_event_logs.RecordEvent(\ - event_type,\ - GetPlayerEvent(),\ - event_data\ - ).get()\ - );\ + if (PlayerEventLogs::Instance()->IsEventEnabled(event_type)) {\ + if (RuleB(Logging, PlayerEventsQSProcess)) {\ + QServ->SendPacket(\ + PlayerEventLogs::Instance()->RecordEvent(\ + event_type,\ + GetPlayerEvent(),\ + event_data\ + ).get()\ + );\ + } \ + else { \ + worldserver.SendPacket(\ + PlayerEventLogs::Instance()->RecordEvent(\ + event_type,\ + GetPlayerEvent(),\ + event_data\ + ).get()\ + );\ + }\ }\ } while (0) #define RecordPlayerEventLogWithClient(c, event_type, event_data) do {\ - if (player_event_logs.IsEventEnabled(event_type)) {\ - worldserver.SendPacket(\ - player_event_logs.RecordEvent(\ - event_type,\ - (c)->GetPlayerEvent(),\ - event_data\ - ).get()\ - );\ + if (PlayerEventLogs::Instance()->IsEventEnabled(event_type)) {\ + if (RuleB(Logging, PlayerEventsQSProcess)) {\ + QServ->SendPacket(\ + PlayerEventLogs::Instance()->RecordEvent(\ + event_type,\ + (c)->GetPlayerEvent(),\ + event_data\ + ).get()\ + );\ + }\ + else {\ + worldserver.SendPacket(\ + PlayerEventLogs::Instance()->RecordEvent(\ + event_type,\ + (c)->GetPlayerEvent(),\ + event_data\ + ).get()\ + );\ + }\ }\ } while (0) diff --git a/common/evolving_items.cpp b/common/evolving_items.cpp new file mode 100644 index 000000000..f14a733f3 --- /dev/null +++ b/common/evolving_items.cpp @@ -0,0 +1,330 @@ +#include "evolving_items.h" +#include "item_instance.h" +#include "events/player_event_logs.h" +#include "repositories/character_evolving_items_repository.h" + +EvolvingItemsManager::EvolvingItemsManager() +{ + m_db = nullptr; + m_content_db = nullptr; +} + +void EvolvingItemsManager::LoadEvolvingItems() const +{ + auto const &results = ItemsEvolvingDetailsRepository::All(*m_content_db); + + if (results.empty()) { + return; + } + + std::ranges::transform( + results.begin(), + results.end(), + std::inserter( + EvolvingItemsManager::Instance()->GetEvolvingItemsCache(), + EvolvingItemsManager::Instance()->GetEvolvingItemsCache().end() + ), + [](const ItemsEvolvingDetailsRepository::ItemsEvolvingDetails &x) { + return std::make_pair(x.item_id, x); + } + ); +} + +void EvolvingItemsManager::SetDatabase(Database *db) +{ + m_db = db; +} + +void EvolvingItemsManager::SetContentDatabase(Database *db) +{ + m_content_db = db; +} + +double EvolvingItemsManager::CalculateProgression(const uint64 current_amount, const uint32 item_id) +{ + if (!EvolvingItemsManager::Instance()->GetEvolvingItemsCache().contains(item_id)) { + return 0; + } + + return EvolvingItemsManager::Instance()->GetEvolvingItemsCache().at(item_id).required_amount > 0 + ? static_cast(current_amount) + / static_cast(EvolvingItemsManager::Instance()->GetEvolvingItemsCache().at(item_id).required_amount) * 100 + : 0; +} + +void EvolvingItemsManager::DoLootChecks(const uint32 char_id, const uint16 slot_id, const EQ::ItemInstance &inst) const +{ + if (!inst) { + return; + } + + inst.SetEvolveEquipped(false); + if (inst.IsEvolving() && slot_id <= EQ::invslot::EQUIPMENT_END && slot_id >= EQ::invslot::EQUIPMENT_BEGIN) { + inst.SetEvolveEquipped(true); + } + + if (!inst.IsEvolving()) { + return; + } + + if (!inst.GetEvolveUniqueID()) { + auto e = CharacterEvolvingItemsRepository::NewEntity(); + + e.character_id = char_id; + e.item_id = inst.GetID(); + e.equipped = inst.GetEvolveEquipped(); + e.final_item_id = EvolvingItemsManager::Instance()->GetFinalItemID(inst); + if (inst.GetEvolveCurrentAmount() > 0) { + e.current_amount = inst.GetEvolveCurrentAmount(); + inst.CalculateEvolveProgression(); + } + + auto r = CharacterEvolvingItemsRepository::InsertOne(*m_db, e); + e.id = r.id; + + inst.SetEvolveUniqueID(e.id); + inst.SetEvolveCharID(e.character_id); + inst.SetEvolveItemID(e.item_id); + inst.SetEvolveFinalItemID(e.final_item_id); + + return; + } + + CharacterEvolvingItemsRepository::SetEquipped(*m_db, inst.GetEvolveUniqueID(), inst.GetEvolveEquipped()); +} + +uint32 EvolvingItemsManager::GetFinalItemID(const EQ::ItemInstance &inst) const +{ + if (!inst) { + return 0; + } + + const auto start_iterator = std::ranges::find_if( + EvolvingItemsManager::Instance()->GetEvolvingItemsCache().cbegin(), + EvolvingItemsManager::Instance()->GetEvolvingItemsCache().cend(), + [&](const std::pair &a) { + return a.second.item_evo_id == inst.GetEvolveLoreID(); + } + ); + + if (start_iterator == std::end(EvolvingItemsManager::Instance()->GetEvolvingItemsCache())) { + return 0; + } + + const auto final_id = std::ranges::max_element( + start_iterator, + EvolvingItemsManager::Instance()->GetEvolvingItemsCache().cend(), + [&]( + const std::pair &a, + const std::pair &b + ) { + return a.second.item_evo_id == b.second.item_evo_id && + a.second.item_evolve_level < b.second.item_evolve_level; + } + ); + + return final_id->first; +} + +uint32 EvolvingItemsManager::GetNextEvolveItemID(const EQ::ItemInstance &inst) const +{ + if (!inst) { + return 0; + } + + int8 const current_level = inst.GetEvolveLvl(); + + const auto iterator = std::ranges::find_if( + EvolvingItemsManager::Instance()->GetEvolvingItemsCache().cbegin(), + EvolvingItemsManager::Instance()->GetEvolvingItemsCache().cend(), + [&](const std::pair &a) { + return a.second.item_evo_id == inst.GetEvolveLoreID() && + a.second.item_evolve_level == current_level + 1; + } + ); + + if (iterator == std::end(EvolvingItemsManager::Instance()->GetEvolvingItemsCache())) { + return 0; + } + + return iterator->first; +} + +ItemsEvolvingDetailsRepository::ItemsEvolvingDetails EvolvingItemsManager::GetEvolveItemDetails(const uint64 unique_id) +{ + if (GetEvolvingItemsCache().contains(unique_id)) { + return GetEvolvingItemsCache().at(unique_id); + } + + return ItemsEvolvingDetailsRepository::NewEntity(); +} + +std::vector EvolvingItemsManager::GetEvolveIDItems( + const uint32 evolve_id +) +{ + std::vector e{}; + + for (auto const &[key, value]: GetEvolvingItemsCache()) { + if (value.item_evo_id == evolve_id) { + e.push_back(value); + } + } + + std::ranges::sort( + e.begin(), + e.end(), + [&]( + ItemsEvolvingDetailsRepository::ItemsEvolvingDetails const &a, + ItemsEvolvingDetailsRepository::ItemsEvolvingDetails const &b + ) { + return a.item_evolve_level < b.item_evolve_level; + } + ); + + return e; +} + +uint64 EvolvingItemsManager::GetTotalEarnedXP(const EQ::ItemInstance &inst) +{ + if (!inst) { + return 0; + } + + uint64 xp = inst.GetEvolveCurrentAmount(); + auto evolve_id_item_cache = GetEvolveIDItems(inst.GetEvolveLoreID()); + auto current_level = inst.GetEvolveLvl(); + + for (auto const &i: evolve_id_item_cache) { + if (i.item_evolve_level < current_level) { + xp += i.required_amount; + } + } + + return xp; +} + +EvolveGetNextItem EvolvingItemsManager::GetNextItemByXP(const EQ::ItemInstance &inst_in, const int64 in_xp) +{ + EvolveGetNextItem ets{}; + if (!inst_in) { + return ets; + } + + const auto evolve_items = GetEvolveIDItems(inst_in.GetEvolveLoreID()); + uint32 max_transfer_level = 0; + int64 xp = in_xp; + + for (auto const &e: evolve_items) { + if (e.item_evolve_level < inst_in.GetEvolveLvl()) { + continue; + } + + int64 have = 0; + if (e.item_evolve_level == inst_in.GetEvolveLvl()) { + have = inst_in.GetEvolveCurrentAmount(); + } + + const auto required = e.required_amount; + const int64 need = required - have; + const int64 balance = xp - need; + + if (balance <= 0) { + ets.new_current_amount = have + xp; + ets.new_item_id = e.item_id; + ets.from_current_amount = 0; + ets.max_transfer_level = max_transfer_level; + return ets; + } + + xp = balance; + max_transfer_level += 1; + + ets.new_current_amount = required; + ets.new_item_id = e.item_id; + ets.from_current_amount = balance - required; + ets.max_transfer_level = max_transfer_level; + } + + return ets; +} + +EvolveTransfer EvolvingItemsManager::DetermineTransferResults( + const EQ::ItemInstance &inst_from, + const EQ::ItemInstance &inst_to +) +{ + EvolveTransfer ets{}; + if (!inst_from || !inst_to) { + return ets; + } + + auto evolving_details_inst_from = EvolvingItemsManager::Instance()->GetEvolveItemDetails(inst_from.GetID()); + auto evolving_details_inst_to = EvolvingItemsManager::Instance()->GetEvolveItemDetails(inst_to.GetID()); + + if (!evolving_details_inst_from.id || !evolving_details_inst_to.id) { + return ets; + } + + if (evolving_details_inst_from.type == evolving_details_inst_to.type) { + uint32 compatibility = 0; + uint64 xp = 0; + if (evolving_details_inst_from.sub_type == evolving_details_inst_to.sub_type) { + compatibility = 100; + } + else { + compatibility = 30; + } + + xp = EvolvingItemsManager::Instance()->GetTotalEarnedXP(inst_from) * compatibility / 100; + auto results = EvolvingItemsManager::Instance()->GetNextItemByXP(inst_to, xp); + + ets.item_from_id = EvolvingItemsManager::Instance()->GetFirstItemInLoreGroup(inst_from.GetEvolveLoreID()); + ets.item_from_current_amount = results.from_current_amount; + ets.item_to_id = results.new_item_id; + ets.item_to_current_amount = results.new_current_amount; + ets.compatibility = compatibility; + ets.max_transfer_level = results.max_transfer_level; + } + + return ets; +} + +uint32 EvolvingItemsManager::GetFirstItemInLoreGroup(const uint32 lore_id) +{ + for (auto const &[key, value]: GetEvolvingItemsCache()) { + if (value.item_evo_id == lore_id && value.item_evolve_level == 1) { + return key; + } + } + + return 0; +} + +uint32 EvolvingItemsManager::GetFirstItemInLoreGroupByItemID(const uint32 item_id) +{ + for (auto const &[key, value]: GetEvolvingItemsCache()) { + if (value.item_id == item_id) { + for (auto const &[key2, value2]: GetEvolvingItemsCache()) { + if (value2.item_evo_id == value.item_evo_id && value2.item_evolve_level == 1) { + return key; + } + } + } + } + + return 0; +} + +void EvolvingItemsManager::LoadPlayerEvent(const EQ::ItemInstance &inst, PlayerEvent::EvolveItem &e) +{ + if (!inst) { + return; + } + + e.item_id = inst.GetID(); + e.item_name = inst.GetItem() ? inst.GetItem()->Name : std::string(); + e.level = inst.GetEvolveLvl(); + e.progression = inst.GetEvolveProgression(); + e.unique_id = inst.GetEvolveUniqueID(); +} diff --git a/common/evolving_items.h b/common/evolving_items.h new file mode 100644 index 000000000..3bdb4d697 --- /dev/null +++ b/common/evolving_items.h @@ -0,0 +1,70 @@ +#ifndef EVOLVING_H +#define EVOLVING_H + +#include "shareddb.h" +#include "events/player_events.h" +#include "repositories/items_evolving_details_repository.h" + +namespace EQ { + class ItemInstance; +} + +namespace EvolvingItems { + namespace Actions { + constexpr int8 UPDATE_ITEMS = 0; + constexpr int8 TRANSFER_WINDOW_OPEN = 1; + constexpr int8 TRANSFER_WINDOW_DETAILS = 2; + constexpr int8 TRANSFER_XP = 3; + constexpr int8 FINAL_RESULT = 4; + } + + namespace Types { + constexpr int8 AMOUNT_OF_EXP = 1; + constexpr int8 NUMBER_OF_KILLS = 2; + constexpr int8 SPECIFIC_MOB_RACE = 3; + constexpr int8 SPECIFIC_ZONE_ID = 4; + } + + namespace SubTypes { + constexpr int8 ALL_EXP = 0; + constexpr int8 SOLO_EXP = 1; + constexpr int8 GROUP_EXP = 2; + constexpr int8 RAID_EXP = 3; + } +} + +class EvolvingItemsManager +{ +public: + EvolvingItemsManager(); + void SetDatabase(Database *db); + void SetContentDatabase(Database *db); + + void LoadEvolvingItems() const; + void DoLootChecks(uint32 char_id, uint16 slot_id, const EQ::ItemInstance &inst) const; + uint32 GetFinalItemID(const EQ::ItemInstance &inst) const; + uint32 GetNextEvolveItemID(const EQ::ItemInstance &inst) const; + uint32 GetFirstItemInLoreGroup(uint32 lore_id); + uint32 GetFirstItemInLoreGroupByItemID(uint32 item_id); + uint64 GetTotalEarnedXP(const EQ::ItemInstance &inst); + static double CalculateProgression(uint64 current_amount, uint32 item_id); + static void LoadPlayerEvent(const EQ::ItemInstance &inst, PlayerEvent::EvolveItem &e); + + ItemsEvolvingDetailsRepository::ItemsEvolvingDetails GetEvolveItemDetails(uint64 id); + EvolveTransfer DetermineTransferResults(const EQ::ItemInstance& inst_from, const EQ::ItemInstance& inst_to); + EvolveGetNextItem GetNextItemByXP(const EQ::ItemInstance &inst_in, int64 in_xp); + std::map& GetEvolvingItemsCache() { return m_evolving_items_cache; } + std::vector GetEvolveIDItems(uint32 evolve_id); + + static EvolvingItemsManager* Instance() + { + static EvolvingItemsManager instance; + return &instance; + } +private: + std::map m_evolving_items_cache; + Database * m_db; + Database * m_content_db; +}; + +#endif //EVOLVING_H diff --git a/common/expedition_lockout_timer.cpp b/common/expedition_lockout_timer.cpp deleted file mode 100644 index 6422ad55a..000000000 --- a/common/expedition_lockout_timer.cpp +++ /dev/null @@ -1,101 +0,0 @@ -/** - * EQEmulator: Everquest Server Emulator - * Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY except by those people which sell it, which - * are required to give you total support for your newly bought product; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR - * A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#include "expedition_lockout_timer.h" -#include "../common/strings.h" -#include "../common/rulesys.h" -#include "../common/util/uuid.h" -#include - -const char* const DZ_REPLAY_TIMER_NAME = "Replay Timer"; // see December 14, 2016 patch notes - -ExpeditionLockoutTimer::ExpeditionLockoutTimer( - std::string expedition_uuid, std::string expedition_name, - std::string event_name, uint64_t expire_time, uint32_t duration -) : - m_expedition_uuid{std::move(expedition_uuid)}, - m_expedition_name{std::move(expedition_name)}, - m_event_name{std::move(event_name)}, - m_expire_time(std::chrono::system_clock::from_time_t(expire_time)), - m_duration(duration) -{ - if (m_event_name == DZ_REPLAY_TIMER_NAME) - { - m_is_replay_timer = true; - } -} - -ExpeditionLockoutTimer ExpeditionLockoutTimer::CreateLockout( - const std::string& expedition_name, const std::string& event_name, uint32_t seconds, std::string uuid) -{ - seconds = static_cast(seconds * RuleR(Expedition, LockoutDurationMultiplier)); - - if (uuid.empty()) - { - uuid = EQ::Util::UUID::Generate().ToString(); - } - - ExpeditionLockoutTimer lockout{uuid, expedition_name, event_name, 0, seconds}; - lockout.Reset(); // sets expire time - return lockout; -} - -uint32_t ExpeditionLockoutTimer::GetSecondsRemaining() const -{ - auto now = std::chrono::system_clock::now(); - if (m_expire_time > now) - { - auto remaining = m_expire_time - now; - return static_cast(std::chrono::duration_cast(remaining).count()); - } - return 0; -} - -ExpeditionLockoutTimer::DaysHoursMinutes ExpeditionLockoutTimer::GetDaysHoursMinutesRemaining() const -{ - auto seconds = GetSecondsRemaining(); - return ExpeditionLockoutTimer::DaysHoursMinutes{ - fmt::format_int(seconds / 86400).str(), // days - fmt::format_int((seconds / 3600) % 24).str(), // hours - fmt::format_int((seconds / 60) % 60).str() // minutes - }; -} - -bool ExpeditionLockoutTimer::IsSameLockout(const ExpeditionLockoutTimer& compare_lockout) const -{ - return compare_lockout.IsSameLockout(GetExpeditionName(), GetEventName()); -} - -bool ExpeditionLockoutTimer::IsSameLockout( - const std::string& expedition_name, const std::string& event_name) const -{ - return GetExpeditionName() == expedition_name && GetEventName() == event_name; -} - -void ExpeditionLockoutTimer::AddLockoutTime(int seconds) -{ - seconds = static_cast(seconds * RuleR(Expedition, LockoutDurationMultiplier)); - - auto new_duration = std::max(0, static_cast(m_duration.count()) + seconds); - - auto start_time = m_expire_time - m_duration; - m_duration = std::chrono::seconds(new_duration); - m_expire_time = start_time + m_duration; -} diff --git a/common/expedition_lockout_timer.h b/common/expedition_lockout_timer.h deleted file mode 100644 index 8cbf3da54..000000000 --- a/common/expedition_lockout_timer.h +++ /dev/null @@ -1,76 +0,0 @@ -/** - * EQEmulator: Everquest Server Emulator - * Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY except by those people which sell it, which - * are required to give you total support for your newly bought product; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR - * A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#ifndef EXPEDITION_LOCKOUT_TIMER_H -#define EXPEDITION_LOCKOUT_TIMER_H - -#include -#include - -extern const char* const DZ_REPLAY_TIMER_NAME; - -class ExpeditionLockoutTimer -{ -public: - ExpeditionLockoutTimer() = default; - ExpeditionLockoutTimer( - std::string expedition_uuid, std::string expedition_name, - std::string event_name, uint64_t expire_time, uint32_t duration); - - static ExpeditionLockoutTimer CreateLockout( - const std::string& expedition_name, const std::string& event_name, - uint32_t seconds, std::string uuid = {}); - - struct DaysHoursMinutes - { - std::string days; - std::string hours; - std::string mins; - }; - - void AddLockoutTime(int seconds); - uint32_t GetDuration() const { return static_cast(m_duration.count()); } - uint64_t GetExpireTime() const { return std::chrono::system_clock::to_time_t(m_expire_time); } - uint64_t GetStartTime() const { return std::chrono::system_clock::to_time_t(m_expire_time - m_duration); } - uint32_t GetSecondsRemaining() const; - DaysHoursMinutes GetDaysHoursMinutesRemaining() const; - const std::string& GetExpeditionName() const { return m_expedition_name; } - const std::string& GetExpeditionUUID() const { return m_expedition_uuid; } - const std::string& GetEventName() const { return m_event_name; } - bool IsExpired() const { return GetSecondsRemaining() == 0; } - bool IsFromExpedition(const std::string& uuid) const { return uuid == m_expedition_uuid; } - bool IsReplayTimer() const { return m_is_replay_timer; } - bool IsSameLockout(const ExpeditionLockoutTimer& compare_lockout) const; - bool IsSameLockout(const std::string& expedition_name, const std::string& event_name) const; - void Reset() { m_expire_time = std::chrono::system_clock::now() + m_duration; } - void SetDuration(uint32_t seconds) { m_duration = std::chrono::seconds(seconds); } - void SetExpireTime(uint64_t expire_time) { m_expire_time = std::chrono::system_clock::from_time_t(expire_time); } - void SetUUID(const std::string& uuid) { m_expedition_uuid = uuid; } - -private: - bool m_is_replay_timer = false; - std::string m_expedition_uuid; // expedition received in - std::string m_expedition_name; - std::string m_event_name; - std::chrono::seconds m_duration; - std::chrono::time_point m_expire_time; -}; - -#endif diff --git a/common/faction.cpp b/common/faction.cpp index 29cf45e58..793991593 100644 --- a/common/faction.cpp +++ b/common/faction.cpp @@ -95,12 +95,12 @@ bool IsOfEqualRace(int r1, int r2) } // TODO: add more values switch (r1) { - case DARK_ELF: + case Race::DarkElf: if (r2 == Race::NeriakCitizen) { return true; } break; - case BARBARIAN: + case Race::Barbarian: if (r2 == Race::HalasCitizen) { return true; } @@ -116,49 +116,49 @@ bool IsOfIndiffRace(int r1, int r2) } // TODO: add more values switch (r1) { - case DARK_ELF: - case OGRE: - case TROLL: - if (r2 == OGRE || r2 == TROLL || r2 == DARK_ELF) { + case Race::DarkElf: + case Race::Ogre: + case Race::Troll: + if (r2 == Race::Ogre || r2 == Race::Troll || r2 == Race::DarkElf) { return true; } break; - case HUMAN: - case BARBARIAN: - case HALF_ELF: - case GNOME: - case HALFLING: - case WOOD_ELF: - if (r2 == HUMAN || - r2 == BARBARIAN || - r2 == ERUDITE || - r2 == HALF_ELF || - r2 == GNOME || - r2 == HALFLING || - r2 == DWARF || - r2 == HIGH_ELF || - r2 == WOOD_ELF) { + case Race::Human: + case Race::Barbarian: + case Race::HalfElf: + case Race::Gnome: + case Race::Halfling: + case Race::WoodElf: + if (r2 == Race::Human || + r2 == Race::Barbarian || + r2 == Race::Erudite || + r2 == Race::HalfElf || + r2 == Race::Gnome || + r2 == Race::Halfling || + r2 == Race::Dwarf || + r2 == Race::HighElf || + r2 == Race::WoodElf) { return true; } break; - case ERUDITE: - if (r2 == HUMAN || r2 == HALF_ELF) { + case Race::Erudite: + if (r2 == Race::Human || r2 == Race::HalfElf) { return true; } break; - case DWARF: - if (r2 == HALFLING || r2 == GNOME) { + case Race::Dwarf: + if (r2 == Race::Halfling || r2 == Race::Gnome) { return true; } break; - case HIGH_ELF: - if (r2 == WOOD_ELF) { + case Race::HighElf: + if (r2 == Race::WoodElf) { return true; } break; - case VAHSHIR: + case Race::VahShir: return true; - case IKSAR: + case Race::Iksar: return false; } return false; diff --git a/common/file.cpp b/common/file.cpp index effed63b0..97eec9051 100644 --- a/common/file.cpp +++ b/common/file.cpp @@ -39,6 +39,7 @@ #include #include #include +#include namespace fs = std::filesystem; @@ -90,23 +91,21 @@ std::string File::GetCwd() FileContentsResult File::GetContents(const std::string &file_name) { - std::string error; - std::ifstream f; - f.open(file_name); - std::string line; - std::string lines; - if (f.is_open()) { - while (f) { - std::getline(f, line); - lines += line + "\n"; - } + std::ifstream f(file_name, std::ios::in | std::ios::binary); + if (!f) { + return { .error = fmt::format("Couldn't open file [{}]", file_name) }; } - else { - error = fmt::format("Couldn't open file [{}]", file_name); + + constexpr size_t CHUNK_SIZE = 4096; // Read 4KB chunks + std::string lines; + std::vector buffer(CHUNK_SIZE); + + while (f.read(buffer.data(), CHUNK_SIZE) || f.gcount() > 0) { + lines.append(buffer.data(), f.gcount()); } return FileContentsResult{ .contents = lines, - .error = error, + .error = {} }; } diff --git a/common/guild_base.cpp b/common/guild_base.cpp index 04275fa5b..3f34c3625 100644 --- a/common/guild_base.cpp +++ b/common/guild_base.cpp @@ -547,60 +547,62 @@ uint32 BaseGuildManager::UpdateDbCreateGuild(std::string name, uint32 leader) bool BaseGuildManager::UpdateDbDeleteGuild(uint32 guild_id, bool local_delete, bool db_delete) { + auto const where_filter = fmt::format("guild_id = {}", guild_id); + auto const bank_items = GuildBankRepository::GetWhere(*m_db, where_filter); + if (local_delete) { - auto where_filter = fmt::format("guildid = {}", guild_id); - auto bank_items = GuildBankRepository::GetWhere(*m_db, where_filter); if (!bank_items.empty()) { LogError( - "Attempt to delete guild id [{}] that still has [{}] items in the bank. Please remove them and try again.", + "Attempt to delete guild id [{}] that still has [{}] items in the bank. Please remove them and try " + "again.", guild_id, bank_items.size() ); LogGuilds( - "Attempt to delete guild id [{}] that still has [{}] items in the bank. Please remove them and try again.", + "Attempt to delete guild id [{}] that still has [{}] items in the bank. Please remove them and try " + "again.", guild_id, bank_items.size() ); + return false; } - else { - std::map::iterator res; - res = m_guilds.find(guild_id); - if (res != m_guilds.end()) { - delete res->second; - m_guilds.erase(res); - LogGuilds("Deleted guild [{}] from memory", guild_id); - //Does this need to be sent to world? - } + + auto res = m_guilds.find(guild_id); + if (res != m_guilds.end()) { + safe_delete(res->second); + m_guilds.erase(res); + LogGuilds("Deleted guild [{}] from memory", guild_id); + // Does this need to be sent to world? } } if (db_delete) { - auto where_filter = fmt::format("guildid = {}", guild_id); - auto bank_items = GuildBankRepository::GetWhere(*m_db, where_filter); if (!bank_items.empty()) { LogError( - "Attempt to delete guild id [{}] that still has [{}] items in the bank. Please remove them and try again.", + "Attempt to delete guild id [{}] that still has [{}] items in the bank. Please remove them and try " + "again.", guild_id, bank_items.size() ); LogGuilds( - "Attempt to delete guild id [{}] that still has [{}] items in the bank. Please remove them and try again.", + "Attempt to delete guild id [{}] that still has [{}] items in the bank. Please remove them and try " + "again.", guild_id, bank_items.size() ); + return false; } - else { - auto where_filter = fmt::format("guild_id = {}", guild_id); - GuildTributesRepository::DeleteOne(*m_db, guild_id); - GuildsRepository::DeleteOne(*m_db, guild_id); - GuildRanksRepository::DeleteWhere(*m_db, where_filter); - GuildPermissionsRepository::DeleteWhere(*m_db, where_filter); - GuildMembersRepository::DeleteWhere(*m_db, where_filter); - LogGuilds("Deleted guild [{}] from the database", guild_id); - } + + GuildTributesRepository::DeleteOne(*m_db, guild_id); + GuildsRepository::DeleteOne(*m_db, guild_id); + GuildRanksRepository::DeleteWhere(*m_db, where_filter); + GuildPermissionsRepository::DeleteWhere(*m_db, where_filter); + GuildMembersRepository::DeleteWhere(*m_db, where_filter); + LogGuilds("Deleted guild [{}] from the database", guild_id); } + return true; } diff --git a/common/inventory_profile.cpp b/common/inventory_profile.cpp index df81e516c..7017184d9 100644 --- a/common/inventory_profile.cpp +++ b/common/inventory_profile.cpp @@ -28,6 +28,7 @@ #include "strings.h" #include "../common/light_source.h" +#include "data_verification.h" //#include @@ -44,6 +45,7 @@ ItemInstQueue::~ItemInstQueue() for (auto iter = m_list.begin(); iter != m_list.end(); ++iter) { safe_delete(*iter); } + m_list.clear(); } @@ -62,8 +64,9 @@ void ItemInstQueue::push_front(EQ::ItemInstance* inst) // Remove item from front of queue EQ::ItemInstance* ItemInstQueue::pop() { - if (m_list.empty()) + if (m_list.empty()) { return nullptr; + } EQ::ItemInstance* inst = m_list.front(); m_list.pop_front(); @@ -73,8 +76,9 @@ EQ::ItemInstance* ItemInstQueue::pop() // Remove item from back of queue EQ::ItemInstance* ItemInstQueue::pop_back() { - if (m_list.empty()) + if (m_list.empty()) { return nullptr; + } EQ::ItemInstance* inst = m_list.back(); m_list.pop_back(); @@ -87,7 +91,6 @@ EQ::ItemInstance* ItemInstQueue::peek_front() const return (m_list.empty()) ? nullptr : m_list.front(); } - // // class EQ::InventoryProfile // @@ -96,26 +99,31 @@ EQ::InventoryProfile::~InventoryProfile() for (auto iter = m_worn.begin(); iter != m_worn.end(); ++iter) { safe_delete(iter->second); } + m_worn.clear(); for (auto iter = m_inv.begin(); iter != m_inv.end(); ++iter) { safe_delete(iter->second); } + m_inv.clear(); for (auto iter = m_bank.begin(); iter != m_bank.end(); ++iter) { safe_delete(iter->second); } + m_bank.clear(); for (auto iter = m_shbank.begin(); iter != m_shbank.end(); ++iter) { safe_delete(iter->second); } + m_shbank.clear(); for (auto iter = m_trade.begin(); iter != m_trade.end(); ++iter) { safe_delete(iter->second); } + m_trade.clear(); } @@ -136,6 +144,7 @@ void EQ::InventoryProfile::CleanDirty() { delete (*iter); ++iter; } + dirty_inst.clear(); } @@ -157,59 +166,44 @@ EQ::ItemInstance* EQ::InventoryProfile::GetItem(int16 slot_id) const } // Non bag slots - else if (slot_id >= invslot::TRADE_BEGIN && slot_id <= invslot::TRADE_END) { + if (EQ::ValueWithin(slot_id, invslot::TRADE_BEGIN, invslot::TRADE_END)) { result = _GetItem(m_trade, slot_id); - } - else if (slot_id >= invslot::SHARED_BANK_BEGIN && slot_id <= invslot::SHARED_BANK_END) { - // Shared Bank slots + } else if (EQ::ValueWithin(slot_id, invslot::SHARED_BANK_BEGIN, invslot::SHARED_BANK_END)) { result = _GetItem(m_shbank, slot_id); - } - else if (slot_id >= invslot::BANK_BEGIN && slot_id <= invslot::BANK_END) { - // Bank slots + } else if (EQ::ValueWithin(slot_id, invslot::BANK_BEGIN, invslot::BANK_END)) { result = _GetItem(m_bank, slot_id); - } - else if ((slot_id >= invslot::GENERAL_BEGIN && slot_id <= invslot::GENERAL_END)) { - // Personal inventory slots + } else if (EQ::ValueWithin(slot_id, invslot::GENERAL_BEGIN, invslot::GENERAL_END)) { result = _GetItem(m_inv, slot_id); - } - else if ((slot_id >= invslot::EQUIPMENT_BEGIN && slot_id <= invslot::EQUIPMENT_END) || - (slot_id >= invslot::TRIBUTE_BEGIN && slot_id <= invslot::TRIBUTE_END) || - (slot_id >= invslot::GUILD_TRIBUTE_BEGIN && slot_id <= invslot::GUILD_TRIBUTE_END)) { - // Equippable slots (on body) + } else if ( + EQ::ValueWithin(slot_id, invslot::EQUIPMENT_BEGIN, invslot::EQUIPMENT_END) || + EQ::ValueWithin(slot_id, invslot::TRIBUTE_BEGIN, invslot::TRIBUTE_END) || + EQ::ValueWithin(slot_id, invslot::GUILD_TRIBUTE_BEGIN, invslot::GUILD_TRIBUTE_END) + ) { result = _GetItem(m_worn, slot_id); } // Inner bag slots - else if (slot_id >= invbag::TRADE_BAGS_BEGIN && slot_id <= invbag::TRADE_BAGS_END) { - // Trade bag slots + else if (EQ::ValueWithin(slot_id, invbag::TRADE_BAGS_BEGIN, invbag::TRADE_BAGS_END)) { ItemInstance* inst = _GetItem(m_trade, InventoryProfile::CalcSlotId(slot_id)); if (inst && inst->IsClassBag()) { result = inst->GetItem(InventoryProfile::CalcBagIdx(slot_id)); } - } - else if (slot_id >= invbag::SHARED_BANK_BAGS_BEGIN && slot_id <= invbag::SHARED_BANK_BAGS_END) { - // Shared Bank bag slots + } else if (EQ::ValueWithin(slot_id, invbag::SHARED_BANK_BAGS_BEGIN, invbag::SHARED_BANK_BAGS_END)) { ItemInstance* inst = _GetItem(m_shbank, InventoryProfile::CalcSlotId(slot_id)); if (inst && inst->IsClassBag()) { result = inst->GetItem(InventoryProfile::CalcBagIdx(slot_id)); } - } - else if (slot_id >= invbag::BANK_BAGS_BEGIN && slot_id <= invbag::BANK_BAGS_END) { - // Bank bag slots + } else if (EQ::ValueWithin(slot_id, invbag::BANK_BAGS_BEGIN, invbag::BANK_BAGS_END)) { ItemInstance* inst = _GetItem(m_bank, InventoryProfile::CalcSlotId(slot_id)); if (inst && inst->IsClassBag()) { result = inst->GetItem(InventoryProfile::CalcBagIdx(slot_id)); } - } - else if (slot_id >= invbag::CURSOR_BAG_BEGIN && slot_id <= invbag::CURSOR_BAG_END) { - // Cursor bag slots + } else if (EQ::ValueWithin(slot_id, invbag::CURSOR_BAG_BEGIN, invbag::CURSOR_BAG_END)) { ItemInstance* inst = m_cursor.peek_front(); if (inst && inst->IsClassBag()) { result = inst->GetItem(InventoryProfile::CalcBagIdx(slot_id)); } - } - else if (slot_id >= invbag::GENERAL_BAGS_BEGIN && slot_id <= invbag::GENERAL_BAGS_END) { - // Personal inventory bag slots + } else if (EQ::ValueWithin(slot_id, invbag::GENERAL_BAGS_BEGIN, invbag::GENERAL_BAGS_END)) { ItemInstance* inst = _GetItem(m_inv, InventoryProfile::CalcSlotId(slot_id)); if (inst && inst->IsClassBag()) { result = inst->GetItem(InventoryProfile::CalcBagIdx(slot_id)); @@ -228,25 +222,27 @@ EQ::ItemInstance* EQ::InventoryProfile::GetItem(int16 slot_id, uint8 bagidx) con // Put an item into specified slot int16 EQ::InventoryProfile::PutItem(int16 slot_id, const ItemInstance& inst) { - if (slot_id <= EQ::invslot::POSSESSIONS_END && slot_id >= EQ::invslot::POSSESSIONS_BEGIN) { - if ((((uint64)1 << slot_id) & m_lookup->PossessionsBitmask) == 0) + if (EQ::ValueWithin(slot_id, EQ::invslot::POSSESSIONS_BEGIN, EQ::invslot::POSSESSIONS_END)) { + if ((((uint64) 1 << slot_id) & m_lookup->PossessionsBitmask) == 0) { return EQ::invslot::SLOT_INVALID; - } - else if (slot_id <= EQ::invbag::GENERAL_BAGS_END && slot_id >= EQ::invbag::GENERAL_BAGS_BEGIN) { + } + } else if (EQ::ValueWithin(slot_id, EQ::invbag::GENERAL_BAGS_BEGIN, EQ::invbag::GENERAL_BAGS_END)) { auto temp_slot = EQ::invslot::GENERAL_BEGIN + ((slot_id - EQ::invbag::GENERAL_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT); - if ((((uint64)1 << temp_slot) & m_lookup->PossessionsBitmask) == 0) + if ((((uint64) 1 << temp_slot) & m_lookup->PossessionsBitmask) == 0) { return EQ::invslot::SLOT_INVALID; - } - else if (slot_id <= EQ::invslot::BANK_END && slot_id >= EQ::invslot::BANK_BEGIN) { - if ((slot_id - EQ::invslot::BANK_BEGIN) >= m_lookup->InventoryTypeSize.Bank) + } + } else if (EQ::ValueWithin(slot_id, EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END)) { + if ((slot_id - EQ::invslot::BANK_BEGIN) >= m_lookup->InventoryTypeSize.Bank) { return EQ::invslot::SLOT_INVALID; - } - else if (slot_id <= EQ::invbag::BANK_BAGS_END && slot_id >= EQ::invbag::BANK_BAGS_BEGIN) { + } + } else if (EQ::ValueWithin(slot_id, EQ::invbag::BANK_BAGS_BEGIN, EQ::invbag::BANK_BAGS_END)) { auto temp_slot = (slot_id - EQ::invbag::BANK_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT; - if (temp_slot >= m_lookup->InventoryTypeSize.Bank) + if (temp_slot >= m_lookup->InventoryTypeSize.Bank) { return EQ::invslot::SLOT_INVALID; + } } + // Clean up item already in slot (if exists) DeleteItem(slot_id); @@ -281,26 +277,23 @@ bool EQ::InventoryProfile::SwapItem( ) { fail_state = swapInvalid; - if (source_slot <= EQ::invslot::POSSESSIONS_END && source_slot >= EQ::invslot::POSSESSIONS_BEGIN) { - if ((((uint64) 1 << source_slot) & m_lookup->PossessionsBitmask) == 0) { + if (EQ::ValueWithin(source_slot, EQ::invslot::POSSESSIONS_BEGIN, EQ::invslot::POSSESSIONS_END)) { + if ((((uint64)1 << source_slot) & m_lookup->PossessionsBitmask) == 0) { fail_state = swapNotAllowed; return false; } - } - else if (source_slot <= EQ::invbag::GENERAL_BAGS_END && source_slot >= EQ::invbag::GENERAL_BAGS_BEGIN) { + } else if (EQ::ValueWithin(source_slot, EQ::invbag::GENERAL_BAGS_BEGIN, EQ::invbag::GENERAL_BAGS_END)) { auto temp_slot = EQ::invslot::GENERAL_BEGIN + ((source_slot - EQ::invbag::GENERAL_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT); if ((((uint64)1 << temp_slot) & m_lookup->PossessionsBitmask) == 0) { fail_state = swapNotAllowed; return false; } - } - else if (source_slot <= EQ::invslot::BANK_END && source_slot >= EQ::invslot::BANK_BEGIN) { + } else if (EQ::ValueWithin(source_slot, EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END)) { if ((source_slot - EQ::invslot::BANK_BEGIN) >= m_lookup->InventoryTypeSize.Bank) { fail_state = swapNotAllowed; return false; } - } - else if (source_slot <= EQ::invbag::BANK_BAGS_END && source_slot >= EQ::invbag::BANK_BAGS_BEGIN) { + } else if (EQ::ValueWithin(source_slot, EQ::invbag::BANK_BAGS_BEGIN, EQ::invbag::BANK_BAGS_END)) { auto temp_slot = (source_slot - EQ::invbag::BANK_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT; if (temp_slot >= m_lookup->InventoryTypeSize.Bank) { fail_state = swapNotAllowed; @@ -308,26 +301,23 @@ bool EQ::InventoryProfile::SwapItem( } } - if (destination_slot <= EQ::invslot::POSSESSIONS_END && destination_slot >= EQ::invslot::POSSESSIONS_BEGIN) { + if (EQ::ValueWithin(destination_slot, EQ::invslot::POSSESSIONS_BEGIN, EQ::invslot::POSSESSIONS_END)) { if ((((uint64)1 << destination_slot) & m_lookup->PossessionsBitmask) == 0) { fail_state = swapNotAllowed; return false; } - } - else if (destination_slot <= EQ::invbag::GENERAL_BAGS_END && destination_slot >= EQ::invbag::GENERAL_BAGS_BEGIN) { + } else if (EQ::ValueWithin(destination_slot, EQ::invbag::GENERAL_BAGS_BEGIN, EQ::invbag::GENERAL_BAGS_END)) { auto temp_slot = EQ::invslot::GENERAL_BEGIN + ((destination_slot - EQ::invbag::GENERAL_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT); if ((((uint64)1 << temp_slot) & m_lookup->PossessionsBitmask) == 0) { fail_state = swapNotAllowed; return false; } - } - else if (destination_slot <= EQ::invslot::BANK_END && destination_slot >= EQ::invslot::BANK_BEGIN) { + } else if (EQ::ValueWithin(destination_slot, EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END)) { if ((destination_slot - EQ::invslot::BANK_BEGIN) >= m_lookup->InventoryTypeSize.Bank) { fail_state = swapNotAllowed; return false; } - } - else if (destination_slot <= EQ::invbag::BANK_BAGS_END && destination_slot >= EQ::invbag::BANK_BAGS_BEGIN) { + } else if (EQ::ValueWithin(destination_slot, EQ::invbag::BANK_BAGS_BEGIN, EQ::invbag::BANK_BAGS_END)) { auto temp_slot = (destination_slot - EQ::invbag::BANK_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT; if (temp_slot >= m_lookup->InventoryTypeSize.Bank) { fail_state = swapNotAllowed; @@ -335,6 +325,7 @@ bool EQ::InventoryProfile::SwapItem( } } + // Temp holding areas for source and destination ItemInstance *source_item_instance = GetItem(source_slot); ItemInstance *destination_item_instance = GetItem(destination_slot); @@ -344,24 +335,32 @@ bool EQ::InventoryProfile::SwapItem( fail_state = swapNotAllowed; return false; } - if ((destination_slot >= invslot::EQUIPMENT_BEGIN && destination_slot <= invslot::EQUIPMENT_END)) { + + source_item_instance->SetEvolveEquipped(false); + if (EQ::ValueWithin(destination_slot, invslot::EQUIPMENT_BEGIN, invslot::EQUIPMENT_END)) { auto source_item = source_item_instance->GetItem(); if (!source_item) { fail_state = swapNullData; return false; } + if (race_id && class_id && !source_item->IsEquipable(race_id, class_id)) { fail_state = swapRaceClass; return false; } + if (deity_id && source_item->Deity && !(Deity::GetBitmask(deity_id) & source_item->Deity)) { fail_state = swapDeity; return false; } + if (level && source_item->ReqLevel && level < source_item->ReqLevel) { fail_state = swapLevel; return false; } + if (source_item_instance->IsEvolving() > 0) { + source_item_instance->SetEvolveEquipped(true); + } } } @@ -370,24 +369,32 @@ bool EQ::InventoryProfile::SwapItem( fail_state = swapNotAllowed; return false; } - if ((source_slot >= invslot::EQUIPMENT_BEGIN && source_slot <= invslot::EQUIPMENT_END)) { + + destination_item_instance->SetEvolveEquipped(false); + if (EQ::ValueWithin(source_slot, invslot::EQUIPMENT_BEGIN, invslot::EQUIPMENT_END)) { auto destination_item = destination_item_instance->GetItem(); if (!destination_item) { fail_state = swapNullData; return false; } + if (race_id && class_id && !destination_item->IsEquipable(race_id, class_id)) { fail_state = swapRaceClass; return false; } + if (deity_id && destination_item->Deity && !(Deity::GetBitmask(deity_id) & destination_item->Deity)) { fail_state = swapDeity; return false; } + if (level && destination_item->ReqLevel && level < destination_item->ReqLevel) { fail_state = swapLevel; return false; } + if (destination_item_instance->IsEvolving()) { + destination_item_instance->SetEvolveEquipped(true); + } } } @@ -439,10 +446,8 @@ bool EQ::InventoryProfile::DeleteItem(int16 slot_id, int16 quantity) { bool EQ::InventoryProfile::CheckNoDrop(int16 slot_id, bool recurse) { ItemInstance* inst = GetItem(slot_id); - if (!inst) - return false; - return (!inst->IsDroppable(recurse)); + return inst ? !inst->IsDroppable(recurse) : false; } // Remove item from bucket without memory delete @@ -453,40 +458,32 @@ EQ::ItemInstance* EQ::InventoryProfile::PopItem(int16 slot_id) if (slot_id == invslot::slotCursor) { p = m_cursor.pop(); - } - else if (slot_id >= invslot::EQUIPMENT_BEGIN && slot_id <= invslot::EQUIPMENT_END) { + } else if (EQ::ValueWithin(slot_id, invslot::EQUIPMENT_BEGIN, invslot::EQUIPMENT_END)) { p = m_worn[slot_id]; m_worn.erase(slot_id); - } - else if (slot_id >= invslot::GENERAL_BEGIN && slot_id <= invslot::GENERAL_END) { + } else if (EQ::ValueWithin(slot_id, invslot::GENERAL_BEGIN, invslot::GENERAL_END)) { p = m_inv[slot_id]; m_inv.erase(slot_id); - } - else if (slot_id >= invslot::TRIBUTE_BEGIN && slot_id <= invslot::TRIBUTE_END) { + } else if (EQ::ValueWithin(slot_id, invslot::TRIBUTE_BEGIN, invslot::TRIBUTE_END)) { p = m_worn[slot_id]; m_worn.erase(slot_id); - } - else if (slot_id >= invslot::GUILD_TRIBUTE_BEGIN && slot_id <= invslot::GUILD_TRIBUTE_END) { + } else if (EQ::ValueWithin(slot_id, invslot::GUILD_TRIBUTE_BEGIN, invslot::GUILD_TRIBUTE_END)) { p = m_worn[slot_id]; m_worn.erase(slot_id); - } - else if (slot_id >= invslot::BANK_BEGIN && slot_id <= invslot::BANK_END) { + } else if (EQ::ValueWithin(slot_id, invslot::BANK_BEGIN, invslot::BANK_END)) { p = m_bank[slot_id]; m_bank.erase(slot_id); - } - else if (slot_id >= invslot::SHARED_BANK_BEGIN && slot_id <= invslot::SHARED_BANK_END) { + } else if (EQ::ValueWithin(slot_id, invslot::SHARED_BANK_BEGIN, invslot::SHARED_BANK_END)) { p = m_shbank[slot_id]; m_shbank.erase(slot_id); - } - else if (slot_id >= invslot::TRADE_BEGIN && slot_id <= invslot::TRADE_END) { + } else if (EQ::ValueWithin(slot_id, invslot::TRADE_BEGIN, invslot::TRADE_END)) { p = m_trade[slot_id]; m_trade.erase(slot_id); - } - else { - // Is slot inside bag? - ItemInstance* baginst = GetItem(InventoryProfile::CalcSlotId(slot_id)); - if (baginst != nullptr && baginst->IsClassBag()) { - p = baginst->PopItem(InventoryProfile::CalcBagIdx(slot_id)); + } else { + // Is slot inside bag? + ItemInstance* bag_inst = GetItem(InventoryProfile::CalcSlotId(slot_id)); + if (bag_inst && bag_inst->IsClassBag()) { + p = bag_inst->PopItem(InventoryProfile::CalcBagIdx(slot_id)); } } @@ -494,43 +491,49 @@ EQ::ItemInstance* EQ::InventoryProfile::PopItem(int16 slot_id) return p; } -bool EQ::InventoryProfile::HasSpaceForItem(const ItemData *ItemToTry, int16 Quantity) { - +bool EQ::InventoryProfile::HasSpaceForItem(const ItemData* ItemToTry, int16 Quantity) +{ if (ItemToTry->Stackable) { - for (int16 i = invslot::GENERAL_BEGIN; i <= invslot::GENERAL_END; i++) { - if ((((uint64)1 << i) & m_lookup->PossessionsBitmask) == 0) + if ((((uint64) 1 << i) & m_lookup->PossessionsBitmask) == 0) { continue; - - ItemInstance* InvItem = GetItem(i); - - if (InvItem && (InvItem->GetItem()->ID == ItemToTry->ID) && (InvItem->GetCharges() < InvItem->GetItem()->StackSize)) { - - int ChargeSlotsLeft = InvItem->GetItem()->StackSize - InvItem->GetCharges(); - - if (Quantity <= ChargeSlotsLeft) - return true; - - Quantity -= ChargeSlotsLeft; - } - if (InvItem && InvItem->IsClassBag()) { - int16 BaseSlotID = InventoryProfile::CalcSlotId(i, invbag::SLOT_BEGIN); - uint8 BagSize = InvItem->GetItem()->BagSlots; - for (uint8 BagSlot = invbag::SLOT_BEGIN; BagSlot < BagSize; BagSlot++) { + ItemInstance* inv_item = GetItem(i); - InvItem = GetItem(BaseSlotID + BagSlot); + if ( + inv_item && + inv_item->GetItem()->ID == ItemToTry->ID && + inv_item->GetCharges() < inv_item->GetItem()->StackSize + ) { + int charges_left = inv_item->GetItem()->StackSize - inv_item->GetCharges(); - if (InvItem && (InvItem->GetItem()->ID == ItemToTry->ID) && - (InvItem->GetCharges() < InvItem->GetItem()->StackSize)) { + if (Quantity <= charges_left) { + return true; + } - int ChargeSlotsLeft = InvItem->GetItem()->StackSize - InvItem->GetCharges(); + Quantity -= charges_left; + } - if (Quantity <= ChargeSlotsLeft) + if (inv_item && inv_item->IsClassBag()) { + int16 base_slot_id = InventoryProfile::CalcSlotId(i, invbag::SLOT_BEGIN); + uint8 bag_slots = inv_item->GetItem()->BagSlots; + + for (uint8 bag_slot = invbag::SLOT_BEGIN; bag_slot < bag_slots; bag_slot++) { + inv_item = GetItem(base_slot_id + bag_slot); + + if ( + inv_item && + inv_item->GetItem()->ID == ItemToTry->ID && + inv_item->GetCharges() < inv_item->GetItem()->StackSize + ) { + int charges_left = inv_item->GetItem()->StackSize - inv_item->GetCharges(); + + if (Quantity <= charges_left) { return true; + } - Quantity -= ChargeSlotsLeft; + Quantity -= charges_left; } } } @@ -538,51 +541,46 @@ bool EQ::InventoryProfile::HasSpaceForItem(const ItemData *ItemToTry, int16 Quan } for (int16 i = invslot::GENERAL_BEGIN; i <= invslot::GENERAL_END; i++) { - if ((((uint64)1 << i) & m_lookup->PossessionsBitmask) == 0) + if ((((uint64) 1 << i) & m_lookup->PossessionsBitmask) == 0) { continue; - - ItemInstance* InvItem = GetItem(i); - - if (!InvItem) { - - if (!ItemToTry->Stackable) { - - if (Quantity == 1) - return true; - else - Quantity--; - } - else { - if (Quantity <= ItemToTry->StackSize) - return true; - else - Quantity -= ItemToTry->StackSize; - } - } - else if (InvItem->IsClassBag() && CanItemFitInContainer(ItemToTry, InvItem->GetItem())) { - int16 BaseSlotID = InventoryProfile::CalcSlotId(i, invbag::SLOT_BEGIN); + ItemInstance* inv_item = GetItem(i); - uint8 BagSize = InvItem->GetItem()->BagSlots; + if (!inv_item) { + if (!ItemToTry->Stackable) { + if (Quantity == 1) { + return true; + } else { + Quantity--; + } + } else { + if (Quantity <= ItemToTry->StackSize) { + return true; + } else { + Quantity -= ItemToTry->StackSize; + } + } + } else if (inv_item->IsClassBag() && CanItemFitInContainer(ItemToTry, inv_item->GetItem())) { + int16 base_slot_id = InventoryProfile::CalcSlotId(i, invbag::SLOT_BEGIN); + uint8 bag_slots = inv_item->GetItem()->BagSlots; - for (uint8 BagSlot = invbag::SLOT_BEGIN; BagSlot < BagSize; BagSlot++) { + for (uint8 bag_slot = invbag::SLOT_BEGIN; bag_slot < bag_slots; bag_slot++) { + inv_item = GetItem(base_slot_id + bag_slot); - InvItem = GetItem(BaseSlotID + BagSlot); - - if (!InvItem) { + if (!inv_item) { if (!ItemToTry->Stackable) { - - if (Quantity == 1) + if (Quantity == 1) { return true; - else + } else { Quantity--; - } - else { - if (Quantity <= ItemToTry->StackSize) + } + } else { + if (Quantity <= ItemToTry->StackSize) { return true; - else + } else { Quantity -= ItemToTry->StackSize; + } } } } @@ -590,7 +588,6 @@ bool EQ::InventoryProfile::HasSpaceForItem(const ItemData *ItemToTry, int16 Quan } return false; - } // Checks that user has at least 'quantity' number of items in a given inventory slot @@ -612,9 +609,9 @@ bool EQ::InventoryProfile::HasAugmentEquippedByID(uint32 item_id) return has_equipped; } -int EQ::InventoryProfile::CountAugmentEquippedByID(uint32 item_id) +uint32 EQ::InventoryProfile::CountAugmentEquippedByID(uint32 item_id) { - int quantity = 0; + uint32 quantity = 0; ItemInstance* item = nullptr; for (int slot_id = EQ::invslot::EQUIPMENT_BEGIN; slot_id <= EQ::invslot::EQUIPMENT_END; ++slot_id) { @@ -643,9 +640,9 @@ bool EQ::InventoryProfile::HasItemEquippedByID(uint32 item_id) return has_equipped; } -int EQ::InventoryProfile::CountItemEquippedByID(uint32 item_id) +uint32 EQ::InventoryProfile::CountItemEquippedByID(uint32 item_id) { - int quantity = 0; + uint32 quantity = 0; ItemInstance* item = nullptr; for (int slot_id = EQ::invslot::EQUIPMENT_BEGIN; slot_id <= EQ::invslot::EQUIPMENT_END; ++slot_id) { @@ -670,40 +667,46 @@ int16 EQ::InventoryProfile::HasItem(uint32 item_id, uint8 quantity, uint8 where) // Check each inventory bucket if (where & invWhereWorn) { slot_id = _HasItem(m_worn, item_id, quantity); - if (slot_id != INVALID_INDEX) + if (slot_id != INVALID_INDEX) { return slot_id; + } } if (where & invWherePersonal) { slot_id = _HasItem(m_inv, item_id, quantity); - if (slot_id != INVALID_INDEX) + if (slot_id != INVALID_INDEX) { return slot_id; + } } if (where & invWhereBank) { slot_id = _HasItem(m_bank, item_id, quantity); - if (slot_id != INVALID_INDEX) + if (slot_id != INVALID_INDEX) { return slot_id; + } } if (where & invWhereSharedBank) { slot_id = _HasItem(m_shbank, item_id, quantity); - if (slot_id != INVALID_INDEX) + if (slot_id != INVALID_INDEX) { return slot_id; + } } if (where & invWhereTrading) { slot_id = _HasItem(m_trade, item_id, quantity); - if (slot_id != INVALID_INDEX) + if (slot_id != INVALID_INDEX) { return slot_id; + } } // Behavioral change - Limbo is no longer checked due to improper handling of return value if (where & invWhereCursor) { // Check cursor queue slot_id = _HasItem(m_cursor, item_id, quantity); - if (slot_id != INVALID_INDEX) + if (slot_id != INVALID_INDEX) { return slot_id; + } } return slot_id; @@ -717,40 +720,46 @@ int16 EQ::InventoryProfile::HasItemByUse(uint8 use, uint8 quantity, uint8 where) // Check each inventory bucket if (where & invWhereWorn) { slot_id = _HasItemByUse(m_worn, use, quantity); - if (slot_id != INVALID_INDEX) + if (slot_id != INVALID_INDEX) { return slot_id; + } } if (where & invWherePersonal) { slot_id = _HasItemByUse(m_inv, use, quantity); - if (slot_id != INVALID_INDEX) + if (slot_id != INVALID_INDEX) { return slot_id; + } } if (where & invWhereBank) { slot_id = _HasItemByUse(m_bank, use, quantity); - if (slot_id != INVALID_INDEX) + if (slot_id != INVALID_INDEX) { return slot_id; + } } if (where & invWhereSharedBank) { slot_id = _HasItemByUse(m_shbank, use, quantity); - if (slot_id != INVALID_INDEX) + if (slot_id != INVALID_INDEX) { return slot_id; + } } if (where & invWhereTrading) { slot_id = _HasItemByUse(m_trade, use, quantity); - if (slot_id != INVALID_INDEX) + if (slot_id != INVALID_INDEX) { return slot_id; + } } // Behavioral change - Limbo is no longer checked due to improper handling of return value if (where & invWhereCursor) { // Check cursor queue slot_id = _HasItemByUse(m_cursor, use, quantity); - if (slot_id != INVALID_INDEX) + if (slot_id != INVALID_INDEX) { return slot_id; + } } return slot_id; @@ -763,40 +772,46 @@ int16 EQ::InventoryProfile::HasItemByLoreGroup(uint32 loregroup, uint8 where) // Check each inventory bucket if (where & invWhereWorn) { slot_id = _HasItemByLoreGroup(m_worn, loregroup); - if (slot_id != INVALID_INDEX) + if (slot_id != INVALID_INDEX) { return slot_id; + } } if (where & invWherePersonal) { slot_id = _HasItemByLoreGroup(m_inv, loregroup); - if (slot_id != INVALID_INDEX) + if (slot_id != INVALID_INDEX) { return slot_id; + } } if (where & invWhereBank) { slot_id = _HasItemByLoreGroup(m_bank, loregroup); - if (slot_id != INVALID_INDEX) + if (slot_id != INVALID_INDEX) { return slot_id; + } } if (where & invWhereSharedBank) { slot_id = _HasItemByLoreGroup(m_shbank, loregroup); - if (slot_id != INVALID_INDEX) + if (slot_id != INVALID_INDEX) { return slot_id; + } } if (where & invWhereTrading) { slot_id = _HasItemByLoreGroup(m_trade, loregroup); - if (slot_id != INVALID_INDEX) + if (slot_id != INVALID_INDEX) { return slot_id; + } } // Behavioral change - Limbo is no longer checked due to improper handling of return value if (where & invWhereCursor) { // Check cursor queue slot_id = _HasItemByLoreGroup(m_cursor, loregroup); - if (slot_id != INVALID_INDEX) + if (slot_id != INVALID_INDEX) { return slot_id; + } } return slot_id; @@ -824,7 +839,7 @@ int16 EQ::InventoryProfile::FindFreeSlot(bool for_bag, bool try_cursor, uint8 mi continue; } - const auto *inst = GetItem(i); + const EQ::ItemInstance* inst = GetItem(i); if (inst && inst->IsClassBag() && inst->GetItem()->BagSize >= min_size) { if (inst->GetItem()->BagType == item::BagTypeQuiver && inst->GetItem()->ItemType != item::ItemTypeArrow) { @@ -832,8 +847,8 @@ int16 EQ::InventoryProfile::FindFreeSlot(bool for_bag, bool try_cursor, uint8 mi } const int16 base_slot_id = InventoryProfile::CalcSlotId(i, invbag::SLOT_BEGIN); + const uint8 slots = inst->GetItem()->BagSlots; - const uint8 slots = inst->GetItem()->BagSlots; for (uint8 j = invbag::SLOT_BEGIN; j < slots; j++) { if (!GetItem(base_slot_id + j)) { // Found available slot within bag @@ -861,22 +876,25 @@ int16 EQ::InventoryProfile::FindFreeSlotForTradeItem(const ItemInstance* inst, i // // I'll probably implement a bitmask in the new inventory system to avoid having to adjust stack bias - if ((general_start < invslot::GENERAL_BEGIN) || (general_start > invslot::GENERAL_END)) - return INVALID_INDEX; - if (bag_start > invbag::SLOT_END) - return INVALID_INDEX; - - if (!inst || !inst->GetID()) + if ( + !EQ::ValueWithin(general_start, invslot::GENERAL_BEGIN, invslot::GENERAL_END) || + bag_start > invbag::SLOT_END || + !inst || + !inst->GetID() + ) { return INVALID_INDEX; + } // step 1: find room for bags (caller should really ask for slots for bags first to avoid sending them to cursor..and bag item loss) if (inst->IsClassBag()) { for (int16 free_slot = general_start; free_slot <= invslot::GENERAL_END; ++free_slot) { - if ((((uint64)1 << free_slot) & m_lookup->PossessionsBitmask) == 0) + if ((((uint64) 1 << free_slot) & m_lookup->PossessionsBitmask) == 0) { continue; + } - if (!m_inv[free_slot]) + if (!m_inv[free_slot]) { return free_slot; + } } return invslot::slotCursor; // return cursor since bags do not stack and will not fit inside other bags..yet...) @@ -885,37 +903,55 @@ int16 EQ::InventoryProfile::FindFreeSlotForTradeItem(const ItemInstance* inst, i // step 2: find partial room for stackables if (inst->IsStackable()) { for (int16 free_slot = general_start; free_slot <= invslot::GENERAL_END; ++free_slot) { - if ((((uint64)1 << free_slot) & m_lookup->PossessionsBitmask) == 0) + if ((((uint64) 1 << free_slot) & m_lookup->PossessionsBitmask) == 0) { continue; + } const ItemInstance* main_inst = m_inv[free_slot]; - if (!main_inst) + if (!main_inst) { continue; + } - if ((main_inst->GetID() == inst->GetID()) && (main_inst->GetCharges() < main_inst->GetItem()->StackSize)) + if ( + main_inst->GetID() == inst->GetID() && + main_inst->GetCharges() < main_inst->GetItem()->StackSize + ) { return free_slot; + } } for (int16 free_slot = general_start; free_slot <= invslot::GENERAL_END; ++free_slot) { - if ((((uint64)1 << free_slot) & m_lookup->PossessionsBitmask) == 0) + if ((((uint64) 1 << free_slot) & m_lookup->PossessionsBitmask) == 0) { continue; + } const ItemInstance* main_inst = m_inv[free_slot]; - if (!main_inst) + if (!main_inst) { continue; + } if (main_inst->IsClassBag()) { // if item-specific containers already have bad items, we won't fix it here... uint8 _bag_start = (free_slot > general_start) ? invbag::SLOT_BEGIN : bag_start; - for (uint8 free_bag_slot = _bag_start; (free_bag_slot < main_inst->GetItem()->BagSlots) && (free_bag_slot <= invbag::SLOT_END); ++free_bag_slot) { + + for ( + uint8 free_bag_slot = _bag_start; + (free_bag_slot < main_inst->GetItem()->BagSlots) && (free_bag_slot <= invbag::SLOT_END); + ++free_bag_slot + ) { const ItemInstance* sub_inst = main_inst->GetItem(free_bag_slot); - if (!sub_inst) + if (!sub_inst) { continue; + } - if ((sub_inst->GetID() == inst->GetID()) && (sub_inst->GetCharges() < sub_inst->GetItem()->StackSize)) + if ( + sub_inst->GetID() == inst->GetID() && + sub_inst->GetCharges() < sub_inst->GetItem()->StackSize + ) { return InventoryProfile::CalcSlotId(free_slot, free_bag_slot); + } } } } @@ -924,18 +960,30 @@ int16 EQ::InventoryProfile::FindFreeSlotForTradeItem(const ItemInstance* inst, i // step 3a: find room for container-specific items (ItemClassArrow) if (inst->GetItem()->ItemType == item::ItemTypeArrow) { for (int16 free_slot = general_start; free_slot <= invslot::GENERAL_END; ++free_slot) { - if ((((uint64)1 << free_slot) & m_lookup->PossessionsBitmask) == 0) + if ((((uint64) 1 << free_slot) & m_lookup->PossessionsBitmask) == 0) { continue; + } const ItemInstance* main_inst = m_inv[free_slot]; - if (!main_inst || (main_inst->GetItem()->BagType != item::BagTypeQuiver) || !main_inst->IsClassBag()) + if ( + !main_inst || + main_inst->GetItem()->BagType != item::BagTypeQuiver || + !main_inst->IsClassBag() + ) { continue; + } uint8 _bag_start = (free_slot > general_start) ? invbag::SLOT_BEGIN : bag_start; - for (uint8 free_bag_slot = _bag_start; (free_bag_slot < main_inst->GetItem()->BagSlots) && (free_bag_slot <= invbag::SLOT_END); ++free_bag_slot) { - if (!main_inst->GetItem(free_bag_slot)) + + for ( + uint8 free_bag_slot = _bag_start; + (free_bag_slot < main_inst->GetItem()->BagSlots) && (free_bag_slot <= invbag::SLOT_END); + ++free_bag_slot + ) { + if (!main_inst->GetItem(free_bag_slot)) { return InventoryProfile::CalcSlotId(free_slot, free_bag_slot); + } } } } @@ -943,47 +991,73 @@ int16 EQ::InventoryProfile::FindFreeSlotForTradeItem(const ItemInstance* inst, i // step 3b: find room for container-specific items (ItemClassSmallThrowing) if (inst->GetItem()->ItemType == item::ItemTypeSmallThrowing) { for (int16 free_slot = general_start; free_slot <= invslot::GENERAL_END; ++free_slot) { - if ((((uint64)1 << free_slot) & m_lookup->PossessionsBitmask) == 0) + if ((((uint64) 1 << free_slot) & m_lookup->PossessionsBitmask) == 0) { continue; + } const ItemInstance* main_inst = m_inv[free_slot]; - if (!main_inst || (main_inst->GetItem()->BagType != item::BagTypeBandolier) || !main_inst->IsClassBag()) + if ( + !main_inst || + main_inst->GetItem()->BagType != item::BagTypeBandolier || + !main_inst->IsClassBag() + ) { continue; + } uint8 _bag_start = (free_slot > general_start) ? invbag::SLOT_BEGIN : bag_start; - for (uint8 free_bag_slot = _bag_start; (free_bag_slot < main_inst->GetItem()->BagSlots) && (free_bag_slot <= invbag::SLOT_END); ++free_bag_slot) { - if (!main_inst->GetItem(free_bag_slot)) + + for ( + uint8 free_bag_slot = _bag_start; + (free_bag_slot < main_inst->GetItem()->BagSlots) && (free_bag_slot <= invbag::SLOT_END); + ++free_bag_slot + ) { + if (!main_inst->GetItem(free_bag_slot)) { return InventoryProfile::CalcSlotId(free_slot, free_bag_slot); + } } } } // step 4: just find an empty slot for (int16 free_slot = general_start; free_slot <= invslot::GENERAL_END; ++free_slot) { - if ((((uint64)1 << free_slot) & m_lookup->PossessionsBitmask) == 0) + if ((((uint64) 1 << free_slot) & m_lookup->PossessionsBitmask) == 0) { continue; + } const ItemInstance* main_inst = m_inv[free_slot]; - if (!main_inst) + if (!main_inst) { return free_slot; + } } for (int16 free_slot = general_start; free_slot <= invslot::GENERAL_END; ++free_slot) { - if ((((uint64)1 << free_slot) & m_lookup->PossessionsBitmask) == 0) + if ((((uint64) 1 << free_slot) & m_lookup->PossessionsBitmask) == 0) { continue; + } const ItemInstance* main_inst = m_inv[free_slot]; if (main_inst && main_inst->IsClassBag()) { - if ((main_inst->GetItem()->BagSize < inst->GetItem()->Size) || (main_inst->GetItem()->BagType == item::BagTypeBandolier) || (main_inst->GetItem()->BagType == item::BagTypeQuiver)) + if ( + main_inst->GetItem()->BagSize < inst->GetItem()->Size || + main_inst->GetItem()->BagType == item::BagTypeBandolier || + main_inst->GetItem()->BagType == item::BagTypeQuiver + ) { continue; + } uint8 _bag_start = (free_slot > general_start) ? invbag::SLOT_BEGIN : bag_start; - for (uint8 free_bag_slot = _bag_start; (free_bag_slot < main_inst->GetItem()->BagSlots) && (free_bag_slot <= invbag::SLOT_END); ++free_bag_slot) { - if (!main_inst->GetItem(free_bag_slot)) + + for ( + uint8 free_bag_slot = _bag_start; + (free_bag_slot < main_inst->GetItem()->BagSlots) && (free_bag_slot <= invbag::SLOT_END); + ++free_bag_slot + ) { + if (!main_inst->GetItem(free_bag_slot)) { return InventoryProfile::CalcSlotId(free_slot, free_bag_slot); + } } } } @@ -993,7 +1067,8 @@ int16 EQ::InventoryProfile::FindFreeSlotForTradeItem(const ItemInstance* inst, i } // Opposite of below: Get parent bag slot_id from a slot inside of bag -int16 EQ::InventoryProfile::CalcSlotId(int16 slot_id) { +int16 EQ::InventoryProfile::CalcSlotId(int16 slot_id) +{ int16 parent_slot_id = INVALID_INDEX; // this is not a bag range... using this risks over-writing existing items @@ -1001,19 +1076,15 @@ int16 EQ::InventoryProfile::CalcSlotId(int16 slot_id) { // parent_slot_id = EmuConstants::BANK_BEGIN + (slot_id - EmuConstants::BANK_BEGIN) / EmuConstants::ITEM_CONTAINER_SIZE; //else if (slot_id >= 3100 && slot_id <= 3179) should be {3031..3110}..where did this range come from!!? (verified db save range) - if (slot_id >= invbag::GENERAL_BAGS_BEGIN && slot_id <= invbag::GENERAL_BAGS_END) { + if (EQ::ValueWithin(slot_id, invbag::GENERAL_BAGS_BEGIN, invbag::GENERAL_BAGS_END)) { parent_slot_id = invslot::GENERAL_BEGIN + (slot_id - invbag::GENERAL_BAGS_BEGIN) / invbag::SLOT_COUNT; - } - else if (slot_id >= invbag::CURSOR_BAG_BEGIN && slot_id <= invbag::CURSOR_BAG_END) { + } else if (EQ::ValueWithin(slot_id, invbag::CURSOR_BAG_BEGIN, invbag::CURSOR_BAG_END)) { parent_slot_id = invslot::slotCursor; - } - else if (slot_id >= invbag::BANK_BAGS_BEGIN && slot_id <= invbag::BANK_BAGS_END) { + } else if (EQ::ValueWithin(slot_id, invbag::BANK_BAGS_BEGIN, invbag::BANK_BAGS_END)) { parent_slot_id = invslot::BANK_BEGIN + (slot_id - invbag::BANK_BAGS_BEGIN) / invbag::SLOT_COUNT; - } - else if (slot_id >= invbag::SHARED_BANK_BAGS_BEGIN && slot_id <= invbag::SHARED_BANK_BAGS_END) { + } else if (EQ::ValueWithin(slot_id, invbag::SHARED_BANK_BAGS_BEGIN, invbag::SHARED_BANK_BAGS_END)) { parent_slot_id = invslot::SHARED_BANK_BEGIN + (slot_id - invbag::SHARED_BANK_BAGS_BEGIN) / invbag::SLOT_COUNT; - } - else if (slot_id >= invbag::TRADE_BAGS_BEGIN && slot_id <= invbag::TRADE_BAGS_END) { + } else if (EQ::ValueWithin(slot_id, invbag::TRADE_BAGS_BEGIN, invbag::TRADE_BAGS_END)) { parent_slot_id = invslot::TRADE_BEGIN + (slot_id - invbag::TRADE_BAGS_BEGIN) / invbag::SLOT_COUNT; } @@ -1021,25 +1092,23 @@ int16 EQ::InventoryProfile::CalcSlotId(int16 slot_id) { } // Calculate slot_id for an item within a bag -int16 EQ::InventoryProfile::CalcSlotId(int16 bagslot_id, uint8 bagidx) { - if (!InventoryProfile::SupportsContainers(bagslot_id)) +int16 EQ::InventoryProfile::CalcSlotId(int16 bagslot_id, uint8 bagidx) +{ + if (!InventoryProfile::SupportsContainers(bagslot_id)) { return INVALID_INDEX; + } int16 slot_id = INVALID_INDEX; - if (bagslot_id == invslot::slotCursor || bagslot_id == 8000) { + if (bagslot_id == invslot::slotCursor) { slot_id = invbag::CURSOR_BAG_BEGIN + bagidx; - } - else if (bagslot_id >= invslot::GENERAL_BEGIN && bagslot_id <= invslot::GENERAL_END) { + } else if (EQ::ValueWithin(bagslot_id, invslot::GENERAL_BEGIN, invslot::GENERAL_END)) { slot_id = invbag::GENERAL_BAGS_BEGIN + (bagslot_id - invslot::GENERAL_BEGIN) * invbag::SLOT_COUNT + bagidx; - } - else if (bagslot_id >= invslot::BANK_BEGIN && bagslot_id <= invslot::BANK_END) { + } else if (EQ::ValueWithin(bagslot_id, invslot::BANK_BEGIN, invslot::BANK_END)) { slot_id = invbag::BANK_BAGS_BEGIN + (bagslot_id - invslot::BANK_BEGIN) * invbag::SLOT_COUNT + bagidx; - } - else if (bagslot_id >= invslot::SHARED_BANK_BEGIN && bagslot_id <= invslot::SHARED_BANK_END) { + } else if (EQ::ValueWithin(bagslot_id, invslot::SHARED_BANK_BEGIN, invslot::SHARED_BANK_END)) { slot_id = invbag::SHARED_BANK_BAGS_BEGIN + (bagslot_id - invslot::SHARED_BANK_BEGIN) * invbag::SLOT_COUNT + bagidx; - } - else if (bagslot_id >= invslot::TRADE_BEGIN && bagslot_id <= invslot::TRADE_END) { + } else if (EQ::ValueWithin(bagslot_id, invslot::TRADE_BEGIN, invslot::TRADE_END)) { slot_id = invbag::TRADE_BAGS_BEGIN + (bagslot_id - invslot::TRADE_BEGIN) * invbag::SLOT_COUNT + bagidx; } @@ -1049,26 +1118,17 @@ int16 EQ::InventoryProfile::CalcSlotId(int16 bagslot_id, uint8 bagidx) { uint8 EQ::InventoryProfile::CalcBagIdx(int16 slot_id) { uint8 index = 0; - // this is not a bag range... using this risks over-writing existing items - //else if (slot_id >= EmuConstants::BANK_BEGIN && slot_id <= EmuConstants::BANK_END) - // index = (slot_id - EmuConstants::BANK_BEGIN) % EmuConstants::ITEM_CONTAINER_SIZE; - - if (slot_id >= invbag::GENERAL_BAGS_BEGIN && slot_id <= invbag::GENERAL_BAGS_END) { + if (EQ::ValueWithin(slot_id, invbag::GENERAL_BAGS_BEGIN, invbag::GENERAL_BAGS_END)) { index = (slot_id - invbag::GENERAL_BAGS_BEGIN) % invbag::SLOT_COUNT; - } - else if (slot_id >= invbag::CURSOR_BAG_BEGIN && slot_id <= invbag::CURSOR_BAG_END) { + } else if (EQ::ValueWithin(slot_id, invbag::CURSOR_BAG_BEGIN, invbag::CURSOR_BAG_END)) { index = (slot_id - invbag::CURSOR_BAG_BEGIN); // % invbag::SLOT_COUNT; - not needed since range is 10 slots - } - else if (slot_id >= invbag::BANK_BAGS_BEGIN && slot_id <= invbag::BANK_BAGS_END) { + } else if (EQ::ValueWithin(slot_id, invbag::BANK_BAGS_BEGIN, invbag::BANK_BAGS_END)) { index = (slot_id - invbag::BANK_BAGS_BEGIN) % invbag::SLOT_COUNT; - } - else if (slot_id >= invbag::SHARED_BANK_BAGS_BEGIN && slot_id <= invbag::SHARED_BANK_BAGS_END) { + } else if (EQ::ValueWithin(slot_id, invbag::SHARED_BANK_BAGS_BEGIN, invbag::SHARED_BANK_BAGS_END)) { index = (slot_id - invbag::SHARED_BANK_BAGS_BEGIN) % invbag::SLOT_COUNT; - } - else if (slot_id >= invbag::TRADE_BAGS_BEGIN && slot_id <= invbag::TRADE_BAGS_END) { + } else if (EQ::ValueWithin(slot_id, invbag::TRADE_BAGS_BEGIN, invbag::TRADE_BAGS_END)) { index = (slot_id - invbag::TRADE_BAGS_BEGIN) % invbag::SLOT_COUNT; - } - else if (slot_id >= invslot::WORLD_BEGIN && slot_id <= invslot::WORLD_END) { + } else if (EQ::ValueWithin(slot_id, invslot::WORLD_BEGIN, invslot::WORLD_END)) { index = (slot_id - invslot::WORLD_BEGIN); // % invbag::SLOT_COUNT; - not needed since range is 10 slots } @@ -1104,45 +1164,53 @@ int16 EQ::InventoryProfile::CalcSlotFromMaterial(uint8 material) uint8 EQ::InventoryProfile::CalcMaterialFromSlot(int16 equipslot) { - switch (equipslot) - { - case invslot::slotHead: - return textures::armorHead; - case invslot::slotChest: - return textures::armorChest; - case invslot::slotArms: - return textures::armorArms; - case invslot::slotWrist1: - //case SLOT_BRACER02: // non-live behavior - return textures::armorWrist; - case invslot::slotHands: - return textures::armorHands; - case invslot::slotLegs: - return textures::armorLegs; - case invslot::slotFeet: - return textures::armorFeet; - case invslot::slotPrimary: - return textures::weaponPrimary; - case invslot::slotSecondary: - return textures::weaponSecondary; - default: - return textures::materialInvalid; + switch (equipslot) { + case invslot::slotHead: + return textures::armorHead; + case invslot::slotChest: + return textures::armorChest; + case invslot::slotArms: + return textures::armorArms; + case invslot::slotWrist1: + return textures::armorWrist; + case invslot::slotHands: + return textures::armorHands; + case invslot::slotLegs: + return textures::armorLegs; + case invslot::slotFeet: + return textures::armorFeet; + case invslot::slotPrimary: + return textures::weaponPrimary; + case invslot::slotSecondary: + return textures::weaponSecondary; + default: + return textures::materialInvalid; } } bool EQ::InventoryProfile::CanItemFitInContainer(const ItemData *ItemToTry, const ItemData *Container) { - if (!ItemToTry || !Container) + if (!ItemToTry || !Container) { return false; + } - if (ItemToTry->Size > Container->BagSize) + if (ItemToTry->Size > Container->BagSize) { return false; + } - if ((Container->BagType == item::BagTypeQuiver) && (ItemToTry->ItemType != item::ItemTypeArrow)) + if ( + Container->BagType == item::BagTypeQuiver && + ItemToTry->ItemType != item::ItemTypeArrow + ) { return false; + } - if ((Container->BagType == item::BagTypeBandolier) && (ItemToTry->ItemType != item::ItemTypeSmallThrowing)) + if ( + Container->BagType == item::BagTypeBandolier && + ItemToTry->ItemType != item::ItemTypeSmallThrowing + ) { return false; + } return true; } @@ -1150,15 +1218,14 @@ bool EQ::InventoryProfile::CanItemFitInContainer(const ItemData *ItemToTry, cons bool EQ::InventoryProfile::SupportsClickCasting(int16 slot_id) { // there are a few non-potion items that identify as ItemTypePotion..so, we still need to ubiquitously include the equipment range - if (slot_id >= invslot::EQUIPMENT_BEGIN && slot_id <= invslot::EQUIPMENT_END) { + if (EQ::ValueWithin(slot_id, invslot::EQUIPMENT_BEGIN, invslot::EQUIPMENT_END)) { return true; - } - else if (slot_id >= invslot::GENERAL_BEGIN && slot_id <= invslot::GENERAL_END) { + } else if (EQ::ValueWithin(slot_id, invslot::GENERAL_BEGIN, invslot::GENERAL_END)) { return true; - } - else if (slot_id >= invbag::GENERAL_BAGS_BEGIN && slot_id <= invbag::GENERAL_BAGS_END) { - if (inventory::StaticLookup(m_mob_version)->AllowClickCastFromBag) + } else if (EQ::ValueWithin(slot_id, invbag::GENERAL_BAGS_BEGIN, invbag::GENERAL_BAGS_END)) { + if (inventory::StaticLookup(m_mob_version)->AllowClickCastFromBag) { return true; + } } return false; @@ -1167,13 +1234,11 @@ bool EQ::InventoryProfile::SupportsClickCasting(int16 slot_id) bool EQ::InventoryProfile::SupportsPotionBeltCasting(int16 slot_id) { // does this have the same criteria as 'SupportsClickCasting' above? (bag clicking per client) - if (slot_id >= invslot::EQUIPMENT_BEGIN && slot_id <= invslot::EQUIPMENT_END) { + if (EQ::ValueWithin(slot_id, invslot::EQUIPMENT_BEGIN, invslot::EQUIPMENT_END)) { return true; - } - else if (slot_id >= invslot::GENERAL_BEGIN && slot_id <= invslot::GENERAL_END) { + } else if (EQ::ValueWithin(slot_id, invslot::GENERAL_BEGIN, invslot::GENERAL_END)) { return true; - } - else if (slot_id >= invbag::GENERAL_BAGS_BEGIN && slot_id <= invbag::GENERAL_BAGS_END) { + } else if (EQ::ValueWithin(slot_id, invbag::GENERAL_BAGS_BEGIN, invbag::GENERAL_BAGS_END)) { return true; } @@ -1183,11 +1248,12 @@ bool EQ::InventoryProfile::SupportsPotionBeltCasting(int16 slot_id) // Test whether a given slot can support a container item bool EQ::InventoryProfile::SupportsContainers(int16 slot_id) { - if ((slot_id == invslot::slotCursor) || - (slot_id >= invslot::GENERAL_BEGIN && slot_id <= invslot::GENERAL_END) || - (slot_id >= invslot::BANK_BEGIN && slot_id <= invslot::BANK_END) || - (slot_id >= invslot::SHARED_BANK_BEGIN && slot_id <= invslot::SHARED_BANK_END) || - (slot_id >= invslot::TRADE_BEGIN && slot_id <= invslot::TRADE_END) + if ( + slot_id == invslot::slotCursor || + EQ::ValueWithin(slot_id, invslot::GENERAL_BEGIN, invslot::GENERAL_END) || + EQ::ValueWithin(slot_id, invslot::BANK_BEGIN, invslot::BANK_END) || + EQ::ValueWithin(slot_id, invslot::SHARED_BANK_BEGIN, invslot::SHARED_BANK_END) || + EQ::ValueWithin(slot_id, invslot::TRADE_BEGIN, invslot::TRADE_END) ) { return true; } @@ -1236,84 +1302,65 @@ uint8 EQ::InventoryProfile::FindBrightestLightType() uint8 brightest_light_type = 0; for (auto iter = m_worn.begin(); iter != m_worn.end(); ++iter) { - if ((iter->first < invslot::EQUIPMENT_BEGIN || iter->first > invslot::EQUIPMENT_END)) + if (!EQ::ValueWithin(iter->first, invslot::EQUIPMENT_BEGIN, invslot::EQUIPMENT_END)) { continue; + } - if (iter->first == invslot::slotAmmo) + if (iter->first == invslot::slotAmmo) { continue; + } - auto inst = iter->second; - if (inst == nullptr) + EQ::ItemInstance* inst = iter->second; + if (!inst) { continue; + } - auto item = inst->GetItem(); - if (item == nullptr) + const EQ::ItemData* item = inst->GetItem(); + if (!item) { continue; + } - if (lightsource::IsLevelGreater(item->Light, brightest_light_type)) + if (lightsource::IsLevelGreater(item->Light, brightest_light_type)) { brightest_light_type = item->Light; + } } uint8 general_light_type = 0; for (auto iter = m_inv.begin(); iter != m_inv.end(); ++iter) { - if (iter->first < invslot::GENERAL_BEGIN || iter->first > invslot::GENERAL_END) + if (!EQ::ValueWithin(iter->first, invslot::GENERAL_BEGIN, invslot::GENERAL_END)) { continue; + } - auto inst = iter->second; - if (inst == nullptr) + EQ::ItemInstance* inst = iter->second; + if (!inst) { continue; + } - auto item = inst->GetItem(); - if (item == nullptr) + const EQ::ItemData* item = inst->GetItem(); + if (!item) { continue; + } - if (!item->IsClassCommon()) - continue; - if (item->Light < 9 || item->Light > 13) + if (!item->IsClassCommon()) { continue; + } - if (lightsource::TypeToLevel(item->Light)) + if (!EQ::ValueWithin(item->Light, 9, 13)) { + continue; + } + + if (lightsource::TypeToLevel(item->Light)) { general_light_type = item->Light; + } } - if (lightsource::IsLevelGreater(general_light_type, brightest_light_type)) + if (lightsource::IsLevelGreater(general_light_type, brightest_light_type)) { brightest_light_type = general_light_type; + } return brightest_light_type; } -void EQ::InventoryProfile::dumpEntireInventory() { - - dumpWornItems(); - dumpInventory(); - dumpBankItems(); - dumpSharedBankItems(); - - std::cout << std::endl; -} - -void EQ::InventoryProfile::dumpWornItems() { - std::cout << "Worn items:" << std::endl; - dumpItemCollection(m_worn); -} - -void EQ::InventoryProfile::dumpInventory() { - std::cout << "Inventory items:" << std::endl; - dumpItemCollection(m_inv); -} - -void EQ::InventoryProfile::dumpBankItems() { - - std::cout << "Bank items:" << std::endl; - dumpItemCollection(m_bank); -} - -void EQ::InventoryProfile::dumpSharedBankItems() { - - std::cout << "Shared Bank items:" << std::endl; - dumpItemCollection(m_shbank); -} - int EQ::InventoryProfile::GetSlotByItemInstCollection(const std::map &collection, ItemInstance *inst) { for (auto iter = collection.begin(); iter != collection.end(); ++iter) { ItemInstance *t_inst = iter->second; @@ -1333,57 +1380,22 @@ int EQ::InventoryProfile::GetSlotByItemInstCollection(const std::map &collection) -{ - for (auto it = collection.cbegin(); it != collection.cend(); ++it) { - auto inst = it->second; - if (!inst || !inst->GetItem()) - continue; - - std::string slot = StringFormat("Slot %d: %s (%d)", it->first, it->second->GetItem()->Name, (inst->GetCharges() <= 0) ? 1 : inst->GetCharges()); - std::cout << slot << std::endl; - - dumpBagContents(inst, &it); - } -} - -void EQ::InventoryProfile::dumpBagContents(ItemInstance *inst, std::map::const_iterator *it) -{ - if (!inst || !inst->IsClassBag()) - return; - - // Go through bag, if bag - for (auto itb = inst->_cbegin(); itb != inst->_cend(); ++itb) { - ItemInstance* baginst = itb->second; - if (!baginst || !baginst->GetItem()) - continue; - - std::string subSlot = StringFormat(" Slot %d: %s (%d)", InventoryProfile::CalcSlotId((*it)->first, itb->first), - baginst->GetItem()->Name, (baginst->GetCharges() <= 0) ? 1 : baginst->GetCharges()); - std::cout << subSlot << std::endl; - } - -} - // Internal Method: Retrieves item within an inventory bucket EQ::ItemInstance* EQ::InventoryProfile::_GetItem(const std::map& bucket, int16 slot_id) const { - if (slot_id <= EQ::invslot::POSSESSIONS_END && slot_id >= EQ::invslot::POSSESSIONS_BEGIN) { - if ((((uint64)1 << slot_id) & m_lookup->PossessionsBitmask) == 0) + if (EQ::ValueWithin(slot_id, EQ::invslot::POSSESSIONS_BEGIN, EQ::invslot::POSSESSIONS_END)) { + if ((((uint64) 1 << slot_id) & m_lookup->PossessionsBitmask) == 0) { return nullptr; - } - else if (slot_id <= EQ::invslot::BANK_END && slot_id >= EQ::invslot::BANK_BEGIN) { - if (slot_id - EQ::invslot::BANK_BEGIN >= m_lookup->InventoryTypeSize.Bank) + } + } else if (EQ::ValueWithin(slot_id, EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END)) { + if (slot_id - EQ::invslot::BANK_BEGIN >= m_lookup->InventoryTypeSize.Bank) { return nullptr; + } } auto it = bucket.find(slot_id); - if (it != bucket.end()) { - return it->second; - } - // Not found! - return nullptr; + return it != bucket.end() ? it->second : nullptr; } // Internal Method: "put" item into bucket, without regard for what is currently in bucket @@ -1393,68 +1405,65 @@ int16 EQ::InventoryProfile::_PutItem(int16 slot_id, ItemInstance* inst) // What happens here when we _PutItem(MainCursor)? Bad things..really bad things... // // If putting a nullptr into slot, we need to remove slot without memory delete - if (inst == nullptr) { + if (!inst) { //Why do we not delete the poped item here???? PopItem(slot_id); return slot_id; } - int16 result = INVALID_INDEX; - int16 parentSlot = INVALID_INDEX; + int16 result = INVALID_INDEX; + int16 parent_slot = INVALID_INDEX; + + inst->SetEvolveEquipped(false); if (slot_id == invslot::slotCursor) { // Replace current item on cursor, if exists m_cursor.pop(); // no memory delete, clients of this function know what they are doing m_cursor.push_front(inst); result = slot_id; - } - else if (slot_id >= invslot::EQUIPMENT_BEGIN && slot_id <= invslot::EQUIPMENT_END) { + } else if (EQ::ValueWithin(slot_id, invslot::EQUIPMENT_BEGIN, invslot::EQUIPMENT_END)) { if ((((uint64)1 << slot_id) & m_lookup->PossessionsBitmask) != 0) { + if (inst->IsEvolving()) { + inst->SetEvolveEquipped(true); + } + m_worn[slot_id] = inst; result = slot_id; } - } - else if ((slot_id >= invslot::GENERAL_BEGIN && slot_id <= invslot::GENERAL_END)) { - if ((((uint64)1 << slot_id) & m_lookup->PossessionsBitmask) != 0) { + } else if (EQ::ValueWithin(slot_id, invslot::GENERAL_BEGIN, invslot::GENERAL_END)) { + if ((((uint64) 1 << slot_id) & m_lookup->PossessionsBitmask) != 0) { m_inv[slot_id] = inst; result = slot_id; } - } - else if (slot_id >= invslot::TRIBUTE_BEGIN && slot_id <= invslot::TRIBUTE_END) { + } else if (EQ::ValueWithin(slot_id, invslot::TRIBUTE_BEGIN, invslot::TRIBUTE_END)) { m_worn[slot_id] = inst; result = slot_id; - } - else if (slot_id >= invslot::GUILD_TRIBUTE_BEGIN && slot_id <= invslot::GUILD_TRIBUTE_END) { + } else if (EQ::ValueWithin(slot_id, invslot::GUILD_TRIBUTE_BEGIN, invslot::GUILD_TRIBUTE_END)) { m_worn[slot_id] = inst; result = slot_id; - } - else if (slot_id >= invslot::BANK_BEGIN && slot_id <= invslot::BANK_END) { + } else if (EQ::ValueWithin(slot_id, invslot::BANK_BEGIN, invslot::BANK_END)) { if (slot_id - EQ::invslot::BANK_BEGIN < m_lookup->InventoryTypeSize.Bank) { m_bank[slot_id] = inst; result = slot_id; } - } - else if (slot_id >= invslot::SHARED_BANK_BEGIN && slot_id <= invslot::SHARED_BANK_END) { + } else if (EQ::ValueWithin(slot_id, invslot::SHARED_BANK_BEGIN, invslot::SHARED_BANK_END)) { m_shbank[slot_id] = inst; result = slot_id; - } - else if (slot_id >= invslot::TRADE_BEGIN && slot_id <= invslot::TRADE_END) { + } else if (EQ::ValueWithin(slot_id, invslot::TRADE_BEGIN, invslot::TRADE_END)) { m_trade[slot_id] = inst; result = slot_id; - } - else { + } else { // Slot must be within a bag - parentSlot = InventoryProfile::CalcSlotId(slot_id); - ItemInstance* baginst = GetItem(parentSlot); // Get parent bag - if (baginst && baginst->IsClassBag()) - { + parent_slot = InventoryProfile::CalcSlotId(slot_id); + ItemInstance* baginst = GetItem(parent_slot); // Get parent bag + if (baginst && baginst->IsClassBag()) { baginst->_PutItem(InventoryProfile::CalcBagIdx(slot_id), inst); result = slot_id; } } if (result == INVALID_INDEX) { - LogError("Invalid slot_id specified ({}) with parent slot id ({})", slot_id, parentSlot); + LogError("Invalid slot_id specified ({}) with parent slot id ({})", slot_id, parent_slot); InventoryProfile::MarkDirty(inst); // Slot not found, clean up } @@ -1467,44 +1476,55 @@ int16 EQ::InventoryProfile::_HasItem(std::map& bucket, uin uint32 quantity_found = 0; for (auto iter = bucket.begin(); iter != bucket.end(); ++iter) { - if (iter->first <= EQ::invslot::POSSESSIONS_END && iter->first >= EQ::invslot::POSSESSIONS_BEGIN) { - if ((((uint64)1 << iter->first) & m_lookup->PossessionsBitmask) == 0) + if (EQ::ValueWithin(iter->first, EQ::invslot::POSSESSIONS_BEGIN, EQ::invslot::POSSESSIONS_END)) { + if ((((uint64) 1 << iter->first) & m_lookup->PossessionsBitmask) == 0) { continue; - } - else if (iter->first <= EQ::invslot::BANK_END && iter->first >= EQ::invslot::BANK_BEGIN) { - if (iter->first - EQ::invslot::BANK_BEGIN >= m_lookup->InventoryTypeSize.Bank) + } + } else if (EQ::ValueWithin(iter->first, EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END)) { + if (iter->first - EQ::invslot::BANK_BEGIN >= m_lookup->InventoryTypeSize.Bank) { continue; + } } - auto inst = iter->second; - if (inst == nullptr) { continue; } + EQ::ItemInstance* inst = iter->second; + if (!inst) { + continue; + } if (inst->GetID() == item_id) { quantity_found += (inst->GetCharges() <= 0) ? 1 : inst->GetCharges(); - if (quantity_found >= quantity) + if (quantity_found >= quantity) { return iter->first; + } } for (int index = invaug::SOCKET_BEGIN; index <= invaug::SOCKET_END; ++index) { - if (inst->GetAugmentItemID(index) == item_id && quantity <= 1) + if (inst->GetAugmentItemID(index) == item_id && quantity <= 1) { return invslot::SLOT_AUGMENT_GENERIC_RETURN; + } } - if (!inst->IsClassBag()) { continue; } + if (!inst->IsClassBag()) { + continue; + } for (auto bag_iter = inst->_cbegin(); bag_iter != inst->_cend(); ++bag_iter) { - auto bag_inst = bag_iter->second; - if (bag_inst == nullptr) { continue; } + EQ::ItemInstance* bag_inst = bag_iter->second; + if (!bag_inst) { + continue; + } if (bag_inst->GetID() == item_id) { quantity_found += (bag_inst->GetCharges() <= 0) ? 1 : bag_inst->GetCharges(); - if (quantity_found >= quantity) + if (quantity_found >= quantity) { return InventoryProfile::CalcSlotId(iter->first, bag_iter->first); + } } for (int index = invaug::SOCKET_BEGIN; index <= invaug::SOCKET_END; ++index) { - if (bag_inst->GetAugmentItemID(index) == item_id && quantity <= 1) + if (bag_inst->GetAugmentItemID(index) == item_id && quantity <= 1) { return invslot::SLOT_AUGMENT_GENERIC_RETURN; + } } } } @@ -1524,35 +1544,45 @@ int16 EQ::InventoryProfile::_HasItem(ItemInstQueue& iqueue, uint32 item_id, uint uint32 quantity_found = 0; for (auto iter = iqueue.cbegin(); iter != iqueue.cend(); ++iter) { - auto inst = *iter; - if (inst == nullptr) { continue; } + EQ::ItemInstance* inst = *iter; + if (!inst) { + continue; + } if (inst->GetID() == item_id) { quantity_found += (inst->GetCharges() <= 0) ? 1 : inst->GetCharges(); - if (quantity_found >= quantity) + if (quantity_found >= quantity) { return invslot::slotCursor; + } } for (int index = invaug::SOCKET_BEGIN; index <= invaug::SOCKET_END; ++index) { - if (inst->GetAugmentItemID(index) == item_id && quantity <= 1) + if (inst->GetAugmentItemID(index) == item_id && quantity <= 1) { return invslot::SLOT_AUGMENT_GENERIC_RETURN; + } } - if (!inst->IsClassBag()) { continue; } + if (!inst->IsClassBag()) { + continue; + } for (auto bag_iter = inst->_cbegin(); bag_iter != inst->_cend(); ++bag_iter) { - auto bag_inst = bag_iter->second; - if (bag_inst == nullptr) { continue; } + EQ::ItemInstance* bag_inst = bag_iter->second; + if (!bag_inst) { + continue; + } if (bag_inst->GetID() == item_id) { quantity_found += (bag_inst->GetCharges() <= 0) ? 1 : bag_inst->GetCharges(); - if (quantity_found >= quantity) + if (quantity_found >= quantity) { return InventoryProfile::CalcSlotId(invslot::slotCursor, bag_iter->first); + } } for (int index = invaug::SOCKET_BEGIN; index <= invaug::SOCKET_END; ++index) { - if (bag_inst->GetAugmentItemID(index) == item_id && quantity <= 1) + if (bag_inst->GetAugmentItemID(index) == item_id && quantity <= 1) { return invslot::SLOT_AUGMENT_GENERIC_RETURN; + } } } @@ -1569,34 +1599,43 @@ int16 EQ::InventoryProfile::_HasItemByUse(std::map& bucket uint32 quantity_found = 0; for (auto iter = bucket.begin(); iter != bucket.end(); ++iter) { - if (iter->first <= EQ::invslot::POSSESSIONS_END && iter->first >= EQ::invslot::POSSESSIONS_BEGIN) { - if ((((uint64)1 << iter->first) & m_lookup->PossessionsBitmask) == 0) + if (EQ::ValueWithin(iter->first, EQ::invslot::POSSESSIONS_BEGIN, EQ::invslot::POSSESSIONS_END)) { + if ((((uint64) 1 << iter->first) & m_lookup->PossessionsBitmask) == 0) { continue; - } - else if (iter->first <= EQ::invslot::BANK_END && iter->first >= EQ::invslot::BANK_BEGIN) { - if (iter->first - EQ::invslot::BANK_BEGIN >= m_lookup->InventoryTypeSize.Bank) + } + } else if (EQ::ValueWithin(iter->first, EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END)) { + if (iter->first - EQ::invslot::BANK_BEGIN >= m_lookup->InventoryTypeSize.Bank) { continue; + } } - auto inst = iter->second; - if (inst == nullptr) { continue; } + EQ::ItemInstance* inst = iter->second; + if (!inst) { + continue; + } if (inst->IsClassCommon() && inst->GetItem()->ItemType == use) { quantity_found += (inst->GetCharges() <= 0) ? 1 : inst->GetCharges(); - if (quantity_found >= quantity) + if (quantity_found >= quantity) { return iter->first; + } } - if (!inst->IsClassBag()) { continue; } + if (!inst->IsClassBag()) { + continue; + } for (auto bag_iter = inst->_cbegin(); bag_iter != inst->_cend(); ++bag_iter) { - auto bag_inst = bag_iter->second; - if (bag_inst == nullptr) { continue; } + EQ::ItemInstance* bag_inst = bag_iter->second; + if (!bag_inst) { + continue; + } if (bag_inst->IsClassCommon() && bag_inst->GetItem()->ItemType == use) { quantity_found += (bag_inst->GetCharges() <= 0) ? 1 : bag_inst->GetCharges(); - if (quantity_found >= quantity) + if (quantity_found >= quantity) { return InventoryProfile::CalcSlotId(iter->first, bag_iter->first); + } } } } @@ -1610,25 +1649,33 @@ int16 EQ::InventoryProfile::_HasItemByUse(ItemInstQueue& iqueue, uint8 use, uint uint32 quantity_found = 0; for (auto iter = iqueue.cbegin(); iter != iqueue.cend(); ++iter) { - auto inst = *iter; - if (inst == nullptr) { continue; } + EQ::ItemInstance* inst = *iter; + if (!inst) { + continue; + } if (inst->IsClassCommon() && inst->GetItem()->ItemType == use) { quantity_found += (inst->GetCharges() <= 0) ? 1 : inst->GetCharges(); - if (quantity_found >= quantity) + if (quantity_found >= quantity) { return invslot::slotCursor; + } } - if (!inst->IsClassBag()) { continue; } + if (!inst->IsClassBag()) { + continue; + } for (auto bag_iter = inst->_cbegin(); bag_iter != inst->_cend(); ++bag_iter) { - auto bag_inst = bag_iter->second; - if (bag_inst == nullptr) { continue; } + EQ::ItemInstance* bag_inst = bag_iter->second; + if (!bag_inst) { + continue; + } if (bag_inst->IsClassCommon() && bag_inst->GetItem()->ItemType == use) { quantity_found += (bag_inst->GetCharges() <= 0) ? 1 : bag_inst->GetCharges(); - if (quantity_found >= quantity) + if (quantity_found >= quantity) { return InventoryProfile::CalcSlotId(invslot::slotCursor, bag_iter->first); + } } } @@ -1642,44 +1689,57 @@ int16 EQ::InventoryProfile::_HasItemByUse(ItemInstQueue& iqueue, uint8 use, uint int16 EQ::InventoryProfile::_HasItemByLoreGroup(std::map& bucket, uint32 loregroup) { for (auto iter = bucket.begin(); iter != bucket.end(); ++iter) { - if (iter->first <= EQ::invslot::POSSESSIONS_END && iter->first >= EQ::invslot::POSSESSIONS_BEGIN) { - if ((((uint64)1 << iter->first) & m_lookup->PossessionsBitmask) == 0) + if (EQ::ValueWithin(iter->first, EQ::invslot::POSSESSIONS_BEGIN, EQ::invslot::POSSESSIONS_END)) { + if ((((uint64) 1 << iter->first) & m_lookup->PossessionsBitmask) == 0) { continue; - } - else if (iter->first <= EQ::invslot::BANK_END && iter->first >= EQ::invslot::BANK_BEGIN) { - if (iter->first - EQ::invslot::BANK_BEGIN >= m_lookup->InventoryTypeSize.Bank) + } + } else if (EQ::ValueWithin(iter->first, EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END)) { + if (iter->first - EQ::invslot::BANK_BEGIN >= m_lookup->InventoryTypeSize.Bank) { continue; + } } - auto inst = iter->second; - if (inst == nullptr) { continue; } + EQ::ItemInstance* inst = iter->second; + if (!inst) { + continue; + } - if (inst->GetItem()->LoreGroup == loregroup) + if (inst->GetItem()->LoreGroup == loregroup) { return iter->first; + } for (int index = invaug::SOCKET_BEGIN; index <= invaug::SOCKET_END; ++index) { auto aug_inst = inst->GetAugment(index); if (aug_inst == nullptr) { continue; } - if (aug_inst->GetItem()->LoreGroup == loregroup) + if (aug_inst->GetItem()->LoreGroup == loregroup) { return invslot::SLOT_AUGMENT_GENERIC_RETURN; + } } - if (!inst->IsClassBag()) { continue; } + if (!inst->IsClassBag()) { + continue; + } for (auto bag_iter = inst->_cbegin(); bag_iter != inst->_cend(); ++bag_iter) { - auto bag_inst = bag_iter->second; - if (bag_inst == nullptr) { continue; } + EQ::ItemInstance* bag_inst = bag_iter->second; + if (!bag_inst) { + continue; + } - if (bag_inst->IsClassCommon() && bag_inst->GetItem()->LoreGroup == loregroup) + if (bag_inst->IsClassCommon() && bag_inst->GetItem()->LoreGroup == loregroup) { return InventoryProfile::CalcSlotId(iter->first, bag_iter->first); + } for (int index = invaug::SOCKET_BEGIN; index <= invaug::SOCKET_END; ++index) { - auto aug_inst = bag_inst->GetAugment(index); - if (aug_inst == nullptr) { continue; } + EQ::ItemInstance* aug_inst = bag_inst->GetAugment(index); + if (!aug_inst) { + continue; + } - if (aug_inst->GetItem()->LoreGroup == loregroup) + if (aug_inst->GetItem()->LoreGroup == loregroup) { return invslot::SLOT_AUGMENT_GENERIC_RETURN; + } } } } @@ -1691,35 +1751,49 @@ int16 EQ::InventoryProfile::_HasItemByLoreGroup(std::map& int16 EQ::InventoryProfile::_HasItemByLoreGroup(ItemInstQueue& iqueue, uint32 loregroup) { for (auto iter = iqueue.cbegin(); iter != iqueue.cend(); ++iter) { - auto inst = *iter; - if (inst == nullptr) { continue; } - - if (inst->GetItem()->LoreGroup == loregroup) - return invslot::slotCursor; - - for (int index = invaug::SOCKET_BEGIN; index <= invaug::SOCKET_END; ++index) { - auto aug_inst = inst->GetAugment(index); - if (aug_inst == nullptr) { continue; } - - if (aug_inst->GetItem()->LoreGroup == loregroup) - return invslot::SLOT_AUGMENT_GENERIC_RETURN; + EQ::ItemInstance* inst = *iter; + if (!inst) { + continue; } - if (!inst->IsClassBag()) { continue; } + if (inst->GetItem()->LoreGroup == loregroup) { + return invslot::slotCursor; + } + + for (int index = invaug::SOCKET_BEGIN; index <= invaug::SOCKET_END; ++index) { + EQ::ItemInstance* aug_inst = inst->GetAugment(index); + if (!aug_inst) { + continue; + } + + if (aug_inst->GetItem()->LoreGroup == loregroup) { + return invslot::SLOT_AUGMENT_GENERIC_RETURN; + } + } + + if (!inst->IsClassBag()) { + continue; + } for (auto bag_iter = inst->_cbegin(); bag_iter != inst->_cend(); ++bag_iter) { - auto bag_inst = bag_iter->second; - if (bag_inst == nullptr) { continue; } + EQ::ItemInstance* bag_inst = bag_iter->second; + if (!bag_inst) { + continue; + } - if (bag_inst->IsClassCommon() && bag_inst->GetItem()->LoreGroup == loregroup) + if (bag_inst->IsClassCommon() && bag_inst->GetItem()->LoreGroup == loregroup) { return InventoryProfile::CalcSlotId(invslot::slotCursor, bag_iter->first); + } for (int index = invaug::SOCKET_BEGIN; index <= invaug::SOCKET_END; ++index) { - auto aug_inst = bag_inst->GetAugment(index); - if (aug_inst == nullptr) { continue; } + EQ::ItemInstance* aug_inst = bag_inst->GetAugment(index); + if (!aug_inst) { + continue; + } - if (aug_inst->GetItem()->LoreGroup == loregroup) + if (aug_inst->GetItem()->LoreGroup == loregroup) { return invslot::SLOT_AUGMENT_GENERIC_RETURN; + } } } @@ -1744,42 +1818,9 @@ std::vector EQ::InventoryProfile::GetAugmentIDsBySlotID(int16 slot_id) return augments; } -std::vector EQ::InventoryProfile::FindAllFreeSlotsThatFitItem(const EQ::ItemData *item_data) -{ - std::vector free_slots{}; - for (int16 i = EQ::invslot::GENERAL_BEGIN; i <= EQ::invslot::GENERAL_END; i++) { - if ((((uint64) 1 << i) & GetLookup()->PossessionsBitmask) == 0) { - continue; - } - - EQ::ItemInstance *inv_item = GetItem(i); - - if (!inv_item) { - // Found available slot in personal inventory - free_slots.push_back(i); - } - - if (inv_item->IsClassBag() && - EQ::InventoryProfile::CanItemFitInContainer(item_data, inv_item->GetItem())) { - - int16 base_slot_id = EQ::InventoryProfile::CalcSlotId(i, EQ::invbag::SLOT_BEGIN); - uint8 bag_size = inv_item->GetItem()->BagSlots; - - for (uint8 bag_slot = EQ::invbag::SLOT_BEGIN; bag_slot < bag_size; bag_slot++) { - auto bag_item = GetItem(base_slot_id + bag_slot); - if (!bag_item) { - // Found available slot within bag - free_slots.push_back(i); - } - } - } - } - return free_slots; -} - int16 EQ::InventoryProfile::FindFirstFreeSlotThatFitsItem(const EQ::ItemData *item_data) { - for (int16 i = EQ::invslot::GENERAL_BEGIN; i <= EQ::invslot::GENERAL_END; i++) { + for (int16 i = EQ::invslot::GENERAL_BEGIN; i <= EQ::invslot::GENERAL_END; i++) { if ((((uint64) 1 << i) & GetLookup()->PossessionsBitmask) == 0) { continue; } @@ -1807,4 +1848,237 @@ int16 EQ::InventoryProfile::FindFirstFreeSlotThatFitsItem(const EQ::ItemData *it } } return 0; -} \ No newline at end of file +} + +//This function has the same flaw as noted above +// Helper functions for evolving items +int16 EQ::InventoryProfile::HasEvolvingItem(uint64 evolve_unique_id, uint8 quantity, uint8 where) +{ + int16 slot_id = INVALID_INDEX; + + // Altered by Father Nitwit to support a specification of + // where to search, with a default value to maintain compatibility + + // Check each inventory bucket + if (where & invWhereWorn) { + slot_id = _HasEvolvingItem(m_worn, evolve_unique_id, quantity); + if (slot_id != INVALID_INDEX) { + return slot_id; + } + } + + if (where & invWherePersonal) { + slot_id = _HasEvolvingItem(m_inv, evolve_unique_id, quantity); + if (slot_id != INVALID_INDEX) { + return slot_id; + } + } + + if (where & invWhereBank) { + slot_id = _HasEvolvingItem(m_bank, evolve_unique_id, quantity); + if (slot_id != INVALID_INDEX) { + return slot_id; + } + } + + if (where & invWhereSharedBank) { + slot_id = _HasEvolvingItem(m_shbank, evolve_unique_id, quantity); + if (slot_id != INVALID_INDEX) { + return slot_id; + } + } + + if (where & invWhereTrading) { + slot_id = _HasEvolvingItem(m_trade, evolve_unique_id, quantity); + if (slot_id != INVALID_INDEX) { + return slot_id; + } + } + + // Behavioral change - Limbo is no longer checked due to improper handling of return value + if (where & invWhereCursor) { + // Check cursor queue + slot_id = _HasEvolvingItem(m_cursor, evolve_unique_id, quantity); + if (slot_id != INVALID_INDEX) { + return slot_id; + } + } + + return slot_id; +} + +// Internal Method: Checks an inventory bucket for a particular evolving item unique id +int16 EQ::InventoryProfile::_HasEvolvingItem( + std::map &bucket, uint64 evolve_unique_id, uint8 quantity) +{ + uint32 quantity_found = 0; + + for (auto const &[key, value]: bucket) { + if (!value) { + continue; + } + + if (key <= EQ::invslot::POSSESSIONS_END && key >= EQ::invslot::POSSESSIONS_BEGIN) { + if (((uint64) 1 << key & m_lookup->PossessionsBitmask) == 0) { + continue; + } + } + else if (key <= EQ::invslot::BANK_END && key >= EQ::invslot::BANK_BEGIN) { + if (key - EQ::invslot::BANK_BEGIN >= m_lookup->InventoryTypeSize.Bank) { + continue; + } + } + + + if (value->GetEvolveUniqueID() == evolve_unique_id) { + quantity_found += value->GetCharges() <= 0 ? 1 : value->GetCharges(); + if (quantity_found >= quantity) { + return key; + } + } + + for (int index = invaug::SOCKET_BEGIN; index <= invaug::SOCKET_END; ++index) { + if (value->GetAugmentEvolveUniqueID(index) == evolve_unique_id && quantity <= 1) { + return invslot::SLOT_AUGMENT_GENERIC_RETURN; + } + } + + if (!value->IsClassBag()) { + continue; + } + + for (auto const &[bag_key, bag_value]: *value->GetContents()) { + if (!bag_value) { + continue; + } + + if (bag_value->GetEvolveUniqueID() == evolve_unique_id) { + quantity_found += bag_value->GetCharges() <= 0 ? 1 : bag_value->GetCharges(); + if (quantity_found >= quantity) { + return CalcSlotId(key, bag_key); + } + } + + for (int index = invaug::SOCKET_BEGIN; index <= invaug::SOCKET_END; ++index) { + if (bag_value->GetAugmentEvolveUniqueID(index) == evolve_unique_id && quantity <= 1) { + return invslot::SLOT_AUGMENT_GENERIC_RETURN; + } + } + } + } + + return INVALID_INDEX; +} + +// Internal Method: Checks an inventory queue type bucket for a particular item +int16 EQ::InventoryProfile::_HasEvolvingItem(ItemInstQueue &iqueue, uint64 evolve_unique_id, uint8 quantity) +{ + // The downfall of this (these) queue procedure is that callers presume that when an item is + // found, it is presented as being available on the cursor. In cases of a parity check, this + // is sufficient. However, in cases where referential criteria is considered, this can lead + // to unintended results. Funtionality should be observed when referencing the return value + // of this query + + uint32 quantity_found = 0; + + for (auto const &inst: iqueue) { + if (!inst) { + continue; + } + + if (inst->GetEvolveUniqueID() == evolve_unique_id) { + quantity_found += inst->GetCharges() <= 0 ? 1 : inst->GetCharges(); + if (quantity_found >= quantity) { + return invslot::slotCursor; + } + } + + for (int index = invaug::SOCKET_BEGIN; index <= invaug::SOCKET_END; ++index) { + if (inst->GetAugmentEvolveUniqueID(index) == evolve_unique_id && quantity <= 1) { + return invslot::SLOT_AUGMENT_GENERIC_RETURN; + } + } + + if (!inst->IsClassBag()) { + continue; + } + + for (auto const &[bag_key, bag_value]: *inst->GetContents()) { + if (!bag_value) { + continue; + } + + if (bag_value->GetEvolveUniqueID() == evolve_unique_id) { + quantity_found += bag_value->GetCharges() <= 0 ? 1 : bag_value->GetCharges(); + if (quantity_found >= quantity) { + return CalcSlotId(invslot::slotCursor, bag_key); + } + } + + for (int index = invaug::SOCKET_BEGIN; index <= invaug::SOCKET_END; ++index) { + if (bag_value->GetAugmentEvolveUniqueID(index) == evolve_unique_id && quantity <= 1) { + return invslot::SLOT_AUGMENT_GENERIC_RETURN; + } + } + } + + // We only check the visible cursor due to lack of queue processing ability (client allows duplicate in limbo) + break; + } + + return INVALID_INDEX; +} + + +int16 EQ::InventoryProfile::FindFirstFreeSlotThatFitsItemWithStacking(ItemInstance *item_inst) const +{ + auto item_data = item_inst->GetItem(); + if (!item_data) { + return INVALID_INDEX; + } + + for (int16 i = invslot::GENERAL_BEGIN; i <= invslot::GENERAL_END; i++) { + auto const inv_item = GetItem(i); + if (!inv_item) { + // Found available slot in personal inventory + // Anything will fit here + return i; + } + + if (item_data->IsClassBag() && item_inst->IsNoneEmptyContainer()) { + // If the inbound item is a bag with items, it cannot be stored within a bag + // Move to next potential slot + continue; + } + + if (inv_item->GetID() == item_data->ID && item_data->Stackable) { + if (item_inst->GetCharges() + inv_item->GetCharges() <= item_data->StackSize) { + // Found a personal inventory slot that has room for a stackable addition + return i; + } + } + + if (inv_item->IsClassBag() && CanItemFitInContainer(item_data, inv_item->GetItem())) { + int16 const base_slot_id = CalcSlotId(i, invbag::SLOT_BEGIN); + uint8 const bag_size = inv_item->GetItem()->BagSlots; + uint8 const item_size = item_data->Size; + + for (uint8 bag_slot = invbag::SLOT_BEGIN; bag_slot < bag_size; bag_slot++) { + auto bag_item = GetItem(base_slot_id + bag_slot); + if (!bag_item) { + // Found available slot within bag that will hold inbound item + return base_slot_id + bag_slot; + } + + if (bag_item && item_data->Stackable && bag_item->GetID() == item_data->ID) { + if (item_inst->GetCharges() + bag_item->GetCharges() <= item_data->StackSize) { + // Found a bag slot has room for a stackable addition + return base_slot_id + bag_slot; + } + } + } + } + } + + return INVALID_INDEX; +} diff --git a/common/inventory_profile.h b/common/inventory_profile.h index 100d5ebd8..577342785 100644 --- a/common/inventory_profile.h +++ b/common/inventory_profile.h @@ -57,6 +57,8 @@ public: inline std::list::const_iterator cbegin() { return m_list.cbegin(); } inline std::list::const_iterator cend() { return m_list.cend(); } + inline std::list::iterator begin() { return m_list.begin(); } + inline std::list::iterator end() { return m_list.end(); } inline int size() { return static_cast(m_list.size()); } // TODO: change to size_t inline bool empty() { return m_list.empty(); } @@ -147,13 +149,13 @@ namespace EQ bool HasItemEquippedByID(uint32 item_id); // Check how many of a specific item the player has equipped by Item ID - int CountItemEquippedByID(uint32 item_id); + uint32 CountItemEquippedByID(uint32 item_id); // Check if player has a specific augment equipped by Item ID bool HasAugmentEquippedByID(uint32 item_id); // Check how many of a specific augment the player has equipped by Item ID - int CountAugmentEquippedByID(uint32 item_id); + uint32 CountAugmentEquippedByID(uint32 item_id); // Get a list of augments from a specific slot ID std::vector GetAugmentIDsBySlotID(int16 slot_id); @@ -176,8 +178,8 @@ namespace EQ // Locate an available inventory slot int16 FindFreeSlot(bool for_bag, bool try_cursor, uint8 min_size = 0, bool is_arrow = false); int16 FindFreeSlotForTradeItem(const ItemInstance* inst, int16 general_start = invslot::GENERAL_BEGIN, uint8 bag_start = invbag::SLOT_BEGIN); - std::vector FindAllFreeSlotsThatFitItem(const EQ::ItemData *inst); int16 FindFirstFreeSlotThatFitsItem(const EQ::ItemData *inst); + int16 FindFirstFreeSlotThatFitsItemWithStacking(ItemInstance *inst) const; // Calculate slot_id for an item within a bag static int16 CalcSlotId(int16 slot_id); // Calc parent bag's slot_id @@ -199,26 +201,25 @@ namespace EQ uint8 FindBrightestLightType(); - void dumpEntireInventory(); - void dumpWornItems(); - void dumpInventory(); - void dumpBankItems(); - void dumpSharedBankItems(); - void SetCustomItemData(uint32 character_id, int16 slot_id, const std::string &identifier, const std::string& value); void SetCustomItemData(uint32 character_id, int16 slot_id, const std::string &identifier, int value); void SetCustomItemData(uint32 character_id, int16 slot_id, const std::string &identifier, float value); void SetCustomItemData(uint32 character_id, int16 slot_id, const std::string &identifier, bool value); std::string GetCustomItemData(int16 slot_id, const std::string& identifier); static const int GetItemStatValue(uint32 item_id, const std::string& identifier); + + std::map& GetWorn() { return m_worn; } + std::map& GetPersonal() { return m_inv; } + int16 HasEvolvingItem(uint64 evolve_unique_id, uint8 quantity, uint8 where); + + inline int16 PushItem(int16 slot_id, ItemInstance* inst) { return _PutItem(slot_id, inst); } + protected: /////////////////////////////// // Protected Methods /////////////////////////////// int GetSlotByItemInstCollection(const std::map &collection, ItemInstance *inst); - void dumpItemCollection(const std::map &collection); - void dumpBagContents(ItemInstance *inst, std::map::const_iterator *it); // Retrieves item within an inventory bucket ItemInstance* _GetItem(const std::map& bucket, int16 slot_id) const; @@ -233,6 +234,8 @@ namespace EQ int16 _HasItemByUse(ItemInstQueue& iqueue, uint8 use, uint8 quantity); int16 _HasItemByLoreGroup(std::map& bucket, uint32 loregroup); int16 _HasItemByLoreGroup(ItemInstQueue& iqueue, uint32 loregroup); + int16 _HasEvolvingItem(std::map& bucket, uint64 evolve_unique_id, uint8 quantity); + int16 _HasEvolvingItem(ItemInstQueue& iqueue, uint64 evolve_unique_id, uint8 quantity); // Player inventory diff --git a/common/ip_util.cpp b/common/ip_util.cpp index 47689a2fa..df4abcfc0 100644 --- a/common/ip_util.cpp +++ b/common/ip_util.cpp @@ -259,3 +259,84 @@ bool IpUtil::IsIPAddress(const std::string &ip_address) } +#include +#ifdef _WIN32 +#include + #pragma comment(lib, "ws2_32.lib") // Link against Winsock library +#else +#include +#include +#include +#include +#endif + +#include +#include +#ifdef _WIN32 +#include + #include // For inet_pton + #pragma comment(lib, "ws2_32.lib") // Link against Winsock library +#else +#include +#include +#include +#include // For inet_pton +#include +#endif + +bool IpUtil::IsPortInUse(const std::string& ip, int port) { + bool in_use = false; + +#ifdef _WIN32 + WSADATA wsaData; + if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { + std::cerr << "WSAStartup failed\n"; + return true; // Assume in use on failure + } +#endif + + int sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) { +#ifdef _WIN32 + WSACleanup(); +#endif + 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); + + // Convert IP address from string to binary format + if (inet_pton(AF_INET, ip.c_str(), &addr.sin_addr) <= 0) { + std::cerr << "Invalid IP address format: " << ip << std::endl; +#ifdef _WIN32 + closesocket(sock); + WSACleanup(); +#else + close(sock); +#endif + return true; // Assume in use on failure + } + + if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + in_use = true; // Bind failed, port is in use + } + +#ifdef _WIN32 + closesocket(sock); + WSACleanup(); +#else + close(sock); +#endif + + return in_use; +} diff --git a/common/ip_util.h b/common/ip_util.h index 3f67492a4..3d96f219c 100644 --- a/common/ip_util.h +++ b/common/ip_util.h @@ -37,6 +37,7 @@ public: int port ); static bool IsIPAddress(const std::string &ip_address); + static bool IsPortInUse(const std::string& ip, int port); }; diff --git a/common/ipc_mutex.cpp b/common/ipc_mutex.cpp index e2c123f22..ebd13a693 100644 --- a/common/ipc_mutex.cpp +++ b/common/ipc_mutex.cpp @@ -55,7 +55,7 @@ namespace EQ { EQ_EXCEPT("IPC Mutex", "Could not create mutex."); } #else - std::string final_name = fmt::format("{}/{}.lock", path.GetSharedMemoryPath(), name); + std::string final_name = fmt::format("{}/{}.lock", PathManager::Instance()->GetSharedMemoryPath(), name); #ifdef __DARWIN #if __DARWIN_C_LEVEL < 200809L diff --git a/common/item_data.cpp b/common/item_data.cpp index 6dcb1f577..c31bad37c 100644 --- a/common/item_data.cpp +++ b/common/item_data.cpp @@ -220,6 +220,34 @@ bool EQ::ItemData::IsType1HWeapon() const return ((ItemType == item::ItemType1HBlunt) || (ItemType == item::ItemType1HSlash) || (ItemType == item::ItemType1HPiercing) || (ItemType == item::ItemTypeMartial)); } +bool EQ::ItemData::IsPetUsable() const +{ + if (ItemClass == item::ItemClassBag) { + return true; + } + + // if it's a misc item and has slots, it's wearable + // this item type is conflated with many other item types + if (ItemClass == item::ItemTypeMisc && Slots != 0) { + return true; + } + + switch (ItemType) { + case item::ItemType1HBlunt: + case item::ItemType1HSlash: + case item::ItemType1HPiercing: + case item::ItemType2HBlunt: + case item::ItemType2HSlash: + case item::ItemTypeMartial: + case item::ItemTypeShield: + case item::ItemTypeArmor: + case item::ItemTypeJewelry: + return true; + default: + return false; + } +} + bool EQ::ItemData::IsType2HWeapon() const { return ((ItemType == item::ItemType2HBlunt) || (ItemType == item::ItemType2HSlash) || (ItemType == item::ItemType2HPiercing)); diff --git a/common/item_data.h b/common/item_data.h index 449a86759..6967c218c 100644 --- a/common/item_data.h +++ b/common/item_data.h @@ -438,7 +438,7 @@ namespace EQ //uint32 Unk054 {}; int16 MaxCharges {}; // Maximum charges items can hold: -1 if not a chargeable item uint8 ItemType {}; // Item Type/Skill (itemClass* from above) - int32 SubType {}; // Some items have sub types that can be used for other things (unbreakable fishing poles, SE_FFItemClass) + int32 SubType {}; // Some items have sub types that can be used for other things (unbreakable fishing poles, SpellEffect::FFItemClass) uint8 Material {}; // Item material type uint32 HerosForgeModel {};// Hero's Forge Armor Model Type (2-13?) float SellRate {}; // Sell rate @@ -550,6 +550,7 @@ namespace EQ bool IsType1HWeapon() const; bool IsType2HWeapon() const; bool IsTypeShield() const; + bool IsPetUsable() const; bool IsQuestItem() const; static bool CheckLoreConflict(const ItemData* l_item, const ItemData* r_item); diff --git a/common/item_fieldlist.h b/common/item_fieldlist.h deleted file mode 100644 index 3c00f1e44..000000000 --- a/common/item_fieldlist.h +++ /dev/null @@ -1,206 +0,0 @@ -/* - - -These fields must be in the order of how they are serialized! - - - -*/ - -F(itemclass) -F(name) -F(lore) -F(idfile) -F(id) -F(weight) -F(norent) -F(nodrop) -F(size) -F(slots) -F(price) -F(icon) -F(UNK012) -F(UNK013) -F(benefitflag) -F(tradeskills) -F(cr) -F(dr) -F(pr) -F(mr) -F(fr) -F(astr) -F(asta) -F(aagi) -F(adex) -F(acha) -F(aint) -F(awis) -F(hp) -F(mana) -F(ac) -F(deity) -F(skillmodvalue) -F(UNK033) -F(skillmodmax) -F(skillmodtype) -F(banedmgrace) -F(banedmgamt) -F(banedmgbody) -F(magic) -F(casttime_) -F(reqlevel) -F(bardtype) -F(bardvalue) -F(light) -F(delay) -F(reclevel) -F(recskill) -F(elemdmgtype) -F(elemdmgamt) -F(range) -F(damage) -F(color) -F(classes) -F(races) -F(UNK054) -F(maxcharges) -F(itemtype) -F(material) -F(herosforgemodel) -F(sellrate) -F(UNK059) -F(casttime) -F(elitematerial) -F(procrate) -F(combateffects) -F(shielding) -F(stunresist) -F(strikethrough) -F(extradmgskill) -F(extradmgamt) -F(spellshield) -F(avoidance) -F(accuracy) -F(charmfileid) -F(factionmod1) -F(factionmod2) -F(factionmod3) -F(factionmod4) -F(factionamt1) -F(factionamt2) -F(factionamt3) -F(factionamt4) -F(charmfile) -F(augtype) -F(augslot1type) -F(augslot1visible) -F(augslot2type) -F(augslot2visible) -F(augslot3type) -F(augslot3visible) -F(augslot4type) -F(augslot4visible) -F(augslot5type) -F(augslot5visible) -F(augslot6type) -F(augslot6visible) -F(ldontheme) -F(ldonprice) -F(ldonsold) -F(bagtype) -F(bagslots) -F(bagsize) -F(bagwr) -F(book) -F(booktype) -F(filename) -F(banedmgraceamt) -F(augrestrict) -F(loregroup) -F(pendingloreflag) -F(artifactflag) -F(summonedflag) -F(favor) -F(fvnodrop) -F(endur) -F(dotshielding) -F(attack) -F(regen) -F(manaregen) -F(enduranceregen) -F(haste) -F(damageshield) -F(recastdelay) -F(recasttype) -F(guildfavor) -F(augdistiller) -F(UNK123) -F(UNK124) -F(attuneable) -F(nopet) -F(UNK127) -F(pointtype) -F(potionbelt) -F(potionbeltslots) -F(stacksize) -F(notransfer) -F(stackable) -F(UNK134) -F(clickeffect) -F(clicktype) -F(clicklevel) -F(clicklevel2) -F(proceffect) -F(proctype) -F(proclevel) -F(proclevel2) -F(worneffect) -F(worntype) -F(wornlevel) -F(wornlevel2) -F(focuseffect) -F(focustype) -F(focuslevel) -F(focuslevel2) -F(scrolleffect) -F(scrolltype) -F(scrolllevel) -F(scrolllevel2) -F(bardeffect) -F(bardeffecttype) -F(bardlevel2) -F(bardlevel) -F(questitemflag) -F(svcorruption) -F(purity) -F(evoitem) -F(evoid) -F(evolvinglevel) -F(evomax) -F(backstabdmg) -F(dsmitigation) -F(heroic_str) -F(heroic_int) -F(heroic_wis) -F(heroic_agi) -F(heroic_dex) -F(heroic_sta) -F(heroic_cha) -F(heroic_mr) -F(heroic_fr) -F(heroic_cr) -F(heroic_dr) -F(heroic_pr) -F(heroic_svcorrup) -F(healamt) -F(spelldmg) -F(ldonsellbackrate) -F(scriptfileid) -F(expendablearrow) -F(clairvoyance) -F(clickname) -F(procname) -F(wornname) -F(focusname) -F(scrollname) -F(subtype) diff --git a/common/item_instance.cpp b/common/item_instance.cpp index 6aeb32223..bb048b1fb 100644 --- a/common/item_instance.cpp +++ b/common/item_instance.cpp @@ -25,6 +25,7 @@ #include "rulesys.h" #include "shareddb.h" #include "strings.h" +#include "evolving_items.h" //#include "../common/light_source.h" @@ -76,6 +77,10 @@ EQ::ItemInstance::ItemInstance(const ItemData* item, int16 charges) { m_color = m_item->Color; } + if (IsEvolving()) { + SetTimer("evolve", RuleI(EvolvingItems, DelayUponEquipping)); + } + m_SerialNumber = GetNextItemInstSerialNumber(); } @@ -95,6 +100,10 @@ EQ::ItemInstance::ItemInstance(SharedDatabase *db, uint32 item_id, int16 charges m_color = 0; } + if (IsEvolving()) { + SetTimer("evolve", RuleI(EvolvingItems, DelayUponEquipping)); + } + m_SerialNumber = GetNextItemInstSerialNumber(); } @@ -146,7 +155,6 @@ EQ::ItemInstance::ItemInstance(const ItemInstance& copy) m_exp = copy.m_exp; m_evolveLvl = copy.m_evolveLvl; - m_activated = copy.m_activated; if (copy.m_scaledItem) { m_scaledItem = new ItemData(*copy.m_scaledItem); @@ -154,12 +162,7 @@ EQ::ItemInstance::ItemInstance(const ItemInstance& copy) m_scaledItem = nullptr; } - if (copy.m_evolveInfo) { - m_evolveInfo = new EvolveInfo(*copy.m_evolveInfo); - } else { - m_evolveInfo = nullptr; - } - + m_evolving_details = copy.m_evolving_details; m_scaling = copy.m_scaling; m_ornamenticon = copy.m_ornamenticon; m_ornamentidfile = copy.m_ornamentidfile; @@ -174,7 +177,6 @@ EQ::ItemInstance::~ItemInstance() Clear(); safe_delete(m_item); safe_delete(m_scaledItem); - safe_delete(m_evolveInfo); } // Query item type @@ -572,7 +574,7 @@ EQ::ItemInstance* EQ::ItemInstance::GetOrnamentationAugment() const uint32 EQ::ItemInstance::GetOrnamentHeroModel(int32 material_slot) const { // Not a Hero Forge item. - if (m_ornament_hero_model == 0 || material_slot < 0) { + if (m_ornament_hero_model == 0) { return 0; } @@ -904,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; + } } } @@ -1030,29 +1040,6 @@ void EQ::ItemInstance::ScaleItem() { m_scaledItem->CharmFileID = 0; // this stops the client from trying to scale the item itself. } -bool EQ::ItemInstance::EvolveOnAllKills() const { - return (m_evolveInfo && m_evolveInfo->AllKills); -} - -int8 EQ::ItemInstance::GetMaxEvolveLvl() const { - if (m_evolveInfo) - return m_evolveInfo->MaxLvl; - else - return 0; -} - -uint32 EQ::ItemInstance::GetKillsNeeded(uint8 currentlevel) { - uint32 kills = -1; // default to -1 (max uint32 value) because this value is usually divided by, so we don't want to ever return zero. - if (m_evolveInfo) - if (currentlevel != m_evolveInfo->MaxLvl) - kills = m_evolveInfo->LvlKills[currentlevel - 1]; - - if (kills == 0) - kills = -1; - - return kills; -} - void EQ::ItemInstance::SetTimer(std::string name, uint32 time) { Timer t(time); t.Start(time, false); @@ -1806,6 +1793,18 @@ std::vector EQ::ItemInstance::GetAugmentIDs() const return augments; } +std::vector EQ::ItemInstance::GetAugmentNames() const +{ + std::vector augment_names; + + for (uint8 slot_id = invaug::SOCKET_BEGIN; slot_id <= invaug::SOCKET_END; slot_id++) { + const auto augment = GetAugment(slot_id); + augment_names.push_back(augment ? augment->GetItem()->Name : ""); + } + + return augment_names; +} + int EQ::ItemInstance::GetItemRegen(bool augments) const { int stat = 0; @@ -1951,28 +1950,54 @@ void EQ::ItemInstance::ClearGUIDMap() { guids.clear(); } -// -// class EvolveInfo -// -EvolveInfo::EvolveInfo() { - // nothing here yet + +bool EQ::ItemInstance::TransferOwnership(Database &db, const uint32 to_char_id) const +{ + if (!to_char_id || !IsEvolving()) { + return false; + } + + SetEvolveCharID(to_char_id); + CharacterEvolvingItemsRepository::UpdateCharID(db, GetEvolveUniqueID(), to_char_id); + return true; } -EvolveInfo::EvolveInfo(uint32 first, uint8 max, bool allkills, uint32 L2, uint32 L3, uint32 L4, uint32 L5, uint32 L6, uint32 L7, uint32 L8, uint32 L9, uint32 L10) { - FirstItem = first; - MaxLvl = max; - AllKills = allkills; - LvlKills[0] = L2; - LvlKills[1] = L3; - LvlKills[2] = L4; - LvlKills[3] = L5; - LvlKills[4] = L6; - LvlKills[5] = L7; - LvlKills[6] = L8; - LvlKills[7] = L9; - LvlKills[8] = L10; +uint32 EQ::ItemInstance::GetAugmentEvolveUniqueID(uint8 augment_index) const +{ + if (!m_item || !m_item->IsClassCommon()) { + return 0; + } + + const auto item = GetItem(augment_index); + if (item) { + return item->GetEvolveUniqueID(); + } + + return 0; } -EvolveInfo::~EvolveInfo() { - +void EQ::ItemInstance::SetTimer(std::string name, uint32 time) const{ + Timer t(time); + t.Start(time, false); + m_timers[name] = t; +} + +void EQ::ItemInstance::SetEvolveEquipped(const bool in) const +{ + if (!IsEvolving()) { + return; + } + + m_evolving_details.equipped = in; + if (in && !GetTimers().contains("evolve")) { + SetTimer("evolve", RuleI(EvolvingItems, DelayUponEquipping)); + return; + } + + if (in) { + GetTimers().at("evolve").SetTimer(RuleI(EvolvingItems, DelayUponEquipping)); + return; + } + + GetTimers().at("evolve").Disable(); } diff --git a/common/item_instance.h b/common/item_instance.h index 928a6aabc..99edfb570 100644 --- a/common/item_instance.h +++ b/common/item_instance.h @@ -23,6 +23,7 @@ #ifndef COMMON_ITEM_INSTANCE_H #define COMMON_ITEM_INSTANCE_H +#include "evolving_items.h" class ItemParse; // Parses item packets @@ -34,6 +35,7 @@ class EvolveInfo; // Stores information about an evolving item family #include "../common/bodytypes.h" #include "../common/deity.h" #include "../common/memory_buffer.h" +#include "../common/repositories/character_evolving_items_repository.h" #include @@ -205,13 +207,9 @@ namespace EQ bool IsDroppable(bool recurse = true) const; bool IsScaling() const { return m_scaling; } - bool IsEvolving() const { return (m_evolveLvl >= 1); } uint32 GetExp() const { return m_exp; } void SetExp(uint32 exp) { m_exp = exp; } void AddExp(uint32 exp) { m_exp += exp; } - bool IsActivated() { return m_activated; } - void SetActivated(bool activated) { m_activated = activated; } - int8 GetEvolveLvl() const { return m_evolveLvl; } void SetScaling(bool v) { m_scaling = v; } uint32 GetOrnamentationIcon() const { return m_ornamenticon; } void SetOrnamentIcon(uint32 ornament_icon) { m_ornamenticon = ornament_icon; } @@ -226,9 +224,6 @@ namespace EQ void Initialize(SharedDatabase *db = nullptr); void ScaleItem(); - bool EvolveOnAllKills() const; - int8 GetMaxEvolveLvl() const; - uint32 GetKillsNeeded(uint8 currentlevel); std::string Serialize(int16 slot_id) const { InternalSerializedItem_Struct s; s.slot_id = slot_id; s.inst = (const void*)this; std::string ser; ser.assign((char*)&s, sizeof(InternalSerializedItem_Struct)); return ser; } void Serialize(OutBuffer& ob, int16 slot_id) const { InternalSerializedItem_Struct isi; isi.slot_id = slot_id; isi.inst = (const void*)this; ob.write((const char*)&isi, sizeof(isi)); } @@ -236,8 +231,9 @@ namespace EQ inline int32 GetSerialNumber() const { return m_SerialNumber; } inline void SetSerialNumber(int32 id) { m_SerialNumber = id; } - std::map& GetTimers() { return m_timers; } + std::map& GetTimers() const { return m_timers; } void SetTimer(std::string name, uint32 time); + void SetTimer(std::string name, uint32 time) const; void StopTimer(std::string name); void ClearTimers(); @@ -309,9 +305,38 @@ namespace EQ int GetItemSkillsStat(EQ::skills::SkillType skill, bool augments = false) const; uint32 GetItemGuildFavor() const; std::vector GetAugmentIDs() const; + std::vector GetAugmentNames() const; static void AddGUIDToMap(uint64 existing_serial_number); static void ClearGUIDMap(); + // evolving items stuff + CharacterEvolvingItemsRepository::CharacterEvolvingItems &GetEvolvingDetails() const { return m_evolving_details; } + + int8 GetEvolveLvl() const { if (GetItem()) { return GetItem()->EvolvingLevel; } return false; } + bool IsEvolving() const { if (GetItem()) { return GetItem()->EvolvingItem; } return false; } + int8 GetMaxEvolveLvl() const { if (GetItem()) { return GetItem()->EvolvingMax; } return false; } + bool GetEvolveActivated() const { return m_evolving_details.activated ? true : false; } + bool GetEvolveEquipped() const { return m_evolving_details.equipped ? true : false; } + double GetEvolveProgression() const { return m_evolving_details.progression; } + uint64 GetEvolveUniqueID() const { return m_evolving_details.id; } + uint32 GetEvolveCharID() const { return m_evolving_details.character_id; } + uint32 GetEvolveItemID() const { return m_evolving_details.item_id; } + uint32 GetEvolveLoreID() const { if (GetItem()) { return GetItem()->EvolvingID; } return false; } + uint64 GetEvolveCurrentAmount() const { return m_evolving_details.current_amount; } + uint32 GetEvolveFinalItemID() const { return m_evolving_details.final_item_id; } + uint32 GetAugmentEvolveUniqueID(uint8 augment_index) const; + void SetEvolveEquipped(const bool in) const; + void SetEvolveActivated(const bool in) const { m_evolving_details.activated = in; } + void SetEvolveProgression(const double in) const { m_evolving_details.progression = in; } + void SetEvolveUniqueID(const uint64 in) const { m_evolving_details.id = in; } + void SetEvolveCharID(const uint32 in) const { m_evolving_details.character_id = in; } + void SetEvolveItemID(const uint32 in) const { m_evolving_details.item_id = in; } + void SetEvolveCurrentAmount(const uint64 in) const { m_evolving_details.current_amount = in; } + void SetEvolveAddToCurrentAmount(const uint64 in) const { m_evolving_details.current_amount += in; } + void SetEvolveFinalItemID(const uint32 in) const { m_evolving_details.final_item_id = in; } + bool TransferOwnership(Database& db, const uint32 to_char_id) const; + void CalculateEvolveProgression() const { m_evolving_details.progression = EvolvingItemsManager::Instance()->CalculateProgression(GetEvolveCurrentAmount(), GetID()); } + protected: ////////////////////////// // Protected Members @@ -323,48 +348,32 @@ namespace EQ void _PutItem(uint8 index, ItemInstance* inst) { m_contents[index] = inst; } - ItemInstTypes m_use_type {ItemInstNormal}; // Usage type for item - const ItemData* m_item {nullptr}; // Ptr to item data - int16 m_charges {0}; // # of charges for chargeable items - uint32 m_price {0}; // Bazaar /trader price - uint32 m_color {0}; - uint32 m_merchantslot {0}; - int16 m_currentslot {0}; - bool m_attuned {false}; - int32 m_merchantcount {1}; //number avaliable on the merchant, -1=unlimited - int32 m_SerialNumber {0}; // Unique identifier for this instance of an item. Needed for Bazaar. - uint32 m_exp {0}; - int8 m_evolveLvl {0}; - bool m_activated {false}; - ItemData* m_scaledItem {nullptr}; - ::EvolveInfo* m_evolveInfo {nullptr}; - bool m_scaling {false}; - uint32 m_ornamenticon {0}; - uint32 m_ornamentidfile {0}; - uint32 m_new_id_file {0}; - uint32 m_ornament_hero_model {0}; - uint32 m_recast_timestamp {0}; - int m_task_delivered_count {0}; + ItemInstTypes m_use_type{ItemInstNormal};// Usage type for item + const ItemData * m_item{nullptr}; // Ptr to item data + int16 m_charges{0}; // # of charges for chargeable items + uint32 m_price{0}; // Bazaar /trader price + uint32 m_color{0}; + uint32 m_merchantslot{0}; + int16 m_currentslot{0}; + bool m_attuned{false}; + int32 m_merchantcount{1};//number avaliable on the merchant, -1=unlimited + int32 m_SerialNumber{0}; // Unique identifier for this instance of an item. Needed for Bazaar. + uint32 m_exp{0}; + int8 m_evolveLvl{0}; + ItemData * m_scaledItem{nullptr}; + bool m_scaling{false}; + uint32 m_ornamenticon{0}; + uint32 m_ornamentidfile{0}; + uint32 m_new_id_file{0}; + uint32 m_ornament_hero_model{0}; + uint32 m_recast_timestamp{0}; + int m_task_delivered_count{0}; + mutable CharacterEvolvingItemsRepository::CharacterEvolvingItems m_evolving_details{}; // Items inside of this item (augs or contents) {}; - std::map m_contents {}; // Zero-based index: min=0, max=9 - std::map m_custom_data {}; - std::map m_timers {}; + std::map m_contents {}; // Zero-based index: min=0, max=9 + std::map m_custom_data {}; + mutable std::map m_timers {}; }; } - -class EvolveInfo { -public: - friend class EQ::ItemInstance; - //temporary - uint16 LvlKills[9]; - uint32 FirstItem; - uint8 MaxLvl; - bool AllKills; - - EvolveInfo(); - EvolveInfo(uint32 first, uint8 max, bool allkills, uint32 L2, uint32 L3, uint32 L4, uint32 L5, uint32 L6, uint32 L7, uint32 L8, uint32 L9, uint32 L10); - ~EvolveInfo(); -}; - #endif /*COMMON_ITEM_INSTANCE_H*/ diff --git a/common/loot.h b/common/loot.h index ba36c364f..62baf1425 100644 --- a/common/loot.h +++ b/common/loot.h @@ -25,6 +25,7 @@ struct LootItem { uint16 trivial_max_level; uint16 npc_min_level; uint16 npc_max_level; + uint32 lootdrop_id; // required for zone state referencing }; typedef std::list LootItems; diff --git a/common/memory/ksm.hpp b/common/memory/ksm.hpp new file mode 100644 index 000000000..079b83f1a --- /dev/null +++ b/common/memory/ksm.hpp @@ -0,0 +1,220 @@ +#ifndef EQEMU_KSM_HPP +#define EQEMU_KSM_HPP + +#include "../eqemu_logsys.h" +#include +#include +#include +#ifdef _WIN32 +#include // For _aligned_malloc, _aligned_free +#include +#else +#include // For madvise +#include // For sysconf, sbrk +#endif + + +// Page-aligned allocator for std::vector +template +class PageAlignedAllocator { +public: + using value_type = T; + + PageAlignedAllocator() noexcept = default; + template + PageAlignedAllocator(const PageAlignedAllocator&) noexcept {} + + T* allocate(std::size_t n) { + void* ptr = nullptr; + size_t size = n * sizeof(T); + +#ifdef _WIN32 + // Simply allocate memory without alignment + ptr = malloc(size); + if (!ptr) throw std::bad_alloc(); +#else + size_t alignment = getPageSize(); // Get the system's page size + if (posix_memalign(&ptr, alignment, size) != 0) { + throw std::bad_alloc(); + } +#endif + return static_cast(ptr); + } + + void deallocate(T* p, std::size_t) noexcept { + free(p); + } + +private: + size_t getPageSize() const + { +#ifdef _WIN32 + SYSTEM_INFO sysInfo; + GetSystemInfo(&sysInfo); + return sysInfo.dwPageSize; // Page size in bytes +#else + return static_cast(sysconf(_SC_PAGESIZE)); +#endif + }; +}; + +template +bool operator==(const PageAlignedAllocator&, const PageAlignedAllocator&) noexcept { + return true; +} + +template +bool operator!=(const PageAlignedAllocator&, const PageAlignedAllocator&) noexcept { + return false; +} + +// Kernel Samepage Merging (KSM) functionality +namespace KSM { + +#ifdef _WIN32 + // Windows-specific placeholder functions (no-op) + inline void CheckPageAlignment(void* ptr) { + } + + inline void* AllocatePageAligned(size_t size) { + return memset(malloc(size), 0, size); + } + + inline void MarkMemoryForKSM(void* start, size_t size) { + } + + inline void AlignHeapToPageBoundary() { + } + + inline void* MarkHeapStart() { + return nullptr; + } + + inline size_t MeasureHeapUsage(void* start) { + return 0; + } +#else + // Linux-specific functionality + inline void CheckPageAlignment(void* ptr) { + size_t page_size = sysconf(_SC_PAGESIZE); + if (reinterpret_cast(ptr) % page_size == 0) { + LogKSMDetail("Memory is page-aligned [{}]", ptr); + } else { + LogKSMDetail("Memory is NOT page-aligned [{}]", ptr); + } + } + + inline void* AllocatePageAligned(size_t size) { + size_t page_size = sysconf(_SC_PAGESIZE); + void* aligned_ptr = nullptr; + if (posix_memalign(&aligned_ptr, page_size, size) != 0) { + LogKSM("Failed to allocate page-aligned memory on Linux. page_size [{}] size [{}] bytes", page_size, size); + } + std::memset(aligned_ptr, 0, size); + return aligned_ptr; + } + + inline void MarkMemoryForKSM(void* start, size_t size) { + if (madvise(start, size, MADV_MERGEABLE) == 0) { + LogKSM("Marked memory for KSM | start [{}] size [{}] bytes", start, size); + } else { + perror("madvise failed"); + } + } + + inline void AlignHeapToPageBoundary() { + size_t page_size = sysconf(_SC_PAGESIZE); + if (page_size == 0) { + LogKSM("Failed to retrieve page size SC_PAGESIZE [{}]", page_size); + return; + } + + void* current_break = sbrk(0); + if (current_break == (void*)-1) { + LogKSM("Failed to retrieve the current program break"); + return; + } + + uintptr_t current_address = reinterpret_cast(current_break); + size_t misalignment = current_address % page_size; + + if (misalignment != 0) { + size_t adjustment = page_size - misalignment; + if (sbrk(adjustment) == (void*)-1) { + LogKSM("Failed to align heap to page boundary. adjustment [{}] bytes", adjustment); + return; + } + } + + LogKSMDetail("Heap aligned to next page boundary. Current break [{}]", sbrk(0)); + } + + inline void* MarkHeapStart() { + void* current_pos = sbrk(0); + AlignHeapToPageBoundary(); + return current_pos; + } + + inline size_t MeasureHeapUsage(void* start) { + void* current_break = sbrk(0); + return static_cast(current_break) - static_cast(start); + } +#endif + + + inline size_t getPageSize() + { +#ifdef _WIN32 + SYSTEM_INFO sysInfo; + GetSystemInfo(&sysInfo); + return sysInfo.dwPageSize; // Page size in bytes +#else + return static_cast(sysconf(_SC_PAGESIZE)); // POSIX page size +#endif + }; + + template + inline void PageAlignVectorAligned(std::vector>& vec) { + if (vec.empty()) { + return; + } + + size_t page_size = getPageSize(); + void* start = vec.data(); + size_t size = vec.size() * sizeof(T); + + // Check if the memory is page-aligned + if (reinterpret_cast(start) % page_size != 0) { + // Allocate a new aligned vector + std::vector> aligned_vec(vec.get_allocator()); + aligned_vec.reserve(vec.capacity()); // Match capacity to avoid reallocation during copy + + // Copy elements from the original vector + aligned_vec.insert(aligned_vec.end(), vec.begin(), vec.end()); + + // Swap the aligned vector with the original vector + vec.swap(aligned_vec); + + // Clear the temporary aligned vector to free its memory + aligned_vec.clear(); + + // Verify the new alignment + start = vec.data(); + if (reinterpret_cast(start) % page_size != 0) { + throw std::runtime_error("Failed to align vector memory to page boundaries."); + } + + LogKSMDetail("Vector reallocated to ensure page alignment. start [{}] size [{}] bytes", start, size); + } else { + LogKSMDetail("Vector is already page-aligned. start [{}] size [{}] bytes", start, size); + } + +#ifndef _WIN32 + // Mark memory for KSM (only on non-Windows systems) + MarkMemoryForKSM(start, size); +#endif + } + +} + +#endif // EQEMU_KSM_HPP diff --git a/common/net/eqstream.cpp b/common/net/eqstream.cpp index 34ad10e41..633dea4c9 100644 --- a/common/net/eqstream.cpp +++ b/common/net/eqstream.cpp @@ -1,11 +1,11 @@ #include "eqstream.h" #include "../eqemu_logsys.h" -EQ::Net::EQStreamManager::EQStreamManager(const EQStreamManagerInterfaceOptions &options) : EQStreamManagerInterface(options), m_daybreak(options.daybreak_options) +EQ::Net::EQStreamManager::EQStreamManager(const EQStreamManagerInterfaceOptions &options) : EQStreamManagerInterface(options), m_reliable_stream(options.reliable_stream_options) { - m_daybreak.OnNewConnection(std::bind(&EQStreamManager::DaybreakNewConnection, this, std::placeholders::_1)); - m_daybreak.OnConnectionStateChange(std::bind(&EQStreamManager::DaybreakConnectionStateChange, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); - m_daybreak.OnPacketRecv(std::bind(&EQStreamManager::DaybreakPacketRecv, this, std::placeholders::_1, std::placeholders::_2)); + m_reliable_stream.OnNewConnection(std::bind(&EQStreamManager::ReliableStreamNewConnection, this, std::placeholders::_1)); + m_reliable_stream.OnConnectionStateChange(std::bind(&EQStreamManager::ReliableStreamConnectionStateChange, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + m_reliable_stream.OnPacketRecv(std::bind(&EQStreamManager::ReliableStreamPacketRecv, this, std::placeholders::_1, std::placeholders::_2)); } EQ::Net::EQStreamManager::~EQStreamManager() @@ -15,11 +15,11 @@ EQ::Net::EQStreamManager::~EQStreamManager() void EQ::Net::EQStreamManager::SetOptions(const EQStreamManagerInterfaceOptions &options) { m_options = options; - auto &opts = m_daybreak.GetOptions(); - opts = options.daybreak_options; + auto &opts = m_reliable_stream.GetOptions(); + opts = options.reliable_stream_options; } -void EQ::Net::EQStreamManager::DaybreakNewConnection(std::shared_ptr connection) +void EQ::Net::EQStreamManager::ReliableStreamNewConnection(std::shared_ptr connection) { std::shared_ptr stream(new EQStream(this, connection)); m_streams.emplace(std::make_pair(connection, stream)); @@ -28,7 +28,7 @@ void EQ::Net::EQStreamManager::DaybreakNewConnection(std::shared_ptr connection, DbProtocolStatus from, DbProtocolStatus to) +void EQ::Net::EQStreamManager::ReliableStreamConnectionStateChange(std::shared_ptr connection, DbProtocolStatus from, DbProtocolStatus to) { auto iter = m_streams.find(connection); if (iter != m_streams.end()) { @@ -42,7 +42,7 @@ void EQ::Net::EQStreamManager::DaybreakConnectionStateChange(std::shared_ptr connection, const Packet &p) +void EQ::Net::EQStreamManager::ReliableStreamPacketRecv(std::shared_ptr connection, const Packet &p) { auto iter = m_streams.find(connection); if (iter != m_streams.end()) { @@ -53,7 +53,7 @@ void EQ::Net::EQStreamManager::DaybreakPacketRecv(std::shared_ptr connection) +EQ::Net::EQStream::EQStream(EQStreamManagerInterface *owner, std::shared_ptr connection) { m_owner = owner; m_connection = connection; @@ -71,7 +71,7 @@ void EQ::Net::EQStream::QueuePacket(const EQApplicationPacket *p, bool ack_req) OpcodeManager::EmuToName(p->GetOpcode()), (*m_opcode_manager)->EmuToEQ(p->GetOpcode()), p->Size(), - (LogSys.IsLogEnabled(Logs::Detail, Logs::PacketServerClient) ? DumpPacketToString(p) : "") + (EQEmuLogSys::Instance()->IsLogEnabled(Logs::Detail, Logs::PacketServerClient) ? DumpPacketToString(p) : "") ); if (m_opcode_manager && *m_opcode_manager) { @@ -235,7 +235,7 @@ EQStreamState EQ::Net::EQStream::GetState() { EQ::Net::EQStream::Stats EQ::Net::EQStream::GetStats() const { Stats ret; - ret.DaybreakStats = m_connection->GetStats(); + ret.ReliableStreamStats = m_connection->GetStats(); for (int i = 0; i < _maxEmuOpcode; ++i) { ret.RecvCount[i] = 0; diff --git a/common/net/eqstream.h b/common/net/eqstream.h index a87c6ec0b..4a8e6ddd9 100644 --- a/common/net/eqstream.h +++ b/common/net/eqstream.h @@ -3,7 +3,7 @@ #include "../eq_packet.h" #include "../eq_stream_intf.h" #include "../opcodemgr.h" -#include "daybreak_connection.h" +#include "reliable_stream_connection.h" #include #include #include @@ -23,21 +23,21 @@ namespace EQ void OnNewConnection(std::function)> func) { m_on_new_connection = func; } void OnConnectionStateChange(std::function, DbProtocolStatus, DbProtocolStatus)> func) { m_on_connection_state_change = func; } private: - DaybreakConnectionManager m_daybreak; + ReliableStreamConnectionManager m_reliable_stream; std::function)> m_on_new_connection; std::function, DbProtocolStatus, DbProtocolStatus)> m_on_connection_state_change; - std::map, std::shared_ptr> m_streams; + std::map, std::shared_ptr> m_streams; - void DaybreakNewConnection(std::shared_ptr connection); - void DaybreakConnectionStateChange(std::shared_ptr connection, DbProtocolStatus from, DbProtocolStatus to); - void DaybreakPacketRecv(std::shared_ptr connection, const Packet &p); + void ReliableStreamNewConnection(std::shared_ptr connection); + void ReliableStreamConnectionStateChange(std::shared_ptr connection, DbProtocolStatus from, DbProtocolStatus to); + void ReliableStreamPacketRecv(std::shared_ptr connection, const Packet &p); friend class EQStream; }; class EQStream : public EQStreamInterface { public: - EQStream(EQStreamManagerInterface *parent, std::shared_ptr connection); + EQStream(EQStreamManagerInterface *parent, std::shared_ptr connection); ~EQStream(); virtual void QueuePacket(const EQApplicationPacket *p, bool ack_req = true); @@ -67,7 +67,7 @@ namespace EQ virtual EQStreamManagerInterface* GetManager() const; private: EQStreamManagerInterface *m_owner; - std::shared_ptr m_connection; + std::shared_ptr m_connection; OpcodeManager **m_opcode_manager; std::deque> m_packet_queue; std::unordered_map m_packet_recv_count; diff --git a/common/net/daybreak_connection.cpp b/common/net/reliable_stream_connection.cpp similarity index 68% rename from common/net/daybreak_connection.cpp rename to common/net/reliable_stream_connection.cpp index 0fefc9982..e1101f452 100644 --- a/common/net/daybreak_connection.cpp +++ b/common/net/reliable_stream_connection.cpp @@ -1,14 +1,18 @@ -#include "daybreak_connection.h" +#include "reliable_stream_connection.h" #include "../event/event_loop.h" -#include "../event/task.h" #include "../data_verification.h" #include "crc32.h" -#include "../eqemu_logsys.h" #include #include -#include -EQ::Net::DaybreakConnectionManager::DaybreakConnectionManager() +// observed client receive window is 300 packets, 140KB +constexpr size_t MAX_CLIENT_RECV_PACKETS_PER_WINDOW = 300; +constexpr size_t MAX_CLIENT_RECV_BYTES_PER_WINDOW = 140 * 1024; + +// buffer pools +SendBufferPool send_buffer_pool; + +EQ::Net::ReliableStreamConnectionManager::ReliableStreamConnectionManager() { m_attached = nullptr; memset(&m_timer, 0, sizeof(uv_timer_t)); @@ -17,7 +21,7 @@ EQ::Net::DaybreakConnectionManager::DaybreakConnectionManager() Attach(EQ::EventLoop::Get().Handle()); } -EQ::Net::DaybreakConnectionManager::DaybreakConnectionManager(const DaybreakConnectionManagerOptions &opts) +EQ::Net::ReliableStreamConnectionManager::ReliableStreamConnectionManager(const ReliableStreamConnectionManagerOptions &opts) { m_attached = nullptr; m_options = opts; @@ -27,12 +31,12 @@ EQ::Net::DaybreakConnectionManager::DaybreakConnectionManager(const DaybreakConn Attach(EQ::EventLoop::Get().Handle()); } -EQ::Net::DaybreakConnectionManager::~DaybreakConnectionManager() +EQ::Net::ReliableStreamConnectionManager::~ReliableStreamConnectionManager() { Detach(); } -void EQ::Net::DaybreakConnectionManager::Attach(uv_loop_t *loop) +void EQ::Net::ReliableStreamConnectionManager::Attach(uv_loop_t *loop) { if (!m_attached) { uv_timer_init(loop, &m_timer); @@ -41,7 +45,7 @@ void EQ::Net::DaybreakConnectionManager::Attach(uv_loop_t *loop) auto update_rate = (uint64_t)(1000.0 / m_options.tic_rate_hertz); uv_timer_start(&m_timer, [](uv_timer_t *handle) { - DaybreakConnectionManager *c = (DaybreakConnectionManager*)handle->data; + ReliableStreamConnectionManager *c = (ReliableStreamConnectionManager*)handle->data; c->UpdateDataBudget(); c->Process(); c->ProcessResend(); @@ -53,16 +57,22 @@ void EQ::Net::DaybreakConnectionManager::Attach(uv_loop_t *loop) uv_ip4_addr("0.0.0.0", m_options.port, &recv_addr); int rc = uv_udp_bind(&m_socket, (const struct sockaddr *)&recv_addr, UV_UDP_REUSEADDR); - rc = uv_udp_recv_start(&m_socket, - [](uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) { - buf->base = new char[suggested_size]; - memset(buf->base, 0, suggested_size); - buf->len = suggested_size; - }, + rc = uv_udp_recv_start( + &m_socket, + [](uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) { + if (suggested_size > 65536) { + buf->base = new char[suggested_size]; + buf->len = suggested_size; + return; + } + + static thread_local char temp_buf[65536]; + buf->base = temp_buf; + buf->len = 65536; + }, [](uv_udp_t* handle, ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned flags) { - DaybreakConnectionManager *c = (DaybreakConnectionManager*)handle->data; + ReliableStreamConnectionManager *c = (ReliableStreamConnectionManager*)handle->data; if (nread < 0 || addr == nullptr) { - delete[] buf->base; return; } @@ -70,14 +80,17 @@ void EQ::Net::DaybreakConnectionManager::Attach(uv_loop_t *loop) uv_ip4_name((const sockaddr_in*)addr, endpoint, 16); auto port = ntohs(((const sockaddr_in*)addr)->sin_port); c->ProcessPacket(endpoint, port, buf->base, nread); - delete[] buf->base; + + if (buf->len > 65536) { + delete[] buf->base; + } }); m_attached = loop; } } -void EQ::Net::DaybreakConnectionManager::Detach() +void EQ::Net::ReliableStreamConnectionManager::Detach() { if (m_attached) { uv_udp_recv_stop(&m_socket); @@ -86,11 +99,11 @@ void EQ::Net::DaybreakConnectionManager::Detach() } } -void EQ::Net::DaybreakConnectionManager::Connect(const std::string &addr, int port) +void EQ::Net::ReliableStreamConnectionManager::Connect(const std::string &addr, int port) { //todo dns resolution - auto connection = std::shared_ptr(new DaybreakConnection(this, addr, port)); + auto connection = std::shared_ptr(new ReliableStreamConnection(this, addr, port)); connection->m_self = connection; if (m_on_new_connection) { @@ -100,7 +113,7 @@ void EQ::Net::DaybreakConnectionManager::Connect(const std::string &addr, int po m_connections.emplace(std::make_pair(std::make_pair(addr, port), connection)); } -void EQ::Net::DaybreakConnectionManager::Process() +void EQ::Net::ReliableStreamConnectionManager::Process() { auto now = Clock::now(); auto iter = m_connections.begin(); @@ -164,7 +177,7 @@ void EQ::Net::DaybreakConnectionManager::Process() } } -void EQ::Net::DaybreakConnectionManager::UpdateDataBudget() +void EQ::Net::ReliableStreamConnectionManager::UpdateDataBudget() { auto outgoing_data_rate = m_options.outgoing_data_rate; if (outgoing_data_rate <= 0.0) { @@ -183,7 +196,7 @@ void EQ::Net::DaybreakConnectionManager::UpdateDataBudget() } } -void EQ::Net::DaybreakConnectionManager::ProcessResend() +void EQ::Net::ReliableStreamConnectionManager::ProcessResend() { auto iter = m_connections.begin(); while (iter != m_connections.end()) { @@ -204,15 +217,15 @@ void EQ::Net::DaybreakConnectionManager::ProcessResend() } } -void EQ::Net::DaybreakConnectionManager::ProcessPacket(const std::string &endpoint, int port, const char *data, size_t size) +void EQ::Net::ReliableStreamConnectionManager::ProcessPacket(const std::string &endpoint, int port, const char *data, size_t size) { if (m_options.simulated_in_packet_loss && m_options.simulated_in_packet_loss >= m_rand.Int(0, 100)) { return; } - if (size < DaybreakHeader::size()) { + if (size < ReliableStreamHeader::size()) { if (m_on_error_message) { - m_on_error_message(fmt::format("Packet of size {0} which is less than {1}", size, DaybreakHeader::size())); + m_on_error_message(fmt::format("Packet of size {0} which is less than {1}", size, ReliableStreamHeader::size())); } return; } @@ -226,9 +239,9 @@ void EQ::Net::DaybreakConnectionManager::ProcessPacket(const std::string &endpoi else { if (data[0] == 0 && data[1] == OP_SessionRequest) { StaticPacket p((void*)data, size); - auto request = p.GetSerialize(0); + auto request = p.GetSerialize(0); - connection = std::shared_ptr(new DaybreakConnection(this, request, endpoint, port)); + connection = std::shared_ptr(new ReliableStreamConnection(this, request, endpoint, port)); connection->m_self = connection; if (m_on_new_connection) { @@ -249,7 +262,7 @@ void EQ::Net::DaybreakConnectionManager::ProcessPacket(const std::string &endpoi } } -std::shared_ptr EQ::Net::DaybreakConnectionManager::FindConnectionByEndpoint(std::string addr, int port) +std::shared_ptr EQ::Net::ReliableStreamConnectionManager::FindConnectionByEndpoint(std::string addr, int port) { auto p = std::make_pair(addr, port); auto iter = m_connections.find(p); @@ -260,9 +273,9 @@ std::shared_ptr EQ::Net::DaybreakConnectionManager: return nullptr; } -void EQ::Net::DaybreakConnectionManager::SendDisconnect(const std::string &addr, int port) +void EQ::Net::ReliableStreamConnectionManager::SendDisconnect(const std::string &addr, int port) { - DaybreakDisconnect header; + ReliableStreamDisconnect header; header.zero = 0; header.opcode = OP_OutOfSession; header.connect_code = 0; @@ -287,7 +300,7 @@ void EQ::Net::DaybreakConnectionManager::SendDisconnect(const std::string &addr, } //new connection made as server -EQ::Net::DaybreakConnection::DaybreakConnection(DaybreakConnectionManager *owner, const DaybreakConnect &connect, const std::string &endpoint, int port) +EQ::Net::ReliableStreamConnection::ReliableStreamConnection(ReliableStreamConnectionManager *owner, const ReliableStreamConnect &connect, const std::string &endpoint, int port) { m_owner = owner; m_last_send = Clock::now(); @@ -310,11 +323,11 @@ EQ::Net::DaybreakConnection::DaybreakConnection(DaybreakConnectionManager *owner m_last_session_stats = Clock::now(); m_outgoing_budget = owner->m_options.outgoing_data_rate; - LogNetcode("New session [{}] with encode key [{}]", m_connect_code, HostToNetwork(m_encode_key)); + LogNetClient("New session [{}] with encode key [{}]", m_connect_code, HostToNetwork(m_encode_key)); } //new connection made as client -EQ::Net::DaybreakConnection::DaybreakConnection(DaybreakConnectionManager *owner, const std::string &endpoint, int port) +EQ::Net::ReliableStreamConnection::ReliableStreamConnection(ReliableStreamConnectionManager *owner, const std::string &endpoint, int port) { m_owner = owner; m_last_send = Clock::now(); @@ -336,35 +349,35 @@ EQ::Net::DaybreakConnection::DaybreakConnection(DaybreakConnectionManager *owner m_outgoing_budget = owner->m_options.outgoing_data_rate; } -EQ::Net::DaybreakConnection::~DaybreakConnection() +EQ::Net::ReliableStreamConnection::~ReliableStreamConnection() { } -void EQ::Net::DaybreakConnection::Close() +void EQ::Net::ReliableStreamConnection::Close() { - if (m_status == StatusConnected) { + if (m_status != StatusDisconnected && m_status != StatusDisconnecting) { FlushBuffer(); SendDisconnect(); + } + if (m_status != StatusDisconnecting) { m_close_time = Clock::now(); - ChangeStatus(StatusDisconnecting); - } - else { - ChangeStatus(StatusDisconnecting); } + + ChangeStatus(StatusDisconnecting); } -void EQ::Net::DaybreakConnection::QueuePacket(Packet &p) +void EQ::Net::ReliableStreamConnection::QueuePacket(Packet &p) { QueuePacket(p, 0, true); } -void EQ::Net::DaybreakConnection::QueuePacket(Packet &p, int stream) +void EQ::Net::ReliableStreamConnection::QueuePacket(Packet &p, int stream) { QueuePacket(p, stream, true); } -void EQ::Net::DaybreakConnection::QueuePacket(Packet &p, int stream, bool reliable) +void EQ::Net::ReliableStreamConnection::QueuePacket(Packet &p, int stream, bool reliable) { if (*(char*)p.Data() == 0) { DynamicPacket packet; @@ -377,21 +390,21 @@ void EQ::Net::DaybreakConnection::QueuePacket(Packet &p, int stream, bool reliab InternalQueuePacket(p, stream, reliable); } -EQ::Net::DaybreakConnectionStats EQ::Net::DaybreakConnection::GetStats() +EQ::Net::ReliableStreamConnectionStats EQ::Net::ReliableStreamConnection::GetStats() { - EQ::Net::DaybreakConnectionStats ret = m_stats; + EQ::Net::ReliableStreamConnectionStats ret = m_stats; ret.datarate_remaining = m_outgoing_budget; ret.avg_ping = m_rolling_ping; return ret; } -void EQ::Net::DaybreakConnection::ResetStats() +void EQ::Net::ReliableStreamConnection::ResetStats() { m_stats.Reset(); } -void EQ::Net::DaybreakConnection::Process() +void EQ::Net::ReliableStreamConnection::Process() { try { auto now = Clock::now(); @@ -409,7 +422,7 @@ void EQ::Net::DaybreakConnection::Process() } } -void EQ::Net::DaybreakConnection::ProcessPacket(Packet &p) +void EQ::Net::ReliableStreamConnection::ProcessPacket(Packet &p) { m_last_recv = Clock::now(); m_stats.recv_packets++; @@ -445,13 +458,13 @@ void EQ::Net::DaybreakConnection::ProcessPacket(Packet &p) switch (m_encode_passes[i]) { case EncodeCompression: if(temp.GetInt8(0) == 0) - Decompress(temp, DaybreakHeader::size(), temp.Length() - DaybreakHeader::size()); + Decompress(temp, ReliableStreamHeader::size(), temp.Length() - ReliableStreamHeader::size()); else Decompress(temp, 1, temp.Length() - 1); break; case EncodeXOR: if (temp.GetInt8(0) == 0) - Decode(temp, DaybreakHeader::size(), temp.Length() - DaybreakHeader::size()); + Decode(temp, ReliableStreamHeader::size(), temp.Length() - ReliableStreamHeader::size()); else Decode(temp, 1, temp.Length() - 1); break; @@ -470,7 +483,7 @@ void EQ::Net::DaybreakConnection::ProcessPacket(Packet &p) switch (m_encode_passes[i]) { case EncodeXOR: if (temp.GetInt8(0) == 0) - Decode(temp, DaybreakHeader::size(), temp.Length() - DaybreakHeader::size()); + Decode(temp, ReliableStreamHeader::size(), temp.Length() - ReliableStreamHeader::size()); else Decode(temp, 1, temp.Length() - 1); break; @@ -489,7 +502,7 @@ void EQ::Net::DaybreakConnection::ProcessPacket(Packet &p) } } -void EQ::Net::DaybreakConnection::ProcessQueue() +void EQ::Net::ReliableStreamConnection::ProcessQueue() { for (int i = 0; i < 4; ++i) { auto stream = &m_streams[i]; @@ -508,7 +521,7 @@ void EQ::Net::DaybreakConnection::ProcessQueue() } } -void EQ::Net::DaybreakConnection::RemoveFromQueue(int stream, uint16_t seq) +void EQ::Net::ReliableStreamConnection::RemoveFromQueue(int stream, uint16_t seq) { auto s = &m_streams[stream]; auto iter = s->packet_queue.find(seq); @@ -519,7 +532,7 @@ void EQ::Net::DaybreakConnection::RemoveFromQueue(int stream, uint16_t seq) } } -void EQ::Net::DaybreakConnection::AddToQueue(int stream, uint16_t seq, const Packet &p) +void EQ::Net::ReliableStreamConnection::AddToQueue(int stream, uint16_t seq, const Packet &p) { auto s = &m_streams[stream]; auto iter = s->packet_queue.find(seq); @@ -531,7 +544,7 @@ void EQ::Net::DaybreakConnection::AddToQueue(int stream, uint16_t seq, const Pac } } -void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p) +void EQ::Net::ReliableStreamConnection::ProcessDecodedPacket(const Packet &p) { if (p.GetInt8(0) == 0) { if (p.Length() < 2) { @@ -615,13 +628,13 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p) case OP_SessionRequest: { if (m_status == StatusConnected) { - auto request = p.GetSerialize(0); + auto request = p.GetSerialize(0); if (NetworkToHost(request.connect_code) != m_connect_code) { return; } - DaybreakConnectReply reply; + ReliableStreamConnectReply reply; reply.zero = 0; reply.opcode = OP_SessionResponse; reply.connect_code = HostToNetwork(m_connect_code); @@ -634,7 +647,7 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p) p.PutSerialize(0, reply); InternalSend(p); - LogNetcode("[OP_SessionRequest] Session [{}] started with encode key [{}]", m_connect_code, HostToNetwork(m_encode_key)); + LogNetClient("[OP_SessionRequest] Session [{}] started with encode key [{}]", m_connect_code, HostToNetwork(m_encode_key)); } break; @@ -643,17 +656,17 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p) case OP_SessionResponse: { if (m_status == StatusConnecting) { - auto reply = p.GetSerialize(0); + auto reply = p.GetSerialize(0); if (m_connect_code == reply.connect_code) { m_encode_key = reply.encode_key; m_crc_bytes = reply.crc_bytes; - m_encode_passes[0] = (DaybreakEncodeType)reply.encode_pass1; - m_encode_passes[1] = (DaybreakEncodeType)reply.encode_pass2; + m_encode_passes[0] = (ReliableStreamEncodeType)reply.encode_pass1; + m_encode_passes[1] = (ReliableStreamEncodeType)reply.encode_pass2; m_max_packet_size = reply.max_packet_size; ChangeStatus(StatusConnected); - LogNetcode( + LogNetClient( "[OP_SessionResponse] Session [{}] refresh with encode key [{}]", m_connect_code, HostToNetwork(m_encode_key) @@ -673,7 +686,7 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p) return; } - auto header = p.GetSerialize(0); + auto header = p.GetSerialize(0); auto sequence = NetworkToHost(header.sequence); auto stream_id = header.opcode - OP_Packet; auto stream = &m_streams[stream_id]; @@ -690,7 +703,7 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p) RemoveFromQueue(stream_id, sequence); SendAck(stream_id, stream->sequence_in); stream->sequence_in++; - StaticPacket next((char*)p.Data() + DaybreakReliableHeader::size(), p.Length() - DaybreakReliableHeader::size()); + StaticPacket next((char*)p.Data() + ReliableStreamReliableHeader::size(), p.Length() - ReliableStreamReliableHeader::size()); ProcessDecodedPacket(next); } @@ -702,7 +715,7 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p) case OP_Fragment3: case OP_Fragment4: { - auto header = p.GetSerialize(0); + auto header = p.GetSerialize(0); auto sequence = NetworkToHost(header.sequence); auto stream_id = header.opcode - OP_Fragment; auto stream = &m_streams[stream_id]; @@ -722,22 +735,22 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p) stream->sequence_in++; if (stream->fragment_total_bytes == 0) { - auto fragheader = p.GetSerialize(0); + auto fragheader = p.GetSerialize(0); stream->fragment_total_bytes = NetworkToHost(fragheader.total_size); stream->fragment_current_bytes = 0; stream->fragment_packet.Reserve(stream->fragment_total_bytes); stream->fragment_packet.PutData( stream->fragment_current_bytes, - (char*)p.Data() + DaybreakReliableFragmentHeader::size(), p.Length() - DaybreakReliableFragmentHeader::size()); + (char*)p.Data() + ReliableStreamReliableFragmentHeader::size(), p.Length() - ReliableStreamReliableFragmentHeader::size()); - stream->fragment_current_bytes += (uint32_t)(p.Length() - DaybreakReliableFragmentHeader::size()); + stream->fragment_current_bytes += (uint32_t)(p.Length() - ReliableStreamReliableFragmentHeader::size()); } else { stream->fragment_packet.PutData( stream->fragment_current_bytes, - (char*)p.Data() + DaybreakReliableHeader::size(), p.Length() - DaybreakReliableHeader::size()); + (char*)p.Data() + ReliableStreamReliableHeader::size(), p.Length() - ReliableStreamReliableHeader::size()); - stream->fragment_current_bytes += (uint32_t)(p.Length() - DaybreakReliableHeader::size()); + stream->fragment_current_bytes += (uint32_t)(p.Length() - ReliableStreamReliableHeader::size()); if (stream->fragment_current_bytes >= stream->fragment_total_bytes) { ProcessDecodedPacket(stream->fragment_packet); @@ -756,7 +769,7 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p) case OP_Ack3: case OP_Ack4: { - auto header = p.GetSerialize(0); + auto header = p.GetSerialize(0); auto sequence = NetworkToHost(header.sequence); auto stream_id = header.opcode - OP_Ack; Ack(stream_id, sequence); @@ -768,7 +781,7 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p) case OP_OutOfOrderAck3: case OP_OutOfOrderAck4: { - auto header = p.GetSerialize(0); + auto header = p.GetSerialize(0); auto sequence = NetworkToHost(header.sequence); auto stream_id = header.opcode - OP_OutOfOrderAck; OutOfOrderAck(stream_id, sequence); @@ -782,7 +795,7 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p) SendDisconnect(); } - LogNetcode( + LogNetClient( "[OP_SessionDisconnect] Session [{}] disconnect with encode key [{}]", m_connect_code, HostToNetwork(m_encode_key) @@ -802,13 +815,13 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p) } case OP_SessionStatRequest: { - auto request = p.GetSerialize(0); + auto request = p.GetSerialize(0); m_stats.sync_remote_sent_packets = EQ::Net::NetworkToHost(request.packets_sent); m_stats.sync_remote_recv_packets = EQ::Net::NetworkToHost(request.packets_recv); m_stats.sync_sent_packets = m_stats.sent_packets; m_stats.sync_recv_packets = m_stats.recv_packets; - DaybreakSessionStatResponse response; + ReliableStreamSessionStatResponse response; response.zero = 0; response.opcode = OP_SessionStatResponse; response.timestamp = request.timestamp; @@ -823,7 +836,7 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p) break; } case OP_SessionStatResponse: { - auto response = p.GetSerialize(0); + auto response = p.GetSerialize(0); m_stats.sync_remote_sent_packets = EQ::Net::NetworkToHost(response.server_sent); m_stats.sync_remote_recv_packets = EQ::Net::NetworkToHost(response.server_recv); m_stats.sync_sent_packets = m_stats.sent_packets; @@ -845,14 +858,14 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p) } } -bool EQ::Net::DaybreakConnection::ValidateCRC(Packet &p) +bool EQ::Net::ReliableStreamConnection::ValidateCRC(Packet &p) { if (m_crc_bytes == 0U) { return true; } if (p.Length() < (size_t)m_crc_bytes) { - LogNetcode("Session [{}] ignored packet (crc bytes invalid on session)", m_connect_code); + LogNetClient("Session [{}] ignored packet (crc bytes invalid on session)", m_connect_code); return false; } @@ -879,7 +892,7 @@ bool EQ::Net::DaybreakConnection::ValidateCRC(Packet &p) return false; } -void EQ::Net::DaybreakConnection::AppendCRC(Packet &p) +void EQ::Net::ReliableStreamConnection::AppendCRC(Packet &p) { if (m_crc_bytes == 0U) { return; @@ -898,7 +911,7 @@ void EQ::Net::DaybreakConnection::AppendCRC(Packet &p) } } -void EQ::Net::DaybreakConnection::ChangeStatus(DbProtocolStatus new_status) +void EQ::Net::ReliableStreamConnection::ChangeStatus(DbProtocolStatus new_status) { if (m_owner->m_on_connection_state_change) { if (auto self = m_self.lock()) { @@ -909,7 +922,7 @@ void EQ::Net::DaybreakConnection::ChangeStatus(DbProtocolStatus new_status) m_status = new_status; } -bool EQ::Net::DaybreakConnection::PacketCanBeEncoded(Packet &p) const +bool EQ::Net::ReliableStreamConnection::PacketCanBeEncoded(Packet &p) const { if (p.Length() < 2) { return false; @@ -928,7 +941,7 @@ bool EQ::Net::DaybreakConnection::PacketCanBeEncoded(Packet &p) const return true; } -void EQ::Net::DaybreakConnection::Decode(Packet &p, size_t offset, size_t length) +void EQ::Net::ReliableStreamConnection::Decode(Packet &p, size_t offset, size_t length) { int key = m_encode_key; char *buffer = (char*)p.Data() + offset; @@ -948,7 +961,7 @@ void EQ::Net::DaybreakConnection::Decode(Packet &p, size_t offset, size_t length } } -void EQ::Net::DaybreakConnection::Encode(Packet &p, size_t offset, size_t length) +void EQ::Net::ReliableStreamConnection::Encode(Packet &p, size_t offset, size_t length) { int key = m_encode_key; char *buffer = (char*)p.Data() + offset; @@ -1037,13 +1050,13 @@ uint32_t Deflate(const uint8_t* in, uint32_t in_len, uint8_t* out, uint32_t out_ } } -void EQ::Net::DaybreakConnection::Decompress(Packet &p, size_t offset, size_t length) +void EQ::Net::ReliableStreamConnection::Decompress(Packet &p, size_t offset, size_t length) { if (length < 2) { return; } - static uint8_t new_buffer[4096]; + static thread_local uint8_t new_buffer[4096]; uint8_t *buffer = (uint8_t*)p.Data() + offset; uint32_t new_length = 0; @@ -1062,9 +1075,9 @@ void EQ::Net::DaybreakConnection::Decompress(Packet &p, size_t offset, size_t le p.PutData(offset, new_buffer, new_length); } -void EQ::Net::DaybreakConnection::Compress(Packet &p, size_t offset, size_t length) +void EQ::Net::ReliableStreamConnection::Compress(Packet &p, size_t offset, size_t length) { - uint8_t new_buffer[2048] = { 0 }; + static thread_local uint8_t new_buffer[2048] = { 0 }; uint8_t *buffer = (uint8_t*)p.Data() + offset; uint32_t new_length = 0; bool send_uncompressed = true; @@ -1084,82 +1097,143 @@ void EQ::Net::DaybreakConnection::Compress(Packet &p, size_t offset, size_t leng p.PutData(offset, new_buffer, new_length); } -void EQ::Net::DaybreakConnection::ProcessResend() +void EQ::Net::ReliableStreamConnection::ProcessResend() { for (int i = 0; i < 4; ++i) { ProcessResend(i); } } -void EQ::Net::DaybreakConnection::ProcessResend(int stream) +void EQ::Net::ReliableStreamConnection::ProcessResend(int stream) { if (m_status == DbProtocolStatus::StatusDisconnected) { return; } - auto resends = 0; - auto now = Clock::now(); - auto s = &m_streams[stream]; - for (auto &entry : s->sent_packets) { - auto time_since_last_send = std::chrono::duration_cast(now - entry.second.last_sent); - if (entry.second.times_resent == 0) { - if ((size_t)time_since_last_send.count() > entry.second.resend_delay) { - auto &p = entry.second.packet; - if (p.Length() >= DaybreakHeader::size()) { - if (p.GetInt8(0) == 0 && p.GetInt8(1) >= OP_Fragment && p.GetInt8(1) <= OP_Fragment4) { - m_stats.resent_fragments++; - } - else { - m_stats.resent_full++; - } - } - else { - m_stats.resent_full++; - } - m_stats.resent_packets++; + if (m_streams[stream].sent_packets.empty()) { + return; + } - InternalBufferedSend(p); - entry.second.last_sent = now; - entry.second.times_resent++; - entry.second.resend_delay = EQ::Clamp(entry.second.resend_delay * 2, m_owner->m_options.resend_delay_min, m_owner->m_options.resend_delay_max); - resends++; + m_resend_packets_sent = 0; + m_resend_bytes_sent = 0; + + auto now = Clock::now(); // Current time + auto s = &m_streams[stream]; + + // Get a reference resend delay (assume first packet represents the typical case) + if (!s->sent_packets.empty()) { + // Check if the first packet has timed out + auto &first_packet = s->sent_packets.begin()->second; + auto time_since_first_sent = std::chrono::duration_cast(now - first_packet.first_sent).count(); + + if (time_since_first_sent >= m_owner->m_options.resend_timeout) { + auto now_ms = std::chrono::duration_cast(now.time_since_epoch()).count(); + auto first_sent_ms = std::chrono::duration_cast(first_packet.first_sent.time_since_epoch()).count(); + LogNetClient( + "Closing connection for m_endpoint [{}] m_port [{}] time_since_first_sent [{}] >= m_owner->m_options.resend_timeout [{}] now [{}] first_packet.first_sent [{}]", + m_endpoint, + m_port, + time_since_first_sent, + m_owner->m_options.resend_timeout, + now_ms, + first_sent_ms + ); + Close(); + return; + } + + if (m_last_ack - now > std::chrono::milliseconds(1000)) { + LogNetClient( + "Resetting m_acked_since_last_resend flag for m_endpoint [{}] m_port [{}]", + m_endpoint, + m_port + ); + m_acked_since_last_resend = true; + } + + // make sure that the first_packet in the list first_sent time is within the resend_delay and now + // if it is not, then we need to resend all packets in the list + if (time_since_first_sent <= first_packet.resend_delay && !m_acked_since_last_resend) { + LogNetClientDetail( + "Not resending packets for m_endpoint [{}] m_port [{}] packets [{}] time_first_sent [{}] resend_delay [{}] m_acked_since_last_resend [{}]", + m_endpoint, + m_port, + s->sent_packets.size(), + time_since_first_sent, + first_packet.resend_delay, + m_acked_since_last_resend + ); + return; + } + } + + if (EQEmuLogSys::Instance()->IsLogEnabled(Logs::General, Logs::NetClient)) { + size_t total_size = 0; + for (auto &e: s->sent_packets) { + total_size += e.second.packet.Length(); + } + + LogNetClientDetail( + "Resending packets for m_endpoint [{}] m_port [{}] packet count [{}] total packet size [{}] m_acked_since_last_resend [{}]", + m_endpoint, + m_port, + s->sent_packets.size(), + total_size, + m_acked_since_last_resend + ); + } + + for (auto &e: s->sent_packets) { + if (m_resend_packets_sent >= MAX_CLIENT_RECV_PACKETS_PER_WINDOW || + m_resend_bytes_sent >= MAX_CLIENT_RECV_BYTES_PER_WINDOW) { + LogNetClient( + "Stopping resend because we hit thresholds for m_endpoint [{}] m_port [{}] m_resend_packets_sent [{}] max [{}] in_queue [{}] m_resend_bytes_sent [{}] max [{}]", + m_endpoint, + m_port, + m_resend_packets_sent, + MAX_CLIENT_RECV_PACKETS_PER_WINDOW, + s->sent_packets.size(), + m_resend_bytes_sent, + MAX_CLIENT_RECV_BYTES_PER_WINDOW + ); + break; + } + + auto &sp = e.second; + auto &p = sp.packet; + if (p.Length() >= ReliableStreamHeader::size()) { + if (p.GetInt8(0) == 0 && p.GetInt8(1) >= OP_Fragment && p.GetInt8(1) <= OP_Fragment4) { + m_stats.resent_fragments++; + } + else { + m_stats.resent_full++; } } else { - auto time_since_first_sent = std::chrono::duration_cast(now - entry.second.first_sent); - if (time_since_first_sent.count() >= m_owner->m_options.resend_timeout) { - Close(); - return; - } - - if ((size_t)time_since_last_send.count() > entry.second.resend_delay) { - auto &p = entry.second.packet; - if (p.Length() >= DaybreakHeader::size()) { - if (p.GetInt8(0) == 0 && p.GetInt8(1) >= OP_Fragment && p.GetInt8(1) <= OP_Fragment4) { - m_stats.resent_fragments++; - } - else { - m_stats.resent_full++; - } - } - else { - m_stats.resent_full++; - } - m_stats.resent_packets++; - - InternalBufferedSend(p); - entry.second.last_sent = now; - entry.second.times_resent++; - entry.second.resend_delay = EQ::Clamp(entry.second.resend_delay * 2, m_owner->m_options.resend_delay_min, m_owner->m_options.resend_delay_max); - resends++; - } + m_stats.resent_full++; } + m_stats.resent_packets++; + + // Resend the packet + InternalBufferedSend(p); + + m_resend_packets_sent++; + m_resend_bytes_sent += p.Length(); + sp.last_sent = now; + sp.times_resent++; + sp.resend_delay = EQ::Clamp( + sp.resend_delay * 2, + m_owner->m_options.resend_delay_min, + m_owner->m_options.resend_delay_max + ); } + + m_acked_since_last_resend = false; + m_last_ack = now; } -void EQ::Net::DaybreakConnection::Ack(int stream, uint16_t seq) +void EQ::Net::ReliableStreamConnection::Ack(int stream, uint16_t seq) { - auto now = Clock::now(); auto s = &m_streams[stream]; auto iter = s->sent_packets.begin(); @@ -1180,9 +1254,12 @@ void EQ::Net::DaybreakConnection::Ack(int stream, uint16_t seq) ++iter; } } + + m_acked_since_last_resend = true; + m_last_ack = now; } -void EQ::Net::DaybreakConnection::OutOfOrderAck(int stream, uint16_t seq) +void EQ::Net::ReliableStreamConnection::OutOfOrderAck(int stream, uint16_t seq) { auto now = Clock::now(); auto s = &m_streams[stream]; @@ -1197,17 +1274,20 @@ void EQ::Net::DaybreakConnection::OutOfOrderAck(int stream, uint16_t seq) s->sent_packets.erase(iter); } + + m_acked_since_last_resend = true; + m_last_ack = now; } -void EQ::Net::DaybreakConnection::UpdateDataBudget(double budget_add) +void EQ::Net::ReliableStreamConnection::UpdateDataBudget(double budget_add) { auto outgoing_data_rate = m_owner->m_options.outgoing_data_rate; m_outgoing_budget = EQ::ClampUpper(m_outgoing_budget + budget_add, outgoing_data_rate); } -void EQ::Net::DaybreakConnection::SendAck(int stream_id, uint16_t seq) +void EQ::Net::ReliableStreamConnection::SendAck(int stream_id, uint16_t seq) { - DaybreakReliableHeader ack; + ReliableStreamReliableHeader ack; ack.zero = 0; ack.opcode = OP_Ack + stream_id; ack.sequence = HostToNetwork(seq); @@ -1218,9 +1298,9 @@ void EQ::Net::DaybreakConnection::SendAck(int stream_id, uint16_t seq) InternalBufferedSend(p); } -void EQ::Net::DaybreakConnection::SendOutOfOrderAck(int stream_id, uint16_t seq) +void EQ::Net::ReliableStreamConnection::SendOutOfOrderAck(int stream_id, uint16_t seq) { - DaybreakReliableHeader ack; + ReliableStreamReliableHeader ack; ack.zero = 0; ack.opcode = OP_OutOfOrderAck + stream_id; ack.sequence = HostToNetwork(seq); @@ -1231,9 +1311,9 @@ void EQ::Net::DaybreakConnection::SendOutOfOrderAck(int stream_id, uint16_t seq) InternalBufferedSend(p); } -void EQ::Net::DaybreakConnection::SendDisconnect() +void EQ::Net::ReliableStreamConnection::SendDisconnect() { - DaybreakDisconnect disconnect; + ReliableStreamDisconnect disconnect; disconnect.zero = 0; disconnect.opcode = OP_SessionDisconnect; disconnect.connect_code = HostToNetwork(m_connect_code); @@ -1242,7 +1322,7 @@ void EQ::Net::DaybreakConnection::SendDisconnect() InternalSend(out); } -void EQ::Net::DaybreakConnection::InternalBufferedSend(Packet &p) +void EQ::Net::ReliableStreamConnection::InternalBufferedSend(Packet &p) { if (p.Length() > 0xFFU) { FlushBuffer(); @@ -1251,7 +1331,7 @@ void EQ::Net::DaybreakConnection::InternalBufferedSend(Packet &p) } //we could add this packet to a combined - size_t raw_size = DaybreakHeader::size() + (size_t)m_crc_bytes + m_buffered_packets_length + m_buffered_packets.size() + 1 + p.Length(); + size_t raw_size = ReliableStreamHeader::size() + (size_t)m_crc_bytes + m_buffered_packets_length + m_buffered_packets.size() + 1 + p.Length(); if (raw_size > m_max_packet_size) { FlushBuffer(); } @@ -1266,9 +1346,9 @@ void EQ::Net::DaybreakConnection::InternalBufferedSend(Packet &p) } } -void EQ::Net::DaybreakConnection::SendConnect() +void EQ::Net::ReliableStreamConnection::SendConnect() { - DaybreakConnect connect; + ReliableStreamConnect connect; connect.zero = 0; connect.opcode = OP_SessionRequest; connect.protocol_version = HostToNetwork(3U); @@ -1281,9 +1361,9 @@ void EQ::Net::DaybreakConnection::SendConnect() InternalSend(p); } -void EQ::Net::DaybreakConnection::SendKeepAlive() +void EQ::Net::ReliableStreamConnection::SendKeepAlive() { - DaybreakHeader keep_alive; + ReliableStreamHeader keep_alive; keep_alive.zero = 0; keep_alive.opcode = OP_KeepAlive; @@ -1293,102 +1373,100 @@ void EQ::Net::DaybreakConnection::SendKeepAlive() InternalSend(p); } -void EQ::Net::DaybreakConnection::InternalSend(Packet &p) -{ +void EQ::Net::ReliableStreamConnection::InternalSend(Packet &p) { if (m_owner->m_options.outgoing_data_rate > 0.0) { auto new_budget = m_outgoing_budget - (p.Length() / 1024.0); if (new_budget <= 0.0) { m_stats.dropped_datarate_packets++; return; - } - else { + } else { m_outgoing_budget = new_budget; } } m_last_send = Clock::now(); - auto send_func = [](uv_udp_send_t* req, int status) { - delete[](char*)req->data; - delete req; - }; + auto pooled_opt = send_buffer_pool.acquire(); + if (!pooled_opt) { + m_stats.dropped_datarate_packets++; + return; + } + + auto [send_req, data, ctx] = *pooled_opt; + ctx->pool = &send_buffer_pool; // set pool pointer + + sockaddr_in send_addr{}; + uv_ip4_addr(m_endpoint.c_str(), m_port, &send_addr); + uv_buf_t send_buffers[1]; if (PacketCanBeEncoded(p)) { - m_stats.bytes_before_encode += p.Length(); DynamicPacket out; out.PutPacket(0, p); - for (int i = 0; i < 2; ++i) { - switch (m_encode_passes[i]) { - case EncodeCompression: - if (out.GetInt8(0) == 0) - Compress(out, DaybreakHeader::size(), out.Length() - DaybreakHeader::size()); - else - Compress(out, 1, out.Length() - 1); - break; - case EncodeXOR: - if (out.GetInt8(0) == 0) - Encode(out, DaybreakHeader::size(), out.Length() - DaybreakHeader::size()); - else - Encode(out, 1, out.Length() - 1); - break; - default: - break; + for (auto &m_encode_passe: m_encode_passes) { + switch (m_encode_passe) { + case EncodeCompression: + if (out.GetInt8(0) == 0) { + Compress(out, ReliableStreamHeader::size(), out.Length() - ReliableStreamHeader::size()); + } else { + Compress(out, 1, out.Length() - 1); + } + break; + case EncodeXOR: + if (out.GetInt8(0) == 0) { + Encode(out, ReliableStreamHeader::size(), out.Length() - ReliableStreamHeader::size()); + } else { + Encode(out, 1, out.Length() - 1); + } + break; + default: + break; } } AppendCRC(out); - - uv_udp_send_t *send_req = new uv_udp_send_t; - memset(send_req, 0, sizeof(*send_req)); - sockaddr_in send_addr; - uv_ip4_addr(m_endpoint.c_str(), m_port, &send_addr); - uv_buf_t send_buffers[1]; - - char *data = new char[out.Length()]; memcpy(data, out.Data(), out.Length()); send_buffers[0] = uv_buf_init(data, out.Length()); - send_req->data = send_buffers[0].base; - - m_stats.sent_bytes += out.Length(); - m_stats.sent_packets++; - if (m_owner->m_options.simulated_out_packet_loss && m_owner->m_options.simulated_out_packet_loss >= m_owner->m_rand.Int(0, 100)) { - delete[](char*)send_req->data; - delete send_req; - return; - } - - uv_udp_send(send_req, &m_owner->m_socket, send_buffers, 1, (sockaddr*)&send_addr, send_func); - return; + } else { + memcpy(data, p.Data(), p.Length()); + send_buffers[0] = uv_buf_init(data, p.Length()); } - m_stats.bytes_before_encode += p.Length(); - - uv_udp_send_t *send_req = new uv_udp_send_t; - sockaddr_in send_addr; - uv_ip4_addr(m_endpoint.c_str(), m_port, &send_addr); - uv_buf_t send_buffers[1]; - - char *data = new char[p.Length()]; - memcpy(data, p.Data(), p.Length()); - send_buffers[0] = uv_buf_init(data, p.Length()); - send_req->data = send_buffers[0].base; - m_stats.sent_bytes += p.Length(); m_stats.sent_packets++; - if (m_owner->m_options.simulated_out_packet_loss && m_owner->m_options.simulated_out_packet_loss >= m_owner->m_rand.Int(0, 100)) { - delete[](char*)send_req->data; - delete send_req; + if (m_owner->m_options.simulated_out_packet_loss && + m_owner->m_options.simulated_out_packet_loss >= m_owner->m_rand.Int(0, 100)) { + send_buffer_pool.release(ctx); return; } - uv_udp_send(send_req, &m_owner->m_socket, send_buffers, 1, (sockaddr*)&send_addr, send_func); + int send_result = uv_udp_send( + send_req, &m_owner->m_socket, send_buffers, 1, (sockaddr *)&send_addr, + [](uv_udp_send_t *req, int status) { + auto *ctx = reinterpret_cast(req->data); + if (!ctx) { + std::cerr << "Error: send_req->data is null in callback!" << std::endl; + return; + } + + if (status < 0) { + std::cerr << "uv_udp_send failed: " << uv_strerror(status) << std::endl; + } + + ctx->pool->release(ctx); + } + ); + + if (send_result < 0) { + std::cerr << "uv_udp_send() failed: " << uv_strerror(send_result) << std::endl; + send_buffer_pool.release(ctx); + } } -void EQ::Net::DaybreakConnection::InternalQueuePacket(Packet &p, int stream_id, bool reliable) +void EQ::Net::ReliableStreamConnection::InternalQueuePacket(Packet &p, int stream_id, bool reliable) { if (!reliable) { auto max_raw_size = 0xFFU - m_crc_bytes; @@ -1402,23 +1480,23 @@ void EQ::Net::DaybreakConnection::InternalQueuePacket(Packet &p, int stream_id, } auto stream = &m_streams[stream_id]; - auto max_raw_size = m_max_packet_size - m_crc_bytes - DaybreakReliableHeader::size() - 1; // -1 for compress flag + auto max_raw_size = m_max_packet_size - m_crc_bytes - ReliableStreamReliableHeader::size() - 1; // -1 for compress flag size_t length = p.Length(); if (length > max_raw_size) { - DaybreakReliableFragmentHeader first_header; + ReliableStreamReliableFragmentHeader first_header; first_header.reliable.zero = 0; first_header.reliable.opcode = OP_Fragment + stream_id; first_header.reliable.sequence = HostToNetwork(stream->sequence_out); first_header.total_size = (uint32_t)HostToNetwork((uint32_t)length); size_t used = 0; - size_t sublen = m_max_packet_size - m_crc_bytes - DaybreakReliableFragmentHeader::size() - 1; // -1 for compress flag + size_t sublen = m_max_packet_size - m_crc_bytes - ReliableStreamReliableFragmentHeader::size() - 1; // -1 for compress flag DynamicPacket first_packet; first_packet.PutSerialize(0, first_header); - first_packet.PutData(DaybreakReliableFragmentHeader::size(), (char*)p.Data() + used, sublen); + first_packet.PutData(ReliableStreamReliableFragmentHeader::size(), (char*)p.Data() + used, sublen); used += sublen; - DaybreakSentPacket sent; + ReliableStreamSentPacket sent; sent.packet.PutPacket(0, first_packet); sent.last_sent = Clock::now(); sent.first_sent = Clock::now(); @@ -1435,22 +1513,22 @@ void EQ::Net::DaybreakConnection::InternalQueuePacket(Packet &p, int stream_id, while (used < length) { auto left = length - used; DynamicPacket packet; - DaybreakReliableHeader header; + ReliableStreamReliableHeader header; header.zero = 0; header.opcode = OP_Fragment + stream_id; header.sequence = HostToNetwork(stream->sequence_out); packet.PutSerialize(0, header); if (left > max_raw_size) { - packet.PutData(DaybreakReliableHeader::size(), (char*)p.Data() + used, max_raw_size); + packet.PutData(ReliableStreamReliableHeader::size(), (char*)p.Data() + used, max_raw_size); used += max_raw_size; } else { - packet.PutData(DaybreakReliableHeader::size(), (char*)p.Data() + used, left); + packet.PutData(ReliableStreamReliableHeader::size(), (char*)p.Data() + used, left); used += left; } - DaybreakSentPacket sent; + ReliableStreamSentPacket sent; sent.packet.PutPacket(0, packet); sent.last_sent = Clock::now(); sent.first_sent = Clock::now(); @@ -1467,14 +1545,14 @@ void EQ::Net::DaybreakConnection::InternalQueuePacket(Packet &p, int stream_id, } else { DynamicPacket packet; - DaybreakReliableHeader header; + ReliableStreamReliableHeader header; header.zero = 0; header.opcode = OP_Packet + stream_id; header.sequence = HostToNetwork(stream->sequence_out); packet.PutSerialize(0, header); - packet.PutPacket(DaybreakReliableHeader::size(), p); + packet.PutPacket(ReliableStreamReliableHeader::size(), p); - DaybreakSentPacket sent; + ReliableStreamSentPacket sent; sent.packet.PutPacket(0, packet); sent.last_sent = Clock::now(); sent.first_sent = Clock::now(); @@ -1490,7 +1568,7 @@ void EQ::Net::DaybreakConnection::InternalQueuePacket(Packet &p, int stream_id, } } -void EQ::Net::DaybreakConnection::FlushBuffer() +void EQ::Net::ReliableStreamConnection::FlushBuffer() { if (m_buffered_packets.empty()) { return; @@ -1517,7 +1595,7 @@ void EQ::Net::DaybreakConnection::FlushBuffer() m_buffered_packets_length = 0; } -EQ::Net::SequenceOrder EQ::Net::DaybreakConnection::CompareSequence(uint16_t expected, uint16_t actual) const +EQ::Net::SequenceOrder EQ::Net::ReliableStreamConnection::CompareSequence(uint16_t expected, uint16_t actual) const { int diff = (int)actual - (int)expected; diff --git a/common/net/daybreak_connection.h b/common/net/reliable_stream_connection.h similarity index 70% rename from common/net/daybreak_connection.h rename to common/net/reliable_stream_connection.h index c2c714813..97e1982c7 100644 --- a/common/net/daybreak_connection.h +++ b/common/net/reliable_stream_connection.h @@ -2,7 +2,8 @@ #include "../random.h" #include "packet.h" -#include "daybreak_structs.h" +#include "reliable_stream_structs.h" +#include "reliable_stream_pooling.h" #include #include #include @@ -15,7 +16,7 @@ namespace EQ { namespace Net { - enum DaybreakProtocolOpcode + enum ReliableStreamProtocolOpcode { OP_Padding = 0x00, OP_SessionRequest = 0x01, @@ -54,7 +55,7 @@ namespace EQ StatusDisconnected }; - enum DaybreakEncodeType + enum ReliableStreamEncodeType { EncodeNone = 0, EncodeCompression = 1, @@ -71,9 +72,9 @@ namespace EQ typedef std::chrono::steady_clock::time_point Timestamp; typedef std::chrono::steady_clock Clock; - struct DaybreakConnectionStats + struct ReliableStreamConnectionStats { - DaybreakConnectionStats() { + ReliableStreamConnectionStats() { recv_bytes = 0; sent_bytes = 0; recv_packets = 0; @@ -133,14 +134,14 @@ namespace EQ uint64_t bytes_before_encode; }; - class DaybreakConnectionManager; - class DaybreakConnection; - class DaybreakConnection + class ReliableStreamConnectionManager; + class ReliableStreamConnection; + class ReliableStreamConnection { public: - DaybreakConnection(DaybreakConnectionManager *owner, const DaybreakConnect &connect, const std::string &endpoint, int port); - DaybreakConnection(DaybreakConnectionManager *owner, const std::string &endpoint, int port); - ~DaybreakConnection(); + ReliableStreamConnection(ReliableStreamConnectionManager *owner, const ReliableStreamConnect &connect, const std::string &endpoint, int port); + ReliableStreamConnection(ReliableStreamConnectionManager *owner, const std::string &endpoint, int port); + ~ReliableStreamConnection(); const std::string& RemoteEndpoint() const { return m_endpoint; } int RemotePort() const { return m_port; } @@ -150,23 +151,23 @@ namespace EQ void QueuePacket(Packet &p, int stream); void QueuePacket(Packet &p, int stream, bool reliable); - DaybreakConnectionStats GetStats(); + ReliableStreamConnectionStats GetStats(); void ResetStats(); size_t GetRollingPing() const { return m_rolling_ping; } DbProtocolStatus GetStatus() const { return m_status; } - const DaybreakEncodeType* GetEncodePasses() const { return m_encode_passes; } - const DaybreakConnectionManager* GetManager() const { return m_owner; } - DaybreakConnectionManager* GetManager() { return m_owner; } + const ReliableStreamEncodeType* GetEncodePasses() const { return m_encode_passes; } + const ReliableStreamConnectionManager* GetManager() const { return m_owner; } + ReliableStreamConnectionManager* GetManager() { return m_owner; } private: - DaybreakConnectionManager *m_owner; + ReliableStreamConnectionManager *m_owner; std::string m_endpoint; int m_port; uint32_t m_connect_code; uint32_t m_encode_key; uint32_t m_max_packet_size; uint32_t m_crc_bytes; - DaybreakEncodeType m_encode_passes[2]; + ReliableStreamEncodeType m_encode_passes[2]; Timestamp m_last_send; Timestamp m_last_recv; @@ -175,13 +176,19 @@ namespace EQ std::list m_buffered_packets; size_t m_buffered_packets_length; std::unique_ptr m_combined; - DaybreakConnectionStats m_stats; + ReliableStreamConnectionStats m_stats; Timestamp m_last_session_stats; size_t m_rolling_ping; Timestamp m_close_time; double m_outgoing_budget; - struct DaybreakSentPacket + // resend tracking + size_t m_resend_packets_sent = 0; + size_t m_resend_bytes_sent = 0; + bool m_acked_since_last_resend = false; + Timestamp m_last_ack; + + struct ReliableStreamSentPacket { DynamicPacket packet; Timestamp last_sent; @@ -190,9 +197,9 @@ namespace EQ size_t resend_delay; }; - struct DaybreakStream + struct ReliableStream { - DaybreakStream() { + ReliableStream() { sequence_in = 0; sequence_out = 0; fragment_current_bytes = 0; @@ -207,11 +214,11 @@ namespace EQ uint32_t fragment_current_bytes; uint32_t fragment_total_bytes; - std::map sent_packets; + std::map sent_packets; }; - DaybreakStream m_streams[4]; - std::weak_ptr m_self; + ReliableStream m_streams[4]; + std::weak_ptr m_self; void Process(); void ProcessPacket(Packet &p); @@ -244,12 +251,12 @@ namespace EQ void FlushBuffer(); SequenceOrder CompareSequence(uint16_t expected, uint16_t actual) const; - friend class DaybreakConnectionManager; + friend class ReliableStreamConnectionManager; }; - struct DaybreakConnectionManagerOptions + struct ReliableStreamConnectionManagerOptions { - DaybreakConnectionManagerOptions() { + ReliableStreamConnectionManagerOptions() { max_connection_count = 0; keepalive_delay_ms = 9000; resend_delay_ms = 30; @@ -261,8 +268,8 @@ namespace EQ connect_stale_ms = 5000; crc_length = 2; max_packet_size = 512; - encode_passes[0] = DaybreakEncodeType::EncodeNone; - encode_passes[1] = DaybreakEncodeType::EncodeNone; + encode_passes[0] = ReliableStreamEncodeType::EncodeNone; + encode_passes[1] = ReliableStreamEncodeType::EncodeNone; port = 0; hold_size = 512; hold_length_ms = 50; @@ -292,28 +299,28 @@ namespace EQ double tic_rate_hertz; size_t resend_timeout; size_t connection_close_time; - DaybreakEncodeType encode_passes[2]; + ReliableStreamEncodeType encode_passes[2]; int port; double outgoing_data_rate; }; - class DaybreakConnectionManager + class ReliableStreamConnectionManager { public: - DaybreakConnectionManager(); - DaybreakConnectionManager(const DaybreakConnectionManagerOptions &opts); - ~DaybreakConnectionManager(); + ReliableStreamConnectionManager(); + ReliableStreamConnectionManager(const ReliableStreamConnectionManagerOptions &opts); + ~ReliableStreamConnectionManager(); void Connect(const std::string &addr, int port); void Process(); void UpdateDataBudget(); void ProcessResend(); - void OnNewConnection(std::function)> func) { m_on_new_connection = func; } - void OnConnectionStateChange(std::function, DbProtocolStatus, DbProtocolStatus)> func) { m_on_connection_state_change = func; } - void OnPacketRecv(std::function, const Packet &)> func) { m_on_packet_recv = func; } + void OnNewConnection(std::function)> func) { m_on_new_connection = func; } + void OnConnectionStateChange(std::function, DbProtocolStatus, DbProtocolStatus)> func) { m_on_connection_state_change = func; } + void OnPacketRecv(std::function, const Packet &)> func) { m_on_packet_recv = func; } void OnErrorMessage(std::function func) { m_on_error_message = func; } - DaybreakConnectionManagerOptions& GetOptions() { return m_options; } + ReliableStreamConnectionManagerOptions& GetOptions() { return m_options; } private: void Attach(uv_loop_t *loop); void Detach(); @@ -322,18 +329,18 @@ namespace EQ uv_timer_t m_timer; uv_udp_t m_socket; uv_loop_t *m_attached; - DaybreakConnectionManagerOptions m_options; - std::function)> m_on_new_connection; - std::function, DbProtocolStatus, DbProtocolStatus)> m_on_connection_state_change; - std::function, const Packet&)> m_on_packet_recv; + ReliableStreamConnectionManagerOptions m_options; + std::function)> m_on_new_connection; + std::function, DbProtocolStatus, DbProtocolStatus)> m_on_connection_state_change; + std::function, const Packet&)> m_on_packet_recv; std::function m_on_error_message; - std::map, std::shared_ptr> m_connections; + std::map, std::shared_ptr> m_connections; void ProcessPacket(const std::string &endpoint, int port, const char *data, size_t size); - std::shared_ptr FindConnectionByEndpoint(std::string addr, int port); + std::shared_ptr FindConnectionByEndpoint(std::string addr, int port); void SendDisconnect(const std::string &addr, int port); - friend class DaybreakConnection; + friend class ReliableStreamConnection; }; } } diff --git a/common/net/reliable_stream_pooling.h b/common/net/reliable_stream_pooling.h new file mode 100644 index 000000000..e374ecdc7 --- /dev/null +++ b/common/net/reliable_stream_pooling.h @@ -0,0 +1,123 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include "../eqemu_logsys.h" +#include + +constexpr size_t UDP_BUFFER_SIZE = 512; + +struct EmbeddedContext { + size_t pool_index; + class SendBufferPool* pool; +}; + +class SendBufferPool { +public: + explicit SendBufferPool(size_t initial_capacity = 64) + : m_capacity(initial_capacity), m_head(0) + { + LogNetClient("[SendBufferPool] Initializing with capacity [{}]", (int)m_capacity); + + m_pool.reserve(m_capacity); + m_locks = std::make_unique(m_capacity); + + for (size_t i = 0; i < m_capacity; ++i) { + auto* req = new PooledUdpSend(); + req->context.pool_index = i; + req->context.pool = this; + req->uv_req.data = &req->context; + + m_pool.emplace_back(std::unique_ptr(req)); + m_locks[i].store(false, std::memory_order_relaxed); + } + } + + std::optional> acquire() { + size_t cap = m_capacity.load(std::memory_order_acquire); + for (size_t i = 0; i < cap; ++i) { + size_t index = m_head.fetch_add(1, std::memory_order_relaxed) % cap; + bool expected = false; + if (m_locks[index].compare_exchange_strong(expected, true)) { + auto* req = m_pool[index].get(); + LogNetClientDetail("[SendBufferPool] Acquired [{}]", index); + return std::make_tuple(&req->uv_req, req->buffer.data(), &req->context); + } + } + + LogNetClient("[SendBufferPool] Growing from [{}] to [{}]", cap, cap * 2); + grow(); + return acquireAfterGrowth(); + } + + void release(EmbeddedContext* ctx) { + if (!ctx || ctx->pool != this || ctx->pool_index >= m_capacity.load(std::memory_order_acquire)) { + LogNetClient("[SendBufferPool] Invalid context release [{}]", ctx ? ctx->pool_index : -1); + return; + } + m_locks[ctx->pool_index].store(false, std::memory_order_release); + LogNetClientDetail("[SendBufferPool] Released [{}]", ctx->pool_index); + } + +private: + struct PooledUdpSend { + uv_udp_send_t uv_req; + std::array buffer; + EmbeddedContext context; + }; + + std::vector> m_pool; + std::unique_ptr m_locks; + std::atomic m_capacity; + std::atomic m_head; + std::mutex m_grow_mutex; + + void grow() { + std::lock_guard lock(m_grow_mutex); + + size_t old_cap = m_capacity.load(std::memory_order_acquire); + size_t new_cap = old_cap * 2; + + m_pool.reserve(new_cap); + for (size_t i = old_cap; i < new_cap; ++i) { + auto* req = new PooledUdpSend(); + req->context.pool_index = i; + req->context.pool = this; + req->uv_req.data = &req->context; + + m_pool.emplace_back(std::unique_ptr(req)); + } + + auto new_locks = std::make_unique(new_cap); + for (size_t i = 0; i < old_cap; ++i) { + new_locks[i].store(m_locks[i].load(std::memory_order_acquire)); + } + for (size_t i = old_cap; i < new_cap; ++i) { + new_locks[i].store(false, std::memory_order_relaxed); + } + + m_locks = std::move(new_locks); + m_capacity.store(new_cap, std::memory_order_release); + + LogNetClient("[SendBufferPool] Grew to [{}] from [{}]", new_cap, old_cap); + } + + std::optional> acquireAfterGrowth() { + size_t cap = m_capacity.load(std::memory_order_acquire); + for (size_t i = 0; i < cap; ++i) { + size_t index = m_head.fetch_add(1, std::memory_order_relaxed) % cap; + bool expected = false; + if (m_locks[index].compare_exchange_strong(expected, true)) { + auto* req = m_pool[index].get(); + LogNetClient("[SendBufferPool] Acquired after grow [{}]", index); + return std::make_tuple(&req->uv_req, req->buffer.data(), &req->context); + } + } + return std::nullopt; + } +}; diff --git a/common/net/daybreak_structs.h b/common/net/reliable_stream_structs.h similarity index 86% rename from common/net/daybreak_structs.h rename to common/net/reliable_stream_structs.h index b03ed18bb..f48e1f9c3 100644 --- a/common/net/daybreak_structs.h +++ b/common/net/reliable_stream_structs.h @@ -8,7 +8,7 @@ namespace EQ { namespace Net { - struct DaybreakHeader + struct ReliableStreamHeader { static size_t size() { return 2; } uint8_t zero; @@ -22,7 +22,7 @@ namespace EQ } }; - struct DaybreakConnect + struct ReliableStreamConnect { static size_t size() { return 14; } uint8_t zero; @@ -42,7 +42,7 @@ namespace EQ } }; - struct DaybreakConnectReply + struct ReliableStreamConnectReply { static size_t size() { return 17; } uint8_t zero; @@ -68,7 +68,7 @@ namespace EQ } }; - struct DaybreakDisconnect + struct ReliableStreamDisconnect { static size_t size() { return 8; } uint8_t zero; @@ -84,7 +84,7 @@ namespace EQ } }; - struct DaybreakReliableHeader + struct ReliableStreamReliableHeader { static size_t size() { return 4; } uint8_t zero; @@ -100,10 +100,10 @@ namespace EQ } }; - struct DaybreakReliableFragmentHeader + struct ReliableStreamReliableFragmentHeader { - static size_t size() { return 4 + DaybreakReliableHeader::size(); } - DaybreakReliableHeader reliable; + static size_t size() { return 4 + ReliableStreamReliableHeader::size(); } + ReliableStreamReliableHeader reliable; uint32_t total_size; template @@ -114,7 +114,7 @@ namespace EQ } }; - struct DaybreakSessionStatRequest + struct ReliableStreamSessionStatRequest { static size_t size() { return 40; } uint8_t zero; @@ -144,7 +144,7 @@ namespace EQ } }; - struct DaybreakSessionStatResponse + struct ReliableStreamSessionStatResponse { static size_t size() { return 40; } uint8_t zero; @@ -171,3 +171,4 @@ namespace EQ }; } } + diff --git a/common/net/servertalk_client_connection.cpp b/common/net/servertalk_client_connection.cpp index d873d43ca..524c9d496 100644 --- a/common/net/servertalk_client_connection.cpp +++ b/common/net/servertalk_client_connection.cpp @@ -62,15 +62,15 @@ void EQ::Net::ServertalkClient::Connect() m_connecting = true; EQ::Net::TCPConnection::Connect(m_addr, m_port, false, [this](std::shared_ptr connection) { if (connection == nullptr) { - LogF(Logs::General, Logs::TCPConnection, "Error connecting to {0}:{1}, attempting to reconnect...", m_addr, m_port); + LogNetTCP("Error connecting to {0}:{1}, attempting to reconnect...", m_addr, m_port); m_connecting = false; return; } - LogF(Logs::General, Logs::TCPConnection, "Connected to {0}:{1}", m_addr, m_port); + LogNetTCP("Connected to {0}:{1}", m_addr, m_port); m_connection = connection; m_connection->OnDisconnect([this](EQ::Net::TCPConnection *c) { - LogF(Logs::General, Logs::TCPConnection, "Connection lost to {0}:{1}, attempting to reconnect...", m_addr, m_port); + LogNetTCP("Connection lost to {0}:{1}, attempting to reconnect...", m_addr, m_port); m_connection.reset(); }); diff --git a/common/net/servertalk_client_connection.h b/common/net/servertalk_client_connection.h index 704b48ded..5a1e3db32 100644 --- a/common/net/servertalk_client_connection.h +++ b/common/net/servertalk_client_connection.h @@ -23,6 +23,9 @@ namespace EQ bool Connected() const { return m_connecting != true; } std::shared_ptr Handle() { return m_connection; } + + const std::unique_ptr &GetTimer() const { return m_timer; } + private: void Connect(); void ProcessData(EQ::Net::TCPConnection *c, const unsigned char *data, size_t length); diff --git a/common/net/servertalk_legacy_client_connection.cpp b/common/net/servertalk_legacy_client_connection.cpp index 8f2260dd6..deba4a464 100644 --- a/common/net/servertalk_legacy_client_connection.cpp +++ b/common/net/servertalk_legacy_client_connection.cpp @@ -58,15 +58,15 @@ void EQ::Net::ServertalkLegacyClient::Connect() m_connecting = true; EQ::Net::TCPConnection::Connect(m_addr, m_port, false, [this](std::shared_ptr connection) { if (connection == nullptr) { - LogF(Logs::General, Logs::TCPConnection, "Error connecting to {0}:{1}, attempting to reconnect...", m_addr, m_port); + LogNetTCP("Error connecting to {0}:{1}, attempting to reconnect...", m_addr, m_port); m_connecting = false; return; } - LogF(Logs::General, Logs::TCPConnection, "Connected to {0}:{1}", m_addr, m_port); + LogNetTCP("Connected to {0}:{1}", m_addr, m_port); m_connection = connection; m_connection->OnDisconnect([this](EQ::Net::TCPConnection *c) { - LogF(Logs::General, Logs::TCPConnection, "Connection lost to {0}:{1}, attempting to reconnect...", m_addr, m_port); + LogNetTCP("Connection lost to {0}:{1}, attempting to reconnect...", m_addr, m_port); m_connection.reset(); }); @@ -131,7 +131,7 @@ void EQ::Net::ServertalkLegacyClient::ProcessReadBuffer() } else { EQ::Net::StaticPacket p(&m_buffer[current + 4], length); - + auto cb = m_message_callbacks.find(opcode); if (cb != m_message_callbacks.end()) { cb->second(opcode, p); diff --git a/common/net/servertalk_server_connection.cpp b/common/net/servertalk_server_connection.cpp index ad86727ef..efc2d572e 100644 --- a/common/net/servertalk_server_connection.cpp +++ b/common/net/servertalk_server_connection.cpp @@ -25,7 +25,7 @@ void EQ::Net::ServertalkServerConnection::Send(uint16_t opcode, EQ::Net::Packet return; if (opcode == ServerOP_UsertoWorldReq) { - auto req_in = (UsertoWorldRequest_Struct*)p.Data(); + auto req_in = (UsertoWorldRequest*)p.Data(); EQ::Net::DynamicPacket req; size_t i = 0; @@ -45,7 +45,7 @@ void EQ::Net::ServertalkServerConnection::Send(uint16_t opcode, EQ::Net::Packet } if (opcode == ServerOP_LSClientAuth) { - auto req_in = (ClientAuth_Struct*)p.Data(); + auto req_in = (ClientAuth*)p.Data(); EQ::Net::DynamicPacket req; size_t i = 0; @@ -54,7 +54,7 @@ void EQ::Net::ServertalkServerConnection::Send(uint16_t opcode, EQ::Net::Packet req.PutData(i, req_in->key, 30); i += 30; req.PutUInt8(i, req_in->lsadmin); i += 1; req.PutUInt16(i, req_in->is_world_admin); i += 2; - req.PutUInt32(i, req_in->ip); i += 4; + req.PutUInt32(i, req_in->ip_address); i += 4; req.PutUInt8(i, req_in->is_client_from_local_network); i += 1; EQ::Net::DynamicPacket out; @@ -123,7 +123,7 @@ void EQ::Net::ServertalkServerConnection::ProcessReadBuffer() { size_t current = 0; size_t total = m_buffer.size(); - constexpr size_t ls_info_size = sizeof(ServerNewLSInfo_Struct); + constexpr size_t ls_info_size = sizeof(LoginserverNewWorldRequest); while (current < total) { auto left = total - current; @@ -138,7 +138,7 @@ void EQ::Net::ServertalkServerConnection::ProcessReadBuffer() //this creates a small edge case where the exact size of a //packet from the modern protocol can't be "43061256" //so in send we pad it one byte if that's the case - if (leg_opcode == ServerOP_NewLSInfo && leg_size == sizeof(ServerNewLSInfo_Struct)) { + if (leg_opcode == ServerOP_NewLSInfo && leg_size == sizeof(LoginserverNewWorldRequest)) { m_legacy_mode = true; m_identifier = "World"; m_parent->ConnectionIdentified(this); @@ -319,7 +319,7 @@ void EQ::Net::ServertalkServerConnection::ProcessMessage(EQ::Net::Packet &p) size_t message_len = length; EQ::Net::StaticPacket packet(&data[0], message_len); - const auto is_detail_enabled = LogSys.IsLogEnabled(Logs::Detail, Logs::PacketServerToServer); + const auto is_detail_enabled = EQEmuLogSys::Instance()->IsLogEnabled(Logs::Detail, Logs::PacketServerToServer); if (opcode != ServerOP_KeepAlive || is_detail_enabled) { LogPacketServerToServer( "[{:#06x}] Size [{}] {}", diff --git a/common/net/tcp_connection.cpp b/common/net/tcp_connection.cpp index 0c2512dcc..f277e95e8 100644 --- a/common/net/tcp_connection.cpp +++ b/common/net/tcp_connection.cpp @@ -1,5 +1,8 @@ #include "tcp_connection.h" #include "../event/event_loop.h" +#include + +WriteReqPool tcp_write_pool; void on_close_handle(uv_handle_t* handle) { delete (uv_tcp_t *)handle; @@ -64,34 +67,37 @@ void EQ::Net::TCPConnection::Connect(const std::string &addr, int port, bool ipv }); } -void EQ::Net::TCPConnection::Start() { - uv_read_start((uv_stream_t*)m_socket, [](uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) { - buf->base = new char[suggested_size]; - buf->len = suggested_size; - }, [](uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) { +void EQ::Net::TCPConnection::Start() +{ + uv_read_start( + (uv_stream_t *) m_socket, [](uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) { + if (suggested_size > 65536) { + buf->base = new char[suggested_size]; + buf->len = suggested_size; + return; + } - TCPConnection *connection = (TCPConnection*)stream->data; + static thread_local char temp_buf[65536]; + buf->base = temp_buf; + buf->len = 65536; + }, [](uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { + auto *connection = (TCPConnection *) stream->data; - if (nread > 0) { - connection->Read(buf->base, nread); + if (nread > 0) { + connection->Read(buf->base, nread); + } + else if (nread == UV_EOF) { + connection->Disconnect(); + } + else if (nread < 0) { + connection->Disconnect(); + } - if (buf->base) { - delete[] buf->base; + if (buf->len > 65536) { + delete [] buf->base; } } - else if (nread == UV_EOF) { - if (buf->base) { - delete[] buf->base; - } - } - else if (nread < 0) { - connection->Disconnect(); - - if (buf->base) { - delete[] buf->base; - } - } - }); + ); } void EQ::Net::TCPConnection::OnRead(std::function cb) @@ -128,43 +134,92 @@ void EQ::Net::TCPConnection::Read(const char *data, size_t count) } } -void EQ::Net::TCPConnection::Write(const char *data, size_t count) -{ - if (!m_socket) { +void EQ::Net::TCPConnection::Write(const char* data, size_t count) { + if (!m_socket || !data || count == 0) { + std::cerr << "TCPConnection::Write - Invalid socket or data\n"; return; } - struct WriteBaton - { - TCPConnection *connection; - char *buffer; - }; - - WriteBaton *baton = new WriteBaton; - baton->connection = this; - baton->buffer = new char[count]; - - uv_write_t *write_req = new uv_write_t; - memset(write_req, 0, sizeof(uv_write_t)); - write_req->data = baton; - uv_buf_t send_buffers[1]; - - memcpy(baton->buffer, data, count); - send_buffers[0] = uv_buf_init(baton->buffer, count); - - uv_write(write_req, (uv_stream_t*)m_socket, send_buffers, 1, [](uv_write_t* req, int status) { - WriteBaton *baton = (WriteBaton*)req->data; - delete[] baton->buffer; - delete req; - - if (status < 0) { - baton->connection->Disconnect(); + if (count <= TCP_BUFFER_SIZE) { + // Fast path: use pooled request with embedded buffer + auto req_opt = tcp_write_pool.acquire(); + if (!req_opt) { + std::cerr << "TCPConnection::Write - Out of write requests\n"; + return; } - delete baton; - }); + TCPWriteReq* write_req = *req_opt; + + // Fill buffer and set context + memcpy(write_req->buffer.data(), data, count); + write_req->connection = this; + write_req->magic = 0xC0FFEE; + + uv_buf_t buf = uv_buf_init(write_req->buffer.data(), static_cast(count)); + + int result = uv_write( + &write_req->req, + reinterpret_cast(m_socket), + &buf, + 1, + [](uv_write_t* req, int status) { + auto* full_req = reinterpret_cast(req); + if (full_req->magic != 0xC0FFEE) { + std::cerr << "uv_write callback - invalid magic, skipping release\n"; + return; + } + + tcp_write_pool.release(full_req); + + if (status < 0 && full_req->connection) { + std::cerr << "uv_write failed: " << uv_strerror(status) << std::endl; + full_req->connection->Disconnect(); + } + } + ); + + if (result < 0) { + std::cerr << "uv_write() failed immediately: " << uv_strerror(result) << std::endl; + tcp_write_pool.release(write_req); + } + + } else { + // Slow path: allocate heap buffer for large write + LogNetTCP("[TCPConnection] Large write of [{}] bytes, using heap buffer", count); + + char* heap_buffer = new char[count]; + memcpy(heap_buffer, data, count); + + uv_write_t* write_req = new uv_write_t; + write_req->data = heap_buffer; + + uv_buf_t buf = uv_buf_init(heap_buffer, static_cast(count)); + + int result = uv_write( + write_req, + reinterpret_cast(m_socket), + &buf, + 1, + [](uv_write_t* req, int status) { + char* data = static_cast(req->data); + delete[] data; + delete req; + + if (status < 0) { + std::cerr << "uv_write (large) failed: " << uv_strerror(status) << std::endl; + } + } + ); + + if (result < 0) { + std::cerr << "uv_write() (large) failed immediately: " << uv_strerror(result) << std::endl; + delete[] heap_buffer; + delete write_req; + } + } } + std::string EQ::Net::TCPConnection::LocalIP() const { sockaddr_storage addr; diff --git a/common/net/tcp_connection.h b/common/net/tcp_connection.h index 6bc256e85..d64695dd2 100644 --- a/common/net/tcp_connection.h +++ b/common/net/tcp_connection.h @@ -1,5 +1,6 @@ #pragma once +#include "tcp_connection_pooling.h" #include #include #include @@ -16,7 +17,7 @@ namespace EQ ~TCPConnection(); static void Connect(const std::string &addr, int port, bool ipv6, std::function)> cb); - + void Start(); void OnRead(std::function cb); void OnDisconnect(std::function cb); diff --git a/common/net/tcp_connection_pooling.h b/common/net/tcp_connection_pooling.h new file mode 100644 index 000000000..44f0abe92 --- /dev/null +++ b/common/net/tcp_connection_pooling.h @@ -0,0 +1,125 @@ +#pragma once + +#include "../eqemu_logsys.h" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace EQ { namespace Net { class TCPConnection; } } + +constexpr size_t TCP_BUFFER_SIZE = 8192; + +struct TCPWriteReq { + uv_write_t req{}; + std::array buffer{}; + size_t buffer_index{}; + EQ::Net::TCPConnection* connection{}; + uint32_t magic = 0xC0FFEE; +}; + +class WriteReqPool { +public: + explicit WriteReqPool(size_t initial_capacity = 512) + : m_capacity(initial_capacity), m_head(0) { + initialize_pool(m_capacity); + } + + std::optional acquire() { + size_t cap = m_capacity.load(std::memory_order_acquire); + + for (size_t i = 0; i < cap; ++i) { + size_t index = m_head.fetch_add(1, std::memory_order_relaxed) % cap; + + bool expected = false; + if (m_locks[index].compare_exchange_strong(expected, true, std::memory_order_acquire)) { + LogNetTCPDetail("[WriteReqPool] Acquired buffer index [{}]", index); + return m_reqs[index].get(); + } + } + + LogNetTCP("[WriteReqPool] Growing from [{}] to [{}]", cap, cap * 2); + grow(); + return acquireAfterGrow(); + } + + void release(TCPWriteReq* req) { + if (!req) return; + + const size_t index = req->buffer_index; + const size_t cap = m_capacity.load(std::memory_order_acquire); + + if (index >= cap || m_reqs[index].get() != req) { + std::cerr << "WriteReqPool::release - Invalid or stale pointer (index=" << index << ")\n"; + return; + } + + m_locks[index].store(false, std::memory_order_release); + LogNetTCPDetail("[WriteReqPool] Released buffer index [{}]", index); + } + +private: + std::vector> m_reqs; + std::unique_ptr m_locks; + std::atomic m_capacity; + std::atomic m_head; + std::mutex m_grow_mutex; + + void initialize_pool(size_t count) { + m_reqs.reserve(count); + m_locks = std::make_unique(count); + + for (size_t i = 0; i < count; ++i) { + auto req = std::make_unique(); + req->buffer_index = i; + req->req.data = req.get(); // optional: for use in libuv callbacks + m_locks[i].store(false, std::memory_order_relaxed); + m_reqs.emplace_back(std::move(req)); + } + + m_capacity.store(count, std::memory_order_release); + } + + void grow() { + std::lock_guard lock(m_grow_mutex); + + const size_t old_cap = m_capacity.load(std::memory_order_acquire); + const size_t new_cap = old_cap * 2; + + m_reqs.reserve(new_cap); + for (size_t i = old_cap; i < new_cap; ++i) { + auto req = std::make_unique(); + req->buffer_index = i; + req->req.data = req.get(); // optional + m_reqs.emplace_back(std::move(req)); + } + + auto new_locks = std::make_unique(new_cap); + for (size_t i = 0; i < old_cap; ++i) { + new_locks[i].store(m_locks[i].load(std::memory_order_acquire)); + } + for (size_t i = old_cap; i < new_cap; ++i) { + new_locks[i].store(false, std::memory_order_relaxed); + } + + m_locks = std::move(new_locks); + m_capacity.store(new_cap, std::memory_order_release); + } + + std::optional acquireAfterGrow() { + const size_t cap = m_capacity.load(std::memory_order_acquire); + + for (size_t i = 0; i < cap; ++i) { + bool expected = false; + if (m_locks[i].compare_exchange_strong(expected, true, std::memory_order_acquire)) { + LogNetTCP("[WriteReqPool] Acquired buffer index [{}] after grow", i); + return m_reqs[i].get(); + } + } + return std::nullopt; + } +}; diff --git a/common/opcode_dispatch.h b/common/opcode_dispatch.h index a19cb79a3..60e381320 100644 --- a/common/opcode_dispatch.h +++ b/common/opcode_dispatch.h @@ -197,6 +197,7 @@ IN(OP_RecipeDetails, uint32); //there is also a complicated OP_RecipeDetails reply struct OUT IN(OP_RecipeAutoCombine, RecipeAutoCombine_Struct); IN(OP_TradeSkillCombine, NewCombine_Struct); +IN(OP_TradeSkillRecipeInspect, TradeSkillRecipeInspect_Struct); IN(OP_ItemName, ItemNamePacket_Struct); IN(OP_AugmentItem, AugmentItem_Struct); IN(OP_ClickDoor, ClickDoor_Struct); @@ -250,6 +251,7 @@ IN(OP_TraderBuy, TraderBuy_Struct); IN(OP_Trader, Trader_ShowItems_Struct); IN(OP_GMFind, GMSummon_Struct); IN(OP_PickPocket, PickPocket_Struct); +IN(OP_PickZone, PickZone_Struct); IN(OP_Bind_Wound, BindWound_Struct); INr(OP_TrackTarget); INr(OP_Track); diff --git a/common/patches/rof.cpp b/common/patches/rof.cpp index 7d16d95e4..15b745b16 100644 --- a/common/patches/rof.cpp +++ b/common/patches/rof.cpp @@ -78,7 +78,7 @@ namespace RoF { //create our opcode manager if we havent already if (opcodes == nullptr) { - std::string opfile = fmt::format("{}/patch_{}.conf", path.GetPatchPath(), name); + std::string opfile = fmt::format("{}/patch_{}.conf", PathManager::Instance()->GetPatchPath(), name); //load up the opcode manager. //TODO: figure out how to support shared memory with multiple patches... @@ -117,7 +117,7 @@ namespace RoF //we need to go to every stream and replace it's manager. if (opcodes != nullptr) { - std::string opfile = fmt::format("{}/patch_{}.conf", path.GetPatchPath(), name); + std::string opfile = fmt::format("{}/patch_{}.conf", PathManager::Instance()->GetPatchPath(), name); if (!opcodes->ReloadOpcodes(opfile.c_str())) { LogNetcode("[OPCODES] Error reloading opcodes file [{}] for patch [{}]", opfile.c_str(), name); return; @@ -1213,22 +1213,22 @@ namespace RoF case 1: { // GuildBankItemUpdate auto emu = (GuildBankItemUpdate_Struct *)in->pBuffer; auto eq = (structs::GuildBankItemUpdate_Struct *)outapp->pBuffer; - eq->Action = 0; - OUT(Unknown004); - eq->Unknown08 = 0; - OUT(SlotID); - OUT(Area); - OUT(Unknown012); - OUT(ItemID); - OUT(Icon); - OUT(Quantity); - OUT(Permissions); - OUT(AllowMerge); - OUT(Useable); - OUT_str(ItemName); - OUT_str(Donator); - OUT_str(WhoFor); - OUT(Unknown226); + eq->action = 0; + OUT(unknown004); + eq->unknown008 = 0; + OUT(slot_id); + OUT(area); + OUT(display); + OUT(item_id); + OUT(icon_id); + OUT(quantity); + OUT(permissions); + OUT(allow_merge); + OUT(is_useable); + OUT_str(item_name); + OUT_str(donator); + OUT_str(who_for); + OUT(unknown226); break; } default: @@ -5188,7 +5188,14 @@ namespace RoF //sprintf(hdr.unknown000, "06e0002Y1W00"); - snprintf(hdr.unknown000, sizeof(hdr.unknown000), "%016d", item->ID); + strn0cpy( + hdr.unknown000, + fmt::format( + "{:016}\0", + packet_type == ItemPacketInvalid ? 0 : inst->GetSerialNumber() + ).c_str(), + sizeof(hdr.unknown000) + ); hdr.stacksize = (inst->IsStackable() ? ((inst->GetCharges() > 1000) ? 0xFFFFFFFF : inst->GetCharges()) : 1); hdr.unknown004 = 0; diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index 9024f93b9..b250a5de6 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -81,7 +81,7 @@ namespace RoF2 //create our opcode manager if we havent already if (opcodes == nullptr) { - std::string opfile = fmt::format("{}/patch_{}.conf", path.GetPatchPath(), name); + std::string opfile = fmt::format("{}/patch_{}.conf", PathManager::Instance()->GetPatchPath(), name); //load up the opcode manager. //TODO: figure out how to support shared memory with multiple patches... @@ -123,7 +123,7 @@ namespace RoF2 //we need to go to every stream and replace it's manager. if (opcodes != nullptr) { - std::string opfile = fmt::format("{}/patch_{}.conf", path.GetPatchPath(), name); + std::string opfile = fmt::format("{}/patch_{}.conf", PathManager::Instance()->GetPatchPath(), name); if (!opcodes->ReloadOpcodes(opfile.c_str())) { LogNetcode("[OPCODES] Error reloading opcodes file [{}] for patch [{}]", opfile.c_str(), name); return; @@ -433,7 +433,9 @@ namespace RoF2 VARSTRUCT_ENCODE_TYPE(uint32, eq, i.item_icon); VARSTRUCT_SKIP_TYPE(uint32, eq); } + dest->QueuePacket(outapp.get()); + safe_delete(in); break; } default: { @@ -468,8 +470,8 @@ namespace RoF2 } auto p_size = 41 * results.size() + name_size + 14; - auto buffer = std::make_unique(p_size); - auto bufptr = buffer.get(); + auto buffer = new char[p_size]; + auto bufptr = buffer; VARSTRUCT_ENCODE_TYPE(uint32, bufptr, 0); VARSTRUCT_ENCODE_TYPE(uint16, bufptr, results[0].trader_zone_id); @@ -487,10 +489,11 @@ namespace RoF2 VARSTRUCT_ENCODE_TYPE(uint32, bufptr, i.item_stat); //itemstat } - safe_delete(in->pBuffer); + safe_delete_array(in->pBuffer); in->size = p_size; - in->pBuffer = (uchar *) buffer.get(); + in->pBuffer = (uchar*)buffer; dest->QueuePacket(in); + safe_delete(in); break; } @@ -500,21 +503,22 @@ namespace RoF2 break; } case WelcomeMessage: { - auto buffer = std::make_unique(sizeof(structs::BazaarWelcome_Struct)); + auto buffer = new char[sizeof(structs::BazaarWelcome_Struct)]; auto emu = (BazaarWelcome_Struct *) in->pBuffer; - auto eq = (structs::BazaarWelcome_Struct *) buffer.get(); + auto eq = (structs::BazaarWelcome_Struct *) buffer; eq->action = structs::RoF2BazaarTraderBuyerActions::WelcomeMessage; eq->num_of_traders = emu->traders; eq->num_of_items = emu->items; - safe_delete(in->pBuffer); + safe_delete_array(in->pBuffer); in->SetOpcode(OP_TraderShop); in->size = sizeof(structs::BazaarWelcome_Struct); - in->pBuffer = (uchar *) buffer.get(); + in->pBuffer = (uchar *)buffer; LogTrading("(RoF2) WelcomeMessage action [{}]", action); dest->QueuePacket(in); + safe_delete(in); break; } @@ -583,19 +587,21 @@ namespace RoF2 auto outapp = new EQApplicationPacket(OP_TraderShop, sizeof(BecomeTrader_Struct)); auto eq = (BecomeTrader_Struct *) outapp->pBuffer; - eq->action = emu->action; - eq->entity_id = emu->entity_id; - eq->trader_id = emu->trader_id; - eq->zone_id = emu->zone_id; + eq->action = emu->action; + eq->entity_id = emu->entity_id; + eq->trader_id = emu->trader_id; + eq->zone_id = emu->zone_id; + eq->zone_instance_id = emu->zone_instance_id; strn0cpy(eq->trader_name, emu->trader_name, sizeof(eq->trader_name)); LogTrading( - "(RoF2) AddTraderToBazaarWindow action [{}] trader_id [{}] entity_id [{}] zone_id [{}]", + "(RoF2) AddTraderToBazaarWindow action [{}] trader_id [{}] entity_id [{}] " + "zone_id [{}] zone_instance_id [{}]", eq->action, eq->trader_id, eq->entity_id, - eq->zone_id - ); + eq->zone_id, + eq->zone_instance_id); dest->FastQueuePacket(&outapp); break; } @@ -890,7 +896,9 @@ namespace RoF2 VARSTRUCT_ENCODE_TYPE(uint16, eq, b.buyer_zone_instance_id); VARSTRUCT_ENCODE_STRING(eq, b.buyer_name.c_str()); } + dest->QueuePacket(outapp.get()); + safe_delete(inapp); break; } case Barter_RemoveFromMerchantWindow: { @@ -961,6 +969,7 @@ namespace RoF2 VARSTRUCT_ENCODE_TYPE(uint32, eq, blsi.seller_quantity); dest->QueuePacket(outapp.get()); + safe_delete(inapp); break; } default: { @@ -1341,6 +1350,58 @@ namespace RoF2 dest->FastQueuePacket(&in, ack_req); } + ENCODE(OP_EvolveItem) + { + EQApplicationPacket *in = *p; + *p = nullptr; + + auto action = *reinterpret_cast(in->pBuffer); + + switch (action) { + case EvolvingItems::Actions::TRANSFER_WINDOW_DETAILS: { + auto emu = reinterpret_cast(in->pBuffer); + + EvolveXPWindowSend e{}; + EQ::Util::MemoryStreamReader ss(emu->serialized_data, in->size - sizeof(emu->action)); + cereal::BinaryInputArchive ar(ss); + ar(e); + + auto item_1 = static_cast(reinterpret_cast(e.serialize_item_1.data())->inst); + auto item_2 = static_cast(reinterpret_cast(e.serialize_item_2.data())->inst); + + EQ::OutBuffer ob; + + SerializeItem(ob, item_1, 0, 0, ItemPacketMerchant); + SerializeItem(ob, item_2, 0, 0, ItemPacketMerchant); + + auto out = std::make_unique( + OP_EvolveItem, + sizeof(EvolveXPWindowSendDetails_Struct) + ob.size() + ); + auto data = reinterpret_cast(out->pBuffer); + + data->action = e.action; + data->compatibility = e.compatibility; + data->max_transfer_level = e.max_transfer_level; + data->item1_unique_id = e.item1_unique_id; + data->item2_unique_id = e.item2_unique_id; + data->item1_present = e.item1_present; + data->item2_present = e.item2_present; + + memcpy(data->serialize_data, ob.str().data(), ob.size()); + dest->QueuePacket(out.get()); + safe_delete(in); + break; + } + default: { + dest->FastQueuePacket(&in); + break; + } + } + } + ENCODE(OP_ExpansionInfo) { ENCODE_LENGTH_EXACT(ExpansionInfo_Struct); @@ -1682,22 +1743,19 @@ namespace RoF2 case 1: { // GuildBankItemUpdate auto emu = (GuildBankItemUpdate_Struct *)in->pBuffer; auto eq = (structs::GuildBankItemUpdate_Struct *)outapp->pBuffer; - eq->Action = 0; - OUT(Unknown004); - eq->Unknown08 = 0; - OUT(SlotID); - OUT(Area); - OUT(Unknown012); - OUT(ItemID); - OUT(Icon); - OUT(Quantity); - OUT(Permissions); - OUT(AllowMerge); - OUT(Useable); - OUT_str(ItemName); - OUT_str(Donator); - OUT_str(WhoFor); - OUT(Unknown226); + eq->action = 0; + OUT(display); + OUT(slot_id); + OUT(area); + OUT(item_id); + OUT(icon_id); + OUT(quantity); + OUT(permissions); + OUT(allow_merge); + OUT(is_useable); + OUT_str(item_name); + OUT_str(donator); + OUT_str(who_for); break; } default: @@ -1843,11 +1901,11 @@ namespace RoF2 } } - auto outapp = new EQApplicationPacket(OP_GuildsList); - outapp->size = packet_size; - outapp->pBuffer = buffer; + safe_delete_array(in->pBuffer); - dest->FastQueuePacket(&outapp); + in->pBuffer = buffer; + in->size = packet_size; + dest->FastQueuePacket(&in); } ENCODE(OP_GuildTributeDonateItem) @@ -2044,6 +2102,33 @@ namespace RoF2 } } + ENCODE(OP_ItemPreviewRequest) + { + EQApplicationPacket* in = *p; + *p = nullptr; + + uchar* in_buf = in->pBuffer; + + auto int_item = (EQ::InternalSerializedItem_Struct*) in_buf; + + EQ::OutBuffer buf; + EQ::OutBuffer::pos_type last_pos = buf.tellp(); + + SerializeItem(buf, (const EQ::ItemInstance*) int_item->inst, int_item->slot_id, 0, ItemPacketInvalid); + if (buf.tellp() == last_pos) { + LogNetcode("RoF2::ENCODE(OP_ItemPreviewRequest) Serialization failed"); + safe_delete_array(in_buf); + safe_delete(in); + return; + } + + in->size = buf.size(); + in->pBuffer = buf.detach(); + + safe_delete_array(in_buf); + dest->FastQueuePacket(&in, ack_req); + } + ENCODE(OP_ItemVerifyReply) { ENCODE_LENGTH_EXACT(ItemVerifyReply_Struct); @@ -4603,7 +4688,7 @@ namespace RoF2 Bitfields->linkdead = 0; Bitfields->showhelm = emu->showhelm; Bitfields->trader = emu->trader ? 1 : 0; - Bitfields->targetable = 1; + Bitfields->targetable = emu->NPC ? emu->untargetable : 1; Bitfields->targetable_with_hotkey = emu->targetable_with_hotkey ? 1 : 0; Bitfields->showname = ShowName; @@ -4754,13 +4839,13 @@ namespace RoF2 VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->petOwnerId); - VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // FindBits MQ2 name + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // FindBits MQ2 name VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->PlayerState); - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // NpcTintIndex - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // PrimaryTintIndex - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // SecondaryTintIndex - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0xffffffff); // These do something with OP_WeaponEquip1 - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0xffffffff); // ^ + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->npc_tint_id); // NpcTintIndex + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // PrimaryTintIndex + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // SecondaryTintIndex + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0xffffffff); // These do something with OP_WeaponEquip1 + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0xffffffff); // ^ if ((emu->NPC == 0) || (emu->race <= Race::Gnome) || (emu->race == Race::Iksar) || (emu->race == Race::VahShir) || (emu->race == Race::Froglok2) || (emu->race == Race::Drakkin) @@ -6218,6 +6303,11 @@ namespace RoF2 FINISH_DIRECT_DECODE(); break; } + case structs::RoF2BazaarTraderBuyerActions::FirstOpenSearch: { + __packet->SetOpcode(OP_BazaarSearch); + LogTrading("(RoF2) First time opening Bazaar Search since zoning. Action [{}]", action); + break; + } case structs::RoF2BazaarTraderBuyerActions::WelcomeMessage: { __packet->SetOpcode(OP_BazaarSearch); LogTrading("(RoF2) WelcomeMessage action [{}]", action); @@ -6358,9 +6448,18 @@ namespace RoF2 //sprintf(hdr.unknown000, "06e0002Y1W00"); strn0cpy(hdr.unknown000, fmt::format("{:016}\0", inst->GetSerialNumber()).c_str(),sizeof(hdr.unknown000)); - hdr.stacksize = - item->ID == PARCEL_MONEY_ITEM_ID ? inst->GetPrice() : (inst->IsStackable() ? ((inst->GetCharges() > 1000) - ? 0xFFFFFFFF : inst->GetCharges()) : 1); + hdr.stacksize = 1; + + if (item->ID == PARCEL_MONEY_ITEM_ID) { + hdr.stacksize = inst->GetPrice(); + } else if (inst->IsStackable()) { + if (inst->GetCharges() > std::numeric_limits::max()) { + hdr.stacksize = std::numeric_limits::max(); + } else { + hdr.stacksize = inst->GetCharges(); + } + } + hdr.unknown004 = 0; structs::InventorySlot_Struct slot_id{}; @@ -6382,6 +6481,11 @@ namespace RoF2 hdr.scaled_value = (inst->IsScaling() ? (inst->GetExp() / 100) : 0); hdr.instance_id = (inst->GetMerchantSlot() ? inst->GetMerchantSlot() : inst->GetSerialNumber()); hdr.parcel_item_id = packet_type == ItemPacketParcel ? inst->GetID() : 0; + if (item->EvolvingItem && packet_type != ItemPacketParcel && packet_type != ItemPacketMerchant) { + hdr.instance_id = inst->GetEvolveUniqueID() & 0xFFFFFFFF; //lower dword + hdr.parcel_item_id = inst->GetEvolveUniqueID() >> 32; //upper dword + } + hdr.last_cast_time = inst->GetRecastTimestamp(); hdr.charges = (inst->IsStackable() ? (item->MaxCharges ? 1 : 0) : ((inst->GetCharges() > 254) ? 0xFFFFFFFF @@ -6395,18 +6499,16 @@ namespace RoF2 ob.write((const char*)&hdr, sizeof(RoF2::structs::ItemSerializationHeader)); if (item->EvolvingItem > 0) { - RoF2::structs::EvolvingItem evotop; + RoF2::structs::EvolvingItem_Struct evotop; + inst->CalculateEvolveProgression(); - evotop.unknown001 = 0; - evotop.unknown002 = 0; - evotop.unknown003 = 0; - evotop.unknown004 = 0; - evotop.evoLevel = item->EvolvingLevel; - evotop.progress = 0; - evotop.Activated = 1; - evotop.evomaxlevel = item->EvolvingMax; + evotop.final_item_id = inst->GetEvolveFinalItemID(); + evotop.evolve_level = item->EvolvingLevel; + evotop.progress = inst->GetEvolveProgression(); + evotop.activated = inst->GetEvolveActivated(); + evotop.evolve_max_level = item->EvolvingMax; - ob.write((const char*)&evotop, sizeof(RoF2::structs::EvolvingItem)); + ob.write((const char*)&evotop, sizeof(RoF2::structs::EvolvingItem_Struct)); } /** @@ -6780,12 +6882,13 @@ namespace RoF2 iqbs.Heirloom = 0; iqbs.Placeable = 0; iqbs.unknown28 = -1; + iqbs.unknown29 = packet_type == ItemPacketInvalid ? 0xFF : 0; iqbs.unknown30 = -1; iqbs.NoZone = 0; iqbs.NoGround = 0; iqbs.unknown37a = 0; // (guessed position) New to RoF2 iqbs.unknown38 = 0; - iqbs.unknown39 = 1; + iqbs.unknown39 = packet_type == ItemPacketInvalid ? 0 : 1;; ob.write((const char*)&iqbs, sizeof(RoF2::structs::ItemQuaternaryBodyStruct)); diff --git a/common/patches/rof2_limits.h b/common/patches/rof2_limits.h index 7e7774de6..668b0df74 100644 --- a/common/patches/rof2_limits.h +++ b/common/patches/rof2_limits.h @@ -1,5 +1,5 @@ /* EQEMu: Everquest Server Emulator - + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.net) This program is free software; you can redistribute it and/or modify @@ -37,7 +37,7 @@ namespace RoF2 const bool AllowOverLevelEquipment = true; - const bool AllowEmptyBagInBag = true; + const bool AllowEmptyBagInBag = true; const bool AllowClickCastFromBag = true; } /*inventory*/ @@ -77,38 +77,40 @@ namespace RoF2 } // namespace enum_ using namespace enum_; - const int16 POSSESSIONS_SIZE = 34; - const int16 BANK_SIZE = 24; - const int16 SHARED_BANK_SIZE = 2; - const int16 TRADE_SIZE = 8; - const int16 WORLD_SIZE = 10; - const int16 LIMBO_SIZE = 36; - const int16 TRIBUTE_SIZE = 5; - const int16 TROPHY_TRIBUTE_SIZE = 0;//unknown - const int16 GUILD_TRIBUTE_SIZE = 2;//unverified - const int16 MERCHANT_SIZE = 200; - const int16 DELETED_SIZE = 0;//unknown - "Recovery Tab" - const int16 CORPSE_SIZE = POSSESSIONS_SIZE; - const int16 BAZAAR_SIZE = 200; - const int16 INSPECT_SIZE = 23; - const int16 REAL_ESTATE_SIZE = 0;//unknown - const int16 VIEW_MOD_PC_SIZE = POSSESSIONS_SIZE; - const int16 VIEW_MOD_BANK_SIZE = BANK_SIZE; + const int16 POSSESSIONS_SIZE = 34; + const int16 BANK_SIZE = 24; + const int16 SHARED_BANK_SIZE = 2; + const int16 TRADE_SIZE = 8; + const int16 WORLD_SIZE = 10; + const int16 LIMBO_SIZE = 36; + const int16 TRIBUTE_SIZE = 5; + const int16 TROPHY_TRIBUTE_SIZE = 0;//unknown + const int16 GUILD_TRIBUTE_SIZE = 2;//unverified + const int16 MERCHANT_SIZE = 500; + const int16 DELETED_SIZE = 0;//unknown - "Recovery Tab" + const int16 CORPSE_SIZE = POSSESSIONS_SIZE; + const int16 BAZAAR_SIZE = 200; + const int16 INSPECT_SIZE = 23; + const int16 REAL_ESTATE_SIZE = 0;//unknown + const int16 VIEW_MOD_PC_SIZE = POSSESSIONS_SIZE; + const int16 VIEW_MOD_BANK_SIZE = BANK_SIZE; const int16 VIEW_MOD_SHARED_BANK_SIZE = SHARED_BANK_SIZE; - const int16 VIEW_MOD_LIMBO_SIZE = LIMBO_SIZE; - const int16 ALT_STORAGE_SIZE = 0;//unknown - "Shroud Bank" - const int16 ARCHIVED_SIZE = 0;//unknown - const int16 MAIL_SIZE = 0;//unknown + const int16 VIEW_MOD_LIMBO_SIZE = LIMBO_SIZE; + const int16 ALT_STORAGE_SIZE = 0;//unknown - "Shroud Bank" + const int16 ARCHIVED_SIZE = 0;//unknown + const int16 MAIL_SIZE = 0;//unknown const int16 GUILD_TROPHY_TRIBUTE_SIZE = 0;//unknown - const int16 KRONO_SIZE = 0;//unknown - const int16 OTHER_SIZE = 0;//unknown + const int16 KRONO_SIZE = 0;//unknown + const int16 GUILD_BANK_MAIN_SIZE = 200; + const int16 GUILD_BANK_DEPOSIT_SIZE = 40; + const int16 OTHER_SIZE = 0;//unknown const int16 TRADE_NPC_SIZE = 4; // defined by implication const int16 TYPE_INVALID = IINVALID; - const int16 TYPE_BEGIN = typePossessions; - const int16 TYPE_END = typeOther; - const int16 TYPE_COUNT = (TYPE_END - TYPE_BEGIN) + 1; + const int16 TYPE_BEGIN = typePossessions; + const int16 TYPE_END = typeOther; + const int16 TYPE_COUNT = (TYPE_END - TYPE_BEGIN) + 1; int16 GetInvTypeSize(int16 inv_type); const char* GetInvTypeName(int16 inv_type); @@ -162,33 +164,54 @@ namespace RoF2 } // namespace enum_ using namespace enum_; - const int16 SLOT_INVALID = IINVALID; - const int16 SLOT_BEGIN = INULL; + const int16 SLOT_TRADESKILL_EXPERIMENT_COMBINE = 1000; + const int16 SLOT_INVALID = IINVALID; + const int16 SLOT_BEGIN = INULL; + + const int16 BANK_BEGIN = 2000; + const int16 BANK_END = (BANK_BEGIN + invtype::BANK_SIZE) - 1; + + const int16 SHARED_BANK_BEGIN = 2500; + const int16 SHARED_BANK_END = (SHARED_BANK_BEGIN + invtype::SHARED_BANK_SIZE) - 1; + + const int16 TRADE_BEGIN = 3000; + const int16 TRADE_END = (TRADE_BEGIN + invtype::TRADE_SIZE) - 1; + + const int16 TRADE_NPC_END = (TRADE_BEGIN + invtype::TRADE_NPC_SIZE) - 1; // defined by implication + + const int16 WORLD_BEGIN = 4000; + const int16 WORLD_END = (WORLD_BEGIN + invtype::WORLD_SIZE) - 1; + + const int16 TRIBUTE_BEGIN = 400; + const int16 TRIBUTE_END = (TRIBUTE_BEGIN + invtype::TRIBUTE_SIZE) - 1; + + const int16 GUILD_TRIBUTE_BEGIN = 450; + const int16 GUILD_TRIBUTE_END = (GUILD_TRIBUTE_BEGIN + invtype::GUILD_TRIBUTE_SIZE) - 1; const int16 POSSESSIONS_BEGIN = slotCharm; - const int16 POSSESSIONS_END = slotCursor; + const int16 POSSESSIONS_END = slotCursor; const int16 POSSESSIONS_COUNT = (POSSESSIONS_END - POSSESSIONS_BEGIN) + 1; const int16 EQUIPMENT_BEGIN = slotCharm; - const int16 EQUIPMENT_END = slotAmmo; + const int16 EQUIPMENT_END = slotAmmo; const int16 EQUIPMENT_COUNT = (EQUIPMENT_END - EQUIPMENT_BEGIN) + 1; const int16 GENERAL_BEGIN = slotGeneral1; - const int16 GENERAL_END = slotGeneral10; + const int16 GENERAL_END = slotGeneral10; const int16 GENERAL_COUNT = (GENERAL_END - GENERAL_BEGIN) + 1; - const int16 BONUS_BEGIN = invslot::slotCharm; - const int16 BONUS_STAT_END = invslot::slotPowerSource; + const int16 BONUS_BEGIN = invslot::slotCharm; + const int16 BONUS_STAT_END = invslot::slotPowerSource; const int16 BONUS_SKILL_END = invslot::slotAmmo; const int16 CORPSE_BEGIN = invslot::slotGeneral1; - const int16 CORPSE_END = invslot::slotGeneral1 + invslot::slotCursor; + const int16 CORPSE_END = invslot::slotGeneral1 + invslot::slotCursor; - const uint64 EQUIPMENT_BITMASK = 0x00000000007FFFFF; - const uint64 GENERAL_BITMASK = 0x00000001FF800000; - const uint64 CURSOR_BITMASK = 0x0000000200000000; + const uint64 EQUIPMENT_BITMASK = 0x00000000007FFFFF; + const uint64 GENERAL_BITMASK = 0x00000001FF800000; + const uint64 CURSOR_BITMASK = 0x0000000200000000; const uint64 POSSESSIONS_BITMASK = (EQUIPMENT_BITMASK | GENERAL_BITMASK | CURSOR_BITMASK); // based on 34-slot count (RoF+) - const uint64 CORPSE_BITMASK = (GENERAL_BITMASK | CURSOR_BITMASK | (EQUIPMENT_BITMASK << 34)); // based on 34-slot count (RoF+) + const uint64 CORPSE_BITMASK = (GENERAL_BITMASK | CURSOR_BITMASK | (EQUIPMENT_BITMASK << 34)); // based on 34-slot count (RoF+) const char* GetInvPossessionsSlotName(int16 inv_slot); @@ -199,10 +222,21 @@ namespace RoF2 namespace invbag { inline EQ::versions::ClientVersion GetInvBagRef() { return EQ::versions::ClientVersion::RoF2; } - const int16 SLOT_INVALID = IINVALID; - const int16 SLOT_BEGIN = INULL; - const int16 SLOT_END = 9; //254; - const int16 SLOT_COUNT = 10; //255; // server Size will be 255..unsure what actual client is (test) + const int16 SLOT_TRADESKILL_EXPERIMENT_COMBINE = 1000; + const int16 SLOT_INVALID = IINVALID; + const int16 SLOT_BEGIN = INULL; + const int16 SLOT_COUNT = 200; + const int16 SLOT_END = SLOT_COUNT - 1; + + const int16 GENERAL_BAGS_BEGIN = 251; + + const int16 CURSOR_BAG_BEGIN = 351; + + const int16 BANK_BAGS_BEGIN = 2031; + + const int16 SHARED_BANK_BAGS_BEGIN = 2531; + + const int16 TRADE_BAGS_BEGIN = 3031; const char* GetInvBagIndexName(int16 bag_index); @@ -212,9 +246,9 @@ namespace RoF2 inline EQ::versions::ClientVersion GetInvAugRef() { return EQ::versions::ClientVersion::RoF2; } const int16 SOCKET_INVALID = IINVALID; - const int16 SOCKET_BEGIN = INULL; - const int16 SOCKET_END = 5; - const int16 SOCKET_COUNT = 6; + const int16 SOCKET_BEGIN = INULL; + const int16 SOCKET_END = 5; + const int16 SOCKET_COUNT = 6; const char* GetInvAugIndexName(int16 aug_index); @@ -272,6 +306,7 @@ namespace RoF2 const size_t SAY_LINK_BODY_SIZE = 56; const uint32 MAX_GUILD_ID = 50000; + const uint32 MAX_BAZAAR_TRADERS = 600; } /*constants*/ @@ -291,7 +326,7 @@ namespace RoF2 namespace spells { inline EQ::versions::ClientVersion GetSkillsRef() { return EQ::versions::ClientVersion::RoF2; } - + enum class CastingSlot : uint32 { Gem1 = 0, Gem2 = 1, @@ -314,7 +349,7 @@ namespace RoF2 const int SPELL_ID_MAX = 45000; const int SPELLBOOK_SIZE = 720; const int SPELL_GEM_COUNT = static_cast(CastingSlot::MaxGems); - + const int LONG_BUFFS = 42; const int SHORT_BUFFS = 20; const int DISC_BUFFS = 1; diff --git a/common/patches/rof2_ops.h b/common/patches/rof2_ops.h index 08d8b9985..754635e41 100644 --- a/common/patches/rof2_ops.h +++ b/common/patches/rof2_ops.h @@ -1,5 +1,5 @@ /* EQEMu: Everquest Server Emulator - + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.net) This program is free software; you can redistribute it and/or modify @@ -70,6 +70,7 @@ E(OP_DzMemberListName) E(OP_DzMemberListStatus) E(OP_DzSetLeaderName) E(OP_Emote) +E(OP_EvolveItem) E(OP_ExpansionInfo) E(OP_FormattedMessage) E(OP_GMLastName) @@ -91,6 +92,7 @@ E(OP_InspectBuffs) E(OP_InspectRequest) E(OP_ItemLinkResponse) E(OP_ItemPacket) +E(OP_ItemPreviewRequest) E(OP_ItemVerifyReply) E(OP_LeadershipExpUpdate) E(OP_LogServer) diff --git a/common/patches/rof2_structs.h b/common/patches/rof2_structs.h index fa43b65b3..b6ad6bbf1 100644 --- a/common/patches/rof2_structs.h +++ b/common/patches/rof2_structs.h @@ -1965,41 +1965,39 @@ struct GuildBankWithdrawItem_Struct struct GuildBankItemUpdate_Struct { - void Init(uint32 inAction, uint32 inUnknown004, uint16 inSlotID, uint16 inArea, uint16 inUnknown012, uint32 inItemID, uint32 inIcon, uint32 inQuantity, + void Init(uint32 inAction, uint32 inUnknown004, uint16 inSlotID, uint16 inArea, uint16 inUnknown016, uint32 inItemID, uint32 inIcon, uint32 inQuantity, uint32 inPermissions, uint32 inAllowMerge, bool inUseable) { - Action = inAction; - Unknown004 = inUnknown004; - SlotID = inSlotID; - Area = inArea; - Unknown012 = inUnknown012; - ItemID = inItemID; - Icon = inIcon; - Quantity = inQuantity; - Permissions = inPermissions; - AllowMerge = inAllowMerge; - Useable = inUseable; - ItemName[0] = '\0'; - Donator[0] = '\0'; - WhoFor[0] = '\0'; + action = inAction; + slot_id = inSlotID; + area = inArea; + display = inUnknown016; + item_id = inItemID; + icon_id = inIcon; + quantity = inQuantity; + permissions = inPermissions; + allow_merge = inAllowMerge; + is_useable = inUseable; + item_name[0] = '\0'; + donator[0] = '\0'; + who_for[0] = '\0'; }; -/*000*/ uint32 Action; -/*004*/ uint32 Unknown004; -/*008*/ uint32 Unknown08; -/*012*/ uint16 SlotID; -/*014*/ uint16 Area; -/*016*/ uint32 Unknown012; -/*020*/ uint32 ItemID; -/*024*/ uint32 Icon; -/*028*/ uint32 Quantity; -/*032*/ uint32 Permissions; -/*036*/ uint8 AllowMerge; -/*037*/ uint8 Useable; // Used in conjunction with the Public-if-useable permission. -/*038*/ char ItemName[64]; -/*102*/ char Donator[64]; -/*166*/ char WhoFor[64]; -/*230*/ uint16 Unknown226; +/*000*/ uint32 action; +/*004*/ uint32 not_used004; //disassemble of client did not use this +/*008*/ uint32 not_used008; //disassemble of client did not use this +/*012*/ uint16 slot_id; +/*014*/ uint16 area; +/*016*/ uint32 display; +/*020*/ uint32 item_id; +/*024*/ uint32 icon_id; +/*028*/ uint32 quantity; +/*032*/ uint32 permissions; +/*036*/ uint8 allow_merge; +/*037*/ uint8 is_useable; // Used in conjunction with the Public-if-useable permission. +/*038*/ char item_name[64]; +/*102*/ char donator[64]; +/*166*/ char who_for[64]; }; struct GuildBankClear_Struct @@ -3119,7 +3117,8 @@ enum RoF2BazaarTraderBuyerActions { BazaarInspect = 18, ClickTrader = 28, ItemMove = 19, - ReconcileItems = 20 + ReconcileItems = 20, + FirstOpenSearch = 26 }; enum RoF2BuyerActions { @@ -3934,6 +3933,11 @@ struct NewCombine_Struct /*24*/ }; +struct TradeSkillRecipeInspect_Struct { + uint32 recipe_id; + uint32 padding[17]; +}; + //client requesting favorite recipies struct TradeskillFavorites_Struct { @@ -4735,7 +4739,7 @@ struct ItemSerializationHeader /*036*/ uint32 merchant_slot; // 1 if not a merchant item /*040*/ uint32 scaled_value; // 0 /*044*/ uint32 instance_id; // unique instance id if not merchant item, else is merchant slot - /*048*/ uint32 parcel_item_id; + /*048*/ uint32 parcel_item_id; /*052*/ uint32 last_cast_time; // Unix Time from PP of last cast for this recast type if recast delay > 0 /*056*/ uint32 charges; // Total Charges an item has (-1 for unlimited) /*060*/ uint32 inst_nodrop; // 1 if the item is no drop (attuned items) @@ -4745,16 +4749,13 @@ struct ItemSerializationHeader uint8 isEvolving; }; -struct EvolvingItem { - uint8 unknown001; - uint8 unknown002; - uint8 unknown003; - uint8 unknown004; - int32 evoLevel; +struct EvolvingItem_Struct { + uint32 final_item_id; + int32 evolve_level; double progress; - uint8 Activated; - int32 evomaxlevel; - uint8 unknown005[4]; + uint8 activated; + int32 evolve_max_level; + uint8 unknown005[4]; }; struct ItemSerializationHeaderFinish @@ -5427,6 +5428,32 @@ struct Parcel_Struct }; }; /*structs*/ +struct EvolveItemToggle_Struct { + uint32 action; + uint32 unknown_004; + uint64 unique_id; + uint32 percentage; + uint32 activated; +}; + +struct EvolveXPWindowReceive_Struct { + uint32 action; + uint32 unknown_004; + uint64 item1_unique_id; + uint64 item2_unique_id; +}; + +struct EvolveXPWindowSendDetails_Struct { + /*000*/ uint32 action; + /*004*/ uint64 item1_unique_id; + /*012*/ uint64 item2_unique_id; + /*020*/ uint32 compatibility; + /*024*/ uint32 max_transfer_level; + /*028*/ uint8 item1_present; + /*029*/ uint8 item2_present; + /*030*/ char serialize_data[]; +}; + }; /*RoF2*/ #endif /*COMMON_ROF2_STRUCTS_H*/ diff --git a/common/patches/rof_structs.h b/common/patches/rof_structs.h index d502eddd6..d065a2b04 100644 --- a/common/patches/rof_structs.h +++ b/common/patches/rof_structs.h @@ -1946,38 +1946,38 @@ struct GuildBankItemUpdate_Struct void Init(uint32 inAction, uint32 inUnknown004, uint16 inSlotID, uint16 inArea, uint16 inUnknown012, uint32 inItemID, uint32 inIcon, uint32 inQuantity, uint32 inPermissions, uint32 inAllowMerge, bool inUseable) { - Action = inAction; - Unknown004 = inUnknown004; - SlotID = inSlotID; - Area = inArea; - Unknown012 = inUnknown012; - ItemID = inItemID; - Icon = inIcon; - Quantity = inQuantity; - Permissions = inPermissions; - AllowMerge = inAllowMerge; - Useable = inUseable; - ItemName[0] = '\0'; - Donator[0] = '\0'; - WhoFor[0] = '\0'; + action = inAction; + unknown004 = inUnknown004; + slot_id = inSlotID; + area = inArea; + display = inUnknown012; + item_id = inItemID; + icon_id = inIcon; + quantity = inQuantity; + permissions = inPermissions; + allow_merge = inAllowMerge; + is_useable = inUseable; + item_name[0] = '\0'; + donator[0] = '\0'; + who_for[0] = '\0'; }; -/*000*/ uint32 Action; -/*004*/ uint32 Unknown004; -/*008*/ uint32 Unknown08; -/*012*/ uint16 SlotID; -/*014*/ uint16 Area; -/*016*/ uint32 Unknown012; -/*020*/ uint32 ItemID; -/*024*/ uint32 Icon; -/*028*/ uint32 Quantity; -/*032*/ uint32 Permissions; -/*036*/ uint8 AllowMerge; -/*037*/ uint8 Useable; // Used in conjunction with the Public-if-useable permission. -/*038*/ char ItemName[64]; -/*102*/ char Donator[64]; -/*166*/ char WhoFor[64]; -/*230*/ uint16 Unknown226; +/*000*/ uint32 action; +/*004*/ uint32 unknown004; +/*008*/ uint32 unknown008; +/*012*/ uint16 slot_id; +/*014*/ uint16 area; +/*016*/ uint32 display; +/*020*/ uint32 item_id; +/*024*/ uint32 icon_id; +/*028*/ uint32 quantity; +/*032*/ uint32 permissions; +/*036*/ uint8 allow_merge; +/*037*/ uint8 is_useable; // Used in conjunction with the Public-if-useable permission. +/*038*/ char item_name[64]; +/*102*/ char donator[64]; +/*166*/ char who_for[64]; +/*230*/ uint16 unknown226; }; struct GuildBankClear_Struct diff --git a/common/patches/sod.cpp b/common/patches/sod.cpp index d8f174196..72a3a3c48 100644 --- a/common/patches/sod.cpp +++ b/common/patches/sod.cpp @@ -72,7 +72,7 @@ namespace SoD { //create our opcode manager if we havent already if (opcodes == nullptr) { - std::string opfile = fmt::format("{}/patch_{}.conf", path.GetPatchPath(), name); + std::string opfile = fmt::format("{}/patch_{}.conf", PathManager::Instance()->GetPatchPath(), name); //load up the opcode manager. //TODO: figure out how to support shared memory with multiple patches... opcodes = new RegularOpcodeManager(); @@ -113,7 +113,7 @@ namespace SoD //we need to go to every stream and replace it's manager. if (opcodes != nullptr) { - std::string opfile = fmt::format("{}/patch_{}.conf", path.GetPatchPath(), name); + std::string opfile = fmt::format("{}/patch_{}.conf", PathManager::Instance()->GetPatchPath(), name); if (!opcodes->ReloadOpcodes(opfile.c_str())) { LogNetcode("[OPCODES] Error reloading opcodes file [{}] for patch [{}]", opfile.c_str(), name); return; @@ -3966,12 +3966,12 @@ namespace SoD SoDSlot = serverSlot - 2; } - else if (serverSlot <= EQ::invbag::GENERAL_BAGS_8_END && serverSlot >= EQ::invbag::GENERAL_BAGS_BEGIN) { - SoDSlot = serverSlot + 11; + else if (serverSlot <= EQ::invbag::GENERAL_BAGS_END && serverSlot >= EQ::invbag::GENERAL_BAGS_BEGIN) { + SoDSlot = serverSlot - (EQ::invbag::GENERAL_BAGS_BEGIN - invbag::GENERAL_BAGS_BEGIN) - ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((serverSlot - EQ::invbag::GENERAL_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT));; } else if (serverSlot <= EQ::invbag::CURSOR_BAG_END && serverSlot >= EQ::invbag::CURSOR_BAG_BEGIN) { - SoDSlot = serverSlot - 9; + SoDSlot = serverSlot - (EQ::invbag::CURSOR_BAG_BEGIN - invbag::CURSOR_BAG_BEGIN); } else if (serverSlot <= EQ::invslot::TRIBUTE_END && serverSlot >= EQ::invslot::TRIBUTE_BEGIN) { @@ -3991,7 +3991,7 @@ namespace SoD } else if (serverSlot <= EQ::invbag::BANK_BAGS_END && serverSlot >= EQ::invbag::BANK_BAGS_BEGIN) { - SoDSlot = serverSlot + 1; + SoDSlot = serverSlot - (EQ::invbag::BANK_BAGS_BEGIN - invbag::BANK_BAGS_BEGIN) - ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((serverSlot - EQ::invbag::BANK_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT));; } else if (serverSlot <= EQ::invslot::SHARED_BANK_END && serverSlot >= EQ::invslot::SHARED_BANK_BEGIN) { @@ -3999,7 +3999,7 @@ namespace SoD } else if (serverSlot <= EQ::invbag::SHARED_BANK_BAGS_END && serverSlot >= EQ::invbag::SHARED_BANK_BAGS_BEGIN) { - SoDSlot = serverSlot + 1; + SoDSlot = serverSlot - (EQ::invbag::SHARED_BANK_BAGS_BEGIN - invbag::SHARED_BANK_BAGS_BEGIN) - ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((serverSlot - EQ::invbag::SHARED_BANK_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT)); } else if (serverSlot <= EQ::invslot::TRADE_END && serverSlot >= EQ::invslot::TRADE_BEGIN) { @@ -4007,7 +4007,7 @@ namespace SoD } else if (serverSlot <= EQ::invbag::TRADE_BAGS_END && serverSlot >= EQ::invbag::TRADE_BAGS_BEGIN) { - SoDSlot = serverSlot; + SoDSlot = serverSlot - (EQ::invbag::TRADE_BAGS_BEGIN - invbag::TRADE_BAGS_BEGIN) - ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((serverSlot - EQ::invbag::TRADE_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT)); } else if (serverSlot <= EQ::invslot::WORLD_END && serverSlot >= EQ::invslot::WORLD_BEGIN) { @@ -4049,11 +4049,11 @@ namespace SoD } else if (sod_slot <= invbag::GENERAL_BAGS_END && sod_slot >= invbag::GENERAL_BAGS_BEGIN) { - server_slot = sod_slot - 11; + server_slot = sod_slot + (EQ::invbag::GENERAL_BAGS_BEGIN - invbag::GENERAL_BAGS_BEGIN) + ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((sod_slot - invbag::GENERAL_BAGS_BEGIN) / invbag::SLOT_COUNT)); } else if (sod_slot <= invbag::CURSOR_BAG_END && sod_slot >= invbag::CURSOR_BAG_BEGIN) { - server_slot = sod_slot + 9; + server_slot = sod_slot + (EQ::invbag::CURSOR_BAG_BEGIN - invbag::CURSOR_BAG_BEGIN); } else if (sod_slot <= invslot::TRIBUTE_END && sod_slot >= invslot::TRIBUTE_BEGIN) { @@ -4073,7 +4073,7 @@ namespace SoD } else if (sod_slot <= invbag::BANK_BAGS_END && sod_slot >= invbag::BANK_BAGS_BEGIN) { - server_slot = sod_slot - 1; + server_slot = sod_slot + (EQ::invbag::BANK_BAGS_BEGIN - invbag::BANK_BAGS_BEGIN) + ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((sod_slot - invbag::BANK_BAGS_BEGIN) / invbag::SLOT_COUNT)); } else if (sod_slot <= invslot::SHARED_BANK_END && sod_slot >= invslot::SHARED_BANK_BEGIN) { @@ -4081,7 +4081,7 @@ namespace SoD } else if (sod_slot <= invbag::SHARED_BANK_BAGS_END && sod_slot >= invbag::SHARED_BANK_BAGS_BEGIN) { - server_slot = sod_slot - 1; + server_slot = sod_slot + (EQ::invbag::SHARED_BANK_BAGS_BEGIN - invbag::SHARED_BANK_BAGS_BEGIN) + ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((sod_slot - invbag::SHARED_BANK_BAGS_BEGIN) / invbag::SLOT_COUNT)); } else if (sod_slot <= invslot::TRADE_END && sod_slot >= invslot::TRADE_BEGIN) { @@ -4089,7 +4089,7 @@ namespace SoD } else if (sod_slot <= invbag::TRADE_BAGS_END && sod_slot >= invbag::TRADE_BAGS_BEGIN) { - server_slot = sod_slot; + server_slot = sod_slot + (EQ::invbag::TRADE_BAGS_BEGIN - invbag::TRADE_BAGS_BEGIN) + ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((sod_slot - invbag::TRADE_BAGS_BEGIN) / invbag::SLOT_COUNT)); } else if (sod_slot <= invslot::WORLD_END && sod_slot >= invslot::WORLD_BEGIN) { diff --git a/common/patches/sof.cpp b/common/patches/sof.cpp index be18a78c7..444b33b35 100644 --- a/common/patches/sof.cpp +++ b/common/patches/sof.cpp @@ -71,7 +71,7 @@ namespace SoF { //create our opcode manager if we havent already if (opcodes == nullptr) { - std::string opfile = fmt::format("{}/patch_{}.conf", path.GetPatchPath(), name); + std::string opfile = fmt::format("{}/patch_{}.conf", PathManager::Instance()->GetPatchPath(), name); //load up the opcode manager. //TODO: figure out how to support shared memory with multiple patches... opcodes = new RegularOpcodeManager(); @@ -110,7 +110,7 @@ namespace SoF //we need to go to every stream and replace it's manager. if (opcodes != nullptr) { - std::string opfile = fmt::format("{}/patch_{}.conf", path.GetPatchPath(), name); + std::string opfile = fmt::format("{}/patch_{}.conf", PathManager::Instance()->GetPatchPath(), name); if (!opcodes->ReloadOpcodes(opfile.c_str())) { LogNetcode("[OPCODES] Error reloading opcodes file [{}] for patch [{}]", opfile.c_str(), name); return; @@ -3355,12 +3355,12 @@ namespace SoF sof_slot = server_slot - 2; } - else if (server_slot <= EQ::invbag::GENERAL_BAGS_8_END && server_slot >= EQ::invbag::GENERAL_BAGS_BEGIN) { - sof_slot = server_slot + 11; + else if (server_slot <= EQ::invbag::GENERAL_BAGS_END && server_slot >= EQ::invbag::GENERAL_BAGS_BEGIN) { + sof_slot = server_slot - (EQ::invbag::GENERAL_BAGS_BEGIN - invbag::GENERAL_BAGS_BEGIN) - ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((server_slot - EQ::invbag::GENERAL_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT));; } else if (server_slot <= EQ::invbag::CURSOR_BAG_END && server_slot >= EQ::invbag::CURSOR_BAG_BEGIN) { - sof_slot = server_slot - 9; + sof_slot = server_slot - (EQ::invbag::CURSOR_BAG_BEGIN - invbag::CURSOR_BAG_BEGIN); } else if (server_slot <= EQ::invslot::TRIBUTE_END && server_slot >= EQ::invslot::TRIBUTE_BEGIN) { @@ -3380,7 +3380,7 @@ namespace SoF } else if (server_slot <= EQ::invbag::BANK_BAGS_END && server_slot >= EQ::invbag::BANK_BAGS_BEGIN) { - sof_slot = server_slot + 1; + sof_slot = server_slot - (EQ::invbag::BANK_BAGS_BEGIN - invbag::BANK_BAGS_BEGIN) - ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((server_slot - EQ::invbag::BANK_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT)); } else if (server_slot <= EQ::invslot::SHARED_BANK_END && server_slot >= EQ::invslot::SHARED_BANK_BEGIN) { @@ -3388,7 +3388,7 @@ namespace SoF } else if (server_slot <= EQ::invbag::SHARED_BANK_BAGS_END && server_slot >= EQ::invbag::SHARED_BANK_BAGS_BEGIN) { - sof_slot = server_slot + 1; + sof_slot = server_slot - (EQ::invbag::SHARED_BANK_BAGS_BEGIN - invbag::SHARED_BANK_BAGS_BEGIN) - ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((server_slot - EQ::invbag::SHARED_BANK_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT)); } else if (server_slot <= EQ::invslot::TRADE_END && server_slot >= EQ::invslot::TRADE_BEGIN) { @@ -3396,7 +3396,7 @@ namespace SoF } else if (server_slot <= EQ::invbag::TRADE_BAGS_END && server_slot >= EQ::invbag::TRADE_BAGS_BEGIN) { - sof_slot = server_slot; + sof_slot = server_slot - (EQ::invbag::TRADE_BAGS_BEGIN - invbag::TRADE_BAGS_BEGIN) - ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((server_slot - EQ::invbag::TRADE_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT)); } else if (server_slot <= EQ::invslot::WORLD_END && server_slot >= EQ::invslot::WORLD_BEGIN) { @@ -3442,11 +3442,11 @@ namespace SoF } else if (sof_slot <= invbag::GENERAL_BAGS_END && sof_slot >= invbag::GENERAL_BAGS_BEGIN) { - server_slot = sof_slot - 11; + server_slot = sof_slot + (EQ::invbag::GENERAL_BAGS_BEGIN - invbag::GENERAL_BAGS_BEGIN) + ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((sof_slot - invbag::GENERAL_BAGS_BEGIN) / invbag::SLOT_COUNT));; } else if (sof_slot <= invbag::CURSOR_BAG_END && sof_slot >= invbag::CURSOR_BAG_BEGIN) { - server_slot = sof_slot + 9; + server_slot = sof_slot + (EQ::invbag::CURSOR_BAG_BEGIN - invbag::CURSOR_BAG_BEGIN); } else if (sof_slot <= invslot::TRIBUTE_END && sof_slot >= invslot::TRIBUTE_BEGIN) { @@ -3466,7 +3466,7 @@ namespace SoF } else if (sof_slot <= invbag::BANK_BAGS_END && sof_slot >= invbag::BANK_BAGS_BEGIN) { - server_slot = sof_slot - 1; + server_slot = sof_slot + (EQ::invbag::BANK_BAGS_BEGIN - invbag::BANK_BAGS_BEGIN) + ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((sof_slot - invbag::BANK_BAGS_BEGIN) / invbag::SLOT_COUNT));; } else if (sof_slot <= invslot::SHARED_BANK_END && sof_slot >= invslot::SHARED_BANK_BEGIN) { @@ -3474,7 +3474,7 @@ namespace SoF } else if (sof_slot <= invbag::SHARED_BANK_BAGS_END && sof_slot >= invbag::SHARED_BANK_BAGS_BEGIN) { - server_slot = sof_slot - 1; + server_slot = sof_slot + (EQ::invbag::SHARED_BANK_BAGS_BEGIN - invbag::SHARED_BANK_BAGS_BEGIN) + ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((sof_slot - invbag::SHARED_BANK_BAGS_BEGIN) / invbag::SLOT_COUNT));; } else if (sof_slot <= invslot::TRADE_END && sof_slot >= invslot::TRADE_BEGIN) { @@ -3482,7 +3482,7 @@ namespace SoF } else if (sof_slot <= invbag::TRADE_BAGS_END && sof_slot >= invbag::TRADE_BAGS_BEGIN) { - server_slot = sof_slot; + server_slot = sof_slot + (EQ::invbag::TRADE_BAGS_BEGIN - invbag::TRADE_BAGS_BEGIN) + ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((sof_slot - invbag::TRADE_BAGS_BEGIN) / invbag::SLOT_COUNT));; } else if (sof_slot <= invslot::WORLD_END && sof_slot >= invslot::WORLD_BEGIN) { diff --git a/common/patches/titanium.cpp b/common/patches/titanium.cpp index e9cec60a6..c93b92581 100644 --- a/common/patches/titanium.cpp +++ b/common/patches/titanium.cpp @@ -73,7 +73,7 @@ namespace Titanium auto Config = EQEmuConfig::get(); //create our opcode manager if we havent already if (opcodes == nullptr) { - std::string opfile = fmt::format("{}/patch_{}.conf", path.GetPatchPath(), name); + std::string opfile = fmt::format("{}/patch_{}.conf", PathManager::Instance()->GetPatchPath(), name); //load up the opcode manager. //TODO: figure out how to support shared memory with multiple patches... opcodes = new RegularOpcodeManager(); @@ -114,7 +114,7 @@ namespace Titanium //we need to go to every stream and replace it's manager. if (opcodes != nullptr) { - std::string opfile = fmt::format("{}/patch_{}.conf", path.GetPatchPath(), name); + std::string opfile = fmt::format("{}/patch_{}.conf", PathManager::Instance()->GetPatchPath(), name); if (!opcodes->ReloadOpcodes(opfile.c_str())) { LogNetcode("[OPCODES] Error reloading opcodes file [{}] for patch [{}]", opfile.c_str(), name); return; @@ -1059,7 +1059,7 @@ namespace Titanium OUT(spawnid); OUT_str(charname); - if (emu->race > 473) + if (emu->race > 474) eq->race = 1; else OUT(race); @@ -1840,7 +1840,7 @@ namespace Titanium emu_cse = (CharacterSelectEntry_Struct *)emu_ptr; eq->Race[char_index] = emu_cse->Race; - if (eq->Race[char_index] > 473) + if (eq->Race[char_index] > 474) eq->Race[char_index] = 1; for (int index = 0; index < EQ::textures::materialCount; ++index) { @@ -2421,7 +2421,7 @@ namespace Titanium strcpy(eq->title, emu->title); // eq->unknown0274 = emu->unknown0274; eq->helm = emu->helm; - if (emu->race > 473) + if (emu->race > 474) eq->race = 1; else eq->race = emu->race; @@ -3596,12 +3596,12 @@ namespace Titanium else if (server_slot == (EQ::invslot::POSSESSIONS_COUNT + EQ::invslot::slotAmmo)) { titanium_slot = server_slot - 4; } - else if (server_slot <= EQ::invbag::GENERAL_BAGS_8_END && + else if (server_slot <= EQ::invbag::GENERAL_BAGS_END && server_slot >= EQ::invbag::GENERAL_BAGS_BEGIN) { - titanium_slot = server_slot; + titanium_slot = server_slot - (EQ::invbag::GENERAL_BAGS_BEGIN - invbag::GENERAL_BAGS_BEGIN) - ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((server_slot - EQ::invbag::GENERAL_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT)); } else if (server_slot <= EQ::invbag::CURSOR_BAG_END && server_slot >= EQ::invbag::CURSOR_BAG_BEGIN) { - titanium_slot = server_slot - 20; + titanium_slot = server_slot - (EQ::invbag::CURSOR_BAG_BEGIN - invbag::CURSOR_BAG_BEGIN); } else if (server_slot <= EQ::invslot::TRIBUTE_END && server_slot >= EQ::invslot::TRIBUTE_BEGIN) { titanium_slot = server_slot; @@ -3616,21 +3616,21 @@ namespace Titanium else if (server_slot <= EQ::invslot::BANK_END && server_slot >= EQ::invslot::BANK_BEGIN) { titanium_slot = server_slot; } - else if (server_slot <= EQ::invbag::BANK_BAGS_16_END && server_slot >= EQ::invbag::BANK_BAGS_BEGIN) { - titanium_slot = server_slot; + else if (server_slot <= EQ::invbag::BANK_BAGS_END && server_slot >= EQ::invbag::BANK_BAGS_BEGIN) { + titanium_slot = server_slot - (EQ::invbag::BANK_BAGS_BEGIN - invbag::BANK_BAGS_BEGIN) - ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((server_slot - EQ::invbag::BANK_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT)); } else if (server_slot <= EQ::invslot::SHARED_BANK_END && server_slot >= EQ::invslot::SHARED_BANK_BEGIN) { titanium_slot = server_slot; } else if (server_slot <= EQ::invbag::SHARED_BANK_BAGS_END && server_slot >= EQ::invbag::SHARED_BANK_BAGS_BEGIN) { - titanium_slot = server_slot; + titanium_slot = server_slot - (EQ::invbag::SHARED_BANK_BAGS_BEGIN - invbag::SHARED_BANK_BAGS_BEGIN) - ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((server_slot - EQ::invbag::SHARED_BANK_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT)); } else if (server_slot <= EQ::invslot::TRADE_END && server_slot >= EQ::invslot::TRADE_BEGIN) { titanium_slot = server_slot; } else if (server_slot <= EQ::invbag::TRADE_BAGS_END && server_slot >= EQ::invbag::TRADE_BAGS_BEGIN) { - titanium_slot = server_slot; + titanium_slot = server_slot - (EQ::invbag::TRADE_BAGS_BEGIN - invbag::TRADE_BAGS_BEGIN) - ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((server_slot - EQ::invbag::TRADE_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT)); } else if (server_slot <= EQ::invslot::WORLD_END && server_slot >= EQ::invslot::WORLD_BEGIN) { titanium_slot = server_slot; @@ -3687,10 +3687,10 @@ namespace Titanium server_slot = titanium_slot + 4; } else if (titanium_slot <= invbag::GENERAL_BAGS_END && titanium_slot >= invbag::GENERAL_BAGS_BEGIN) { - server_slot = titanium_slot; + server_slot = titanium_slot + (EQ::invbag::GENERAL_BAGS_BEGIN - invbag::GENERAL_BAGS_BEGIN) + ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((titanium_slot - invbag::GENERAL_BAGS_BEGIN) / invbag::SLOT_COUNT)); } else if (titanium_slot <= invbag::CURSOR_BAG_END && titanium_slot >= invbag::CURSOR_BAG_BEGIN) { - server_slot = titanium_slot + 20; + server_slot = titanium_slot + (EQ::invbag::CURSOR_BAG_BEGIN - invbag::CURSOR_BAG_BEGIN); } else if (titanium_slot <= invslot::TRIBUTE_END && titanium_slot >= invslot::TRIBUTE_BEGIN) { server_slot = titanium_slot; @@ -3705,19 +3705,19 @@ namespace Titanium server_slot = titanium_slot; } else if (titanium_slot <= invbag::BANK_BAGS_END && titanium_slot >= invbag::BANK_BAGS_BEGIN) { - server_slot = titanium_slot; + server_slot = titanium_slot + (EQ::invbag::BANK_BAGS_BEGIN - invbag::BANK_BAGS_BEGIN) + ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((titanium_slot - invbag::BANK_BAGS_BEGIN) / invbag::SLOT_COUNT)); } else if (titanium_slot <= invslot::SHARED_BANK_END && titanium_slot >= invslot::SHARED_BANK_BEGIN) { server_slot = titanium_slot; } else if (titanium_slot <= invbag::SHARED_BANK_BAGS_END && titanium_slot >= invbag::SHARED_BANK_BAGS_BEGIN) { - server_slot = titanium_slot; + server_slot = titanium_slot + (EQ::invbag::SHARED_BANK_BAGS_BEGIN - invbag::SHARED_BANK_BAGS_BEGIN) + ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((titanium_slot - invbag::SHARED_BANK_BAGS_BEGIN) / invbag::SLOT_COUNT)); } else if (titanium_slot <= invslot::TRADE_END && titanium_slot >= invslot::TRADE_BEGIN) { server_slot = titanium_slot; } else if (titanium_slot <= invbag::TRADE_BAGS_END && titanium_slot >= invbag::TRADE_BAGS_BEGIN) { - server_slot = titanium_slot; + server_slot = titanium_slot + (EQ::invbag::TRADE_BAGS_BEGIN - invbag::TRADE_BAGS_BEGIN) + ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((titanium_slot - invbag::TRADE_BAGS_BEGIN) / invbag::SLOT_COUNT)); } else if (titanium_slot <= invslot::WORLD_END && titanium_slot >= invslot::WORLD_BEGIN) { server_slot = titanium_slot; diff --git a/common/patches/titanium_limits.h b/common/patches/titanium_limits.h index cdd3171f4..cd7d9e5d8 100644 --- a/common/patches/titanium_limits.h +++ b/common/patches/titanium_limits.h @@ -286,8 +286,8 @@ namespace Titanium const size_t CHARACTER_CREATION_LIMIT = 8; // Hard-coded in client - DO NOT ALTER - const size_t SAY_LINK_BODY_SIZE = 45; - const uint32 MAX_GUILD_ID = 1500; + const size_t SAY_LINK_BODY_SIZE = 45; + const uint32 MAX_GUILD_ID = 1500; } /*constants*/ diff --git a/common/patches/titanium_structs.h b/common/patches/titanium_structs.h index 831198363..2d9275942 100644 --- a/common/patches/titanium_structs.h +++ b/common/patches/titanium_structs.h @@ -2463,25 +2463,25 @@ struct WhoAllReturnStruct { struct BeginTrader_Struct { uint32 action; uint32 unknown04; - uint64 serial_number[80]; - uint32 cost[80]; + uint64 serial_number[EQ::invtype::BAZAAR_SIZE]; + uint32 cost[EQ::invtype::BAZAAR_SIZE]; }; struct Trader_Struct { uint32 action; uint32 unknown004; - uint64 item_id[80]; - uint32 item_cost[80]; + uint64 item_id[EQ::invtype::BAZAAR_SIZE]; + uint32 item_cost[EQ::invtype::BAZAAR_SIZE]; }; struct ClickTrader_Struct { uint32 code; uint32 unknown[161];//damn soe this is totally pointless :/ but at least your finally using memset! Good job :) -LE - uint32 itemcost[80]; + uint32 itemcost[EQ::invtype::BAZAAR_SIZE]; }; struct GetItems_Struct{ - uint32 items[80]; + uint32 items[EQ::invtype::BAZAAR_SIZE]; }; struct BecomeTrader_Struct { diff --git a/common/patches/uf.cpp b/common/patches/uf.cpp index da07f068e..6433621f5 100644 --- a/common/patches/uf.cpp +++ b/common/patches/uf.cpp @@ -76,7 +76,7 @@ namespace UF { //create our opcode manager if we havent already if (opcodes == nullptr) { - std::string opfile = fmt::format("{}/patch_{}.conf", path.GetPatchPath(), name); + std::string opfile = fmt::format("{}/patch_{}.conf", PathManager::Instance()->GetPatchPath(), name); //load up the opcode manager. //TODO: figure out how to support shared memory with multiple patches... opcodes = new RegularOpcodeManager(); @@ -117,7 +117,7 @@ namespace UF //we need to go to every stream and replace it's manager. if (opcodes != nullptr) { - std::string opfile = fmt::format("{}/patch_{}.conf", path.GetPatchPath(), name); + std::string opfile = fmt::format("{}/patch_{}.conf", PathManager::Instance()->GetPatchPath(), name); if (!opcodes->ReloadOpcodes(opfile.c_str())) { LogNetcode("[OPCODES] Error reloading opcodes file [{}] for patch [{}]", opfile.c_str(), name); return; @@ -4908,12 +4908,12 @@ namespace UF UFSlot = serverSlot - 2; } - else if (serverSlot <= EQ::invbag::GENERAL_BAGS_8_END && serverSlot >= EQ::invbag::GENERAL_BAGS_BEGIN) { - UFSlot = serverSlot + 11; + else if (serverSlot <= EQ::invbag::GENERAL_BAGS_END && serverSlot >= EQ::invbag::GENERAL_BAGS_BEGIN) { + UFSlot = serverSlot - (EQ::invbag::GENERAL_BAGS_BEGIN - invbag::GENERAL_BAGS_BEGIN)/*3748*/ - ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((serverSlot - EQ::invbag::GENERAL_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT)); // + 11; } else if (serverSlot <= EQ::invbag::CURSOR_BAG_END && serverSlot >= EQ::invbag::CURSOR_BAG_BEGIN) { - UFSlot = serverSlot - 9; + UFSlot = serverSlot - (EQ::invbag::CURSOR_BAG_BEGIN - invbag::CURSOR_BAG_BEGIN)/*5668*/; // - 9; } else if (serverSlot <= EQ::invslot::TRIBUTE_END && serverSlot >= EQ::invslot::TRIBUTE_BEGIN) { @@ -4933,7 +4933,7 @@ namespace UF } else if (serverSlot <= EQ::invbag::BANK_BAGS_END && serverSlot >= EQ::invbag::BANK_BAGS_BEGIN) { - UFSlot = serverSlot + 1; + UFSlot = serverSlot - (EQ::invbag::BANK_BAGS_BEGIN - invbag::BANK_BAGS_BEGIN) - ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((serverSlot - EQ::invbag::BANK_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT)); // + 1; } else if (serverSlot <= EQ::invslot::SHARED_BANK_END && serverSlot >= EQ::invslot::SHARED_BANK_BEGIN) { @@ -4941,7 +4941,7 @@ namespace UF } else if (serverSlot <= EQ::invbag::SHARED_BANK_BAGS_END && serverSlot >= EQ::invbag::SHARED_BANK_BAGS_BEGIN) { - UFSlot = serverSlot + 1; + UFSlot = serverSlot - (EQ::invbag::SHARED_BANK_BAGS_BEGIN - invbag::SHARED_BANK_BAGS_BEGIN) - ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((serverSlot - EQ::invbag::SHARED_BANK_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT)); // + 1; } else if (serverSlot <= EQ::invslot::TRADE_END && serverSlot >= EQ::invslot::TRADE_BEGIN) { @@ -4949,7 +4949,7 @@ namespace UF } else if (serverSlot <= EQ::invbag::TRADE_BAGS_END && serverSlot >= EQ::invbag::TRADE_BAGS_BEGIN) { - UFSlot = serverSlot; + UFSlot = serverSlot - (EQ::invbag::TRADE_BAGS_BEGIN - invbag::TRADE_BAGS_BEGIN) - ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((serverSlot - EQ::invbag::TRADE_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT)); // + 0; } else if (serverSlot <= EQ::invslot::WORLD_END && serverSlot >= EQ::invslot::WORLD_BEGIN) { @@ -4991,11 +4991,11 @@ namespace UF } else if (ufSlot <= invbag::GENERAL_BAGS_END && ufSlot >= invbag::GENERAL_BAGS_BEGIN) { - ServerSlot = ufSlot - 11; + ServerSlot = ufSlot + (EQ::invbag::GENERAL_BAGS_BEGIN - invbag::GENERAL_BAGS_BEGIN)/*3748*/ + ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((ufSlot - invbag::GENERAL_BAGS_BEGIN) / invbag::SLOT_COUNT)); // - 11; } else if (ufSlot <= invbag::CURSOR_BAG_END && ufSlot >= invbag::CURSOR_BAG_BEGIN) { - ServerSlot = ufSlot + 9; + ServerSlot = ufSlot + (EQ::invbag::CURSOR_BAG_BEGIN - invbag::CURSOR_BAG_BEGIN)/*5668*/; // + 9; } else if (ufSlot <= invslot::TRIBUTE_END && ufSlot >= invslot::TRIBUTE_BEGIN) { @@ -5015,7 +5015,7 @@ namespace UF } else if (ufSlot <= invbag::BANK_BAGS_END && ufSlot >= invbag::BANK_BAGS_BEGIN) { - ServerSlot = ufSlot - 1; + ServerSlot = ufSlot + (EQ::invbag::BANK_BAGS_BEGIN - invbag::BANK_BAGS_BEGIN) + ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((ufSlot - invbag::BANK_BAGS_BEGIN) / invbag::SLOT_COUNT)); // - 1; } else if (ufSlot <= invslot::SHARED_BANK_END && ufSlot >= invslot::SHARED_BANK_BEGIN) { @@ -5023,7 +5023,7 @@ namespace UF } else if (ufSlot <= invbag::SHARED_BANK_BAGS_END && ufSlot >= invbag::SHARED_BANK_BAGS_BEGIN) { - ServerSlot = ufSlot - 1; + ServerSlot = ufSlot + (EQ::invbag::SHARED_BANK_BAGS_BEGIN - invbag::SHARED_BANK_BAGS_BEGIN) + ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((ufSlot - invbag::SHARED_BANK_BAGS_BEGIN) / invbag::SLOT_COUNT)); // - 1; } else if (ufSlot <= invslot::TRADE_END && ufSlot >= invslot::TRADE_BEGIN) { @@ -5031,7 +5031,7 @@ namespace UF } else if (ufSlot <= invbag::TRADE_BAGS_END && ufSlot >= invbag::TRADE_BAGS_BEGIN) { - ServerSlot = ufSlot; + ServerSlot = ufSlot + (EQ::invbag::TRADE_BAGS_BEGIN - invbag::TRADE_BAGS_BEGIN) + ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((ufSlot - invbag::TRADE_BAGS_BEGIN) / invbag::SLOT_COUNT)); // - 0; } else if (ufSlot <= invslot::WORLD_END && ufSlot >= invslot::WORLD_BEGIN) { diff --git a/common/path_manager.cpp b/common/path_manager.cpp index 452722c84..c63835854 100644 --- a/common/path_manager.cpp +++ b/common/path_manager.cpp @@ -5,31 +5,19 @@ #include "strings.h" #include + namespace fs = std::filesystem; -inline std::string striptrailingslash(const std::string &file_path) -{ - if (file_path.back() == '/' || file_path.back() == '\\') { - return file_path.substr(0, file_path.length() - 1); - } - - return file_path; -} - -void PathManager::LoadPaths() +void PathManager::Init() { m_server_path = File::FindEqemuConfigPath(); - if (!m_server_path.empty()) { - std::filesystem::current_path(m_server_path); - } - if (m_server_path.empty()) { LogInfo("Failed to load server path"); return; } - LogInfo("server [{}]", m_server_path); + std::filesystem::current_path(m_server_path); if (!EQEmuConfig::LoadConfig()) { LogError("Failed to load eqemu config"); @@ -38,66 +26,85 @@ void PathManager::LoadPaths() const auto c = EQEmuConfig::get(); - // maps - if (File::Exists(fs::path{m_server_path + "/" + c->MapDir}.string())) { - m_maps_path = fs::relative(fs::path{m_server_path + "/" + c->MapDir}).string(); - } - else if (File::Exists(fs::path{m_server_path + "/maps"}.string())) { - m_maps_path = fs::relative(fs::path{m_server_path + "/maps"}).string(); - } - else if (File::Exists(fs::path{m_server_path + "/Maps"}.string())) { - m_maps_path = fs::relative(fs::path{m_server_path + "/Maps"}).string(); + auto resolve_path = [&](const std::string& dir, const std::vector& fallback_dirs = {}) -> std::string { + // relative + if (File::Exists((fs::path{m_server_path} / dir).string())) { + return fs::relative(fs::path{m_server_path} / dir).lexically_normal().string(); + } + + // absolute + if (File::Exists(fs::path{dir}.string())) { + return fs::absolute(fs::path{dir}).string(); + } + + // fallback search options if specified + for (const auto& fallback : fallback_dirs) { + if (File::Exists((fs::path{m_server_path} / fallback).string())) { + return fs::relative(fs::path{m_server_path} / fallback).lexically_normal().string(); + } + } + + // if all else fails, just set it to the config value + return dir; + }; + + auto load_many_paths_fallback = [&](const std::vector& dirs, const std::string& fallback, std::vector& target) { + target.clear(); + if (!dirs.empty()) { + for (const auto& path : dirs) { + target.push_back(resolve_path(path)); + } + } else { + target.push_back(resolve_path(fallback)); + } + }; + + load_many_paths_fallback(c->GetQuestDirectories(), c->QuestDir, m_quests_paths); + load_many_paths_fallback(c->GetPluginsDirectories(), c->PluginDir, m_plugin_paths); + load_many_paths_fallback(c->GetLuaModuleDirectories(), c->LuaModuleDir, m_lua_module_paths); + + // resolve all paths + m_maps_path = resolve_path(c->MapDir, {"maps", "Maps"}); + m_lua_mods_path = resolve_path("mods"); + m_patch_path = resolve_path(c->PatchDir); + m_opcode_path = resolve_path(c->OpcodeDir); + m_shared_memory_path = resolve_path(c->SharedMemDir); + m_log_path = resolve_path(c->LogDir, {"logs"}); + + // Log all paths in a loop + std::vector> paths = { + {"server", m_server_path}, + {"logs", m_log_path}, + {"maps", m_maps_path}, + {"lua mods", m_lua_mods_path}, + {"patches", m_patch_path}, + {"opcode", m_opcode_path}, + {"shared_memory", m_shared_memory_path} + }; + + constexpr int name_width = 15; + constexpr int path_width = 0; + constexpr int break_length = 70; + + LogInfo("Loading server paths"); + LogInfo("{}", Strings::Repeat("-", break_length)); + for (const auto& [name, in_path] : paths) { + if (!in_path.empty()) { + LogInfo("{:>{}} > [{:<{}}]", name, name_width, in_path, path_width); + } } - // quests - if (File::Exists(fs::path{m_server_path + "/" + c->QuestDir}.string())) { - m_quests_path = fs::relative(fs::path{m_server_path + "/" + c->QuestDir}).string(); - } + auto log_paths = [&](const std::string& label, const std::vector& paths) { + if (!paths.empty()) { + LogInfo("{:>{}} > [{:<{}}]", label, name_width - 1, Strings::Join(paths, ";"), path_width); + } + }; - // plugins - if (File::Exists(fs::path{m_server_path + "/" + c->PluginDir}.string())) { - m_plugins_path = fs::relative(fs::path{m_server_path + "/" + c->PluginDir}).string(); - } + log_paths("quests", m_quests_paths); + log_paths("plugins", m_plugin_paths); + log_paths("lua_modules", m_lua_module_paths); - // lua_modules - if (File::Exists(fs::path{m_server_path + "/" + c->LuaModuleDir}.string())) { - m_lua_modules_path = fs::relative(fs::path{m_server_path + "/" + c->LuaModuleDir}).string(); - } - - // lua mods - if (File::Exists(fs::path{ m_server_path + "/mods" }.string())) { - m_lua_mods_path = fs::relative(fs::path{ m_server_path + "/mods" }).string(); - } - - // patches - if (File::Exists(fs::path{m_server_path + "/" + c->PatchDir}.string())) { - m_patch_path = fs::relative(fs::path{m_server_path + "/" + c->PatchDir}).string(); - } - - // patches - if (File::Exists(fs::path{ m_server_path + "/" + c->OpcodeDir }.string())) { - m_opcode_path = fs::relative(fs::path{ m_server_path + "/" + c->OpcodeDir }).string(); - } - - // shared_memory_path - if (File::Exists(fs::path{m_server_path + "/" + c->SharedMemDir}.string())) { - m_shared_memory_path = fs::relative(fs::path{ m_server_path + "/" + c->SharedMemDir }).string(); - } - - // logging path - if (File::Exists(fs::path{m_server_path + "/" + c->LogDir}.string())) { - m_log_path = fs::relative(fs::path{m_server_path + "/" + c->LogDir}).string(); - } - - LogInfo("logs path [{}]", m_log_path); - LogInfo("lua mods path [{}]", m_lua_mods_path); - LogInfo("lua_modules path [{}]", m_lua_modules_path); - LogInfo("maps path [{}]", m_maps_path); - LogInfo("patches path [{}]", m_patch_path); - LogInfo("opcode path [{}]", m_opcode_path); - LogInfo("plugins path [{}]", m_plugins_path); - LogInfo("quests path [{}]", m_quests_path); - LogInfo("shared_memory path [{}]", m_shared_memory_path); + LogInfo("{}", Strings::Repeat("-", break_length)); } const std::string &PathManager::GetServerPath() const @@ -110,21 +117,26 @@ const std::string &PathManager::GetMapsPath() const return m_maps_path; } -const std::string &PathManager::GetQuestsPath() const -{ - return m_quests_path; -} - -const std::string &PathManager::GetPluginsPath() const -{ - return m_plugins_path; -} - const std::string &PathManager::GetSharedMemoryPath() const { return m_shared_memory_path; } +std::vector PathManager::GetQuestPaths() const +{ + return m_quests_paths; +} + +std::vector PathManager::GetPluginPaths() const +{ + return m_plugin_paths; +} + +std::vector PathManager::GetLuaModulePaths() const +{ + return m_lua_module_paths; +} + const std::string &PathManager::GetLogPath() const { return m_log_path; @@ -140,11 +152,6 @@ const std::string &PathManager::GetOpcodePath() const return m_opcode_path; } -const std::string &PathManager::GetLuaModulesPath() const -{ - return m_lua_modules_path; -} - const std::string &PathManager::GetLuaModsPath() const { return m_lua_mods_path; diff --git a/common/path_manager.h b/common/path_manager.h index 3ef55d0a7..67919511e 100644 --- a/common/path_manager.h +++ b/common/path_manager.h @@ -3,10 +3,17 @@ #include +#include class PathManager { public: - void LoadPaths(); + void Init(); + + static PathManager *Instance() + { + static PathManager instance; + return &instance; + } [[nodiscard]] const std::string &GetLogPath() const; [[nodiscard]] const std::string &GetLuaModsPath() const; @@ -14,24 +21,27 @@ public: [[nodiscard]] const std::string &GetMapsPath() const; [[nodiscard]] const std::string &GetPatchPath() const; [[nodiscard]] const std::string &GetOpcodePath() const; - [[nodiscard]] const std::string &GetPluginsPath() const; - [[nodiscard]] const std::string &GetQuestsPath() const; [[nodiscard]] const std::string &GetServerPath() const; [[nodiscard]] const std::string &GetSharedMemoryPath() const; + [[nodiscard]] std::vector GetQuestPaths() const; + [[nodiscard]] std::vector GetPluginPaths() const; + [[nodiscard]] std::vector GetLuaModulePaths() const; private: - std::string m_log_path; - std::string m_lua_mods_path; - std::string m_lua_modules_path; - std::string m_maps_path; - std::string m_patch_path; - std::string m_opcode_path; - std::string m_plugins_path; - std::string m_quests_path; - std::string m_server_path; - std::string m_shared_memory_path; + std::string m_log_path; + std::string m_lua_mods_path; + std::string m_maps_path; + std::string m_patch_path; + std::string m_opcode_path; + std::string m_quests_path; + std::vector m_quests_paths; + std::vector m_plugin_paths; + std::vector m_lua_module_paths; + + +private: + std::string m_server_path; + std::string m_shared_memory_path; }; -extern PathManager path; - #endif //EQEMU_PATH_MANAGER_H diff --git a/common/pch/app-pch.h b/common/pch/app-pch.h new file mode 100644 index 000000000..aa2244f13 --- /dev/null +++ b/common/pch/app-pch.h @@ -0,0 +1,9 @@ +#pragma once + +#include "../types.h" +#include "../database.h" +#include "../strings.h" +#include "../eqemu_logsys.h" +#include "../eqemu_logsys_log_aliases.h" +#include "../features.h" +#include "../global_define.h" diff --git a/common/pch/pch.h b/common/pch/pch.h deleted file mode 100644 index 06b1d1a8f..000000000 --- a/common/pch/pch.h +++ /dev/null @@ -1,34 +0,0 @@ -// types -#include -#include -#include -#include - -// containers -#include -#include -#include -#include -#include -#include -#include - -// utilities -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// fmt -#include - -// lua -#include "lua.hpp" -#include -#include diff --git a/common/pch/std-pch.h b/common/pch/std-pch.h new file mode 100644 index 000000000..3b1b9faad --- /dev/null +++ b/common/pch/std-pch.h @@ -0,0 +1,14 @@ +#pragma once + +// Lightweight, widely used +#include +#include +#include +#include +#include +#include +#include +#include + +// fmt +#include diff --git a/common/process/process.cpp b/common/process/process.cpp index badb0fe11..56bbeff2b 100644 --- a/common/process/process.cpp +++ b/common/process/process.cpp @@ -1,6 +1,7 @@ #include #include #include "process.h" +#include std::string Process::execute(const std::string &cmd) { diff --git a/common/races.cpp b/common/races.cpp index 033d188bf..a01597640 100644 --- a/common/races.cpp +++ b/common/races.cpp @@ -22,1467 +22,1376 @@ const char* GetRaceIDName(uint16 race_id) { switch (race_id) { - // Old Race Labels - //case HUMAN: - // return "Human"; - //case BARBARIAN: - // return "Barbarian"; - //case ERUDITE: - // return "Erudite"; - //case WOOD_ELF: - // return "Wood Elf"; - //case HIGH_ELF: - // return "High Elf"; - //case DARK_ELF: - // return "Dark Elf"; - //case HALF_ELF: - // return "Half Elf"; - //case DWARF: - // return "Dwarf"; - //case TROLL: - // return "Troll"; - //case OGRE: - // return "Ogre"; - //case HALFLING: - // return "Halfling"; - //case GNOME: - // return "Gnome"; - //case IKSAR: - // return "Iksar"; - //case WEREWOLF: - // return "Werewolf"; - //case SKELETON: - // return "Skeleton"; - //case ELEMENTAL: - // return "Elemental"; - //case EYE_OF_ZOMM: - // return "Eye of Zomm"; - //case WOLF_ELEMENTAL: - // return "Wolf Elemental"; - //case IKSAR_SKELETON: - // return "Iksar Skeleton"; - //case VAHSHIR: - // return "Vah Shir"; - //case FROGLOK: - //case FROGLOK2: // "Piranha"? (low-byte issue..) - // return "Froglok"; - //case DRAKKIN: - // return "Drakkin"; - - // RoF2 Race Labels - case Race::Abhorrent: - return "Abhorrent"; - case Race::AirElemental: - case Race::AirElemental2: - return "Air Elemental"; - case Race::AirMephit: - return "Air Mephit"; - case Race::Akhevan: - case Race::Akheva: - return "Akheva"; - case Race::Alaran: - return "Alaran"; - case Race::AlaranGhost: - return "Alaran Ghost"; - case Race::AlaranSentryStone: - return "Alaran Sentry Stone"; - case Race::Alligator: - case Race::Alligator2: - return "Alligator"; - case Race::Denizen: - case Race::Amygdalan: - return "Amygdalan"; - case Race::Aneuk: - return "Aneuk"; - case Race::AnimatedArmor: - return "Animated Armor"; - case Race::AnimatedHand: - return "Animated Hand"; - case Race::AnimatedStatue: - case Race::AnimatedStatue2: - return "Animated Statue"; - case Race::Apexus: - return "Apexus"; - case Race::Arachnid: - return "Arachnid"; - case Race::ArcanistOfHate: - return "Arcanist of Hate"; - case Race::Armadillo: - return "Armadillo"; - case Race::ArmorRack: - return "Armor Rack"; - case Race::Aviak: - case Race::Aviak2: - return "Aviak"; - case Race::AviakPullAlong: - return "Aviak Pull Along"; - case Race::AyonaeRo: - return "Ayonae Ro"; - case Race::Banner: - case Race::Banner2: - case Race::Banner3: - case Race::Banner4: - case Race::Banner5: - case Race::Banner6: - case Race::Banner7: - return "Banner"; - case Race::TenthAnniversaryBanner: - return "10th Anniversary Banner"; - case Race::Banshee: - case Race::Banshee2: - case Race::Banshee3: - return "Banshee"; - case Race::Barbarian: - case Race::HalasCitizen: - return "Barbarian"; - case Race::Barrel: - return "Barrel"; - case Race::UndeadBoat: - return "Barrel Barge Ship"; - case Race::Basilisk: - return "Basilisk"; - case Race::GiantBat: - case Race::Bat: - case Race::Bat2: - return "Bat"; - case Race::Bazu: - return "Bazu"; - case Race::Bear: - case Race::Bear2: - case Race::Bear3: - return "Bear"; - case Race::BearMount: - return "Bear Mount"; - case Race::BearTrap: - return "Bear Trap"; - case Race::Beetle: - case Race::Beetle2: - case Race::Beetle3: - return "Beetle"; - case Race::HumanBeggar: - return "Beggar"; - case Race::Bellikos: - return "Bellikos"; - case Race::Bertoxxulous: - case Race::BertoxxulousNew: - return "Bertoxxulous"; - case Race::Bixie: - case Race::Bixie2: - return "Bixie"; - case Race::BlimpShip: - return "Blimp Ship"; - case Race::BlindDreamer: - return "Blind Dreamer"; - case Race::BloodRaven: - return "Blood Raven"; - case Race::Boat: - case Race::Boat2: - return "Boat"; - case Race::Bolvirk: - return "Bolvirk"; - case Race::BoneGolem: - case Race::BoneGolem2: - return "Bone Golem"; - case Race::Bones: - return "Bones"; - case Race::BookDervish: - return "Book Dervish"; - case Race::Portal: - return "BoT Portal"; - case Race::Boulder: - return "Boulder"; - case Race::Box: - return "Box"; - case Race::Braxi: - return "Braxi"; - case Race::BraxiMount: - return "Braxi Mount"; - case Race::Brell: - return "Brell"; - case Race::BrellsFirstCreation: - return "Brell's First Creation"; - case Race::Bristlebane: - return "Bristlebane"; - case Race::BrokenClockwork: - return "Broken Clockwork"; - case Race::Brontotherium: - return "Brontotherium"; - case Race::Brownie: - case Race::Brownie2: - return "Brownie"; - case Race::Bubonian: - return "Bubonian"; - case Race::BubonianUnderling: - return "Bubonian Underling"; - case Race::Burynai: - case Race::Burynai2: - return "Burynai"; - case Race::TenthAnniversaryCake: - return "10th Anniversary Cake"; - case Race::Campfire: - return "Campfire"; - case Race::CarrierHand: - return "Carrier Hand"; - case Race::Cat: - return "Cat"; - case Race::CazicThule: - case Race::CazicThule2: - return "Cazic Thule"; - case Race::Centaur: - case Race::Centaur2: - return "Centaur"; - case Race::Chest: - case Race::Chest2: - case Race::Chest3: - return "Chest"; - case Race::Chimera: - case Race::Chimera2: - return "Chimera"; - case Race::Chokidai: - return "Chokidai"; - case Race::Clam: - return "Clam"; - case Race::CliknarMount: - return "Cliknar Mount"; - case Race::CliknarQueen: - return "Cliknar Queen"; - case Race::CliknarSoldier: - return "Cliknar Soldier"; - case Race::CliknarWorker: - return "Cliknar Worker"; - case Race::ClockworkBeetle: - return "Clockwork Beetle"; - case Race::ClockworkBoar: - return "Clockwork Boar"; - case Race::ClockworkBomb: - return "Clockwork Bomb"; - case Race::ClockworkBrain: - return "Clockwork Brain"; - case Race::ClockworkGnome: - return "Clockwork Gnome"; - case Race::ClockworkGolem: - return "Clockwork Golem"; - case Race::ClockworkGuardian: - return "Clockwork Guardian"; - case Race::Cockatrice: - return "Cockatrice"; - case Race::Coffin: - case Race::Coffin2: - return "Coffin"; - case Race::CoinPurse: - return "Coin Purse"; - case Race::Coldain: - case Race::Coldain2: - case Race::Coldain3: - return "Coldain"; - case Race::Coral: - return "Coral"; - case Race::Corathus: - return "Corathus"; - case Race::Crab: - return "Crab"; - case Race::Cragbeast: - return "Cragbeast"; - case Race::Cragslither: - return "Cragslither"; - case Race::Crocodile: - return "Crocodile"; - case Race::Crystal: - return "Crystal"; - case Race::CrystalShard: - return "Crystal Shard"; - case Race::CrystalSphere: - return "Crystal Sphere"; - case Race::CrystalSpider: - return "Crystal Spider"; - case Race::CrystalskinAmbuloid: - return "Crystalskin Ambuloid"; - case Race::CrystalskinSessiloid: - return "Crystalskin Sessiloid"; - case Race::DaisyMan: - return "Daisy Man"; - case Race::DarkElf: - case Race::NeriakCitizen: - return "Dark Elf"; - case Race::DarkLord: - return "Dark Lord"; - case Race::DemiLich: - return "Demi Lich"; - case Race::DemonVulture: - return "Demon Vulture"; - case Race::Dervish: - case Race::Dervish2: - case Race::Dervish3: - case Race::Dervish4: - return "Dervish"; - case Race::Dervish5: - return "Dervish(Ver. 5)"; - case Race::Dervish6: - return "Dervish(Ver. 6)"; - case Race::Devourer: - return "Devourer"; - case Race::DireWolf: - return "Dire Wolf"; - case Race::DiscordShip: - return "Discord Ship"; - case Race::Discordling: - return "Discordling"; - case Race::DiseasedFiend: - return "Diseased Fiend"; - case Race::Djinn: - return "Djinn"; - case Race::Drachnid: - case Race::Drachnid2: - return "Drachnid"; - case Race::DrachnidCocoon: - return "Drachnid Cocoon"; - case Race::Dracolich: - return "Dracolich"; - case Race::Draglock: - return "Draglock"; - case Race::LavaDragon: - case Race::DragonSkeleton: - case Race::WaterDragon: - case Race::VeliousDragon: - case Race::ClockworkDragon: - case Race::BlackAndWhiteDragon: - case Race::GhostDragon: - case Race::PrismaticDragon: - case Race::Quarm: - case Race::Dragon: - case Race::Dragon2: - case Race::Dragon3: - case Race::Dragon4: - case Race::Dragon5: - case Race::Dragon6: - case Race::Dragon7: - return "Dragon"; - case Race::DragonBones: - return "Dragon Bones"; - case Race::DragonEgg: - return "Dragon Egg"; - case Race::DragonStatue: - return "Dragon Statue"; - case Race::Dragorn: - return "Dragorn"; - case Race::DragornBox: - return "Dragorn Box"; - case Race::Drake: - case Race::Drake2: - case Race::Drake3: - return "Drake"; - case Race::Drakkin: - return "Drakkin"; - case Race::Drixie: - return "Drixie"; - case Race::Drogmor: - return "Drogmore"; - case Race::Drolvarg: - return "Drolvarg"; - case Race::Dryad: - return "Dryad"; - case Race::Dwarf: - case Race::KaladimCitizen: - return "Dwarf"; - case Race::DynamiteKeg: - return "Dynamite Keg"; - case Race::Dynleth: - return "Dyn'Leth"; - case Race::EarthElemental: - case Race::EarthElemental2: - return "Earth Elemental"; - case Race::EarthMephit: - return "Earth Mephit"; - case Race::GiantEel: - return "Eel"; - case Race::Efreeti: - case Race::Efreeti2: - return "Efreeti"; - case Race::Elddar: - return "Elddar"; - case Race::Elemental: - return "Elemental"; - case Race::ElkHead: - return "Elk Head"; - case Race::ElvenBoat: - return "Elven Boat"; - case Race::ElvenGhost: - return "Elven Ghost"; - case Race::EnchantedArmor: - return "Enchanted Armor"; - case Race::Erollisi: - return "Erollisi"; - case Race::Erudite: - case Race::EruditeCitizen: - case Race::Erudite2: - return "Erudite"; - case Race::EvanTest: - return "Evan Test"; - case Race::EvilEye: - case Race::EvilEye2: - case Race::EvilEye3: - return "Evil Eye"; - case Race::Exoskeleton: - return "Exoskeleton"; - case Race::ExplosiveCart: - return "Explosive Cart"; - case Race::EyeOfZomm: - return "Eye"; - case Race::Fairy: - case Race::Fairy2: - return "Fairy"; - case Race::FallenKnight: - return "Fallen Knight"; - case Race::Faun: - return "Faun"; - case Race::FayDrake: - return "Fay Drake"; - case Race::FenninRo: - return "Fennin Ro"; - case Race::Feran: - return "Feran"; - case Race::FeranMount: - return "Feran Mount"; - case Race::Fiend: - return "Fiend"; - case Race::FireElemental: - case Race::FireElemental2: - return "Fire Elemental"; - case Race::FireMephit: - return "Fire Mephit"; - case Race::Fish: - case Race::KunarkFish: - return "Fish"; - case Race::Flag: - return "Flag"; - case Race::FloatingIsland: - return "Floating Island"; - case Race::FloatingSkull: - return "Floating Skull"; - case Race::FloatingTower: - return "Floating Tower"; - case Race::Fly: - return "Fly"; - case Race::FlyingCarpet: - return "Flying Carpet"; - case Race::ForestGiant2: - return "Forest Giant"; - case Race::Frog: - case Race::Frog2: - return "Frog"; - case Race::Froglok: - case Race::FroglokGhoul: - case Race::Froglok2: - return "Froglok"; - case Race::FroglokGhost: - return "Froglok Ghost"; - case Race::FroglokSkeleton: - return "Froglok Skeleton"; - case Race::FungalFiend: - return "Fungal Fiend"; - case Race::FungusPatch: - return "Fungus Patch"; - case Race::Fungusman: - return "Fungusman"; - case Race::Galorian: - return "Galorian"; - case Race::Gargoyle: - case Race::Gargoyle2: - return "Gargoyle"; - case Race::Gasbag: - return "Gasbag"; - case Race::GelatinousCube: - case Race::GelatinousCube2: - return "Gelatinous Cube"; - case Race::Gelidran: - return "Gelidran"; - case Race::Genari: - return "Genari"; - case Race::Geonid: - return "Geonid"; - case Race::Ghost: - case Race::DwarfGhost: - case Race::EruditeGhost: - case Race::PirateGhost: - return "Ghost"; - case Race::GhostShip: - case Race::GhostShip2: - return "Ghost Ship"; - case Race::Ghoul: - case Race::Ghoul2: - return "Ghoul"; - case Race::Giant: - case Race::ForestGiant: - case Race::FrostGiant: - case Race::StormGiant: - case Race::EarthGolem: - case Race::IronGolem: - case Race::StormGolem: - case Race::AirGolem: - case Race::WoodGolem: - case Race::FireGolem: - case Race::WaterGolem: - case Race::Giant2: - case Race::Giant3: - return "Giant"; - case Race::GiantClockwork: - return "Giant Clockwork"; - case Race::Giant4: - return "Giant(Rallosian mats)"; - case Race::GiantShade: - return "Giant Shade"; - case Race::Gigyn: - return "Gigyn"; - case Race::GingerbreadMan: - return "Gingerbread Man"; - case Race::Girplan: - return "Girplan"; - case Race::Gnoll: - case Race::Gnoll2: - case Race::Gnoll3: - return "Gnoll"; - case Race::Gnome: - return "Gnome"; - case Race::Gnomework: - return "Gnomework"; - case Race::GnomishBalloon: - return "Gnomish Balloon"; - case Race::GnomishBoat: - return "Gnomish Boat"; - case Race::GnomishHoveringTransport: - return "Gnomish Hovering Transport"; - case Race::GnomishRocketPack: - return "Gnomish Rocket Pack"; - case Race::Goblin: - case Race::Bloodgill: - case Race::KunarkGoblin: - case Race::NewGoblin: - case Race::Goblin2: - return "Goblin"; - case Race::Luclin2: - return "God - Luclin(Ver. 2)"; - case Race::Luclin3: - return "God - Luclin(Ver. 3)"; - case Race::Luclin4: - return "God - Luclin(Ver. 4)"; - case Race::GodOfDiscord: - return "God of Discord"; - case Race::Golem: - case Race::Golem2: - return "Golem"; - case Race::Goo: - case Race::Goo2: - case Race::Goo3: - case Race::Goo4: - return "Goo"; - case Race::Goral: - return "Goral"; - case Race::GoralMount: - return "Goral Mount"; - case Race::Gorgon: - return "Gorgon"; - case Race::Gorilla: - case Race::Gorilla2: - return "Gorilla"; - case Race::GrandfatherClock: - return "Grandfather Clock"; - case Race::GrekenYoung: - return "Greken - Young"; - case Race::GrekenYoungAdult: - return "Greken - Young Adult"; - case Race::Grendlaen: - return "Grendlaen"; - case Race::GriegVeneficus: - return "Grieg Veneficus"; - case Race::Griffin: - case Race::Griffin2: - return "Griffin"; - case Race::Grimling: - return "Grimling"; - case Race::GroundShaker: - return "Ground Shaker"; - case Race::FreeportGuard: - case Race::Felguard: - case Race::Fayguard: - case Race::VahShirGuard: - return "Guard"; - case Race::GuardOfJustice: - return "Guard of Justice"; - case Race::GuardianCpu: - return "Guardian CPU"; - case Race::Hadal: - return "Hadal"; - case Race::Hag: - return "Hag"; - case Race::HalfElf: - return "Half Elf"; - case Race::Halfling: - case Race::RivervaleCitizen: - return "Halfling"; - case Race::Harpy: - case Race::Harpy2: - return "Harpy"; - case Race::HighElf: - return "High Elf"; - case Race::Hippogriff: - return "Hippogriff"; - case Race::Holgresh: - case Race::Holgresh2: - return "Holgresh"; - case Race::HoneyPot: - return "Honey Pot"; - case Race::Horse: - case Race::Horse2: - case Race::Horse3: - return "Horse"; - case Race::HoveringPlatform: - return "Hovering Platform"; - case Race::Hraquis: - return "Hraquis"; - case Race::Human: - case Race::HighpassCitizen: - case Race::QeynosCitizen: - case Race::Human2: - return "Human"; - case Race::HumanGhost: - return "Human Ghost"; - case Race::Huvul: - return "Huvul"; - case Race::HydraCrystal: - return "Hydra Crystal"; - case Race::HydraMount: - return "Hydra Mount"; - case Race::Hydra: - return "Hydra NPC"; - case Race::Hynid: - return "Hynid"; - case Race::IceSpectre: - return "Ice Spectre"; - case Race::Ikaav: - return "Ikaav"; - case Race::Iksar: - case Race::IksarCitizen: - return "Iksar"; - case Race::IksarGhost: - return "Iksar Ghost"; - case Race::IksarGolem: - return "Iksar Golem"; - case Race::IksarSkeleton: - return "Iksar Skeleton"; - case Race::IksarSpirit: - return "Iksar Spirit"; - case Race::Imp: - return "Imp"; - case Race::Innoruuk: - return "Innoruuk"; - case Race::Insect: - return "Insect"; - case Race::InteractiveObject: - return "Interactive Object"; - case Race::InvisibleMan: - case Race::InvisibleMan2: - case Race::InvisibleMan3: - return "Invisible Man"; - case Race::InvisibleManOfZomm: - return "Invisible Man of Zomm"; - case Race::Ixt: - return "Ixt"; - case Race::Jokester: - return "Jokester"; - case Race::JumJumBucket: - return "Jum Jum Bucket"; - case Race::JunkBeast: - return "Junk Beast"; - case Race::Kangon: - return "Kangon"; - case Race::KangonMount: - return "Kangon Mount"; - case Race::Karana: - return "Karana"; - case Race::PhinigelAutropos: - case Race::Kedge: - return "Kedge"; - case Race::Kerran: - case Race::Kerran2: - return "Kerran"; - case Race::Kirin: - case Race::Kirin2: - return "Kirin"; - case Race::KnightOfHate: - return "Knight of Hate"; - case Race::KnightOfPestilence: - return "Knight of Pestilence"; - case Race::Kobold: - case Race::Kobold2: - return "Kobold"; - case Race::Kraken: - return "Kraken"; - case Race::Kyv: - return "Kyv"; - case Race::Launch: - return "Launch"; - case Race::LavaRock: - return "Lava Rock"; - case Race::LavaSpider: - return "Lava Spider"; - case Race::LavaSpiderQueen: - return "Lava Spider Queen"; - case Race::Leech: - return "Leech"; - case Race::Lepertoloth: - return "Lepertoloth"; - case Race::Lightcrawler: - return "Lightcrawler"; - case Race::LightingWarrior: - return "Lightning Warrior"; - case Race::Lion: - return "Lion"; - case Race::LizardMan: - return "Lizard Man"; - case Race::Luclin: - return "Luclin"; - case Race::Luggald: - case Race::Luggald2: - return "Luggald"; - case Race::Luggald3: - return "Luggalds"; - case Race::Malarian: - return "Malarian"; - case Race::Mammoth: - case Race::Mammoth2: - return "Mammoth"; - case Race::ManEatingPlant: - return "Man - Eating Plant"; - case Race::Mansion: - return "Mansion"; - case Race::Manticore: - return "Manticore"; - case Race::Unknown5: - return "Mantrap"; - case Race::Marionette: - return "Marionette"; - case Race::Mastruq: - return "Mastruq"; - case Race::MataMuram: - return "Mata Muram"; - case Race::Toolbox: - return "Medium Plant"; - case Race::Mephit: - return "Mephit"; - case Race::MerchantShip: - return "Merchant Ship"; - case Race::Mermaid: - return "Mermaid"; - case Race::Mimic: - return "Mimic"; - case Race::MiniPom: - return "Mini POM"; - case Race::Minotaur: - case Race::Minotaur2: - case Race::Minotaur3: - case Race::Minotaur4: - return "Minotaur"; - case Race::MithanielMarr: - return "Mithaniel Marr"; - case Race::MorellThule: - return "Morell Thule"; - case Race::Mosquito: - return "Mosquito"; - case Race::MouthOfInsanity: - return "Mouth of Insanity"; - case Race::Muddite: - return "Muddite"; - case Race::Mummy: - return "Mummy"; - case Race::MuramiteArmorPile: - return "Muramite Armor Pile"; - case Race::Murkglider: - return "Murkglider"; - case Race::MurkgliderEggSack: - return "Murkglider Egg Sac"; - case Race::Mutna: - return "Mutna"; - case Race::Nekhon: - return "Nekhon"; - case Race::Netherbian: - return "Netherbian"; - case Race::Nightmare: - return "Nightmare"; - case Race::NightmareGargoyle: - return "Nightmare Gargoyle"; - case Race::NightmareGoblin: - return "Nightmare Goblin"; - case Race::NightmareMephit: - return "Nightmare Mephit"; - case Race::Unicorn2: - case Race::Unicorn3: - return "Nightmare / Unicorn"; - case Race::NightmareWraith: - return "Nightmare Wraith"; - case Race::Nihil: - return "Nihil"; - case Race::Nilborien: - return "Nilborien"; - case Race::Noc: - return "Noc"; - case Race::Nymph: - return "Nymph"; - case Race::Ogre: - case Race::OggokCitizen: - return "Ogre"; - case Race::Ogre2: - return "Ogre NPC - Male"; - case Race::Orb: - return "Orb"; - case Race::Orc: - case Race::Orc2: - return "Orc"; - case Race::Othmir: - return "Othmir"; - case Race::Owlbear: - return "Owlbear"; - case Race::ParasiticScavenger: - return "Parasitic Scavenger"; - case Race::Pegasus: - case Race::Pegasus2: - case Race::Pegasus3: - return "Pegasus"; - case Race::Phoenix: - return "Phoenix"; - case Race::Piranha: - return "Piranha"; - case Race::OneArmedPirate: - case Race::SpiritmasterNadox: - case Race::BrokenSkullTaskmaster: - case Race::GnomePirate: - case Race::DarkElfPirate: - case Race::OgrePirate: - case Race::HumanPirate: - case Race::EruditePirate: - return "Pirate"; - case Race::PirateShip: - return "Pirate Ship"; - case Race::Pixie: - return "Pixie"; - case Race::PoisonFrog: - return "Poison Frog"; - case Race::Portal2: - return "Portal"; - case Race::PowderKeg: - return "Powder Keg"; - case Race::PressurePlate: - return "Pressure Plate"; - case Race::PufferSpore: - return "Puffer Spore"; - case Race::Puma: - case Race::Puma2: - case Race::Puma3: - return "Puma"; - case Race::Pusling: - return "Pusling"; - case Race::Pyrilen: - return "Pyrilen"; - case Race::Ratuk: - return "Ra`tuk"; - case Race::Rabbit: - return "Rabbit"; - case Race::StatueOfRallosZek: - case Race::NewRallosZek: - return "Rallos Zek"; - case Race::RallosOgre: - return "Rallos Zek Minion"; - case Race::Raptor: - case Race::Raptor2: - return "Raptor"; - case Race::RaptorMount: - return "Raptor Mount"; - case Race::GiantRat: - case Race::Rat: - return "Rat"; - case Race::RatMount: - return "Rat Mount"; - case Race::Ratman: - case Race::Ratman2: - return "Ratman"; - case Race::ReanimatedHand: - return "Reanimated Hand"; - case Race::Recuso: - return "Recuso"; - case Race::RegenerationPool: - return "Regeneration Pool"; - case Race::RelicCase: - return "Relic case "; - case Race::RhinoBeetle: - return "Rhino Beetle"; - case Race::Rhinoceros: - return "Rhinoceros"; - case Race::RobocopterOfZomm: - return "Robocopter of Zomm"; - case Race::RockPile: - return "Rock Pile"; - case Race::Rockhopper: - return "Rockhopper"; - case Race::RonnieTest: - return "Ronnie Test"; - case Race::RootTentacle: - return "Root Tentacle"; - case Race::RotDogMount: - return "Rot Dog Mount"; - case Race::RotDog: - return "Rotdog"; - case Race::Rotocopter: - return "Rotocopter"; - case Race::Rowboat: - return "Rowboat"; - case Race::RoyalGuard: - return "Royal Guard"; - case Race::RujarkianOrc: - case Race::MasterOrc: - return "Rujarkian Orc"; - case Race::RunedOrb: - return "Runed Orb"; - case Race::RunicSymbol: - return "Runic Symbol"; - case Race::Sabertooth: - return "Saber - toothed Cat"; - case Race::SaltpetterBomb: - return "Saltpetter Bomb"; - case Race::SandElf: - return "Sand Elf"; - case Race::Sandman: - return "Sandman"; - case Race::Sarnak: - case Race::Sarnak2: - return "Sarnak"; - case Race::SarnakGolem: - return "Sarnak Golem"; - case Race::SarnakSpirit: - return "Sarnak Spirit"; - case Race::Saryrn: - return "Saryrn"; - case Race::Satyr: - return "Satyr"; - case Race::ScaledWolf: - return "Scaled Wolf"; - case Race::Scarecrow: - case Race::Scarecrow2: - return "Scarecrow"; - case Race::ScarletCheetah: - return "Scarlet Cheetah"; - case Race::ScleraMount: - return "Sclera Mount"; - case Race::Scorpion: - case Race::IksarScorpion: - case Race::Scorpion2: - return "Scorpion"; - case Race::Scrykin: - return "Scrykin"; - case Race::SeaTurtle: - return "Sea Turtle"; - case Race::SeaHorse: - return "Seahorse"; - case Race::Selyrah: - return "Selyrah"; - case Race::SelyrahMount: - return "Selyrah Mount"; - case Race::LordInquisitorSeru: - return "Seru"; - case Race::ServantOfShadow: - return "Servant of Shadow"; - case Race::SessiloidMount: - return "Sessiloid Mount"; - case Race::Shade: - case Race::Shade2: - case Race::Shade3: - return "Shade"; - case Race::KhatiSha: - return "Shadel"; - case Race::ShamblingMound: - return "Shambling Mound"; - case Race::Shark: - return "Shark"; - case Race::Shiknar: - return "Shik'Nar"; - case Race::Shiliskin: - return "Shiliskin"; - case Race::Ship: - return "Ship"; - case Race::ShipInABottle: - return "Ship in a Bottle"; - case Race::Shissar: - case Race::Shissar2: - return "Shissar"; - case Race::Shrieker: - return "Shrieker"; - case Race::Siren: - case Race::Siren2: - return "Siren"; - case Race::SkeletalHorse: - return "Skeletal Horse"; - case Race::Skeleton: - case Race::Skeleton2: - case Race::Skeleton3: - return "Skeleton"; - case Race::Skunk: - return "Skunk"; - case Race::Skystrider: - return "Skystrider"; - case Race::Plant2: - return "Small Plant"; - case Race::GiantSnake: - case Race::Snake: - return "Snake"; - case Race::SnakeElemental: - return "Snake Elemental"; - case Race::SnowDervish: - return "Snow Dervish"; - case Race::SnowRabbit: - return "Snow Rabbit"; - case Race::Sokokar: - return "Sokokar"; - case Race::SokokarMount: - return "Sokokar Mount"; - case Race::SokokarMount2: - return "Sokokar(w saddle)"; - case Race::SolusekRo: - case Race::SolusekRo2: - return "Solusek Ro"; - case Race::SolusekRoGuard: - return "Solusek Ro Guard"; - case Race::SonicWolf: - return "Sonic Wolf"; - case Race::SoulDevourer: - return "Soul Devourer"; - case Race::Spectre: - case Race::Spectre2: - return "Spectre"; - case Race::SpellParticle: - return "Spell Particle 1"; - case Race::Sphinx: - case Race::Sphinx2: - return "Sphinx"; - case Race::GiantSpider: - case Race::Spider: - return "Spider"; - case Race::SpiderEggSack: - return "Spider Egg Sack"; - case Race::SpiderMount: - return "Spider Mount"; - case Race::SpiderQueen: - return "Spider Queen"; - case Race::SpikeTrap: - return "Spike Trap"; - case Race::SpiritWolf: - return "Spirit Wolf"; - case Race::Sporali: - return "Sporali"; - case Race::StoneJug: - return "Stone Jug"; - case Race::StonePylon: - return "Stone Pylon"; - case Race::StoneRing: - return "Stone Ring"; - case Race::StoneWorker: - case Race::StoneWorker2: - return "Stone Worker"; - case Race::Stonegrabber: - return "Stonegrabber"; - case Race::Stonemite: - return "Stonemite"; - case Race::Stormrider: - return "Stormrider"; - case Race::Succubus: - return "Succubus"; - case Race::Succulent: - return "Succulent"; - case Race::SullonZek: - return "Sullon Zek"; - case Race::Shadel: - return "Sun Revenant"; - case Race::Sunflower: - return "Sunflower"; - case Race::Swinetor: - return "Swinetor"; - case Race::Swordfish: - return "Swordfish"; - case Race::Synarcana: - return "Synarcana"; - case Race::Table: - return "Table"; - case Race::FroglokTadpole: - return "Tadpole"; - case Race::Taelosian: - return "Taelosian"; - case Race::WineCask: - return "Tall Plant"; - case Race::TallonZek: - return "Tallon Zek"; - case Race::Taneth: - return "Taneth"; - case Race::TarewMarr: - return "Tarew Marr"; - case Race::Tegi: - return "Tegi"; - case Race::TeleportMan: - return "Teleport Man"; - case Race::TeleportationStand: - return "Teleportation Stand"; - case Race::Telmira: - return "Telmira"; - case Race::TentacleTerror: - case Race::TentacleTerror2: - return "Tentacle Terror"; - case Race::TerrisThule: - return "Terris Thule"; - case Race::TestObject: - return "Test Object"; - case Race::Rathe: - return "The Rathe"; - case Race::TribunalNew: - return "The Tribunal"; - case Race::ThoughtHorror: - return "Thought Horror"; - case Race::Tiger: - return "Tiger"; - case Race::TinSoldier: - return "Tin Soldier"; - case Race::Plant: - return "Toolbox"; - case Race::TopiaryLion: - return "Topiary Lion"; - case Race::TopiaryLionMount: - return "Topiary Lion Mount"; - case Race::Tormentor: - return "Tormentor"; - case Race::Totem: - case Race::Totem2: - return "Totem"; - case Race::Trakanon: - return "Trakanon"; - case Race::Tranquilion: - return "Tranquilion"; - case Race::Treant: - case Race::Treant2: - case Race::Treant3: - return "Treant"; - case Race::Tribunal: - return "Tribunal"; - case Race::Triumvirate: - return "Triumvirate"; - case Race::Troll: - case Race::GrobbCitizen: - case Race::TrollCrewMember: - case Race::PirateDeckhand: - case Race::BrokenSkullPirate: - return "Troll"; - case Race::TrollZombie: - return "Troll Zombie"; - case Race::Trusik: - return "Trusik"; - case Race::Tsetsian: - return "Tsetsian"; - case Race::Tumbleweed: - return "Tumbleweed"; - case Race::Tunare: - return "Tunare"; - case Race::Turepta: - return "Turepta"; - case Race::Ukun: - return "Ukun"; - case Race::Ulthork: - return "Ulthork"; - case Race::UndeadChokidai: - return "Undead Chokidai"; - case Race::UndeadFootman: - return "Undead Footman"; - case Race::UndeadFroglok: - return "Undead Froglok"; - case Race::UndeadIksar: - return "Undead Iksar"; - case Race::UndeadKnight: - return "Undead Knight"; - case Race::UndeadSarnak: - return "Undead Sarnak"; - case Race::UndeadVeksar: - return "Undead Veksar"; - case Race::Underbulk: - return "Underbulk"; - case Race::Unicorn: - return "Unicorn"; - case Race::Doug: - case Race::MinorIllusion: - case Race::Tree: - case Race::Unknown: - case Race::Unknown2: - case Race::Unknown3: - case Race::Unknown4: - return "UNKNOWN RACE"; - case Race::VahShir: - case Race::VahShirKing: - return "Vah Shir"; - case Race::VahShirSkeleton: - return "Vah Shir Skeleton"; - case Race::VallonZek: - return "Vallon Zek"; - case Race::Valorian: - case Race::Valorian2: - return "Valorian"; - case Race::Vampire: - case Race::ElfVampire: - case Race::Vampire2: - case Race::VampireVolatalis: - case Race::UndeadVampire: - case Race::Vampire3: - case Race::MasterVampire: - case Race::Vampire4: - return "Vampire"; - case Race::Vase: - return "Vase"; - case Race::Vegerog: - return "Vegerog"; - case Race::Veksar: - case Race::Veksar2: - case Race::Veksar3: - return "Veksar"; - case Race::VenrilSathir: - return "Venril Sathir"; - case Race::VineMaw: - return "Vine Maw"; - case Race::Wagon: - return "Wagon"; - case Race::Walrus: - return "Walrus"; - case Race::WarBoar: - case Race::WarBoar2: - return "War Boar"; - case Race::WarWraith: - return "War Wraith"; - case Race::Wasp: - return "Wasp"; - case Race::WaterElemental: - case Race::WaterElemental2: - return "Water Elemental"; - case Race::WaterMephit: - return "Water Mephit"; - case Race::WaterSpout: - return "Water Spout"; - case Race::WeaponRack: - case Race::WeaponRack2: - return "Weapon Rack"; - case Race::Web: - return "Web"; - case Race::WeddingAltar: - return "Wedding Altar"; - case Race::WeddingArbor: - return "Wedding Arbor"; - case Race::WeddingFlowers: - return "Wedding Flowers"; - case Race::Wereorc: - return "Wereorc"; - case Race::Werewolf: - case Race::Werewolf2: - case Race::Werewolf3: - return "Werewolf"; - case Race::WetfangMinnow: - return "Wetfang Minnow"; - case Race::Whirligig: - return "Whirligig"; - case Race::WickerBasket: - return "Wicker Basket"; - case Race::Wisp: - return "Will - O - Wisp"; - case Race::StoneJug2: - case Race::WineCask2: - return "Wine Cask"; - case Race::Witheran: - case Race::Witheran2: - return "Witheran"; - case Race::Wolf: - case Race::WolfElemental: - case Race::Wolf2: - return "Wolf"; - case Race::WoodElf: - return "Wood Elf"; - case Race::Worg: - case Race::Worg2: - return "Worg"; - case Race::Worm: - return "Worm"; - case Race::Wretch: - return "Wretch"; - case Race::Wrulon: - case Race::Wrulon2: - return "Wrulon"; - case Race::Wurm: - case Race::Wurm2: - return "Wurm"; - case Race::WurmMount: - return "Wurm Mount"; - case Race::Wyvern: - case Race::Wyvern2: - return "Wyvern"; - case Race::Xalgoz: - return "Xalgoz"; - case Race::Xaric: - return "Xaric the Unspoken"; - case Race::Xegony: - return "Xegony"; - case Race::Yakkar: - return "Yakkar"; - case Race::Yeti: - return "Yeti"; - case Race::Zebuxoruk: - return "Zebuxoruk"; - case Race::ZebuxoruksCage: - return "Zebuxoruk's Cage"; - case Race::Zelniak: - return "Zelniak"; - case Race::Zombie: - case Race::Zombie2: - return "Zombie"; - default: - return "UNKNOWN RACE"; + case Race::Abhorrent: + return "Abhorrent"; + case Race::AirElemental: + case Race::AirElemental2: + return "Air Elemental"; + case Race::AirMephit: + return "Air Mephit"; + case Race::Akhevan: + case Race::Akheva: + return "Akheva"; + case Race::Alaran: + return "Alaran"; + case Race::AlaranGhost: + return "Alaran Ghost"; + case Race::AlaranSentryStone: + return "Alaran Sentry Stone"; + case Race::Alligator: + case Race::Alligator2: + return "Alligator"; + case Race::Denizen: + case Race::Amygdalan: + return "Amygdalan"; + case Race::Aneuk: + return "Aneuk"; + case Race::AnimatedArmor: + return "Animated Armor"; + case Race::AnimatedHand: + return "Animated Hand"; + case Race::AnimatedStatue: + case Race::AnimatedStatue2: + return "Animated Statue"; + case Race::Apexus: + return "Apexus"; + case Race::Arachnid: + return "Arachnid"; + case Race::ArcanistOfHate: + return "Arcanist of Hate"; + case Race::Armadillo: + return "Armadillo"; + case Race::ArmorRack: + return "Armor Rack"; + case Race::Aviak: + case Race::Aviak2: + return "Aviak"; + case Race::AviakPullAlong: + return "Aviak Pull Along"; + case Race::AyonaeRo: + return "Ayonae Ro"; + case Race::Banner: + case Race::Banner2: + case Race::Banner3: + case Race::Banner4: + case Race::Banner5: + case Race::Banner6: + case Race::Banner7: + return "Banner"; + case Race::TenthAnniversaryBanner: + return "10th Anniversary Banner"; + case Race::Banshee: + case Race::Banshee2: + case Race::Banshee3: + return "Banshee"; + case Race::Barbarian: + case Race::HalasCitizen: + return "Barbarian"; + case Race::Barrel: + return "Barrel"; + case Race::UndeadBoat: + return "Barrel Barge Ship"; + case Race::Basilisk: + return "Basilisk"; + case Race::GiantBat: + case Race::Bat: + case Race::Bat2: + return "Bat"; + case Race::Bazu: + return "Bazu"; + case Race::Bear: + case Race::Bear2: + case Race::Bear3: + return "Bear"; + case Race::BearMount: + return "Bear Mount"; + case Race::BearTrap: + return "Bear Trap"; + case Race::Beetle: + case Race::Beetle2: + case Race::Beetle3: + return "Beetle"; + case Race::HumanBeggar: + return "Beggar"; + case Race::Bellikos: + return "Bellikos"; + case Race::Bertoxxulous: + case Race::BertoxxulousNew: + return "Bertoxxulous"; + case Race::Bixie: + case Race::Bixie2: + return "Bixie"; + case Race::BlimpShip: + return "Blimp Ship"; + case Race::BlindDreamer: + return "Blind Dreamer"; + case Race::BloodRaven: + return "Blood Raven"; + case Race::Boat: + case Race::Boat2: + return "Boat"; + case Race::Bolvirk: + return "Bolvirk"; + case Race::BoneGolem: + case Race::BoneGolem2: + return "Bone Golem"; + case Race::Bones: + return "Bones"; + case Race::BookDervish: + return "Book Dervish"; + case Race::Portal: + return "BoT Portal"; + case Race::Boulder: + return "Boulder"; + case Race::Box: + return "Box"; + case Race::Braxi: + return "Braxi"; + case Race::BraxiMount: + return "Braxi Mount"; + case Race::Brell: + return "Brell"; + case Race::BrellsFirstCreation: + return "Brell's First Creation"; + case Race::Bristlebane: + return "Bristlebane"; + case Race::BrokenClockwork: + return "Broken Clockwork"; + case Race::Brontotherium: + return "Brontotherium"; + case Race::Brownie: + case Race::Brownie2: + return "Brownie"; + case Race::Bubonian: + return "Bubonian"; + case Race::BubonianUnderling: + return "Bubonian Underling"; + case Race::Burynai: + case Race::Burynai2: + return "Burynai"; + case Race::TenthAnniversaryCake: + return "10th Anniversary Cake"; + case Race::Campfire: + return "Campfire"; + case Race::CarrierHand: + return "Carrier Hand"; + case Race::Cat: + return "Cat"; + case Race::CazicThule: + case Race::CazicThule2: + return "Cazic Thule"; + case Race::Centaur: + case Race::Centaur2: + return "Centaur"; + case Race::Chest: + case Race::Chest2: + case Race::Chest3: + return "Chest"; + case Race::Chimera: + case Race::Chimera2: + return "Chimera"; + case Race::Chokidai: + return "Chokidai"; + case Race::Clam: + return "Clam"; + case Race::CliknarMount: + return "Cliknar Mount"; + case Race::CliknarQueen: + return "Cliknar Queen"; + case Race::CliknarSoldier: + return "Cliknar Soldier"; + case Race::CliknarWorker: + return "Cliknar Worker"; + case Race::ClockworkBeetle: + return "Clockwork Beetle"; + case Race::ClockworkBoar: + return "Clockwork Boar"; + case Race::ClockworkBomb: + return "Clockwork Bomb"; + case Race::ClockworkBrain: + return "Clockwork Brain"; + case Race::ClockworkGnome: + return "Clockwork Gnome"; + case Race::ClockworkGolem: + return "Clockwork Golem"; + case Race::ClockworkGuardian: + return "Clockwork Guardian"; + case Race::Cockatrice: + return "Cockatrice"; + case Race::Coffin: + case Race::Coffin2: + return "Coffin"; + case Race::CoinPurse: + return "Coin Purse"; + case Race::Coldain: + case Race::Coldain2: + case Race::Coldain3: + return "Coldain"; + case Race::Coral: + return "Coral"; + case Race::Corathus: + return "Corathus"; + case Race::Crab: + return "Crab"; + case Race::Cragbeast: + return "Cragbeast"; + case Race::Cragslither: + return "Cragslither"; + case Race::Crocodile: + return "Crocodile"; + case Race::Crystal: + return "Crystal"; + case Race::CrystalShard: + return "Crystal Shard"; + case Race::CrystalSphere: + return "Crystal Sphere"; + case Race::CrystalSpider: + return "Crystal Spider"; + case Race::CrystalskinAmbuloid: + return "Crystalskin Ambuloid"; + case Race::CrystalskinSessiloid: + return "Crystalskin Sessiloid"; + case Race::DaisyMan: + return "Daisy Man"; + case Race::DarkElf: + case Race::NeriakCitizen: + return "Dark Elf"; + case Race::DarkLord: + return "Dark Lord"; + case Race::DemiLich: + return "Demi Lich"; + case Race::DemonVulture: + return "Demon Vulture"; + case Race::Dervish: + case Race::Dervish2: + case Race::Dervish3: + case Race::Dervish4: + return "Dervish"; + case Race::Dervish5: + return "Dervish(Ver. 5)"; + case Race::Dervish6: + return "Dervish(Ver. 6)"; + case Race::Devourer: + return "Devourer"; + case Race::DireWolf: + return "Dire Wolf"; + case Race::DiscordShip: + return "Discord Ship"; + case Race::Discordling: + return "Discordling"; + case Race::DiseasedFiend: + return "Diseased Fiend"; + case Race::Djinn: + return "Djinn"; + case Race::Drachnid: + case Race::Drachnid2: + return "Drachnid"; + case Race::DrachnidCocoon: + return "Drachnid Cocoon"; + case Race::Dracolich: + return "Dracolich"; + case Race::Draglock: + return "Draglock"; + case Race::LavaDragon: + case Race::DragonSkeleton: + case Race::WaterDragon: + case Race::VeliousDragon: + case Race::ClockworkDragon: + case Race::BlackAndWhiteDragon: + case Race::GhostDragon: + case Race::PrismaticDragon: + case Race::Quarm: + case Race::Dragon: + case Race::Dragon2: + case Race::Dragon3: + case Race::Dragon4: + case Race::Dragon5: + case Race::Dragon6: + case Race::Dragon7: + return "Dragon"; + case Race::DragonBones: + return "Dragon Bones"; + case Race::DragonEgg: + return "Dragon Egg"; + case Race::DragonStatue: + return "Dragon Statue"; + case Race::Dragorn: + return "Dragorn"; + case Race::DragornBox: + return "Dragorn Box"; + case Race::Drake: + case Race::Drake2: + case Race::Drake3: + return "Drake"; + case Race::Drakkin: + return "Drakkin"; + case Race::Drixie: + return "Drixie"; + case Race::Drogmor: + return "Drogmore"; + case Race::Drolvarg: + return "Drolvarg"; + case Race::Dryad: + return "Dryad"; + case Race::Dwarf: + case Race::KaladimCitizen: + return "Dwarf"; + case Race::DynamiteKeg: + return "Dynamite Keg"; + case Race::Dynleth: + return "Dyn'Leth"; + case Race::EarthElemental: + case Race::EarthElemental2: + return "Earth Elemental"; + case Race::EarthMephit: + return "Earth Mephit"; + case Race::GiantEel: + return "Eel"; + case Race::Efreeti: + case Race::Efreeti2: + return "Efreeti"; + case Race::Elddar: + return "Elddar"; + case Race::Elemental: + return "Elemental"; + case Race::ElkHead: + return "Elk Head"; + case Race::ElvenBoat: + return "Elven Boat"; + case Race::ElvenGhost: + return "Elven Ghost"; + case Race::EnchantedArmor: + return "Enchanted Armor"; + case Race::Erollisi: + return "Erollisi"; + case Race::Erudite: + case Race::EruditeCitizen: + case Race::Erudite2: + return "Erudite"; + case Race::EvanTest: + return "Evan Test"; + case Race::EvilEye: + case Race::EvilEye2: + case Race::EvilEye3: + return "Evil Eye"; + case Race::Exoskeleton: + return "Exoskeleton"; + case Race::ExplosiveCart: + return "Explosive Cart"; + case Race::EyeOfZomm: + return "Eye"; + case Race::Fairy: + case Race::Fairy2: + return "Fairy"; + case Race::FallenKnight: + return "Fallen Knight"; + case Race::Faun: + return "Faun"; + case Race::FayDrake: + return "Fay Drake"; + case Race::FenninRo: + return "Fennin Ro"; + case Race::Feran: + return "Feran"; + case Race::FeranMount: + return "Feran Mount"; + case Race::Fiend: + return "Fiend"; + case Race::FireElemental: + case Race::FireElemental2: + return "Fire Elemental"; + case Race::FireMephit: + return "Fire Mephit"; + case Race::Fish: + case Race::KunarkFish: + return "Fish"; + case Race::Flag: + return "Flag"; + case Race::FloatingIsland: + return "Floating Island"; + case Race::FloatingSkull: + return "Floating Skull"; + case Race::FloatingTower: + return "Floating Tower"; + case Race::Fly: + return "Fly"; + case Race::FlyingCarpet: + return "Flying Carpet"; + case Race::ForestGiant2: + return "Forest Giant"; + case Race::Frog: + case Race::Frog2: + return "Frog"; + case Race::Froglok: + case Race::FroglokGhoul: + case Race::Froglok2: + return "Froglok"; + case Race::FroglokGhost: + return "Froglok Ghost"; + case Race::FroglokSkeleton: + return "Froglok Skeleton"; + case Race::FungalFiend: + return "Fungal Fiend"; + case Race::FungusPatch: + return "Fungus Patch"; + case Race::Fungusman: + return "Fungusman"; + case Race::Galorian: + return "Galorian"; + case Race::Gargoyle: + case Race::Gargoyle2: + return "Gargoyle"; + case Race::Gasbag: + return "Gasbag"; + case Race::GelatinousCube: + case Race::GelatinousCube2: + return "Gelatinous Cube"; + case Race::Gelidran: + return "Gelidran"; + case Race::Genari: + return "Genari"; + case Race::Geonid: + return "Geonid"; + case Race::Ghost: + case Race::DwarfGhost: + case Race::EruditeGhost: + case Race::PirateGhost: + return "Ghost"; + case Race::GhostShip: + case Race::GhostShip2: + return "Ghost Ship"; + case Race::Ghoul: + case Race::Ghoul2: + return "Ghoul"; + case Race::Giant: + case Race::ForestGiant: + case Race::FrostGiant: + case Race::StormGiant: + case Race::EarthGolem: + case Race::IronGolem: + case Race::StormGolem: + case Race::AirGolem: + case Race::WoodGolem: + case Race::FireGolem: + case Race::WaterGolem: + case Race::Giant2: + case Race::Giant3: + return "Giant"; + case Race::GiantClockwork: + return "Giant Clockwork"; + case Race::Giant4: + return "Giant(Rallosian mats)"; + case Race::GiantShade: + return "Giant Shade"; + case Race::Gigyn: + return "Gigyn"; + case Race::GingerbreadMan: + return "Gingerbread Man"; + case Race::Girplan: + return "Girplan"; + case Race::Gnoll: + case Race::Gnoll2: + case Race::Gnoll3: + return "Gnoll"; + case Race::Gnome: + return "Gnome"; + case Race::Gnomework: + return "Gnomework"; + case Race::GnomishBalloon: + return "Gnomish Balloon"; + case Race::GnomishBoat: + return "Gnomish Boat"; + case Race::GnomishHoveringTransport: + return "Gnomish Hovering Transport"; + case Race::GnomishRocketPack: + return "Gnomish Rocket Pack"; + case Race::Goblin: + case Race::Bloodgill: + case Race::KunarkGoblin: + case Race::NewGoblin: + case Race::Goblin2: + return "Goblin"; + case Race::Luclin2: + return "God - Luclin(Ver. 2)"; + case Race::Luclin3: + return "God - Luclin(Ver. 3)"; + case Race::Luclin4: + return "God - Luclin(Ver. 4)"; + case Race::GodOfDiscord: + return "God of Discord"; + case Race::Golem: + case Race::Golem2: + return "Golem"; + case Race::Goo: + case Race::Goo2: + case Race::Goo3: + case Race::Goo4: + return "Goo"; + case Race::Goral: + return "Goral"; + case Race::GoralMount: + return "Goral Mount"; + case Race::Gorgon: + return "Gorgon"; + case Race::Gorilla: + case Race::Gorilla2: + return "Gorilla"; + case Race::GrandfatherClock: + return "Grandfather Clock"; + case Race::GrekenYoung: + return "Greken - Young"; + case Race::GrekenYoungAdult: + return "Greken - Young Adult"; + case Race::Grendlaen: + return "Grendlaen"; + case Race::GriegVeneficus: + return "Grieg Veneficus"; + case Race::Griffin: + case Race::Griffin2: + return "Griffin"; + case Race::Grimling: + return "Grimling"; + case Race::GroundShaker: + return "Ground Shaker"; + case Race::FreeportGuard: + case Race::Felguard: + case Race::Fayguard: + case Race::VahShirGuard: + return "Guard"; + case Race::GuardOfJustice: + return "Guard of Justice"; + case Race::GuardianCpu: + return "Guardian CPU"; + case Race::Hadal: + return "Hadal"; + case Race::Hag: + return "Hag"; + case Race::HalfElf: + return "Half Elf"; + case Race::Halfling: + case Race::RivervaleCitizen: + return "Halfling"; + case Race::Harpy: + case Race::Harpy2: + return "Harpy"; + case Race::HighElf: + return "High Elf"; + case Race::Hippogriff: + return "Hippogriff"; + case Race::Holgresh: + case Race::Holgresh2: + return "Holgresh"; + case Race::HoneyPot: + return "Honey Pot"; + case Race::Horse: + case Race::Horse2: + case Race::Horse3: + return "Horse"; + case Race::HoveringPlatform: + return "Hovering Platform"; + case Race::Hraquis: + return "Hraquis"; + case Race::Human: + case Race::HighpassCitizen: + case Race::QeynosCitizen: + case Race::Human2: + return "Human"; + case Race::HumanGhost: + return "Human Ghost"; + case Race::Huvul: + return "Huvul"; + case Race::HydraCrystal: + return "Hydra Crystal"; + case Race::HydraMount: + return "Hydra Mount"; + case Race::Hydra: + return "Hydra NPC"; + case Race::Hynid: + return "Hynid"; + case Race::IceSpectre: + return "Ice Spectre"; + case Race::Ikaav: + return "Ikaav"; + case Race::Iksar: + case Race::IksarCitizen: + return "Iksar"; + case Race::IksarGhost: + return "Iksar Ghost"; + case Race::IksarGolem: + return "Iksar Golem"; + case Race::IksarSkeleton: + return "Iksar Skeleton"; + case Race::IksarSpirit: + return "Iksar Spirit"; + case Race::Imp: + return "Imp"; + case Race::Innoruuk: + return "Innoruuk"; + case Race::Insect: + return "Insect"; + case Race::InteractiveObject: + return "Interactive Object"; + case Race::InvisibleMan: + case Race::InvisibleMan2: + case Race::InvisibleMan3: + return "Invisible Man"; + case Race::InvisibleManOfZomm: + return "Invisible Man of Zomm"; + case Race::Ixt: + return "Ixt"; + case Race::Jokester: + return "Jokester"; + case Race::JumJumBucket: + return "Jum Jum Bucket"; + case Race::JunkBeast: + return "Junk Beast"; + case Race::Kangon: + return "Kangon"; + case Race::KangonMount: + return "Kangon Mount"; + case Race::Karana: + return "Karana"; + case Race::PhinigelAutropos: + case Race::Kedge: + return "Kedge"; + case Race::Kerran: + case Race::Kerran2: + return "Kerran"; + case Race::Kirin: + case Race::Kirin2: + return "Kirin"; + case Race::KnightOfHate: + return "Knight of Hate"; + case Race::KnightOfPestilence: + return "Knight of Pestilence"; + case Race::Kobold: + case Race::Kobold2: + return "Kobold"; + case Race::Kraken: + return "Kraken"; + case Race::Kyv: + return "Kyv"; + case Race::Launch: + return "Launch"; + case Race::LavaRock: + return "Lava Rock"; + case Race::LavaSpider: + return "Lava Spider"; + case Race::LavaSpiderQueen: + return "Lava Spider Queen"; + case Race::Leech: + return "Leech"; + case Race::Lepertoloth: + return "Lepertoloth"; + case Race::Lightcrawler: + return "Lightcrawler"; + case Race::LightingWarrior: + return "Lightning Warrior"; + case Race::Lion: + return "Lion"; + case Race::LizardMan: + return "Lizard Man"; + case Race::Luclin: + return "Luclin"; + case Race::Luggald: + case Race::Luggald2: + return "Luggald"; + case Race::Luggald3: + return "Luggalds"; + case Race::Malarian: + return "Malarian"; + case Race::Mammoth: + case Race::Mammoth2: + return "Mammoth"; + case Race::ManEatingPlant: + return "Man - Eating Plant"; + case Race::Mansion: + return "Mansion"; + case Race::Manticore: + return "Manticore"; + case Race::Unknown5: + return "Mantrap"; + case Race::Marionette: + return "Marionette"; + case Race::Mastruq: + return "Mastruq"; + case Race::MataMuram: + return "Mata Muram"; + case Race::Toolbox: + return "Medium Plant"; + case Race::Mephit: + return "Mephit"; + case Race::MerchantShip: + return "Merchant Ship"; + case Race::Mermaid: + return "Mermaid"; + case Race::Mimic: + return "Mimic"; + case Race::MiniPom: + return "Mini POM"; + case Race::Minotaur: + case Race::Minotaur2: + case Race::Minotaur3: + case Race::Minotaur4: + return "Minotaur"; + case Race::MithanielMarr: + return "Mithaniel Marr"; + case Race::MorellThule: + return "Morell Thule"; + case Race::Mosquito: + return "Mosquito"; + case Race::MouthOfInsanity: + return "Mouth of Insanity"; + case Race::Muddite: + return "Muddite"; + case Race::Mummy: + return "Mummy"; + case Race::MuramiteArmorPile: + return "Muramite Armor Pile"; + case Race::Murkglider: + return "Murkglider"; + case Race::MurkgliderEggSack: + return "Murkglider Egg Sac"; + case Race::Mutna: + return "Mutna"; + case Race::Nekhon: + return "Nekhon"; + case Race::Netherbian: + return "Netherbian"; + case Race::Nightmare: + return "Nightmare"; + case Race::NightmareGargoyle: + return "Nightmare Gargoyle"; + case Race::NightmareGoblin: + return "Nightmare Goblin"; + case Race::NightmareMephit: + return "Nightmare Mephit"; + case Race::Unicorn2: + case Race::Unicorn3: + return "Nightmare / Unicorn"; + case Race::NightmareWraith: + return "Nightmare Wraith"; + case Race::Nihil: + return "Nihil"; + case Race::Nilborien: + return "Nilborien"; + case Race::Noc: + return "Noc"; + case Race::Nymph: + return "Nymph"; + case Race::Ogre: + case Race::OggokCitizen: + return "Ogre"; + case Race::Ogre2: + return "Ogre NPC - Male"; + case Race::Orb: + return "Orb"; + case Race::Orc: + case Race::Orc2: + return "Orc"; + case Race::Othmir: + return "Othmir"; + case Race::Owlbear: + return "Owlbear"; + case Race::ParasiticScavenger: + return "Parasitic Scavenger"; + case Race::Pegasus: + case Race::Pegasus2: + case Race::Pegasus3: + return "Pegasus"; + case Race::Phoenix: + return "Phoenix"; + case Race::Piranha: + return "Piranha"; + case Race::OneArmedPirate: + case Race::SpiritmasterNadox: + case Race::BrokenSkullTaskmaster: + case Race::GnomePirate: + case Race::DarkElfPirate: + case Race::OgrePirate: + case Race::HumanPirate: + case Race::EruditePirate: + return "Pirate"; + case Race::PirateShip: + return "Pirate Ship"; + case Race::Pixie: + return "Pixie"; + case Race::PoisonFrog: + return "Poison Frog"; + case Race::Portal2: + return "Portal"; + case Race::PowderKeg: + return "Powder Keg"; + case Race::PressurePlate: + return "Pressure Plate"; + case Race::PufferSpore: + return "Puffer Spore"; + case Race::Puma: + case Race::Puma2: + case Race::Puma3: + return "Puma"; + case Race::Pusling: + return "Pusling"; + case Race::Pyrilen: + return "Pyrilen"; + case Race::Ratuk: + return "Ra`tuk"; + case Race::Rabbit: + return "Rabbit"; + case Race::StatueOfRallosZek: + case Race::NewRallosZek: + return "Rallos Zek"; + case Race::RallosOgre: + return "Rallos Zek Minion"; + case Race::Raptor: + case Race::Raptor2: + return "Raptor"; + case Race::RaptorMount: + return "Raptor Mount"; + case Race::GiantRat: + case Race::Rat: + return "Rat"; + case Race::RatMount: + return "Rat Mount"; + case Race::Ratman: + case Race::Ratman2: + return "Ratman"; + case Race::ReanimatedHand: + return "Reanimated Hand"; + case Race::Recuso: + return "Recuso"; + case Race::RegenerationPool: + return "Regeneration Pool"; + case Race::RelicCase: + return "Relic case "; + case Race::RhinoBeetle: + return "Rhino Beetle"; + case Race::Rhinoceros: + return "Rhinoceros"; + case Race::RobocopterOfZomm: + return "Robocopter of Zomm"; + case Race::RockPile: + return "Rock Pile"; + case Race::Rockhopper: + return "Rockhopper"; + case Race::RonnieTest: + return "Ronnie Test"; + case Race::RootTentacle: + return "Root Tentacle"; + case Race::RotDogMount: + return "Rot Dog Mount"; + case Race::RotDog: + return "Rotdog"; + case Race::Rotocopter: + return "Rotocopter"; + case Race::Rowboat: + return "Rowboat"; + case Race::RoyalGuard: + return "Royal Guard"; + case Race::RujarkianOrc: + case Race::MasterOrc: + return "Rujarkian Orc"; + case Race::RunedOrb: + return "Runed Orb"; + case Race::RunicSymbol: + return "Runic Symbol"; + case Race::Sabertooth: + return "Saber - toothed Cat"; + case Race::SaltpetterBomb: + return "Saltpetter Bomb"; + case Race::SandElf: + return "Sand Elf"; + case Race::Sandman: + return "Sandman"; + case Race::Sarnak: + case Race::Sarnak2: + return "Sarnak"; + case Race::SarnakGolem: + return "Sarnak Golem"; + case Race::SarnakSpirit: + return "Sarnak Spirit"; + case Race::Saryrn: + return "Saryrn"; + case Race::Satyr: + return "Satyr"; + case Race::ScaledWolf: + return "Scaled Wolf"; + case Race::Scarecrow: + case Race::Scarecrow2: + return "Scarecrow"; + case Race::ScarletCheetah: + return "Scarlet Cheetah"; + case Race::ScleraMount: + return "Sclera Mount"; + case Race::Scorpion: + case Race::IksarScorpion: + case Race::Scorpion2: + return "Scorpion"; + case Race::Scrykin: + return "Scrykin"; + case Race::SeaTurtle: + return "Sea Turtle"; + case Race::SeaHorse: + return "Seahorse"; + case Race::Selyrah: + return "Selyrah"; + case Race::SelyrahMount: + return "Selyrah Mount"; + case Race::LordInquisitorSeru: + return "Seru"; + case Race::ServantOfShadow: + return "Servant of Shadow"; + case Race::SessiloidMount: + return "Sessiloid Mount"; + case Race::Shade: + case Race::Shade2: + case Race::Shade3: + return "Shade"; + case Race::KhatiSha: + return "Shadel"; + case Race::ShamblingMound: + return "Shambling Mound"; + case Race::Shark: + return "Shark"; + case Race::Shiknar: + return "Shik'Nar"; + case Race::Shiliskin: + return "Shiliskin"; + case Race::Ship: + return "Ship"; + case Race::ShipInABottle: + return "Ship in a Bottle"; + case Race::Shissar: + case Race::Shissar2: + return "Shissar"; + case Race::Shrieker: + return "Shrieker"; + case Race::Siren: + case Race::Siren2: + return "Siren"; + case Race::SkeletalHorse: + return "Skeletal Horse"; + case Race::Skeleton: + case Race::Skeleton2: + case Race::Skeleton3: + return "Skeleton"; + case Race::Skunk: + return "Skunk"; + case Race::Skystrider: + return "Skystrider"; + case Race::Plant2: + return "Small Plant"; + case Race::GiantSnake: + case Race::Snake: + return "Snake"; + case Race::SnakeElemental: + return "Snake Elemental"; + case Race::SnowDervish: + return "Snow Dervish"; + case Race::SnowRabbit: + return "Snow Rabbit"; + case Race::Sokokar: + return "Sokokar"; + case Race::SokokarMount: + return "Sokokar Mount"; + case Race::SokokarMount2: + return "Sokokar(w saddle)"; + case Race::SolusekRo: + case Race::SolusekRo2: + return "Solusek Ro"; + case Race::SolusekRoGuard: + return "Solusek Ro Guard"; + case Race::SonicWolf: + return "Sonic Wolf"; + case Race::SoulDevourer: + return "Soul Devourer"; + case Race::Spectre: + case Race::Spectre2: + return "Spectre"; + case Race::SpellParticle: + return "Spell Particle 1"; + case Race::Sphinx: + case Race::Sphinx2: + return "Sphinx"; + case Race::GiantSpider: + case Race::Spider: + return "Spider"; + case Race::SpiderEggSack: + return "Spider Egg Sack"; + case Race::SpiderMount: + return "Spider Mount"; + case Race::SpiderQueen: + return "Spider Queen"; + case Race::SpikeTrap: + return "Spike Trap"; + case Race::SpiritWolf: + return "Spirit Wolf"; + case Race::Sporali: + return "Sporali"; + case Race::StoneJug: + return "Stone Jug"; + case Race::StonePylon: + return "Stone Pylon"; + case Race::StoneRing: + return "Stone Ring"; + case Race::StoneWorker: + case Race::StoneWorker2: + return "Stone Worker"; + case Race::Stonegrabber: + return "Stonegrabber"; + case Race::Stonemite: + return "Stonemite"; + case Race::Stormrider: + return "Stormrider"; + case Race::Succubus: + return "Succubus"; + case Race::Succulent: + return "Succulent"; + case Race::SullonZek: + return "Sullon Zek"; + case Race::Shadel: + return "Sun Revenant"; + case Race::Sunflower: + return "Sunflower"; + case Race::Swinetor: + return "Swinetor"; + case Race::Swordfish: + return "Swordfish"; + case Race::Synarcana: + return "Synarcana"; + case Race::Table: + return "Table"; + case Race::FroglokTadpole: + return "Tadpole"; + case Race::Taelosian: + return "Taelosian"; + case Race::WineCask: + return "Tall Plant"; + case Race::TallonZek: + return "Tallon Zek"; + case Race::Taneth: + return "Taneth"; + case Race::TarewMarr: + return "Tarew Marr"; + case Race::Tegi: + return "Tegi"; + case Race::TeleportMan: + return "Teleport Man"; + case Race::TeleportationStand: + return "Teleportation Stand"; + case Race::Telmira: + return "Telmira"; + case Race::TentacleTerror: + case Race::TentacleTerror2: + return "Tentacle Terror"; + case Race::TerrisThule: + return "Terris Thule"; + case Race::TestObject: + return "Test Object"; + case Race::Rathe: + return "The Rathe"; + case Race::TribunalNew: + return "The Tribunal"; + case Race::ThoughtHorror: + return "Thought Horror"; + case Race::Tiger: + return "Tiger"; + case Race::TinSoldier: + return "Tin Soldier"; + case Race::Plant: + return "Toolbox"; + case Race::TopiaryLion: + return "Topiary Lion"; + case Race::TopiaryLionMount: + return "Topiary Lion Mount"; + case Race::Tormentor: + return "Tormentor"; + case Race::Totem: + case Race::Totem2: + return "Totem"; + case Race::Trakanon: + return "Trakanon"; + case Race::Tranquilion: + return "Tranquilion"; + case Race::Treant: + case Race::Treant2: + case Race::Treant3: + return "Treant"; + case Race::Tribunal: + return "Tribunal"; + case Race::Triumvirate: + return "Triumvirate"; + case Race::Troll: + case Race::GrobbCitizen: + case Race::TrollCrewMember: + case Race::PirateDeckhand: + case Race::BrokenSkullPirate: + return "Troll"; + case Race::TrollZombie: + return "Troll Zombie"; + case Race::Trusik: + return "Trusik"; + case Race::Tsetsian: + return "Tsetsian"; + case Race::Tumbleweed: + return "Tumbleweed"; + case Race::Tunare: + return "Tunare"; + case Race::Turepta: + return "Turepta"; + case Race::Ukun: + return "Ukun"; + case Race::Ulthork: + return "Ulthork"; + case Race::UndeadChokidai: + return "Undead Chokidai"; + case Race::UndeadFootman: + return "Undead Footman"; + case Race::UndeadFroglok: + return "Undead Froglok"; + case Race::UndeadIksar: + return "Undead Iksar"; + case Race::UndeadKnight: + return "Undead Knight"; + case Race::UndeadSarnak: + return "Undead Sarnak"; + case Race::UndeadVeksar: + return "Undead Veksar"; + case Race::Underbulk: + return "Underbulk"; + case Race::Unicorn: + return "Unicorn"; + case Race::Doug: + case Race::MinorIllusion: + case Race::Tree: + case Race::Unknown: + case Race::Unknown2: + case Race::Unknown3: + case Race::Unknown4: + return "UNKNOWN RACE"; + case Race::VahShir: + case Race::VahShirKing: + return "Vah Shir"; + case Race::VahShirSkeleton: + return "Vah Shir Skeleton"; + case Race::VallonZek: + return "Vallon Zek"; + case Race::Valorian: + case Race::Valorian2: + return "Valorian"; + case Race::Vampire: + case Race::ElfVampire: + case Race::Vampire2: + case Race::VampireVolatalis: + case Race::UndeadVampire: + case Race::Vampire3: + case Race::MasterVampire: + case Race::Vampire4: + return "Vampire"; + case Race::Vase: + return "Vase"; + case Race::Vegerog: + return "Vegerog"; + case Race::Veksar: + case Race::Veksar2: + case Race::Veksar3: + return "Veksar"; + case Race::VenrilSathir: + return "Venril Sathir"; + case Race::VineMaw: + return "Vine Maw"; + case Race::Wagon: + return "Wagon"; + case Race::Walrus: + return "Walrus"; + case Race::WarBoar: + case Race::WarBoar2: + return "War Boar"; + case Race::WarWraith: + return "War Wraith"; + case Race::Wasp: + return "Wasp"; + case Race::WaterElemental: + case Race::WaterElemental2: + return "Water Elemental"; + case Race::WaterMephit: + return "Water Mephit"; + case Race::WaterSpout: + return "Water Spout"; + case Race::WeaponRack: + case Race::WeaponRack2: + return "Weapon Rack"; + case Race::Web: + return "Web"; + case Race::WeddingAltar: + return "Wedding Altar"; + case Race::WeddingArbor: + return "Wedding Arbor"; + case Race::WeddingFlowers: + return "Wedding Flowers"; + case Race::Wereorc: + return "Wereorc"; + case Race::Werewolf: + case Race::Werewolf2: + case Race::Werewolf3: + return "Werewolf"; + case Race::WetfangMinnow: + return "Wetfang Minnow"; + case Race::Whirligig: + return "Whirligig"; + case Race::WickerBasket: + return "Wicker Basket"; + case Race::Wisp: + return "Will - O - Wisp"; + case Race::StoneJug2: + case Race::WineCask2: + return "Wine Cask"; + case Race::Witheran: + case Race::Witheran2: + return "Witheran"; + case Race::Wolf: + case Race::WolfElemental: + case Race::Wolf2: + return "Wolf"; + case Race::WoodElf: + return "Wood Elf"; + case Race::Worg: + case Race::Worg2: + return "Worg"; + case Race::Worm: + return "Worm"; + case Race::Wretch: + return "Wretch"; + case Race::Wrulon: + case Race::Wrulon2: + return "Wrulon"; + case Race::Wurm: + case Race::Wurm2: + return "Wurm"; + case Race::WurmMount: + return "Wurm Mount"; + case Race::Wyvern: + case Race::Wyvern2: + return "Wyvern"; + case Race::Xalgoz: + return "Xalgoz"; + case Race::Xaric: + return "Xaric the Unspoken"; + case Race::Xegony: + return "Xegony"; + case Race::Yakkar: + return "Yakkar"; + case Race::Yeti: + return "Yeti"; + case Race::Zebuxoruk: + return "Zebuxoruk"; + case Race::ZebuxoruksCage: + return "Zebuxoruk's Cage"; + case Race::Zelniak: + return "Zelniak"; + case Race::Zombie: + case Race::Zombie2: + return "Zombie"; + default: + return "UNKNOWN RACE"; } } -const char* GetPlayerRaceName(uint32 player_race_value) +uint32 GetPlayerRaceValue(uint16 race_id) { - return GetRaceIDName(GetRaceIDFromPlayerRaceValue(player_race_value)); -} - -uint32 GetPlayerRaceValue(uint16 race_id) { switch (race_id) { - case HUMAN: - case BARBARIAN: - case ERUDITE: - case WOOD_ELF: - case HIGH_ELF: - case DARK_ELF: - case HALF_ELF: - case DWARF: - case TROLL: - case OGRE: - case HALFLING: - case GNOME: + case Race::Human: + case Race::Barbarian: + case Race::Erudite: + case Race::WoodElf: + case Race::HighElf: + case Race::DarkElf: + case Race::HalfElf: + case Race::Dwarf: + case Race::Troll: + case Race::Ogre: + case Race::Halfling: + case Race::Gnome: return race_id; - case IKSAR: - return PLAYER_RACE_IKSAR; - case VAHSHIR: - return PLAYER_RACE_VAHSHIR; - case FROGLOK: - case FROGLOK2: - return PLAYER_RACE_FROGLOK; - case DRAKKIN: - return PLAYER_RACE_DRAKKIN; + case Race::Iksar: + return RaceIndex::Iksar; + case Race::VahShir: + return RaceIndex::VahShir; + case Race::Froglok2: + return RaceIndex::Froglok; + case Race::Drakkin: + return RaceIndex::Drakkin; default: - return PLAYER_RACE_UNKNOWN; // watch + return Race::Doug; } } -uint16 GetPlayerRaceBit(uint16 race_id) { - switch (race_id) { - case HUMAN: - return PLAYER_RACE_HUMAN_BIT; - case BARBARIAN: - return PLAYER_RACE_BARBARIAN_BIT; - case ERUDITE: - return PLAYER_RACE_ERUDITE_BIT; - case WOOD_ELF: - return PLAYER_RACE_WOOD_ELF_BIT; - case HIGH_ELF: - return PLAYER_RACE_HIGH_ELF_BIT; - case DARK_ELF: - return PLAYER_RACE_DARK_ELF_BIT; - case HALF_ELF: - return PLAYER_RACE_HALF_ELF_BIT; - case DWARF: - return PLAYER_RACE_DWARF_BIT; - case TROLL: - return PLAYER_RACE_TROLL_BIT; - case OGRE: - return PLAYER_RACE_OGRE_BIT; - case HALFLING: - return PLAYER_RACE_HALFLING_BIT; - case GNOME: - return PLAYER_RACE_GNOME_BIT; - case IKSAR: - return PLAYER_RACE_IKSAR_BIT; - case VAHSHIR: - return PLAYER_RACE_VAHSHIR_BIT; - case FROGLOK: - return PLAYER_RACE_FROGLOK_BIT; - case DRAKKIN: - return PLAYER_RACE_DRAKKIN_BIT; - default: - return PLAYER_RACE_UNKNOWN_BIT; - } -} - -uint16 GetRaceIDFromPlayerRaceValue(uint32 player_race_value) { - switch (player_race_value) { - case PLAYER_RACE_HUMAN: - case PLAYER_RACE_BARBARIAN: - case PLAYER_RACE_ERUDITE: - case PLAYER_RACE_WOOD_ELF: - case PLAYER_RACE_HIGH_ELF: - case PLAYER_RACE_DARK_ELF: - case PLAYER_RACE_HALF_ELF: - case PLAYER_RACE_DWARF: - case PLAYER_RACE_TROLL: - case PLAYER_RACE_OGRE: - case PLAYER_RACE_HALFLING: - case PLAYER_RACE_GNOME: - return player_race_value; - case PLAYER_RACE_IKSAR: - return IKSAR; - case PLAYER_RACE_VAHSHIR: - return VAHSHIR; - case PLAYER_RACE_FROGLOK: - return FROGLOK; - case PLAYER_RACE_DRAKKIN: - return DRAKKIN; - default: - return PLAYER_RACE_UNKNOWN; // watch - } -} - -uint16 GetRaceIDFromPlayerRaceBit(uint32 player_race_bit) +uint16 GetPlayerRaceBit(uint16 race_id) { - switch (player_race_bit) { - case PLAYER_RACE_HUMAN_BIT: - return HUMAN; - case PLAYER_RACE_BARBARIAN_BIT: - return BARBARIAN; - case PLAYER_RACE_ERUDITE_BIT: - return ERUDITE; - case PLAYER_RACE_WOOD_ELF_BIT: - return WOOD_ELF; - case PLAYER_RACE_HIGH_ELF_BIT: - return HIGH_ELF; - case PLAYER_RACE_DARK_ELF_BIT: - return DARK_ELF; - case PLAYER_RACE_HALF_ELF_BIT: - return HALF_ELF; - case PLAYER_RACE_DWARF_BIT: - return DWARF; - case PLAYER_RACE_TROLL_BIT: - return TROLL; - case PLAYER_RACE_OGRE_BIT: - return OGRE; - case PLAYER_RACE_HALFLING_BIT: - return HALFLING; - case PLAYER_RACE_GNOME_BIT: - return GNOME; - case PLAYER_RACE_IKSAR_BIT: - return IKSAR; - case PLAYER_RACE_VAHSHIR_BIT: - return VAHSHIR; - case PLAYER_RACE_FROGLOK_BIT: - return FROGLOK; - case PLAYER_RACE_DRAKKIN_BIT: - return DRAKKIN; - default: - return PLAYER_RACE_UNKNOWN; // watch + switch (race_id) { + case Race::Human: + return RaceBitmask::Human; + case Race::Barbarian: + return RaceBitmask::Barbarian; + case Race::Erudite: + return RaceBitmask::Erudite; + case Race::WoodElf: + return RaceBitmask::WoodElf; + case Race::HighElf: + return RaceBitmask::HighElf; + case Race::DarkElf: + return RaceBitmask::DarkElf; + case Race::HalfElf: + return RaceBitmask::HalfElf; + case Race::Dwarf: + return RaceBitmask::Dwarf; + case Race::Troll: + return RaceBitmask::Troll; + case Race::Ogre: + return RaceBitmask::Ogre; + case Race::Halfling: + return RaceBitmask::Halfling; + case Race::Gnome: + return RaceBitmask::Gnome; + case Race::Iksar: + return RaceBitmask::Iksar; + case Race::VahShir: + return RaceBitmask::VahShir; + case Race::Froglok2: + return RaceBitmask::Froglok; + case Race::Drakkin: + return RaceBitmask::Drakkin; + default: + return RaceBitmask::Unknown; + } +} + +uint16 GetRaceIDFromPlayerRaceValue(uint32 player_race_value) +{ + switch (player_race_value) { + case RaceIndex::Human: + case RaceIndex::Barbarian: + case RaceIndex::Erudite: + case RaceIndex::WoodElf: + case RaceIndex::HighElf: + case RaceIndex::DarkElf: + case RaceIndex::HalfElf: + case RaceIndex::Dwarf: + case RaceIndex::Troll: + case RaceIndex::Ogre: + case RaceIndex::Halfling: + case RaceIndex::Gnome: + return player_race_value; + case RaceIndex::Iksar: + return Race::Iksar; + case RaceIndex::VahShir: + return Race::VahShir; + case RaceIndex::Froglok: + return Race::Froglok2; + case RaceIndex::Drakkin: + return Race::Drakkin; + default: + return Race::Doug; } } @@ -1605,634 +1514,480 @@ float GetRaceGenderDefaultHeight(int race, int gender) return male_height[race]; } -// PlayerAppearance prep -#define HUMAN_MALE ((HUMAN << 8) | Gender::Male) -#define HUMAN_FEMALE ((HUMAN << 8) | Gender::Female) -#define BARBARIAN_MALE ((BARBARIAN << 8) | Gender::Male) -#define BARBARIAN_FEMALE ((BARBARIAN << 8) | Gender::Female) -#define ERUDITE_MALE ((ERUDITE << 8) | Gender::Male) -#define ERUDITE_FEMALE ((ERUDITE << 8) | Gender::Female) -#define WOOD_ELF_MALE ((WOOD_ELF << 8) | Gender::Male) -#define WOOD_ELF_FEMALE ((WOOD_ELF << 8) | Gender::Female) -#define HIGH_ELF_MALE ((HIGH_ELF << 8) | Gender::Male) -#define HIGH_ELF_FEMALE ((HIGH_ELF << 8) | Gender::Female) -#define DARK_ELF_MALE ((DARK_ELF << 8) | Gender::Male) -#define DARK_ELF_FEMALE ((DARK_ELF << 8) | Gender::Female) -#define HALF_ELF_MALE ((HALF_ELF << 8) | Gender::Male) -#define HALF_ELF_FEMALE ((HALF_ELF << 8) | Gender::Female) -#define DWARF_MALE ((DWARF << 8) | Gender::Male) -#define DWARF_FEMALE ((DWARF << 8) | Gender::Female) -#define TROLL_MALE ((TROLL << 8) | Gender::Male) -#define TROLL_FEMALE ((TROLL << 8) | Gender::Female) -#define OGRE_MALE ((OGRE << 8) | Gender::Male) -#define OGRE_FEMALE ((OGRE << 8) | Gender::Female) -#define HALFLING_MALE ((HALFLING << 8) | Gender::Male) -#define HALFLING_FEMALE ((HALFLING << 8) | Gender::Female) -#define GNOME_MALE ((GNOME << 8) | Gender::Male) -#define GNOME_FEMALE ((GNOME << 8) | Gender::Female) -#define IKSAR_MALE ((IKSAR << 8) | Gender::Male) -#define IKSAR_FEMALE ((IKSAR << 8) | Gender::Female) -#define VAHSHIR_MALE ((VAHSHIR << 8) | Gender::Male) -#define VAHSHIR_FEMALE ((VAHSHIR << 8) | Gender::Female) -#define FROGLOK_MALE ((FROGLOK << 8) | Gender::Male) -#define FROGLOK_FEMALE ((FROGLOK << 8) | Gender::Female) -#define DRAKKIN_MALE ((DRAKKIN << 8) | Gender::Male) -#define DRAKKIN_FEMALE ((DRAKKIN << 8) | Gender::Female) +#define GetRaceGenderMask(race_id, gender_id) (((int)race_id << 8) | gender_id) -#define BINDRG(r, g) (((int)r << 8) | g) - - -bool PlayerAppearance::IsValidBeard(uint16 race_id, uint8 gender_id, uint8 beard_value, bool use_luclin) +bool RaceAppearance::IsValidBeard(uint16 race_id, uint8 gender_id, uint8 beard_value, bool use_luclin) { - if (beard_value == 0xFF) + if (beard_value == std::numeric_limits::max()) { return true; + } if (use_luclin) { - switch (BINDRG(race_id, gender_id)) { - case DWARF_FEMALE: - if (beard_value <= 1) - return true; - break; - case HIGH_ELF_MALE: - case DARK_ELF_MALE: - case HALF_ELF_MALE: - case DRAKKIN_FEMALE: - if (beard_value <= 3) - return true; - break; - case HUMAN_MALE: - case BARBARIAN_MALE: - case ERUDITE_MALE: - case DWARF_MALE: - case HALFLING_MALE: - case GNOME_MALE: - if (beard_value <= 5) - return true; - break; - case DRAKKIN_MALE: - if (beard_value <= 11) - return true; - break; - default: - break; + switch (GetRaceGenderMask(race_id, gender_id)) { + case RaceAppearance::DwarfFemale: + if (beard_value <= 1) { + return true; + } + + break; + case RaceAppearance::HighElfMale: + case RaceAppearance::DarkElfMale: + case RaceAppearance::HalfElfMale: + case RaceAppearance::DrakkinFemale: + if (beard_value <= 3) { + return true; + } + + break; + case RaceAppearance::HumanMale: + case RaceAppearance::BarbarianMale: + case RaceAppearance::EruditeMale: + case RaceAppearance::DwarfMale: + case RaceAppearance::HalflingMale: + case RaceAppearance::GnomeMale: + if (beard_value <= 5) { + return true; + } + + break; + case RaceAppearance::DrakkinMale: + if (beard_value <= 11) { + return true; + } + + break; + default: + break; } + return false; - } - else { - switch (BINDRG(race_id, gender_id)) { - case DRAKKIN_FEMALE: - if (beard_value <= 3) - return true; - break; - case DRAKKIN_MALE: - if (beard_value <= 11) - return true; - break; - default: - break; + } else { + switch (GetRaceGenderMask(race_id, gender_id)) { + case RaceAppearance::DrakkinFemale: + if (beard_value <= 3) { + return true; + } + + break; + case RaceAppearance::DrakkinMale: + if (beard_value <= 11) { + return true; + } + + break; + default: + break; } + return false; } } -bool PlayerAppearance::IsValidBeardColor(uint16 race_id, uint8 gender_id, uint8 beard_color_value, bool use_luclin) +bool RaceAppearance::IsValidBeardColor(uint16 race_id, uint8 gender_id, uint8 beard_color_value, bool use_luclin) { - if (beard_color_value == 0xFF) - return true; - - switch (BINDRG(race_id, gender_id)) { - case GNOME_MALE: - if (beard_color_value <= 24) - return true; - break; - case HUMAN_MALE: - case BARBARIAN_MALE: - case ERUDITE_MALE: - case HALF_ELF_MALE: - case DWARF_MALE: - case DWARF_FEMALE: - case HALFLING_MALE: - if (beard_color_value <= 19) - return true; - break; - case DARK_ELF_MALE: - if (beard_color_value >= 13 && beard_color_value <= 18) - return true; - break; - case HIGH_ELF_MALE: - if (beard_color_value <= 14) - return true; - break; - case FROGLOK_MALE: - case FROGLOK_FEMALE: - case DRAKKIN_MALE: - case DRAKKIN_FEMALE: - if (beard_color_value <= 3) - return true; - break; - default: - break; - } - return false; -} - -bool PlayerAppearance::IsValidDetail(uint16 race_id, uint8 gender_id, uint32 detail_value, bool use_luclin) -{ - if (detail_value == 0xFFFFFFFF) + if (beard_color_value == std::numeric_limits::max()) { return true; - - switch (BINDRG(race_id, gender_id)) { - case DRAKKIN_MALE: - case DRAKKIN_FEMALE: - if (detail_value <= 7) - return true; - break; - default: - break; } + + switch (GetRaceGenderMask(race_id, gender_id)) { + case RaceAppearance::GnomeMale: + if (beard_color_value <= 24) { + return true; + } + + break; + case RaceAppearance::HumanMale: + case RaceAppearance::BarbarianMale: + case RaceAppearance::EruditeMale: + case RaceAppearance::HalfElfMale: + case RaceAppearance::DwarfMale: + case RaceAppearance::DwarfFemale: + case RaceAppearance::HalflingMale: + if (beard_color_value <= 19) { + return true; + } + + break; + case RaceAppearance::DarkElfMale: + if (EQ::ValueWithin(beard_color_value, 13, 18)) { + return true; + } + + break; + case RaceAppearance::HighElfMale: + if (beard_color_value <= 14) { + return true; + } + + break; + case RaceAppearance::FroglokMale: + case RaceAppearance::FroglokFemale: + case RaceAppearance::DrakkinMale: + case RaceAppearance::DrakkinFemale: + if (beard_color_value <= 3) { + return true; + } + + break; + default: + break; + } + return false; } -bool PlayerAppearance::IsValidEyeColor(uint16 race_id, uint8 gender_id, uint8 eye_color_value, bool use_luclin) +bool RaceAppearance::IsValidDetail(uint16 race_id, uint8 gender_id, uint32 detail_value, bool use_luclin) { - return true; // need valid criteria - - switch (BINDRG(race_id, gender_id)) { - case HUMAN_MALE: - case HUMAN_FEMALE: - case BARBARIAN_MALE: - case BARBARIAN_FEMALE: - case ERUDITE_MALE: - case ERUDITE_FEMALE: - case WOOD_ELF_MALE: - case WOOD_ELF_FEMALE: - case HIGH_ELF_MALE: - case HIGH_ELF_FEMALE: - case DARK_ELF_MALE: - case DARK_ELF_FEMALE: - case HALF_ELF_MALE: - case HALF_ELF_FEMALE: - case DWARF_MALE: - case DWARF_FEMALE: - case OGRE_MALE: - case OGRE_FEMALE: - case HALFLING_MALE: - case HALFLING_FEMALE: - case GNOME_MALE: - case GNOME_FEMALE: - case IKSAR_MALE: - case IKSAR_FEMALE: - case VAHSHIR_MALE: - case VAHSHIR_FEMALE: - if (eye_color_value <= 9) - return true; - break; - case TROLL_MALE: - case TROLL_FEMALE: - if (eye_color_value <= 10) - return true; - break; - case FROGLOK_MALE: - case FROGLOK_FEMALE: - case DRAKKIN_MALE: - case DRAKKIN_FEMALE: - if (eye_color_value <= 11) - return true; - break; - default: - break; - } - return false; -} - -bool PlayerAppearance::IsValidFace(uint16 race_id, uint8 gender_id, uint8 face_value, bool use_luclin) -{ - if (face_value == 0xFF) + if (detail_value == std::numeric_limits::max()) { return true; - - switch (BINDRG(race_id, gender_id)) { - case DRAKKIN_MALE: - case DRAKKIN_FEMALE: - if (face_value <= 6) - return true; - break; - case HUMAN_MALE: - case HUMAN_FEMALE: - case BARBARIAN_MALE: - case BARBARIAN_FEMALE: - case ERUDITE_MALE: - case ERUDITE_FEMALE: - case WOOD_ELF_MALE: - case WOOD_ELF_FEMALE: - case HIGH_ELF_MALE: - case HIGH_ELF_FEMALE: - case DARK_ELF_MALE: - case DARK_ELF_FEMALE: - case HALF_ELF_MALE: - case HALF_ELF_FEMALE: - case DWARF_MALE: - case DWARF_FEMALE: - case TROLL_MALE: - case TROLL_FEMALE: - case OGRE_MALE: - case OGRE_FEMALE: - case HALFLING_MALE: - case HALFLING_FEMALE: - case GNOME_MALE: - case GNOME_FEMALE: - case IKSAR_MALE: - case IKSAR_FEMALE: - case VAHSHIR_MALE: - case VAHSHIR_FEMALE: - if (face_value <= 7) - return true; - break; - case FROGLOK_MALE: - case FROGLOK_FEMALE: - if (face_value <= 9) - return true; - break; - default: - break; } + + switch (GetRaceGenderMask(race_id, gender_id)) { + case RaceAppearance::DrakkinMale: + case RaceAppearance::DrakkinFemale: + if (detail_value <= 7) { + return true; + } + + break; + default: + break; + } + return false; } -bool PlayerAppearance::IsValidHair(uint16 race_id, uint8 gender_id, uint8 hair_value, bool use_luclin) +bool RaceAppearance::IsValidEyeColor(uint16 race_id, uint8 gender_id, uint8 eye_color_value, bool use_luclin) { - if (hair_value == 0xFF) + if (eye_color_value == std::numeric_limits::max()) { return true; + } + + switch (GetRaceGenderMask(race_id, gender_id)) { + case RaceAppearance::HumanMale: + case RaceAppearance::HumanFemale: + case RaceAppearance::BarbarianMale: + case RaceAppearance::BarbarianFemale: + case RaceAppearance::EruditeMale: + case RaceAppearance::EruditeFemale: + case RaceAppearance::WoodElfMale: + case RaceAppearance::WoodElfFemale: + case RaceAppearance::HighElfMale: + case RaceAppearance::HighElfFemale: + case RaceAppearance::DarkElfMale: + case RaceAppearance::DarkElfFemale: + case RaceAppearance::HalfElfMale: + case RaceAppearance::HalfElfFemale: + case RaceAppearance::DwarfMale: + case RaceAppearance::DwarfFemale: + case RaceAppearance::OgreMale: + case RaceAppearance::OgreFemale: + case RaceAppearance::HalflingMale: + case RaceAppearance::HalflingFemale: + case RaceAppearance::GnomeMale: + case RaceAppearance::GnomeFemale: + case RaceAppearance::IksarMale: + case RaceAppearance::IksarFemale: + case RaceAppearance::VahShirMale: + case RaceAppearance::VahShirFemale: + if (eye_color_value <= 9) { + return true; + } + + break; + case RaceAppearance::TrollMale: + case RaceAppearance::TrollFemale: + if (eye_color_value <= 10) { + return true; + } + + break; + case RaceAppearance::FroglokMale: + case RaceAppearance::FroglokFemale: + case RaceAppearance::DrakkinMale: + case RaceAppearance::DrakkinFemale: + if (eye_color_value <= 11) { + return true; + } + + break; + default: + break; + } + + return false; +} + +bool RaceAppearance::IsValidFace(uint16 race_id, uint8 gender_id, uint8 face_value, bool use_luclin) +{ + if (face_value == std::numeric_limits::max()) { + return true; + } + + switch (GetRaceGenderMask(race_id, gender_id)) { + case RaceAppearance::DrakkinMale: + case RaceAppearance::DrakkinFemale: + if (face_value <= 6) { + return true; + } + + break; + case RaceAppearance::HumanMale: + case RaceAppearance::HumanFemale: + case RaceAppearance::BarbarianMale: + case RaceAppearance::BarbarianFemale: + case RaceAppearance::EruditeMale: + case RaceAppearance::EruditeFemale: + case RaceAppearance::WoodElfMale: + case RaceAppearance::WoodElfFemale: + case RaceAppearance::HighElfMale: + case RaceAppearance::HighElfFemale: + case RaceAppearance::DarkElfMale: + case RaceAppearance::DarkElfFemale: + case RaceAppearance::HalfElfMale: + case RaceAppearance::HalfElfFemale: + case RaceAppearance::DwarfMale: + case RaceAppearance::DwarfFemale: + case RaceAppearance::TrollMale: + case RaceAppearance::TrollFemale: + case RaceAppearance::OgreMale: + case RaceAppearance::OgreFemale: + case RaceAppearance::HalflingMale: + case RaceAppearance::HalflingFemale: + case RaceAppearance::GnomeMale: + case RaceAppearance::GnomeFemale: + case RaceAppearance::IksarMale: + case RaceAppearance::IksarFemale: + case RaceAppearance::VahShirMale: + case RaceAppearance::VahShirFemale: + if (face_value <= 7) { + return true; + } + + break; + case RaceAppearance::FroglokMale: + case RaceAppearance::FroglokFemale: + if (face_value <= 9) { + return true; + } + + break; + default: + break; + } + + return false; +} + +bool RaceAppearance::IsValidHair(uint16 race_id, uint8 gender_id, uint8 hair_value, bool use_luclin) +{ + if (hair_value == std::numeric_limits::max()) { + return true; + } if (use_luclin) { - switch (BINDRG(race_id, gender_id)) { - case HUMAN_MALE: - case HUMAN_FEMALE: - case BARBARIAN_MALE: - case BARBARIAN_FEMALE: - case WOOD_ELF_MALE: - case WOOD_ELF_FEMALE: - case HIGH_ELF_MALE: - case HIGH_ELF_FEMALE: - case DARK_ELF_MALE: - case DARK_ELF_FEMALE: - case HALF_ELF_MALE: - case HALF_ELF_FEMALE: - case DWARF_MALE: - case DWARF_FEMALE: - case TROLL_FEMALE: - case OGRE_FEMALE: - case HALFLING_MALE: - case HALFLING_FEMALE: - case GNOME_MALE: - case GNOME_FEMALE: - if (hair_value <= 3) - return true; - break; - case ERUDITE_MALE: - if (hair_value <= 5) - return true; - break; - case DRAKKIN_FEMALE: - if (hair_value <= 7) - return true; - break; - case ERUDITE_FEMALE: - case DRAKKIN_MALE: - if (hair_value <= 8) - return true; - break; - default: - break; + switch (GetRaceGenderMask(race_id, gender_id)) { + case RaceAppearance::HumanMale: + case RaceAppearance::HumanFemale: + case RaceAppearance::BarbarianMale: + case RaceAppearance::BarbarianFemale: + case RaceAppearance::WoodElfMale: + case RaceAppearance::WoodElfFemale: + case RaceAppearance::HighElfMale: + case RaceAppearance::HighElfFemale: + case RaceAppearance::DarkElfMale: + case RaceAppearance::DarkElfFemale: + case RaceAppearance::HalfElfMale: + case RaceAppearance::HalfElfFemale: + case RaceAppearance::DwarfMale: + case RaceAppearance::DwarfFemale: + case RaceAppearance::TrollFemale: + case RaceAppearance::OgreFemale: + case RaceAppearance::HalflingMale: + case RaceAppearance::HalflingFemale: + case RaceAppearance::GnomeMale: + case RaceAppearance::GnomeFemale: + if (hair_value <= 3) { + return true; + } + + break; + case RaceAppearance::EruditeMale: + if (hair_value <= 5) { + return true; + } + + break; + case RaceAppearance::DrakkinFemale: + if (hair_value <= 7) { + return true; + } + + break; + case RaceAppearance::EruditeFemale: + case RaceAppearance::DrakkinMale: + if (hair_value <= 8) { + return true; + } + + break; + default: + break; } + return false; - } - else { - switch (BINDRG(race_id, gender_id)) { - case DRAKKIN_FEMALE: - if (hair_value <= 7) - return true; - break; - case DRAKKIN_MALE: - if (hair_value <= 8) - return true; - break; - default: - break; + } else { + switch (GetRaceGenderMask(race_id, gender_id)) { + case RaceAppearance::DrakkinFemale: + if (hair_value <= 7) { + return true; + } + + break; + case RaceAppearance::DrakkinMale: + if (hair_value <= 8) { + return true; + } + + break; + default: + break; } + return false; } } -bool PlayerAppearance::IsValidHairColor(uint16 race_id, uint8 gender_id, uint8 hair_color_value, bool use_luclin) +bool RaceAppearance::IsValidHairColor(uint16 race_id, uint8 gender_id, uint8 hair_color_value, bool use_luclin) { - if (hair_color_value == 0xFF) + if (hair_color_value == std::numeric_limits::max()) { return true; - - switch (BINDRG(race_id, gender_id)) { - case GNOME_MALE: - case GNOME_FEMALE: - if (hair_color_value <= 24) - return true; - break; - case TROLL_FEMALE: - case OGRE_FEMALE: - if (hair_color_value <= 23) - return true; - break; - case HUMAN_MALE: - case HUMAN_FEMALE: - case BARBARIAN_MALE: - case BARBARIAN_FEMALE: - case WOOD_ELF_MALE: - case WOOD_ELF_FEMALE: - case HALF_ELF_MALE: - case HALF_ELF_FEMALE: - case DWARF_MALE: - case DWARF_FEMALE: - case HALFLING_MALE: - case HALFLING_FEMALE: - if (hair_color_value <= 19) - return true; - break; - case DARK_ELF_MALE: - case DARK_ELF_FEMALE: - if (hair_color_value >= 13 && hair_color_value <= 18) - return true; - break; - case HIGH_ELF_MALE: - case HIGH_ELF_FEMALE: - if (hair_color_value <= 14) - return true; - break; - case FROGLOK_MALE: - case FROGLOK_FEMALE: - case DRAKKIN_MALE: - case DRAKKIN_FEMALE: - if (hair_color_value <= 3) - return true; - break; - default: - break; } + + switch (GetRaceGenderMask(race_id, gender_id)) { + case RaceAppearance::GnomeMale: + case RaceAppearance::GnomeFemale: + if (hair_color_value <= 24) { + return true; + } + + break; + case RaceAppearance::TrollFemale: + case RaceAppearance::OgreFemale: + if (hair_color_value <= 23) { + return true; + } + + break; + case RaceAppearance::HumanMale: + case RaceAppearance::HumanFemale: + case RaceAppearance::BarbarianMale: + case RaceAppearance::BarbarianFemale: + case RaceAppearance::WoodElfMale: + case RaceAppearance::WoodElfFemale: + case RaceAppearance::HalfElfMale: + case RaceAppearance::HalfElfFemale: + case RaceAppearance::DwarfMale: + case RaceAppearance::DwarfFemale: + case RaceAppearance::HalflingMale: + case RaceAppearance::HalflingFemale: + if (hair_color_value <= 19) { + return true; + } + + break; + case RaceAppearance::DarkElfMale: + case RaceAppearance::DarkElfFemale: + if (EQ::ValueWithin(hair_color_value, 13, 18)) { + return true; + } + + break; + case RaceAppearance::HighElfMale: + case RaceAppearance::HighElfFemale: + if (hair_color_value <= 14) { + return true; + } + + break; + case RaceAppearance::FroglokMale: + case RaceAppearance::FroglokFemale: + case RaceAppearance::DrakkinMale: + case RaceAppearance::DrakkinFemale: + if (hair_color_value <= 3) { + return true; + } + + break; + default: + break; + } + return false; } -bool PlayerAppearance::IsValidHead(uint16 race_id, uint8 gender_id, uint8 head_value, bool use_luclin) +bool RaceAppearance::IsValidHeritage(uint16 race_id, uint8 gender_id, uint32 heritage_value, bool use_luclin) { - if (head_value == 0xFF) + if (heritage_value == std::numeric_limits::max()) { return true; + } - if (use_luclin) { - switch (BINDRG(race_id, gender_id)) { - case HUMAN_MALE: - case HUMAN_FEMALE: - case BARBARIAN_MALE: - case BARBARIAN_FEMALE: - case WOOD_ELF_MALE: - case WOOD_ELF_FEMALE: - case HIGH_ELF_MALE: - case HIGH_ELF_FEMALE: - case DARK_ELF_MALE: - case DARK_ELF_FEMALE: - case HALF_ELF_MALE: - case HALF_ELF_FEMALE: - case DWARF_MALE: - case DWARF_FEMALE: - case TROLL_MALE: - case TROLL_FEMALE: - case OGRE_MALE: - case OGRE_FEMALE: - case HALFLING_MALE: - case HALFLING_FEMALE: - case GNOME_MALE: - case GNOME_FEMALE: - case IKSAR_MALE: - case IKSAR_FEMALE: - case VAHSHIR_MALE: - case VAHSHIR_FEMALE: - case FROGLOK_MALE: - case FROGLOK_FEMALE: - case DRAKKIN_MALE: - case DRAKKIN_FEMALE: - if (head_value <= 3) - return true; - break; - case ERUDITE_MALE: - case ERUDITE_FEMALE: - if (head_value <= 4) + switch (GetRaceGenderMask(race_id, gender_id)) { + case RaceAppearance::DrakkinMale: + case RaceAppearance::DrakkinFemale: + if (heritage_value <= 7) { return true; + } + break; default: break; - } - return false; } - else { - switch (BINDRG(race_id, gender_id)) { - case HUMAN_MALE: - case HUMAN_FEMALE: - case BARBARIAN_MALE: - case BARBARIAN_FEMALE: - case ERUDITE_MALE: - case ERUDITE_FEMALE: - case WOOD_ELF_MALE: - case WOOD_ELF_FEMALE: - case HIGH_ELF_MALE: - case HIGH_ELF_FEMALE: - case DARK_ELF_MALE: - case DARK_ELF_FEMALE: - case HALF_ELF_MALE: - case HALF_ELF_FEMALE: - case DWARF_MALE: - case DWARF_FEMALE: - case TROLL_MALE: - case TROLL_FEMALE: - case OGRE_MALE: - case OGRE_FEMALE: - case HALFLING_MALE: - case HALFLING_FEMALE: - case IKSAR_MALE: - case IKSAR_FEMALE: - case VAHSHIR_MALE: - case VAHSHIR_FEMALE: - case FROGLOK_MALE: - case FROGLOK_FEMALE: - case DRAKKIN_MALE: - case DRAKKIN_FEMALE: - if (head_value <= 3) - return true; - break; - case GNOME_MALE: - case GNOME_FEMALE: - if (head_value <= 4) - return true; - break; - default: - break; - } - return false; - } -} -bool PlayerAppearance::IsValidHeritage(uint16 race_id, uint8 gender_id, uint32 heritage_value, bool use_luclin) -{ - if (heritage_value == 0xFFFFFFFF) - return true; - - switch (BINDRG(race_id, gender_id)) { - case DRAKKIN_MALE: - case DRAKKIN_FEMALE: - if (heritage_value <= 7) // > 5 seems to jumble other features..else, some heritages have 'specialized' features - return true; - break; - default: - break; - } return false; } -bool PlayerAppearance::IsValidTattoo(uint16 race_id, uint8 gender_id, uint32 tattoo_value, bool use_luclin) +bool RaceAppearance::IsValidTattoo(uint16 race_id, uint8 gender_id, uint32 tattoo_value, bool use_luclin) { - if (tattoo_value == 0xFFFFFFFF) + if (tattoo_value == std::numeric_limits::max()) { return true; - - switch (BINDRG(race_id, gender_id)) { - case DRAKKIN_MALE: - case DRAKKIN_FEMALE: - if (tattoo_value <= 7) - return true; - break; - default: - break; } + + switch (GetRaceGenderMask(race_id, gender_id)) { + case RaceAppearance::DrakkinMale: + case RaceAppearance::DrakkinFemale: + if (tattoo_value <= 7) { + return true; + } + + break; + default: + break; + } + return false; } -bool PlayerAppearance::IsValidTexture(uint16 race_id, uint8 gender_id, uint8 texture_value, bool use_luclin) +bool RaceAppearance::IsValidWoad(uint16 race_id, uint8 gender_id, uint8 woad_value, bool use_luclin) { - if (texture_value == 0xFF) + if (woad_value == std::numeric_limits::max()) { return true; + } if (use_luclin) { - switch (BINDRG(race_id, gender_id)) { - case HUMAN_MALE: - case HUMAN_FEMALE: - case IKSAR_MALE: - case IKSAR_FEMALE: - case DRAKKIN_MALE: - case DRAKKIN_FEMALE: - if ((texture_value >= 10 && texture_value <= 16) || texture_value <= 4) - return true; - break; - case ERUDITE_MALE: - case ERUDITE_FEMALE: - case HIGH_ELF_MALE: - case HIGH_ELF_FEMALE: - case DARK_ELF_MALE: - case DARK_ELF_FEMALE: - case GNOME_MALE: - case GNOME_FEMALE: - case FROGLOK_MALE: - case FROGLOK_FEMALE: - if ((texture_value >= 10 && texture_value <= 16) || texture_value <= 3) - return true; - break; - case BARBARIAN_MALE: - case BARBARIAN_FEMALE: - case WOOD_ELF_MALE: - case WOOD_ELF_FEMALE: - case HALF_ELF_MALE: - case HALF_ELF_FEMALE: - case DWARF_MALE: - case DWARF_FEMALE: - case TROLL_MALE: - case TROLL_FEMALE: - case OGRE_MALE: - case OGRE_FEMALE: - case HALFLING_MALE: - case HALFLING_FEMALE: - case VAHSHIR_MALE: - case VAHSHIR_FEMALE: - if (texture_value <= 3) - return true; - break; - default: - break; - } - return false; - } - else { - switch (BINDRG(race_id, gender_id)) { - case HUMAN_MALE: - case HUMAN_FEMALE: - case ERUDITE_MALE: - case ERUDITE_FEMALE: - case DRAKKIN_MALE: - case DRAKKIN_FEMALE: - if ((texture_value >= 10 && texture_value <= 16) || texture_value <= 4) - return true; - break; - case HIGH_ELF_MALE: - case HIGH_ELF_FEMALE: - case DARK_ELF_MALE: - case DARK_ELF_FEMALE: - case GNOME_MALE: - case GNOME_FEMALE: - case FROGLOK_MALE: - case FROGLOK_FEMALE: - if ((texture_value >= 10 && texture_value <= 16) || texture_value <= 3) - return true; - break; - case VAHSHIR_MALE: - case VAHSHIR_FEMALE: - if (texture_value == 50 || texture_value <= 3) - return true; - break; - case IKSAR_MALE: - case IKSAR_FEMALE: - if (texture_value == 10 || texture_value <= 4) - return true; - break; - case BARBARIAN_MALE: - case BARBARIAN_FEMALE: - case WOOD_ELF_MALE: - case WOOD_ELF_FEMALE: - case HALF_ELF_MALE: - case HALF_ELF_FEMALE: - case DWARF_MALE: - case DWARF_FEMALE: - case TROLL_MALE: - case TROLL_FEMALE: - case OGRE_MALE: - case OGRE_FEMALE: - case HALFLING_MALE: - case HALFLING_FEMALE: - if (texture_value <= 3) - return true; - break; - default: - break; - } - return false; - } -} + switch (GetRaceGenderMask(race_id, gender_id)) { + case RaceAppearance::BarbarianMale: + case RaceAppearance::BarbarianFemale: + if (woad_value <= 8) { + return true; + } -bool PlayerAppearance::IsValidWoad(uint16 race_id, uint8 gender_id, uint8 woad_value, bool use_luclin) -{ - if (woad_value == 0xFF) - return true; - - if (use_luclin) { - switch (BINDRG(race_id, gender_id)) { - case BARBARIAN_MALE: - case BARBARIAN_FEMALE: - if (woad_value <= 8) - return true; - break; - default: - break; + break; + default: + break; } } + return false; } diff --git a/common/races.h b/common/races.h index b3f6988c5..3d9d9f8d3 100644 --- a/common/races.h +++ b/common/races.h @@ -21,103 +21,7 @@ #include "../common/types.h" #include -namespace Gender { - constexpr uint8 Male = 0; - constexpr uint8 Female = 1; - constexpr uint8 Neuter = 2; -} - -//theres a big list straight from the client below. - -#define HUMAN 1 -#define BARBARIAN 2 -#define ERUDITE 3 -#define WOOD_ELF 4 -#define HIGH_ELF 5 -#define DARK_ELF 6 -#define HALF_ELF 7 -#define DWARF 8 -#define TROLL 9 -#define OGRE 10 -#define HALFLING 11 -#define GNOME 12 -#define WEREWOLF 14 -#define WOLF 42 -#define BEAR 43 -#define SKELETON 60 -#define TIGER 63 -#define ELEMENTAL 75 -#define ALLIGATOR 91 -#define OGGOK_CITIZEN 93 -#define EYE_OF_ZOMM 108 -#define WOLF_ELEMENTAL 120 -#define INVISIBLE_MAN 127 -#define IKSAR 128 -#define VAHSHIR 130 -#define CONTROLLED_BOAT 141 -#define MINOR_ILL_OBJ 142 -#define TREE 143 -#define IKSAR_SKELETON 161 -#define FROGLOK 330 -// TODO: check all clients for (BYTE) usage of '/who all' class and remove FROGLOK2, if possible (330 - 74 = 256 .. WORD->BYTE conversion loss...) -#define FROGLOK2 74 // Not sure why /who all reports race as 74 for frogloks -#define FAIRY 473 -#define DRAKKIN 522 // 32768 -#define EMU_RACE_NPC 131069 // was 65533 -#define EMU_RACE_PET 131070 // was 65534 -#define EMU_RACE_UNKNOWN 131071 // was 65535 - - -// player race values -#define PLAYER_RACE_UNKNOWN 0 -#define PLAYER_RACE_HUMAN 1 -#define PLAYER_RACE_BARBARIAN 2 -#define PLAYER_RACE_ERUDITE 3 -#define PLAYER_RACE_WOOD_ELF 4 -#define PLAYER_RACE_HIGH_ELF 5 -#define PLAYER_RACE_DARK_ELF 6 -#define PLAYER_RACE_HALF_ELF 7 -#define PLAYER_RACE_DWARF 8 -#define PLAYER_RACE_TROLL 9 -#define PLAYER_RACE_OGRE 10 -#define PLAYER_RACE_HALFLING 11 -#define PLAYER_RACE_GNOME 12 -#define PLAYER_RACE_IKSAR 13 -#define PLAYER_RACE_VAHSHIR 14 -#define PLAYER_RACE_FROGLOK 15 -#define PLAYER_RACE_DRAKKIN 16 - -#define PLAYER_RACE_COUNT 16 - - -#define PLAYER_RACE_EMU_NPC 17 -#define PLAYER_RACE_EMU_PET 18 -#define PLAYER_RACE_EMU_COUNT 19 - - -// player race bits -#define PLAYER_RACE_UNKNOWN_BIT 0 -#define PLAYER_RACE_HUMAN_BIT 1 -#define PLAYER_RACE_BARBARIAN_BIT 2 -#define PLAYER_RACE_ERUDITE_BIT 4 -#define PLAYER_RACE_WOOD_ELF_BIT 8 -#define PLAYER_RACE_HIGH_ELF_BIT 16 -#define PLAYER_RACE_DARK_ELF_BIT 32 -#define PLAYER_RACE_HALF_ELF_BIT 64 -#define PLAYER_RACE_DWARF_BIT 128 -#define PLAYER_RACE_TROLL_BIT 256 -#define PLAYER_RACE_OGRE_BIT 512 -#define PLAYER_RACE_HALFLING_BIT 1024 -#define PLAYER_RACE_GNOME_BIT 2048 -#define PLAYER_RACE_IKSAR_BIT 4096 -#define PLAYER_RACE_VAHSHIR_BIT 8192 -#define PLAYER_RACE_FROGLOK_BIT 16384 -#define PLAYER_RACE_DRAKKIN_BIT 32768 - -#define PLAYER_RACE_ALL_MASK 65535 - const char* GetRaceIDName(uint16 race_id); -const char* GetPlayerRaceName(uint32 player_race_value); const char* GetGenderName(uint32 gender_id); bool IsPlayerRace(uint16 race_id); @@ -127,25 +31,13 @@ uint32 GetPlayerRaceValue(uint16 race_id); uint16 GetPlayerRaceBit(uint16 race_id); uint16 GetRaceIDFromPlayerRaceValue(uint32 player_race_value); -uint16 GetRaceIDFromPlayerRaceBit(uint32 player_race_bit); float GetRaceGenderDefaultHeight(int race, int gender); -// player race-/gender-based model feature validators -namespace PlayerAppearance -{ - bool IsValidBeard(uint16 race_id, uint8 gender_id, uint8 beard_value, bool use_luclin = true); - bool IsValidBeardColor(uint16 race_id, uint8 gender_id, uint8 beard_color_value, bool use_luclin = true); - bool IsValidDetail(uint16 race_id, uint8 gender_id, uint32 detail_value, bool use_luclin = true); - bool IsValidEyeColor(uint16 race_id, uint8 gender_id, uint8 eye_color_value, bool use_luclin = true); - bool IsValidFace(uint16 race_id, uint8 gender_id, uint8 face_value, bool use_luclin = true); - bool IsValidHair(uint16 race_id, uint8 gender_id, uint8 hair_value, bool use_luclin = true); - bool IsValidHairColor(uint16 race_id, uint8 gender_id, uint8 hair_color_value, bool use_luclin = true); - bool IsValidHead(uint16 race_id, uint8 gender_id, uint8 head_value, bool use_luclin = true); - bool IsValidHeritage(uint16 race_id, uint8 gender_id, uint32 heritage_value, bool use_luclin = true); - bool IsValidTattoo(uint16 race_id, uint8 gender_id, uint32 tattoo_value, bool use_luclin = true); - bool IsValidTexture(uint16 race_id, uint8 gender_id, uint8 texture_value, bool use_luclin = true); - bool IsValidWoad(uint16 race_id, uint8 gender_id, uint8 woad_value, bool use_luclin = true); +namespace Gender { + constexpr uint8 Male = 0; + constexpr uint8 Female = 1; + constexpr uint8 Neuter = 2; } namespace Race { @@ -884,8 +776,92 @@ namespace Race { constexpr uint16 Pegasus3 = 732; constexpr uint16 InteractiveObject = 2250; constexpr uint16 Node = 2254; +} - constexpr uint16 ALL_RACES_BITMASK = 65535; +namespace RaceBitmask { + constexpr uint16 Unknown = 0; + constexpr uint16 Human = 1; + constexpr uint16 Barbarian = 2; + constexpr uint16 Erudite = 4; + constexpr uint16 WoodElf = 8; + constexpr uint16 HighElf = 16; + constexpr uint16 DarkElf = 32; + constexpr uint16 HalfElf = 64; + constexpr uint16 Dwarf = 128; + constexpr uint16 Troll = 256; + constexpr uint16 Ogre = 512; + constexpr uint16 Halfling = 1024; + constexpr uint16 Gnome = 2048; + constexpr uint16 Iksar = 4096; + constexpr uint16 VahShir = 8192; + constexpr uint16 Froglok = 16384; + constexpr uint16 Drakkin = 32768; + constexpr uint16 All = 65535; +} + +namespace RaceIndex { + constexpr uint16 Human = 1; + constexpr uint16 Barbarian = 2; + constexpr uint16 Erudite = 3; + constexpr uint16 WoodElf = 4; + constexpr uint16 HighElf = 5; + constexpr uint16 DarkElf = 6; + constexpr uint16 HalfElf = 7; + constexpr uint16 Dwarf = 8; + constexpr uint16 Troll = 9; + constexpr uint16 Ogre = 10; + constexpr uint16 Halfling = 11; + constexpr uint16 Gnome = 12; + constexpr uint16 Iksar = 13; + constexpr uint16 VahShir = 14; + constexpr uint16 Froglok = 15; + constexpr uint16 Drakkin = 16; +} + +namespace RaceAppearance { + bool IsValidBeard(uint16 race_id, uint8 gender_id, uint8 beard_value, bool use_luclin = true); + bool IsValidBeardColor(uint16 race_id, uint8 gender_id, uint8 beard_color_value, bool use_luclin = true); + bool IsValidDetail(uint16 race_id, uint8 gender_id, uint32 detail_value, bool use_luclin = true); + bool IsValidEyeColor(uint16 race_id, uint8 gender_id, uint8 eye_color_value, bool use_luclin = true); + bool IsValidFace(uint16 race_id, uint8 gender_id, uint8 face_value, bool use_luclin = true); + bool IsValidHair(uint16 race_id, uint8 gender_id, uint8 hair_value, bool use_luclin = true); + bool IsValidHairColor(uint16 race_id, uint8 gender_id, uint8 hair_color_value, bool use_luclin = true); + bool IsValidHeritage(uint16 race_id, uint8 gender_id, uint32 heritage_value, bool use_luclin = true); + bool IsValidTattoo(uint16 race_id, uint8 gender_id, uint32 tattoo_value, bool use_luclin = true); + bool IsValidWoad(uint16 race_id, uint8 gender_id, uint8 woad_value, bool use_luclin = true); + + constexpr int HumanMale = (Race::Human << 8) | Gender::Male; + constexpr int HumanFemale = (Race::Human << 8) | Gender::Female; + constexpr int BarbarianMale = (Race::Barbarian << 8) | Gender::Male; + constexpr int BarbarianFemale = (Race::Barbarian << 8) | Gender::Female; + constexpr int EruditeMale = (Race::Erudite << 8) | Gender::Male; + constexpr int EruditeFemale = (Race::Erudite << 8) | Gender::Female; + constexpr int WoodElfMale = (Race::WoodElf << 8) | Gender::Male; + constexpr int WoodElfFemale = (Race::WoodElf << 8) | Gender::Female; + constexpr int HighElfMale = (Race::HighElf << 8) | Gender::Male; + constexpr int HighElfFemale = (Race::HighElf << 8) | Gender::Female; + constexpr int DarkElfMale = (Race::DarkElf << 8) | Gender::Male; + constexpr int DarkElfFemale = (Race::DarkElf << 8) | Gender::Female; + constexpr int HalfElfMale = (Race::HalfElf << 8) | Gender::Male; + constexpr int HalfElfFemale = (Race::HalfElf << 8) | Gender::Female; + constexpr int DwarfMale = (Race::Dwarf << 8) | Gender::Male; + constexpr int DwarfFemale = (Race::Dwarf << 8) | Gender::Female; + constexpr int TrollMale = (Race::Troll << 8) | Gender::Male; + constexpr int TrollFemale = (Race::Troll << 8) | Gender::Female; + constexpr int OgreMale = (Race::Ogre << 8) | Gender::Male; + constexpr int OgreFemale = (Race::Ogre << 8) | Gender::Female; + constexpr int HalflingMale = (Race::Halfling << 8) | Gender::Male; + constexpr int HalflingFemale = (Race::Halfling << 8) | Gender::Female; + constexpr int GnomeMale = (Race::Gnome << 8) | Gender::Male; + constexpr int GnomeFemale = (Race::Gnome << 8) | Gender::Female; + constexpr int IksarMale = (Race::Iksar << 8) | Gender::Male; + constexpr int IksarFemale = (Race::Iksar << 8) | Gender::Female; + constexpr int VahShirMale = (Race::VahShir << 8) | Gender::Male; + constexpr int VahShirFemale = (Race::VahShir << 8) | Gender::Female; + constexpr int FroglokMale = (Race::Froglok2 << 8) | Gender::Male; + constexpr int FroglokFemale = (Race::Froglok2 << 8) | Gender::Female; + constexpr int DrakkinMale = (Race::Drakkin << 8) | Gender::Male; + constexpr int DrakkinFemale = (Race::Drakkin << 8) | Gender::Female; } #endif diff --git a/common/random.h b/common/random.h index 6d351121f..73b14369e 100644 --- a/common/random.h +++ b/common/random.h @@ -116,6 +116,12 @@ namespace EQ { Reseed(); } + static Random* Instance() + { + static Random instance; + return &instance; + } + private: #ifndef BIASED_INT_DIST typedef std::uniform_int_distribution::param_type int_param_t; diff --git a/common/repositories/adventure_stats_repository.h b/common/repositories/adventure_stats_repository.h index a4025ed2b..5b93a7fe1 100644 --- a/common/repositories/adventure_stats_repository.h +++ b/common/repositories/adventure_stats_repository.h @@ -49,23 +49,23 @@ public: std::string field; switch (theme_id) { - case LDoNThemes::GUK: { + case LDoNTheme::GUK: { field = "guk_"; break; } - case LDoNThemes::MIR: { + case LDoNTheme::MIR: { field = "mir_"; break; } - case LDoNThemes::MMC: { + case LDoNTheme::MMC: { field = "mmc_"; break; } - case LDoNThemes::RUJ: { + case LDoNTheme::RUJ: { field = "ruj_"; break; } - case LDoNThemes::TAK: { + case LDoNTheme::TAK: { field = "tak_"; break; } diff --git a/common/repositories/base/base_expeditions_repository.h b/common/repositories/base/base_bot_blocked_buffs_repository.h similarity index 60% rename from common/repositories/base/base_expeditions_repository.h rename to common/repositories/base/base_bot_blocked_buffs_repository.h index b4dff8502..77b0ee2cd 100644 --- a/common/repositories/base/base_expeditions_repository.h +++ b/common/repositories/base/base_bot_blocked_buffs_repository.h @@ -9,44 +9,44 @@ * @docs https://docs.eqemu.io/developer/repositories */ -#ifndef EQEMU_BASE_EXPEDITIONS_REPOSITORY_H -#define EQEMU_BASE_EXPEDITIONS_REPOSITORY_H +#ifndef EQEMU_BASE_BOT_BLOCKED_BUFFS_REPOSITORY_H +#define EQEMU_BASE_BOT_BLOCKED_BUFFS_REPOSITORY_H #include "../../database.h" #include "../../strings.h" #include -class BaseExpeditionsRepository { +class BaseBotBlockedBuffsRepository { public: - struct Expeditions { - uint32_t id; - uint32_t dynamic_zone_id; - uint8_t add_replay_on_join; - uint8_t is_locked; + struct BotBlockedBuffs { + uint32_t bot_id; + uint32_t spell_id; + uint8_t blocked; + uint8_t blocked_pet; }; static std::string PrimaryKey() { - return std::string("id"); + return std::string("bot_id"); } static std::vector Columns() { return { - "id", - "dynamic_zone_id", - "add_replay_on_join", - "is_locked", + "bot_id", + "spell_id", + "blocked", + "blocked_pet", }; } static std::vector SelectColumns() { return { - "id", - "dynamic_zone_id", - "add_replay_on_join", - "is_locked", + "bot_id", + "spell_id", + "blocked", + "blocked_pet", }; } @@ -62,7 +62,7 @@ public: static std::string TableName() { - return std::string("expeditions"); + return std::string("bot_blocked_buffs"); } static std::string BaseSelect() @@ -83,35 +83,35 @@ public: ); } - static Expeditions NewEntity() + static BotBlockedBuffs NewEntity() { - Expeditions e{}; + BotBlockedBuffs e{}; - e.id = 0; - e.dynamic_zone_id = 0; - e.add_replay_on_join = 1; - e.is_locked = 0; + e.bot_id = 0; + e.spell_id = 0; + e.blocked = 0; + e.blocked_pet = 0; return e; } - static Expeditions GetExpeditions( - const std::vector &expeditionss, - int expeditions_id + static BotBlockedBuffs GetBotBlockedBuffs( + const std::vector &bot_blocked_buffss, + int bot_blocked_buffs_id ) { - for (auto &expeditions : expeditionss) { - if (expeditions.id == expeditions_id) { - return expeditions; + for (auto &bot_blocked_buffs : bot_blocked_buffss) { + if (bot_blocked_buffs.bot_id == bot_blocked_buffs_id) { + return bot_blocked_buffs; } } return NewEntity(); } - static Expeditions FindOne( + static BotBlockedBuffs FindOne( Database& db, - int expeditions_id + int bot_blocked_buffs_id ) { auto results = db.QueryDatabase( @@ -119,18 +119,18 @@ public: "{} WHERE {} = {} LIMIT 1", BaseSelect(), PrimaryKey(), - expeditions_id + bot_blocked_buffs_id ) ); auto row = results.begin(); if (results.RowCount() == 1) { - Expeditions e{}; + BotBlockedBuffs e{}; - e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; - e.dynamic_zone_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; - e.add_replay_on_join = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 1; - e.is_locked = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.bot_id = row[0] ? static_cast(atoi(row[0])) : 0; + e.spell_id = row[1] ? static_cast(atoi(row[1])) : 0; + e.blocked = row[2] ? static_cast(atoi(row[2])) : 0; + e.blocked_pet = row[3] ? static_cast(atoi(row[3])) : 0; return e; } @@ -140,7 +140,7 @@ public: static int DeleteOne( Database& db, - int expeditions_id + int bot_blocked_buffs_id ) { auto results = db.QueryDatabase( @@ -148,7 +148,7 @@ public: "DELETE FROM {} WHERE {} = {}", TableName(), PrimaryKey(), - expeditions_id + bot_blocked_buffs_id ) ); @@ -157,16 +157,17 @@ public: static int UpdateOne( Database& db, - const Expeditions &e + const BotBlockedBuffs &e ) { std::vector v; auto columns = Columns(); - v.push_back(columns[1] + " = " + std::to_string(e.dynamic_zone_id)); - v.push_back(columns[2] + " = " + std::to_string(e.add_replay_on_join)); - v.push_back(columns[3] + " = " + std::to_string(e.is_locked)); + v.push_back(columns[0] + " = " + std::to_string(e.bot_id)); + v.push_back(columns[1] + " = " + std::to_string(e.spell_id)); + v.push_back(columns[2] + " = " + std::to_string(e.blocked)); + v.push_back(columns[3] + " = " + std::to_string(e.blocked_pet)); auto results = db.QueryDatabase( fmt::format( @@ -174,24 +175,24 @@ public: TableName(), Strings::Implode(", ", v), PrimaryKey(), - e.id + e.bot_id ) ); return (results.Success() ? results.RowsAffected() : 0); } - static Expeditions InsertOne( + static BotBlockedBuffs InsertOne( Database& db, - Expeditions e + BotBlockedBuffs e ) { std::vector v; - v.push_back(std::to_string(e.id)); - v.push_back(std::to_string(e.dynamic_zone_id)); - v.push_back(std::to_string(e.add_replay_on_join)); - v.push_back(std::to_string(e.is_locked)); + v.push_back(std::to_string(e.bot_id)); + v.push_back(std::to_string(e.spell_id)); + v.push_back(std::to_string(e.blocked)); + v.push_back(std::to_string(e.blocked_pet)); auto results = db.QueryDatabase( fmt::format( @@ -202,7 +203,7 @@ public: ); if (results.Success()) { - e.id = results.LastInsertedID(); + e.bot_id = results.LastInsertedID(); return e; } @@ -213,7 +214,7 @@ public: static int InsertMany( Database& db, - const std::vector &entries + const std::vector &entries ) { std::vector insert_chunks; @@ -221,10 +222,10 @@ public: for (auto &e: entries) { std::vector v; - v.push_back(std::to_string(e.id)); - v.push_back(std::to_string(e.dynamic_zone_id)); - v.push_back(std::to_string(e.add_replay_on_join)); - v.push_back(std::to_string(e.is_locked)); + v.push_back(std::to_string(e.bot_id)); + v.push_back(std::to_string(e.spell_id)); + v.push_back(std::to_string(e.blocked)); + v.push_back(std::to_string(e.blocked_pet)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } @@ -242,9 +243,9 @@ public: return (results.Success() ? results.RowsAffected() : 0); } - static std::vector All(Database& db) + static std::vector All(Database& db) { - std::vector all_entries; + std::vector all_entries; auto results = db.QueryDatabase( fmt::format( @@ -256,12 +257,12 @@ public: all_entries.reserve(results.RowCount()); for (auto row = results.begin(); row != results.end(); ++row) { - Expeditions e{}; + BotBlockedBuffs e{}; - e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; - e.dynamic_zone_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; - e.add_replay_on_join = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 1; - e.is_locked = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.bot_id = row[0] ? static_cast(atoi(row[0])) : 0; + e.spell_id = row[1] ? static_cast(atoi(row[1])) : 0; + e.blocked = row[2] ? static_cast(atoi(row[2])) : 0; + e.blocked_pet = row[3] ? static_cast(atoi(row[3])) : 0; all_entries.push_back(e); } @@ -269,9 +270,9 @@ public: return all_entries; } - static std::vector GetWhere(Database& db, const std::string &where_filter) + static std::vector GetWhere(Database& db, const std::string &where_filter) { - std::vector all_entries; + std::vector all_entries; auto results = db.QueryDatabase( fmt::format( @@ -284,12 +285,12 @@ public: all_entries.reserve(results.RowCount()); for (auto row = results.begin(); row != results.end(); ++row) { - Expeditions e{}; + BotBlockedBuffs e{}; - e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; - e.dynamic_zone_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; - e.add_replay_on_join = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 1; - e.is_locked = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.bot_id = row[0] ? static_cast(atoi(row[0])) : 0; + e.spell_id = row[1] ? static_cast(atoi(row[1])) : 0; + e.blocked = row[2] ? static_cast(atoi(row[2])) : 0; + e.blocked_pet = row[3] ? static_cast(atoi(row[3])) : 0; all_entries.push_back(e); } @@ -359,15 +360,15 @@ public: static int ReplaceOne( Database& db, - const Expeditions &e + const BotBlockedBuffs &e ) { std::vector v; - v.push_back(std::to_string(e.id)); - v.push_back(std::to_string(e.dynamic_zone_id)); - v.push_back(std::to_string(e.add_replay_on_join)); - v.push_back(std::to_string(e.is_locked)); + v.push_back(std::to_string(e.bot_id)); + v.push_back(std::to_string(e.spell_id)); + v.push_back(std::to_string(e.blocked)); + v.push_back(std::to_string(e.blocked_pet)); auto results = db.QueryDatabase( fmt::format( @@ -382,7 +383,7 @@ public: static int ReplaceMany( Database& db, - const std::vector &entries + const std::vector &entries ) { std::vector insert_chunks; @@ -390,10 +391,10 @@ public: for (auto &e: entries) { std::vector v; - v.push_back(std::to_string(e.id)); - v.push_back(std::to_string(e.dynamic_zone_id)); - v.push_back(std::to_string(e.add_replay_on_join)); - v.push_back(std::to_string(e.is_locked)); + v.push_back(std::to_string(e.bot_id)); + v.push_back(std::to_string(e.spell_id)); + v.push_back(std::to_string(e.blocked)); + v.push_back(std::to_string(e.blocked_pet)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } @@ -412,4 +413,4 @@ public: } }; -#endif //EQEMU_BASE_EXPEDITIONS_REPOSITORY_H +#endif //EQEMU_BASE_BOT_BLOCKED_BUFFS_REPOSITORY_H diff --git a/common/repositories/base/base_bot_data_repository.h b/common/repositories/base/base_bot_data_repository.h index 96622c2db..2cd4b1d04 100644 --- a/common/repositories/base/base_bot_data_repository.h +++ b/common/repositories/base/base_bot_data_repository.h @@ -64,13 +64,6 @@ public: int16_t poison; int16_t disease; int16_t corruption; - uint32_t show_helm; - uint32_t follow_distance; - uint8_t stop_melee_level; - int32_t expansion_bitmask; - uint8_t enforce_spell_settings; - uint8_t archery_setting; - uint32_t caster_range; }; static std::string PrimaryKey() @@ -126,13 +119,6 @@ public: "poison", "disease", "corruption", - "show_helm", - "follow_distance", - "stop_melee_level", - "expansion_bitmask", - "enforce_spell_settings", - "archery_setting", - "caster_range", }; } @@ -184,13 +170,6 @@ public: "poison", "disease", "corruption", - "show_helm", - "follow_distance", - "stop_melee_level", - "expansion_bitmask", - "enforce_spell_settings", - "archery_setting", - "caster_range", }; } @@ -276,13 +255,7 @@ public: e.poison = 0; e.disease = 0; e.corruption = 0; - e.show_helm = 0; - e.follow_distance = 200; - e.stop_melee_level = 255; - e.expansion_bitmask = -1; - e.enforce_spell_settings = 0; - e.archery_setting = 0; - e.caster_range = 300; + return e; } @@ -364,13 +337,6 @@ public: e.poison = row[42] ? static_cast(atoi(row[42])) : 0; e.disease = row[43] ? static_cast(atoi(row[43])) : 0; e.corruption = row[44] ? static_cast(atoi(row[44])) : 0; - e.show_helm = row[45] ? static_cast(strtoul(row[45], nullptr, 10)) : 0; - e.follow_distance = row[46] ? static_cast(strtoul(row[46], nullptr, 10)) : 200; - e.stop_melee_level = row[47] ? static_cast(strtoul(row[47], nullptr, 10)) : 255; - e.expansion_bitmask = row[48] ? static_cast(atoi(row[48])) : -1; - e.enforce_spell_settings = row[49] ? static_cast(strtoul(row[49], nullptr, 10)) : 0; - e.archery_setting = row[50] ? static_cast(strtoul(row[50], nullptr, 10)) : 0; - e.caster_range = row[51] ? static_cast(strtoul(row[51], nullptr, 10)) : 300; return e; } @@ -383,14 +349,25 @@ public: int bot_data_id ) { - auto results = db.QueryDatabase( - fmt::format( + std::string query; + + if (RuleB(Bots, BotSoftDeletes)) { + query = fmt::format( + "UPDATE {} SET name = SUBSTRING(CONCAT(name, '-deleted-', UNIX_TIMESTAMP()), 1, 64) WHERE {} = {}", + TableName(), + PrimaryKey(), + bot_data_id + ); + } + else { + query = fmt::format( "DELETE FROM {} WHERE {} = {}", TableName(), PrimaryKey(), bot_data_id - ) - ); + ); + } + auto results = db.QueryDatabase(query); return (results.Success() ? results.RowsAffected() : 0); } @@ -448,13 +425,6 @@ public: v.push_back(columns[42] + " = " + std::to_string(e.poison)); v.push_back(columns[43] + " = " + std::to_string(e.disease)); v.push_back(columns[44] + " = " + std::to_string(e.corruption)); - v.push_back(columns[45] + " = " + std::to_string(e.show_helm)); - v.push_back(columns[46] + " = " + std::to_string(e.follow_distance)); - v.push_back(columns[47] + " = " + std::to_string(e.stop_melee_level)); - v.push_back(columns[48] + " = " + std::to_string(e.expansion_bitmask)); - v.push_back(columns[49] + " = " + std::to_string(e.enforce_spell_settings)); - v.push_back(columns[50] + " = " + std::to_string(e.archery_setting)); - v.push_back(columns[51] + " = " + std::to_string(e.caster_range)); auto results = db.QueryDatabase( fmt::format( @@ -521,13 +491,6 @@ public: v.push_back(std::to_string(e.poison)); v.push_back(std::to_string(e.disease)); v.push_back(std::to_string(e.corruption)); - v.push_back(std::to_string(e.show_helm)); - v.push_back(std::to_string(e.follow_distance)); - v.push_back(std::to_string(e.stop_melee_level)); - v.push_back(std::to_string(e.expansion_bitmask)); - v.push_back(std::to_string(e.enforce_spell_settings)); - v.push_back(std::to_string(e.archery_setting)); - v.push_back(std::to_string(e.caster_range)); auto results = db.QueryDatabase( fmt::format( @@ -602,13 +565,6 @@ public: v.push_back(std::to_string(e.poison)); v.push_back(std::to_string(e.disease)); v.push_back(std::to_string(e.corruption)); - v.push_back(std::to_string(e.show_helm)); - v.push_back(std::to_string(e.follow_distance)); - v.push_back(std::to_string(e.stop_melee_level)); - v.push_back(std::to_string(e.expansion_bitmask)); - v.push_back(std::to_string(e.enforce_spell_settings)); - v.push_back(std::to_string(e.archery_setting)); - v.push_back(std::to_string(e.caster_range)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } @@ -687,13 +643,6 @@ public: e.poison = row[42] ? static_cast(atoi(row[42])) : 0; e.disease = row[43] ? static_cast(atoi(row[43])) : 0; e.corruption = row[44] ? static_cast(atoi(row[44])) : 0; - e.show_helm = row[45] ? static_cast(strtoul(row[45], nullptr, 10)) : 0; - e.follow_distance = row[46] ? static_cast(strtoul(row[46], nullptr, 10)) : 200; - e.stop_melee_level = row[47] ? static_cast(strtoul(row[47], nullptr, 10)) : 255; - e.expansion_bitmask = row[48] ? static_cast(atoi(row[48])) : -1; - e.enforce_spell_settings = row[49] ? static_cast(strtoul(row[49], nullptr, 10)) : 0; - e.archery_setting = row[50] ? static_cast(strtoul(row[50], nullptr, 10)) : 0; - e.caster_range = row[51] ? static_cast(strtoul(row[51], nullptr, 10)) : 300; all_entries.push_back(e); } @@ -763,13 +712,6 @@ public: e.poison = row[42] ? static_cast(atoi(row[42])) : 0; e.disease = row[43] ? static_cast(atoi(row[43])) : 0; e.corruption = row[44] ? static_cast(atoi(row[44])) : 0; - e.show_helm = row[45] ? static_cast(strtoul(row[45], nullptr, 10)) : 0; - e.follow_distance = row[46] ? static_cast(strtoul(row[46], nullptr, 10)) : 200; - e.stop_melee_level = row[47] ? static_cast(strtoul(row[47], nullptr, 10)) : 255; - e.expansion_bitmask = row[48] ? static_cast(atoi(row[48])) : -1; - e.enforce_spell_settings = row[49] ? static_cast(strtoul(row[49], nullptr, 10)) : 0; - e.archery_setting = row[50] ? static_cast(strtoul(row[50], nullptr, 10)) : 0; - e.caster_range = row[51] ? static_cast(strtoul(row[51], nullptr, 10)) : 300; all_entries.push_back(e); } @@ -889,13 +831,6 @@ public: v.push_back(std::to_string(e.poison)); v.push_back(std::to_string(e.disease)); v.push_back(std::to_string(e.corruption)); - v.push_back(std::to_string(e.show_helm)); - v.push_back(std::to_string(e.follow_distance)); - v.push_back(std::to_string(e.stop_melee_level)); - v.push_back(std::to_string(e.expansion_bitmask)); - v.push_back(std::to_string(e.enforce_spell_settings)); - v.push_back(std::to_string(e.archery_setting)); - v.push_back(std::to_string(e.caster_range)); auto results = db.QueryDatabase( fmt::format( @@ -963,13 +898,6 @@ public: v.push_back(std::to_string(e.poison)); v.push_back(std::to_string(e.disease)); v.push_back(std::to_string(e.corruption)); - v.push_back(std::to_string(e.show_helm)); - v.push_back(std::to_string(e.follow_distance)); - v.push_back(std::to_string(e.stop_melee_level)); - v.push_back(std::to_string(e.expansion_bitmask)); - v.push_back(std::to_string(e.enforce_spell_settings)); - v.push_back(std::to_string(e.archery_setting)); - v.push_back(std::to_string(e.caster_range)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } diff --git a/common/repositories/base/base_bot_settings_repository.h b/common/repositories/base/base_bot_settings_repository.h new file mode 100644 index 000000000..8fbaff17d --- /dev/null +++ b/common/repositories/base/base_bot_settings_repository.h @@ -0,0 +1,464 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://docs.eqemu.io/developer/repositories + */ + +#ifndef EQEMU_BASE_BOT_SETTINGS_REPOSITORY_H +#define EQEMU_BASE_BOT_SETTINGS_REPOSITORY_H + +#include "../../database.h" +#include "../../strings.h" +#include + +class BaseBotSettingsRepository { +public: + struct BotSettings { + uint32_t character_id; + uint32_t bot_id; + uint8_t stance; + uint16_t setting_id; + uint8_t setting_type; + int32_t value; + std::string category_name; + std::string setting_name; + }; + + static std::string PrimaryKey() + { + return std::string("character_id"); + } + + static std::vector Columns() + { + return { + "character_id", + "bot_id", + "stance", + "setting_id", + "setting_type", + "value", + "category_name", + "setting_name", + }; + } + + static std::vector SelectColumns() + { + return { + "character_id", + "bot_id", + "stance", + "setting_id", + "setting_type", + "value", + "category_name", + "setting_name", + }; + } + + static std::string ColumnsRaw() + { + return std::string(Strings::Implode(", ", Columns())); + } + + static std::string SelectColumnsRaw() + { + return std::string(Strings::Implode(", ", SelectColumns())); + } + + static std::string TableName() + { + return std::string("bot_settings"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + SelectColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static BotSettings NewEntity() + { + BotSettings e{}; + + e.character_id = 0; + e.bot_id = 0; + e.stance = 0; + e.setting_id = 0; + e.setting_type = 0; + e.value = 0; + e.category_name = ""; + e.setting_name = ""; + + return e; + } + + static BotSettings GetBotSettings( + const std::vector &bot_settingss, + int bot_settings_id + ) + { + for (auto &bot_settings : bot_settingss) { + if (bot_settings.character_id == bot_settings_id) { + return bot_settings; + } + } + + return NewEntity(); + } + + static BotSettings FindOne( + Database& db, + int bot_settings_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {} = {} LIMIT 1", + BaseSelect(), + PrimaryKey(), + bot_settings_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + BotSettings e{}; + + e.character_id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.bot_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.stance = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.setting_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.setting_type = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.value = row[5] ? static_cast(atoi(row[5])) : 0; + e.category_name = row[6] ? row[6] : ""; + e.setting_name = row[7] ? row[7] : ""; + + return e; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int bot_settings_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + bot_settings_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + const BotSettings &e + ) + { + std::vector v; + + auto columns = Columns(); + + v.push_back(columns[0] + " = " + std::to_string(e.character_id)); + v.push_back(columns[1] + " = " + std::to_string(e.bot_id)); + v.push_back(columns[2] + " = " + std::to_string(e.stance)); + v.push_back(columns[3] + " = " + std::to_string(e.setting_id)); + v.push_back(columns[4] + " = " + std::to_string(e.setting_type)); + v.push_back(columns[5] + " = " + std::to_string(e.value)); + v.push_back(columns[6] + " = '" + Strings::Escape(e.category_name) + "'"); + v.push_back(columns[7] + " = '" + Strings::Escape(e.setting_name) + "'"); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + Strings::Implode(", ", v), + PrimaryKey(), + e.character_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static BotSettings InsertOne( + Database& db, + BotSettings e + ) + { + std::vector v; + + v.push_back(std::to_string(e.character_id)); + v.push_back(std::to_string(e.bot_id)); + v.push_back(std::to_string(e.stance)); + v.push_back(std::to_string(e.setting_id)); + v.push_back(std::to_string(e.setting_type)); + v.push_back(std::to_string(e.value)); + v.push_back("'" + Strings::Escape(e.category_name) + "'"); + v.push_back("'" + Strings::Escape(e.setting_name) + "'"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + Strings::Implode(",", v) + ) + ); + + if (results.Success()) { + e.character_id = results.LastInsertedID(); + return e; + } + + e = NewEntity(); + + return e; + } + + static int InsertMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.character_id)); + v.push_back(std::to_string(e.bot_id)); + v.push_back(std::to_string(e.stance)); + v.push_back(std::to_string(e.setting_id)); + v.push_back(std::to_string(e.setting_type)); + v.push_back(std::to_string(e.value)); + v.push_back("'" + Strings::Escape(e.category_name) + "'"); + v.push_back("'" + Strings::Escape(e.setting_name) + "'"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + BotSettings e{}; + + e.character_id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.bot_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.stance = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.setting_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.setting_type = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.value = row[5] ? static_cast(atoi(row[5])) : 0; + e.category_name = row[6] ? row[6] : ""; + e.setting_name = row[7] ? row[7] : ""; + + all_entries.push_back(e); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, const std::string &where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + BotSettings e{}; + + e.character_id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.bot_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.stance = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.setting_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.setting_type = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.value = row[5] ? static_cast(atoi(row[5])) : 0; + e.category_name = row[6] ? row[6] : ""; + e.setting_name = row[7] ? row[7] : ""; + + all_entries.push_back(e); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, const std::string &where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int64 GetMaxId(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COALESCE(MAX({}), 0) FROM {}", + PrimaryKey(), + TableName() + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static int64 Count(Database& db, const std::string &where_filter = "") + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COUNT(*) FROM {} {}", + TableName(), + (where_filter.empty() ? "" : "WHERE " + where_filter) + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static std::string BaseReplace() + { + return fmt::format( + "REPLACE INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static int ReplaceOne( + Database& db, + const BotSettings &e + ) + { + std::vector v; + + v.push_back(std::to_string(e.character_id)); + v.push_back(std::to_string(e.bot_id)); + v.push_back(std::to_string(e.stance)); + v.push_back(std::to_string(e.setting_id)); + v.push_back(std::to_string(e.setting_type)); + v.push_back(std::to_string(e.value)); + v.push_back("'" + Strings::Escape(e.category_name) + "'"); + v.push_back("'" + Strings::Escape(e.setting_name) + "'"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseReplace(), + Strings::Implode(",", v) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int ReplaceMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.character_id)); + v.push_back(std::to_string(e.bot_id)); + v.push_back(std::to_string(e.stance)); + v.push_back(std::to_string(e.setting_id)); + v.push_back(std::to_string(e.setting_type)); + v.push_back(std::to_string(e.value)); + v.push_back("'" + Strings::Escape(e.category_name) + "'"); + v.push_back("'" + Strings::Escape(e.setting_name) + "'"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseReplace(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } +}; + +#endif //EQEMU_BASE_BOT_SETTINGS_REPOSITORY_H diff --git a/common/repositories/base/base_character_corpses_repository.h b/common/repositories/base/base_character_corpses_repository.h index de6aebca9..2c61799ea 100644 --- a/common/repositories/base/base_character_corpses_repository.h +++ b/common/repositories/base/base_character_corpses_repository.h @@ -70,6 +70,7 @@ public: uint32_t gm_exp; uint32_t killed_by; uint8_t rezzable; + std::string entity_variables; }; static std::string PrimaryKey() @@ -131,6 +132,7 @@ public: "gm_exp", "killed_by", "rezzable", + "entity_variables", }; } @@ -188,6 +190,7 @@ public: "gm_exp", "killed_by", "rezzable", + "entity_variables", }; } @@ -279,6 +282,7 @@ public: e.gm_exp = 0; e.killed_by = 0; e.rezzable = 0; + e.entity_variables = ""; return e; } @@ -366,6 +370,7 @@ public: e.gm_exp = row[48] ? static_cast(strtoul(row[48], nullptr, 10)) : 0; e.killed_by = row[49] ? static_cast(strtoul(row[49], nullptr, 10)) : 0; e.rezzable = row[50] ? static_cast(strtoul(row[50], nullptr, 10)) : 0; + e.entity_variables = row[51] ? row[51] : ""; return e; } @@ -449,6 +454,7 @@ public: v.push_back(columns[48] + " = " + std::to_string(e.gm_exp)); v.push_back(columns[49] + " = " + std::to_string(e.killed_by)); v.push_back(columns[50] + " = " + std::to_string(e.rezzable)); + v.push_back(columns[51] + " = '" + Strings::Escape(e.entity_variables) + "'"); auto results = db.QueryDatabase( fmt::format( @@ -521,6 +527,7 @@ public: v.push_back(std::to_string(e.gm_exp)); v.push_back(std::to_string(e.killed_by)); v.push_back(std::to_string(e.rezzable)); + v.push_back("'" + Strings::Escape(e.entity_variables) + "'"); auto results = db.QueryDatabase( fmt::format( @@ -601,6 +608,7 @@ public: v.push_back(std::to_string(e.gm_exp)); v.push_back(std::to_string(e.killed_by)); v.push_back(std::to_string(e.rezzable)); + v.push_back("'" + Strings::Escape(e.entity_variables) + "'"); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } @@ -685,6 +693,7 @@ public: e.gm_exp = row[48] ? static_cast(strtoul(row[48], nullptr, 10)) : 0; e.killed_by = row[49] ? static_cast(strtoul(row[49], nullptr, 10)) : 0; e.rezzable = row[50] ? static_cast(strtoul(row[50], nullptr, 10)) : 0; + e.entity_variables = row[51] ? row[51] : ""; all_entries.push_back(e); } @@ -760,6 +769,7 @@ public: e.gm_exp = row[48] ? static_cast(strtoul(row[48], nullptr, 10)) : 0; e.killed_by = row[49] ? static_cast(strtoul(row[49], nullptr, 10)) : 0; e.rezzable = row[50] ? static_cast(strtoul(row[50], nullptr, 10)) : 0; + e.entity_variables = row[51] ? row[51] : ""; all_entries.push_back(e); } @@ -885,6 +895,7 @@ public: v.push_back(std::to_string(e.gm_exp)); v.push_back(std::to_string(e.killed_by)); v.push_back(std::to_string(e.rezzable)); + v.push_back("'" + Strings::Escape(e.entity_variables) + "'"); auto results = db.QueryDatabase( fmt::format( @@ -958,6 +969,7 @@ public: v.push_back(std::to_string(e.gm_exp)); v.push_back(std::to_string(e.killed_by)); v.push_back(std::to_string(e.rezzable)); + v.push_back("'" + Strings::Escape(e.entity_variables) + "'"); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } diff --git a/common/repositories/base/base_character_data_repository.h b/common/repositories/base/base_character_data_repository.h index 4e66dbd72..85374f7fc 100644 --- a/common/repositories/base/base_character_data_repository.h +++ b/common/repositories/base/base_character_data_repository.h @@ -115,7 +115,8 @@ public: uint8_t lfg; std::string mailkey; uint8_t xtargets; - int8_t firstlogon; + uint8_t ingame; + uint32_t first_login; uint32_t e_aa_effects; uint32_t e_percent_to_aa; uint32_t e_expended_aa_spent; @@ -123,6 +124,7 @@ public: uint32_t aa_points_old; uint32_t e_last_invsnapshot; time_t deleted_at; + uint8_t illusion_block; }; static std::string PrimaryKey() @@ -229,7 +231,8 @@ public: "lfg", "mailkey", "xtargets", - "firstlogon", + "ingame", + "first_login", "e_aa_effects", "e_percent_to_aa", "e_expended_aa_spent", @@ -237,6 +240,7 @@ public: "aa_points_old", "e_last_invsnapshot", "deleted_at", + "illusion_block", }; } @@ -339,7 +343,8 @@ public: "lfg", "mailkey", "xtargets", - "firstlogon", + "ingame", + "first_login", "e_aa_effects", "e_percent_to_aa", "e_expended_aa_spent", @@ -347,6 +352,7 @@ public: "aa_points_old", "e_last_invsnapshot", "UNIX_TIMESTAMP(deleted_at)", + "illusion_block", }; } @@ -483,7 +489,8 @@ public: e.lfg = 0; e.mailkey = ""; e.xtargets = 5; - e.firstlogon = 0; + e.ingame = 0; + e.first_login = 0; e.e_aa_effects = 0; e.e_percent_to_aa = 0; e.e_expended_aa_spent = 0; @@ -491,6 +498,7 @@ public: e.aa_points_old = 0; e.e_last_invsnapshot = 0; e.deleted_at = 0; + e.illusion_block = 0; return e; } @@ -623,14 +631,16 @@ public: e.lfg = row[93] ? static_cast(strtoul(row[93], nullptr, 10)) : 0; e.mailkey = row[94] ? row[94] : ""; e.xtargets = row[95] ? static_cast(strtoul(row[95], nullptr, 10)) : 5; - e.firstlogon = row[96] ? static_cast(atoi(row[96])) : 0; - e.e_aa_effects = row[97] ? static_cast(strtoul(row[97], nullptr, 10)) : 0; - e.e_percent_to_aa = row[98] ? static_cast(strtoul(row[98], nullptr, 10)) : 0; - e.e_expended_aa_spent = row[99] ? static_cast(strtoul(row[99], nullptr, 10)) : 0; - e.aa_points_spent_old = row[100] ? static_cast(strtoul(row[100], nullptr, 10)) : 0; - e.aa_points_old = row[101] ? static_cast(strtoul(row[101], nullptr, 10)) : 0; - e.e_last_invsnapshot = row[102] ? static_cast(strtoul(row[102], nullptr, 10)) : 0; - e.deleted_at = strtoll(row[103] ? row[103] : "-1", nullptr, 10); + e.ingame = row[96] ? static_cast(strtoul(row[96], nullptr, 10)) : 0; + e.first_login = row[97] ? static_cast(strtoul(row[97], nullptr, 10)) : 0; + e.e_aa_effects = row[98] ? static_cast(strtoul(row[98], nullptr, 10)) : 0; + e.e_percent_to_aa = row[99] ? static_cast(strtoul(row[99], nullptr, 10)) : 0; + e.e_expended_aa_spent = row[100] ? static_cast(strtoul(row[100], nullptr, 10)) : 0; + e.aa_points_spent_old = row[101] ? static_cast(strtoul(row[101], nullptr, 10)) : 0; + e.aa_points_old = row[102] ? static_cast(strtoul(row[102], nullptr, 10)) : 0; + e.e_last_invsnapshot = row[103] ? static_cast(strtoul(row[103], nullptr, 10)) : 0; + e.deleted_at = strtoll(row[104] ? row[104] : "-1", nullptr, 10); + e.illusion_block = row[105] ? static_cast(strtoul(row[105], nullptr, 10)) : 0; return e; } @@ -759,14 +769,16 @@ public: v.push_back(columns[93] + " = " + std::to_string(e.lfg)); v.push_back(columns[94] + " = '" + Strings::Escape(e.mailkey) + "'"); v.push_back(columns[95] + " = " + std::to_string(e.xtargets)); - v.push_back(columns[96] + " = " + std::to_string(e.firstlogon)); - v.push_back(columns[97] + " = " + std::to_string(e.e_aa_effects)); - v.push_back(columns[98] + " = " + std::to_string(e.e_percent_to_aa)); - v.push_back(columns[99] + " = " + std::to_string(e.e_expended_aa_spent)); - v.push_back(columns[100] + " = " + std::to_string(e.aa_points_spent_old)); - v.push_back(columns[101] + " = " + std::to_string(e.aa_points_old)); - v.push_back(columns[102] + " = " + std::to_string(e.e_last_invsnapshot)); - v.push_back(columns[103] + " = FROM_UNIXTIME(" + (e.deleted_at > 0 ? std::to_string(e.deleted_at) : "null") + ")"); + v.push_back(columns[96] + " = " + std::to_string(e.ingame)); + v.push_back(columns[97] + " = " + std::to_string(e.first_login)); + v.push_back(columns[98] + " = " + std::to_string(e.e_aa_effects)); + v.push_back(columns[99] + " = " + std::to_string(e.e_percent_to_aa)); + v.push_back(columns[100] + " = " + std::to_string(e.e_expended_aa_spent)); + v.push_back(columns[101] + " = " + std::to_string(e.aa_points_spent_old)); + v.push_back(columns[102] + " = " + std::to_string(e.aa_points_old)); + v.push_back(columns[103] + " = " + std::to_string(e.e_last_invsnapshot)); + v.push_back(columns[104] + " = FROM_UNIXTIME(" + (e.deleted_at > 0 ? std::to_string(e.deleted_at) : "null") + ")"); + v.push_back(columns[105] + " = " + std::to_string(e.illusion_block)); auto results = db.QueryDatabase( fmt::format( @@ -884,7 +896,8 @@ public: v.push_back(std::to_string(e.lfg)); v.push_back("'" + Strings::Escape(e.mailkey) + "'"); v.push_back(std::to_string(e.xtargets)); - v.push_back(std::to_string(e.firstlogon)); + v.push_back(std::to_string(e.ingame)); + v.push_back(std::to_string(e.first_login)); v.push_back(std::to_string(e.e_aa_effects)); v.push_back(std::to_string(e.e_percent_to_aa)); v.push_back(std::to_string(e.e_expended_aa_spent)); @@ -892,6 +905,7 @@ public: v.push_back(std::to_string(e.aa_points_old)); v.push_back(std::to_string(e.e_last_invsnapshot)); v.push_back("FROM_UNIXTIME(" + (e.deleted_at > 0 ? std::to_string(e.deleted_at) : "null") + ")"); + v.push_back(std::to_string(e.illusion_block)); auto results = db.QueryDatabase( fmt::format( @@ -1017,7 +1031,8 @@ public: v.push_back(std::to_string(e.lfg)); v.push_back("'" + Strings::Escape(e.mailkey) + "'"); v.push_back(std::to_string(e.xtargets)); - v.push_back(std::to_string(e.firstlogon)); + v.push_back(std::to_string(e.ingame)); + v.push_back(std::to_string(e.first_login)); v.push_back(std::to_string(e.e_aa_effects)); v.push_back(std::to_string(e.e_percent_to_aa)); v.push_back(std::to_string(e.e_expended_aa_spent)); @@ -1025,6 +1040,7 @@ public: v.push_back(std::to_string(e.aa_points_old)); v.push_back(std::to_string(e.e_last_invsnapshot)); v.push_back("FROM_UNIXTIME(" + (e.deleted_at > 0 ? std::to_string(e.deleted_at) : "null") + ")"); + v.push_back(std::to_string(e.illusion_block)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } @@ -1154,14 +1170,16 @@ public: e.lfg = row[93] ? static_cast(strtoul(row[93], nullptr, 10)) : 0; e.mailkey = row[94] ? row[94] : ""; e.xtargets = row[95] ? static_cast(strtoul(row[95], nullptr, 10)) : 5; - e.firstlogon = row[96] ? static_cast(atoi(row[96])) : 0; - e.e_aa_effects = row[97] ? static_cast(strtoul(row[97], nullptr, 10)) : 0; - e.e_percent_to_aa = row[98] ? static_cast(strtoul(row[98], nullptr, 10)) : 0; - e.e_expended_aa_spent = row[99] ? static_cast(strtoul(row[99], nullptr, 10)) : 0; - e.aa_points_spent_old = row[100] ? static_cast(strtoul(row[100], nullptr, 10)) : 0; - e.aa_points_old = row[101] ? static_cast(strtoul(row[101], nullptr, 10)) : 0; - e.e_last_invsnapshot = row[102] ? static_cast(strtoul(row[102], nullptr, 10)) : 0; - e.deleted_at = strtoll(row[103] ? row[103] : "-1", nullptr, 10); + e.ingame = row[96] ? static_cast(strtoul(row[96], nullptr, 10)) : 0; + e.first_login = row[97] ? static_cast(strtoul(row[97], nullptr, 10)) : 0; + e.e_aa_effects = row[98] ? static_cast(strtoul(row[98], nullptr, 10)) : 0; + e.e_percent_to_aa = row[99] ? static_cast(strtoul(row[99], nullptr, 10)) : 0; + e.e_expended_aa_spent = row[100] ? static_cast(strtoul(row[100], nullptr, 10)) : 0; + e.aa_points_spent_old = row[101] ? static_cast(strtoul(row[101], nullptr, 10)) : 0; + e.aa_points_old = row[102] ? static_cast(strtoul(row[102], nullptr, 10)) : 0; + e.e_last_invsnapshot = row[103] ? static_cast(strtoul(row[103], nullptr, 10)) : 0; + e.deleted_at = strtoll(row[104] ? row[104] : "-1", nullptr, 10); + e.illusion_block = row[105] ? static_cast(strtoul(row[105], nullptr, 10)) : 0; all_entries.push_back(e); } @@ -1282,14 +1300,16 @@ public: e.lfg = row[93] ? static_cast(strtoul(row[93], nullptr, 10)) : 0; e.mailkey = row[94] ? row[94] : ""; e.xtargets = row[95] ? static_cast(strtoul(row[95], nullptr, 10)) : 5; - e.firstlogon = row[96] ? static_cast(atoi(row[96])) : 0; - e.e_aa_effects = row[97] ? static_cast(strtoul(row[97], nullptr, 10)) : 0; - e.e_percent_to_aa = row[98] ? static_cast(strtoul(row[98], nullptr, 10)) : 0; - e.e_expended_aa_spent = row[99] ? static_cast(strtoul(row[99], nullptr, 10)) : 0; - e.aa_points_spent_old = row[100] ? static_cast(strtoul(row[100], nullptr, 10)) : 0; - e.aa_points_old = row[101] ? static_cast(strtoul(row[101], nullptr, 10)) : 0; - e.e_last_invsnapshot = row[102] ? static_cast(strtoul(row[102], nullptr, 10)) : 0; - e.deleted_at = strtoll(row[103] ? row[103] : "-1", nullptr, 10); + e.ingame = row[96] ? static_cast(strtoul(row[96], nullptr, 10)) : 0; + e.first_login = row[97] ? static_cast(strtoul(row[97], nullptr, 10)) : 0; + e.e_aa_effects = row[98] ? static_cast(strtoul(row[98], nullptr, 10)) : 0; + e.e_percent_to_aa = row[99] ? static_cast(strtoul(row[99], nullptr, 10)) : 0; + e.e_expended_aa_spent = row[100] ? static_cast(strtoul(row[100], nullptr, 10)) : 0; + e.aa_points_spent_old = row[101] ? static_cast(strtoul(row[101], nullptr, 10)) : 0; + e.aa_points_old = row[102] ? static_cast(strtoul(row[102], nullptr, 10)) : 0; + e.e_last_invsnapshot = row[103] ? static_cast(strtoul(row[103], nullptr, 10)) : 0; + e.deleted_at = strtoll(row[104] ? row[104] : "-1", nullptr, 10); + e.illusion_block = row[105] ? static_cast(strtoul(row[105], nullptr, 10)) : 0; all_entries.push_back(e); } @@ -1460,7 +1480,8 @@ public: v.push_back(std::to_string(e.lfg)); v.push_back("'" + Strings::Escape(e.mailkey) + "'"); v.push_back(std::to_string(e.xtargets)); - v.push_back(std::to_string(e.firstlogon)); + v.push_back(std::to_string(e.ingame)); + v.push_back(std::to_string(e.first_login)); v.push_back(std::to_string(e.e_aa_effects)); v.push_back(std::to_string(e.e_percent_to_aa)); v.push_back(std::to_string(e.e_expended_aa_spent)); @@ -1468,6 +1489,7 @@ public: v.push_back(std::to_string(e.aa_points_old)); v.push_back(std::to_string(e.e_last_invsnapshot)); v.push_back("FROM_UNIXTIME(" + (e.deleted_at > 0 ? std::to_string(e.deleted_at) : "null") + ")"); + v.push_back(std::to_string(e.illusion_block)); auto results = db.QueryDatabase( fmt::format( @@ -1586,7 +1608,8 @@ public: v.push_back(std::to_string(e.lfg)); v.push_back("'" + Strings::Escape(e.mailkey) + "'"); v.push_back(std::to_string(e.xtargets)); - v.push_back(std::to_string(e.firstlogon)); + v.push_back(std::to_string(e.ingame)); + v.push_back(std::to_string(e.first_login)); v.push_back(std::to_string(e.e_aa_effects)); v.push_back(std::to_string(e.e_percent_to_aa)); v.push_back(std::to_string(e.e_expended_aa_spent)); @@ -1594,6 +1617,7 @@ public: v.push_back(std::to_string(e.aa_points_old)); v.push_back(std::to_string(e.e_last_invsnapshot)); v.push_back("FROM_UNIXTIME(" + (e.deleted_at > 0 ? std::to_string(e.deleted_at) : "null") + ")"); + v.push_back(std::to_string(e.illusion_block)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } diff --git a/common/repositories/base/base_character_evolving_items_repository.h b/common/repositories/base/base_character_evolving_items_repository.h new file mode 100644 index 000000000..9aa6c1773 --- /dev/null +++ b/common/repositories/base/base_character_evolving_items_repository.h @@ -0,0 +1,475 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://docs.eqemu.io/developer/repositories + */ + +#ifndef EQEMU_BASE_CHARACTER_EVOLVING_ITEMS_REPOSITORY_H +#define EQEMU_BASE_CHARACTER_EVOLVING_ITEMS_REPOSITORY_H + +#include "../../database.h" +#include "../../strings.h" +#include + +class BaseCharacterEvolvingItemsRepository { +public: + struct CharacterEvolvingItems { + uint64_t id; + uint32_t character_id; + uint32_t item_id; + uint8_t activated; + uint8_t equipped; + int64_t current_amount; + double progression; + uint32_t final_item_id; + time_t deleted_at; + }; + + static std::string PrimaryKey() + { + return std::string("id"); + } + + static std::vector Columns() + { + return { + "id", + "character_id", + "item_id", + "activated", + "equipped", + "current_amount", + "progression", + "final_item_id", + "deleted_at", + }; + } + + static std::vector SelectColumns() + { + return { + "id", + "character_id", + "item_id", + "activated", + "equipped", + "current_amount", + "progression", + "final_item_id", + "UNIX_TIMESTAMP(deleted_at)", + }; + } + + static std::string ColumnsRaw() + { + return std::string(Strings::Implode(", ", Columns())); + } + + static std::string SelectColumnsRaw() + { + return std::string(Strings::Implode(", ", SelectColumns())); + } + + static std::string TableName() + { + return std::string("character_evolving_items"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + SelectColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static CharacterEvolvingItems NewEntity() + { + CharacterEvolvingItems e{}; + + e.id = 0; + e.character_id = 0; + e.item_id = 0; + e.activated = 0; + e.equipped = 0; + e.current_amount = 0; + e.progression = 0; + e.final_item_id = 0; + e.deleted_at = 0; + + return e; + } + + static CharacterEvolvingItems GetCharacterEvolvingItems( + const std::vector &character_evolving_itemss, + int character_evolving_items_id + ) + { + for (auto &character_evolving_items : character_evolving_itemss) { + if (character_evolving_items.id == character_evolving_items_id) { + return character_evolving_items; + } + } + + return NewEntity(); + } + + static CharacterEvolvingItems FindOne( + Database& db, + int character_evolving_items_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {} = {} LIMIT 1", + BaseSelect(), + PrimaryKey(), + character_evolving_items_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + CharacterEvolvingItems e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.character_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.activated = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.equipped = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.current_amount = row[5] ? strtoll(row[5], nullptr, 10) : 0; + e.progression = row[6] ? strtod(row[6], nullptr) : 0; + e.final_item_id = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.deleted_at = strtoll(row[8] ? row[8] : "-1", nullptr, 10); + + return e; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int character_evolving_items_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + character_evolving_items_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + const CharacterEvolvingItems &e + ) + { + std::vector v; + + auto columns = Columns(); + + v.push_back(columns[1] + " = " + std::to_string(e.character_id)); + v.push_back(columns[2] + " = " + std::to_string(e.item_id)); + v.push_back(columns[3] + " = " + std::to_string(e.activated)); + v.push_back(columns[4] + " = " + std::to_string(e.equipped)); + v.push_back(columns[5] + " = " + std::to_string(e.current_amount)); + v.push_back(columns[6] + " = " + std::to_string(e.progression)); + v.push_back(columns[7] + " = " + std::to_string(e.final_item_id)); + v.push_back(columns[8] + " = FROM_UNIXTIME(" + (e.deleted_at > 0 ? std::to_string(e.deleted_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + Strings::Implode(", ", v), + PrimaryKey(), + e.id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static CharacterEvolvingItems InsertOne( + Database& db, + CharacterEvolvingItems e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.character_id)); + v.push_back(std::to_string(e.item_id)); + v.push_back(std::to_string(e.activated)); + v.push_back(std::to_string(e.equipped)); + v.push_back(std::to_string(e.current_amount)); + v.push_back(std::to_string(e.progression)); + v.push_back(std::to_string(e.final_item_id)); + v.push_back("FROM_UNIXTIME(" + (e.deleted_at > 0 ? std::to_string(e.deleted_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + Strings::Implode(",", v) + ) + ); + + if (results.Success()) { + e.id = results.LastInsertedID(); + return e; + } + + e = NewEntity(); + + return e; + } + + static int InsertMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.character_id)); + v.push_back(std::to_string(e.item_id)); + v.push_back(std::to_string(e.activated)); + v.push_back(std::to_string(e.equipped)); + v.push_back(std::to_string(e.current_amount)); + v.push_back(std::to_string(e.progression)); + v.push_back(std::to_string(e.final_item_id)); + v.push_back("FROM_UNIXTIME(" + (e.deleted_at > 0 ? std::to_string(e.deleted_at) : "null") + ")"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + CharacterEvolvingItems e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.character_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.activated = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.equipped = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.current_amount = row[5] ? strtoll(row[5], nullptr, 10) : 0; + e.progression = row[6] ? strtod(row[6], nullptr) : 0; + e.final_item_id = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.deleted_at = strtoll(row[8] ? row[8] : "-1", nullptr, 10); + + all_entries.push_back(e); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, const std::string &where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + CharacterEvolvingItems e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.character_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.activated = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.equipped = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.current_amount = row[5] ? strtoll(row[5], nullptr, 10) : 0; + e.progression = row[6] ? strtod(row[6], nullptr) : 0; + e.final_item_id = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.deleted_at = strtoll(row[8] ? row[8] : "-1", nullptr, 10); + + all_entries.push_back(e); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, const std::string &where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int64 GetMaxId(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COALESCE(MAX({}), 0) FROM {}", + PrimaryKey(), + TableName() + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static int64 Count(Database& db, const std::string &where_filter = "") + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COUNT(*) FROM {} {}", + TableName(), + (where_filter.empty() ? "" : "WHERE " + where_filter) + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static std::string BaseReplace() + { + return fmt::format( + "REPLACE INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static int ReplaceOne( + Database& db, + const CharacterEvolvingItems &e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.character_id)); + v.push_back(std::to_string(e.item_id)); + v.push_back(std::to_string(e.activated)); + v.push_back(std::to_string(e.equipped)); + v.push_back(std::to_string(e.current_amount)); + v.push_back(std::to_string(e.progression)); + v.push_back(std::to_string(e.final_item_id)); + v.push_back("FROM_UNIXTIME(" + (e.deleted_at > 0 ? std::to_string(e.deleted_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseReplace(), + Strings::Implode(",", v) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int ReplaceMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.character_id)); + v.push_back(std::to_string(e.item_id)); + v.push_back(std::to_string(e.activated)); + v.push_back(std::to_string(e.equipped)); + v.push_back(std::to_string(e.current_amount)); + v.push_back(std::to_string(e.progression)); + v.push_back(std::to_string(e.final_item_id)); + v.push_back("FROM_UNIXTIME(" + (e.deleted_at > 0 ? std::to_string(e.deleted_at) : "null") + ")"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseReplace(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } +}; + +#endif //EQEMU_BASE_CHARACTER_EVOLVING_ITEMS_REPOSITORY_H diff --git a/common/repositories/base/base_character_parcels_containers_repository.h b/common/repositories/base/base_character_parcels_containers_repository.h index 9c98cc068..12ca611b7 100644 --- a/common/repositories/base/base_character_parcels_containers_repository.h +++ b/common/repositories/base/base_character_parcels_containers_repository.h @@ -30,6 +30,7 @@ public: uint32_t aug_slot_5; uint32_t aug_slot_6; uint32_t quantity; + uint32_t evolve_amount; }; static std::string PrimaryKey() @@ -51,6 +52,7 @@ public: "aug_slot_5", "aug_slot_6", "quantity", + "evolve_amount", }; } @@ -68,6 +70,7 @@ public: "aug_slot_5", "aug_slot_6", "quantity", + "evolve_amount", }; } @@ -108,17 +111,18 @@ public: { CharacterParcelsContainers e{}; - e.id = 0; - e.parcels_id = 0; - e.slot_id = 0; - e.item_id = 0; - e.aug_slot_1 = 0; - e.aug_slot_2 = 0; - e.aug_slot_3 = 0; - e.aug_slot_4 = 0; - e.aug_slot_5 = 0; - e.aug_slot_6 = 0; - e.quantity = 0; + e.id = 0; + e.parcels_id = 0; + e.slot_id = 0; + e.item_id = 0; + e.aug_slot_1 = 0; + e.aug_slot_2 = 0; + e.aug_slot_3 = 0; + e.aug_slot_4 = 0; + e.aug_slot_5 = 0; + e.aug_slot_6 = 0; + e.quantity = 0; + e.evolve_amount = 0; return e; } @@ -155,17 +159,18 @@ public: if (results.RowCount() == 1) { CharacterParcelsContainers e{}; - e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; - e.parcels_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; - e.slot_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; - e.item_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; - e.aug_slot_1 = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; - e.aug_slot_2 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; - e.aug_slot_3 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; - e.aug_slot_4 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; - e.aug_slot_5 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; - e.aug_slot_6 = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; - e.quantity = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; + e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.parcels_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.slot_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.item_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.aug_slot_1 = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.aug_slot_2 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.aug_slot_3 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.aug_slot_4 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.aug_slot_5 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.aug_slot_6 = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.quantity = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; + e.evolve_amount = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; return e; } @@ -209,6 +214,7 @@ public: v.push_back(columns[8] + " = " + std::to_string(e.aug_slot_5)); v.push_back(columns[9] + " = " + std::to_string(e.aug_slot_6)); v.push_back(columns[10] + " = " + std::to_string(e.quantity)); + v.push_back(columns[11] + " = " + std::to_string(e.evolve_amount)); auto results = db.QueryDatabase( fmt::format( @@ -241,6 +247,7 @@ public: v.push_back(std::to_string(e.aug_slot_5)); v.push_back(std::to_string(e.aug_slot_6)); v.push_back(std::to_string(e.quantity)); + v.push_back(std::to_string(e.evolve_amount)); auto results = db.QueryDatabase( fmt::format( @@ -281,6 +288,7 @@ public: v.push_back(std::to_string(e.aug_slot_5)); v.push_back(std::to_string(e.aug_slot_6)); v.push_back(std::to_string(e.quantity)); + v.push_back(std::to_string(e.evolve_amount)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } @@ -314,17 +322,18 @@ public: for (auto row = results.begin(); row != results.end(); ++row) { CharacterParcelsContainers e{}; - e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; - e.parcels_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; - e.slot_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; - e.item_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; - e.aug_slot_1 = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; - e.aug_slot_2 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; - e.aug_slot_3 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; - e.aug_slot_4 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; - e.aug_slot_5 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; - e.aug_slot_6 = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; - e.quantity = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; + e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.parcels_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.slot_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.item_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.aug_slot_1 = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.aug_slot_2 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.aug_slot_3 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.aug_slot_4 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.aug_slot_5 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.aug_slot_6 = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.quantity = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; + e.evolve_amount = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; all_entries.push_back(e); } @@ -349,17 +358,18 @@ public: for (auto row = results.begin(); row != results.end(); ++row) { CharacterParcelsContainers e{}; - e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; - e.parcels_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; - e.slot_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; - e.item_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; - e.aug_slot_1 = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; - e.aug_slot_2 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; - e.aug_slot_3 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; - e.aug_slot_4 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; - e.aug_slot_5 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; - e.aug_slot_6 = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; - e.quantity = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; + e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.parcels_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.slot_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.item_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.aug_slot_1 = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.aug_slot_2 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.aug_slot_3 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.aug_slot_4 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.aug_slot_5 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.aug_slot_6 = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.quantity = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; + e.evolve_amount = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; all_entries.push_back(e); } @@ -445,6 +455,7 @@ public: v.push_back(std::to_string(e.aug_slot_5)); v.push_back(std::to_string(e.aug_slot_6)); v.push_back(std::to_string(e.quantity)); + v.push_back(std::to_string(e.evolve_amount)); auto results = db.QueryDatabase( fmt::format( @@ -478,6 +489,7 @@ public: v.push_back(std::to_string(e.aug_slot_5)); v.push_back(std::to_string(e.aug_slot_6)); v.push_back(std::to_string(e.quantity)); + v.push_back(std::to_string(e.evolve_amount)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } diff --git a/common/repositories/base/base_character_parcels_repository.h b/common/repositories/base/base_character_parcels_repository.h index 9263f307b..df7242aed 100644 --- a/common/repositories/base/base_character_parcels_repository.h +++ b/common/repositories/base/base_character_parcels_repository.h @@ -30,6 +30,7 @@ public: uint32_t aug_slot_6; uint32_t slot_id; uint32_t quantity; + uint32_t evolve_amount; std::string from_name; std::string note; time_t sent_date; @@ -54,6 +55,7 @@ public: "aug_slot_6", "slot_id", "quantity", + "evolve_amount", "from_name", "note", "sent_date", @@ -74,6 +76,7 @@ public: "aug_slot_6", "slot_id", "quantity", + "evolve_amount", "from_name", "note", "UNIX_TIMESTAMP(sent_date)", @@ -117,20 +120,21 @@ public: { CharacterParcels e{}; - e.id = 0; - e.char_id = 0; - e.item_id = 0; - e.aug_slot_1 = 0; - e.aug_slot_2 = 0; - e.aug_slot_3 = 0; - e.aug_slot_4 = 0; - e.aug_slot_5 = 0; - e.aug_slot_6 = 0; - e.slot_id = 0; - e.quantity = 0; - e.from_name = ""; - e.note = ""; - e.sent_date = 0; + e.id = 0; + e.char_id = 0; + e.item_id = 0; + e.aug_slot_1 = 0; + e.aug_slot_2 = 0; + e.aug_slot_3 = 0; + e.aug_slot_4 = 0; + e.aug_slot_5 = 0; + e.aug_slot_6 = 0; + e.slot_id = 0; + e.quantity = 0; + e.evolve_amount = 0; + e.from_name = ""; + e.note = ""; + e.sent_date = 0; return e; } @@ -167,20 +171,21 @@ public: if (results.RowCount() == 1) { CharacterParcels e{}; - e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; - e.char_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; - e.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; - e.aug_slot_1 = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; - e.aug_slot_2 = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; - e.aug_slot_3 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; - e.aug_slot_4 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; - e.aug_slot_5 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; - e.aug_slot_6 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; - e.slot_id = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; - e.quantity = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; - e.from_name = row[11] ? row[11] : ""; - e.note = row[12] ? row[12] : ""; - e.sent_date = strtoll(row[13] ? row[13] : "-1", nullptr, 10); + e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.char_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.aug_slot_1 = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.aug_slot_2 = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.aug_slot_3 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.aug_slot_4 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.aug_slot_5 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.aug_slot_6 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.slot_id = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.quantity = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; + e.evolve_amount = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; + e.from_name = row[12] ? row[12] : ""; + e.note = row[13] ? row[13] : ""; + e.sent_date = strtoll(row[14] ? row[14] : "-1", nullptr, 10); return e; } @@ -224,9 +229,10 @@ public: v.push_back(columns[8] + " = " + std::to_string(e.aug_slot_6)); v.push_back(columns[9] + " = " + std::to_string(e.slot_id)); v.push_back(columns[10] + " = " + std::to_string(e.quantity)); - v.push_back(columns[11] + " = '" + Strings::Escape(e.from_name) + "'"); - v.push_back(columns[12] + " = '" + Strings::Escape(e.note) + "'"); - v.push_back(columns[13] + " = FROM_UNIXTIME(" + (e.sent_date > 0 ? std::to_string(e.sent_date) : "null") + ")"); + v.push_back(columns[11] + " = " + std::to_string(e.evolve_amount)); + v.push_back(columns[12] + " = '" + Strings::Escape(e.from_name) + "'"); + v.push_back(columns[13] + " = '" + Strings::Escape(e.note) + "'"); + v.push_back(columns[14] + " = FROM_UNIXTIME(" + (e.sent_date > 0 ? std::to_string(e.sent_date) : "null") + ")"); auto results = db.QueryDatabase( fmt::format( @@ -259,6 +265,7 @@ public: v.push_back(std::to_string(e.aug_slot_6)); v.push_back(std::to_string(e.slot_id)); v.push_back(std::to_string(e.quantity)); + v.push_back(std::to_string(e.evolve_amount)); v.push_back("'" + Strings::Escape(e.from_name) + "'"); v.push_back("'" + Strings::Escape(e.note) + "'"); v.push_back("FROM_UNIXTIME(" + (e.sent_date > 0 ? std::to_string(e.sent_date) : "null") + ")"); @@ -302,6 +309,7 @@ public: v.push_back(std::to_string(e.aug_slot_6)); v.push_back(std::to_string(e.slot_id)); v.push_back(std::to_string(e.quantity)); + v.push_back(std::to_string(e.evolve_amount)); v.push_back("'" + Strings::Escape(e.from_name) + "'"); v.push_back("'" + Strings::Escape(e.note) + "'"); v.push_back("FROM_UNIXTIME(" + (e.sent_date > 0 ? std::to_string(e.sent_date) : "null") + ")"); @@ -338,20 +346,21 @@ public: for (auto row = results.begin(); row != results.end(); ++row) { CharacterParcels e{}; - e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; - e.char_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; - e.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; - e.aug_slot_1 = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; - e.aug_slot_2 = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; - e.aug_slot_3 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; - e.aug_slot_4 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; - e.aug_slot_5 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; - e.aug_slot_6 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; - e.slot_id = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; - e.quantity = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; - e.from_name = row[11] ? row[11] : ""; - e.note = row[12] ? row[12] : ""; - e.sent_date = strtoll(row[13] ? row[13] : "-1", nullptr, 10); + e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.char_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.aug_slot_1 = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.aug_slot_2 = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.aug_slot_3 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.aug_slot_4 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.aug_slot_5 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.aug_slot_6 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.slot_id = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.quantity = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; + e.evolve_amount = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; + e.from_name = row[12] ? row[12] : ""; + e.note = row[13] ? row[13] : ""; + e.sent_date = strtoll(row[14] ? row[14] : "-1", nullptr, 10); all_entries.push_back(e); } @@ -376,20 +385,21 @@ public: for (auto row = results.begin(); row != results.end(); ++row) { CharacterParcels e{}; - e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; - e.char_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; - e.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; - e.aug_slot_1 = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; - e.aug_slot_2 = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; - e.aug_slot_3 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; - e.aug_slot_4 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; - e.aug_slot_5 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; - e.aug_slot_6 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; - e.slot_id = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; - e.quantity = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; - e.from_name = row[11] ? row[11] : ""; - e.note = row[12] ? row[12] : ""; - e.sent_date = strtoll(row[13] ? row[13] : "-1", nullptr, 10); + e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.char_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.aug_slot_1 = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.aug_slot_2 = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.aug_slot_3 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.aug_slot_4 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.aug_slot_5 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.aug_slot_6 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.slot_id = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.quantity = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; + e.evolve_amount = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; + e.from_name = row[12] ? row[12] : ""; + e.note = row[13] ? row[13] : ""; + e.sent_date = strtoll(row[14] ? row[14] : "-1", nullptr, 10); all_entries.push_back(e); } @@ -475,6 +485,7 @@ public: v.push_back(std::to_string(e.aug_slot_6)); v.push_back(std::to_string(e.slot_id)); v.push_back(std::to_string(e.quantity)); + v.push_back(std::to_string(e.evolve_amount)); v.push_back("'" + Strings::Escape(e.from_name) + "'"); v.push_back("'" + Strings::Escape(e.note) + "'"); v.push_back("FROM_UNIXTIME(" + (e.sent_date > 0 ? std::to_string(e.sent_date) : "null") + ")"); @@ -511,6 +522,7 @@ public: v.push_back(std::to_string(e.aug_slot_6)); v.push_back(std::to_string(e.slot_id)); v.push_back(std::to_string(e.quantity)); + v.push_back(std::to_string(e.evolve_amount)); v.push_back("'" + Strings::Escape(e.from_name) + "'"); v.push_back("'" + Strings::Escape(e.note) + "'"); v.push_back("FROM_UNIXTIME(" + (e.sent_date > 0 ? std::to_string(e.sent_date) : "null") + ")"); diff --git a/common/repositories/base/base_character_pet_name_repository.h b/common/repositories/base/base_character_pet_name_repository.h new file mode 100644 index 000000000..59a8a572f --- /dev/null +++ b/common/repositories/base/base_character_pet_name_repository.h @@ -0,0 +1,392 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://docs.eqemu.io/developer/repositories + */ + +#ifndef EQEMU_BASE_CHARACTER_PET_NAME_REPOSITORY_H +#define EQEMU_BASE_CHARACTER_PET_NAME_REPOSITORY_H + +#include "../../database.h" +#include "../../strings.h" +#include + +class BaseCharacterPetNameRepository { +public: + struct CharacterPetName { + int32_t character_id; + std::string name; + }; + + static std::string PrimaryKey() + { + return std::string("character_id"); + } + + static std::vector Columns() + { + return { + "character_id", + "name", + }; + } + + static std::vector SelectColumns() + { + return { + "character_id", + "name", + }; + } + + static std::string ColumnsRaw() + { + return std::string(Strings::Implode(", ", Columns())); + } + + static std::string SelectColumnsRaw() + { + return std::string(Strings::Implode(", ", SelectColumns())); + } + + static std::string TableName() + { + return std::string("character_pet_name"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + SelectColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static CharacterPetName NewEntity() + { + CharacterPetName e{}; + + e.character_id = 0; + e.name = ""; + + return e; + } + + static CharacterPetName GetCharacterPetName( + const std::vector &character_pet_names, + int character_pet_name_id + ) + { + for (auto &character_pet_name : character_pet_names) { + if (character_pet_name.character_id == character_pet_name_id) { + return character_pet_name; + } + } + + return NewEntity(); + } + + static CharacterPetName FindOne( + Database& db, + int character_pet_name_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {} = {} LIMIT 1", + BaseSelect(), + PrimaryKey(), + character_pet_name_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + CharacterPetName e{}; + + e.character_id = row[0] ? static_cast(atoi(row[0])) : 0; + e.name = row[1] ? row[1] : ""; + + return e; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int character_pet_name_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + character_pet_name_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + const CharacterPetName &e + ) + { + std::vector v; + + auto columns = Columns(); + + v.push_back(columns[0] + " = " + std::to_string(e.character_id)); + v.push_back(columns[1] + " = '" + Strings::Escape(e.name) + "'"); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + Strings::Implode(", ", v), + PrimaryKey(), + e.character_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static CharacterPetName InsertOne( + Database& db, + CharacterPetName e + ) + { + std::vector v; + + v.push_back(std::to_string(e.character_id)); + v.push_back("'" + Strings::Escape(e.name) + "'"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + Strings::Implode(",", v) + ) + ); + + if (results.Success()) { + e.character_id = results.LastInsertedID(); + return e; + } + + e = NewEntity(); + + return e; + } + + static int InsertMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.character_id)); + v.push_back("'" + Strings::Escape(e.name) + "'"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + CharacterPetName e{}; + + e.character_id = row[0] ? static_cast(atoi(row[0])) : 0; + e.name = row[1] ? row[1] : ""; + + all_entries.push_back(e); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, const std::string &where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + CharacterPetName e{}; + + e.character_id = row[0] ? static_cast(atoi(row[0])) : 0; + e.name = row[1] ? row[1] : ""; + + all_entries.push_back(e); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, const std::string &where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int64 GetMaxId(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COALESCE(MAX({}), 0) FROM {}", + PrimaryKey(), + TableName() + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static int64 Count(Database& db, const std::string &where_filter = "") + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COUNT(*) FROM {} {}", + TableName(), + (where_filter.empty() ? "" : "WHERE " + where_filter) + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static std::string BaseReplace() + { + return fmt::format( + "REPLACE INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static int ReplaceOne( + Database& db, + const CharacterPetName &e + ) + { + std::vector v; + + v.push_back(std::to_string(e.character_id)); + v.push_back("'" + Strings::Escape(e.name) + "'"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseReplace(), + Strings::Implode(",", v) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int ReplaceMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.character_id)); + v.push_back("'" + Strings::Escape(e.name) + "'"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseReplace(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } +}; + +#endif //EQEMU_BASE_CHARACTER_PET_NAME_REPOSITORY_H diff --git a/common/repositories/base/base_character_stats_record_repository.h b/common/repositories/base/base_character_stats_record_repository.h index d4bd8d041..aac41280a 100644 --- a/common/repositories/base/base_character_stats_record_repository.h +++ b/common/repositories/base/base_character_stats_record_repository.h @@ -70,6 +70,7 @@ public: int32_t endurance_regen; int32_t shielding; int32_t spell_damage; + int32_t heal_amount; int32_t spell_shielding; int32_t strikethrough; int32_t stun_resist; @@ -154,6 +155,7 @@ public: "endurance_regen", "shielding", "spell_damage", + "heal_amount", "spell_shielding", "strikethrough", "stun_resist", @@ -234,6 +236,7 @@ public: "endurance_regen", "shielding", "spell_damage", + "heal_amount", "spell_shielding", "strikethrough", "stun_resist", @@ -348,6 +351,7 @@ public: e.endurance_regen = 0; e.shielding = 0; e.spell_damage = 0; + e.heal_amount = 0; e.spell_shielding = 0; e.strikethrough = 0; e.stun_resist = 0; @@ -458,29 +462,30 @@ public: e.endurance_regen = row[48] ? static_cast(atoi(row[48])) : 0; e.shielding = row[49] ? static_cast(atoi(row[49])) : 0; e.spell_damage = row[50] ? static_cast(atoi(row[50])) : 0; - e.spell_shielding = row[51] ? static_cast(atoi(row[51])) : 0; - e.strikethrough = row[52] ? static_cast(atoi(row[52])) : 0; - e.stun_resist = row[53] ? static_cast(atoi(row[53])) : 0; - e.backstab = row[54] ? static_cast(atoi(row[54])) : 0; - e.wind = row[55] ? static_cast(atoi(row[55])) : 0; - e.brass = row[56] ? static_cast(atoi(row[56])) : 0; - e.string = row[57] ? static_cast(atoi(row[57])) : 0; - e.percussion = row[58] ? static_cast(atoi(row[58])) : 0; - e.singing = row[59] ? static_cast(atoi(row[59])) : 0; - e.baking = row[60] ? static_cast(atoi(row[60])) : 0; - e.alchemy = row[61] ? static_cast(atoi(row[61])) : 0; - e.tailoring = row[62] ? static_cast(atoi(row[62])) : 0; - e.blacksmithing = row[63] ? static_cast(atoi(row[63])) : 0; - e.fletching = row[64] ? static_cast(atoi(row[64])) : 0; - e.brewing = row[65] ? static_cast(atoi(row[65])) : 0; - e.jewelry = row[66] ? static_cast(atoi(row[66])) : 0; - e.pottery = row[67] ? static_cast(atoi(row[67])) : 0; - e.research = row[68] ? static_cast(atoi(row[68])) : 0; - e.alcohol = row[69] ? static_cast(atoi(row[69])) : 0; - e.fishing = row[70] ? static_cast(atoi(row[70])) : 0; - e.tinkering = row[71] ? static_cast(atoi(row[71])) : 0; - e.created_at = strtoll(row[72] ? row[72] : "-1", nullptr, 10); - e.updated_at = strtoll(row[73] ? row[73] : "-1", nullptr, 10); + e.heal_amount = row[51] ? static_cast(atoi(row[51])) : 0; + e.spell_shielding = row[52] ? static_cast(atoi(row[52])) : 0; + e.strikethrough = row[53] ? static_cast(atoi(row[53])) : 0; + e.stun_resist = row[54] ? static_cast(atoi(row[54])) : 0; + e.backstab = row[55] ? static_cast(atoi(row[55])) : 0; + e.wind = row[56] ? static_cast(atoi(row[56])) : 0; + e.brass = row[57] ? static_cast(atoi(row[57])) : 0; + e.string = row[58] ? static_cast(atoi(row[58])) : 0; + e.percussion = row[59] ? static_cast(atoi(row[59])) : 0; + e.singing = row[60] ? static_cast(atoi(row[60])) : 0; + e.baking = row[61] ? static_cast(atoi(row[61])) : 0; + e.alchemy = row[62] ? static_cast(atoi(row[62])) : 0; + e.tailoring = row[63] ? static_cast(atoi(row[63])) : 0; + e.blacksmithing = row[64] ? static_cast(atoi(row[64])) : 0; + e.fletching = row[65] ? static_cast(atoi(row[65])) : 0; + e.brewing = row[66] ? static_cast(atoi(row[66])) : 0; + e.jewelry = row[67] ? static_cast(atoi(row[67])) : 0; + e.pottery = row[68] ? static_cast(atoi(row[68])) : 0; + e.research = row[69] ? static_cast(atoi(row[69])) : 0; + e.alcohol = row[70] ? static_cast(atoi(row[70])) : 0; + e.fishing = row[71] ? static_cast(atoi(row[71])) : 0; + e.tinkering = row[72] ? static_cast(atoi(row[72])) : 0; + e.created_at = strtoll(row[73] ? row[73] : "-1", nullptr, 10); + e.updated_at = strtoll(row[74] ? row[74] : "-1", nullptr, 10); return e; } @@ -565,29 +570,30 @@ public: v.push_back(columns[48] + " = " + std::to_string(e.endurance_regen)); v.push_back(columns[49] + " = " + std::to_string(e.shielding)); v.push_back(columns[50] + " = " + std::to_string(e.spell_damage)); - v.push_back(columns[51] + " = " + std::to_string(e.spell_shielding)); - v.push_back(columns[52] + " = " + std::to_string(e.strikethrough)); - v.push_back(columns[53] + " = " + std::to_string(e.stun_resist)); - v.push_back(columns[54] + " = " + std::to_string(e.backstab)); - v.push_back(columns[55] + " = " + std::to_string(e.wind)); - v.push_back(columns[56] + " = " + std::to_string(e.brass)); - v.push_back(columns[57] + " = " + std::to_string(e.string)); - v.push_back(columns[58] + " = " + std::to_string(e.percussion)); - v.push_back(columns[59] + " = " + std::to_string(e.singing)); - v.push_back(columns[60] + " = " + std::to_string(e.baking)); - v.push_back(columns[61] + " = " + std::to_string(e.alchemy)); - v.push_back(columns[62] + " = " + std::to_string(e.tailoring)); - v.push_back(columns[63] + " = " + std::to_string(e.blacksmithing)); - v.push_back(columns[64] + " = " + std::to_string(e.fletching)); - v.push_back(columns[65] + " = " + std::to_string(e.brewing)); - v.push_back(columns[66] + " = " + std::to_string(e.jewelry)); - v.push_back(columns[67] + " = " + std::to_string(e.pottery)); - v.push_back(columns[68] + " = " + std::to_string(e.research)); - v.push_back(columns[69] + " = " + std::to_string(e.alcohol)); - v.push_back(columns[70] + " = " + std::to_string(e.fishing)); - v.push_back(columns[71] + " = " + std::to_string(e.tinkering)); - v.push_back(columns[72] + " = FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); - v.push_back(columns[73] + " = FROM_UNIXTIME(" + (e.updated_at > 0 ? std::to_string(e.updated_at) : "null") + ")"); + v.push_back(columns[51] + " = " + std::to_string(e.heal_amount)); + v.push_back(columns[52] + " = " + std::to_string(e.spell_shielding)); + v.push_back(columns[53] + " = " + std::to_string(e.strikethrough)); + v.push_back(columns[54] + " = " + std::to_string(e.stun_resist)); + v.push_back(columns[55] + " = " + std::to_string(e.backstab)); + v.push_back(columns[56] + " = " + std::to_string(e.wind)); + v.push_back(columns[57] + " = " + std::to_string(e.brass)); + v.push_back(columns[58] + " = " + std::to_string(e.string)); + v.push_back(columns[59] + " = " + std::to_string(e.percussion)); + v.push_back(columns[60] + " = " + std::to_string(e.singing)); + v.push_back(columns[61] + " = " + std::to_string(e.baking)); + v.push_back(columns[62] + " = " + std::to_string(e.alchemy)); + v.push_back(columns[63] + " = " + std::to_string(e.tailoring)); + v.push_back(columns[64] + " = " + std::to_string(e.blacksmithing)); + v.push_back(columns[65] + " = " + std::to_string(e.fletching)); + v.push_back(columns[66] + " = " + std::to_string(e.brewing)); + v.push_back(columns[67] + " = " + std::to_string(e.jewelry)); + v.push_back(columns[68] + " = " + std::to_string(e.pottery)); + v.push_back(columns[69] + " = " + std::to_string(e.research)); + v.push_back(columns[70] + " = " + std::to_string(e.alcohol)); + v.push_back(columns[71] + " = " + std::to_string(e.fishing)); + v.push_back(columns[72] + " = " + std::to_string(e.tinkering)); + v.push_back(columns[73] + " = FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + v.push_back(columns[74] + " = FROM_UNIXTIME(" + (e.updated_at > 0 ? std::to_string(e.updated_at) : "null") + ")"); auto results = db.QueryDatabase( fmt::format( @@ -660,6 +666,7 @@ public: v.push_back(std::to_string(e.endurance_regen)); v.push_back(std::to_string(e.shielding)); v.push_back(std::to_string(e.spell_damage)); + v.push_back(std::to_string(e.heal_amount)); v.push_back(std::to_string(e.spell_shielding)); v.push_back(std::to_string(e.strikethrough)); v.push_back(std::to_string(e.stun_resist)); @@ -763,6 +770,7 @@ public: v.push_back(std::to_string(e.endurance_regen)); v.push_back(std::to_string(e.shielding)); v.push_back(std::to_string(e.spell_damage)); + v.push_back(std::to_string(e.heal_amount)); v.push_back(std::to_string(e.spell_shielding)); v.push_back(std::to_string(e.strikethrough)); v.push_back(std::to_string(e.stun_resist)); @@ -870,29 +878,30 @@ public: e.endurance_regen = row[48] ? static_cast(atoi(row[48])) : 0; e.shielding = row[49] ? static_cast(atoi(row[49])) : 0; e.spell_damage = row[50] ? static_cast(atoi(row[50])) : 0; - e.spell_shielding = row[51] ? static_cast(atoi(row[51])) : 0; - e.strikethrough = row[52] ? static_cast(atoi(row[52])) : 0; - e.stun_resist = row[53] ? static_cast(atoi(row[53])) : 0; - e.backstab = row[54] ? static_cast(atoi(row[54])) : 0; - e.wind = row[55] ? static_cast(atoi(row[55])) : 0; - e.brass = row[56] ? static_cast(atoi(row[56])) : 0; - e.string = row[57] ? static_cast(atoi(row[57])) : 0; - e.percussion = row[58] ? static_cast(atoi(row[58])) : 0; - e.singing = row[59] ? static_cast(atoi(row[59])) : 0; - e.baking = row[60] ? static_cast(atoi(row[60])) : 0; - e.alchemy = row[61] ? static_cast(atoi(row[61])) : 0; - e.tailoring = row[62] ? static_cast(atoi(row[62])) : 0; - e.blacksmithing = row[63] ? static_cast(atoi(row[63])) : 0; - e.fletching = row[64] ? static_cast(atoi(row[64])) : 0; - e.brewing = row[65] ? static_cast(atoi(row[65])) : 0; - e.jewelry = row[66] ? static_cast(atoi(row[66])) : 0; - e.pottery = row[67] ? static_cast(atoi(row[67])) : 0; - e.research = row[68] ? static_cast(atoi(row[68])) : 0; - e.alcohol = row[69] ? static_cast(atoi(row[69])) : 0; - e.fishing = row[70] ? static_cast(atoi(row[70])) : 0; - e.tinkering = row[71] ? static_cast(atoi(row[71])) : 0; - e.created_at = strtoll(row[72] ? row[72] : "-1", nullptr, 10); - e.updated_at = strtoll(row[73] ? row[73] : "-1", nullptr, 10); + e.heal_amount = row[51] ? static_cast(atoi(row[51])) : 0; + e.spell_shielding = row[52] ? static_cast(atoi(row[52])) : 0; + e.strikethrough = row[53] ? static_cast(atoi(row[53])) : 0; + e.stun_resist = row[54] ? static_cast(atoi(row[54])) : 0; + e.backstab = row[55] ? static_cast(atoi(row[55])) : 0; + e.wind = row[56] ? static_cast(atoi(row[56])) : 0; + e.brass = row[57] ? static_cast(atoi(row[57])) : 0; + e.string = row[58] ? static_cast(atoi(row[58])) : 0; + e.percussion = row[59] ? static_cast(atoi(row[59])) : 0; + e.singing = row[60] ? static_cast(atoi(row[60])) : 0; + e.baking = row[61] ? static_cast(atoi(row[61])) : 0; + e.alchemy = row[62] ? static_cast(atoi(row[62])) : 0; + e.tailoring = row[63] ? static_cast(atoi(row[63])) : 0; + e.blacksmithing = row[64] ? static_cast(atoi(row[64])) : 0; + e.fletching = row[65] ? static_cast(atoi(row[65])) : 0; + e.brewing = row[66] ? static_cast(atoi(row[66])) : 0; + e.jewelry = row[67] ? static_cast(atoi(row[67])) : 0; + e.pottery = row[68] ? static_cast(atoi(row[68])) : 0; + e.research = row[69] ? static_cast(atoi(row[69])) : 0; + e.alcohol = row[70] ? static_cast(atoi(row[70])) : 0; + e.fishing = row[71] ? static_cast(atoi(row[71])) : 0; + e.tinkering = row[72] ? static_cast(atoi(row[72])) : 0; + e.created_at = strtoll(row[73] ? row[73] : "-1", nullptr, 10); + e.updated_at = strtoll(row[74] ? row[74] : "-1", nullptr, 10); all_entries.push_back(e); } @@ -968,29 +977,30 @@ public: e.endurance_regen = row[48] ? static_cast(atoi(row[48])) : 0; e.shielding = row[49] ? static_cast(atoi(row[49])) : 0; e.spell_damage = row[50] ? static_cast(atoi(row[50])) : 0; - e.spell_shielding = row[51] ? static_cast(atoi(row[51])) : 0; - e.strikethrough = row[52] ? static_cast(atoi(row[52])) : 0; - e.stun_resist = row[53] ? static_cast(atoi(row[53])) : 0; - e.backstab = row[54] ? static_cast(atoi(row[54])) : 0; - e.wind = row[55] ? static_cast(atoi(row[55])) : 0; - e.brass = row[56] ? static_cast(atoi(row[56])) : 0; - e.string = row[57] ? static_cast(atoi(row[57])) : 0; - e.percussion = row[58] ? static_cast(atoi(row[58])) : 0; - e.singing = row[59] ? static_cast(atoi(row[59])) : 0; - e.baking = row[60] ? static_cast(atoi(row[60])) : 0; - e.alchemy = row[61] ? static_cast(atoi(row[61])) : 0; - e.tailoring = row[62] ? static_cast(atoi(row[62])) : 0; - e.blacksmithing = row[63] ? static_cast(atoi(row[63])) : 0; - e.fletching = row[64] ? static_cast(atoi(row[64])) : 0; - e.brewing = row[65] ? static_cast(atoi(row[65])) : 0; - e.jewelry = row[66] ? static_cast(atoi(row[66])) : 0; - e.pottery = row[67] ? static_cast(atoi(row[67])) : 0; - e.research = row[68] ? static_cast(atoi(row[68])) : 0; - e.alcohol = row[69] ? static_cast(atoi(row[69])) : 0; - e.fishing = row[70] ? static_cast(atoi(row[70])) : 0; - e.tinkering = row[71] ? static_cast(atoi(row[71])) : 0; - e.created_at = strtoll(row[72] ? row[72] : "-1", nullptr, 10); - e.updated_at = strtoll(row[73] ? row[73] : "-1", nullptr, 10); + e.heal_amount = row[51] ? static_cast(atoi(row[51])) : 0; + e.spell_shielding = row[52] ? static_cast(atoi(row[52])) : 0; + e.strikethrough = row[53] ? static_cast(atoi(row[53])) : 0; + e.stun_resist = row[54] ? static_cast(atoi(row[54])) : 0; + e.backstab = row[55] ? static_cast(atoi(row[55])) : 0; + e.wind = row[56] ? static_cast(atoi(row[56])) : 0; + e.brass = row[57] ? static_cast(atoi(row[57])) : 0; + e.string = row[58] ? static_cast(atoi(row[58])) : 0; + e.percussion = row[59] ? static_cast(atoi(row[59])) : 0; + e.singing = row[60] ? static_cast(atoi(row[60])) : 0; + e.baking = row[61] ? static_cast(atoi(row[61])) : 0; + e.alchemy = row[62] ? static_cast(atoi(row[62])) : 0; + e.tailoring = row[63] ? static_cast(atoi(row[63])) : 0; + e.blacksmithing = row[64] ? static_cast(atoi(row[64])) : 0; + e.fletching = row[65] ? static_cast(atoi(row[65])) : 0; + e.brewing = row[66] ? static_cast(atoi(row[66])) : 0; + e.jewelry = row[67] ? static_cast(atoi(row[67])) : 0; + e.pottery = row[68] ? static_cast(atoi(row[68])) : 0; + e.research = row[69] ? static_cast(atoi(row[69])) : 0; + e.alcohol = row[70] ? static_cast(atoi(row[70])) : 0; + e.fishing = row[71] ? static_cast(atoi(row[71])) : 0; + e.tinkering = row[72] ? static_cast(atoi(row[72])) : 0; + e.created_at = strtoll(row[73] ? row[73] : "-1", nullptr, 10); + e.updated_at = strtoll(row[74] ? row[74] : "-1", nullptr, 10); all_entries.push_back(e); } @@ -1116,6 +1126,7 @@ public: v.push_back(std::to_string(e.endurance_regen)); v.push_back(std::to_string(e.shielding)); v.push_back(std::to_string(e.spell_damage)); + v.push_back(std::to_string(e.heal_amount)); v.push_back(std::to_string(e.spell_shielding)); v.push_back(std::to_string(e.strikethrough)); v.push_back(std::to_string(e.stun_resist)); @@ -1212,6 +1223,7 @@ public: v.push_back(std::to_string(e.endurance_regen)); v.push_back(std::to_string(e.shielding)); v.push_back(std::to_string(e.spell_damage)); + v.push_back(std::to_string(e.heal_amount)); v.push_back(std::to_string(e.spell_shielding)); v.push_back(std::to_string(e.strikethrough)); v.push_back(std::to_string(e.stun_resist)); diff --git a/common/repositories/base/base_data_buckets_repository.h b/common/repositories/base/base_data_buckets_repository.h index 3e7ca05d0..e111eaabf 100644 --- a/common/repositories/base/base_data_buckets_repository.h +++ b/common/repositories/base/base_data_buckets_repository.h @@ -23,9 +23,12 @@ public: std::string key_; std::string value; uint32_t expires; - int64_t character_id; - int64_t npc_id; - int64_t bot_id; + uint64_t account_id; + uint64_t character_id; + uint32_t npc_id; + uint32_t bot_id; + uint16_t zone_id; + uint16_t instance_id; // cereal template @@ -36,9 +39,12 @@ public: CEREAL_NVP(key_), CEREAL_NVP(value), CEREAL_NVP(expires), + CEREAL_NVP(account_id), CEREAL_NVP(character_id), CEREAL_NVP(npc_id), - CEREAL_NVP(bot_id) + CEREAL_NVP(bot_id), + CEREAL_NVP(zone_id), + CEREAL_NVP(instance_id) ); } }; @@ -55,9 +61,12 @@ public: "`key`", "value", "expires", + "account_id", "character_id", "npc_id", "bot_id", + "zone_id", + "instance_id", }; } @@ -68,9 +77,12 @@ public: "`key`", "value", "expires", + "account_id", "character_id", "npc_id", "bot_id", + "zone_id", + "instance_id", }; } @@ -115,9 +127,12 @@ public: e.key_ = ""; e.value = ""; e.expires = 0; + e.account_id = 0; e.character_id = 0; e.npc_id = 0; e.bot_id = 0; + e.zone_id = 0; + e.instance_id = 0; return e; } @@ -158,9 +173,12 @@ public: e.key_ = row[1] ? row[1] : ""; e.value = row[2] ? row[2] : ""; e.expires = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; - e.character_id = row[4] ? strtoll(row[4], nullptr, 10) : 0; - e.npc_id = row[5] ? strtoll(row[5], nullptr, 10) : 0; - e.bot_id = row[6] ? strtoll(row[6], nullptr, 10) : 0; + e.account_id = row[4] ? strtoull(row[4], nullptr, 10) : 0; + e.character_id = row[5] ? strtoull(row[5], nullptr, 10) : 0; + e.npc_id = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.bot_id = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.zone_id = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.instance_id = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; return e; } @@ -197,9 +215,12 @@ public: v.push_back(columns[1] + " = '" + Strings::Escape(e.key_) + "'"); v.push_back(columns[2] + " = '" + Strings::Escape(e.value) + "'"); v.push_back(columns[3] + " = " + std::to_string(e.expires)); - v.push_back(columns[4] + " = " + std::to_string(e.character_id)); - v.push_back(columns[5] + " = " + std::to_string(e.npc_id)); - v.push_back(columns[6] + " = " + std::to_string(e.bot_id)); + v.push_back(columns[4] + " = " + std::to_string(e.account_id)); + v.push_back(columns[5] + " = " + std::to_string(e.character_id)); + v.push_back(columns[6] + " = " + std::to_string(e.npc_id)); + v.push_back(columns[7] + " = " + std::to_string(e.bot_id)); + v.push_back(columns[8] + " = " + std::to_string(e.zone_id)); + v.push_back(columns[9] + " = " + std::to_string(e.instance_id)); auto results = db.QueryDatabase( fmt::format( @@ -225,9 +246,12 @@ public: v.push_back("'" + Strings::Escape(e.key_) + "'"); v.push_back("'" + Strings::Escape(e.value) + "'"); v.push_back(std::to_string(e.expires)); + v.push_back(std::to_string(e.account_id)); v.push_back(std::to_string(e.character_id)); v.push_back(std::to_string(e.npc_id)); v.push_back(std::to_string(e.bot_id)); + v.push_back(std::to_string(e.zone_id)); + v.push_back(std::to_string(e.instance_id)); auto results = db.QueryDatabase( fmt::format( @@ -261,9 +285,12 @@ public: v.push_back("'" + Strings::Escape(e.key_) + "'"); v.push_back("'" + Strings::Escape(e.value) + "'"); v.push_back(std::to_string(e.expires)); + v.push_back(std::to_string(e.account_id)); v.push_back(std::to_string(e.character_id)); v.push_back(std::to_string(e.npc_id)); v.push_back(std::to_string(e.bot_id)); + v.push_back(std::to_string(e.zone_id)); + v.push_back(std::to_string(e.instance_id)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } @@ -301,9 +328,12 @@ public: e.key_ = row[1] ? row[1] : ""; e.value = row[2] ? row[2] : ""; e.expires = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; - e.character_id = row[4] ? strtoll(row[4], nullptr, 10) : 0; - e.npc_id = row[5] ? strtoll(row[5], nullptr, 10) : 0; - e.bot_id = row[6] ? strtoll(row[6], nullptr, 10) : 0; + e.account_id = row[4] ? strtoull(row[4], nullptr, 10) : 0; + e.character_id = row[5] ? strtoull(row[5], nullptr, 10) : 0; + e.npc_id = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.bot_id = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.zone_id = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.instance_id = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; all_entries.push_back(e); } @@ -332,9 +362,12 @@ public: e.key_ = row[1] ? row[1] : ""; e.value = row[2] ? row[2] : ""; e.expires = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; - e.character_id = row[4] ? strtoll(row[4], nullptr, 10) : 0; - e.npc_id = row[5] ? strtoll(row[5], nullptr, 10) : 0; - e.bot_id = row[6] ? strtoll(row[6], nullptr, 10) : 0; + e.account_id = row[4] ? strtoull(row[4], nullptr, 10) : 0; + e.character_id = row[5] ? strtoull(row[5], nullptr, 10) : 0; + e.npc_id = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.bot_id = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.zone_id = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.instance_id = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; all_entries.push_back(e); } @@ -413,9 +446,12 @@ public: v.push_back("'" + Strings::Escape(e.key_) + "'"); v.push_back("'" + Strings::Escape(e.value) + "'"); v.push_back(std::to_string(e.expires)); + v.push_back(std::to_string(e.account_id)); v.push_back(std::to_string(e.character_id)); v.push_back(std::to_string(e.npc_id)); v.push_back(std::to_string(e.bot_id)); + v.push_back(std::to_string(e.zone_id)); + v.push_back(std::to_string(e.instance_id)); auto results = db.QueryDatabase( fmt::format( @@ -442,9 +478,12 @@ public: v.push_back("'" + Strings::Escape(e.key_) + "'"); v.push_back("'" + Strings::Escape(e.value) + "'"); v.push_back(std::to_string(e.expires)); + v.push_back(std::to_string(e.account_id)); v.push_back(std::to_string(e.character_id)); v.push_back(std::to_string(e.npc_id)); v.push_back(std::to_string(e.bot_id)); + v.push_back(std::to_string(e.zone_id)); + v.push_back(std::to_string(e.instance_id)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } diff --git a/common/repositories/base/base_expedition_lockouts_repository.h b/common/repositories/base/base_dynamic_zone_lockouts_repository.h similarity index 81% rename from common/repositories/base/base_expedition_lockouts_repository.h rename to common/repositories/base/base_dynamic_zone_lockouts_repository.h index b4ec21804..b787d6c3b 100644 --- a/common/repositories/base/base_expedition_lockouts_repository.h +++ b/common/repositories/base/base_dynamic_zone_lockouts_repository.h @@ -9,18 +9,18 @@ * @docs https://docs.eqemu.io/developer/repositories */ -#ifndef EQEMU_BASE_EXPEDITION_LOCKOUTS_REPOSITORY_H -#define EQEMU_BASE_EXPEDITION_LOCKOUTS_REPOSITORY_H +#ifndef EQEMU_BASE_DYNAMIC_ZONE_LOCKOUTS_REPOSITORY_H +#define EQEMU_BASE_DYNAMIC_ZONE_LOCKOUTS_REPOSITORY_H #include "../../database.h" #include "../../strings.h" #include -class BaseExpeditionLockoutsRepository { +class BaseDynamicZoneLockoutsRepository { public: - struct ExpeditionLockouts { + struct DynamicZoneLockouts { uint32_t id; - uint32_t expedition_id; + uint32_t dynamic_zone_id; std::string event_name; time_t expire_time; uint32_t duration; @@ -36,7 +36,7 @@ public: { return { "id", - "expedition_id", + "dynamic_zone_id", "event_name", "expire_time", "duration", @@ -48,7 +48,7 @@ public: { return { "id", - "expedition_id", + "dynamic_zone_id", "event_name", "UNIX_TIMESTAMP(expire_time)", "duration", @@ -68,7 +68,7 @@ public: static std::string TableName() { - return std::string("expedition_lockouts"); + return std::string("dynamic_zone_lockouts"); } static std::string BaseSelect() @@ -89,12 +89,12 @@ public: ); } - static ExpeditionLockouts NewEntity() + static DynamicZoneLockouts NewEntity() { - ExpeditionLockouts e{}; + DynamicZoneLockouts e{}; e.id = 0; - e.expedition_id = 0; + e.dynamic_zone_id = 0; e.event_name = ""; e.expire_time = std::time(nullptr); e.duration = 0; @@ -103,23 +103,23 @@ public: return e; } - static ExpeditionLockouts GetExpeditionLockouts( - const std::vector &expedition_lockoutss, - int expedition_lockouts_id + static DynamicZoneLockouts GetDynamicZoneLockouts( + const std::vector &dynamic_zone_lockoutss, + int dynamic_zone_lockouts_id ) { - for (auto &expedition_lockouts : expedition_lockoutss) { - if (expedition_lockouts.id == expedition_lockouts_id) { - return expedition_lockouts; + for (auto &dynamic_zone_lockouts : dynamic_zone_lockoutss) { + if (dynamic_zone_lockouts.id == dynamic_zone_lockouts_id) { + return dynamic_zone_lockouts; } } return NewEntity(); } - static ExpeditionLockouts FindOne( + static DynamicZoneLockouts FindOne( Database& db, - int expedition_lockouts_id + int dynamic_zone_lockouts_id ) { auto results = db.QueryDatabase( @@ -127,16 +127,16 @@ public: "{} WHERE {} = {} LIMIT 1", BaseSelect(), PrimaryKey(), - expedition_lockouts_id + dynamic_zone_lockouts_id ) ); auto row = results.begin(); if (results.RowCount() == 1) { - ExpeditionLockouts e{}; + DynamicZoneLockouts e{}; e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; - e.expedition_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.dynamic_zone_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; e.event_name = row[2] ? row[2] : ""; e.expire_time = strtoll(row[3] ? row[3] : "-1", nullptr, 10); e.duration = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; @@ -150,7 +150,7 @@ public: static int DeleteOne( Database& db, - int expedition_lockouts_id + int dynamic_zone_lockouts_id ) { auto results = db.QueryDatabase( @@ -158,7 +158,7 @@ public: "DELETE FROM {} WHERE {} = {}", TableName(), PrimaryKey(), - expedition_lockouts_id + dynamic_zone_lockouts_id ) ); @@ -167,14 +167,14 @@ public: static int UpdateOne( Database& db, - const ExpeditionLockouts &e + const DynamicZoneLockouts &e ) { std::vector v; auto columns = Columns(); - v.push_back(columns[1] + " = " + std::to_string(e.expedition_id)); + v.push_back(columns[1] + " = " + std::to_string(e.dynamic_zone_id)); v.push_back(columns[2] + " = '" + Strings::Escape(e.event_name) + "'"); v.push_back(columns[3] + " = FROM_UNIXTIME(" + (e.expire_time > 0 ? std::to_string(e.expire_time) : "null") + ")"); v.push_back(columns[4] + " = " + std::to_string(e.duration)); @@ -193,15 +193,15 @@ public: return (results.Success() ? results.RowsAffected() : 0); } - static ExpeditionLockouts InsertOne( + static DynamicZoneLockouts InsertOne( Database& db, - ExpeditionLockouts e + DynamicZoneLockouts e ) { std::vector v; v.push_back(std::to_string(e.id)); - v.push_back(std::to_string(e.expedition_id)); + v.push_back(std::to_string(e.dynamic_zone_id)); v.push_back("'" + Strings::Escape(e.event_name) + "'"); v.push_back("FROM_UNIXTIME(" + (e.expire_time > 0 ? std::to_string(e.expire_time) : "null") + ")"); v.push_back(std::to_string(e.duration)); @@ -227,7 +227,7 @@ public: static int InsertMany( Database& db, - const std::vector &entries + const std::vector &entries ) { std::vector insert_chunks; @@ -236,7 +236,7 @@ public: std::vector v; v.push_back(std::to_string(e.id)); - v.push_back(std::to_string(e.expedition_id)); + v.push_back(std::to_string(e.dynamic_zone_id)); v.push_back("'" + Strings::Escape(e.event_name) + "'"); v.push_back("FROM_UNIXTIME(" + (e.expire_time > 0 ? std::to_string(e.expire_time) : "null") + ")"); v.push_back(std::to_string(e.duration)); @@ -258,9 +258,9 @@ public: return (results.Success() ? results.RowsAffected() : 0); } - static std::vector All(Database& db) + static std::vector All(Database& db) { - std::vector all_entries; + std::vector all_entries; auto results = db.QueryDatabase( fmt::format( @@ -272,10 +272,10 @@ public: all_entries.reserve(results.RowCount()); for (auto row = results.begin(); row != results.end(); ++row) { - ExpeditionLockouts e{}; + DynamicZoneLockouts e{}; e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; - e.expedition_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.dynamic_zone_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; e.event_name = row[2] ? row[2] : ""; e.expire_time = strtoll(row[3] ? row[3] : "-1", nullptr, 10); e.duration = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; @@ -287,9 +287,9 @@ public: return all_entries; } - static std::vector GetWhere(Database& db, const std::string &where_filter) + static std::vector GetWhere(Database& db, const std::string &where_filter) { - std::vector all_entries; + std::vector all_entries; auto results = db.QueryDatabase( fmt::format( @@ -302,10 +302,10 @@ public: all_entries.reserve(results.RowCount()); for (auto row = results.begin(); row != results.end(); ++row) { - ExpeditionLockouts e{}; + DynamicZoneLockouts e{}; e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; - e.expedition_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.dynamic_zone_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; e.event_name = row[2] ? row[2] : ""; e.expire_time = strtoll(row[3] ? row[3] : "-1", nullptr, 10); e.duration = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; @@ -379,13 +379,13 @@ public: static int ReplaceOne( Database& db, - const ExpeditionLockouts &e + const DynamicZoneLockouts &e ) { std::vector v; v.push_back(std::to_string(e.id)); - v.push_back(std::to_string(e.expedition_id)); + v.push_back(std::to_string(e.dynamic_zone_id)); v.push_back("'" + Strings::Escape(e.event_name) + "'"); v.push_back("FROM_UNIXTIME(" + (e.expire_time > 0 ? std::to_string(e.expire_time) : "null") + ")"); v.push_back(std::to_string(e.duration)); @@ -404,7 +404,7 @@ public: static int ReplaceMany( Database& db, - const std::vector &entries + const std::vector &entries ) { std::vector insert_chunks; @@ -413,7 +413,7 @@ public: std::vector v; v.push_back(std::to_string(e.id)); - v.push_back(std::to_string(e.expedition_id)); + v.push_back(std::to_string(e.dynamic_zone_id)); v.push_back("'" + Strings::Escape(e.event_name) + "'"); v.push_back("FROM_UNIXTIME(" + (e.expire_time > 0 ? std::to_string(e.expire_time) : "null") + ")"); v.push_back(std::to_string(e.duration)); @@ -436,4 +436,4 @@ public: } }; -#endif //EQEMU_BASE_EXPEDITION_LOCKOUTS_REPOSITORY_H +#endif //EQEMU_BASE_DYNAMIC_ZONE_LOCKOUTS_REPOSITORY_H diff --git a/common/repositories/base/base_dynamic_zones_repository.h b/common/repositories/base/base_dynamic_zones_repository.h index 8fe5266ab..d9231c54e 100644 --- a/common/repositories/base/base_dynamic_zones_repository.h +++ b/common/repositories/base/base_dynamic_zones_repository.h @@ -42,6 +42,8 @@ public: float zone_in_z; float zone_in_heading; uint8_t has_zone_in; + int8_t is_locked; + int8_t add_replay; }; static std::string PrimaryKey() @@ -75,6 +77,8 @@ public: "zone_in_z", "zone_in_heading", "has_zone_in", + "is_locked", + "add_replay", }; } @@ -104,6 +108,8 @@ public: "zone_in_z", "zone_in_heading", "has_zone_in", + "is_locked", + "add_replay", }; } @@ -167,6 +173,8 @@ public: e.zone_in_z = 0; e.zone_in_heading = 0; e.has_zone_in = 0; + e.is_locked = 0; + e.add_replay = 1; return e; } @@ -226,6 +234,8 @@ public: e.zone_in_z = row[20] ? strtof(row[20], nullptr) : 0; e.zone_in_heading = row[21] ? strtof(row[21], nullptr) : 0; e.has_zone_in = row[22] ? static_cast(strtoul(row[22], nullptr, 10)) : 0; + e.is_locked = row[23] ? static_cast(atoi(row[23])) : 0; + e.add_replay = row[24] ? static_cast(atoi(row[24])) : 1; return e; } @@ -281,6 +291,8 @@ public: v.push_back(columns[20] + " = " + std::to_string(e.zone_in_z)); v.push_back(columns[21] + " = " + std::to_string(e.zone_in_heading)); v.push_back(columns[22] + " = " + std::to_string(e.has_zone_in)); + v.push_back(columns[23] + " = " + std::to_string(e.is_locked)); + v.push_back(columns[24] + " = " + std::to_string(e.add_replay)); auto results = db.QueryDatabase( fmt::format( @@ -325,6 +337,8 @@ public: v.push_back(std::to_string(e.zone_in_z)); v.push_back(std::to_string(e.zone_in_heading)); v.push_back(std::to_string(e.has_zone_in)); + v.push_back(std::to_string(e.is_locked)); + v.push_back(std::to_string(e.add_replay)); auto results = db.QueryDatabase( fmt::format( @@ -377,6 +391,8 @@ public: v.push_back(std::to_string(e.zone_in_z)); v.push_back(std::to_string(e.zone_in_heading)); v.push_back(std::to_string(e.has_zone_in)); + v.push_back(std::to_string(e.is_locked)); + v.push_back(std::to_string(e.add_replay)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } @@ -433,6 +449,8 @@ public: e.zone_in_z = row[20] ? strtof(row[20], nullptr) : 0; e.zone_in_heading = row[21] ? strtof(row[21], nullptr) : 0; e.has_zone_in = row[22] ? static_cast(strtoul(row[22], nullptr, 10)) : 0; + e.is_locked = row[23] ? static_cast(atoi(row[23])) : 0; + e.add_replay = row[24] ? static_cast(atoi(row[24])) : 1; all_entries.push_back(e); } @@ -480,6 +498,8 @@ public: e.zone_in_z = row[20] ? strtof(row[20], nullptr) : 0; e.zone_in_heading = row[21] ? strtof(row[21], nullptr) : 0; e.has_zone_in = row[22] ? static_cast(strtoul(row[22], nullptr, 10)) : 0; + e.is_locked = row[23] ? static_cast(atoi(row[23])) : 0; + e.add_replay = row[24] ? static_cast(atoi(row[24])) : 1; all_entries.push_back(e); } @@ -577,6 +597,8 @@ public: v.push_back(std::to_string(e.zone_in_z)); v.push_back(std::to_string(e.zone_in_heading)); v.push_back(std::to_string(e.has_zone_in)); + v.push_back(std::to_string(e.is_locked)); + v.push_back(std::to_string(e.add_replay)); auto results = db.QueryDatabase( fmt::format( @@ -622,6 +644,8 @@ public: v.push_back(std::to_string(e.zone_in_z)); v.push_back(std::to_string(e.zone_in_heading)); v.push_back(std::to_string(e.has_zone_in)); + v.push_back(std::to_string(e.is_locked)); + v.push_back(std::to_string(e.add_replay)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } diff --git a/common/repositories/base/base_guild_bank_repository.h b/common/repositories/base/base_guild_bank_repository.h index bd359a77d..a857987ac 100644 --- a/common/repositories/base/base_guild_bank_repository.h +++ b/common/repositories/base/base_guild_bank_repository.h @@ -6,7 +6,7 @@ * Any modifications to base repositories are to be made by the generator only * * @generator ./utils/scripts/generators/repository-generator.pl - * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories + * @docs https://docs.eqemu.io/developer/repositories */ #ifndef EQEMU_BASE_GUILD_BANK_REPOSITORY_H @@ -16,19 +16,24 @@ #include "../../strings.h" #include - class BaseGuildBankRepository { public: struct GuildBank { uint32_t id; - uint32_t guildid; + uint32_t guild_id; uint8_t area; uint32_t slot; - uint32_t itemid; - uint32_t qty; + uint32_t item_id; + uint32_t augment_one_id; + uint32_t augment_two_id; + uint32_t augment_three_id; + uint32_t augment_four_id; + uint32_t augment_five_id; + uint32_t augment_six_id; + int32_t quantity; std::string donator; uint8_t permissions; - std::string whofor; + std::string who_for; }; static std::string PrimaryKey() @@ -40,14 +45,20 @@ public: { return { "id", - "guildid", + "guild_id", "area", "slot", - "itemid", - "qty", + "item_id", + "augment_one_id", + "augment_two_id", + "augment_three_id", + "augment_four_id", + "augment_five_id", + "augment_six_id", + "quantity", "donator", "permissions", - "whofor", + "who_for", }; } @@ -55,14 +66,20 @@ public: { return { "id", - "guildid", + "guild_id", "area", "slot", - "itemid", - "qty", + "item_id", + "augment_one_id", + "augment_two_id", + "augment_three_id", + "augment_four_id", + "augment_five_id", + "augment_six_id", + "quantity", "donator", "permissions", - "whofor", + "who_for", }; } @@ -103,15 +120,21 @@ public: { GuildBank e{}; - e.id = 0; - e.guildid = 0; - e.area = 0; - e.slot = 0; - e.itemid = 0; - e.qty = 0; - e.donator = ""; - e.permissions = 0; - e.whofor = ""; + e.id = 0; + e.guild_id = 0; + e.area = 0; + e.slot = 0; + e.item_id = 0; + e.augment_one_id = 0; + e.augment_two_id = 0; + e.augment_three_id = 0; + e.augment_four_id = 0; + e.augment_five_id = 0; + e.augment_six_id = 0; + e.quantity = 0; + e.donator = ""; + e.permissions = 0; + e.who_for = ""; return e; } @@ -148,15 +171,21 @@ public: if (results.RowCount() == 1) { GuildBank e{}; - e.id = static_cast(strtoul(row[0], nullptr, 10)); - e.guildid = static_cast(strtoul(row[1], nullptr, 10)); - e.area = static_cast(strtoul(row[2], nullptr, 10)); - e.slot = static_cast(strtoul(row[3], nullptr, 10)); - e.itemid = static_cast(strtoul(row[4], nullptr, 10)); - e.qty = static_cast(strtoul(row[5], nullptr, 10)); - e.donator = row[6] ? row[6] : ""; - e.permissions = static_cast(strtoul(row[7], nullptr, 10)); - e.whofor = row[8] ? row[8] : ""; + e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.guild_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.area = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.slot = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.item_id = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.augment_one_id = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.augment_two_id = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.augment_three_id = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.augment_four_id = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.augment_five_id = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.augment_six_id = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; + e.quantity = row[11] ? static_cast(atoi(row[11])) : 0; + e.donator = row[12] ? row[12] : ""; + e.permissions = row[13] ? static_cast(strtoul(row[13], nullptr, 10)) : 0; + e.who_for = row[14] ? row[14] : ""; return e; } @@ -190,14 +219,20 @@ public: auto columns = Columns(); - v.push_back(columns[1] + " = " + std::to_string(e.guildid)); + v.push_back(columns[1] + " = " + std::to_string(e.guild_id)); v.push_back(columns[2] + " = " + std::to_string(e.area)); v.push_back(columns[3] + " = " + std::to_string(e.slot)); - v.push_back(columns[4] + " = " + std::to_string(e.itemid)); - v.push_back(columns[5] + " = " + std::to_string(e.qty)); - v.push_back(columns[6] + " = '" + Strings::Escape(e.donator) + "'"); - v.push_back(columns[7] + " = " + std::to_string(e.permissions)); - v.push_back(columns[8] + " = '" + Strings::Escape(e.whofor) + "'"); + v.push_back(columns[4] + " = " + std::to_string(e.item_id)); + v.push_back(columns[5] + " = " + std::to_string(e.augment_one_id)); + v.push_back(columns[6] + " = " + std::to_string(e.augment_two_id)); + v.push_back(columns[7] + " = " + std::to_string(e.augment_three_id)); + v.push_back(columns[8] + " = " + std::to_string(e.augment_four_id)); + v.push_back(columns[9] + " = " + std::to_string(e.augment_five_id)); + v.push_back(columns[10] + " = " + std::to_string(e.augment_six_id)); + v.push_back(columns[11] + " = " + std::to_string(e.quantity)); + v.push_back(columns[12] + " = '" + Strings::Escape(e.donator) + "'"); + v.push_back(columns[13] + " = " + std::to_string(e.permissions)); + v.push_back(columns[14] + " = '" + Strings::Escape(e.who_for) + "'"); auto results = db.QueryDatabase( fmt::format( @@ -220,14 +255,20 @@ public: std::vector v; v.push_back(std::to_string(e.id)); - v.push_back(std::to_string(e.guildid)); + v.push_back(std::to_string(e.guild_id)); v.push_back(std::to_string(e.area)); v.push_back(std::to_string(e.slot)); - v.push_back(std::to_string(e.itemid)); - v.push_back(std::to_string(e.qty)); + v.push_back(std::to_string(e.item_id)); + v.push_back(std::to_string(e.augment_one_id)); + v.push_back(std::to_string(e.augment_two_id)); + v.push_back(std::to_string(e.augment_three_id)); + v.push_back(std::to_string(e.augment_four_id)); + v.push_back(std::to_string(e.augment_five_id)); + v.push_back(std::to_string(e.augment_six_id)); + v.push_back(std::to_string(e.quantity)); v.push_back("'" + Strings::Escape(e.donator) + "'"); v.push_back(std::to_string(e.permissions)); - v.push_back("'" + Strings::Escape(e.whofor) + "'"); + v.push_back("'" + Strings::Escape(e.who_for) + "'"); auto results = db.QueryDatabase( fmt::format( @@ -258,14 +299,20 @@ public: std::vector v; v.push_back(std::to_string(e.id)); - v.push_back(std::to_string(e.guildid)); + v.push_back(std::to_string(e.guild_id)); v.push_back(std::to_string(e.area)); v.push_back(std::to_string(e.slot)); - v.push_back(std::to_string(e.itemid)); - v.push_back(std::to_string(e.qty)); + v.push_back(std::to_string(e.item_id)); + v.push_back(std::to_string(e.augment_one_id)); + v.push_back(std::to_string(e.augment_two_id)); + v.push_back(std::to_string(e.augment_three_id)); + v.push_back(std::to_string(e.augment_four_id)); + v.push_back(std::to_string(e.augment_five_id)); + v.push_back(std::to_string(e.augment_six_id)); + v.push_back(std::to_string(e.quantity)); v.push_back("'" + Strings::Escape(e.donator) + "'"); v.push_back(std::to_string(e.permissions)); - v.push_back("'" + Strings::Escape(e.whofor) + "'"); + v.push_back("'" + Strings::Escape(e.who_for) + "'"); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } @@ -299,15 +346,21 @@ public: for (auto row = results.begin(); row != results.end(); ++row) { GuildBank e{}; - e.id = static_cast(strtoul(row[0], nullptr, 10)); - e.guildid = static_cast(strtoul(row[1], nullptr, 10)); - e.area = static_cast(strtoul(row[2], nullptr, 10)); - e.slot = static_cast(strtoul(row[3], nullptr, 10)); - e.itemid = static_cast(strtoul(row[4], nullptr, 10)); - e.qty = static_cast(strtoul(row[5], nullptr, 10)); - e.donator = row[6] ? row[6] : ""; - e.permissions = static_cast(strtoul(row[7], nullptr, 10)); - e.whofor = row[8] ? row[8] : ""; + e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.guild_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.area = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.slot = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.item_id = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.augment_one_id = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.augment_two_id = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.augment_three_id = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.augment_four_id = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.augment_five_id = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.augment_six_id = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; + e.quantity = row[11] ? static_cast(atoi(row[11])) : 0; + e.donator = row[12] ? row[12] : ""; + e.permissions = row[13] ? static_cast(strtoul(row[13], nullptr, 10)) : 0; + e.who_for = row[14] ? row[14] : ""; all_entries.push_back(e); } @@ -332,15 +385,21 @@ public: for (auto row = results.begin(); row != results.end(); ++row) { GuildBank e{}; - e.id = static_cast(strtoul(row[0], nullptr, 10)); - e.guildid = static_cast(strtoul(row[1], nullptr, 10)); - e.area = static_cast(strtoul(row[2], nullptr, 10)); - e.slot = static_cast(strtoul(row[3], nullptr, 10)); - e.itemid = static_cast(strtoul(row[4], nullptr, 10)); - e.qty = static_cast(strtoul(row[5], nullptr, 10)); - e.donator = row[6] ? row[6] : ""; - e.permissions = static_cast(strtoul(row[7], nullptr, 10)); - e.whofor = row[8] ? row[8] : ""; + e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.guild_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.area = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.slot = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.item_id = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.augment_one_id = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.augment_two_id = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.augment_three_id = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.augment_four_id = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.augment_five_id = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.augment_six_id = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; + e.quantity = row[11] ? static_cast(atoi(row[11])) : 0; + e.donator = row[12] ? row[12] : ""; + e.permissions = row[13] ? static_cast(strtoul(row[13], nullptr, 10)) : 0; + e.who_for = row[14] ? row[14] : ""; all_entries.push_back(e); } @@ -399,6 +458,90 @@ public: return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); } + static std::string BaseReplace() + { + return fmt::format( + "REPLACE INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static int ReplaceOne( + Database& db, + const GuildBank &e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.guild_id)); + v.push_back(std::to_string(e.area)); + v.push_back(std::to_string(e.slot)); + v.push_back(std::to_string(e.item_id)); + v.push_back(std::to_string(e.augment_one_id)); + v.push_back(std::to_string(e.augment_two_id)); + v.push_back(std::to_string(e.augment_three_id)); + v.push_back(std::to_string(e.augment_four_id)); + v.push_back(std::to_string(e.augment_five_id)); + v.push_back(std::to_string(e.augment_six_id)); + v.push_back(std::to_string(e.quantity)); + v.push_back("'" + Strings::Escape(e.donator) + "'"); + v.push_back(std::to_string(e.permissions)); + v.push_back("'" + Strings::Escape(e.who_for) + "'"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseReplace(), + Strings::Implode(",", v) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int ReplaceMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.guild_id)); + v.push_back(std::to_string(e.area)); + v.push_back(std::to_string(e.slot)); + v.push_back(std::to_string(e.item_id)); + v.push_back(std::to_string(e.augment_one_id)); + v.push_back(std::to_string(e.augment_two_id)); + v.push_back(std::to_string(e.augment_three_id)); + v.push_back(std::to_string(e.augment_four_id)); + v.push_back(std::to_string(e.augment_five_id)); + v.push_back(std::to_string(e.augment_six_id)); + v.push_back(std::to_string(e.quantity)); + v.push_back("'" + Strings::Escape(e.donator) + "'"); + v.push_back(std::to_string(e.permissions)); + v.push_back("'" + Strings::Escape(e.who_for) + "'"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseReplace(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } }; #endif //EQEMU_BASE_GUILD_BANK_REPOSITORY_H diff --git a/common/repositories/base/base_horses_repository.h b/common/repositories/base/base_horses_repository.h index 5b12d52a6..5c49b9967 100644 --- a/common/repositories/base/base_horses_repository.h +++ b/common/repositories/base/base_horses_repository.h @@ -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(atoi(row[0])) : 0; - e.filename = row[1] ? row[1] : ""; - e.race = row[2] ? static_cast(atoi(row[2])) : 216; - e.gender = row[3] ? static_cast(atoi(row[3])) : 0; - e.texture = row[4] ? static_cast(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(atoi(row[0])) : 0; + e.filename = row[1] ? row[1] : ""; + e.race = row[2] ? static_cast(atoi(row[2])) : 216; + e.gender = row[3] ? static_cast(atoi(row[3])) : 0; + e.texture = row[4] ? static_cast(atoi(row[4])) : 0; + e.helmtexture = row[5] ? static_cast(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(atoi(row[0])) : 0; - e.filename = row[1] ? row[1] : ""; - e.race = row[2] ? static_cast(atoi(row[2])) : 216; - e.gender = row[3] ? static_cast(atoi(row[3])) : 0; - e.texture = row[4] ? static_cast(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(atoi(row[0])) : 0; + e.filename = row[1] ? row[1] : ""; + e.race = row[2] ? static_cast(atoi(row[2])) : 216; + e.gender = row[3] ? static_cast(atoi(row[3])) : 0; + e.texture = row[4] ? static_cast(atoi(row[4])) : 0; + e.helmtexture = row[5] ? static_cast(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(atoi(row[0])) : 0; - e.filename = row[1] ? row[1] : ""; - e.race = row[2] ? static_cast(atoi(row[2])) : 216; - e.gender = row[3] ? static_cast(atoi(row[3])) : 0; - e.texture = row[4] ? static_cast(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(atoi(row[0])) : 0; + e.filename = row[1] ? row[1] : ""; + e.race = row[2] ? static_cast(atoi(row[2])) : 216; + e.gender = row[3] ? static_cast(atoi(row[3])) : 0; + e.texture = row[4] ? static_cast(atoi(row[4])) : 0; + e.helmtexture = row[5] ? static_cast(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) + "'"); diff --git a/common/repositories/base/base_instance_list_repository.h b/common/repositories/base/base_instance_list_repository.h index 4b0d730a4..80e64d453 100644 --- a/common/repositories/base/base_instance_list_repository.h +++ b/common/repositories/base/base_instance_list_repository.h @@ -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(strtoul(row[3], nullptr, 10)) : 0; e.start_time = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; e.duration = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; - e.never_expires = row[6] ? static_cast(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(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(strtoul(row[3], nullptr, 10)) : 0; e.start_time = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; e.duration = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; - e.never_expires = row[6] ? static_cast(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(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(strtoul(row[3], nullptr, 10)) : 0; e.start_time = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; e.duration = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; - e.never_expires = row[6] ? static_cast(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(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) + "'"); diff --git a/common/repositories/base/base_inventory_repository.h b/common/repositories/base/base_inventory_repository.h index a029ac6b4..9a4b5122b 100644 --- a/common/repositories/base/base_inventory_repository.h +++ b/common/repositories/base/base_inventory_repository.h @@ -19,48 +19,48 @@ class BaseInventoryRepository { public: struct Inventory { - uint32_t charid; - uint32_t slotid; - uint32_t itemid; + uint32_t character_id; + uint32_t slot_id; + uint32_t item_id; uint16_t charges; uint32_t color; - uint32_t augslot1; - uint32_t augslot2; - uint32_t augslot3; - uint32_t augslot4; - uint32_t augslot5; - int32_t augslot6; + uint32_t augment_one; + uint32_t augment_two; + uint32_t augment_three; + uint32_t augment_four; + uint32_t augment_five; + uint32_t augment_six; uint8_t instnodrop; std::string custom_data; - uint32_t ornamenticon; - uint32_t ornamentidfile; + uint32_t ornament_icon; + uint32_t ornament_idfile; int32_t ornament_hero_model; uint64_t guid; }; static std::string PrimaryKey() { - return std::string("charid"); + return std::string("character_id"); } static std::vector Columns() { return { - "charid", - "slotid", - "itemid", + "character_id", + "slot_id", + "item_id", "charges", "color", - "augslot1", - "augslot2", - "augslot3", - "augslot4", - "augslot5", - "augslot6", + "augment_one", + "augment_two", + "augment_three", + "augment_four", + "augment_five", + "augment_six", "instnodrop", "custom_data", - "ornamenticon", - "ornamentidfile", + "ornament_icon", + "ornament_idfile", "ornament_hero_model", "guid", }; @@ -69,21 +69,21 @@ public: static std::vector SelectColumns() { return { - "charid", - "slotid", - "itemid", + "character_id", + "slot_id", + "item_id", "charges", "color", - "augslot1", - "augslot2", - "augslot3", - "augslot4", - "augslot5", - "augslot6", + "augment_one", + "augment_two", + "augment_three", + "augment_four", + "augment_five", + "augment_six", "instnodrop", "custom_data", - "ornamenticon", - "ornamentidfile", + "ornament_icon", + "ornament_idfile", "ornament_hero_model", "guid", }; @@ -126,21 +126,21 @@ public: { Inventory e{}; - e.charid = 0; - e.slotid = 0; - e.itemid = 0; + e.character_id = 0; + e.slot_id = 0; + e.item_id = 0; e.charges = 0; e.color = 0; - e.augslot1 = 0; - e.augslot2 = 0; - e.augslot3 = 0; - e.augslot4 = 0; - e.augslot5 = 0; - e.augslot6 = 0; + e.augment_one = 0; + e.augment_two = 0; + e.augment_three = 0; + e.augment_four = 0; + e.augment_five = 0; + e.augment_six = 0; e.instnodrop = 0; e.custom_data = ""; - e.ornamenticon = 0; - e.ornamentidfile = 0; + e.ornament_icon = 0; + e.ornament_idfile = 0; e.ornament_hero_model = 0; e.guid = 0; @@ -153,7 +153,7 @@ public: ) { for (auto &inventory : inventorys) { - if (inventory.charid == inventory_id) { + if (inventory.character_id == inventory_id) { return inventory; } } @@ -179,21 +179,21 @@ public: if (results.RowCount() == 1) { Inventory e{}; - e.charid = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; - e.slotid = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; - e.itemid = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.character_id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.slot_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; e.charges = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; e.color = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; - e.augslot1 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; - e.augslot2 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; - e.augslot3 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; - e.augslot4 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; - e.augslot5 = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; - e.augslot6 = row[10] ? static_cast(atoi(row[10])) : 0; + e.augment_one = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.augment_two = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.augment_three = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.augment_four = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.augment_five = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.augment_six = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; e.instnodrop = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; e.custom_data = row[12] ? row[12] : ""; - e.ornamenticon = row[13] ? static_cast(strtoul(row[13], nullptr, 10)) : 0; - e.ornamentidfile = row[14] ? static_cast(strtoul(row[14], nullptr, 10)) : 0; + e.ornament_icon = row[13] ? static_cast(strtoul(row[13], nullptr, 10)) : 0; + e.ornament_idfile = row[14] ? static_cast(strtoul(row[14], nullptr, 10)) : 0; e.ornament_hero_model = row[15] ? static_cast(atoi(row[15])) : 0; e.guid = row[16] ? strtoull(row[16], nullptr, 10) : 0; @@ -229,21 +229,21 @@ public: auto columns = Columns(); - v.push_back(columns[0] + " = " + std::to_string(e.charid)); - v.push_back(columns[1] + " = " + std::to_string(e.slotid)); - v.push_back(columns[2] + " = " + std::to_string(e.itemid)); + v.push_back(columns[0] + " = " + std::to_string(e.character_id)); + v.push_back(columns[1] + " = " + std::to_string(e.slot_id)); + v.push_back(columns[2] + " = " + std::to_string(e.item_id)); v.push_back(columns[3] + " = " + std::to_string(e.charges)); v.push_back(columns[4] + " = " + std::to_string(e.color)); - v.push_back(columns[5] + " = " + std::to_string(e.augslot1)); - v.push_back(columns[6] + " = " + std::to_string(e.augslot2)); - v.push_back(columns[7] + " = " + std::to_string(e.augslot3)); - v.push_back(columns[8] + " = " + std::to_string(e.augslot4)); - v.push_back(columns[9] + " = " + std::to_string(e.augslot5)); - v.push_back(columns[10] + " = " + std::to_string(e.augslot6)); + v.push_back(columns[5] + " = " + std::to_string(e.augment_one)); + v.push_back(columns[6] + " = " + std::to_string(e.augment_two)); + v.push_back(columns[7] + " = " + std::to_string(e.augment_three)); + v.push_back(columns[8] + " = " + std::to_string(e.augment_four)); + v.push_back(columns[9] + " = " + std::to_string(e.augment_five)); + v.push_back(columns[10] + " = " + std::to_string(e.augment_six)); v.push_back(columns[11] + " = " + std::to_string(e.instnodrop)); v.push_back(columns[12] + " = '" + Strings::Escape(e.custom_data) + "'"); - v.push_back(columns[13] + " = " + std::to_string(e.ornamenticon)); - v.push_back(columns[14] + " = " + std::to_string(e.ornamentidfile)); + v.push_back(columns[13] + " = " + std::to_string(e.ornament_icon)); + v.push_back(columns[14] + " = " + std::to_string(e.ornament_idfile)); v.push_back(columns[15] + " = " + std::to_string(e.ornament_hero_model)); v.push_back(columns[16] + " = " + std::to_string(e.guid)); @@ -253,7 +253,7 @@ public: TableName(), Strings::Implode(", ", v), PrimaryKey(), - e.charid + e.character_id ) ); @@ -267,21 +267,21 @@ public: { std::vector v; - v.push_back(std::to_string(e.charid)); - v.push_back(std::to_string(e.slotid)); - v.push_back(std::to_string(e.itemid)); + v.push_back(std::to_string(e.character_id)); + v.push_back(std::to_string(e.slot_id)); + v.push_back(std::to_string(e.item_id)); v.push_back(std::to_string(e.charges)); v.push_back(std::to_string(e.color)); - v.push_back(std::to_string(e.augslot1)); - v.push_back(std::to_string(e.augslot2)); - v.push_back(std::to_string(e.augslot3)); - v.push_back(std::to_string(e.augslot4)); - v.push_back(std::to_string(e.augslot5)); - v.push_back(std::to_string(e.augslot6)); + v.push_back(std::to_string(e.augment_one)); + v.push_back(std::to_string(e.augment_two)); + v.push_back(std::to_string(e.augment_three)); + v.push_back(std::to_string(e.augment_four)); + v.push_back(std::to_string(e.augment_five)); + v.push_back(std::to_string(e.augment_six)); v.push_back(std::to_string(e.instnodrop)); v.push_back("'" + Strings::Escape(e.custom_data) + "'"); - v.push_back(std::to_string(e.ornamenticon)); - v.push_back(std::to_string(e.ornamentidfile)); + v.push_back(std::to_string(e.ornament_icon)); + v.push_back(std::to_string(e.ornament_idfile)); v.push_back(std::to_string(e.ornament_hero_model)); v.push_back(std::to_string(e.guid)); @@ -294,7 +294,7 @@ public: ); if (results.Success()) { - e.charid = results.LastInsertedID(); + e.character_id = results.LastInsertedID(); return e; } @@ -313,21 +313,21 @@ public: for (auto &e: entries) { std::vector v; - v.push_back(std::to_string(e.charid)); - v.push_back(std::to_string(e.slotid)); - v.push_back(std::to_string(e.itemid)); + v.push_back(std::to_string(e.character_id)); + v.push_back(std::to_string(e.slot_id)); + v.push_back(std::to_string(e.item_id)); v.push_back(std::to_string(e.charges)); v.push_back(std::to_string(e.color)); - v.push_back(std::to_string(e.augslot1)); - v.push_back(std::to_string(e.augslot2)); - v.push_back(std::to_string(e.augslot3)); - v.push_back(std::to_string(e.augslot4)); - v.push_back(std::to_string(e.augslot5)); - v.push_back(std::to_string(e.augslot6)); + v.push_back(std::to_string(e.augment_one)); + v.push_back(std::to_string(e.augment_two)); + v.push_back(std::to_string(e.augment_three)); + v.push_back(std::to_string(e.augment_four)); + v.push_back(std::to_string(e.augment_five)); + v.push_back(std::to_string(e.augment_six)); v.push_back(std::to_string(e.instnodrop)); v.push_back("'" + Strings::Escape(e.custom_data) + "'"); - v.push_back(std::to_string(e.ornamenticon)); - v.push_back(std::to_string(e.ornamentidfile)); + v.push_back(std::to_string(e.ornament_icon)); + v.push_back(std::to_string(e.ornament_idfile)); v.push_back(std::to_string(e.ornament_hero_model)); v.push_back(std::to_string(e.guid)); @@ -363,21 +363,21 @@ public: for (auto row = results.begin(); row != results.end(); ++row) { Inventory e{}; - e.charid = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; - e.slotid = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; - e.itemid = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.character_id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.slot_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; e.charges = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; e.color = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; - e.augslot1 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; - e.augslot2 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; - e.augslot3 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; - e.augslot4 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; - e.augslot5 = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; - e.augslot6 = row[10] ? static_cast(atoi(row[10])) : 0; + e.augment_one = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.augment_two = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.augment_three = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.augment_four = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.augment_five = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.augment_six = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; e.instnodrop = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; e.custom_data = row[12] ? row[12] : ""; - e.ornamenticon = row[13] ? static_cast(strtoul(row[13], nullptr, 10)) : 0; - e.ornamentidfile = row[14] ? static_cast(strtoul(row[14], nullptr, 10)) : 0; + e.ornament_icon = row[13] ? static_cast(strtoul(row[13], nullptr, 10)) : 0; + e.ornament_idfile = row[14] ? static_cast(strtoul(row[14], nullptr, 10)) : 0; e.ornament_hero_model = row[15] ? static_cast(atoi(row[15])) : 0; e.guid = row[16] ? strtoull(row[16], nullptr, 10) : 0; @@ -404,21 +404,21 @@ public: for (auto row = results.begin(); row != results.end(); ++row) { Inventory e{}; - e.charid = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; - e.slotid = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; - e.itemid = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.character_id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.slot_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; e.charges = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; e.color = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; - e.augslot1 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; - e.augslot2 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; - e.augslot3 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; - e.augslot4 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; - e.augslot5 = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; - e.augslot6 = row[10] ? static_cast(atoi(row[10])) : 0; + e.augment_one = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.augment_two = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.augment_three = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.augment_four = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.augment_five = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.augment_six = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; e.instnodrop = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; e.custom_data = row[12] ? row[12] : ""; - e.ornamenticon = row[13] ? static_cast(strtoul(row[13], nullptr, 10)) : 0; - e.ornamentidfile = row[14] ? static_cast(strtoul(row[14], nullptr, 10)) : 0; + e.ornament_icon = row[13] ? static_cast(strtoul(row[13], nullptr, 10)) : 0; + e.ornament_idfile = row[14] ? static_cast(strtoul(row[14], nullptr, 10)) : 0; e.ornament_hero_model = row[15] ? static_cast(atoi(row[15])) : 0; e.guid = row[16] ? strtoull(row[16], nullptr, 10) : 0; @@ -495,21 +495,21 @@ public: { std::vector v; - v.push_back(std::to_string(e.charid)); - v.push_back(std::to_string(e.slotid)); - v.push_back(std::to_string(e.itemid)); + v.push_back(std::to_string(e.character_id)); + v.push_back(std::to_string(e.slot_id)); + v.push_back(std::to_string(e.item_id)); v.push_back(std::to_string(e.charges)); v.push_back(std::to_string(e.color)); - v.push_back(std::to_string(e.augslot1)); - v.push_back(std::to_string(e.augslot2)); - v.push_back(std::to_string(e.augslot3)); - v.push_back(std::to_string(e.augslot4)); - v.push_back(std::to_string(e.augslot5)); - v.push_back(std::to_string(e.augslot6)); + v.push_back(std::to_string(e.augment_one)); + v.push_back(std::to_string(e.augment_two)); + v.push_back(std::to_string(e.augment_three)); + v.push_back(std::to_string(e.augment_four)); + v.push_back(std::to_string(e.augment_five)); + v.push_back(std::to_string(e.augment_six)); v.push_back(std::to_string(e.instnodrop)); v.push_back("'" + Strings::Escape(e.custom_data) + "'"); - v.push_back(std::to_string(e.ornamenticon)); - v.push_back(std::to_string(e.ornamentidfile)); + v.push_back(std::to_string(e.ornament_icon)); + v.push_back(std::to_string(e.ornament_idfile)); v.push_back(std::to_string(e.ornament_hero_model)); v.push_back(std::to_string(e.guid)); @@ -534,21 +534,21 @@ public: for (auto &e: entries) { std::vector v; - v.push_back(std::to_string(e.charid)); - v.push_back(std::to_string(e.slotid)); - v.push_back(std::to_string(e.itemid)); + v.push_back(std::to_string(e.character_id)); + v.push_back(std::to_string(e.slot_id)); + v.push_back(std::to_string(e.item_id)); v.push_back(std::to_string(e.charges)); v.push_back(std::to_string(e.color)); - v.push_back(std::to_string(e.augslot1)); - v.push_back(std::to_string(e.augslot2)); - v.push_back(std::to_string(e.augslot3)); - v.push_back(std::to_string(e.augslot4)); - v.push_back(std::to_string(e.augslot5)); - v.push_back(std::to_string(e.augslot6)); + v.push_back(std::to_string(e.augment_one)); + v.push_back(std::to_string(e.augment_two)); + v.push_back(std::to_string(e.augment_three)); + v.push_back(std::to_string(e.augment_four)); + v.push_back(std::to_string(e.augment_five)); + v.push_back(std::to_string(e.augment_six)); v.push_back(std::to_string(e.instnodrop)); v.push_back("'" + Strings::Escape(e.custom_data) + "'"); - v.push_back(std::to_string(e.ornamenticon)); - v.push_back(std::to_string(e.ornamentidfile)); + v.push_back(std::to_string(e.ornament_icon)); + v.push_back(std::to_string(e.ornament_idfile)); v.push_back(std::to_string(e.ornament_hero_model)); v.push_back(std::to_string(e.guid)); diff --git a/common/repositories/base/base_items_evolving_details_repository.h b/common/repositories/base/base_items_evolving_details_repository.h new file mode 100644 index 000000000..5930b3cd8 --- /dev/null +++ b/common/repositories/base/base_items_evolving_details_repository.h @@ -0,0 +1,451 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://docs.eqemu.io/developer/repositories + */ + +#ifndef EQEMU_BASE_ITEMS_EVOLVING_DETAILS_REPOSITORY_H +#define EQEMU_BASE_ITEMS_EVOLVING_DETAILS_REPOSITORY_H + +#include "../../database.h" +#include "../../strings.h" +#include + +class BaseItemsEvolvingDetailsRepository { +public: + struct ItemsEvolvingDetails { + uint32_t id; + uint32_t item_evo_id; + uint32_t item_evolve_level; + uint32_t item_id; + uint32_t type; + std::string sub_type; + int64_t required_amount; + }; + + static std::string PrimaryKey() + { + return std::string("id"); + } + + static std::vector Columns() + { + return { + "id", + "item_evo_id", + "item_evolve_level", + "item_id", + "type", + "sub_type", + "required_amount", + }; + } + + static std::vector SelectColumns() + { + return { + "id", + "item_evo_id", + "item_evolve_level", + "item_id", + "type", + "sub_type", + "required_amount", + }; + } + + static std::string ColumnsRaw() + { + return std::string(Strings::Implode(", ", Columns())); + } + + static std::string SelectColumnsRaw() + { + return std::string(Strings::Implode(", ", SelectColumns())); + } + + static std::string TableName() + { + return std::string("items_evolving_details"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + SelectColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static ItemsEvolvingDetails NewEntity() + { + ItemsEvolvingDetails e{}; + + e.id = 0; + e.item_evo_id = 0; + e.item_evolve_level = 0; + e.item_id = 0; + e.type = 0; + e.sub_type = "0"; + e.required_amount = 0; + + return e; + } + + static ItemsEvolvingDetails GetItemsEvolvingDetails( + const std::vector &items_evolving_detailss, + int items_evolving_details_id + ) + { + for (auto &items_evolving_details : items_evolving_detailss) { + if (items_evolving_details.id == items_evolving_details_id) { + return items_evolving_details; + } + } + + return NewEntity(); + } + + static ItemsEvolvingDetails FindOne( + Database& db, + int items_evolving_details_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {} = {} LIMIT 1", + BaseSelect(), + PrimaryKey(), + items_evolving_details_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + ItemsEvolvingDetails e{}; + + e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.item_evo_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.item_evolve_level = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.item_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.type = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.sub_type = row[5] ? row[5] : "0"; + e.required_amount = row[6] ? strtoll(row[6], nullptr, 10) : 0; + + return e; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int items_evolving_details_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + items_evolving_details_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + const ItemsEvolvingDetails &e + ) + { + std::vector v; + + auto columns = Columns(); + + v.push_back(columns[1] + " = " + std::to_string(e.item_evo_id)); + v.push_back(columns[2] + " = " + std::to_string(e.item_evolve_level)); + v.push_back(columns[3] + " = " + std::to_string(e.item_id)); + v.push_back(columns[4] + " = " + std::to_string(e.type)); + v.push_back(columns[5] + " = '" + Strings::Escape(e.sub_type) + "'"); + v.push_back(columns[6] + " = " + std::to_string(e.required_amount)); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + Strings::Implode(", ", v), + PrimaryKey(), + e.id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static ItemsEvolvingDetails InsertOne( + Database& db, + ItemsEvolvingDetails e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.item_evo_id)); + v.push_back(std::to_string(e.item_evolve_level)); + v.push_back(std::to_string(e.item_id)); + v.push_back(std::to_string(e.type)); + v.push_back("'" + Strings::Escape(e.sub_type) + "'"); + v.push_back(std::to_string(e.required_amount)); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + Strings::Implode(",", v) + ) + ); + + if (results.Success()) { + e.id = results.LastInsertedID(); + return e; + } + + e = NewEntity(); + + return e; + } + + static int InsertMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.item_evo_id)); + v.push_back(std::to_string(e.item_evolve_level)); + v.push_back(std::to_string(e.item_id)); + v.push_back(std::to_string(e.type)); + v.push_back("'" + Strings::Escape(e.sub_type) + "'"); + v.push_back(std::to_string(e.required_amount)); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + ItemsEvolvingDetails e{}; + + e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.item_evo_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.item_evolve_level = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.item_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.type = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.sub_type = row[5] ? row[5] : "0"; + e.required_amount = row[6] ? strtoll(row[6], nullptr, 10) : 0; + + all_entries.push_back(e); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, const std::string &where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + ItemsEvolvingDetails e{}; + + e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.item_evo_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.item_evolve_level = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.item_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.type = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.sub_type = row[5] ? row[5] : "0"; + e.required_amount = row[6] ? strtoll(row[6], nullptr, 10) : 0; + + all_entries.push_back(e); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, const std::string &where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int64 GetMaxId(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COALESCE(MAX({}), 0) FROM {}", + PrimaryKey(), + TableName() + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static int64 Count(Database& db, const std::string &where_filter = "") + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COUNT(*) FROM {} {}", + TableName(), + (where_filter.empty() ? "" : "WHERE " + where_filter) + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static std::string BaseReplace() + { + return fmt::format( + "REPLACE INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static int ReplaceOne( + Database& db, + const ItemsEvolvingDetails &e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.item_evo_id)); + v.push_back(std::to_string(e.item_evolve_level)); + v.push_back(std::to_string(e.item_id)); + v.push_back(std::to_string(e.type)); + v.push_back("'" + Strings::Escape(e.sub_type) + "'"); + v.push_back(std::to_string(e.required_amount)); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseReplace(), + Strings::Implode(",", v) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int ReplaceMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.item_evo_id)); + v.push_back(std::to_string(e.item_evolve_level)); + v.push_back(std::to_string(e.item_id)); + v.push_back(std::to_string(e.type)); + v.push_back("'" + Strings::Escape(e.sub_type) + "'"); + v.push_back(std::to_string(e.required_amount)); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseReplace(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } +}; + +#endif //EQEMU_BASE_ITEMS_EVOLVING_DETAILS_REPOSITORY_H diff --git a/common/repositories/base/base_npc_types_repository.h b/common/repositories/base/base_npc_types_repository.h index 1eb12660b..f6019e987 100644 --- a/common/repositories/base/base_npc_types_repository.h +++ b/common/repositories/base/base_npc_types_repository.h @@ -123,7 +123,7 @@ public: int8_t legtexture; int8_t feettexture; int8_t light; - int8_t walkspeed; + float walkspeed; int32_t peqid; int8_t unique_; int8_t fixed; @@ -148,6 +148,8 @@ public: int32_t faction_amount; uint8_t keeps_sold_items; uint8_t is_parcel_merchant; + uint8_t multiquest_enabled; + uint16_t npc_tint_id; }; static std::string PrimaryKey() @@ -287,6 +289,8 @@ public: "faction_amount", "keeps_sold_items", "is_parcel_merchant", + "multiquest_enabled", + "npc_tint_id", }; } @@ -422,6 +426,8 @@ public: "faction_amount", "keeps_sold_items", "is_parcel_merchant", + "multiquest_enabled", + "npc_tint_id", }; } @@ -591,6 +597,8 @@ public: e.faction_amount = 0; e.keeps_sold_items = 1; e.is_parcel_merchant = 0; + e.multiquest_enabled = 0; + e.npc_tint_id = 0; return e; } @@ -731,7 +739,7 @@ public: e.legtexture = row[101] ? static_cast(atoi(row[101])) : 0; e.feettexture = row[102] ? static_cast(atoi(row[102])) : 0; e.light = row[103] ? static_cast(atoi(row[103])) : 0; - e.walkspeed = row[104] ? static_cast(atoi(row[104])) : 0; + e.walkspeed = row[104] ? strtof(row[104], nullptr) : 0; e.peqid = row[105] ? static_cast(atoi(row[105])) : 0; e.unique_ = row[106] ? static_cast(atoi(row[106])) : 0; e.fixed = row[107] ? static_cast(atoi(row[107])) : 0; @@ -756,6 +764,8 @@ public: e.faction_amount = row[126] ? static_cast(atoi(row[126])) : 0; e.keeps_sold_items = row[127] ? static_cast(strtoul(row[127], nullptr, 10)) : 1; e.is_parcel_merchant = row[128] ? static_cast(strtoul(row[128], nullptr, 10)) : 0; + e.multiquest_enabled = row[129] ? static_cast(strtoul(row[129], nullptr, 10)) : 0; + e.npc_tint_id = row[130] ? static_cast(strtoul(row[130], nullptr, 10)) : 0; return e; } @@ -917,6 +927,8 @@ public: v.push_back(columns[126] + " = " + std::to_string(e.faction_amount)); v.push_back(columns[127] + " = " + std::to_string(e.keeps_sold_items)); v.push_back(columns[128] + " = " + std::to_string(e.is_parcel_merchant)); + v.push_back(columns[129] + " = " + std::to_string(e.multiquest_enabled)); + v.push_back(columns[130] + " = " + std::to_string(e.npc_tint_id)); auto results = db.QueryDatabase( fmt::format( @@ -1067,6 +1079,8 @@ public: v.push_back(std::to_string(e.faction_amount)); v.push_back(std::to_string(e.keeps_sold_items)); v.push_back(std::to_string(e.is_parcel_merchant)); + v.push_back(std::to_string(e.multiquest_enabled)); + v.push_back(std::to_string(e.npc_tint_id)); auto results = db.QueryDatabase( fmt::format( @@ -1225,6 +1239,8 @@ public: v.push_back(std::to_string(e.faction_amount)); v.push_back(std::to_string(e.keeps_sold_items)); v.push_back(std::to_string(e.is_parcel_merchant)); + v.push_back(std::to_string(e.multiquest_enabled)); + v.push_back(std::to_string(e.npc_tint_id)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } @@ -1362,7 +1378,7 @@ public: e.legtexture = row[101] ? static_cast(atoi(row[101])) : 0; e.feettexture = row[102] ? static_cast(atoi(row[102])) : 0; e.light = row[103] ? static_cast(atoi(row[103])) : 0; - e.walkspeed = row[104] ? static_cast(atoi(row[104])) : 0; + e.walkspeed = row[104] ? strtof(row[104], nullptr) : 0; e.peqid = row[105] ? static_cast(atoi(row[105])) : 0; e.unique_ = row[106] ? static_cast(atoi(row[106])) : 0; e.fixed = row[107] ? static_cast(atoi(row[107])) : 0; @@ -1387,6 +1403,8 @@ public: e.faction_amount = row[126] ? static_cast(atoi(row[126])) : 0; e.keeps_sold_items = row[127] ? static_cast(strtoul(row[127], nullptr, 10)) : 1; e.is_parcel_merchant = row[128] ? static_cast(strtoul(row[128], nullptr, 10)) : 0; + e.multiquest_enabled = row[129] ? static_cast(strtoul(row[129], nullptr, 10)) : 0; + e.npc_tint_id = row[130] ? static_cast(strtoul(row[130], nullptr, 10)) : 0; all_entries.push_back(e); } @@ -1515,7 +1533,7 @@ public: e.legtexture = row[101] ? static_cast(atoi(row[101])) : 0; e.feettexture = row[102] ? static_cast(atoi(row[102])) : 0; e.light = row[103] ? static_cast(atoi(row[103])) : 0; - e.walkspeed = row[104] ? static_cast(atoi(row[104])) : 0; + e.walkspeed = row[104] ? strtof(row[104], nullptr) : 0; e.peqid = row[105] ? static_cast(atoi(row[105])) : 0; e.unique_ = row[106] ? static_cast(atoi(row[106])) : 0; e.fixed = row[107] ? static_cast(atoi(row[107])) : 0; @@ -1540,6 +1558,8 @@ public: e.faction_amount = row[126] ? static_cast(atoi(row[126])) : 0; e.keeps_sold_items = row[127] ? static_cast(strtoul(row[127], nullptr, 10)) : 1; e.is_parcel_merchant = row[128] ? static_cast(strtoul(row[128], nullptr, 10)) : 0; + e.multiquest_enabled = row[129] ? static_cast(strtoul(row[129], nullptr, 10)) : 0; + e.npc_tint_id = row[130] ? static_cast(strtoul(row[130], nullptr, 10)) : 0; all_entries.push_back(e); } @@ -1743,6 +1763,8 @@ public: v.push_back(std::to_string(e.faction_amount)); v.push_back(std::to_string(e.keeps_sold_items)); v.push_back(std::to_string(e.is_parcel_merchant)); + v.push_back(std::to_string(e.multiquest_enabled)); + v.push_back(std::to_string(e.npc_tint_id)); auto results = db.QueryDatabase( fmt::format( @@ -1894,6 +1916,8 @@ public: v.push_back(std::to_string(e.faction_amount)); v.push_back(std::to_string(e.keeps_sold_items)); v.push_back(std::to_string(e.is_parcel_merchant)); + v.push_back(std::to_string(e.multiquest_enabled)); + v.push_back(std::to_string(e.npc_tint_id)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } diff --git a/common/repositories/base/base_pets_beastlord_data_repository.h b/common/repositories/base/base_pets_beastlord_data_repository.h index 1ae32f793..9960a8c36 100644 --- a/common/repositories/base/base_pets_beastlord_data_repository.h +++ b/common/repositories/base/base_pets_beastlord_data_repository.h @@ -144,6 +144,7 @@ public: e.texture = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; e.helm_texture = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; e.gender = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 2; + e.size_modifier = row[5] ? (strtof(row[5], nullptr) > 0.0f ? strtof(row[5], nullptr) : 1) : 1; e.face = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; return e; @@ -287,6 +288,7 @@ public: e.texture = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; e.helm_texture = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; e.gender = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 2; + e.size_modifier = row[5] ? (strtof(row[5], nullptr) > 0.0f ? strtof(row[5], nullptr) : 1) : 1; e.face = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; all_entries.push_back(e); @@ -317,6 +319,7 @@ public: e.texture = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; e.helm_texture = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; e.gender = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 2; + e.size_modifier = row[5] ? (strtof(row[5], nullptr) > 0.0f ? strtof(row[5], nullptr) : 1) : 1; e.face = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; all_entries.push_back(e); diff --git a/common/repositories/base/base_player_event_aa_purchase_repository.h b/common/repositories/base/base_player_event_aa_purchase_repository.h new file mode 100644 index 000000000..ca8fb2724 --- /dev/null +++ b/common/repositories/base/base_player_event_aa_purchase_repository.h @@ -0,0 +1,439 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://docs.eqemu.io/developer/repositories + */ + +#ifndef EQEMU_BASE_PLAYER_EVENT_AA_PURCHASE_REPOSITORY_H +#define EQEMU_BASE_PLAYER_EVENT_AA_PURCHASE_REPOSITORY_H + +#include "../../database.h" +#include "../../strings.h" +#include + +class BasePlayerEventAaPurchaseRepository { +public: + struct PlayerEventAaPurchase { + uint64_t id; + int32_t aa_ability_id; + int32_t cost; + int32_t previous_id; + int32_t next_id; + time_t created_at; + }; + + static std::string PrimaryKey() + { + return std::string("id"); + } + + static std::vector Columns() + { + return { + "id", + "aa_ability_id", + "cost", + "previous_id", + "next_id", + "created_at", + }; + } + + static std::vector SelectColumns() + { + return { + "id", + "aa_ability_id", + "cost", + "previous_id", + "next_id", + "UNIX_TIMESTAMP(created_at)", + }; + } + + static std::string ColumnsRaw() + { + return std::string(Strings::Implode(", ", Columns())); + } + + static std::string SelectColumnsRaw() + { + return std::string(Strings::Implode(", ", SelectColumns())); + } + + static std::string TableName() + { + return std::string("player_event_aa_purchase"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + SelectColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static PlayerEventAaPurchase NewEntity() + { + PlayerEventAaPurchase e{}; + + e.id = 0; + e.aa_ability_id = 0; + e.cost = 0; + e.previous_id = 0; + e.next_id = 0; + e.created_at = 0; + + return e; + } + + static PlayerEventAaPurchase GetPlayerEventAaPurchase( + const std::vector &player_event_aa_purchases, + int player_event_aa_purchase_id + ) + { + for (auto &player_event_aa_purchase : player_event_aa_purchases) { + if (player_event_aa_purchase.id == player_event_aa_purchase_id) { + return player_event_aa_purchase; + } + } + + return NewEntity(); + } + + static PlayerEventAaPurchase FindOne( + Database& db, + int player_event_aa_purchase_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {} = {} LIMIT 1", + BaseSelect(), + PrimaryKey(), + player_event_aa_purchase_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + PlayerEventAaPurchase e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.aa_ability_id = row[1] ? static_cast(atoi(row[1])) : 0; + e.cost = row[2] ? static_cast(atoi(row[2])) : 0; + e.previous_id = row[3] ? static_cast(atoi(row[3])) : 0; + e.next_id = row[4] ? static_cast(atoi(row[4])) : 0; + e.created_at = strtoll(row[5] ? row[5] : "-1", nullptr, 10); + + return e; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int player_event_aa_purchase_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + player_event_aa_purchase_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + const PlayerEventAaPurchase &e + ) + { + std::vector v; + + auto columns = Columns(); + + v.push_back(columns[1] + " = " + std::to_string(e.aa_ability_id)); + v.push_back(columns[2] + " = " + std::to_string(e.cost)); + v.push_back(columns[3] + " = " + std::to_string(e.previous_id)); + v.push_back(columns[4] + " = " + std::to_string(e.next_id)); + v.push_back(columns[5] + " = FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + Strings::Implode(", ", v), + PrimaryKey(), + e.id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static PlayerEventAaPurchase InsertOne( + Database& db, + PlayerEventAaPurchase e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.aa_ability_id)); + v.push_back(std::to_string(e.cost)); + v.push_back(std::to_string(e.previous_id)); + v.push_back(std::to_string(e.next_id)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + Strings::Implode(",", v) + ) + ); + + if (results.Success()) { + e.id = results.LastInsertedID(); + return e; + } + + e = NewEntity(); + + return e; + } + + static int InsertMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.aa_ability_id)); + v.push_back(std::to_string(e.cost)); + v.push_back(std::to_string(e.previous_id)); + v.push_back(std::to_string(e.next_id)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + PlayerEventAaPurchase e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.aa_ability_id = row[1] ? static_cast(atoi(row[1])) : 0; + e.cost = row[2] ? static_cast(atoi(row[2])) : 0; + e.previous_id = row[3] ? static_cast(atoi(row[3])) : 0; + e.next_id = row[4] ? static_cast(atoi(row[4])) : 0; + e.created_at = strtoll(row[5] ? row[5] : "-1", nullptr, 10); + + all_entries.push_back(e); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, const std::string &where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + PlayerEventAaPurchase e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.aa_ability_id = row[1] ? static_cast(atoi(row[1])) : 0; + e.cost = row[2] ? static_cast(atoi(row[2])) : 0; + e.previous_id = row[3] ? static_cast(atoi(row[3])) : 0; + e.next_id = row[4] ? static_cast(atoi(row[4])) : 0; + e.created_at = strtoll(row[5] ? row[5] : "-1", nullptr, 10); + + all_entries.push_back(e); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, const std::string &where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int64 GetMaxId(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COALESCE(MAX({}), 0) FROM {}", + PrimaryKey(), + TableName() + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static int64 Count(Database& db, const std::string &where_filter = "") + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COUNT(*) FROM {} {}", + TableName(), + (where_filter.empty() ? "" : "WHERE " + where_filter) + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static std::string BaseReplace() + { + return fmt::format( + "REPLACE INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static int ReplaceOne( + Database& db, + const PlayerEventAaPurchase &e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.aa_ability_id)); + v.push_back(std::to_string(e.cost)); + v.push_back(std::to_string(e.previous_id)); + v.push_back(std::to_string(e.next_id)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseReplace(), + Strings::Implode(",", v) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int ReplaceMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.aa_ability_id)); + v.push_back(std::to_string(e.cost)); + v.push_back(std::to_string(e.previous_id)); + v.push_back(std::to_string(e.next_id)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseReplace(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } +}; + +#endif //EQEMU_BASE_PLAYER_EVENT_AA_PURCHASE_REPOSITORY_H diff --git a/common/repositories/base/base_player_event_killed_named_npc_repository.h b/common/repositories/base/base_player_event_killed_named_npc_repository.h new file mode 100644 index 000000000..027e5afbf --- /dev/null +++ b/common/repositories/base/base_player_event_killed_named_npc_repository.h @@ -0,0 +1,451 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://docs.eqemu.io/developer/repositories + */ + +#ifndef EQEMU_BASE_PLAYER_EVENT_KILLED_NAMED_NPC_REPOSITORY_H +#define EQEMU_BASE_PLAYER_EVENT_KILLED_NAMED_NPC_REPOSITORY_H + +#include "../../database.h" +#include "../../strings.h" +#include + +class BasePlayerEventKilledNamedNpcRepository { +public: + struct PlayerEventKilledNamedNpc { + uint64_t id; + uint32_t npc_id; + std::string npc_name; + uint32_t combat_time_seconds; + uint64_t total_damage_per_second_taken; + uint64_t total_heal_per_second_taken; + time_t created_at; + }; + + static std::string PrimaryKey() + { + return std::string("id"); + } + + static std::vector Columns() + { + return { + "id", + "npc_id", + "npc_name", + "combat_time_seconds", + "total_damage_per_second_taken", + "total_heal_per_second_taken", + "created_at", + }; + } + + static std::vector SelectColumns() + { + return { + "id", + "npc_id", + "npc_name", + "combat_time_seconds", + "total_damage_per_second_taken", + "total_heal_per_second_taken", + "UNIX_TIMESTAMP(created_at)", + }; + } + + static std::string ColumnsRaw() + { + return std::string(Strings::Implode(", ", Columns())); + } + + static std::string SelectColumnsRaw() + { + return std::string(Strings::Implode(", ", SelectColumns())); + } + + static std::string TableName() + { + return std::string("player_event_killed_named_npc"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + SelectColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static PlayerEventKilledNamedNpc NewEntity() + { + PlayerEventKilledNamedNpc e{}; + + e.id = 0; + e.npc_id = 0; + e.npc_name = ""; + e.combat_time_seconds = 0; + e.total_damage_per_second_taken = 0; + e.total_heal_per_second_taken = 0; + e.created_at = 0; + + return e; + } + + static PlayerEventKilledNamedNpc GetPlayerEventKilledNamedNpc( + const std::vector &player_event_killed_named_npcs, + int player_event_killed_named_npc_id + ) + { + for (auto &player_event_killed_named_npc : player_event_killed_named_npcs) { + if (player_event_killed_named_npc.id == player_event_killed_named_npc_id) { + return player_event_killed_named_npc; + } + } + + return NewEntity(); + } + + static PlayerEventKilledNamedNpc FindOne( + Database& db, + int player_event_killed_named_npc_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {} = {} LIMIT 1", + BaseSelect(), + PrimaryKey(), + player_event_killed_named_npc_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + PlayerEventKilledNamedNpc e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.npc_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.npc_name = row[2] ? row[2] : ""; + e.combat_time_seconds = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.total_damage_per_second_taken = row[4] ? strtoull(row[4], nullptr, 10) : 0; + e.total_heal_per_second_taken = row[5] ? strtoull(row[5], nullptr, 10) : 0; + e.created_at = strtoll(row[6] ? row[6] : "-1", nullptr, 10); + + return e; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int player_event_killed_named_npc_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + player_event_killed_named_npc_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + const PlayerEventKilledNamedNpc &e + ) + { + std::vector v; + + auto columns = Columns(); + + v.push_back(columns[1] + " = " + std::to_string(e.npc_id)); + v.push_back(columns[2] + " = '" + Strings::Escape(e.npc_name) + "'"); + v.push_back(columns[3] + " = " + std::to_string(e.combat_time_seconds)); + v.push_back(columns[4] + " = " + std::to_string(e.total_damage_per_second_taken)); + v.push_back(columns[5] + " = " + std::to_string(e.total_heal_per_second_taken)); + v.push_back(columns[6] + " = FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + Strings::Implode(", ", v), + PrimaryKey(), + e.id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static PlayerEventKilledNamedNpc InsertOne( + Database& db, + PlayerEventKilledNamedNpc e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.npc_id)); + v.push_back("'" + Strings::Escape(e.npc_name) + "'"); + v.push_back(std::to_string(e.combat_time_seconds)); + v.push_back(std::to_string(e.total_damage_per_second_taken)); + v.push_back(std::to_string(e.total_heal_per_second_taken)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + Strings::Implode(",", v) + ) + ); + + if (results.Success()) { + e.id = results.LastInsertedID(); + return e; + } + + e = NewEntity(); + + return e; + } + + static int InsertMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.npc_id)); + v.push_back("'" + Strings::Escape(e.npc_name) + "'"); + v.push_back(std::to_string(e.combat_time_seconds)); + v.push_back(std::to_string(e.total_damage_per_second_taken)); + v.push_back(std::to_string(e.total_heal_per_second_taken)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + PlayerEventKilledNamedNpc e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.npc_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.npc_name = row[2] ? row[2] : ""; + e.combat_time_seconds = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.total_damage_per_second_taken = row[4] ? strtoull(row[4], nullptr, 10) : 0; + e.total_heal_per_second_taken = row[5] ? strtoull(row[5], nullptr, 10) : 0; + e.created_at = strtoll(row[6] ? row[6] : "-1", nullptr, 10); + + all_entries.push_back(e); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, const std::string &where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + PlayerEventKilledNamedNpc e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.npc_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.npc_name = row[2] ? row[2] : ""; + e.combat_time_seconds = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.total_damage_per_second_taken = row[4] ? strtoull(row[4], nullptr, 10) : 0; + e.total_heal_per_second_taken = row[5] ? strtoull(row[5], nullptr, 10) : 0; + e.created_at = strtoll(row[6] ? row[6] : "-1", nullptr, 10); + + all_entries.push_back(e); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, const std::string &where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int64 GetMaxId(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COALESCE(MAX({}), 0) FROM {}", + PrimaryKey(), + TableName() + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static int64 Count(Database& db, const std::string &where_filter = "") + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COUNT(*) FROM {} {}", + TableName(), + (where_filter.empty() ? "" : "WHERE " + where_filter) + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static std::string BaseReplace() + { + return fmt::format( + "REPLACE INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static int ReplaceOne( + Database& db, + const PlayerEventKilledNamedNpc &e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.npc_id)); + v.push_back("'" + Strings::Escape(e.npc_name) + "'"); + v.push_back(std::to_string(e.combat_time_seconds)); + v.push_back(std::to_string(e.total_damage_per_second_taken)); + v.push_back(std::to_string(e.total_heal_per_second_taken)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseReplace(), + Strings::Implode(",", v) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int ReplaceMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.npc_id)); + v.push_back("'" + Strings::Escape(e.npc_name) + "'"); + v.push_back(std::to_string(e.combat_time_seconds)); + v.push_back(std::to_string(e.total_damage_per_second_taken)); + v.push_back(std::to_string(e.total_heal_per_second_taken)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseReplace(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } +}; + +#endif //EQEMU_BASE_PLAYER_EVENT_KILLED_NAMED_NPC_REPOSITORY_H diff --git a/common/repositories/base/base_player_event_killed_npc_repository.h b/common/repositories/base/base_player_event_killed_npc_repository.h new file mode 100644 index 000000000..13fb3fe86 --- /dev/null +++ b/common/repositories/base/base_player_event_killed_npc_repository.h @@ -0,0 +1,451 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://docs.eqemu.io/developer/repositories + */ + +#ifndef EQEMU_BASE_PLAYER_EVENT_KILLED_NPC_REPOSITORY_H +#define EQEMU_BASE_PLAYER_EVENT_KILLED_NPC_REPOSITORY_H + +#include "../../database.h" +#include "../../strings.h" +#include + +class BasePlayerEventKilledNpcRepository { +public: + struct PlayerEventKilledNpc { + uint64_t id; + uint32_t npc_id; + std::string npc_name; + uint32_t combat_time_seconds; + uint64_t total_damage_per_second_taken; + uint64_t total_heal_per_second_taken; + time_t created_at; + }; + + static std::string PrimaryKey() + { + return std::string("id"); + } + + static std::vector Columns() + { + return { + "id", + "npc_id", + "npc_name", + "combat_time_seconds", + "total_damage_per_second_taken", + "total_heal_per_second_taken", + "created_at", + }; + } + + static std::vector SelectColumns() + { + return { + "id", + "npc_id", + "npc_name", + "combat_time_seconds", + "total_damage_per_second_taken", + "total_heal_per_second_taken", + "UNIX_TIMESTAMP(created_at)", + }; + } + + static std::string ColumnsRaw() + { + return std::string(Strings::Implode(", ", Columns())); + } + + static std::string SelectColumnsRaw() + { + return std::string(Strings::Implode(", ", SelectColumns())); + } + + static std::string TableName() + { + return std::string("player_event_killed_npc"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + SelectColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static PlayerEventKilledNpc NewEntity() + { + PlayerEventKilledNpc e{}; + + e.id = 0; + e.npc_id = 0; + e.npc_name = ""; + e.combat_time_seconds = 0; + e.total_damage_per_second_taken = 0; + e.total_heal_per_second_taken = 0; + e.created_at = 0; + + return e; + } + + static PlayerEventKilledNpc GetPlayerEventKilledNpc( + const std::vector &player_event_killed_npcs, + int player_event_killed_npc_id + ) + { + for (auto &player_event_killed_npc : player_event_killed_npcs) { + if (player_event_killed_npc.id == player_event_killed_npc_id) { + return player_event_killed_npc; + } + } + + return NewEntity(); + } + + static PlayerEventKilledNpc FindOne( + Database& db, + int player_event_killed_npc_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {} = {} LIMIT 1", + BaseSelect(), + PrimaryKey(), + player_event_killed_npc_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + PlayerEventKilledNpc e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.npc_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.npc_name = row[2] ? row[2] : ""; + e.combat_time_seconds = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.total_damage_per_second_taken = row[4] ? strtoull(row[4], nullptr, 10) : 0; + e.total_heal_per_second_taken = row[5] ? strtoull(row[5], nullptr, 10) : 0; + e.created_at = strtoll(row[6] ? row[6] : "-1", nullptr, 10); + + return e; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int player_event_killed_npc_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + player_event_killed_npc_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + const PlayerEventKilledNpc &e + ) + { + std::vector v; + + auto columns = Columns(); + + v.push_back(columns[1] + " = " + std::to_string(e.npc_id)); + v.push_back(columns[2] + " = '" + Strings::Escape(e.npc_name) + "'"); + v.push_back(columns[3] + " = " + std::to_string(e.combat_time_seconds)); + v.push_back(columns[4] + " = " + std::to_string(e.total_damage_per_second_taken)); + v.push_back(columns[5] + " = " + std::to_string(e.total_heal_per_second_taken)); + v.push_back(columns[6] + " = FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + Strings::Implode(", ", v), + PrimaryKey(), + e.id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static PlayerEventKilledNpc InsertOne( + Database& db, + PlayerEventKilledNpc e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.npc_id)); + v.push_back("'" + Strings::Escape(e.npc_name) + "'"); + v.push_back(std::to_string(e.combat_time_seconds)); + v.push_back(std::to_string(e.total_damage_per_second_taken)); + v.push_back(std::to_string(e.total_heal_per_second_taken)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + Strings::Implode(",", v) + ) + ); + + if (results.Success()) { + e.id = results.LastInsertedID(); + return e; + } + + e = NewEntity(); + + return e; + } + + static int InsertMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.npc_id)); + v.push_back("'" + Strings::Escape(e.npc_name) + "'"); + v.push_back(std::to_string(e.combat_time_seconds)); + v.push_back(std::to_string(e.total_damage_per_second_taken)); + v.push_back(std::to_string(e.total_heal_per_second_taken)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + PlayerEventKilledNpc e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.npc_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.npc_name = row[2] ? row[2] : ""; + e.combat_time_seconds = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.total_damage_per_second_taken = row[4] ? strtoull(row[4], nullptr, 10) : 0; + e.total_heal_per_second_taken = row[5] ? strtoull(row[5], nullptr, 10) : 0; + e.created_at = strtoll(row[6] ? row[6] : "-1", nullptr, 10); + + all_entries.push_back(e); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, const std::string &where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + PlayerEventKilledNpc e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.npc_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.npc_name = row[2] ? row[2] : ""; + e.combat_time_seconds = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.total_damage_per_second_taken = row[4] ? strtoull(row[4], nullptr, 10) : 0; + e.total_heal_per_second_taken = row[5] ? strtoull(row[5], nullptr, 10) : 0; + e.created_at = strtoll(row[6] ? row[6] : "-1", nullptr, 10); + + all_entries.push_back(e); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, const std::string &where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int64 GetMaxId(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COALESCE(MAX({}), 0) FROM {}", + PrimaryKey(), + TableName() + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static int64 Count(Database& db, const std::string &where_filter = "") + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COUNT(*) FROM {} {}", + TableName(), + (where_filter.empty() ? "" : "WHERE " + where_filter) + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static std::string BaseReplace() + { + return fmt::format( + "REPLACE INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static int ReplaceOne( + Database& db, + const PlayerEventKilledNpc &e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.npc_id)); + v.push_back("'" + Strings::Escape(e.npc_name) + "'"); + v.push_back(std::to_string(e.combat_time_seconds)); + v.push_back(std::to_string(e.total_damage_per_second_taken)); + v.push_back(std::to_string(e.total_heal_per_second_taken)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseReplace(), + Strings::Implode(",", v) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int ReplaceMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.npc_id)); + v.push_back("'" + Strings::Escape(e.npc_name) + "'"); + v.push_back(std::to_string(e.combat_time_seconds)); + v.push_back(std::to_string(e.total_damage_per_second_taken)); + v.push_back(std::to_string(e.total_heal_per_second_taken)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseReplace(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } +}; + +#endif //EQEMU_BASE_PLAYER_EVENT_KILLED_NPC_REPOSITORY_H diff --git a/common/repositories/base/base_player_event_killed_raid_npc_repository.h b/common/repositories/base/base_player_event_killed_raid_npc_repository.h new file mode 100644 index 000000000..beba705c6 --- /dev/null +++ b/common/repositories/base/base_player_event_killed_raid_npc_repository.h @@ -0,0 +1,451 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://docs.eqemu.io/developer/repositories + */ + +#ifndef EQEMU_BASE_PLAYER_EVENT_KILLED_RAID_NPC_REPOSITORY_H +#define EQEMU_BASE_PLAYER_EVENT_KILLED_RAID_NPC_REPOSITORY_H + +#include "../../database.h" +#include "../../strings.h" +#include + +class BasePlayerEventKilledRaidNpcRepository { +public: + struct PlayerEventKilledRaidNpc { + uint64_t id; + uint32_t npc_id; + std::string npc_name; + uint32_t combat_time_seconds; + uint64_t total_damage_per_second_taken; + uint64_t total_heal_per_second_taken; + time_t created_at; + }; + + static std::string PrimaryKey() + { + return std::string("id"); + } + + static std::vector Columns() + { + return { + "id", + "npc_id", + "npc_name", + "combat_time_seconds", + "total_damage_per_second_taken", + "total_heal_per_second_taken", + "created_at", + }; + } + + static std::vector SelectColumns() + { + return { + "id", + "npc_id", + "npc_name", + "combat_time_seconds", + "total_damage_per_second_taken", + "total_heal_per_second_taken", + "UNIX_TIMESTAMP(created_at)", + }; + } + + static std::string ColumnsRaw() + { + return std::string(Strings::Implode(", ", Columns())); + } + + static std::string SelectColumnsRaw() + { + return std::string(Strings::Implode(", ", SelectColumns())); + } + + static std::string TableName() + { + return std::string("player_event_killed_raid_npc"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + SelectColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static PlayerEventKilledRaidNpc NewEntity() + { + PlayerEventKilledRaidNpc e{}; + + e.id = 0; + e.npc_id = 0; + e.npc_name = ""; + e.combat_time_seconds = 0; + e.total_damage_per_second_taken = 0; + e.total_heal_per_second_taken = 0; + e.created_at = 0; + + return e; + } + + static PlayerEventKilledRaidNpc GetPlayerEventKilledRaidNpc( + const std::vector &player_event_killed_raid_npcs, + int player_event_killed_raid_npc_id + ) + { + for (auto &player_event_killed_raid_npc : player_event_killed_raid_npcs) { + if (player_event_killed_raid_npc.id == player_event_killed_raid_npc_id) { + return player_event_killed_raid_npc; + } + } + + return NewEntity(); + } + + static PlayerEventKilledRaidNpc FindOne( + Database& db, + int player_event_killed_raid_npc_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {} = {} LIMIT 1", + BaseSelect(), + PrimaryKey(), + player_event_killed_raid_npc_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + PlayerEventKilledRaidNpc e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.npc_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.npc_name = row[2] ? row[2] : ""; + e.combat_time_seconds = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.total_damage_per_second_taken = row[4] ? strtoull(row[4], nullptr, 10) : 0; + e.total_heal_per_second_taken = row[5] ? strtoull(row[5], nullptr, 10) : 0; + e.created_at = strtoll(row[6] ? row[6] : "-1", nullptr, 10); + + return e; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int player_event_killed_raid_npc_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + player_event_killed_raid_npc_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + const PlayerEventKilledRaidNpc &e + ) + { + std::vector v; + + auto columns = Columns(); + + v.push_back(columns[1] + " = " + std::to_string(e.npc_id)); + v.push_back(columns[2] + " = '" + Strings::Escape(e.npc_name) + "'"); + v.push_back(columns[3] + " = " + std::to_string(e.combat_time_seconds)); + v.push_back(columns[4] + " = " + std::to_string(e.total_damage_per_second_taken)); + v.push_back(columns[5] + " = " + std::to_string(e.total_heal_per_second_taken)); + v.push_back(columns[6] + " = FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + Strings::Implode(", ", v), + PrimaryKey(), + e.id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static PlayerEventKilledRaidNpc InsertOne( + Database& db, + PlayerEventKilledRaidNpc e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.npc_id)); + v.push_back("'" + Strings::Escape(e.npc_name) + "'"); + v.push_back(std::to_string(e.combat_time_seconds)); + v.push_back(std::to_string(e.total_damage_per_second_taken)); + v.push_back(std::to_string(e.total_heal_per_second_taken)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + Strings::Implode(",", v) + ) + ); + + if (results.Success()) { + e.id = results.LastInsertedID(); + return e; + } + + e = NewEntity(); + + return e; + } + + static int InsertMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.npc_id)); + v.push_back("'" + Strings::Escape(e.npc_name) + "'"); + v.push_back(std::to_string(e.combat_time_seconds)); + v.push_back(std::to_string(e.total_damage_per_second_taken)); + v.push_back(std::to_string(e.total_heal_per_second_taken)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + PlayerEventKilledRaidNpc e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.npc_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.npc_name = row[2] ? row[2] : ""; + e.combat_time_seconds = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.total_damage_per_second_taken = row[4] ? strtoull(row[4], nullptr, 10) : 0; + e.total_heal_per_second_taken = row[5] ? strtoull(row[5], nullptr, 10) : 0; + e.created_at = strtoll(row[6] ? row[6] : "-1", nullptr, 10); + + all_entries.push_back(e); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, const std::string &where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + PlayerEventKilledRaidNpc e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.npc_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.npc_name = row[2] ? row[2] : ""; + e.combat_time_seconds = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.total_damage_per_second_taken = row[4] ? strtoull(row[4], nullptr, 10) : 0; + e.total_heal_per_second_taken = row[5] ? strtoull(row[5], nullptr, 10) : 0; + e.created_at = strtoll(row[6] ? row[6] : "-1", nullptr, 10); + + all_entries.push_back(e); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, const std::string &where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int64 GetMaxId(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COALESCE(MAX({}), 0) FROM {}", + PrimaryKey(), + TableName() + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static int64 Count(Database& db, const std::string &where_filter = "") + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COUNT(*) FROM {} {}", + TableName(), + (where_filter.empty() ? "" : "WHERE " + where_filter) + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static std::string BaseReplace() + { + return fmt::format( + "REPLACE INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static int ReplaceOne( + Database& db, + const PlayerEventKilledRaidNpc &e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.npc_id)); + v.push_back("'" + Strings::Escape(e.npc_name) + "'"); + v.push_back(std::to_string(e.combat_time_seconds)); + v.push_back(std::to_string(e.total_damage_per_second_taken)); + v.push_back(std::to_string(e.total_heal_per_second_taken)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseReplace(), + Strings::Implode(",", v) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int ReplaceMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.npc_id)); + v.push_back("'" + Strings::Escape(e.npc_name) + "'"); + v.push_back(std::to_string(e.combat_time_seconds)); + v.push_back(std::to_string(e.total_damage_per_second_taken)); + v.push_back(std::to_string(e.total_heal_per_second_taken)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseReplace(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } +}; + +#endif //EQEMU_BASE_PLAYER_EVENT_KILLED_RAID_NPC_REPOSITORY_H diff --git a/common/repositories/base/base_player_event_log_settings_repository.h b/common/repositories/base/base_player_event_log_settings_repository.h index e74156cf1..94a53b981 100644 --- a/common/repositories/base/base_player_event_log_settings_repository.h +++ b/common/repositories/base/base_player_event_log_settings_repository.h @@ -15,7 +15,7 @@ #include "../../database.h" #include "../../strings.h" #include - +#include class BasePlayerEventLogSettingsRepository { public: struct PlayerEventLogSettings { @@ -24,6 +24,21 @@ public: int8_t event_enabled; int32_t retention_days; int32_t discord_webhook_id; + uint8_t etl_enabled; + + // cereal + template + void serialize(Archive &ar) + { + ar( + CEREAL_NVP(id), + CEREAL_NVP(event_name), + CEREAL_NVP(event_enabled), + CEREAL_NVP(retention_days), + CEREAL_NVP(discord_webhook_id), + CEREAL_NVP(etl_enabled) + ); + } }; static std::string PrimaryKey() @@ -39,6 +54,7 @@ public: "event_enabled", "retention_days", "discord_webhook_id", + "etl_enabled", }; } @@ -50,6 +66,7 @@ public: "event_enabled", "retention_days", "discord_webhook_id", + "etl_enabled", }; } @@ -95,6 +112,7 @@ public: e.event_enabled = 0; e.retention_days = 0; e.discord_webhook_id = 0; + e.etl_enabled = 0; return e; } @@ -136,6 +154,7 @@ public: e.event_enabled = row[2] ? static_cast(atoi(row[2])) : 0; e.retention_days = row[3] ? static_cast(atoi(row[3])) : 0; e.discord_webhook_id = row[4] ? static_cast(atoi(row[4])) : 0; + e.etl_enabled = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; return e; } @@ -174,6 +193,7 @@ public: v.push_back(columns[2] + " = " + std::to_string(e.event_enabled)); v.push_back(columns[3] + " = " + std::to_string(e.retention_days)); v.push_back(columns[4] + " = " + std::to_string(e.discord_webhook_id)); + v.push_back(columns[5] + " = " + std::to_string(e.etl_enabled)); auto results = db.QueryDatabase( fmt::format( @@ -200,6 +220,7 @@ public: v.push_back(std::to_string(e.event_enabled)); v.push_back(std::to_string(e.retention_days)); v.push_back(std::to_string(e.discord_webhook_id)); + v.push_back(std::to_string(e.etl_enabled)); auto results = db.QueryDatabase( fmt::format( @@ -234,6 +255,7 @@ public: v.push_back(std::to_string(e.event_enabled)); v.push_back(std::to_string(e.retention_days)); v.push_back(std::to_string(e.discord_webhook_id)); + v.push_back(std::to_string(e.etl_enabled)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } @@ -272,6 +294,7 @@ public: e.event_enabled = row[2] ? static_cast(atoi(row[2])) : 0; e.retention_days = row[3] ? static_cast(atoi(row[3])) : 0; e.discord_webhook_id = row[4] ? static_cast(atoi(row[4])) : 0; + e.etl_enabled = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; all_entries.push_back(e); } @@ -301,6 +324,7 @@ public: e.event_enabled = row[2] ? static_cast(atoi(row[2])) : 0; e.retention_days = row[3] ? static_cast(atoi(row[3])) : 0; e.discord_webhook_id = row[4] ? static_cast(atoi(row[4])) : 0; + e.etl_enabled = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; all_entries.push_back(e); } @@ -380,6 +404,7 @@ public: v.push_back(std::to_string(e.event_enabled)); v.push_back(std::to_string(e.retention_days)); v.push_back(std::to_string(e.discord_webhook_id)); + v.push_back(std::to_string(e.etl_enabled)); auto results = db.QueryDatabase( fmt::format( @@ -407,6 +432,7 @@ public: v.push_back(std::to_string(e.event_enabled)); v.push_back(std::to_string(e.retention_days)); v.push_back(std::to_string(e.discord_webhook_id)); + v.push_back(std::to_string(e.etl_enabled)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } diff --git a/common/repositories/base/base_player_event_logs_repository.h b/common/repositories/base/base_player_event_logs_repository.h index 91f0074b1..3a23feec8 100644 --- a/common/repositories/base/base_player_event_logs_repository.h +++ b/common/repositories/base/base_player_event_logs_repository.h @@ -31,6 +31,7 @@ public: int32_t event_type_id; std::string event_type_name; std::string event_data; + int64_t etl_table_id; time_t created_at; // cereal @@ -50,6 +51,7 @@ public: CEREAL_NVP(event_type_id), CEREAL_NVP(event_type_name), CEREAL_NVP(event_data), + CEREAL_NVP(etl_table_id), CEREAL_NVP(created_at) ); } @@ -75,6 +77,7 @@ public: "event_type_id", "event_type_name", "event_data", + "etl_table_id", "created_at", }; } @@ -94,6 +97,7 @@ public: "event_type_id", "event_type_name", "event_data", + "etl_table_id", "UNIX_TIMESTAMP(created_at)", }; } @@ -147,6 +151,7 @@ public: e.event_type_id = 0; e.event_type_name = ""; e.event_data = ""; + e.etl_table_id = 0; e.created_at = 0; return e; @@ -196,7 +201,8 @@ public: e.event_type_id = row[9] ? static_cast(atoi(row[9])) : 0; e.event_type_name = row[10] ? row[10] : ""; e.event_data = row[11] ? row[11] : ""; - e.created_at = strtoll(row[12] ? row[12] : "-1", nullptr, 10); + e.etl_table_id = row[12] ? strtoll(row[12], nullptr, 10) : 0; + e.created_at = strtoll(row[13] ? row[13] : "-1", nullptr, 10); return e; } @@ -241,7 +247,8 @@ public: v.push_back(columns[9] + " = " + std::to_string(e.event_type_id)); v.push_back(columns[10] + " = '" + Strings::Escape(e.event_type_name) + "'"); v.push_back(columns[11] + " = '" + Strings::Escape(e.event_data) + "'"); - v.push_back(columns[12] + " = FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + v.push_back(columns[12] + " = " + std::to_string(e.etl_table_id)); + v.push_back(columns[13] + " = FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); auto results = db.QueryDatabase( fmt::format( @@ -275,6 +282,7 @@ public: v.push_back(std::to_string(e.event_type_id)); v.push_back("'" + Strings::Escape(e.event_type_name) + "'"); v.push_back("'" + Strings::Escape(e.event_data) + "'"); + v.push_back(std::to_string(e.etl_table_id)); v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); auto results = db.QueryDatabase( @@ -317,6 +325,7 @@ public: v.push_back(std::to_string(e.event_type_id)); v.push_back("'" + Strings::Escape(e.event_type_name) + "'"); v.push_back("'" + Strings::Escape(e.event_data) + "'"); + v.push_back(std::to_string(e.etl_table_id)); v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); @@ -363,7 +372,8 @@ public: e.event_type_id = row[9] ? static_cast(atoi(row[9])) : 0; e.event_type_name = row[10] ? row[10] : ""; e.event_data = row[11] ? row[11] : ""; - e.created_at = strtoll(row[12] ? row[12] : "-1", nullptr, 10); + e.etl_table_id = row[12] ? strtoll(row[12], nullptr, 10) : 0; + e.created_at = strtoll(row[13] ? row[13] : "-1", nullptr, 10); all_entries.push_back(e); } @@ -400,7 +410,8 @@ public: e.event_type_id = row[9] ? static_cast(atoi(row[9])) : 0; e.event_type_name = row[10] ? row[10] : ""; e.event_data = row[11] ? row[11] : ""; - e.created_at = strtoll(row[12] ? row[12] : "-1", nullptr, 10); + e.etl_table_id = row[12] ? strtoll(row[12], nullptr, 10) : 0; + e.created_at = strtoll(row[13] ? row[13] : "-1", nullptr, 10); all_entries.push_back(e); } @@ -487,6 +498,7 @@ public: v.push_back(std::to_string(e.event_type_id)); v.push_back("'" + Strings::Escape(e.event_type_name) + "'"); v.push_back("'" + Strings::Escape(e.event_data) + "'"); + v.push_back(std::to_string(e.etl_table_id)); v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); auto results = db.QueryDatabase( @@ -522,6 +534,7 @@ public: v.push_back(std::to_string(e.event_type_id)); v.push_back("'" + Strings::Escape(e.event_type_name) + "'"); v.push_back("'" + Strings::Escape(e.event_data) + "'"); + v.push_back(std::to_string(e.etl_table_id)); v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); diff --git a/common/repositories/base/base_player_event_loot_items_repository.h b/common/repositories/base/base_player_event_loot_items_repository.h new file mode 100644 index 000000000..42c1c928a --- /dev/null +++ b/common/repositories/base/base_player_event_loot_items_repository.h @@ -0,0 +1,523 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://docs.eqemu.io/developer/repositories + */ + +#ifndef EQEMU_BASE_PLAYER_EVENT_LOOT_ITEMS_REPOSITORY_H +#define EQEMU_BASE_PLAYER_EVENT_LOOT_ITEMS_REPOSITORY_H + +#include "../../database.h" +#include "../../strings.h" +#include + +class BasePlayerEventLootItemsRepository { +public: + struct PlayerEventLootItems { + uint64_t id; + uint32_t item_id; + std::string item_name; + int32_t charges; + uint32_t augment_1_id; + uint32_t augment_2_id; + uint32_t augment_3_id; + uint32_t augment_4_id; + uint32_t augment_5_id; + uint32_t augment_6_id; + uint32_t npc_id; + std::string corpse_name; + time_t created_at; + }; + + static std::string PrimaryKey() + { + return std::string("id"); + } + + static std::vector Columns() + { + return { + "id", + "item_id", + "item_name", + "charges", + "augment_1_id", + "augment_2_id", + "augment_3_id", + "augment_4_id", + "augment_5_id", + "augment_6_id", + "npc_id", + "corpse_name", + "created_at", + }; + } + + static std::vector SelectColumns() + { + return { + "id", + "item_id", + "item_name", + "charges", + "augment_1_id", + "augment_2_id", + "augment_3_id", + "augment_4_id", + "augment_5_id", + "augment_6_id", + "npc_id", + "corpse_name", + "UNIX_TIMESTAMP(created_at)", + }; + } + + static std::string ColumnsRaw() + { + return std::string(Strings::Implode(", ", Columns())); + } + + static std::string SelectColumnsRaw() + { + return std::string(Strings::Implode(", ", SelectColumns())); + } + + static std::string TableName() + { + return std::string("player_event_loot_items"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + SelectColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static PlayerEventLootItems NewEntity() + { + PlayerEventLootItems e{}; + + e.id = 0; + e.item_id = 0; + e.item_name = ""; + e.charges = 0; + e.augment_1_id = 0; + e.augment_2_id = 0; + e.augment_3_id = 0; + e.augment_4_id = 0; + e.augment_5_id = 0; + e.augment_6_id = 0; + e.npc_id = 0; + e.corpse_name = ""; + e.created_at = 0; + + return e; + } + + static PlayerEventLootItems GetPlayerEventLootItems( + const std::vector &player_event_loot_itemss, + int player_event_loot_items_id + ) + { + for (auto &player_event_loot_items : player_event_loot_itemss) { + if (player_event_loot_items.id == player_event_loot_items_id) { + return player_event_loot_items; + } + } + + return NewEntity(); + } + + static PlayerEventLootItems FindOne( + Database& db, + int player_event_loot_items_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {} = {} LIMIT 1", + BaseSelect(), + PrimaryKey(), + player_event_loot_items_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + PlayerEventLootItems e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.item_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.item_name = row[2] ? row[2] : ""; + e.charges = row[3] ? static_cast(atoi(row[3])) : 0; + e.augment_1_id = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.augment_2_id = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.augment_3_id = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.augment_4_id = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.augment_5_id = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.augment_6_id = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.npc_id = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; + e.corpse_name = row[11] ? row[11] : ""; + e.created_at = strtoll(row[12] ? row[12] : "-1", nullptr, 10); + + return e; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int player_event_loot_items_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + player_event_loot_items_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + const PlayerEventLootItems &e + ) + { + std::vector v; + + auto columns = Columns(); + + v.push_back(columns[1] + " = " + std::to_string(e.item_id)); + v.push_back(columns[2] + " = '" + Strings::Escape(e.item_name) + "'"); + v.push_back(columns[3] + " = " + std::to_string(e.charges)); + v.push_back(columns[4] + " = " + std::to_string(e.augment_1_id)); + v.push_back(columns[5] + " = " + std::to_string(e.augment_2_id)); + v.push_back(columns[6] + " = " + std::to_string(e.augment_3_id)); + v.push_back(columns[7] + " = " + std::to_string(e.augment_4_id)); + v.push_back(columns[8] + " = " + std::to_string(e.augment_5_id)); + v.push_back(columns[9] + " = " + std::to_string(e.augment_6_id)); + v.push_back(columns[10] + " = " + std::to_string(e.npc_id)); + v.push_back(columns[11] + " = '" + Strings::Escape(e.corpse_name) + "'"); + v.push_back(columns[12] + " = FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + Strings::Implode(", ", v), + PrimaryKey(), + e.id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static PlayerEventLootItems InsertOne( + Database& db, + PlayerEventLootItems e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.item_id)); + v.push_back("'" + Strings::Escape(e.item_name) + "'"); + v.push_back(std::to_string(e.charges)); + v.push_back(std::to_string(e.augment_1_id)); + v.push_back(std::to_string(e.augment_2_id)); + v.push_back(std::to_string(e.augment_3_id)); + v.push_back(std::to_string(e.augment_4_id)); + v.push_back(std::to_string(e.augment_5_id)); + v.push_back(std::to_string(e.augment_6_id)); + v.push_back(std::to_string(e.npc_id)); + v.push_back("'" + Strings::Escape(e.corpse_name) + "'"); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + Strings::Implode(",", v) + ) + ); + + if (results.Success()) { + e.id = results.LastInsertedID(); + return e; + } + + e = NewEntity(); + + return e; + } + + static int InsertMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.item_id)); + v.push_back("'" + Strings::Escape(e.item_name) + "'"); + v.push_back(std::to_string(e.charges)); + v.push_back(std::to_string(e.augment_1_id)); + v.push_back(std::to_string(e.augment_2_id)); + v.push_back(std::to_string(e.augment_3_id)); + v.push_back(std::to_string(e.augment_4_id)); + v.push_back(std::to_string(e.augment_5_id)); + v.push_back(std::to_string(e.augment_6_id)); + v.push_back(std::to_string(e.npc_id)); + v.push_back("'" + Strings::Escape(e.corpse_name) + "'"); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + PlayerEventLootItems e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.item_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.item_name = row[2] ? row[2] : ""; + e.charges = row[3] ? static_cast(atoi(row[3])) : 0; + e.augment_1_id = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.augment_2_id = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.augment_3_id = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.augment_4_id = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.augment_5_id = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.augment_6_id = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.npc_id = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; + e.corpse_name = row[11] ? row[11] : ""; + e.created_at = strtoll(row[12] ? row[12] : "-1", nullptr, 10); + + all_entries.push_back(e); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, const std::string &where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + PlayerEventLootItems e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.item_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.item_name = row[2] ? row[2] : ""; + e.charges = row[3] ? static_cast(atoi(row[3])) : 0; + e.augment_1_id = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.augment_2_id = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.augment_3_id = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.augment_4_id = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.augment_5_id = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.augment_6_id = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.npc_id = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; + e.corpse_name = row[11] ? row[11] : ""; + e.created_at = strtoll(row[12] ? row[12] : "-1", nullptr, 10); + + all_entries.push_back(e); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, const std::string &where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int64 GetMaxId(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COALESCE(MAX({}), 0) FROM {}", + PrimaryKey(), + TableName() + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static int64 Count(Database& db, const std::string &where_filter = "") + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COUNT(*) FROM {} {}", + TableName(), + (where_filter.empty() ? "" : "WHERE " + where_filter) + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static std::string BaseReplace() + { + return fmt::format( + "REPLACE INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static int ReplaceOne( + Database& db, + const PlayerEventLootItems &e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.item_id)); + v.push_back("'" + Strings::Escape(e.item_name) + "'"); + v.push_back(std::to_string(e.charges)); + v.push_back(std::to_string(e.augment_1_id)); + v.push_back(std::to_string(e.augment_2_id)); + v.push_back(std::to_string(e.augment_3_id)); + v.push_back(std::to_string(e.augment_4_id)); + v.push_back(std::to_string(e.augment_5_id)); + v.push_back(std::to_string(e.augment_6_id)); + v.push_back(std::to_string(e.npc_id)); + v.push_back("'" + Strings::Escape(e.corpse_name) + "'"); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseReplace(), + Strings::Implode(",", v) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int ReplaceMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.item_id)); + v.push_back("'" + Strings::Escape(e.item_name) + "'"); + v.push_back(std::to_string(e.charges)); + v.push_back(std::to_string(e.augment_1_id)); + v.push_back(std::to_string(e.augment_2_id)); + v.push_back(std::to_string(e.augment_3_id)); + v.push_back(std::to_string(e.augment_4_id)); + v.push_back(std::to_string(e.augment_5_id)); + v.push_back(std::to_string(e.augment_6_id)); + v.push_back(std::to_string(e.npc_id)); + v.push_back("'" + Strings::Escape(e.corpse_name) + "'"); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseReplace(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } +}; + +#endif //EQEMU_BASE_PLAYER_EVENT_LOOT_ITEMS_REPOSITORY_H diff --git a/common/repositories/base/base_player_event_merchant_purchase_repository.h b/common/repositories/base/base_player_event_merchant_purchase_repository.h new file mode 100644 index 000000000..99ed4e7c5 --- /dev/null +++ b/common/repositories/base/base_player_event_merchant_purchase_repository.h @@ -0,0 +1,511 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://docs.eqemu.io/developer/repositories + */ + +#ifndef EQEMU_BASE_PLAYER_EVENT_MERCHANT_PURCHASE_REPOSITORY_H +#define EQEMU_BASE_PLAYER_EVENT_MERCHANT_PURCHASE_REPOSITORY_H + +#include "../../database.h" +#include "../../strings.h" +#include + +class BasePlayerEventMerchantPurchaseRepository { +public: + struct PlayerEventMerchantPurchase { + uint64_t id; + uint32_t npc_id; + std::string merchant_name; + uint32_t merchant_type; + uint32_t item_id; + std::string item_name; + int32_t charges; + uint32_t cost; + uint32_t alternate_currency_id; + uint64_t player_money_balance; + uint64_t player_currency_balance; + time_t created_at; + }; + + static std::string PrimaryKey() + { + return std::string("id"); + } + + static std::vector Columns() + { + return { + "id", + "npc_id", + "merchant_name", + "merchant_type", + "item_id", + "item_name", + "charges", + "cost", + "alternate_currency_id", + "player_money_balance", + "player_currency_balance", + "created_at", + }; + } + + static std::vector SelectColumns() + { + return { + "id", + "npc_id", + "merchant_name", + "merchant_type", + "item_id", + "item_name", + "charges", + "cost", + "alternate_currency_id", + "player_money_balance", + "player_currency_balance", + "UNIX_TIMESTAMP(created_at)", + }; + } + + static std::string ColumnsRaw() + { + return std::string(Strings::Implode(", ", Columns())); + } + + static std::string SelectColumnsRaw() + { + return std::string(Strings::Implode(", ", SelectColumns())); + } + + static std::string TableName() + { + return std::string("player_event_merchant_purchase"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + SelectColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static PlayerEventMerchantPurchase NewEntity() + { + PlayerEventMerchantPurchase e{}; + + e.id = 0; + e.npc_id = 0; + e.merchant_name = ""; + e.merchant_type = 0; + e.item_id = 0; + e.item_name = ""; + e.charges = 0; + e.cost = 0; + e.alternate_currency_id = 0; + e.player_money_balance = 0; + e.player_currency_balance = 0; + e.created_at = 0; + + return e; + } + + static PlayerEventMerchantPurchase GetPlayerEventMerchantPurchase( + const std::vector &player_event_merchant_purchases, + int player_event_merchant_purchase_id + ) + { + for (auto &player_event_merchant_purchase : player_event_merchant_purchases) { + if (player_event_merchant_purchase.id == player_event_merchant_purchase_id) { + return player_event_merchant_purchase; + } + } + + return NewEntity(); + } + + static PlayerEventMerchantPurchase FindOne( + Database& db, + int player_event_merchant_purchase_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {} = {} LIMIT 1", + BaseSelect(), + PrimaryKey(), + player_event_merchant_purchase_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + PlayerEventMerchantPurchase e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.npc_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.merchant_name = row[2] ? row[2] : ""; + e.merchant_type = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.item_id = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.item_name = row[5] ? row[5] : ""; + e.charges = row[6] ? static_cast(atoi(row[6])) : 0; + e.cost = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.alternate_currency_id = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.player_money_balance = row[9] ? strtoull(row[9], nullptr, 10) : 0; + e.player_currency_balance = row[10] ? strtoull(row[10], nullptr, 10) : 0; + e.created_at = strtoll(row[11] ? row[11] : "-1", nullptr, 10); + + return e; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int player_event_merchant_purchase_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + player_event_merchant_purchase_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + const PlayerEventMerchantPurchase &e + ) + { + std::vector v; + + auto columns = Columns(); + + v.push_back(columns[1] + " = " + std::to_string(e.npc_id)); + v.push_back(columns[2] + " = '" + Strings::Escape(e.merchant_name) + "'"); + v.push_back(columns[3] + " = " + std::to_string(e.merchant_type)); + v.push_back(columns[4] + " = " + std::to_string(e.item_id)); + v.push_back(columns[5] + " = '" + Strings::Escape(e.item_name) + "'"); + v.push_back(columns[6] + " = " + std::to_string(e.charges)); + v.push_back(columns[7] + " = " + std::to_string(e.cost)); + v.push_back(columns[8] + " = " + std::to_string(e.alternate_currency_id)); + v.push_back(columns[9] + " = " + std::to_string(e.player_money_balance)); + v.push_back(columns[10] + " = " + std::to_string(e.player_currency_balance)); + v.push_back(columns[11] + " = FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + Strings::Implode(", ", v), + PrimaryKey(), + e.id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static PlayerEventMerchantPurchase InsertOne( + Database& db, + PlayerEventMerchantPurchase e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.npc_id)); + v.push_back("'" + Strings::Escape(e.merchant_name) + "'"); + v.push_back(std::to_string(e.merchant_type)); + v.push_back(std::to_string(e.item_id)); + v.push_back("'" + Strings::Escape(e.item_name) + "'"); + v.push_back(std::to_string(e.charges)); + v.push_back(std::to_string(e.cost)); + v.push_back(std::to_string(e.alternate_currency_id)); + v.push_back(std::to_string(e.player_money_balance)); + v.push_back(std::to_string(e.player_currency_balance)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + Strings::Implode(",", v) + ) + ); + + if (results.Success()) { + e.id = results.LastInsertedID(); + return e; + } + + e = NewEntity(); + + return e; + } + + static int InsertMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.npc_id)); + v.push_back("'" + Strings::Escape(e.merchant_name) + "'"); + v.push_back(std::to_string(e.merchant_type)); + v.push_back(std::to_string(e.item_id)); + v.push_back("'" + Strings::Escape(e.item_name) + "'"); + v.push_back(std::to_string(e.charges)); + v.push_back(std::to_string(e.cost)); + v.push_back(std::to_string(e.alternate_currency_id)); + v.push_back(std::to_string(e.player_money_balance)); + v.push_back(std::to_string(e.player_currency_balance)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + PlayerEventMerchantPurchase e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.npc_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.merchant_name = row[2] ? row[2] : ""; + e.merchant_type = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.item_id = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.item_name = row[5] ? row[5] : ""; + e.charges = row[6] ? static_cast(atoi(row[6])) : 0; + e.cost = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.alternate_currency_id = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.player_money_balance = row[9] ? strtoull(row[9], nullptr, 10) : 0; + e.player_currency_balance = row[10] ? strtoull(row[10], nullptr, 10) : 0; + e.created_at = strtoll(row[11] ? row[11] : "-1", nullptr, 10); + + all_entries.push_back(e); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, const std::string &where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + PlayerEventMerchantPurchase e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.npc_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.merchant_name = row[2] ? row[2] : ""; + e.merchant_type = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.item_id = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.item_name = row[5] ? row[5] : ""; + e.charges = row[6] ? static_cast(atoi(row[6])) : 0; + e.cost = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.alternate_currency_id = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.player_money_balance = row[9] ? strtoull(row[9], nullptr, 10) : 0; + e.player_currency_balance = row[10] ? strtoull(row[10], nullptr, 10) : 0; + e.created_at = strtoll(row[11] ? row[11] : "-1", nullptr, 10); + + all_entries.push_back(e); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, const std::string &where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int64 GetMaxId(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COALESCE(MAX({}), 0) FROM {}", + PrimaryKey(), + TableName() + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static int64 Count(Database& db, const std::string &where_filter = "") + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COUNT(*) FROM {} {}", + TableName(), + (where_filter.empty() ? "" : "WHERE " + where_filter) + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static std::string BaseReplace() + { + return fmt::format( + "REPLACE INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static int ReplaceOne( + Database& db, + const PlayerEventMerchantPurchase &e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.npc_id)); + v.push_back("'" + Strings::Escape(e.merchant_name) + "'"); + v.push_back(std::to_string(e.merchant_type)); + v.push_back(std::to_string(e.item_id)); + v.push_back("'" + Strings::Escape(e.item_name) + "'"); + v.push_back(std::to_string(e.charges)); + v.push_back(std::to_string(e.cost)); + v.push_back(std::to_string(e.alternate_currency_id)); + v.push_back(std::to_string(e.player_money_balance)); + v.push_back(std::to_string(e.player_currency_balance)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseReplace(), + Strings::Implode(",", v) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int ReplaceMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.npc_id)); + v.push_back("'" + Strings::Escape(e.merchant_name) + "'"); + v.push_back(std::to_string(e.merchant_type)); + v.push_back(std::to_string(e.item_id)); + v.push_back("'" + Strings::Escape(e.item_name) + "'"); + v.push_back(std::to_string(e.charges)); + v.push_back(std::to_string(e.cost)); + v.push_back(std::to_string(e.alternate_currency_id)); + v.push_back(std::to_string(e.player_money_balance)); + v.push_back(std::to_string(e.player_currency_balance)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseReplace(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } +}; + +#endif //EQEMU_BASE_PLAYER_EVENT_MERCHANT_PURCHASE_REPOSITORY_H diff --git a/common/repositories/base/base_player_event_merchant_sell_repository.h b/common/repositories/base/base_player_event_merchant_sell_repository.h new file mode 100644 index 000000000..13c018165 --- /dev/null +++ b/common/repositories/base/base_player_event_merchant_sell_repository.h @@ -0,0 +1,511 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://docs.eqemu.io/developer/repositories + */ + +#ifndef EQEMU_BASE_PLAYER_EVENT_MERCHANT_SELL_REPOSITORY_H +#define EQEMU_BASE_PLAYER_EVENT_MERCHANT_SELL_REPOSITORY_H + +#include "../../database.h" +#include "../../strings.h" +#include + +class BasePlayerEventMerchantSellRepository { +public: + struct PlayerEventMerchantSell { + uint64_t id; + uint32_t npc_id; + std::string merchant_name; + uint32_t merchant_type; + uint32_t item_id; + std::string item_name; + int32_t charges; + uint32_t cost; + uint32_t alternate_currency_id; + uint64_t player_money_balance; + uint64_t player_currency_balance; + time_t created_at; + }; + + static std::string PrimaryKey() + { + return std::string("id"); + } + + static std::vector Columns() + { + return { + "id", + "npc_id", + "merchant_name", + "merchant_type", + "item_id", + "item_name", + "charges", + "cost", + "alternate_currency_id", + "player_money_balance", + "player_currency_balance", + "created_at", + }; + } + + static std::vector SelectColumns() + { + return { + "id", + "npc_id", + "merchant_name", + "merchant_type", + "item_id", + "item_name", + "charges", + "cost", + "alternate_currency_id", + "player_money_balance", + "player_currency_balance", + "UNIX_TIMESTAMP(created_at)", + }; + } + + static std::string ColumnsRaw() + { + return std::string(Strings::Implode(", ", Columns())); + } + + static std::string SelectColumnsRaw() + { + return std::string(Strings::Implode(", ", SelectColumns())); + } + + static std::string TableName() + { + return std::string("player_event_merchant_sell"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + SelectColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static PlayerEventMerchantSell NewEntity() + { + PlayerEventMerchantSell e{}; + + e.id = 0; + e.npc_id = 0; + e.merchant_name = ""; + e.merchant_type = 0; + e.item_id = 0; + e.item_name = ""; + e.charges = 0; + e.cost = 0; + e.alternate_currency_id = 0; + e.player_money_balance = 0; + e.player_currency_balance = 0; + e.created_at = 0; + + return e; + } + + static PlayerEventMerchantSell GetPlayerEventMerchantSell( + const std::vector &player_event_merchant_sells, + int player_event_merchant_sell_id + ) + { + for (auto &player_event_merchant_sell : player_event_merchant_sells) { + if (player_event_merchant_sell.id == player_event_merchant_sell_id) { + return player_event_merchant_sell; + } + } + + return NewEntity(); + } + + static PlayerEventMerchantSell FindOne( + Database& db, + int player_event_merchant_sell_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {} = {} LIMIT 1", + BaseSelect(), + PrimaryKey(), + player_event_merchant_sell_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + PlayerEventMerchantSell e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.npc_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.merchant_name = row[2] ? row[2] : ""; + e.merchant_type = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.item_id = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.item_name = row[5] ? row[5] : ""; + e.charges = row[6] ? static_cast(atoi(row[6])) : 0; + e.cost = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.alternate_currency_id = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.player_money_balance = row[9] ? strtoull(row[9], nullptr, 10) : 0; + e.player_currency_balance = row[10] ? strtoull(row[10], nullptr, 10) : 0; + e.created_at = strtoll(row[11] ? row[11] : "-1", nullptr, 10); + + return e; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int player_event_merchant_sell_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + player_event_merchant_sell_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + const PlayerEventMerchantSell &e + ) + { + std::vector v; + + auto columns = Columns(); + + v.push_back(columns[1] + " = " + std::to_string(e.npc_id)); + v.push_back(columns[2] + " = '" + Strings::Escape(e.merchant_name) + "'"); + v.push_back(columns[3] + " = " + std::to_string(e.merchant_type)); + v.push_back(columns[4] + " = " + std::to_string(e.item_id)); + v.push_back(columns[5] + " = '" + Strings::Escape(e.item_name) + "'"); + v.push_back(columns[6] + " = " + std::to_string(e.charges)); + v.push_back(columns[7] + " = " + std::to_string(e.cost)); + v.push_back(columns[8] + " = " + std::to_string(e.alternate_currency_id)); + v.push_back(columns[9] + " = " + std::to_string(e.player_money_balance)); + v.push_back(columns[10] + " = " + std::to_string(e.player_currency_balance)); + v.push_back(columns[11] + " = FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + Strings::Implode(", ", v), + PrimaryKey(), + e.id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static PlayerEventMerchantSell InsertOne( + Database& db, + PlayerEventMerchantSell e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.npc_id)); + v.push_back("'" + Strings::Escape(e.merchant_name) + "'"); + v.push_back(std::to_string(e.merchant_type)); + v.push_back(std::to_string(e.item_id)); + v.push_back("'" + Strings::Escape(e.item_name) + "'"); + v.push_back(std::to_string(e.charges)); + v.push_back(std::to_string(e.cost)); + v.push_back(std::to_string(e.alternate_currency_id)); + v.push_back(std::to_string(e.player_money_balance)); + v.push_back(std::to_string(e.player_currency_balance)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + Strings::Implode(",", v) + ) + ); + + if (results.Success()) { + e.id = results.LastInsertedID(); + return e; + } + + e = NewEntity(); + + return e; + } + + static int InsertMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.npc_id)); + v.push_back("'" + Strings::Escape(e.merchant_name) + "'"); + v.push_back(std::to_string(e.merchant_type)); + v.push_back(std::to_string(e.item_id)); + v.push_back("'" + Strings::Escape(e.item_name) + "'"); + v.push_back(std::to_string(e.charges)); + v.push_back(std::to_string(e.cost)); + v.push_back(std::to_string(e.alternate_currency_id)); + v.push_back(std::to_string(e.player_money_balance)); + v.push_back(std::to_string(e.player_currency_balance)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + PlayerEventMerchantSell e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.npc_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.merchant_name = row[2] ? row[2] : ""; + e.merchant_type = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.item_id = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.item_name = row[5] ? row[5] : ""; + e.charges = row[6] ? static_cast(atoi(row[6])) : 0; + e.cost = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.alternate_currency_id = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.player_money_balance = row[9] ? strtoull(row[9], nullptr, 10) : 0; + e.player_currency_balance = row[10] ? strtoull(row[10], nullptr, 10) : 0; + e.created_at = strtoll(row[11] ? row[11] : "-1", nullptr, 10); + + all_entries.push_back(e); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, const std::string &where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + PlayerEventMerchantSell e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.npc_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.merchant_name = row[2] ? row[2] : ""; + e.merchant_type = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.item_id = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.item_name = row[5] ? row[5] : ""; + e.charges = row[6] ? static_cast(atoi(row[6])) : 0; + e.cost = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.alternate_currency_id = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.player_money_balance = row[9] ? strtoull(row[9], nullptr, 10) : 0; + e.player_currency_balance = row[10] ? strtoull(row[10], nullptr, 10) : 0; + e.created_at = strtoll(row[11] ? row[11] : "-1", nullptr, 10); + + all_entries.push_back(e); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, const std::string &where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int64 GetMaxId(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COALESCE(MAX({}), 0) FROM {}", + PrimaryKey(), + TableName() + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static int64 Count(Database& db, const std::string &where_filter = "") + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COUNT(*) FROM {} {}", + TableName(), + (where_filter.empty() ? "" : "WHERE " + where_filter) + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static std::string BaseReplace() + { + return fmt::format( + "REPLACE INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static int ReplaceOne( + Database& db, + const PlayerEventMerchantSell &e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.npc_id)); + v.push_back("'" + Strings::Escape(e.merchant_name) + "'"); + v.push_back(std::to_string(e.merchant_type)); + v.push_back(std::to_string(e.item_id)); + v.push_back("'" + Strings::Escape(e.item_name) + "'"); + v.push_back(std::to_string(e.charges)); + v.push_back(std::to_string(e.cost)); + v.push_back(std::to_string(e.alternate_currency_id)); + v.push_back(std::to_string(e.player_money_balance)); + v.push_back(std::to_string(e.player_currency_balance)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseReplace(), + Strings::Implode(",", v) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int ReplaceMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.npc_id)); + v.push_back("'" + Strings::Escape(e.merchant_name) + "'"); + v.push_back(std::to_string(e.merchant_type)); + v.push_back(std::to_string(e.item_id)); + v.push_back("'" + Strings::Escape(e.item_name) + "'"); + v.push_back(std::to_string(e.charges)); + v.push_back(std::to_string(e.cost)); + v.push_back(std::to_string(e.alternate_currency_id)); + v.push_back(std::to_string(e.player_money_balance)); + v.push_back(std::to_string(e.player_currency_balance)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseReplace(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } +}; + +#endif //EQEMU_BASE_PLAYER_EVENT_MERCHANT_SELL_REPOSITORY_H diff --git a/common/repositories/base/base_player_event_npc_handin_entries_repository.h b/common/repositories/base/base_player_event_npc_handin_entries_repository.h new file mode 100644 index 000000000..4d2ee2ead --- /dev/null +++ b/common/repositories/base/base_player_event_npc_handin_entries_repository.h @@ -0,0 +1,523 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://docs.eqemu.io/developer/repositories + */ + +#ifndef EQEMU_BASE_PLAYER_EVENT_NPC_HANDIN_ENTRIES_REPOSITORY_H +#define EQEMU_BASE_PLAYER_EVENT_NPC_HANDIN_ENTRIES_REPOSITORY_H + +#include "../../database.h" +#include "../../strings.h" +#include + +class BasePlayerEventNpcHandinEntriesRepository { +public: + struct PlayerEventNpcHandinEntries { + uint64_t id; + uint64_t player_event_npc_handin_id; + uint32_t type; + uint32_t item_id; + int32_t charges; + uint32_t evolve_level; + uint64_t evolve_amount; + uint32_t augment_1_id; + uint32_t augment_2_id; + uint32_t augment_3_id; + uint32_t augment_4_id; + uint32_t augment_5_id; + uint32_t augment_6_id; + }; + + static std::string PrimaryKey() + { + return std::string("id"); + } + + static std::vector Columns() + { + return { + "id", + "player_event_npc_handin_id", + "type", + "item_id", + "charges", + "evolve_level", + "evolve_amount", + "augment_1_id", + "augment_2_id", + "augment_3_id", + "augment_4_id", + "augment_5_id", + "augment_6_id", + }; + } + + static std::vector SelectColumns() + { + return { + "id", + "player_event_npc_handin_id", + "type", + "item_id", + "charges", + "evolve_level", + "evolve_amount", + "augment_1_id", + "augment_2_id", + "augment_3_id", + "augment_4_id", + "augment_5_id", + "augment_6_id", + }; + } + + static std::string ColumnsRaw() + { + return std::string(Strings::Implode(", ", Columns())); + } + + static std::string SelectColumnsRaw() + { + return std::string(Strings::Implode(", ", SelectColumns())); + } + + static std::string TableName() + { + return std::string("player_event_npc_handin_entries"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + SelectColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static PlayerEventNpcHandinEntries NewEntity() + { + PlayerEventNpcHandinEntries e{}; + + e.id = 0; + e.player_event_npc_handin_id = 0; + e.type = 0; + e.item_id = 0; + e.charges = 0; + e.evolve_level = 0; + e.evolve_amount = 0; + e.augment_1_id = 0; + e.augment_2_id = 0; + e.augment_3_id = 0; + e.augment_4_id = 0; + e.augment_5_id = 0; + e.augment_6_id = 0; + + return e; + } + + static PlayerEventNpcHandinEntries GetPlayerEventNpcHandinEntries( + const std::vector &player_event_npc_handin_entriess, + int player_event_npc_handin_entries_id + ) + { + for (auto &player_event_npc_handin_entries : player_event_npc_handin_entriess) { + if (player_event_npc_handin_entries.id == player_event_npc_handin_entries_id) { + return player_event_npc_handin_entries; + } + } + + return NewEntity(); + } + + static PlayerEventNpcHandinEntries FindOne( + Database& db, + int player_event_npc_handin_entries_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {} = {} LIMIT 1", + BaseSelect(), + PrimaryKey(), + player_event_npc_handin_entries_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + PlayerEventNpcHandinEntries e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.player_event_npc_handin_id = row[1] ? strtoull(row[1], nullptr, 10) : 0; + e.type = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.item_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.charges = row[4] ? static_cast(atoi(row[4])) : 0; + e.evolve_level = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.evolve_amount = row[6] ? strtoull(row[6], nullptr, 10) : 0; + e.augment_1_id = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.augment_2_id = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.augment_3_id = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.augment_4_id = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; + e.augment_5_id = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; + e.augment_6_id = row[12] ? static_cast(strtoul(row[12], nullptr, 10)) : 0; + + return e; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int player_event_npc_handin_entries_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + player_event_npc_handin_entries_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + const PlayerEventNpcHandinEntries &e + ) + { + std::vector v; + + auto columns = Columns(); + + v.push_back(columns[1] + " = " + std::to_string(e.player_event_npc_handin_id)); + v.push_back(columns[2] + " = " + std::to_string(e.type)); + v.push_back(columns[3] + " = " + std::to_string(e.item_id)); + v.push_back(columns[4] + " = " + std::to_string(e.charges)); + v.push_back(columns[5] + " = " + std::to_string(e.evolve_level)); + v.push_back(columns[6] + " = " + std::to_string(e.evolve_amount)); + v.push_back(columns[7] + " = " + std::to_string(e.augment_1_id)); + v.push_back(columns[8] + " = " + std::to_string(e.augment_2_id)); + v.push_back(columns[9] + " = " + std::to_string(e.augment_3_id)); + v.push_back(columns[10] + " = " + std::to_string(e.augment_4_id)); + v.push_back(columns[11] + " = " + std::to_string(e.augment_5_id)); + v.push_back(columns[12] + " = " + std::to_string(e.augment_6_id)); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + Strings::Implode(", ", v), + PrimaryKey(), + e.id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static PlayerEventNpcHandinEntries InsertOne( + Database& db, + PlayerEventNpcHandinEntries e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.player_event_npc_handin_id)); + v.push_back(std::to_string(e.type)); + v.push_back(std::to_string(e.item_id)); + v.push_back(std::to_string(e.charges)); + v.push_back(std::to_string(e.evolve_level)); + v.push_back(std::to_string(e.evolve_amount)); + v.push_back(std::to_string(e.augment_1_id)); + v.push_back(std::to_string(e.augment_2_id)); + v.push_back(std::to_string(e.augment_3_id)); + v.push_back(std::to_string(e.augment_4_id)); + v.push_back(std::to_string(e.augment_5_id)); + v.push_back(std::to_string(e.augment_6_id)); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + Strings::Implode(",", v) + ) + ); + + if (results.Success()) { + e.id = results.LastInsertedID(); + return e; + } + + e = NewEntity(); + + return e; + } + + static int InsertMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.player_event_npc_handin_id)); + v.push_back(std::to_string(e.type)); + v.push_back(std::to_string(e.item_id)); + v.push_back(std::to_string(e.charges)); + v.push_back(std::to_string(e.evolve_level)); + v.push_back(std::to_string(e.evolve_amount)); + v.push_back(std::to_string(e.augment_1_id)); + v.push_back(std::to_string(e.augment_2_id)); + v.push_back(std::to_string(e.augment_3_id)); + v.push_back(std::to_string(e.augment_4_id)); + v.push_back(std::to_string(e.augment_5_id)); + v.push_back(std::to_string(e.augment_6_id)); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + PlayerEventNpcHandinEntries e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.player_event_npc_handin_id = row[1] ? strtoull(row[1], nullptr, 10) : 0; + e.type = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.item_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.charges = row[4] ? static_cast(atoi(row[4])) : 0; + e.evolve_level = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.evolve_amount = row[6] ? strtoull(row[6], nullptr, 10) : 0; + e.augment_1_id = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.augment_2_id = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.augment_3_id = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.augment_4_id = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; + e.augment_5_id = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; + e.augment_6_id = row[12] ? static_cast(strtoul(row[12], nullptr, 10)) : 0; + + all_entries.push_back(e); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, const std::string &where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + PlayerEventNpcHandinEntries e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.player_event_npc_handin_id = row[1] ? strtoull(row[1], nullptr, 10) : 0; + e.type = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.item_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.charges = row[4] ? static_cast(atoi(row[4])) : 0; + e.evolve_level = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.evolve_amount = row[6] ? strtoull(row[6], nullptr, 10) : 0; + e.augment_1_id = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.augment_2_id = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.augment_3_id = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.augment_4_id = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; + e.augment_5_id = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; + e.augment_6_id = row[12] ? static_cast(strtoul(row[12], nullptr, 10)) : 0; + + all_entries.push_back(e); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, const std::string &where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int64 GetMaxId(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COALESCE(MAX({}), 0) FROM {}", + PrimaryKey(), + TableName() + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static int64 Count(Database& db, const std::string &where_filter = "") + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COUNT(*) FROM {} {}", + TableName(), + (where_filter.empty() ? "" : "WHERE " + where_filter) + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static std::string BaseReplace() + { + return fmt::format( + "REPLACE INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static int ReplaceOne( + Database& db, + const PlayerEventNpcHandinEntries &e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.player_event_npc_handin_id)); + v.push_back(std::to_string(e.type)); + v.push_back(std::to_string(e.item_id)); + v.push_back(std::to_string(e.charges)); + v.push_back(std::to_string(e.evolve_level)); + v.push_back(std::to_string(e.evolve_amount)); + v.push_back(std::to_string(e.augment_1_id)); + v.push_back(std::to_string(e.augment_2_id)); + v.push_back(std::to_string(e.augment_3_id)); + v.push_back(std::to_string(e.augment_4_id)); + v.push_back(std::to_string(e.augment_5_id)); + v.push_back(std::to_string(e.augment_6_id)); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseReplace(), + Strings::Implode(",", v) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int ReplaceMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.player_event_npc_handin_id)); + v.push_back(std::to_string(e.type)); + v.push_back(std::to_string(e.item_id)); + v.push_back(std::to_string(e.charges)); + v.push_back(std::to_string(e.evolve_level)); + v.push_back(std::to_string(e.evolve_amount)); + v.push_back(std::to_string(e.augment_1_id)); + v.push_back(std::to_string(e.augment_2_id)); + v.push_back(std::to_string(e.augment_3_id)); + v.push_back(std::to_string(e.augment_4_id)); + v.push_back(std::to_string(e.augment_5_id)); + v.push_back(std::to_string(e.augment_6_id)); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseReplace(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } +}; + +#endif //EQEMU_BASE_PLAYER_EVENT_NPC_HANDIN_ENTRIES_REPOSITORY_H diff --git a/common/repositories/base/base_player_event_npc_handin_repository.h b/common/repositories/base/base_player_event_npc_handin_repository.h new file mode 100644 index 000000000..acf460c50 --- /dev/null +++ b/common/repositories/base/base_player_event_npc_handin_repository.h @@ -0,0 +1,523 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://docs.eqemu.io/developer/repositories + */ + +#ifndef EQEMU_BASE_PLAYER_EVENT_NPC_HANDIN_REPOSITORY_H +#define EQEMU_BASE_PLAYER_EVENT_NPC_HANDIN_REPOSITORY_H + +#include "../../database.h" +#include "../../strings.h" +#include + +class BasePlayerEventNpcHandinRepository { +public: + struct PlayerEventNpcHandin { + uint64_t id; + uint32_t npc_id; + std::string npc_name; + uint64_t handin_copper; + uint64_t handin_silver; + uint64_t handin_gold; + uint64_t handin_platinum; + uint64_t return_copper; + uint64_t return_silver; + uint64_t return_gold; + uint64_t return_platinum; + uint8_t is_quest_handin; + time_t created_at; + }; + + static std::string PrimaryKey() + { + return std::string("id"); + } + + static std::vector Columns() + { + return { + "id", + "npc_id", + "npc_name", + "handin_copper", + "handin_silver", + "handin_gold", + "handin_platinum", + "return_copper", + "return_silver", + "return_gold", + "return_platinum", + "is_quest_handin", + "created_at", + }; + } + + static std::vector SelectColumns() + { + return { + "id", + "npc_id", + "npc_name", + "handin_copper", + "handin_silver", + "handin_gold", + "handin_platinum", + "return_copper", + "return_silver", + "return_gold", + "return_platinum", + "is_quest_handin", + "UNIX_TIMESTAMP(created_at)", + }; + } + + static std::string ColumnsRaw() + { + return std::string(Strings::Implode(", ", Columns())); + } + + static std::string SelectColumnsRaw() + { + return std::string(Strings::Implode(", ", SelectColumns())); + } + + static std::string TableName() + { + return std::string("player_event_npc_handin"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + SelectColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static PlayerEventNpcHandin NewEntity() + { + PlayerEventNpcHandin e{}; + + e.id = 0; + e.npc_id = 0; + e.npc_name = ""; + e.handin_copper = 0; + e.handin_silver = 0; + e.handin_gold = 0; + e.handin_platinum = 0; + e.return_copper = 0; + e.return_silver = 0; + e.return_gold = 0; + e.return_platinum = 0; + e.is_quest_handin = 0; + e.created_at = 0; + + return e; + } + + static PlayerEventNpcHandin GetPlayerEventNpcHandin( + const std::vector &player_event_npc_handins, + int player_event_npc_handin_id + ) + { + for (auto &player_event_npc_handin : player_event_npc_handins) { + if (player_event_npc_handin.id == player_event_npc_handin_id) { + return player_event_npc_handin; + } + } + + return NewEntity(); + } + + static PlayerEventNpcHandin FindOne( + Database& db, + int player_event_npc_handin_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {} = {} LIMIT 1", + BaseSelect(), + PrimaryKey(), + player_event_npc_handin_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + PlayerEventNpcHandin e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.npc_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.npc_name = row[2] ? row[2] : ""; + e.handin_copper = row[3] ? strtoull(row[3], nullptr, 10) : 0; + e.handin_silver = row[4] ? strtoull(row[4], nullptr, 10) : 0; + e.handin_gold = row[5] ? strtoull(row[5], nullptr, 10) : 0; + e.handin_platinum = row[6] ? strtoull(row[6], nullptr, 10) : 0; + e.return_copper = row[7] ? strtoull(row[7], nullptr, 10) : 0; + e.return_silver = row[8] ? strtoull(row[8], nullptr, 10) : 0; + e.return_gold = row[9] ? strtoull(row[9], nullptr, 10) : 0; + e.return_platinum = row[10] ? strtoull(row[10], nullptr, 10) : 0; + e.is_quest_handin = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; + e.created_at = strtoll(row[12] ? row[12] : "-1", nullptr, 10); + + return e; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int player_event_npc_handin_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + player_event_npc_handin_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + const PlayerEventNpcHandin &e + ) + { + std::vector v; + + auto columns = Columns(); + + v.push_back(columns[1] + " = " + std::to_string(e.npc_id)); + v.push_back(columns[2] + " = '" + Strings::Escape(e.npc_name) + "'"); + v.push_back(columns[3] + " = " + std::to_string(e.handin_copper)); + v.push_back(columns[4] + " = " + std::to_string(e.handin_silver)); + v.push_back(columns[5] + " = " + std::to_string(e.handin_gold)); + v.push_back(columns[6] + " = " + std::to_string(e.handin_platinum)); + v.push_back(columns[7] + " = " + std::to_string(e.return_copper)); + v.push_back(columns[8] + " = " + std::to_string(e.return_silver)); + v.push_back(columns[9] + " = " + std::to_string(e.return_gold)); + v.push_back(columns[10] + " = " + std::to_string(e.return_platinum)); + v.push_back(columns[11] + " = " + std::to_string(e.is_quest_handin)); + v.push_back(columns[12] + " = FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + Strings::Implode(", ", v), + PrimaryKey(), + e.id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static PlayerEventNpcHandin InsertOne( + Database& db, + PlayerEventNpcHandin e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.npc_id)); + v.push_back("'" + Strings::Escape(e.npc_name) + "'"); + v.push_back(std::to_string(e.handin_copper)); + v.push_back(std::to_string(e.handin_silver)); + v.push_back(std::to_string(e.handin_gold)); + v.push_back(std::to_string(e.handin_platinum)); + v.push_back(std::to_string(e.return_copper)); + v.push_back(std::to_string(e.return_silver)); + v.push_back(std::to_string(e.return_gold)); + v.push_back(std::to_string(e.return_platinum)); + v.push_back(std::to_string(e.is_quest_handin)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + Strings::Implode(",", v) + ) + ); + + if (results.Success()) { + e.id = results.LastInsertedID(); + return e; + } + + e = NewEntity(); + + return e; + } + + static int InsertMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.npc_id)); + v.push_back("'" + Strings::Escape(e.npc_name) + "'"); + v.push_back(std::to_string(e.handin_copper)); + v.push_back(std::to_string(e.handin_silver)); + v.push_back(std::to_string(e.handin_gold)); + v.push_back(std::to_string(e.handin_platinum)); + v.push_back(std::to_string(e.return_copper)); + v.push_back(std::to_string(e.return_silver)); + v.push_back(std::to_string(e.return_gold)); + v.push_back(std::to_string(e.return_platinum)); + v.push_back(std::to_string(e.is_quest_handin)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + PlayerEventNpcHandin e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.npc_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.npc_name = row[2] ? row[2] : ""; + e.handin_copper = row[3] ? strtoull(row[3], nullptr, 10) : 0; + e.handin_silver = row[4] ? strtoull(row[4], nullptr, 10) : 0; + e.handin_gold = row[5] ? strtoull(row[5], nullptr, 10) : 0; + e.handin_platinum = row[6] ? strtoull(row[6], nullptr, 10) : 0; + e.return_copper = row[7] ? strtoull(row[7], nullptr, 10) : 0; + e.return_silver = row[8] ? strtoull(row[8], nullptr, 10) : 0; + e.return_gold = row[9] ? strtoull(row[9], nullptr, 10) : 0; + e.return_platinum = row[10] ? strtoull(row[10], nullptr, 10) : 0; + e.is_quest_handin = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; + e.created_at = strtoll(row[12] ? row[12] : "-1", nullptr, 10); + + all_entries.push_back(e); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, const std::string &where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + PlayerEventNpcHandin e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.npc_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.npc_name = row[2] ? row[2] : ""; + e.handin_copper = row[3] ? strtoull(row[3], nullptr, 10) : 0; + e.handin_silver = row[4] ? strtoull(row[4], nullptr, 10) : 0; + e.handin_gold = row[5] ? strtoull(row[5], nullptr, 10) : 0; + e.handin_platinum = row[6] ? strtoull(row[6], nullptr, 10) : 0; + e.return_copper = row[7] ? strtoull(row[7], nullptr, 10) : 0; + e.return_silver = row[8] ? strtoull(row[8], nullptr, 10) : 0; + e.return_gold = row[9] ? strtoull(row[9], nullptr, 10) : 0; + e.return_platinum = row[10] ? strtoull(row[10], nullptr, 10) : 0; + e.is_quest_handin = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; + e.created_at = strtoll(row[12] ? row[12] : "-1", nullptr, 10); + + all_entries.push_back(e); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, const std::string &where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int64 GetMaxId(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COALESCE(MAX({}), 0) FROM {}", + PrimaryKey(), + TableName() + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static int64 Count(Database& db, const std::string &where_filter = "") + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COUNT(*) FROM {} {}", + TableName(), + (where_filter.empty() ? "" : "WHERE " + where_filter) + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static std::string BaseReplace() + { + return fmt::format( + "REPLACE INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static int ReplaceOne( + Database& db, + const PlayerEventNpcHandin &e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.npc_id)); + v.push_back("'" + Strings::Escape(e.npc_name) + "'"); + v.push_back(std::to_string(e.handin_copper)); + v.push_back(std::to_string(e.handin_silver)); + v.push_back(std::to_string(e.handin_gold)); + v.push_back(std::to_string(e.handin_platinum)); + v.push_back(std::to_string(e.return_copper)); + v.push_back(std::to_string(e.return_silver)); + v.push_back(std::to_string(e.return_gold)); + v.push_back(std::to_string(e.return_platinum)); + v.push_back(std::to_string(e.is_quest_handin)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseReplace(), + Strings::Implode(",", v) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int ReplaceMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.npc_id)); + v.push_back("'" + Strings::Escape(e.npc_name) + "'"); + v.push_back(std::to_string(e.handin_copper)); + v.push_back(std::to_string(e.handin_silver)); + v.push_back(std::to_string(e.handin_gold)); + v.push_back(std::to_string(e.handin_platinum)); + v.push_back(std::to_string(e.return_copper)); + v.push_back(std::to_string(e.return_silver)); + v.push_back(std::to_string(e.return_gold)); + v.push_back(std::to_string(e.return_platinum)); + v.push_back(std::to_string(e.is_quest_handin)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseReplace(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } +}; + +#endif //EQEMU_BASE_PLAYER_EVENT_NPC_HANDIN_REPOSITORY_H diff --git a/common/repositories/base/base_player_event_speech_repository.h b/common/repositories/base/base_player_event_speech_repository.h new file mode 100644 index 000000000..80823f3e6 --- /dev/null +++ b/common/repositories/base/base_player_event_speech_repository.h @@ -0,0 +1,463 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://docs.eqemu.io/developer/repositories + */ + +#ifndef EQEMU_BASE_PLAYER_EVENT_SPEECH_REPOSITORY_H +#define EQEMU_BASE_PLAYER_EVENT_SPEECH_REPOSITORY_H + +#include "../../database.h" +#include "../../strings.h" +#include + +class BasePlayerEventSpeechRepository { +public: + struct PlayerEventSpeech { + uint64_t id; + std::string to_char_id; + std::string from_char_id; + uint32_t guild_id; + uint32_t type; + uint32_t min_status; + std::string message; + time_t created_at; + }; + + static std::string PrimaryKey() + { + return std::string("id"); + } + + static std::vector Columns() + { + return { + "id", + "to_char_id", + "from_char_id", + "guild_id", + "type", + "min_status", + "message", + "created_at", + }; + } + + static std::vector SelectColumns() + { + return { + "id", + "to_char_id", + "from_char_id", + "guild_id", + "type", + "min_status", + "message", + "UNIX_TIMESTAMP(created_at)", + }; + } + + static std::string ColumnsRaw() + { + return std::string(Strings::Implode(", ", Columns())); + } + + static std::string SelectColumnsRaw() + { + return std::string(Strings::Implode(", ", SelectColumns())); + } + + static std::string TableName() + { + return std::string("player_event_speech"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + SelectColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static PlayerEventSpeech NewEntity() + { + PlayerEventSpeech e{}; + + e.id = 0; + e.to_char_id = ""; + e.from_char_id = ""; + e.guild_id = 0; + e.type = 0; + e.min_status = 0; + e.message = ""; + e.created_at = 0; + + return e; + } + + static PlayerEventSpeech GetPlayerEventSpeech( + const std::vector &player_event_speechs, + int player_event_speech_id + ) + { + for (auto &player_event_speech : player_event_speechs) { + if (player_event_speech.id == player_event_speech_id) { + return player_event_speech; + } + } + + return NewEntity(); + } + + static PlayerEventSpeech FindOne( + Database& db, + int player_event_speech_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {} = {} LIMIT 1", + BaseSelect(), + PrimaryKey(), + player_event_speech_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + PlayerEventSpeech e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.to_char_id = row[1] ? row[1] : ""; + e.from_char_id = row[2] ? row[2] : ""; + e.guild_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.type = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.min_status = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.message = row[6] ? row[6] : ""; + e.created_at = strtoll(row[7] ? row[7] : "-1", nullptr, 10); + + return e; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int player_event_speech_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + player_event_speech_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + const PlayerEventSpeech &e + ) + { + std::vector v; + + auto columns = Columns(); + + v.push_back(columns[1] + " = '" + Strings::Escape(e.to_char_id) + "'"); + v.push_back(columns[2] + " = '" + Strings::Escape(e.from_char_id) + "'"); + v.push_back(columns[3] + " = " + std::to_string(e.guild_id)); + v.push_back(columns[4] + " = " + std::to_string(e.type)); + v.push_back(columns[5] + " = " + std::to_string(e.min_status)); + v.push_back(columns[6] + " = '" + Strings::Escape(e.message) + "'"); + v.push_back(columns[7] + " = FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + Strings::Implode(", ", v), + PrimaryKey(), + e.id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static PlayerEventSpeech InsertOne( + Database& db, + PlayerEventSpeech e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back("'" + Strings::Escape(e.to_char_id) + "'"); + v.push_back("'" + Strings::Escape(e.from_char_id) + "'"); + v.push_back(std::to_string(e.guild_id)); + v.push_back(std::to_string(e.type)); + v.push_back(std::to_string(e.min_status)); + v.push_back("'" + Strings::Escape(e.message) + "'"); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + Strings::Implode(",", v) + ) + ); + + if (results.Success()) { + e.id = results.LastInsertedID(); + return e; + } + + e = NewEntity(); + + return e; + } + + static int InsertMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back("'" + Strings::Escape(e.to_char_id) + "'"); + v.push_back("'" + Strings::Escape(e.from_char_id) + "'"); + v.push_back(std::to_string(e.guild_id)); + v.push_back(std::to_string(e.type)); + v.push_back(std::to_string(e.min_status)); + v.push_back("'" + Strings::Escape(e.message) + "'"); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + PlayerEventSpeech e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.to_char_id = row[1] ? row[1] : ""; + e.from_char_id = row[2] ? row[2] : ""; + e.guild_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.type = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.min_status = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.message = row[6] ? row[6] : ""; + e.created_at = strtoll(row[7] ? row[7] : "-1", nullptr, 10); + + all_entries.push_back(e); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, const std::string &where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + PlayerEventSpeech e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.to_char_id = row[1] ? row[1] : ""; + e.from_char_id = row[2] ? row[2] : ""; + e.guild_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.type = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.min_status = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.message = row[6] ? row[6] : ""; + e.created_at = strtoll(row[7] ? row[7] : "-1", nullptr, 10); + + all_entries.push_back(e); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, const std::string &where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int64 GetMaxId(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COALESCE(MAX({}), 0) FROM {}", + PrimaryKey(), + TableName() + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static int64 Count(Database& db, const std::string &where_filter = "") + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COUNT(*) FROM {} {}", + TableName(), + (where_filter.empty() ? "" : "WHERE " + where_filter) + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static std::string BaseReplace() + { + return fmt::format( + "REPLACE INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static int ReplaceOne( + Database& db, + const PlayerEventSpeech &e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back("'" + Strings::Escape(e.to_char_id) + "'"); + v.push_back("'" + Strings::Escape(e.from_char_id) + "'"); + v.push_back(std::to_string(e.guild_id)); + v.push_back(std::to_string(e.type)); + v.push_back(std::to_string(e.min_status)); + v.push_back("'" + Strings::Escape(e.message) + "'"); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseReplace(), + Strings::Implode(",", v) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int ReplaceMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back("'" + Strings::Escape(e.to_char_id) + "'"); + v.push_back("'" + Strings::Escape(e.from_char_id) + "'"); + v.push_back(std::to_string(e.guild_id)); + v.push_back(std::to_string(e.type)); + v.push_back(std::to_string(e.min_status)); + v.push_back("'" + Strings::Escape(e.message) + "'"); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseReplace(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } +}; + +#endif //EQEMU_BASE_PLAYER_EVENT_SPEECH_REPOSITORY_H diff --git a/common/repositories/base/base_player_event_trade_entries_repository.h b/common/repositories/base/base_player_event_trade_entries_repository.h new file mode 100644 index 000000000..ae117e32a --- /dev/null +++ b/common/repositories/base/base_player_event_trade_entries_repository.h @@ -0,0 +1,535 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://docs.eqemu.io/developer/repositories + */ + +#ifndef EQEMU_BASE_PLAYER_EVENT_TRADE_ENTRIES_REPOSITORY_H +#define EQEMU_BASE_PLAYER_EVENT_TRADE_ENTRIES_REPOSITORY_H + +#include "../../database.h" +#include "../../strings.h" +#include + +class BasePlayerEventTradeEntriesRepository { +public: + struct PlayerEventTradeEntries { + uint64_t id; + uint64_t player_event_trade_id; + uint32_t char_id; + int16_t slot; + uint32_t item_id; + int16_t charges; + uint32_t augment_1_id; + uint32_t augment_2_id; + uint32_t augment_3_id; + uint32_t augment_4_id; + uint32_t augment_5_id; + uint32_t augment_6_id; + int8_t in_bag; + time_t created_at; + }; + + static std::string PrimaryKey() + { + return std::string("id"); + } + + static std::vector Columns() + { + return { + "id", + "player_event_trade_id", + "char_id", + "slot", + "item_id", + "charges", + "augment_1_id", + "augment_2_id", + "augment_3_id", + "augment_4_id", + "augment_5_id", + "augment_6_id", + "in_bag", + "created_at", + }; + } + + static std::vector SelectColumns() + { + return { + "id", + "player_event_trade_id", + "char_id", + "slot", + "item_id", + "charges", + "augment_1_id", + "augment_2_id", + "augment_3_id", + "augment_4_id", + "augment_5_id", + "augment_6_id", + "in_bag", + "UNIX_TIMESTAMP(created_at)", + }; + } + + static std::string ColumnsRaw() + { + return std::string(Strings::Implode(", ", Columns())); + } + + static std::string SelectColumnsRaw() + { + return std::string(Strings::Implode(", ", SelectColumns())); + } + + static std::string TableName() + { + return std::string("player_event_trade_entries"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + SelectColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static PlayerEventTradeEntries NewEntity() + { + PlayerEventTradeEntries e{}; + + e.id = 0; + e.player_event_trade_id = 0; + e.char_id = 0; + e.slot = 0; + e.item_id = 0; + e.charges = 0; + e.augment_1_id = 0; + e.augment_2_id = 0; + e.augment_3_id = 0; + e.augment_4_id = 0; + e.augment_5_id = 0; + e.augment_6_id = 0; + e.in_bag = 0; + e.created_at = 0; + + return e; + } + + static PlayerEventTradeEntries GetPlayerEventTradeEntries( + const std::vector &player_event_trade_entriess, + int player_event_trade_entries_id + ) + { + for (auto &player_event_trade_entries : player_event_trade_entriess) { + if (player_event_trade_entries.id == player_event_trade_entries_id) { + return player_event_trade_entries; + } + } + + return NewEntity(); + } + + static PlayerEventTradeEntries FindOne( + Database& db, + int player_event_trade_entries_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {} = {} LIMIT 1", + BaseSelect(), + PrimaryKey(), + player_event_trade_entries_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + PlayerEventTradeEntries e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.player_event_trade_id = row[1] ? strtoull(row[1], nullptr, 10) : 0; + e.char_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.slot = row[3] ? static_cast(atoi(row[3])) : 0; + e.item_id = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.charges = row[5] ? static_cast(atoi(row[5])) : 0; + e.augment_1_id = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.augment_2_id = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.augment_3_id = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.augment_4_id = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.augment_5_id = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; + e.augment_6_id = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; + e.in_bag = row[12] ? static_cast(atoi(row[12])) : 0; + e.created_at = strtoll(row[13] ? row[13] : "-1", nullptr, 10); + + return e; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int player_event_trade_entries_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + player_event_trade_entries_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + const PlayerEventTradeEntries &e + ) + { + std::vector v; + + auto columns = Columns(); + + v.push_back(columns[1] + " = " + std::to_string(e.player_event_trade_id)); + v.push_back(columns[2] + " = " + std::to_string(e.char_id)); + v.push_back(columns[3] + " = " + std::to_string(e.slot)); + v.push_back(columns[4] + " = " + std::to_string(e.item_id)); + v.push_back(columns[5] + " = " + std::to_string(e.charges)); + v.push_back(columns[6] + " = " + std::to_string(e.augment_1_id)); + v.push_back(columns[7] + " = " + std::to_string(e.augment_2_id)); + v.push_back(columns[8] + " = " + std::to_string(e.augment_3_id)); + v.push_back(columns[9] + " = " + std::to_string(e.augment_4_id)); + v.push_back(columns[10] + " = " + std::to_string(e.augment_5_id)); + v.push_back(columns[11] + " = " + std::to_string(e.augment_6_id)); + v.push_back(columns[12] + " = " + std::to_string(e.in_bag)); + v.push_back(columns[13] + " = FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + Strings::Implode(", ", v), + PrimaryKey(), + e.id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static PlayerEventTradeEntries InsertOne( + Database& db, + PlayerEventTradeEntries e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.player_event_trade_id)); + v.push_back(std::to_string(e.char_id)); + v.push_back(std::to_string(e.slot)); + v.push_back(std::to_string(e.item_id)); + v.push_back(std::to_string(e.charges)); + v.push_back(std::to_string(e.augment_1_id)); + v.push_back(std::to_string(e.augment_2_id)); + v.push_back(std::to_string(e.augment_3_id)); + v.push_back(std::to_string(e.augment_4_id)); + v.push_back(std::to_string(e.augment_5_id)); + v.push_back(std::to_string(e.augment_6_id)); + v.push_back(std::to_string(e.in_bag)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + Strings::Implode(",", v) + ) + ); + + if (results.Success()) { + e.id = results.LastInsertedID(); + return e; + } + + e = NewEntity(); + + return e; + } + + static int InsertMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.player_event_trade_id)); + v.push_back(std::to_string(e.char_id)); + v.push_back(std::to_string(e.slot)); + v.push_back(std::to_string(e.item_id)); + v.push_back(std::to_string(e.charges)); + v.push_back(std::to_string(e.augment_1_id)); + v.push_back(std::to_string(e.augment_2_id)); + v.push_back(std::to_string(e.augment_3_id)); + v.push_back(std::to_string(e.augment_4_id)); + v.push_back(std::to_string(e.augment_5_id)); + v.push_back(std::to_string(e.augment_6_id)); + v.push_back(std::to_string(e.in_bag)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + PlayerEventTradeEntries e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.player_event_trade_id = row[1] ? strtoull(row[1], nullptr, 10) : 0; + e.char_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.slot = row[3] ? static_cast(atoi(row[3])) : 0; + e.item_id = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.charges = row[5] ? static_cast(atoi(row[5])) : 0; + e.augment_1_id = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.augment_2_id = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.augment_3_id = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.augment_4_id = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.augment_5_id = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; + e.augment_6_id = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; + e.in_bag = row[12] ? static_cast(atoi(row[12])) : 0; + e.created_at = strtoll(row[13] ? row[13] : "-1", nullptr, 10); + + all_entries.push_back(e); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, const std::string &where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + PlayerEventTradeEntries e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.player_event_trade_id = row[1] ? strtoull(row[1], nullptr, 10) : 0; + e.char_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.slot = row[3] ? static_cast(atoi(row[3])) : 0; + e.item_id = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.charges = row[5] ? static_cast(atoi(row[5])) : 0; + e.augment_1_id = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.augment_2_id = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.augment_3_id = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.augment_4_id = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.augment_5_id = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; + e.augment_6_id = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; + e.in_bag = row[12] ? static_cast(atoi(row[12])) : 0; + e.created_at = strtoll(row[13] ? row[13] : "-1", nullptr, 10); + + all_entries.push_back(e); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, const std::string &where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int64 GetMaxId(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COALESCE(MAX({}), 0) FROM {}", + PrimaryKey(), + TableName() + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static int64 Count(Database& db, const std::string &where_filter = "") + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COUNT(*) FROM {} {}", + TableName(), + (where_filter.empty() ? "" : "WHERE " + where_filter) + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static std::string BaseReplace() + { + return fmt::format( + "REPLACE INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static int ReplaceOne( + Database& db, + const PlayerEventTradeEntries &e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.player_event_trade_id)); + v.push_back(std::to_string(e.char_id)); + v.push_back(std::to_string(e.slot)); + v.push_back(std::to_string(e.item_id)); + v.push_back(std::to_string(e.charges)); + v.push_back(std::to_string(e.augment_1_id)); + v.push_back(std::to_string(e.augment_2_id)); + v.push_back(std::to_string(e.augment_3_id)); + v.push_back(std::to_string(e.augment_4_id)); + v.push_back(std::to_string(e.augment_5_id)); + v.push_back(std::to_string(e.augment_6_id)); + v.push_back(std::to_string(e.in_bag)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseReplace(), + Strings::Implode(",", v) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int ReplaceMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.player_event_trade_id)); + v.push_back(std::to_string(e.char_id)); + v.push_back(std::to_string(e.slot)); + v.push_back(std::to_string(e.item_id)); + v.push_back(std::to_string(e.charges)); + v.push_back(std::to_string(e.augment_1_id)); + v.push_back(std::to_string(e.augment_2_id)); + v.push_back(std::to_string(e.augment_3_id)); + v.push_back(std::to_string(e.augment_4_id)); + v.push_back(std::to_string(e.augment_5_id)); + v.push_back(std::to_string(e.augment_6_id)); + v.push_back(std::to_string(e.in_bag)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseReplace(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } +}; + +#endif //EQEMU_BASE_PLAYER_EVENT_TRADE_ENTRIES_REPOSITORY_H diff --git a/common/repositories/base/base_player_event_trade_repository.h b/common/repositories/base/base_player_event_trade_repository.h new file mode 100644 index 000000000..58409a5cb --- /dev/null +++ b/common/repositories/base/base_player_event_trade_repository.h @@ -0,0 +1,511 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://docs.eqemu.io/developer/repositories + */ + +#ifndef EQEMU_BASE_PLAYER_EVENT_TRADE_REPOSITORY_H +#define EQEMU_BASE_PLAYER_EVENT_TRADE_REPOSITORY_H + +#include "../../database.h" +#include "../../strings.h" +#include + +class BasePlayerEventTradeRepository { +public: + struct PlayerEventTrade { + uint32_t id; + uint32_t char1_id; + uint32_t char2_id; + uint64_t char1_copper; + uint64_t char1_silver; + uint64_t char1_gold; + uint64_t char1_platinum; + uint64_t char2_copper; + uint64_t char2_silver; + uint64_t char2_gold; + uint64_t char2_platinum; + time_t created_at; + }; + + static std::string PrimaryKey() + { + return std::string("id"); + } + + static std::vector Columns() + { + return { + "id", + "char1_id", + "char2_id", + "char1_copper", + "char1_silver", + "char1_gold", + "char1_platinum", + "char2_copper", + "char2_silver", + "char2_gold", + "char2_platinum", + "created_at", + }; + } + + static std::vector SelectColumns() + { + return { + "id", + "char1_id", + "char2_id", + "char1_copper", + "char1_silver", + "char1_gold", + "char1_platinum", + "char2_copper", + "char2_silver", + "char2_gold", + "char2_platinum", + "UNIX_TIMESTAMP(created_at)", + }; + } + + static std::string ColumnsRaw() + { + return std::string(Strings::Implode(", ", Columns())); + } + + static std::string SelectColumnsRaw() + { + return std::string(Strings::Implode(", ", SelectColumns())); + } + + static std::string TableName() + { + return std::string("player_event_trade"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + SelectColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static PlayerEventTrade NewEntity() + { + PlayerEventTrade e{}; + + e.id = 0; + e.char1_id = 0; + e.char2_id = 0; + e.char1_copper = 0; + e.char1_silver = 0; + e.char1_gold = 0; + e.char1_platinum = 0; + e.char2_copper = 0; + e.char2_silver = 0; + e.char2_gold = 0; + e.char2_platinum = 0; + e.created_at = 0; + + return e; + } + + static PlayerEventTrade GetPlayerEventTrade( + const std::vector &player_event_trades, + int player_event_trade_id + ) + { + for (auto &player_event_trade : player_event_trades) { + if (player_event_trade.id == player_event_trade_id) { + return player_event_trade; + } + } + + return NewEntity(); + } + + static PlayerEventTrade FindOne( + Database& db, + int player_event_trade_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {} = {} LIMIT 1", + BaseSelect(), + PrimaryKey(), + player_event_trade_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + PlayerEventTrade e{}; + + e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.char1_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.char2_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.char1_copper = row[3] ? strtoull(row[3], nullptr, 10) : 0; + e.char1_silver = row[4] ? strtoull(row[4], nullptr, 10) : 0; + e.char1_gold = row[5] ? strtoull(row[5], nullptr, 10) : 0; + e.char1_platinum = row[6] ? strtoull(row[6], nullptr, 10) : 0; + e.char2_copper = row[7] ? strtoull(row[7], nullptr, 10) : 0; + e.char2_silver = row[8] ? strtoull(row[8], nullptr, 10) : 0; + e.char2_gold = row[9] ? strtoull(row[9], nullptr, 10) : 0; + e.char2_platinum = row[10] ? strtoull(row[10], nullptr, 10) : 0; + e.created_at = strtoll(row[11] ? row[11] : "-1", nullptr, 10); + + return e; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int player_event_trade_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + player_event_trade_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + const PlayerEventTrade &e + ) + { + std::vector v; + + auto columns = Columns(); + + v.push_back(columns[1] + " = " + std::to_string(e.char1_id)); + v.push_back(columns[2] + " = " + std::to_string(e.char2_id)); + v.push_back(columns[3] + " = " + std::to_string(e.char1_copper)); + v.push_back(columns[4] + " = " + std::to_string(e.char1_silver)); + v.push_back(columns[5] + " = " + std::to_string(e.char1_gold)); + v.push_back(columns[6] + " = " + std::to_string(e.char1_platinum)); + v.push_back(columns[7] + " = " + std::to_string(e.char2_copper)); + v.push_back(columns[8] + " = " + std::to_string(e.char2_silver)); + v.push_back(columns[9] + " = " + std::to_string(e.char2_gold)); + v.push_back(columns[10] + " = " + std::to_string(e.char2_platinum)); + v.push_back(columns[11] + " = FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + Strings::Implode(", ", v), + PrimaryKey(), + e.id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static PlayerEventTrade InsertOne( + Database& db, + PlayerEventTrade e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.char1_id)); + v.push_back(std::to_string(e.char2_id)); + v.push_back(std::to_string(e.char1_copper)); + v.push_back(std::to_string(e.char1_silver)); + v.push_back(std::to_string(e.char1_gold)); + v.push_back(std::to_string(e.char1_platinum)); + v.push_back(std::to_string(e.char2_copper)); + v.push_back(std::to_string(e.char2_silver)); + v.push_back(std::to_string(e.char2_gold)); + v.push_back(std::to_string(e.char2_platinum)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + Strings::Implode(",", v) + ) + ); + + if (results.Success()) { + e.id = results.LastInsertedID(); + return e; + } + + e = NewEntity(); + + return e; + } + + static int InsertMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.char1_id)); + v.push_back(std::to_string(e.char2_id)); + v.push_back(std::to_string(e.char1_copper)); + v.push_back(std::to_string(e.char1_silver)); + v.push_back(std::to_string(e.char1_gold)); + v.push_back(std::to_string(e.char1_platinum)); + v.push_back(std::to_string(e.char2_copper)); + v.push_back(std::to_string(e.char2_silver)); + v.push_back(std::to_string(e.char2_gold)); + v.push_back(std::to_string(e.char2_platinum)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + PlayerEventTrade e{}; + + e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.char1_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.char2_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.char1_copper = row[3] ? strtoull(row[3], nullptr, 10) : 0; + e.char1_silver = row[4] ? strtoull(row[4], nullptr, 10) : 0; + e.char1_gold = row[5] ? strtoull(row[5], nullptr, 10) : 0; + e.char1_platinum = row[6] ? strtoull(row[6], nullptr, 10) : 0; + e.char2_copper = row[7] ? strtoull(row[7], nullptr, 10) : 0; + e.char2_silver = row[8] ? strtoull(row[8], nullptr, 10) : 0; + e.char2_gold = row[9] ? strtoull(row[9], nullptr, 10) : 0; + e.char2_platinum = row[10] ? strtoull(row[10], nullptr, 10) : 0; + e.created_at = strtoll(row[11] ? row[11] : "-1", nullptr, 10); + + all_entries.push_back(e); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, const std::string &where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + PlayerEventTrade e{}; + + e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.char1_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.char2_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.char1_copper = row[3] ? strtoull(row[3], nullptr, 10) : 0; + e.char1_silver = row[4] ? strtoull(row[4], nullptr, 10) : 0; + e.char1_gold = row[5] ? strtoull(row[5], nullptr, 10) : 0; + e.char1_platinum = row[6] ? strtoull(row[6], nullptr, 10) : 0; + e.char2_copper = row[7] ? strtoull(row[7], nullptr, 10) : 0; + e.char2_silver = row[8] ? strtoull(row[8], nullptr, 10) : 0; + e.char2_gold = row[9] ? strtoull(row[9], nullptr, 10) : 0; + e.char2_platinum = row[10] ? strtoull(row[10], nullptr, 10) : 0; + e.created_at = strtoll(row[11] ? row[11] : "-1", nullptr, 10); + + all_entries.push_back(e); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, const std::string &where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int64 GetMaxId(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COALESCE(MAX({}), 0) FROM {}", + PrimaryKey(), + TableName() + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static int64 Count(Database& db, const std::string &where_filter = "") + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COUNT(*) FROM {} {}", + TableName(), + (where_filter.empty() ? "" : "WHERE " + where_filter) + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static std::string BaseReplace() + { + return fmt::format( + "REPLACE INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static int ReplaceOne( + Database& db, + const PlayerEventTrade &e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.char1_id)); + v.push_back(std::to_string(e.char2_id)); + v.push_back(std::to_string(e.char1_copper)); + v.push_back(std::to_string(e.char1_silver)); + v.push_back(std::to_string(e.char1_gold)); + v.push_back(std::to_string(e.char1_platinum)); + v.push_back(std::to_string(e.char2_copper)); + v.push_back(std::to_string(e.char2_silver)); + v.push_back(std::to_string(e.char2_gold)); + v.push_back(std::to_string(e.char2_platinum)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseReplace(), + Strings::Implode(",", v) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int ReplaceMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.char1_id)); + v.push_back(std::to_string(e.char2_id)); + v.push_back(std::to_string(e.char1_copper)); + v.push_back(std::to_string(e.char1_silver)); + v.push_back(std::to_string(e.char1_gold)); + v.push_back(std::to_string(e.char1_platinum)); + v.push_back(std::to_string(e.char2_copper)); + v.push_back(std::to_string(e.char2_silver)); + v.push_back(std::to_string(e.char2_gold)); + v.push_back(std::to_string(e.char2_platinum)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseReplace(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } +}; + +#endif //EQEMU_BASE_PLAYER_EVENT_TRADE_REPOSITORY_H diff --git a/common/repositories/base/base_respawn_times_repository.h b/common/repositories/base/base_respawn_times_repository.h index 94d5526a8..303296c10 100644 --- a/common/repositories/base/base_respawn_times_repository.h +++ b/common/repositories/base/base_respawn_times_repository.h @@ -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(atoi(row[0])) : 0; e.start = row[1] ? static_cast(atoi(row[1])) : 0; e.duration = row[2] ? static_cast(atoi(row[2])) : 0; - e.instance_id = row[3] ? static_cast(atoi(row[3])) : 0; + e.expire_at = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.instance_id = row[4] ? static_cast(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(atoi(row[0])) : 0; e.start = row[1] ? static_cast(atoi(row[1])) : 0; e.duration = row[2] ? static_cast(atoi(row[2])) : 0; - e.instance_id = row[3] ? static_cast(atoi(row[3])) : 0; + e.expire_at = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.instance_id = row[4] ? static_cast(atoi(row[4])) : 0; all_entries.push_back(e); } @@ -290,7 +299,8 @@ public: e.id = row[0] ? static_cast(atoi(row[0])) : 0; e.start = row[1] ? static_cast(atoi(row[1])) : 0; e.duration = row[2] ? static_cast(atoi(row[2])) : 0; - e.instance_id = row[3] ? static_cast(atoi(row[3])) : 0; + e.expire_at = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.instance_id = row[4] ? static_cast(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) + ")"); diff --git a/common/repositories/base/base_sharedbank_repository.h b/common/repositories/base/base_sharedbank_repository.h new file mode 100644 index 000000000..d208251da --- /dev/null +++ b/common/repositories/base/base_sharedbank_repository.h @@ -0,0 +1,560 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://docs.eqemu.io/developer/repositories + */ + +#ifndef EQEMU_BASE_SHAREDBANK_REPOSITORY_H +#define EQEMU_BASE_SHAREDBANK_REPOSITORY_H + +#include "../../database.h" +#include "../../strings.h" +#include + +class BaseSharedbankRepository { +public: + struct Sharedbank { + uint32_t account_id; + uint32_t slot_id; + uint32_t item_id; + uint16_t charges; + uint32_t color; + uint32_t augment_one; + uint32_t augment_two; + uint32_t augment_three; + uint32_t augment_four; + uint32_t augment_five; + uint32_t augment_six; + std::string custom_data; + uint32_t ornament_icon; + uint32_t ornament_idfile; + int32_t ornament_hero_model; + uint64_t guid; + }; + + static std::string PrimaryKey() + { + return std::string("account_id"); + } + + static std::vector Columns() + { + return { + "account_id", + "slot_id", + "item_id", + "charges", + "color", + "augment_one", + "augment_two", + "augment_three", + "augment_four", + "augment_five", + "augment_six", + "custom_data", + "ornament_icon", + "ornament_idfile", + "ornament_hero_model", + "guid", + }; + } + + static std::vector SelectColumns() + { + return { + "account_id", + "slot_id", + "item_id", + "charges", + "color", + "augment_one", + "augment_two", + "augment_three", + "augment_four", + "augment_five", + "augment_six", + "custom_data", + "ornament_icon", + "ornament_idfile", + "ornament_hero_model", + "guid", + }; + } + + static std::string ColumnsRaw() + { + return std::string(Strings::Implode(", ", Columns())); + } + + static std::string SelectColumnsRaw() + { + return std::string(Strings::Implode(", ", SelectColumns())); + } + + static std::string TableName() + { + return std::string("sharedbank"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + SelectColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static Sharedbank NewEntity() + { + Sharedbank e{}; + + e.account_id = 0; + e.slot_id = 0; + e.item_id = 0; + e.charges = 0; + e.color = 0; + e.augment_one = 0; + e.augment_two = 0; + e.augment_three = 0; + e.augment_four = 0; + e.augment_five = 0; + e.augment_six = 0; + e.custom_data = ""; + e.ornament_icon = 0; + e.ornament_idfile = 0; + e.ornament_hero_model = 0; + e.guid = 0; + + return e; + } + + static Sharedbank GetSharedbank( + const std::vector &sharedbanks, + int sharedbank_id + ) + { + for (auto &sharedbank : sharedbanks) { + if (sharedbank.account_id == sharedbank_id) { + return sharedbank; + } + } + + return NewEntity(); + } + + static Sharedbank FindOne( + Database& db, + int sharedbank_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {} = {} LIMIT 1", + BaseSelect(), + PrimaryKey(), + sharedbank_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + Sharedbank e{}; + + e.account_id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.slot_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.charges = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.color = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.augment_one = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.augment_two = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.augment_three = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.augment_four = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.augment_five = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.augment_six = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; + e.custom_data = row[11] ? row[11] : ""; + e.ornament_icon = row[12] ? static_cast(strtoul(row[12], nullptr, 10)) : 0; + e.ornament_idfile = row[13] ? static_cast(strtoul(row[13], nullptr, 10)) : 0; + e.ornament_hero_model = row[14] ? static_cast(atoi(row[14])) : 0; + e.guid = row[15] ? strtoull(row[15], nullptr, 10) : 0; + + return e; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int sharedbank_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + sharedbank_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + const Sharedbank &e + ) + { + std::vector v; + + auto columns = Columns(); + + v.push_back(columns[0] + " = " + std::to_string(e.account_id)); + v.push_back(columns[1] + " = " + std::to_string(e.slot_id)); + v.push_back(columns[2] + " = " + std::to_string(e.item_id)); + v.push_back(columns[3] + " = " + std::to_string(e.charges)); + v.push_back(columns[4] + " = " + std::to_string(e.color)); + v.push_back(columns[5] + " = " + std::to_string(e.augment_one)); + v.push_back(columns[6] + " = " + std::to_string(e.augment_two)); + v.push_back(columns[7] + " = " + std::to_string(e.augment_three)); + v.push_back(columns[8] + " = " + std::to_string(e.augment_four)); + v.push_back(columns[9] + " = " + std::to_string(e.augment_five)); + v.push_back(columns[10] + " = " + std::to_string(e.augment_six)); + v.push_back(columns[11] + " = '" + Strings::Escape(e.custom_data) + "'"); + v.push_back(columns[12] + " = " + std::to_string(e.ornament_icon)); + v.push_back(columns[13] + " = " + std::to_string(e.ornament_idfile)); + v.push_back(columns[14] + " = " + std::to_string(e.ornament_hero_model)); + v.push_back(columns[15] + " = " + std::to_string(e.guid)); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + Strings::Implode(", ", v), + PrimaryKey(), + e.account_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static Sharedbank InsertOne( + Database& db, + Sharedbank e + ) + { + std::vector v; + + v.push_back(std::to_string(e.account_id)); + v.push_back(std::to_string(e.slot_id)); + v.push_back(std::to_string(e.item_id)); + v.push_back(std::to_string(e.charges)); + v.push_back(std::to_string(e.color)); + v.push_back(std::to_string(e.augment_one)); + v.push_back(std::to_string(e.augment_two)); + v.push_back(std::to_string(e.augment_three)); + v.push_back(std::to_string(e.augment_four)); + v.push_back(std::to_string(e.augment_five)); + v.push_back(std::to_string(e.augment_six)); + v.push_back("'" + Strings::Escape(e.custom_data) + "'"); + v.push_back(std::to_string(e.ornament_icon)); + v.push_back(std::to_string(e.ornament_idfile)); + v.push_back(std::to_string(e.ornament_hero_model)); + v.push_back(std::to_string(e.guid)); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + Strings::Implode(",", v) + ) + ); + + if (results.Success()) { + e.account_id = results.LastInsertedID(); + return e; + } + + e = NewEntity(); + + return e; + } + + static int InsertMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.account_id)); + v.push_back(std::to_string(e.slot_id)); + v.push_back(std::to_string(e.item_id)); + v.push_back(std::to_string(e.charges)); + v.push_back(std::to_string(e.color)); + v.push_back(std::to_string(e.augment_one)); + v.push_back(std::to_string(e.augment_two)); + v.push_back(std::to_string(e.augment_three)); + v.push_back(std::to_string(e.augment_four)); + v.push_back(std::to_string(e.augment_five)); + v.push_back(std::to_string(e.augment_six)); + v.push_back("'" + Strings::Escape(e.custom_data) + "'"); + v.push_back(std::to_string(e.ornament_icon)); + v.push_back(std::to_string(e.ornament_idfile)); + v.push_back(std::to_string(e.ornament_hero_model)); + v.push_back(std::to_string(e.guid)); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + Sharedbank e{}; + + e.account_id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.slot_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.charges = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.color = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.augment_one = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.augment_two = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.augment_three = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.augment_four = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.augment_five = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.augment_six = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; + e.custom_data = row[11] ? row[11] : ""; + e.ornament_icon = row[12] ? static_cast(strtoul(row[12], nullptr, 10)) : 0; + e.ornament_idfile = row[13] ? static_cast(strtoul(row[13], nullptr, 10)) : 0; + e.ornament_hero_model = row[14] ? static_cast(atoi(row[14])) : 0; + e.guid = row[15] ? strtoull(row[15], nullptr, 10) : 0; + + all_entries.push_back(e); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, const std::string &where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + Sharedbank e{}; + + e.account_id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.slot_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.charges = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.color = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.augment_one = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.augment_two = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.augment_three = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.augment_four = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.augment_five = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.augment_six = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; + e.custom_data = row[11] ? row[11] : ""; + e.ornament_icon = row[12] ? static_cast(strtoul(row[12], nullptr, 10)) : 0; + e.ornament_idfile = row[13] ? static_cast(strtoul(row[13], nullptr, 10)) : 0; + e.ornament_hero_model = row[14] ? static_cast(atoi(row[14])) : 0; + e.guid = row[15] ? strtoull(row[15], nullptr, 10) : 0; + + all_entries.push_back(e); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, const std::string &where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int64 GetMaxId(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COALESCE(MAX({}), 0) FROM {}", + PrimaryKey(), + TableName() + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static int64 Count(Database& db, const std::string &where_filter = "") + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COUNT(*) FROM {} {}", + TableName(), + (where_filter.empty() ? "" : "WHERE " + where_filter) + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static std::string BaseReplace() + { + return fmt::format( + "REPLACE INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static int ReplaceOne( + Database& db, + const Sharedbank &e + ) + { + std::vector v; + + v.push_back(std::to_string(e.account_id)); + v.push_back(std::to_string(e.slot_id)); + v.push_back(std::to_string(e.item_id)); + v.push_back(std::to_string(e.charges)); + v.push_back(std::to_string(e.color)); + v.push_back(std::to_string(e.augment_one)); + v.push_back(std::to_string(e.augment_two)); + v.push_back(std::to_string(e.augment_three)); + v.push_back(std::to_string(e.augment_four)); + v.push_back(std::to_string(e.augment_five)); + v.push_back(std::to_string(e.augment_six)); + v.push_back("'" + Strings::Escape(e.custom_data) + "'"); + v.push_back(std::to_string(e.ornament_icon)); + v.push_back(std::to_string(e.ornament_idfile)); + v.push_back(std::to_string(e.ornament_hero_model)); + v.push_back(std::to_string(e.guid)); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseReplace(), + Strings::Implode(",", v) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int ReplaceMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.account_id)); + v.push_back(std::to_string(e.slot_id)); + v.push_back(std::to_string(e.item_id)); + v.push_back(std::to_string(e.charges)); + v.push_back(std::to_string(e.color)); + v.push_back(std::to_string(e.augment_one)); + v.push_back(std::to_string(e.augment_two)); + v.push_back(std::to_string(e.augment_three)); + v.push_back(std::to_string(e.augment_four)); + v.push_back(std::to_string(e.augment_five)); + v.push_back(std::to_string(e.augment_six)); + v.push_back("'" + Strings::Escape(e.custom_data) + "'"); + v.push_back(std::to_string(e.ornament_icon)); + v.push_back(std::to_string(e.ornament_idfile)); + v.push_back(std::to_string(e.ornament_hero_model)); + v.push_back(std::to_string(e.guid)); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseReplace(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } +}; + +#endif //EQEMU_BASE_SHAREDBANK_REPOSITORY_H diff --git a/common/repositories/base/base_trader_repository.h b/common/repositories/base/base_trader_repository.h index d7cdbbefc..99236fcac 100644 --- a/common/repositories/base/base_trader_repository.h +++ b/common/repositories/base/base_trader_repository.h @@ -28,13 +28,15 @@ public: uint32_t aug_slot_4; uint32_t aug_slot_5; uint32_t aug_slot_6; - int32_t item_sn; + uint32_t item_sn; int32_t item_charges; - uint64_t item_cost; + uint32_t item_cost; uint8_t slot_id; uint32_t char_entity_id; uint32_t char_zone_id; - int8_t active_transaction; + int32_t char_zone_instance_id; + uint8_t active_transaction; + time_t listing_date; }; static std::string PrimaryKey() @@ -60,7 +62,9 @@ public: "slot_id", "char_entity_id", "char_zone_id", + "char_zone_instance_id", "active_transaction", + "listing_date", }; } @@ -82,7 +86,9 @@ public: "slot_id", "char_entity_id", "char_zone_id", + "char_zone_instance_id", "active_transaction", + "UNIX_TIMESTAMP(listing_date)", }; } @@ -123,22 +129,24 @@ public: { Trader e{}; - e.id = 0; - e.char_id = 0; - e.item_id = 0; - e.aug_slot_1 = 0; - e.aug_slot_2 = 0; - e.aug_slot_3 = 0; - e.aug_slot_4 = 0; - e.aug_slot_5 = 0; - e.aug_slot_6 = 0; - e.item_sn = 0; - e.item_charges = 0; - e.item_cost = 0; - e.slot_id = 0; - e.char_entity_id = 0; - e.char_zone_id = 0; - e.active_transaction = 0; + e.id = 0; + e.char_id = 0; + e.item_id = 0; + e.aug_slot_1 = 0; + e.aug_slot_2 = 0; + e.aug_slot_3 = 0; + e.aug_slot_4 = 0; + e.aug_slot_5 = 0; + e.aug_slot_6 = 0; + e.item_sn = 0; + e.item_charges = 0; + e.item_cost = 0; + e.slot_id = 0; + e.char_entity_id = 0; + e.char_zone_id = 0; + e.char_zone_instance_id = 0; + e.active_transaction = 0; + e.listing_date = 0; return e; } @@ -175,22 +183,24 @@ public: if (results.RowCount() == 1) { Trader e{}; - e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; - e.char_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; - e.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; - e.aug_slot_1 = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; - e.aug_slot_2 = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; - e.aug_slot_3 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; - e.aug_slot_4 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; - e.aug_slot_5 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; - e.aug_slot_6 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; - e.item_sn = row[9] ? static_cast(atoi(row[9])) : 0; - e.item_charges = row[10] ? static_cast(atoi(row[10])) : 0; - e.item_cost = row[11] ? strtoull(row[11], nullptr, 10) : 0; - e.slot_id = row[12] ? static_cast(strtoul(row[12], nullptr, 10)) : 0; - e.char_entity_id = row[13] ? static_cast(strtoul(row[13], nullptr, 10)) : 0; - e.char_zone_id = row[14] ? static_cast(strtoul(row[14], nullptr, 10)) : 0; - e.active_transaction = row[15] ? static_cast(atoi(row[15])) : 0; + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.char_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.aug_slot_1 = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.aug_slot_2 = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.aug_slot_3 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.aug_slot_4 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.aug_slot_5 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.aug_slot_6 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.item_sn = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.item_charges = row[10] ? static_cast(atoi(row[10])) : 0; + e.item_cost = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; + e.slot_id = row[12] ? static_cast(strtoul(row[12], nullptr, 10)) : 0; + e.char_entity_id = row[13] ? static_cast(strtoul(row[13], nullptr, 10)) : 0; + e.char_zone_id = row[14] ? static_cast(strtoul(row[14], nullptr, 10)) : 0; + e.char_zone_instance_id = row[15] ? static_cast(atoi(row[15])) : 0; + e.active_transaction = row[16] ? static_cast(strtoul(row[16], nullptr, 10)) : 0; + e.listing_date = strtoll(row[17] ? row[17] : "-1", nullptr, 10); return e; } @@ -238,7 +248,9 @@ public: v.push_back(columns[12] + " = " + std::to_string(e.slot_id)); v.push_back(columns[13] + " = " + std::to_string(e.char_entity_id)); v.push_back(columns[14] + " = " + std::to_string(e.char_zone_id)); - v.push_back(columns[15] + " = " + std::to_string(e.active_transaction)); + v.push_back(columns[15] + " = " + std::to_string(e.char_zone_instance_id)); + v.push_back(columns[16] + " = " + std::to_string(e.active_transaction)); + v.push_back(columns[17] + " = FROM_UNIXTIME(" + (e.listing_date > 0 ? std::to_string(e.listing_date) : "null") + ")"); auto results = db.QueryDatabase( fmt::format( @@ -275,7 +287,9 @@ public: v.push_back(std::to_string(e.slot_id)); v.push_back(std::to_string(e.char_entity_id)); v.push_back(std::to_string(e.char_zone_id)); + v.push_back(std::to_string(e.char_zone_instance_id)); v.push_back(std::to_string(e.active_transaction)); + v.push_back("FROM_UNIXTIME(" + (e.listing_date > 0 ? std::to_string(e.listing_date) : "null") + ")"); auto results = db.QueryDatabase( fmt::format( @@ -320,7 +334,9 @@ public: v.push_back(std::to_string(e.slot_id)); v.push_back(std::to_string(e.char_entity_id)); v.push_back(std::to_string(e.char_zone_id)); + v.push_back(std::to_string(e.char_zone_instance_id)); v.push_back(std::to_string(e.active_transaction)); + v.push_back("FROM_UNIXTIME(" + (e.listing_date > 0 ? std::to_string(e.listing_date) : "null") + ")"); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } @@ -354,22 +370,24 @@ public: for (auto row = results.begin(); row != results.end(); ++row) { Trader e{}; - e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; - e.char_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; - e.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; - e.aug_slot_1 = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; - e.aug_slot_2 = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; - e.aug_slot_3 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; - e.aug_slot_4 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; - e.aug_slot_5 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; - e.aug_slot_6 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; - e.item_sn = row[9] ? static_cast(atoi(row[9])) : 0; - e.item_charges = row[10] ? static_cast(atoi(row[10])) : 0; - e.item_cost = row[11] ? strtoull(row[11], nullptr, 10) : 0; - e.slot_id = row[12] ? static_cast(strtoul(row[12], nullptr, 10)) : 0; - e.char_entity_id = row[13] ? static_cast(strtoul(row[13], nullptr, 10)) : 0; - e.char_zone_id = row[14] ? static_cast(strtoul(row[14], nullptr, 10)) : 0; - e.active_transaction = row[15] ? static_cast(atoi(row[15])) : 0; + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.char_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.aug_slot_1 = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.aug_slot_2 = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.aug_slot_3 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.aug_slot_4 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.aug_slot_5 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.aug_slot_6 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.item_sn = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.item_charges = row[10] ? static_cast(atoi(row[10])) : 0; + e.item_cost = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; + e.slot_id = row[12] ? static_cast(strtoul(row[12], nullptr, 10)) : 0; + e.char_entity_id = row[13] ? static_cast(strtoul(row[13], nullptr, 10)) : 0; + e.char_zone_id = row[14] ? static_cast(strtoul(row[14], nullptr, 10)) : 0; + e.char_zone_instance_id = row[15] ? static_cast(atoi(row[15])) : 0; + e.active_transaction = row[16] ? static_cast(strtoul(row[16], nullptr, 10)) : 0; + e.listing_date = strtoll(row[17] ? row[17] : "-1", nullptr, 10); all_entries.push_back(e); } @@ -394,22 +412,24 @@ public: for (auto row = results.begin(); row != results.end(); ++row) { Trader e{}; - e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; - e.char_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; - e.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; - e.aug_slot_1 = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; - e.aug_slot_2 = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; - e.aug_slot_3 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; - e.aug_slot_4 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; - e.aug_slot_5 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; - e.aug_slot_6 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; - e.item_sn = row[9] ? static_cast(atoi(row[9])) : 0; - e.item_charges = row[10] ? static_cast(atoi(row[10])) : 0; - e.item_cost = row[11] ? strtoull(row[11], nullptr, 10) : 0; - e.slot_id = row[12] ? static_cast(strtoul(row[12], nullptr, 10)) : 0; - e.char_entity_id = row[13] ? static_cast(strtoul(row[13], nullptr, 10)) : 0; - e.char_zone_id = row[14] ? static_cast(strtoul(row[14], nullptr, 10)) : 0; - e.active_transaction = row[15] ? static_cast(atoi(row[15])) : 0; + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.char_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.aug_slot_1 = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.aug_slot_2 = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.aug_slot_3 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.aug_slot_4 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.aug_slot_5 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.aug_slot_6 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.item_sn = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.item_charges = row[10] ? static_cast(atoi(row[10])) : 0; + e.item_cost = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; + e.slot_id = row[12] ? static_cast(strtoul(row[12], nullptr, 10)) : 0; + e.char_entity_id = row[13] ? static_cast(strtoul(row[13], nullptr, 10)) : 0; + e.char_zone_id = row[14] ? static_cast(strtoul(row[14], nullptr, 10)) : 0; + e.char_zone_instance_id = row[15] ? static_cast(atoi(row[15])) : 0; + e.active_transaction = row[16] ? static_cast(strtoul(row[16], nullptr, 10)) : 0; + e.listing_date = strtoll(row[17] ? row[17] : "-1", nullptr, 10); all_entries.push_back(e); } @@ -499,7 +519,9 @@ public: v.push_back(std::to_string(e.slot_id)); v.push_back(std::to_string(e.char_entity_id)); v.push_back(std::to_string(e.char_zone_id)); + v.push_back(std::to_string(e.char_zone_instance_id)); v.push_back(std::to_string(e.active_transaction)); + v.push_back("FROM_UNIXTIME(" + (e.listing_date > 0 ? std::to_string(e.listing_date) : "null") + ")"); auto results = db.QueryDatabase( fmt::format( @@ -537,7 +559,9 @@ public: v.push_back(std::to_string(e.slot_id)); v.push_back(std::to_string(e.char_entity_id)); v.push_back(std::to_string(e.char_zone_id)); + v.push_back(std::to_string(e.char_zone_instance_id)); v.push_back(std::to_string(e.active_transaction)); + v.push_back("FROM_UNIXTIME(" + (e.listing_date > 0 ? std::to_string(e.listing_date) : "null") + ")"); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } diff --git a/common/repositories/base/base_zone_repository.h b/common/repositories/base/base_zone_repository.h index 0e23c0b85..1b4293109 100644 --- a/common/repositories/base/base_zone_repository.h +++ b/common/repositories/base/base_zone_repository.h @@ -111,12 +111,13 @@ public: int32_t fast_regen_mana; int32_t fast_regen_endurance; int32_t npc_max_aggro_dist; - uint32_t max_movement_update_range; + uint32_t client_update_range; int32_t underworld_teleport_index; int32_t lava_damage; int32_t min_lava_damage; uint8_t idle_when_empty; uint32_t seconds_before_idle; + int32_t shard_at_player_count; }; static std::string PrimaryKey() @@ -219,12 +220,13 @@ public: "fast_regen_mana", "fast_regen_endurance", "npc_max_aggro_dist", - "max_movement_update_range", + "client_update_range", "underworld_teleport_index", "lava_damage", "min_lava_damage", "idle_when_empty", "seconds_before_idle", + "shard_at_player_count", }; } @@ -323,12 +325,13 @@ public: "fast_regen_mana", "fast_regen_endurance", "npc_max_aggro_dist", - "max_movement_update_range", + "client_update_range", "underworld_teleport_index", "lava_damage", "min_lava_damage", "idle_when_empty", "seconds_before_idle", + "shard_at_player_count", }; } @@ -461,12 +464,13 @@ public: e.fast_regen_mana = 180; e.fast_regen_endurance = 180; e.npc_max_aggro_dist = 600; - e.max_movement_update_range = 600; + e.client_update_range = 600; e.underworld_teleport_index = 0; e.lava_damage = 50; e.min_lava_damage = 10; e.idle_when_empty = 1; e.seconds_before_idle = 60; + e.shard_at_player_count = 0; return e; } @@ -595,12 +599,13 @@ public: e.fast_regen_mana = row[89] ? static_cast(atoi(row[89])) : 180; e.fast_regen_endurance = row[90] ? static_cast(atoi(row[90])) : 180; e.npc_max_aggro_dist = row[91] ? static_cast(atoi(row[91])) : 600; - e.max_movement_update_range = row[92] ? static_cast(strtoul(row[92], nullptr, 10)) : 600; + e.client_update_range = row[92] ? static_cast(strtoul(row[92], nullptr, 10)) : 600; e.underworld_teleport_index = row[93] ? static_cast(atoi(row[93])) : 0; e.lava_damage = row[94] ? static_cast(atoi(row[94])) : 50; e.min_lava_damage = row[95] ? static_cast(atoi(row[95])) : 10; e.idle_when_empty = row[96] ? static_cast(strtoul(row[96], nullptr, 10)) : 1; e.seconds_before_idle = row[97] ? static_cast(strtoul(row[97], nullptr, 10)) : 60; + e.shard_at_player_count = row[98] ? static_cast(atoi(row[98])) : 0; return e; } @@ -725,12 +730,13 @@ public: v.push_back(columns[89] + " = " + std::to_string(e.fast_regen_mana)); v.push_back(columns[90] + " = " + std::to_string(e.fast_regen_endurance)); v.push_back(columns[91] + " = " + std::to_string(e.npc_max_aggro_dist)); - v.push_back(columns[92] + " = " + std::to_string(e.max_movement_update_range)); + v.push_back(columns[92] + " = " + std::to_string(e.client_update_range)); v.push_back(columns[93] + " = " + std::to_string(e.underworld_teleport_index)); v.push_back(columns[94] + " = " + std::to_string(e.lava_damage)); v.push_back(columns[95] + " = " + std::to_string(e.min_lava_damage)); v.push_back(columns[96] + " = " + std::to_string(e.idle_when_empty)); v.push_back(columns[97] + " = " + std::to_string(e.seconds_before_idle)); + v.push_back(columns[98] + " = " + std::to_string(e.shard_at_player_count)); auto results = db.QueryDatabase( fmt::format( @@ -844,12 +850,13 @@ public: v.push_back(std::to_string(e.fast_regen_mana)); v.push_back(std::to_string(e.fast_regen_endurance)); v.push_back(std::to_string(e.npc_max_aggro_dist)); - v.push_back(std::to_string(e.max_movement_update_range)); + v.push_back(std::to_string(e.client_update_range)); v.push_back(std::to_string(e.underworld_teleport_index)); v.push_back(std::to_string(e.lava_damage)); v.push_back(std::to_string(e.min_lava_damage)); v.push_back(std::to_string(e.idle_when_empty)); v.push_back(std::to_string(e.seconds_before_idle)); + v.push_back(std::to_string(e.shard_at_player_count)); auto results = db.QueryDatabase( fmt::format( @@ -971,12 +978,13 @@ public: v.push_back(std::to_string(e.fast_regen_mana)); v.push_back(std::to_string(e.fast_regen_endurance)); v.push_back(std::to_string(e.npc_max_aggro_dist)); - v.push_back(std::to_string(e.max_movement_update_range)); + v.push_back(std::to_string(e.client_update_range)); v.push_back(std::to_string(e.underworld_teleport_index)); v.push_back(std::to_string(e.lava_damage)); v.push_back(std::to_string(e.min_lava_damage)); v.push_back(std::to_string(e.idle_when_empty)); v.push_back(std::to_string(e.seconds_before_idle)); + v.push_back(std::to_string(e.shard_at_player_count)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } @@ -1102,12 +1110,13 @@ public: e.fast_regen_mana = row[89] ? static_cast(atoi(row[89])) : 180; e.fast_regen_endurance = row[90] ? static_cast(atoi(row[90])) : 180; e.npc_max_aggro_dist = row[91] ? static_cast(atoi(row[91])) : 600; - e.max_movement_update_range = row[92] ? static_cast(strtoul(row[92], nullptr, 10)) : 600; + e.client_update_range = row[92] ? static_cast(strtoul(row[92], nullptr, 10)) : 600; e.underworld_teleport_index = row[93] ? static_cast(atoi(row[93])) : 0; e.lava_damage = row[94] ? static_cast(atoi(row[94])) : 50; e.min_lava_damage = row[95] ? static_cast(atoi(row[95])) : 10; e.idle_when_empty = row[96] ? static_cast(strtoul(row[96], nullptr, 10)) : 1; e.seconds_before_idle = row[97] ? static_cast(strtoul(row[97], nullptr, 10)) : 60; + e.shard_at_player_count = row[98] ? static_cast(atoi(row[98])) : 0; all_entries.push_back(e); } @@ -1224,12 +1233,13 @@ public: e.fast_regen_mana = row[89] ? static_cast(atoi(row[89])) : 180; e.fast_regen_endurance = row[90] ? static_cast(atoi(row[90])) : 180; e.npc_max_aggro_dist = row[91] ? static_cast(atoi(row[91])) : 600; - e.max_movement_update_range = row[92] ? static_cast(strtoul(row[92], nullptr, 10)) : 600; + e.client_update_range = row[92] ? static_cast(strtoul(row[92], nullptr, 10)) : 600; e.underworld_teleport_index = row[93] ? static_cast(atoi(row[93])) : 0; e.lava_damage = row[94] ? static_cast(atoi(row[94])) : 50; e.min_lava_damage = row[95] ? static_cast(atoi(row[95])) : 10; e.idle_when_empty = row[96] ? static_cast(strtoul(row[96], nullptr, 10)) : 1; e.seconds_before_idle = row[97] ? static_cast(strtoul(row[97], nullptr, 10)) : 60; + e.shard_at_player_count = row[98] ? static_cast(atoi(row[98])) : 0; all_entries.push_back(e); } @@ -1396,12 +1406,13 @@ public: v.push_back(std::to_string(e.fast_regen_mana)); v.push_back(std::to_string(e.fast_regen_endurance)); v.push_back(std::to_string(e.npc_max_aggro_dist)); - v.push_back(std::to_string(e.max_movement_update_range)); + v.push_back(std::to_string(e.client_update_range)); v.push_back(std::to_string(e.underworld_teleport_index)); v.push_back(std::to_string(e.lava_damage)); v.push_back(std::to_string(e.min_lava_damage)); v.push_back(std::to_string(e.idle_when_empty)); v.push_back(std::to_string(e.seconds_before_idle)); + v.push_back(std::to_string(e.shard_at_player_count)); auto results = db.QueryDatabase( fmt::format( @@ -1516,12 +1527,13 @@ public: v.push_back(std::to_string(e.fast_regen_mana)); v.push_back(std::to_string(e.fast_regen_endurance)); v.push_back(std::to_string(e.npc_max_aggro_dist)); - v.push_back(std::to_string(e.max_movement_update_range)); + v.push_back(std::to_string(e.client_update_range)); v.push_back(std::to_string(e.underworld_teleport_index)); v.push_back(std::to_string(e.lava_damage)); v.push_back(std::to_string(e.min_lava_damage)); v.push_back(std::to_string(e.idle_when_empty)); v.push_back(std::to_string(e.seconds_before_idle)); + v.push_back(std::to_string(e.shard_at_player_count)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } diff --git a/common/repositories/base/base_zone_state_spawns_repository.h b/common/repositories/base/base_zone_state_spawns_repository.h new file mode 100644 index 000000000..d77e11b47 --- /dev/null +++ b/common/repositories/base/base_zone_state_spawns_repository.h @@ -0,0 +1,715 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://docs.eqemu.io/developer/repositories + */ + +#ifndef EQEMU_BASE_ZONE_STATE_SPAWNS_REPOSITORY_H +#define EQEMU_BASE_ZONE_STATE_SPAWNS_REPOSITORY_H + +#include "../../database.h" +#include "../../strings.h" +#include + +class BaseZoneStateSpawnsRepository { +public: + struct ZoneStateSpawns { + int64_t id; + 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; + uint32_t spawngroup_id; + float x; + float y; + float z; + float heading; + uint32_t respawn_time; + uint32_t variance; + uint32_t grid; + int32_t current_waypoint; + int16_t path_when_zone_idle; + uint16_t condition_id; + int16_t condition_min_value; + int16_t enabled; + uint16_t anim; + std::string loot_data; + std::string entity_variables; + std::string buffs; + int64_t hp; + int64_t mana; + int64_t endurance; + time_t created_at; + }; + + static std::string PrimaryKey() + { + return std::string("id"); + } + + static std::vector Columns() + { + return { + "id", + "zone_id", + "instance_id", + "is_corpse", + "is_zone", + "decay_in_seconds", + "npc_id", + "spawn2_id", + "spawngroup_id", + "x", + "y", + "z", + "heading", + "respawn_time", + "variance", + "grid", + "current_waypoint", + "path_when_zone_idle", + "condition_id", + "condition_min_value", + "enabled", + "anim", + "loot_data", + "entity_variables", + "buffs", + "hp", + "mana", + "endurance", + "created_at", + }; + } + + static std::vector SelectColumns() + { + return { + "id", + "zone_id", + "instance_id", + "is_corpse", + "is_zone", + "decay_in_seconds", + "npc_id", + "spawn2_id", + "spawngroup_id", + "x", + "y", + "z", + "heading", + "respawn_time", + "variance", + "grid", + "current_waypoint", + "path_when_zone_idle", + "condition_id", + "condition_min_value", + "enabled", + "anim", + "loot_data", + "entity_variables", + "buffs", + "hp", + "mana", + "endurance", + "UNIX_TIMESTAMP(created_at)", + }; + } + + static std::string ColumnsRaw() + { + return std::string(Strings::Implode(", ", Columns())); + } + + static std::string SelectColumnsRaw() + { + return std::string(Strings::Implode(", ", SelectColumns())); + } + + static std::string TableName() + { + return std::string("zone_state_spawns"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + SelectColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static ZoneStateSpawns NewEntity() + { + ZoneStateSpawns e{}; + + e.id = 0; + 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; + e.spawngroup_id = 0; + e.x = 0; + e.y = 0; + e.z = 0; + e.heading = 0; + e.respawn_time = 0; + e.variance = 0; + e.grid = 0; + e.current_waypoint = 0; + e.path_when_zone_idle = 0; + e.condition_id = 0; + e.condition_min_value = 0; + e.enabled = 1; + e.anim = 0; + e.loot_data = ""; + e.entity_variables = ""; + e.buffs = ""; + e.hp = 0; + e.mana = 0; + e.endurance = 0; + e.created_at = 0; + + return e; + } + + static ZoneStateSpawns GetZoneStateSpawns( + const std::vector &zone_state_spawnss, + int zone_state_spawns_id + ) + { + for (auto &zone_state_spawns : zone_state_spawnss) { + if (zone_state_spawns.id == zone_state_spawns_id) { + return zone_state_spawns; + } + } + + return NewEntity(); + } + + static ZoneStateSpawns FindOne( + Database& db, + int zone_state_spawns_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {} = {} LIMIT 1", + BaseSelect(), + PrimaryKey(), + zone_state_spawns_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + ZoneStateSpawns e{}; + + e.id = row[0] ? strtoll(row[0], nullptr, 10) : 0; + e.zone_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.instance_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.is_corpse = row[3] ? static_cast(atoi(row[3])) : 0; + e.is_zone = row[4] ? static_cast(atoi(row[4])) : 0; + e.decay_in_seconds = row[5] ? static_cast(atoi(row[5])) : 0; + e.npc_id = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.spawn2_id = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.spawngroup_id = row[8] ? static_cast(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(strtoul(row[13], nullptr, 10)) : 0; + e.variance = row[14] ? static_cast(strtoul(row[14], nullptr, 10)) : 0; + e.grid = row[15] ? static_cast(strtoul(row[15], nullptr, 10)) : 0; + e.current_waypoint = row[16] ? static_cast(atoi(row[16])) : 0; + e.path_when_zone_idle = row[17] ? static_cast(atoi(row[17])) : 0; + e.condition_id = row[18] ? static_cast(strtoul(row[18], nullptr, 10)) : 0; + e.condition_min_value = row[19] ? static_cast(atoi(row[19])) : 0; + e.enabled = row[20] ? static_cast(atoi(row[20])) : 1; + e.anim = row[21] ? static_cast(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; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int zone_state_spawns_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + zone_state_spawns_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + const ZoneStateSpawns &e + ) + { + std::vector v; + + auto columns = Columns(); + + 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.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( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + Strings::Implode(", ", v), + PrimaryKey(), + e.id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static ZoneStateSpawns InsertOne( + Database& db, + ZoneStateSpawns e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + 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)); + v.push_back(std::to_string(e.spawngroup_id)); + v.push_back(std::to_string(e.x)); + v.push_back(std::to_string(e.y)); + v.push_back(std::to_string(e.z)); + v.push_back(std::to_string(e.heading)); + v.push_back(std::to_string(e.respawn_time)); + v.push_back(std::to_string(e.variance)); + v.push_back(std::to_string(e.grid)); + v.push_back(std::to_string(e.current_waypoint)); + v.push_back(std::to_string(e.path_when_zone_idle)); + v.push_back(std::to_string(e.condition_id)); + v.push_back(std::to_string(e.condition_min_value)); + v.push_back(std::to_string(e.enabled)); + v.push_back(std::to_string(e.anim)); + v.push_back("'" + Strings::Escape(e.loot_data) + "'"); + v.push_back("'" + Strings::Escape(e.entity_variables) + "'"); + v.push_back("'" + Strings::Escape(e.buffs) + "'"); + v.push_back(std::to_string(e.hp)); + v.push_back(std::to_string(e.mana)); + v.push_back(std::to_string(e.endurance)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + Strings::Implode(",", v) + ) + ); + + if (results.Success()) { + e.id = results.LastInsertedID(); + return e; + } + + e = NewEntity(); + + return e; + } + + static int InsertMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + 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)); + v.push_back(std::to_string(e.spawngroup_id)); + v.push_back(std::to_string(e.x)); + v.push_back(std::to_string(e.y)); + v.push_back(std::to_string(e.z)); + v.push_back(std::to_string(e.heading)); + v.push_back(std::to_string(e.respawn_time)); + v.push_back(std::to_string(e.variance)); + v.push_back(std::to_string(e.grid)); + v.push_back(std::to_string(e.current_waypoint)); + v.push_back(std::to_string(e.path_when_zone_idle)); + v.push_back(std::to_string(e.condition_id)); + v.push_back(std::to_string(e.condition_min_value)); + v.push_back(std::to_string(e.enabled)); + v.push_back(std::to_string(e.anim)); + v.push_back("'" + Strings::Escape(e.loot_data) + "'"); + v.push_back("'" + Strings::Escape(e.entity_variables) + "'"); + v.push_back("'" + Strings::Escape(e.buffs) + "'"); + v.push_back(std::to_string(e.hp)); + v.push_back(std::to_string(e.mana)); + v.push_back(std::to_string(e.endurance)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + ZoneStateSpawns e{}; + + e.id = row[0] ? strtoll(row[0], nullptr, 10) : 0; + e.zone_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.instance_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.is_corpse = row[3] ? static_cast(atoi(row[3])) : 0; + e.is_zone = row[4] ? static_cast(atoi(row[4])) : 0; + e.decay_in_seconds = row[5] ? static_cast(atoi(row[5])) : 0; + e.npc_id = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.spawn2_id = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.spawngroup_id = row[8] ? static_cast(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(strtoul(row[13], nullptr, 10)) : 0; + e.variance = row[14] ? static_cast(strtoul(row[14], nullptr, 10)) : 0; + e.grid = row[15] ? static_cast(strtoul(row[15], nullptr, 10)) : 0; + e.current_waypoint = row[16] ? static_cast(atoi(row[16])) : 0; + e.path_when_zone_idle = row[17] ? static_cast(atoi(row[17])) : 0; + e.condition_id = row[18] ? static_cast(strtoul(row[18], nullptr, 10)) : 0; + e.condition_min_value = row[19] ? static_cast(atoi(row[19])) : 0; + e.enabled = row[20] ? static_cast(atoi(row[20])) : 1; + e.anim = row[21] ? static_cast(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); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, const std::string &where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + ZoneStateSpawns e{}; + + e.id = row[0] ? strtoll(row[0], nullptr, 10) : 0; + e.zone_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.instance_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.is_corpse = row[3] ? static_cast(atoi(row[3])) : 0; + e.is_zone = row[4] ? static_cast(atoi(row[4])) : 0; + e.decay_in_seconds = row[5] ? static_cast(atoi(row[5])) : 0; + e.npc_id = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.spawn2_id = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.spawngroup_id = row[8] ? static_cast(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(strtoul(row[13], nullptr, 10)) : 0; + e.variance = row[14] ? static_cast(strtoul(row[14], nullptr, 10)) : 0; + e.grid = row[15] ? static_cast(strtoul(row[15], nullptr, 10)) : 0; + e.current_waypoint = row[16] ? static_cast(atoi(row[16])) : 0; + e.path_when_zone_idle = row[17] ? static_cast(atoi(row[17])) : 0; + e.condition_id = row[18] ? static_cast(strtoul(row[18], nullptr, 10)) : 0; + e.condition_min_value = row[19] ? static_cast(atoi(row[19])) : 0; + e.enabled = row[20] ? static_cast(atoi(row[20])) : 1; + e.anim = row[21] ? static_cast(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); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, const std::string &where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int64 GetMaxId(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COALESCE(MAX({}), 0) FROM {}", + PrimaryKey(), + TableName() + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static int64 Count(Database& db, const std::string &where_filter = "") + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COUNT(*) FROM {} {}", + TableName(), + (where_filter.empty() ? "" : "WHERE " + where_filter) + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static std::string BaseReplace() + { + return fmt::format( + "REPLACE INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static int ReplaceOne( + Database& db, + const ZoneStateSpawns &e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + 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)); + v.push_back(std::to_string(e.spawngroup_id)); + v.push_back(std::to_string(e.x)); + v.push_back(std::to_string(e.y)); + v.push_back(std::to_string(e.z)); + v.push_back(std::to_string(e.heading)); + v.push_back(std::to_string(e.respawn_time)); + v.push_back(std::to_string(e.variance)); + v.push_back(std::to_string(e.grid)); + v.push_back(std::to_string(e.current_waypoint)); + v.push_back(std::to_string(e.path_when_zone_idle)); + v.push_back(std::to_string(e.condition_id)); + v.push_back(std::to_string(e.condition_min_value)); + v.push_back(std::to_string(e.enabled)); + v.push_back(std::to_string(e.anim)); + v.push_back("'" + Strings::Escape(e.loot_data) + "'"); + v.push_back("'" + Strings::Escape(e.entity_variables) + "'"); + v.push_back("'" + Strings::Escape(e.buffs) + "'"); + v.push_back(std::to_string(e.hp)); + v.push_back(std::to_string(e.mana)); + v.push_back(std::to_string(e.endurance)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseReplace(), + Strings::Implode(",", v) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int ReplaceMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + 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)); + v.push_back(std::to_string(e.spawngroup_id)); + v.push_back(std::to_string(e.x)); + v.push_back(std::to_string(e.y)); + v.push_back(std::to_string(e.z)); + v.push_back(std::to_string(e.heading)); + v.push_back(std::to_string(e.respawn_time)); + v.push_back(std::to_string(e.variance)); + v.push_back(std::to_string(e.grid)); + v.push_back(std::to_string(e.current_waypoint)); + v.push_back(std::to_string(e.path_when_zone_idle)); + v.push_back(std::to_string(e.condition_id)); + v.push_back(std::to_string(e.condition_min_value)); + v.push_back(std::to_string(e.enabled)); + v.push_back(std::to_string(e.anim)); + v.push_back("'" + Strings::Escape(e.loot_data) + "'"); + v.push_back("'" + Strings::Escape(e.entity_variables) + "'"); + v.push_back("'" + Strings::Escape(e.buffs) + "'"); + v.push_back(std::to_string(e.hp)); + v.push_back(std::to_string(e.mana)); + v.push_back(std::to_string(e.endurance)); + v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseReplace(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } +}; + +#endif //EQEMU_BASE_ZONE_STATE_SPAWNS_REPOSITORY_H diff --git a/common/repositories/bot_blocked_buffs_repository.h b/common/repositories/bot_blocked_buffs_repository.h new file mode 100644 index 000000000..be6466cbb --- /dev/null +++ b/common/repositories/bot_blocked_buffs_repository.h @@ -0,0 +1,50 @@ +#ifndef EQEMU_BOT_BLOCKED_BUFFS_REPOSITORY_H +#define EQEMU_BOT_BLOCKED_BUFFS_REPOSITORY_H + +#include "../database.h" +#include "../strings.h" +#include "base/base_bot_blocked_buffs_repository.h" + +class BotBlockedBuffsRepository: public BaseBotBlockedBuffsRepository { +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 + * + * BotBlockedBuffsRepository::GetByZoneAndVersion(int zone_id, int zone_version) + * BotBlockedBuffsRepository::GetWhereNeverExpires() + * BotBlockedBuffsRepository::GetWhereXAndY() + * BotBlockedBuffsRepository::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 + +}; + +#endif //EQEMU_BOT_BLOCKED_BUFFS_REPOSITORY_H diff --git a/common/repositories/bot_data_repository.h b/common/repositories/bot_data_repository.h index f6508479f..e8d0038a4 100644 --- a/common/repositories/bot_data_repository.h +++ b/common/repositories/bot_data_repository.h @@ -44,46 +44,6 @@ public: */ // Custom extended repository methods here - static bool SaveAllHelmAppearances(Database& db, const uint32 owner_id, const bool show_flag) - { - auto results = db.QueryDatabase( - fmt::format( - "UPDATE `{}` SET `show_helm` = {} WHERE `owner_id` = {}", - TableName(), - show_flag ? 1 : 0, - owner_id - ) - ); - - return results.Success(); - } - - static bool ToggleAllHelmAppearances(Database& db, const uint32 owner_id) - { - auto results = db.QueryDatabase( - fmt::format( - "UPDATE `{}` SET `show_helm` = (`show_helm` XOR '1') WHERE `owner_id` = {}", - TableName(), - owner_id - ) - ); - - return results.Success(); - } - - static bool SaveAllFollowDistances(Database& db, const uint32 owner_id, const uint32 follow_distance) - { - auto results = db.QueryDatabase( - fmt::format( - "UPDATE `{}` SET `follow_distance` = {} WHERE `owner_id` = {}", - TableName(), - follow_distance, - owner_id - ) - ); - - return results.Success(); - } }; #endif //EQEMU_BOT_DATA_REPOSITORY_H diff --git a/common/repositories/bot_settings_repository.h b/common/repositories/bot_settings_repository.h new file mode 100644 index 000000000..7cf1d0dbd --- /dev/null +++ b/common/repositories/bot_settings_repository.h @@ -0,0 +1,50 @@ +#ifndef EQEMU_BOT_SETTINGS_REPOSITORY_H +#define EQEMU_BOT_SETTINGS_REPOSITORY_H + +#include "../database.h" +#include "../strings.h" +#include "base/base_bot_settings_repository.h" + +class BotSettingsRepository: public BaseBotSettingsRepository { +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 + * + * BotSettingsRepository::GetByZoneAndVersion(int zone_id, int zone_version) + * BotSettingsRepository::GetWhereNeverExpires() + * BotSettingsRepository::GetWhereXAndY() + * BotSettingsRepository::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 + +}; + +#endif //EQEMU_BOT_SETTINGS_REPOSITORY_H diff --git a/common/repositories/buyer_repository.h b/common/repositories/buyer_repository.h index 382ad9ccf..2d402b0dc 100644 --- a/common/repositories/buyer_repository.h +++ b/common/repositories/buyer_repository.h @@ -106,13 +106,8 @@ public: return false; } - auto buy_lines = BaseBuyerBuyLinesRepository::GetWhere( - db, - fmt::format("`buyer_id` = '{}'", buyer.front().id) - ); - if (buy_lines.empty()) { - return false; - } + auto buy_lines = + BaseBuyerBuyLinesRepository::GetWhere(db, fmt::format("`buyer_id` = {}", buyer.front().id)); std::vector buy_line_ids{}; for (auto const &bl: buy_lines) { @@ -121,23 +116,65 @@ public: DeleteWhere(db, fmt::format("`char_id` = '{}';", char_id)); if (buy_line_ids.empty()) { - return false; + return true; } BaseBuyerBuyLinesRepository::DeleteWhere( - db, - fmt::format("`id` IN({})", Strings::Implode(", ", buy_line_ids)) + db, fmt::format("`id` IN({})", Strings::Implode(", ", buy_line_ids)) ); BaseBuyerTradeItemsRepository::DeleteWhere( - db, - fmt::format( - "`buyer_buy_lines_id` IN({})", - Strings::Implode(", ", buy_line_ids)) + db, fmt::format("`buyer_buy_lines_id` IN({})", Strings::Implode(", ", buy_line_ids)) ); } return true; } + + static bool DeleteBuyers(Database &db, uint32 char_zone_id, uint32 char_zone_instance_id) + { + auto buyers = GetWhere( + db, + fmt::format( + "`char_zone_id` = {} AND `char_zone_instance_id` = {}", char_zone_id, char_zone_instance_id + ) + ); + if (buyers.empty()) { + return false; + } + + std::vector buyer_ids{}; + std::vector buy_line_ids{}; + + for (auto const &b: buyers) { + buyer_ids.push_back(std::to_string(b.id)); + } + + auto buy_lines = BaseBuyerBuyLinesRepository::GetWhere( + db, fmt::format("`buyer_id` IN({})", Strings::Implode(", ", buyer_ids)) + ); + + if (!buy_lines.empty()) { + for (auto const &bl: buy_lines) { + buy_line_ids.push_back(std::to_string(bl.id)); + } + } + + DeleteWhere(db, fmt::format("`id` IN({});", Strings::Implode(", ", buyer_ids))); + if (buy_line_ids.empty()) { + return true; + } + + BaseBuyerBuyLinesRepository::DeleteWhere( + db, + fmt::format("`id` IN({})", Strings::Implode(", ", buy_line_ids)) + ); + BaseBuyerTradeItemsRepository::DeleteWhere( + db, + fmt::format("`buyer_buy_lines_id` IN({})", Strings::Implode(", ", buy_line_ids)) + ); + + return true; + } }; #endif //EQEMU_BUYER_REPOSITORY_H diff --git a/common/repositories/character_corpses_repository.h b/common/repositories/character_corpses_repository.h index 5bb95c3c3..0e60949fb 100644 --- a/common/repositories/character_corpses_repository.h +++ b/common/repositories/character_corpses_repository.h @@ -231,6 +231,21 @@ public: return UpdateOne(db, corpse); } + + static int UpdateEntityVariables(Database& db, uint32 corpse_id, const std::string& json_string) + { + auto results = db.QueryDatabase( + fmt::format( + "UPDATE `{}` SET `entity_variables` = '{}' WHERE `{}` = {}", + TableName(), + Strings::Escape(json_string), + PrimaryKey(), + corpse_id + ) + ); + + return results.Success() ? results.RowsAffected() : 0; + } }; #endif //EQEMU_CHARACTER_CORPSES_REPOSITORY_H diff --git a/common/repositories/character_data_repository.h b/common/repositories/character_data_repository.h index 249218279..68390fc35 100644 --- a/common/repositories/character_data_repository.h +++ b/common/repositories/character_data_repository.h @@ -80,6 +80,133 @@ public: return l.empty() ? CharacterDataRepository::NewEntity() : l.front(); } + + struct InstancePlayerCount { + int32_t instance_id; + uint32_t zone_id; + uint32_t player_count; + }; + + static std::vector GetInstanceZonePlayerCounts(Database& db, int zone_id) { + std::vector zone_player_counts; + + uint64_t shard_instance_duration = 3155760000; + + auto query = fmt::format(SQL( + SELECT + zone_id, + 0 AS instance_id, + COUNT(id) AS player_count + FROM + character_data + WHERE + zone_instance = 0 + AND zone_id = {} + AND last_login >= UNIX_TIMESTAMP(NOW()) - 600 + GROUP BY + zone_id + ORDER BY + zone_id, player_count DESC + ), zone_id); + + auto results = db.QueryDatabase(query); + for (auto row = results.begin(); row != results.end(); ++row) { + InstancePlayerCount e{}; + e.zone_id = std::stoi(row[0]); + e.instance_id = 0; + e.player_count = std::stoi(row[2]); + zone_player_counts.push_back(e); + } + + if (zone_player_counts.empty()) { + InstancePlayerCount e{}; + e.zone_id = zone_id; + e.instance_id = 0; + e.player_count = 0; + zone_player_counts.push_back(e); + } + + // duration 3155760000 is for shards explicitly + query = fmt::format( + SQL( + SELECT + i.id AS instance_id, + i.zone AS zone_id, + COUNT(c.id) AS player_count + FROM + instance_list i + LEFT JOIN + character_data c + ON + i.zone = c.zone_id + AND i.id = c.zone_instance + AND c.last_login >= UNIX_TIMESTAMP(NOW()) - 600 + AND (i.start_time + i.duration >= UNIX_TIMESTAMP(NOW()) OR i.never_expires = 0) + AND i.duration = {} + WHERE + i.zone IS NOT NULL AND i.zone = {} + GROUP BY + i.id, i.zone, i.version + ORDER BY + i.id ASC; + ), shard_instance_duration, zone_id + ); + + results = db.QueryDatabase(query); + if (!results.Success() || results.RowCount() == 0) { + return zone_player_counts; + } + + for (auto row = results.begin(); row != results.end(); ++row) { + InstancePlayerCount e{}; + e.instance_id = std::stoi(row[0]); + e.zone_id = std::stoi(row[1]); + e.player_count = std::stoi(row[2]); + zone_player_counts.push_back(e); + } + + return zone_player_counts; + } + + static std::vector GetCharacterIDsByAccountID( + Database& db, + uint32_t account_id + ) + { + std::vector character_ids; + + auto query = fmt::format( + "SELECT id FROM character_data WHERE account_id = {} AND deleted_at IS NULL", + account_id + ); + + auto results = db.QueryDatabase(query); + if (results.Success()) { + for (auto row : results) { + if (row[0]) { + character_ids.push_back(static_cast(std::stoul(row[0]))); + } + } + } + + return character_ids; + } + + static uint32_t GetTotalTimePlayed(Database& db, uint32_t account_id) + { + auto query = fmt::format( + "SELECT SUM(time_played) FROM `character_data` WHERE `account_id` = {}", + account_id + ); + + auto results = db.QueryDatabase(query); + if (!results.Success()) { + return 0; + } + + auto row = results.begin(); + return Strings::ToUnsignedInt(row[0]); + } }; #endif //EQEMU_CHARACTER_DATA_REPOSITORY_H diff --git a/common/repositories/character_evolving_items_repository.h b/common/repositories/character_evolving_items_repository.h new file mode 100644 index 000000000..8bc379505 --- /dev/null +++ b/common/repositories/character_evolving_items_repository.h @@ -0,0 +1,66 @@ +#ifndef EQEMU_CHARACTER_EVOLVING_ITEMS_REPOSITORY_H +#define EQEMU_CHARACTER_EVOLVING_ITEMS_REPOSITORY_H + +#include "../database.h" +#include "../strings.h" +#include "base/base_character_evolving_items_repository.h" + +#include + +class CharacterEvolvingItemsRepository: public BaseCharacterEvolvingItemsRepository { +public: + // Custom extended repository methods here + + static CharacterEvolvingItems SetCurrentAmountAndProgression(Database& db, const uint64 id, const uint64 amount, const double progression) + { + auto e = FindOne(db, id); + if (e.id == 0) { + return NewEntity(); + } + + e.current_amount = amount; + e.progression = progression; + e.deleted_at = 0; + UpdateOne(db, e); + return e; + } + + static CharacterEvolvingItems SetEquipped(Database& db, const uint64 id, const bool equipped) + { + auto e = FindOne(db, id); + if (e.id == 0) { + return NewEntity(); + } + + e.equipped = equipped; + e.deleted_at = 0; + UpdateOne(db, e); + return e; + } + + static CharacterEvolvingItems SoftDelete(Database& db, const uint64 id) + { + auto e = FindOne(db, id); + if (e.id == 0) { + return NewEntity(); + } + + e.deleted_at = time(nullptr); + UpdateOne(db, e); + return e; + } + + static bool UpdateCharID(Database &db, const uint64 id, const uint32 to_char_id) + { + auto e = FindOne(db, id); + if (e.id == 0) { + return false; + } + + e.character_id = to_char_id; + e.deleted_at = 0; + return UpdateOne(db, e); + } +}; + +#endif //EQEMU_CHARACTER_EVOLVING_ITEMS_REPOSITORY_H diff --git a/common/repositories/character_expedition_lockouts_repository.h b/common/repositories/character_expedition_lockouts_repository.h index bf16e49a4..54fcc8704 100644 --- a/common/repositories/character_expedition_lockouts_repository.h +++ b/common/repositories/character_expedition_lockouts_repository.h @@ -2,7 +2,7 @@ #define EQEMU_CHARACTER_EXPEDITION_LOCKOUTS_REPOSITORY_H #include "../database.h" -#include "../expedition_lockout_timer.h" +#include "../dynamic_zone_lockout.h" #include "../strings.h" #include "base/base_character_expedition_lockouts_repository.h" #include @@ -47,33 +47,8 @@ public: // Custom extended repository methods here - struct CharacterExpeditionLockoutsTimeStamp { - int id; - int character_id; - std::string expedition_name; - std::string event_name; - time_t expire_time; - int duration; - std::string from_expedition_uuid; - }; - - static ExpeditionLockoutTimer GetExpeditionLockoutTimerFromEntry( - CharacterExpeditionLockoutsTimeStamp&& entry) - { - ExpeditionLockoutTimer lockout_timer{ - std::move(entry.from_expedition_uuid), - std::move(entry.expedition_name), - std::move(entry.event_name), - static_cast(entry.expire_time), - static_cast(entry.duration) - }; - - return lockout_timer; - } - - static std::unordered_map> GetManyCharacterLockoutTimers( - Database& db, const std::vector& character_ids, - const std::string& expedition_name, const std::string& ordered_event_name) + static std::unordered_map> GetLockouts( + Database& db, const std::vector& char_ids, const std::string& expedition) { auto results = db.QueryDatabase(fmt::format(SQL( SELECT @@ -84,39 +59,171 @@ public: from_expedition_uuid FROM character_expedition_lockouts WHERE - character_id IN ({}) + character_id IN ({0}) AND expire_time > NOW() - AND expedition_name = '{}' + AND expedition_name = '{1}' ORDER BY - FIELD(character_id, {}), - FIELD(event_name, '{}') DESC + FIELD(character_id, {0}), + FIELD(event_name, '{2}') DESC ), - fmt::join(character_ids, ","), - Strings::Escape(expedition_name), - fmt::join(character_ids, ","), - Strings::Escape(ordered_event_name) + fmt::join(char_ids, ","), + Strings::Escape(expedition), + Strings::Escape(DzLockout::ReplayTimer) )); - std::unordered_map> lockouts; + std::unordered_map> lockouts; for (auto row = results.begin(); row != results.end(); ++row) { - CharacterExpeditionLockoutsTimeStamp entry{}; - int col = 0; - entry.character_id = std::strtoul(row[col++], nullptr, 10); - entry.expire_time = std::strtoull(row[col++], nullptr, 10); - entry.duration = std::strtoul(row[col++], nullptr, 10); - entry.event_name = row[col++]; - entry.expedition_name = expedition_name; - entry.from_expedition_uuid = row[col++]; + uint32_t char_id = std::strtoul(row[col++], nullptr, 10); + time_t expire_time = std::strtoull(row[col++], nullptr, 10); + uint32_t duration = std::strtoul(row[col++], nullptr, 10); + std::string event = row[col++]; + std::string uuid = row[col++]; - auto lockout = GetExpeditionLockoutTimerFromEntry(std::move(entry)); - lockouts[entry.character_id].emplace_back(std::move(lockout)); + lockouts[char_id].emplace_back(std::move(uuid), expedition, std::move(event), expire_time, duration); } return lockouts; } + + static std::vector GetLockouts(Database& db, uint32_t char_id) + { + std::vector lockouts; + + auto rows = GetWhere(db, fmt::format("character_id = {} AND expire_time > NOW()", char_id)); + lockouts.reserve(rows.size()); + + for (auto& row : rows) + { + lockouts.emplace_back( + std::move(row.from_expedition_uuid), + std::move(row.expedition_name), + std::move(row.event_name), + row.expire_time, + row.duration + ); + } + + return lockouts; + } + + static std::vector GetLockouts(Database& db, const std::vector& names, const std::string& expedition, const std::string& event) + { + if (names.empty()) + { + return {}; + } + + return GetWhere(db, fmt::format( + "character_id IN (select id from character_data where name IN ('{}')) AND expire_time > NOW() AND expedition_name = '{}' AND event_name = '{}' LIMIT 1", + fmt::join(names, "','"), Strings::Escape(expedition), Strings::Escape(event))); + } + + static void InsertLockouts(Database& db, uint32_t char_id, const std::vector& lockouts) + { + std::string insert_values; + for (const auto& lockout : lockouts) + { + fmt::format_to(std::back_inserter(insert_values), + "({}, FROM_UNIXTIME({}), {}, '{}', '{}', '{}'),", + char_id, + lockout.GetExpireTime(), + lockout.GetDuration(), + Strings::Escape(lockout.UUID()), + Strings::Escape(lockout.DzName()), + Strings::Escape(lockout.Event()) + ); + } + + if (!insert_values.empty()) + { + insert_values.pop_back(); // trailing comma + + auto query = fmt::format(SQL( + INSERT INTO character_expedition_lockouts + (character_id, expire_time, duration, from_expedition_uuid, expedition_name, event_name) + VALUES {} + ON DUPLICATE KEY UPDATE + from_expedition_uuid = VALUES(from_expedition_uuid), + expire_time = VALUES(expire_time), + duration = VALUES(duration); + ), insert_values); + + db.QueryDatabase(query); + } + } + + static void InsertLockout(Database& db, const std::vector& char_ids, const DzLockout& lockout) + { + std::string insert_values; + for (const auto& char_id : char_ids) + { + fmt::format_to(std::back_inserter(insert_values), + "({}, FROM_UNIXTIME({}), {}, '{}', '{}', '{}'),", + char_id, + lockout.GetExpireTime(), + lockout.GetDuration(), + Strings::Escape(lockout.UUID()), + Strings::Escape(lockout.DzName()), + Strings::Escape(lockout.Event()) + ); + } + + if (!insert_values.empty()) + { + insert_values.pop_back(); // trailing comma + + auto query = fmt::format(SQL( + INSERT INTO character_expedition_lockouts + (character_id, expire_time, duration, from_expedition_uuid, expedition_name, event_name) + VALUES {} + ON DUPLICATE KEY UPDATE + from_expedition_uuid = VALUES(from_expedition_uuid), + expire_time = VALUES(expire_time), + duration = VALUES(duration); + ), insert_values); + + db.QueryDatabase(query); + } + } + + // inserts a new lockout or updates existing lockout with seconds added to current time + static void AddLockoutDuration(Database& db, const std::vector& char_ids, const DzLockout& lockout, int seconds) + { + std::string insert_values; + for (const auto& char_id : char_ids) + { + fmt::format_to(std::back_inserter(insert_values), + "({}, FROM_UNIXTIME({}), {}, '{}', '{}', '{}'),", + char_id, + lockout.GetExpireTime(), + lockout.GetDuration(), + Strings::Escape(lockout.UUID()), + Strings::Escape(lockout.DzName()), + Strings::Escape(lockout.Event()) + ); + } + + if (!insert_values.empty()) + { + insert_values.pop_back(); // trailing comma + + auto query = fmt::format(SQL( + INSERT INTO character_expedition_lockouts + (character_id, expire_time, duration, from_expedition_uuid, expedition_name, event_name) + VALUES {0} + ON DUPLICATE KEY UPDATE + from_expedition_uuid = VALUES(from_expedition_uuid), + expire_time = DATE_ADD(expire_time, INTERVAL {1} SECOND), + duration = GREATEST(0, CAST(duration AS SIGNED) + {1}); + ), insert_values, seconds); + + db.QueryDatabase(query); + } + } + }; #endif //EQEMU_CHARACTER_EXPEDITION_LOCKOUTS_REPOSITORY_H diff --git a/common/repositories/character_pet_name_repository.h b/common/repositories/character_pet_name_repository.h new file mode 100644 index 000000000..8fbb1e94b --- /dev/null +++ b/common/repositories/character_pet_name_repository.h @@ -0,0 +1,13 @@ +#ifndef EQEMU_CHARACTER_PET_NAME_REPOSITORY_H +#define EQEMU_CHARACTER_PET_NAME_REPOSITORY_H + +#include "../database.h" +#include "../strings.h" +#include "base/base_character_pet_name_repository.h" + +class CharacterPetNameRepository: public BaseCharacterPetNameRepository { +public: + // Custom extended repository methods here +}; + +#endif //EQEMU_CHARACTER_PET_NAME_REPOSITORY_H diff --git a/common/repositories/command_subsettings_repository.h b/common/repositories/command_subsettings_repository.h index 7faaf76b1..aa99d164a 100644 --- a/common/repositories/command_subsettings_repository.h +++ b/common/repositories/command_subsettings_repository.h @@ -49,6 +49,7 @@ public: // these are the base definitions for command_subsettings and can be over-ridden by the database std::vector static_records = { {.parent_command = "find", .sub_command = "aa", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "findaa"}, + {.parent_command = "find", .sub_command = "account", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "findaccount"}, {.parent_command = "find", .sub_command = "body_type", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "findbodytype"}, {.parent_command = "find", .sub_command = "bug_category", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "findbugcategory"}, {.parent_command = "find", .sub_command = "character", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "findcharacter"}, @@ -142,6 +143,7 @@ public: {.parent_command = "show", .sub_command = "hatelist", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "hatelist"}, {.parent_command = "show", .sub_command = "inventory", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "peekinv"}, {.parent_command = "show", .sub_command = "ip_lookup", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "iplookup"}, + {.parent_command = "show", .sub_command = "keyring", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "showkeyring"}, {.parent_command = "show", .sub_command = "line_of_sight", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "checklos"}, {.parent_command = "show", .sub_command = "network", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "network"}, {.parent_command = "show", .sub_command = "network_stats", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "netstats"}, diff --git a/common/repositories/criteria/content_filter_criteria.h b/common/repositories/criteria/content_filter_criteria.h index 0883097d4..e53fcab77 100644 --- a/common/repositories/criteria/content_filter_criteria.h +++ b/common/repositories/criteria/content_filter_criteria.h @@ -14,7 +14,7 @@ namespace ContentFilterCriteria { table_prefix = table_prefix + "."; } - int current_expansion_filter_criteria = content_service.GetCurrentExpansion(); + int current_expansion_filter_criteria = WorldContentService::Instance()->GetCurrentExpansion(); if (current_expansion_filter_criteria == Expansion::EXPANSION_ALL) { current_expansion_filter_criteria = Expansion::EXPANSION_FILTER_MAX; } @@ -33,8 +33,8 @@ namespace ContentFilterCriteria { table_prefix ); - std::vector flags_disabled = content_service.GetContentFlagsDisabled(); - std::vector flags_enabled = content_service.GetContentFlagsEnabled(); + std::vector flags_disabled = WorldContentService::Instance()->GetContentFlagsDisabled(); + std::vector flags_enabled = WorldContentService::Instance()->GetContentFlagsEnabled(); std::string flags_in_filter_enabled; std::string flags_in_filter_disabled; if (!flags_enabled.empty()) { diff --git a/common/repositories/dynamic_zone_lockouts_repository.h b/common/repositories/dynamic_zone_lockouts_repository.h new file mode 100644 index 000000000..f7babc2f0 --- /dev/null +++ b/common/repositories/dynamic_zone_lockouts_repository.h @@ -0,0 +1,84 @@ +#ifndef EQEMU_DYNAMIC_ZONE_LOCKOUTS_REPOSITORY_H +#define EQEMU_DYNAMIC_ZONE_LOCKOUTS_REPOSITORY_H + +#include "../database.h" +#include "../strings.h" +#include "../dynamic_zone_lockout.h" +#include "base/base_dynamic_zone_lockouts_repository.h" + +class DynamicZoneLockoutsRepository: public BaseDynamicZoneLockoutsRepository { +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 + * + * DynamicZoneLockoutsRepository::GetByZoneAndVersion(int zone_id, int zone_version) + * DynamicZoneLockoutsRepository::GetWhereNeverExpires() + * DynamicZoneLockoutsRepository::GetWhereXAndY() + * DynamicZoneLockoutsRepository::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 InsertLockouts(Database& db, uint32_t dz_id, const std::vector& lockouts) + { + std::string insert_values; + for (const auto& lockout : lockouts) + { + fmt::format_to(std::back_inserter(insert_values), + "({}, '{}', '{}', FROM_UNIXTIME({}), {}),", + dz_id, + Strings::Escape(lockout.UUID()), + Strings::Escape(lockout.Event()), + lockout.GetExpireTime(), + lockout.GetDuration() + ); + } + + if (!insert_values.empty()) + { + insert_values.pop_back(); // trailing comma + + auto query = fmt::format(SQL( + INSERT INTO dynamic_zone_lockouts + (dynamic_zone_id, from_expedition_uuid, event_name, expire_time, duration) + VALUES {} + ON DUPLICATE KEY UPDATE + from_expedition_uuid = VALUES(from_expedition_uuid), + expire_time = VALUES(expire_time), + duration = VALUES(duration); + ), insert_values); + + db.QueryDatabase(query); + } + } + +}; + +#endif //EQEMU_DYNAMIC_ZONE_LOCKOUTS_REPOSITORY_H diff --git a/common/repositories/dynamic_zone_members_repository.h b/common/repositories/dynamic_zone_members_repository.h index b1afe41ca..51b5121f2 100644 --- a/common/repositories/dynamic_zone_members_repository.h +++ b/common/repositories/dynamic_zone_members_repository.h @@ -65,7 +65,7 @@ public: )); } - static std::vector GetAllWithNames(Database& db) + static std::vector AllWithNames(Database& db) { std::vector all_entries; @@ -146,65 +146,34 @@ public: static void RemoveMember(Database& db, uint32_t dynamic_zone_id, uint32_t character_id) { - db.QueryDatabase(fmt::format(SQL( - DELETE FROM {} - WHERE dynamic_zone_id = {} AND character_id = {}; - ), - TableName(), dynamic_zone_id, character_id - )); + DeleteWhere(db, fmt::format("dynamic_zone_id = {} AND character_id = {}", dynamic_zone_id, character_id)); } static void RemoveAllMembers(Database& db, uint32_t dynamic_zone_id) { - db.QueryDatabase(fmt::format(SQL( - DELETE FROM {} - WHERE dynamic_zone_id = {}; - ), - TableName(), dynamic_zone_id - )); + DeleteWhere(db, fmt::format("dynamic_zone_id = {}", dynamic_zone_id)); } - static void RemoveAllMembers(Database& db, std::vector dynamic_zone_ids) + static uint32_t InsertOrUpdateMany(Database& db, const std::vector& entries) { - if (!dynamic_zone_ids.empty()) + if (entries.empty()) { - db.QueryDatabase(fmt::format(SQL( - DELETE FROM {} - WHERE dynamic_zone_id IN ({}); - ), - TableName(), Strings::Join(dynamic_zone_ids, ",") - )); - } - } - - static int InsertOrUpdateMany(Database& db, - const std::vector& dynamic_zone_members_entries) - { - std::vector insert_chunks; - - for (auto &dynamic_zone_members_entry: dynamic_zone_members_entries) - { - std::vector insert_values; - - insert_values.push_back(std::to_string(dynamic_zone_members_entry.id)); - insert_values.push_back(std::to_string(dynamic_zone_members_entry.dynamic_zone_id)); - insert_values.push_back(std::to_string(dynamic_zone_members_entry.character_id)); - - insert_chunks.push_back("(" + Strings::Implode(",", insert_values) + ")"); + return 0; } - std::vector insert_values; + std::vector values; + values.reserve(entries.size()); - auto results = db.QueryDatabase( - fmt::format( - "INSERT INTO {} ({}) VALUES {} ON DUPLICATE KEY UPDATE id = id;", - TableName(), - ColumnsRaw(), - Strings::Implode(",", insert_chunks) - ) - ); + for (const auto& entry : entries) + { + values.push_back(fmt::format("({},{},{})", entry.id, entry.dynamic_zone_id, entry.character_id)); + } - return (results.Success() ? results.RowsAffected() : 0); + auto results = db.QueryDatabase(fmt::format( + "INSERT INTO {} ({}) VALUES {} ON DUPLICATE KEY UPDATE id = id;", + TableName(), ColumnsRaw(), fmt::join(values, ","))); + + return results.Success() ? results.RowsAffected() : 0; } }; diff --git a/common/repositories/dynamic_zones_repository.h b/common/repositories/dynamic_zones_repository.h index a64aef8cc..2065a10d5 100644 --- a/common/repositories/dynamic_zones_repository.h +++ b/common/repositories/dynamic_zones_repository.h @@ -70,6 +70,8 @@ public: float zone_in_z; float zone_in_heading; int has_zone_in; + int8_t is_locked; + int8_t add_replay; int zone; int version; int is_global; @@ -105,6 +107,8 @@ public: dynamic_zones.zone_in_z, dynamic_zones.zone_in_heading, dynamic_zones.has_zone_in, + dynamic_zones.is_locked, + dynamic_zones.add_replay, instance_list.zone, instance_list.version, instance_list.is_global, @@ -144,6 +148,8 @@ public: entry.zone_in_z = strtof(row[col++], nullptr); entry.zone_in_heading = strtof(row[col++], nullptr); entry.has_zone_in = strtol(row[col++], nullptr, 10) != 0; + entry.is_locked = static_cast(strtol(row[col++], nullptr, 10)); + entry.add_replay = static_cast(strtol(row[col++], nullptr, 10)); // from instance_list entry.zone = strtol(row[col++], nullptr, 10); entry.version = strtol(row[col++], nullptr, 10); @@ -244,6 +250,22 @@ public: } } + static void UpdateLocked(Database& db, uint32_t dz_id, bool lock) + { + if (dz_id != 0) + { + db.QueryDatabase(fmt::format("UPDATE dynamic_zones SET is_locked = {} WHERE id = {}", lock, dz_id)); + } + } + + static void UpdateReplayOnJoin(Database& db, uint32_t dz_id, bool enabled) + { + if (dz_id != 0) + { + db.QueryDatabase(fmt::format("UPDATE dynamic_zones SET add_replay = {} WHERE id = {}", enabled, dz_id)); + } + } + static void UpdateSwitchID(Database& db, uint32_t dz_id, int dz_switch_id) { if (dz_id != 0) @@ -351,6 +373,59 @@ public: return all_entries; } + + struct CharacterDz + { + uint32_t id; + std::string name; + uint32_t dz_id; + }; + + // get character ids with possible active dz id by type + static std::vector GetCharactersWithDz(Database& db, const std::vector& names, int type) + { + if (names.empty()) + { + return {}; + } + + std::vector entries; + entries.reserve(names.size()); + + auto results = db.QueryDatabase(fmt::format(SQL( + SELECT + character_data.id, + character_data.name, + MAX(dynamic_zones.id) + FROM character_data + LEFT JOIN dynamic_zone_members + ON character_data.id = dynamic_zone_members.character_id + LEFT JOIN dynamic_zones + ON dynamic_zone_members.dynamic_zone_id = dynamic_zones.id + AND dynamic_zones.`type` = {0} + WHERE character_data.name IN ('{1}') + GROUP BY character_data.id + ORDER BY FIELD(character_data.name, '{1}') + ), + type, + fmt::join(names, "','") + )); + + if (results.Success()) + { + for (auto row = results.begin(); row != results.end(); ++row) + { + CharacterDz entry{}; + entry.id = std::strtoul(row[0], nullptr, 10); + entry.name = row[1]; + entry.dz_id = row[2] ? std::strtoul(row[2], nullptr, 10) : 0; + + entries.push_back(std::move(entry)); + } + } + + return entries; + } }; #endif //EQEMU_DYNAMIC_ZONES_REPOSITORY_H diff --git a/common/repositories/expedition_lockouts_repository.h b/common/repositories/expedition_lockouts_repository.h deleted file mode 100644 index 4ccae0d39..000000000 --- a/common/repositories/expedition_lockouts_repository.h +++ /dev/null @@ -1,102 +0,0 @@ -#ifndef EQEMU_EXPEDITION_LOCKOUTS_REPOSITORY_H -#define EQEMU_EXPEDITION_LOCKOUTS_REPOSITORY_H - -#include "../database.h" -#include "../strings.h" -#include "base/base_expedition_lockouts_repository.h" - -class ExpeditionLockoutsRepository: public BaseExpeditionLockoutsRepository { -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 - * - * ExpeditionLockoutsRepository::GetByZoneAndVersion(int zone_id, int zone_version) - * ExpeditionLockoutsRepository::GetWhereNeverExpires() - * ExpeditionLockoutsRepository::GetWhereXAndY() - * ExpeditionLockoutsRepository::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 - - struct ExpeditionLockoutsWithTimestamp { - uint32_t id; - uint32_t expedition_id; - std::string event_name; - time_t expire_time; - int duration; - std::string from_expedition_uuid; - }; - - static std::vector GetWithTimestamp( - Database& db, const std::vector& expedition_ids) - { - if (expedition_ids.empty()) - { - return {}; - } - - std::vector all_entries; - - auto results = db.QueryDatabase(fmt::format(SQL( - SELECT - id, - expedition_id, - event_name, - UNIX_TIMESTAMP(expire_time), - duration, - from_expedition_uuid - FROM expedition_lockouts - WHERE expedition_id IN ({}) - ), - Strings::Join(expedition_ids, ",") - )); - - all_entries.reserve(results.RowCount()); - - for (auto row = results.begin(); row != results.end(); ++row) - { - ExpeditionLockoutsWithTimestamp entry{}; - - int col = 0; - entry.id = strtoul(row[col++], nullptr, 10); - entry.expedition_id = strtoul(row[col++], nullptr, 10); - entry.event_name = row[col++]; - entry.expire_time = strtoull(row[col++], nullptr, 10); - entry.duration = strtol(row[col++], nullptr, 10); - entry.from_expedition_uuid = row[col++]; - - all_entries.emplace_back(std::move(entry)); - } - - return all_entries; - } -}; - -#endif //EQEMU_EXPEDITION_LOCKOUTS_REPOSITORY_H diff --git a/common/repositories/expeditions_repository.h b/common/repositories/expeditions_repository.h deleted file mode 100644 index aab6acf5f..000000000 --- a/common/repositories/expeditions_repository.h +++ /dev/null @@ -1,134 +0,0 @@ -#ifndef EQEMU_EXPEDITIONS_REPOSITORY_H -#define EQEMU_EXPEDITIONS_REPOSITORY_H - -#include "../database.h" -#include "../strings.h" -#include "base/base_expeditions_repository.h" - -class ExpeditionsRepository: public BaseExpeditionsRepository { -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 - * - * ExpeditionsRepository::GetByZoneAndVersion(int zone_id, int zone_version) - * ExpeditionsRepository::GetWhereNeverExpires() - * ExpeditionsRepository::GetWhereXAndY() - * ExpeditionsRepository::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 - - struct CharacterExpedition - { - uint32_t id; - std::string name; - uint32_t expedition_id; - }; - - static std::vector GetCharactersWithExpedition( - Database& db, const std::vector& character_names) - { - if (character_names.empty()) - { - return {}; - } - - std::vector entries; - - auto joined_character_names = fmt::format("'{}'", Strings::Join(character_names, "','")); - - auto results = db.QueryDatabase(fmt::format(SQL( - SELECT - character_data.id, - character_data.name, - MAX(expeditions.id) - FROM character_data - LEFT JOIN dynamic_zone_members - ON character_data.id = dynamic_zone_members.character_id - LEFT JOIN expeditions - ON dynamic_zone_members.dynamic_zone_id = expeditions.dynamic_zone_id - WHERE character_data.name IN ({}) - GROUP BY character_data.id - ORDER BY FIELD(character_data.name, {}) - ), - joined_character_names, - joined_character_names - )); - - if (results.Success()) - { - entries.reserve(results.RowCount()); - - for (auto row = results.begin(); row != results.end(); ++row) - { - CharacterExpedition entry{}; - entry.id = std::strtoul(row[0], nullptr, 10); - entry.name = row[1]; - entry.expedition_id = row[2] ? std::strtoul(row[2], nullptr, 10) : 0; - - entries.emplace_back(std::move(entry)); - } - } - - return entries; - } - - static uint32_t GetIDByMemberID(Database& db, uint32_t character_id) - { - if (character_id == 0) - { - return 0; - } - - uint32_t expedition_id = 0; - - auto results = db.QueryDatabase(fmt::format(SQL( - SELECT - expeditions.id - FROM expeditions - INNER JOIN dynamic_zone_members - ON expeditions.dynamic_zone_id = dynamic_zone_members.dynamic_zone_id - WHERE - dynamic_zone_members.character_id = {} - ), - character_id - )); - - if (results.Success() && results.RowCount() > 0) - { - auto row = results.begin(); - expedition_id = std::strtoul(row[0], nullptr, 10); - } - - return expedition_id; - } -}; - -#endif //EQEMU_EXPEDITIONS_REPOSITORY_H diff --git a/common/repositories/instance_list_player_repository.h b/common/repositories/instance_list_player_repository.h index 8a8e31c0f..348ddc625 100644 --- a/common/repositories/instance_list_player_repository.h +++ b/common/repositories/instance_list_player_repository.h @@ -45,33 +45,26 @@ public: // Custom extended repository methods here - static int InsertOrUpdateMany(Database& db, - const std::vector& instance_list_player_entries) + static uint32_t InsertOrUpdateMany(Database& db, const std::vector& entries) { - std::vector insert_chunks; - - for (auto &instance_list_player_entry: instance_list_player_entries) + if (entries.empty()) { - std::vector insert_values; - - insert_values.push_back(std::to_string(instance_list_player_entry.id)); - insert_values.push_back(std::to_string(instance_list_player_entry.charid)); - - insert_chunks.push_back("(" + Strings::Implode(",", insert_values) + ")"); + return 0; } - std::vector insert_values; + std::vector values; + values.reserve(entries.size()); - auto results = db.QueryDatabase( - fmt::format( - "INSERT INTO {} ({}) VALUES {} ON DUPLICATE KEY UPDATE id = VALUES(id)", - TableName(), - ColumnsRaw(), - Strings::Implode(",", insert_chunks) - ) - ); + for (const auto& entry : entries) + { + values.push_back(fmt::format("({},{})", entry.id, entry.charid)); + } - return (results.Success() ? results.RowsAffected() : 0); + auto results = db.QueryDatabase(fmt::format( + "INSERT INTO {} ({}) VALUES {} ON DUPLICATE KEY UPDATE id = VALUES(id)", + TableName(), ColumnsRaw(), fmt::join(values, ","))); + + return results.Success() ? results.RowsAffected() : 0; } static bool ReplaceOne(Database& db, InstanceListPlayer e) diff --git a/common/repositories/instance_list_repository.h b/common/repositories/instance_list_repository.h index e177e5063..dbaf8d2a4 100644 --- a/common/repositories/instance_list_repository.h +++ b/common/repositories/instance_list_repository.h @@ -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(), diff --git a/common/repositories/items_evolving_details_repository.h b/common/repositories/items_evolving_details_repository.h new file mode 100644 index 000000000..906bb6095 --- /dev/null +++ b/common/repositories/items_evolving_details_repository.h @@ -0,0 +1,14 @@ +#ifndef EQEMU_ITEMS_EVOLVING_DETAILS_REPOSITORY_H +#define EQEMU_ITEMS_EVOLVING_DETAILS_REPOSITORY_H + +#include "../database.h" +#include "../strings.h" +#include "base/base_items_evolving_details_repository.h" + +class ItemsEvolvingDetailsRepository: public BaseItemsEvolvingDetailsRepository { +public: + // Custom extended repository methods here + +}; + +#endif //EQEMU_ITEMS_EVOLVING_DETAILS_REPOSITORY_H diff --git a/common/repositories/items_repository.h b/common/repositories/items_repository.h index 919c11dd3..4e5a8868e 100644 --- a/common/repositories/items_repository.h +++ b/common/repositories/items_repository.h @@ -7,6 +7,14 @@ class ItemsRepository: public BaseItemsRepository { public: + struct Bazaar_Results { + uint32 item_id; + std::string name; + bool stackable; + uint32 icon; + uint32 stats; + }; + static std::vector GetItemIDsBySearchCriteria( Database& db, std::string search_string, @@ -37,6 +45,51 @@ public: return item_id_list; } + static std::unordered_map GetItemsForBazaarSearch( + Database& db, + const std::vector &search_ids, + const std::string &name, + const std::string &field_criteria_items, + const std::string &where_criteria_items, + const uint32 query_limit = 0 + ) + { + auto query = fmt::format( + "SELECT id, name, stackable, icon, {} " + "FROM items " + "WHERE `name` LIKE '%%{}%%' AND {} AND id IN({}) " + "ORDER BY id ASC", + field_criteria_items, + Strings::Escape(name), + where_criteria_items, + Strings::Implode(",", search_ids) + ); + + if (query_limit >= 1) { + query += fmt::format(" LIMIT {}", query_limit); + } + + std::unordered_map item_list; + + auto results = db.QueryDatabase(query); + if (!results.Success() || !results.RowCount()) { + return item_list; + } + + item_list.reserve(results.RowCount()); + for (auto row : results) { + Bazaar_Results br{}; + br.item_id = row[0] ? static_cast(atoi(row[0])) : 0; + br.name = row[1] ? row[1] : ""; + br.stackable = atoi(row[2]) ? true : false; + br.icon = row[3] ? static_cast(atoi(row[3])) : 0; + br.stats = row[4] ? static_cast(atoi(row[4])) : 0; + + item_list.emplace(br.item_id, br); + } + + return item_list; + } }; diff --git a/common/repositories/login_accounts_repository.h b/common/repositories/login_accounts_repository.h index 2ad6797ae..052f4fb16 100644 --- a/common/repositories/login_accounts_repository.h +++ b/common/repositories/login_accounts_repository.h @@ -4,47 +4,94 @@ #include "../database.h" #include "../strings.h" #include "base/base_login_accounts_repository.h" +#include "../../loginserver/encryption.h" +#include "../../loginserver/login_types.h" -class LoginAccountsRepository: public BaseLoginAccountsRepository { +class LoginAccountsRepository : public BaseLoginAccountsRepository { public: + static int64 GetFreeID(Database &db, const std::string &loginserver) + { + auto query = fmt::format( + "SELECT IFNULL(MAX(id), 0) + 1 FROM login_accounts WHERE source_loginserver = '{}'", + Strings::Escape(loginserver) + ); - /** - * 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 - * - * LoginAccountsRepository::GetByZoneAndVersion(int zone_id, int zone_version) - * LoginAccountsRepository::GetWhereNeverExpires() - * LoginAccountsRepository::GetWhereXAndY() - * LoginAccountsRepository::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 - */ + auto results = db.QueryDatabase(query); + if (!results.Success() || results.RowCount() != 1) { + return 0; + } - // Custom extended repository methods here + auto row = results.begin(); + return Strings::ToUnsignedInt(row[0]); + } + + static LoginAccountsRepository::LoginAccounts CreateAccountFromContext( + Database &db, + LoginAccountContext c + ) + { + auto a = LoginAccountsRepository::NewEntity(); + + if (!c.password_is_encrypted) { + auto e = EncryptPasswordFromContext(c); + a.account_password = e.password; + } + + a.id = c.login_account_id > 0 ? c.login_account_id : GetFreeID(db, c.source_loginserver); + a.account_name = c.username; + a.account_email = !c.email.empty() ? c.email : "local_creation"; + a.source_loginserver = c.source_loginserver; + a.last_ip_address = "127.0.0.1"; + a.last_login_date = std::time(nullptr); + a.created_at = std::time(nullptr); + LoginAccountsRepository::InsertOne(db, a); + + return GetAccountFromContext(db, c).id > 0 ? a : NewEntity(); + } + + static LoginAccountsRepository::LoginAccounts GetAccountFromContext( + Database &db, + LoginAccountContext c + ) + { + std::string where = fmt::format( + "account_name = '{}' AND source_loginserver = '{}'", + Strings::Escape(c.username), + Strings::Escape(c.source_loginserver) + ); + + if (!c.email.empty()) { + where += fmt::format(" AND account_email = '{}'", Strings::Escape(c.email)); + } + if (c.login_account_id > 0) { + where += fmt::format(" AND id = {}", c.login_account_id); + } + + where += " LIMIT 1"; + + auto results = LoginAccountsRepository::GetWhere(db, where); + + auto e = LoginAccountsRepository::NewEntity(); + if (results.size() == 1) { + e = results.front(); + } + + return e; + } + + static LoginAccounts UpdateAccountPassword(Database &db, LoginAccounts a, std::string password) + { + LoginAccountContext c; + c.username = a.account_name; + c.password = password; + auto e = EncryptPasswordFromContext(c); + a.account_password = e.password; + + int success = LoginAccountsRepository::UpdateOne(db, a); + + return success ? a : NewEntity(); + } }; #endif //EQEMU_LOGIN_ACCOUNTS_REPOSITORY_H diff --git a/common/repositories/login_server_admins_repository.h b/common/repositories/login_server_admins_repository.h index 8afda242c..f8b126a60 100644 --- a/common/repositories/login_server_admins_repository.h +++ b/common/repositories/login_server_admins_repository.h @@ -5,46 +5,24 @@ #include "../strings.h" #include "base/base_login_server_admins_repository.h" -class LoginServerAdminsRepository: public BaseLoginServerAdminsRepository { +class LoginServerAdminsRepository : public BaseLoginServerAdminsRepository { public: + static LoginServerAdmins GetByName(Database &db, std::string account_name) + { + auto admins = GetWhere( + db, + fmt::format( + "account_name = '{}' LIMIT 1", + Strings::Escape(account_name) + ) + ); - /** - * 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 - * - * LoginServerAdminsRepository::GetByZoneAndVersion(int zone_id, int zone_version) - * LoginServerAdminsRepository::GetWhereNeverExpires() - * LoginServerAdminsRepository::GetWhereXAndY() - * LoginServerAdminsRepository::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 + if (admins.size() == 1) { + return admins.front(); + } + return NewEntity(); + } }; #endif //EQEMU_LOGIN_SERVER_ADMINS_REPOSITORY_H diff --git a/common/repositories/login_world_servers_repository.h b/common/repositories/login_world_servers_repository.h index 8cda28e32..50516686c 100644 --- a/common/repositories/login_world_servers_repository.h +++ b/common/repositories/login_world_servers_repository.h @@ -7,44 +7,27 @@ class LoginWorldServersRepository: public BaseLoginWorldServersRepository { public: + static LoginWorldServers GetFromWorldContext(Database &db, LoginWorldContext c) { + std::string where = fmt::format( + "short_name = '{}' AND long_name = '{}'", + Strings::Escape(c.short_name), + Strings::Escape(c.long_name) + ); - /** - * 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 - * - * LoginWorldServersRepository::GetByZoneAndVersion(int zone_id, int zone_version) - * LoginWorldServersRepository::GetWhereNeverExpires() - * LoginWorldServersRepository::GetWhereXAndY() - * LoginWorldServersRepository::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 - */ + if (c.admin_id > 0) { + where += fmt::format(" AND login_server_admin_id = {}", c.admin_id); + } - // Custom extended repository methods here + where += " LIMIT 1"; + auto results = GetWhere(db, where); + auto e = NewEntity(); + if (results.size() == 1) { + e = results.front(); + } + + return e; + } }; #endif //EQEMU_LOGIN_WORLD_SERVERS_REPOSITORY_H diff --git a/common/repositories/player_event_aa_purchase_repository.h b/common/repositories/player_event_aa_purchase_repository.h new file mode 100644 index 000000000..189c6c619 --- /dev/null +++ b/common/repositories/player_event_aa_purchase_repository.h @@ -0,0 +1,13 @@ +#ifndef EQEMU_PLAYER_EVENT_AA_PURCHASE_REPOSITORY_H +#define EQEMU_PLAYER_EVENT_AA_PURCHASE_REPOSITORY_H + +#include "../database.h" +#include "../strings.h" +#include "base/base_player_event_aa_purchase_repository.h" + +class PlayerEventAaPurchaseRepository: public BasePlayerEventAaPurchaseRepository { +public: + // Custom extended repository methods here +}; + +#endif //EQEMU_PLAYER_EVENT_AA_PURCHASE_REPOSITORY_H diff --git a/common/repositories/player_event_killed_named_npc_repository.h b/common/repositories/player_event_killed_named_npc_repository.h new file mode 100644 index 000000000..bd93fa8ce --- /dev/null +++ b/common/repositories/player_event_killed_named_npc_repository.h @@ -0,0 +1,13 @@ +#ifndef EQEMU_PLAYER_EVENT_KILLED_NAMED_NPC_REPOSITORY_H +#define EQEMU_PLAYER_EVENT_KILLED_NAMED_NPC_REPOSITORY_H + +#include "../database.h" +#include "../strings.h" +#include "base/base_player_event_killed_named_npc_repository.h" + +class PlayerEventKilledNamedNpcRepository: public BasePlayerEventKilledNamedNpcRepository { +public: + // Custom extended repository methods here +}; + +#endif //EQEMU_PLAYER_EVENT_KILLED_NAMED_NPC_REPOSITORY_H diff --git a/common/repositories/player_event_killed_npc_repository.h b/common/repositories/player_event_killed_npc_repository.h new file mode 100644 index 000000000..fcbacf7c5 --- /dev/null +++ b/common/repositories/player_event_killed_npc_repository.h @@ -0,0 +1,13 @@ +#ifndef EQEMU_PLAYER_EVENT_KILLED_NPC_REPOSITORY_H +#define EQEMU_PLAYER_EVENT_KILLED_NPC_REPOSITORY_H + +#include "../database.h" +#include "../strings.h" +#include "base/base_player_event_killed_npc_repository.h" + +class PlayerEventKilledNpcRepository: public BasePlayerEventKilledNpcRepository { +public: + // Custom extended repository methods here +}; + +#endif //EQEMU_PLAYER_EVENT_KILLED_NPC_REPOSITORY_H diff --git a/common/repositories/player_event_killed_raid_npc_repository.h b/common/repositories/player_event_killed_raid_npc_repository.h new file mode 100644 index 000000000..8b4002c7b --- /dev/null +++ b/common/repositories/player_event_killed_raid_npc_repository.h @@ -0,0 +1,13 @@ +#ifndef EQEMU_PLAYER_EVENT_KILLED_RAID_NPC_REPOSITORY_H +#define EQEMU_PLAYER_EVENT_KILLED_RAID_NPC_REPOSITORY_H + +#include "../database.h" +#include "../strings.h" +#include "base/base_player_event_killed_raid_npc_repository.h" + +class PlayerEventKilledRaidNpcRepository: public BasePlayerEventKilledRaidNpcRepository { +public: + // Custom extended repository methods here +}; + +#endif //EQEMU_PLAYER_EVENT_KILLED_RAID_NPC_REPOSITORY_H diff --git a/common/repositories/player_event_log_settings_repository.h b/common/repositories/player_event_log_settings_repository.h index 770db7c3f..10aebc135 100644 --- a/common/repositories/player_event_log_settings_repository.h +++ b/common/repositories/player_event_log_settings_repository.h @@ -2,49 +2,13 @@ #define EQEMU_PLAYER_EVENT_LOG_SETTINGS_REPOSITORY_H #include "../database.h" +#include "../events/player_events.h" #include "../strings.h" #include "base/base_player_event_log_settings_repository.h" +#include "base/base_player_event_loot_items_repository.h" class PlayerEventLogSettingsRepository: public BasePlayerEventLogSettingsRepository { 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 - * - * PlayerEventLogSettingsRepository::GetByZoneAndVersion(int zone_id, int zone_version) - * PlayerEventLogSettingsRepository::GetWhereNeverExpires() - * PlayerEventLogSettingsRepository::GetWhereXAndY() - * PlayerEventLogSettingsRepository::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 - }; - #endif //EQEMU_PLAYER_EVENT_LOG_SETTINGS_REPOSITORY_H diff --git a/common/repositories/player_event_logs_repository.h b/common/repositories/player_event_logs_repository.h index 97a6d6431..59de5e46c 100644 --- a/common/repositories/player_event_logs_repository.h +++ b/common/repositories/player_event_logs_repository.h @@ -7,44 +7,7 @@ class PlayerEventLogsRepository: public BasePlayerEventLogsRepository { 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 - * - * PlayerEventLogsRepository::GetByZoneAndVersion(int zone_id, int zone_version) - * PlayerEventLogsRepository::GetWhereNeverExpires() - * PlayerEventLogsRepository::GetWhereXAndY() - * PlayerEventLogsRepository::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 - }; #endif //EQEMU_PLAYER_EVENT_LOGS_REPOSITORY_H diff --git a/common/repositories/player_event_loot_items_repository.h b/common/repositories/player_event_loot_items_repository.h new file mode 100644 index 000000000..5767d1fb4 --- /dev/null +++ b/common/repositories/player_event_loot_items_repository.h @@ -0,0 +1,13 @@ +#ifndef EQEMU_PLAYER_EVENT_LOOT_ITEMS_REPOSITORY_H +#define EQEMU_PLAYER_EVENT_LOOT_ITEMS_REPOSITORY_H + +#include "../database.h" +#include "../strings.h" +#include "base/base_player_event_loot_items_repository.h" + +class PlayerEventLootItemsRepository: public BasePlayerEventLootItemsRepository { +public: + // Custom extended repository methods here +}; + +#endif //EQEMU_PLAYER_EVENT_LOOT_ITEMS_REPOSITORY_H diff --git a/common/repositories/player_event_merchant_purchase_repository.h b/common/repositories/player_event_merchant_purchase_repository.h new file mode 100644 index 000000000..9be7aef6a --- /dev/null +++ b/common/repositories/player_event_merchant_purchase_repository.h @@ -0,0 +1,13 @@ +#ifndef EQEMU_PLAYER_EVENT_MERCHANT_PURCHASE_REPOSITORY_H +#define EQEMU_PLAYER_EVENT_MERCHANT_PURCHASE_REPOSITORY_H + +#include "../database.h" +#include "../strings.h" +#include "base/base_player_event_merchant_purchase_repository.h" + +class PlayerEventMerchantPurchaseRepository: public BasePlayerEventMerchantPurchaseRepository { +public: + // Custom extended repository methods here +}; + +#endif //EQEMU_PLAYER_EVENT_MERCHANT_PURCHASE_REPOSITORY_H diff --git a/common/repositories/player_event_merchant_sell_repository.h b/common/repositories/player_event_merchant_sell_repository.h new file mode 100644 index 000000000..c93131b7a --- /dev/null +++ b/common/repositories/player_event_merchant_sell_repository.h @@ -0,0 +1,13 @@ +#ifndef EQEMU_PLAYER_EVENT_MERCHANT_SELL_REPOSITORY_H +#define EQEMU_PLAYER_EVENT_MERCHANT_SELL_REPOSITORY_H + +#include "../database.h" +#include "../strings.h" +#include "base/base_player_event_merchant_sell_repository.h" + +class PlayerEventMerchantSellRepository: public BasePlayerEventMerchantSellRepository { +public: + // Custom extended repository methods here +}; + +#endif //EQEMU_PLAYER_EVENT_MERCHANT_SELL_REPOSITORY_H diff --git a/common/repositories/player_event_npc_handin_entries_repository.h b/common/repositories/player_event_npc_handin_entries_repository.h new file mode 100644 index 000000000..f3c6d3bbe --- /dev/null +++ b/common/repositories/player_event_npc_handin_entries_repository.h @@ -0,0 +1,13 @@ +#ifndef EQEMU_PLAYER_EVENT_NPC_HANDIN_ENTRIES_REPOSITORY_H +#define EQEMU_PLAYER_EVENT_NPC_HANDIN_ENTRIES_REPOSITORY_H + +#include "../database.h" +#include "../strings.h" +#include "base/base_player_event_npc_handin_entries_repository.h" + +class PlayerEventNpcHandinEntriesRepository: public BasePlayerEventNpcHandinEntriesRepository { +public: + // Custom extended repository methods here +}; + +#endif //EQEMU_PLAYER_EVENT_NPC_HANDIN_ENTRIES_REPOSITORY_H diff --git a/common/repositories/player_event_npc_handin_repository.h b/common/repositories/player_event_npc_handin_repository.h new file mode 100644 index 000000000..2b3e23386 --- /dev/null +++ b/common/repositories/player_event_npc_handin_repository.h @@ -0,0 +1,13 @@ +#ifndef EQEMU_PLAYER_EVENT_NPC_HANDIN_REPOSITORY_H +#define EQEMU_PLAYER_EVENT_NPC_HANDIN_REPOSITORY_H + +#include "../database.h" +#include "../strings.h" +#include "base/base_player_event_npc_handin_repository.h" + +class PlayerEventNpcHandinRepository: public BasePlayerEventNpcHandinRepository { +public: + // Custom extended repository methods here +}; + +#endif //EQEMU_PLAYER_EVENT_NPC_HANDIN_REPOSITORY_H diff --git a/common/repositories/player_event_speech_repository.h b/common/repositories/player_event_speech_repository.h new file mode 100644 index 000000000..22f9158fb --- /dev/null +++ b/common/repositories/player_event_speech_repository.h @@ -0,0 +1,13 @@ +#ifndef EQEMU_PLAYER_EVENT_SPEECH_REPOSITORY_H +#define EQEMU_PLAYER_EVENT_SPEECH_REPOSITORY_H + +#include "../database.h" +#include "../strings.h" +#include "base/base_player_event_speech_repository.h" + +class PlayerEventSpeechRepository: public BasePlayerEventSpeechRepository { +public: + // Custom extended repository methods here +}; + +#endif //EQEMU_PLAYER_EVENT_SPEECH_REPOSITORY_H diff --git a/common/repositories/player_event_trade_entries_repository.h b/common/repositories/player_event_trade_entries_repository.h new file mode 100644 index 000000000..775d83370 --- /dev/null +++ b/common/repositories/player_event_trade_entries_repository.h @@ -0,0 +1,13 @@ +#ifndef EQEMU_PLAYER_EVENT_TRADE_ENTRIES_REPOSITORY_H +#define EQEMU_PLAYER_EVENT_TRADE_ENTRIES_REPOSITORY_H + +#include "../database.h" +#include "../strings.h" +#include "base/base_player_event_trade_entries_repository.h" + +class PlayerEventTradeEntriesRepository: public BasePlayerEventTradeEntriesRepository { +public: + // Custom extended repository methods here +}; + +#endif //EQEMU_PLAYER_EVENT_TRADE_ENTRIES_REPOSITORY_H diff --git a/common/repositories/player_event_trade_repository.h b/common/repositories/player_event_trade_repository.h new file mode 100644 index 000000000..7813e6fcd --- /dev/null +++ b/common/repositories/player_event_trade_repository.h @@ -0,0 +1,13 @@ +#ifndef EQEMU_PLAYER_EVENT_TRADE_REPOSITORY_H +#define EQEMU_PLAYER_EVENT_TRADE_REPOSITORY_H + +#include "../database.h" +#include "../strings.h" +#include "base/base_player_event_trade_repository.h" + +class PlayerEventTradeRepository: public BasePlayerEventTradeRepository { +public: + // Custom extended repository methods here +}; + +#endif //EQEMU_PLAYER_EVENT_TRADE_REPOSITORY_H diff --git a/common/repositories/respawn_times_repository.h b/common/repositories/respawn_times_repository.h index 5eada10f7..3f2ddfa8b 100644 --- a/common/repositories/respawn_times_repository.h +++ b/common/repositories/respawn_times_repository.h @@ -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 diff --git a/common/repositories/rule_values_repository.h b/common/repositories/rule_values_repository.h index 072175d5f..d7eb1530e 100644 --- a/common/repositories/rule_values_repository.h +++ b/common/repositories/rule_values_repository.h @@ -43,6 +43,47 @@ public: * method and encapsulate filters there */ + template + static std::vector join_tuple( + const std::string &glue, + const std::pair &encapsulation, + const std::vector> &src + ) + { + if (src.empty()) { + return {}; + } + + std::vector output; + + for (const std::tuple &src_iter: src) { + + output.emplace_back( + + fmt::format( + "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", + encapsulation.first, + std::get<0>(src_iter), + encapsulation.second, + glue, + encapsulation.first, + std::get<1>(src_iter), + encapsulation.second, + glue, + encapsulation.first, + std::get<2>(src_iter), + encapsulation.second, + glue, + encapsulation.first, + std::get<3>(src_iter), + encapsulation.second + ) + ); + } + + return output; + } + // Custom extended repository methods here static std::vector GetRuleNames(Database &db, int rule_set_id) { @@ -87,12 +128,28 @@ public: return v; } + template + static std::string + ImplodePair(const std::string &glue, const std::pair &encapsulation, const std::vector &src) + { + if (src.empty()) { + return {}; + } + std::ostringstream oss; + for (const T &src_iter: src) { + oss << encapsulation.first << src_iter << encapsulation.second << glue; + } + std::string output(oss.str()); + output.resize(output.size() - glue.size()); + return output; + } + static bool DeleteOrphanedRules(Database& db, std::vector& v) { const auto query = fmt::format( "DELETE FROM {} WHERE rule_name IN ({})", TableName(), - Strings::ImplodePair(",", std::pair('\'', '\''), v) + ImplodePair(",", std::pair('\'', '\''), v) ); return db.QueryDatabase(query).Success(); @@ -103,7 +160,7 @@ public: const auto query = fmt::format( "REPLACE INTO {} (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES {}", TableName(), - Strings::ImplodePair( + ImplodePair( ",", std::pair('(', ')'), join_tuple(",", std::pair('\'', '\''), v) diff --git a/common/repositories/sharedbank_repository.h b/common/repositories/sharedbank_repository.h index 15243d83e..d0aa47eaf 100644 --- a/common/repositories/sharedbank_repository.h +++ b/common/repositories/sharedbank_repository.h @@ -3,310 +3,47 @@ #include "../database.h" #include "../strings.h" +#include "base/base_sharedbank_repository.h" -class SharedbankRepository { +class SharedbankRepository: public BaseSharedbankRepository { public: - struct Sharedbank { - int acctid; - int slotid; - int itemid; - int16 charges; - int augslot1; - int augslot2; - int augslot3; - int augslot4; - int augslot5; - int augslot6; - std::string custom_data; - }; - static std::string PrimaryKey() - { - return std::string(""); - } + /** + * 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 + * + * SharedbankRepository::GetByZoneAndVersion(int zone_id, int zone_version) + * SharedbankRepository::GetWhereNeverExpires() + * SharedbankRepository::GetWhereXAndY() + * SharedbankRepository::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 + */ - static std::vector Columns() - { - return { - "acctid", - "slotid", - "itemid", - "charges", - "augslot1", - "augslot2", - "augslot3", - "augslot4", - "augslot5", - "augslot6", - "custom_data", - }; - } - - static std::string ColumnsRaw() - { - return std::string(Strings::Implode(", ", Columns())); - } - - static std::string InsertColumnsRaw() - { - std::vector insert_columns; - - for (auto &column : Columns()) { - if (column == PrimaryKey()) { - continue; - } - - insert_columns.push_back(column); - } - - return std::string(Strings::Implode(", ", insert_columns)); - } - - static std::string TableName() - { - return std::string("sharedbank"); - } - - static std::string BaseSelect() - { - return fmt::format( - "SELECT {} FROM {}", - ColumnsRaw(), - TableName() - ); - } - - static std::string BaseInsert() - { - return fmt::format( - "INSERT INTO {} ({}) ", - TableName(), - InsertColumnsRaw() - ); - } - - static Sharedbank NewEntity() - { - Sharedbank entry{}; - - entry.acctid = 0; - entry.slotid = 0; - entry.itemid = 0; - entry.charges = 0; - entry.augslot1 = 0; - entry.augslot2 = 0; - entry.augslot3 = 0; - entry.augslot4 = 0; - entry.augslot5 = 0; - entry.augslot6 = 0; - entry.custom_data = 0; - - return entry; - } - - static Sharedbank GetSharedbankEntry( - const std::vector &sharedbanks, - int sharedbank_id - ) - { - for (auto &sharedbank : sharedbanks) { - if (sharedbank. == sharedbank_id) { - return sharedbank; - } - } - - return NewEntity(); - } - - static Sharedbank FindOne( - int sharedbank_id - ) - { - auto results = database.QueryDatabase( - fmt::format( - "{} WHERE id = {} LIMIT 1", - BaseSelect(), - sharedbank_id - ) - ); - - auto row = results.begin(); - if (results.RowCount() == 1) { - Sharedbank entry{}; - - entry.acctid = atoi(row[0]); - entry.slotid = atoi(row[1]); - entry.itemid = atoi(row[2]); - entry.charges = atoi(row[3]); - entry.augslot1 = atoi(row[4]); - entry.augslot2 = atoi(row[5]); - entry.augslot3 = atoi(row[6]); - entry.augslot4 = atoi(row[7]); - entry.augslot5 = atoi(row[8]); - entry.augslot6 = atoi(row[9]); - entry.custom_data = row[10]; - - return entry; - } - - return NewEntity(); - } - - static int DeleteOne( - int sharedbank_id - ) - { - auto results = database.QueryDatabase( - fmt::format( - "DELETE FROM {} WHERE {} = {}", - TableName(), - PrimaryKey(), - sharedbank_id - ) - ); - - return (results.Success() ? results.RowsAffected() : 0); - } - - static int UpdateOne( - Sharedbank sharedbank_entry - ) - { - std::vector update_values; - - auto columns = Columns(); - - update_values.push_back(columns[0] + " = " + std::to_string(sharedbank_entry.acctid)); - update_values.push_back(columns[1] + " = " + std::to_string(sharedbank_entry.slotid)); - update_values.push_back(columns[2] + " = " + std::to_string(sharedbank_entry.itemid)); - update_values.push_back(columns[3] + " = " + std::to_string(sharedbank_entry.charges)); - update_values.push_back(columns[4] + " = " + std::to_string(sharedbank_entry.augslot1)); - update_values.push_back(columns[5] + " = " + std::to_string(sharedbank_entry.augslot2)); - update_values.push_back(columns[6] + " = " + std::to_string(sharedbank_entry.augslot3)); - update_values.push_back(columns[7] + " = " + std::to_string(sharedbank_entry.augslot4)); - update_values.push_back(columns[8] + " = " + std::to_string(sharedbank_entry.augslot5)); - update_values.push_back(columns[9] + " = " + std::to_string(sharedbank_entry.augslot6)); - update_values.push_back(columns[10] + " = '" + Strings::Escape(sharedbank_entry.custom_data) + "'"); - - auto results = database.QueryDatabase( - fmt::format( - "UPDATE {} SET {} WHERE {} = {}", - TableName(), - Strings::Implode(", ", update_values), - PrimaryKey(), - sharedbank_entry. - ) - ); - - return (results.Success() ? results.RowsAffected() : 0); - } - - static Sharedbank InsertOne( - Sharedbank sharedbank_entry - ) - { - std::vector insert_values; - - insert_values.push_back(std::to_string(sharedbank_entry.acctid)); - insert_values.push_back(std::to_string(sharedbank_entry.slotid)); - insert_values.push_back(std::to_string(sharedbank_entry.itemid)); - insert_values.push_back(std::to_string(sharedbank_entry.charges)); - insert_values.push_back(std::to_string(sharedbank_entry.augslot1)); - insert_values.push_back(std::to_string(sharedbank_entry.augslot2)); - insert_values.push_back(std::to_string(sharedbank_entry.augslot3)); - insert_values.push_back(std::to_string(sharedbank_entry.augslot4)); - insert_values.push_back(std::to_string(sharedbank_entry.augslot5)); - insert_values.push_back(std::to_string(sharedbank_entry.augslot6)); - insert_values.push_back("'" + Strings::Escape(sharedbank_entry.custom_data) + "'"); - - auto results = database.QueryDatabase( - fmt::format( - "{} VALUES ({})", - BaseInsert(), - Strings::Implode(",", insert_values) - ) - ); - - if (results.Success()) { - sharedbank_entry.id = results.LastInsertedID(); - return sharedbank_entry; - } - - sharedbank_entry = InstanceListRepository::NewEntity(); - - return sharedbank_entry; - } - - static int InsertMany( - std::vector sharedbank_entries - ) - { - std::vector insert_chunks; - - for (auto &sharedbank_entry: sharedbank_entries) { - std::vector insert_values; - - insert_values.push_back(std::to_string(sharedbank_entry.acctid)); - insert_values.push_back(std::to_string(sharedbank_entry.slotid)); - insert_values.push_back(std::to_string(sharedbank_entry.itemid)); - insert_values.push_back(std::to_string(sharedbank_entry.charges)); - insert_values.push_back(std::to_string(sharedbank_entry.augslot1)); - insert_values.push_back(std::to_string(sharedbank_entry.augslot2)); - insert_values.push_back(std::to_string(sharedbank_entry.augslot3)); - insert_values.push_back(std::to_string(sharedbank_entry.augslot4)); - insert_values.push_back(std::to_string(sharedbank_entry.augslot5)); - insert_values.push_back(std::to_string(sharedbank_entry.augslot6)); - insert_values.push_back("'" + Strings::Escape(sharedbank_entry.custom_data) + "'"); - - insert_chunks.push_back("(" + Strings::Implode(",", insert_values) + ")"); - } - - std::vector insert_values; - - auto results = database.QueryDatabase( - fmt::format( - "{} VALUES {}", - BaseInsert(), - Strings::Implode(",", insert_chunks) - ) - ); - - return (results.Success() ? results.RowsAffected() : 0); - } - - static std::vector All() - { - std::vector all_entries; - - auto results = database.QueryDatabase( - fmt::format( - "{}", - BaseSelect() - ) - ); - - all_entries.reserve(results.RowCount()); - - for (auto row = results.begin(); row != results.end(); ++row) { - Sharedbank entry{}; - - entry.acctid = atoi(row[0]); - entry.slotid = atoi(row[1]); - entry.itemid = atoi(row[2]); - entry.charges = atoi(row[3]); - entry.augslot1 = atoi(row[4]); - entry.augslot2 = atoi(row[5]); - entry.augslot3 = atoi(row[6]); - entry.augslot4 = atoi(row[7]); - entry.augslot5 = atoi(row[8]); - entry.augslot6 = atoi(row[9]); - entry.custom_data = row[10]; - - all_entries.push_back(entry); - } - - return all_entries; - } + // Custom extended repository methods here }; diff --git a/common/repositories/trader_repository.h b/common/repositories/trader_repository.h index 6c6ab35d2..0354d6437 100644 --- a/common/repositories/trader_repository.h +++ b/common/repositories/trader_repository.h @@ -12,10 +12,12 @@ class TraderRepository : public BaseTraderRepository { public: + static constexpr uint32 TRADER_CONVERT_ID = 4000000000; struct DistinctTraders_Struct { uint32 trader_id; uint32 zone_id; + uint32 zone_instance_id; uint32 entity_id; std::string trader_name; }; @@ -26,6 +28,11 @@ public: std::vector traders{}; }; + struct BazaarTraderSearch_Struct { + Trader trader; + std::string trader_name; + }; + struct WelcomeData_Struct { uint32 count_of_traders; uint32 count_of_items; @@ -35,29 +42,53 @@ public: GetBazaarSearchResults( SharedDatabase &db, BazaarSearchCriteria_Struct search, - uint32 char_zone_id + uint32 char_zone_id, + int32 char_zone_instance_id ); - static BulkTraders_Struct GetDistinctTraders(Database &db) + static BulkTraders_Struct GetDistinctTraders( + Database &db, + uint32 char_zone_instance_id, + uint32 max_results = std::numeric_limits::max() + ) { BulkTraders_Struct all_entries{}; std::vector distinct_traders; + MySQLRequestResult results; - auto results = db.QueryDatabase( - "SELECT DISTINCT(t.char_id), t.char_zone_id, t.char_entity_id, c.name " - "FROM trader AS t " - "JOIN character_data AS c ON t.char_id = c.id;" - ); + if (RuleB(Bazaar, UseAlternateBazaarSearch)) { + results = db.QueryDatabase(fmt::format( + "SELECT DISTINCT(t.char_id), t.char_zone_id, t.char_zone_instance_id, t.char_entity_id, c.name " + "FROM trader AS t " + "JOIN character_data AS c ON t.char_id = c.id " + "WHERE t.char_zone_instance_id = {} " + "ORDER BY t.char_zone_instance_id ASC " + "LIMIT {}", + char_zone_instance_id, + max_results) + ); + } + else { + results = db.QueryDatabase(fmt::format( + "SELECT DISTINCT(t.char_id), t.char_zone_id, t.char_zone_instance_id, t.char_entity_id, c.name " + "FROM trader AS t " + "JOIN character_data AS c ON t.char_id = c.id " + "ORDER BY t.char_zone_instance_id ASC " + "LIMIT {}", + max_results) + ); + } distinct_traders.reserve(results.RowCount()); for (auto row: results) { DistinctTraders_Struct e{}; - e.trader_id = Strings::ToInt(row[0]); - e.zone_id = Strings::ToInt(row[1]); - e.entity_id = Strings::ToInt(row[2]); - e.trader_name = row[3] ? row[3] : ""; + e.trader_id = Strings::ToInt(row[0]); + e.zone_id = Strings::ToInt(row[1]); + e.zone_instance_id = Strings::ToInt(row[2]); + e.entity_id = Strings::ToInt(row[3]); + e.trader_name = row[4] ? row[4] : ""; all_entries.name_length += e.trader_name.length() + 1; all_entries.traders.push_back(e); @@ -112,7 +143,8 @@ public: } for (auto &i: items) { - i.item_cost = new_price; + i.item_cost = new_price; + i.listing_date = time(nullptr); } return ReplaceMany(db, items); @@ -160,6 +192,7 @@ public: auto m = trader_item[0]; m.item_charges = quantity; + m.listing_date = time(nullptr); return UpdateOne(db, m); } @@ -203,6 +236,7 @@ public: } e.active_transaction = status == true ? 1 : 0; + e.listing_date = time(nullptr); return UpdateOne(db, e); } @@ -221,6 +255,85 @@ public: return DeleteWhere(db, fmt::format("`id` IN({})", Strings::Implode(",", delete_ids))); } + + static DistinctTraders_Struct GetTraderByInstanceAndSerialnumber( + Database &db, + uint32 instance_id, + const char *serial_number + ) + { + DistinctTraders_Struct trader{}; + + auto query = fmt::format( + "SELECT t.id, t.char_id, c.name " + "FROM trader AS t " + "JOIN character_data AS c ON c.id = t.char_id " + "WHERE t.char_zone_id = 151 AND t.char_zone_instance_id = {} AND t.item_sn = {} LIMIT 1", + instance_id, + serial_number + ); + + auto results = db.QueryDatabase(query); + + if (results.RowCount() == 0) { + return trader; + } + + auto row = results.begin(); + std::string name = row[2]; + trader.trader_id = Strings::ToUnsignedInt(row[1]); + trader.trader_name = row[2] ? row[2] : ""; + + return trader; + } + + static std::vector GetBazaarTraderDetails( + Database &db, + std::string &search_criteria_trader + ) + { + std::vector all_entries{}; + + auto query = fmt::format( + "SELECT trader.*, c.`name` FROM `trader` INNER JOIN character_data AS c ON trader.char_id = c.id " + "WHERE {} ORDER BY trader.char_id ASC", + search_criteria_trader + ); + + auto results = db.QueryDatabase(query); + + if (results.RowCount() == 0) { + return all_entries; + } + + all_entries.reserve(results.RowCount()); + for (auto row = results.begin(); row != results.end(); ++row) { + BazaarTraderSearch_Struct e{}; + + e.trader.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.trader.char_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.trader.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.trader.aug_slot_1 = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.trader.aug_slot_2 = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.trader.aug_slot_3 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.trader.aug_slot_4 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.trader.aug_slot_5 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.trader.aug_slot_6 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.trader.item_sn = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.trader.item_charges = row[10] ? static_cast(atoi(row[10])) : 0; + e.trader.item_cost = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; + e.trader.slot_id = row[12] ? static_cast(strtoul(row[12], nullptr, 10)) : 0; + e.trader.char_entity_id = row[13] ? static_cast(strtoul(row[13], nullptr, 10)) : 0; + e.trader.char_zone_id = row[14] ? static_cast(strtoul(row[14], nullptr, 10)) : 0; + e.trader.char_zone_instance_id = row[15] ? static_cast(atoi(row[15])) : 0; + e.trader.active_transaction = row[16] ? static_cast(strtoul(row[16], nullptr, 10)) : 0; + e.trader_name = row[17] ? row[17] : std::string(""); + + all_entries.push_back(e); + } + + return all_entries; + } }; #endif //EQEMU_TRADER_REPOSITORY_H diff --git a/common/repositories/zone_state_spawns_repository.h b/common/repositories/zone_state_spawns_repository.h new file mode 100644 index 000000000..adb48edec --- /dev/null +++ b/common/repositories/zone_state_spawns_repository.h @@ -0,0 +1,82 @@ +#ifndef EQEMU_ZONE_STATE_SPAWNS_REPOSITORY_H +#define EQEMU_ZONE_STATE_SPAWNS_REPOSITORY_H + +#include "../database.h" +#include "../strings.h" +#include "base/base_zone_state_spawns_repository.h" + +class ZoneStateSpawnsRepository : public BaseZoneStateSpawnsRepository { +public: + 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()) + ); + } + } + +}; + +#endif //EQEMU_ZONE_STATE_SPAWNS_REPOSITORY_H diff --git a/common/rulesys.cpp b/common/rulesys.cpp index f84b65d32..f73112b1b 100644 --- a/common/rulesys.cpp +++ b/common/rulesys.cpp @@ -502,6 +502,19 @@ bool RuleManager::UpdateInjectedRules(Database *db, const std::string &rule_set_ } } + // update rules in the database where the description is different + for (auto &e : RuleValuesRepository::All(*db)) { + auto i = rule_data.find(e.rule_name); + if (i != rule_data.end()) { + // if notes are different, update them + if (i->second.second != nullptr && *i->second.second != e.notes) { + LogInfo("Updating rule [{}] notes to [{}]", i->first, *i->second.second); + e.notes = *i->second.second; + RuleValuesRepository::ReplaceOne(*db, e); + } + } + } + if (injected_rule_entries.size()) { if (!RuleValuesRepository::InjectRules(*db, injected_rule_entries)) { return false; diff --git a/common/ruletypes.h b/common/ruletypes.h index a4a199400..1e08a9cbd 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -54,7 +54,7 @@ RULE_INT(Character, CorpseDecayTime, 604800000, "Time after which the corpse dec RULE_INT(Character, EmptyCorpseDecayTime, 10800000, "Time after which an empty corpse decays (milliseconds) DEFAULT: 10800000 (3 Hours)") RULE_INT(Character, CorpseResTime, 10800000, "Time after which the corpse can no longer be resurrected (milliseconds) DEFAULT: 10800000 (3 Hours)") RULE_INT(Character, DuelCorpseResTime, 600000, "Time before cant res corpse after a duel (milliseconds) DEFAULT: 600000 (10 Minutes)") -RULE_INT(Character, CorpseOwnerOnlineTime, 30000, "How often corpse will check if its owner is online DEFAULT: 30000 (30 Seconds)") +RULE_INT(Character, CorpseOwnerOnlineCheckTime, 300, "How often corpse will check if its owner is online DEFAULT: 300 (5 minutes)") RULE_BOOL(Character, LeaveCorpses, true, "Setting whether you leave a corpse behind") RULE_BOOL(Character, LeaveNakedCorpses, false, "Setting whether you leave a corpse without items") RULE_INT(Character, MaxDraggedCorpses, 2, "Maximum number of corpses you can drag at once") @@ -156,6 +156,7 @@ RULE_REAL(Character, TradeskillUpPottery, 4.0, "Pottery skillup rate adjustment. RULE_REAL(Character, TradeskillUpResearch, 1.0, "Research skillup rate adjustment. Lower is faster") RULE_REAL(Character, TradeskillUpTinkering, 2.0, "Tinkering skillup rate adjustment. Lower is faster") RULE_REAL(Character, TradeskillUpTailoring, 2.0, "Tailoring skillup rate adjustment. Lower is faster") +RULE_REAL(Character, TradeskillUpMinChance, 2.5, "Determines the minimum percentage chance to gain a skill increase from a tradeskill. Cannot go below 2.5") RULE_BOOL(Character, MarqueeHPUpdates, false, "Will show health percentage in center of screen if health lesser than 100%") RULE_INT(Character, IksarCommonTongue, 95, "Starting value for Common Tongue for Iksars") RULE_INT(Character, OgreCommonTongue, 95, "Starting value for Common Tongue for Ogres") @@ -230,6 +231,14 @@ RULE_BOOL(Character, PlayerTradingLoreFeedback, true, "If enabled, during a play RULE_INT(Character, MendAlwaysSucceedValue, 199, "Value at which mend will always succeed its skill check. Default: 199") 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_BOOL(Character, EnableAutoAFK, false, "Enable or disable the auto AFK feature, cuts down on packet spam") +RULE_BOOL(Character, AutoIdleFilterPackets, true, "Enable or disable filtering packets when auto AFK is enabled, heavily cuts down on packet spam in zones with lots of players") +RULE_INT(Character, SecondsBeforeIdleCombatZone, 600, "Seconds before a player is considered idle in combat zones (600 = 10 minutes)") +RULE_INT(Character, SecondsBeforeIdleNonCombatZone, 60, "Seconds before a player is considered idle in non-combat zones (60 = 1 minute)") +RULE_INT(Character, SecondsBeforeAFKCombatZone, 1800, "Seconds before a player is considered AFK in combat zones (1800 = 30 minutes)") +RULE_INT(Character, SecondsBeforeAFKNonCombatZone, 600, "Seconds before a player is considered AFK in non-combat zones (600 = 10 minutes)") RULE_CATEGORY_END() RULE_CATEGORY(Mercs) @@ -259,6 +268,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) @@ -283,11 +293,12 @@ RULE_CATEGORY(Pets) RULE_REAL(Pets, AttackCommandRange, 150, "Range at which a pet will respond to attack commands") RULE_BOOL(Pets, UnTargetableSwarmPet, false, "Setting whether swarm pets should be targetable") RULE_REAL(Pets, PetPowerLevelCap, 10, "Maximum number of levels a player pet can go up with pet power") -RULE_BOOL(Pets, CanTakeNoDrop, false, "Setting whether anyone can give no-drop items to pets") -RULE_BOOL(Pets, CanTakeQuestItems, true, "Setting whether anyone can give quest items to pets") RULE_BOOL(Pets, LivelikeBreakCharmOnInvis, true, "Default: true will break charm on any type of invis (hide/ivu/iva/etc) false will only break if the pet can not see you (ex. you have an undead pet and cast IVU") RULE_BOOL(Pets, ClientPetsUseOwnerNameInLastName, true, "Disable this to keep client pet's last names from being owner_name's pet") +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) @@ -339,7 +350,11 @@ RULE_STRING(World, MOTD, "", "Server MOTD sent on login, change from empty to ha RULE_STRING(World, Rules, "", "Server Rules, change from empty to have this be used instead of variables table 'rules' value, lines are pipe (|) separated, example: A|B|C") RULE_BOOL(World, EnableAutoLogin, false, "Enables or disables auto login of characters, allowing people to log characters in directly from loginserver to ingame") RULE_BOOL(World, EnablePVPRegions, true, "Enables or disables PVP Regions automatically setting your PVP flag") -RULE_STRING(World, SupportedClients, "", "Comma-delimited list of clients to restrict to. Supported values are Titanium | SoF | SoD | UF | RoF | RoF2. Example: Titanium,RoF2") +RULE_STRING(World, SupportedClients, "RoF2", "Comma-delimited list of clients to restrict to. Supported values are Titanium | SoF | SoD | UF | RoF | RoF2. Example: Titanium,RoF2") +RULE_STRING(World, CustomFilesKey, "", "Enable if the server requires custom files and sends a key to validate. Empty string to disable. Example: eqcustom_v1") +RULE_STRING(World, CustomFilesUrl, "github.com/knervous/eqnexus/releases", "URL to display at character select if client is missing custom files") +RULE_INT(World, CustomFilesAdminLevel, 20, "Admin level at which custom file key is not required when CustomFilesKey is specified") +RULE_BOOL(World, RealTimeCalculateGuilds, false, "(Temp feature flag) If true, guilds will be calculated in real time instead of at zone boot. This is a performance hit but allows for more dynamic guilds.") RULE_CATEGORY_END() RULE_CATEGORY(Zone) @@ -369,6 +384,12 @@ RULE_INT(Zone, FishingChance, 399, "Chance of fishing from zone table vs global RULE_BOOL(Zone, AllowCrossZoneSpellsOnBots, false, "Set to true to allow cross zone spells (cast/remove) to affect bots") 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) @@ -378,6 +399,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, 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) @@ -520,6 +545,7 @@ RULE_INT(Spells, TargetedAOEMaxTargets, 4, "Max number of targets a Targeted AOE RULE_INT(Spells, PointBlankAOEMaxTargets, 0, "Max number of targets a Point-Blank AOE spell can cast on. Set to 0 for no limit.") RULE_INT(Spells, DefaultAOEMaxTargets, 0, "Max number of targets that an AOE spell which does not meet other descriptions can cast on. Set to 0 for no limit.") RULE_BOOL(Spells, AllowFocusOnSkillDamageSpells, false, "Allow focus effects 185, 459, and 482 to enhance SkillAttack spell effect 193") +RULE_STRING(Spells, AlwaysStackSpells, "", "Comma-Seperated list of spell IDs to always stack with every other spell, except themselves.") RULE_CATEGORY_END() RULE_CATEGORY(Combat) @@ -593,7 +619,7 @@ RULE_INT(Combat, SneakPullAssistRange, 400, "Modified range of assist for sneak RULE_BOOL(Combat, Classic2HBAnimation, false, "2HB will use the 2 hand piercing animation instead of the overhead slashing animation") RULE_BOOL(Combat, ArcheryConsumesAmmo, true, "Set to false to disable Archery Ammo Consumption") RULE_BOOL(Combat, ThrowingConsumesAmmo, true, "Set to false to disable Throwing Ammo Consumption") -RULE_BOOL(Combat, UseLiveRiposteMechanics, false, "Set to true to disable SPA 173 SE_RiposteChance from making those with the effect on them immune to enrage, can longer riposte from a riposte.") +RULE_BOOL(Combat, UseLiveRiposteMechanics, false, "Set to true to disable SPA 173 SpellEffect::RiposteChance from making those with the effect on them immune to enrage, can longer riposte from a riposte.") RULE_INT(Combat, FrontalStunImmunityClasses, 0, "Bitmask for Classes than have frontal stun immunity, No Races (0) by default.") RULE_BOOL(Combat, NPCsUseFrontalStunImmunityClasses, false, "Enable or disable NPCs using frontal stun immunity Classes from Combat:FrontalStunImmunityClasses, false by default.") RULE_INT(Combat, FrontalStunImmunityRaces, 512, "Bitmask for Races than have frontal stun immunity, Ogre (512) only by default.") @@ -659,8 +685,6 @@ RULE_BOOL(NPC, EnableNPCQuestJournal, false, "Setting whether the NPC Quest Jour RULE_INT(NPC, LastFightingDelayMovingMin, 10000, "Minimum time before mob goes home after all aggro loss (milliseconds)") RULE_INT(NPC, LastFightingDelayMovingMax, 20000, "Maximum time before mob goes home after all aggro loss (milliseconds)") RULE_BOOL(NPC, SmartLastFightingDelayMoving, true, "When true, mobs that started going home previously will do so again immediately if still on FD hate list") -RULE_BOOL(NPC, ReturnNonQuestNoDropItems, false, "Returns NO DROP items on NPC that don't have an EVENT_TRADE sub in their script") -RULE_BOOL(NPC, ReturnQuestItemsFromNonQuestNPCs, false, "Returns Quest items traded to NPCs that are not flagged as a Quest NPC") RULE_INT(NPC, StartEnrageValue, 9, " Percentage HP that an NPC will begin to enrage") RULE_BOOL(NPC, LiveLikeEnrage, false, "If set to true then only player controlled pets will enrage") RULE_BOOL(NPC, EnableMeritBasedFaction, false, "If set to true, faction will be given in the same way as experience (solo/group/raid)") @@ -708,6 +732,7 @@ RULE_INT(Aggro, BardAggroCap, 40, "per song bard aggro cap.") RULE_INT(Aggro, InitialAggroBonus, 100, "Initial Aggro Bonus, Default: 100") RULE_INT(Aggro, InitialPetAggroBonus, 100, "Initial Pet Aggro Bonus, Default 100") RULE_STRING(Aggro, ExcludedFleeAllyFactionIDs, "0|5013|5014|5023|5032", "Common Faction IDs that are excluded from faction checks in EntityList::FleeAllyCount") +RULE_BOOL(Aggro, AggroBotPets, false, "If enabled, NPCs will aggro bot pets") RULE_CATEGORY_END() RULE_CATEGORY(TaskSystem) @@ -750,13 +775,13 @@ RULE_INT(Bots, CommandSpellRank, 1, "Filters bot command spells by rank. 1, 2 an RULE_INT(Bots, CreationLimit, 150, "Number of bots that each account can create") RULE_BOOL(Bots, FinishBuffing, false, "Allow for buffs to complete even if the bot caster is out of mana. Only affects buffing out of combat") RULE_BOOL(Bots, GroupBuffing, false, "Bots will cast single target buffs as group buffs, default is false for single. Does not make single target buffs work for MGB") +RULE_BOOL(Bots, RaidBuffing, false, "Bots will cast single target buffs as raid buffs, default is false for single. Does not make single target buffs work for MGB") RULE_INT(Bots, HealRotationMaxMembers, 24, "Maximum number of heal rotation members") RULE_INT(Bots, HealRotationMaxTargets, 12, "Maximum number of heal rotation targets") RULE_REAL(Bots, ManaRegen, 2.0, "Adjust mana regen. Acts as a final multiplier, stacks with Rule Character:ManaRegenMultiplier.") RULE_BOOL(Bots, PreferNoManaCommandSpells, true, "Give sorting priority to newer no-mana spells (i.e., 'Bind Affinity')") RULE_BOOL(Bots, QuestableSpawnLimit, false, "Optional quest method to manage bot spawn limits using the quest_globals name bot_spawn_limit, see: /bazaar/Aediles_Thrall.pl") RULE_INT(Bots, SpawnLimit, 71, "Number of bots a character can have spawned at one time, You + 71 bots is a 12 group pseudo-raid") -RULE_BOOL(Bots, BotGroupXP, false, "Determines whether client gets experience for bots outside their group") RULE_BOOL(Bots, BotLevelsWithOwner, false, "Auto-updates spawned bots as owner levels/de-levels (false is original behavior)") RULE_INT(Bots, BotCharacterLevel, 0, "If level is greater that value player can spawn bots if BotCharacterLevelEnabled is true") RULE_INT(Bots, CasterStopMeleeLevel, 13, "Level at which caster bots stop melee attacks") @@ -779,6 +804,110 @@ RULE_BOOL(Bots, CanClickMageEpicV1, true, "Whether or not bots are allowed to cl RULE_BOOL(Bots, BotsIgnoreLevelBasedHasteCaps, false, "Ignores hard coded level based haste caps.") RULE_INT(Bots, BotsHasteCap, 100, "Haste cap for non-v3(over haste) haste") RULE_INT(Bots, BotsHastev3Cap, 25, "Haste cap for v3(over haste) haste") +RULE_BOOL(Bots, CrossRaidBuffingAndHealing, true, "If True, bots will be able to cast on all raid members rather than just their raid group members. Default true.") +RULE_BOOL(Bots, CanCastIllusionsOnPets, false, "If True, bots will be able to cast spells that have an illusion effect on pets. Default false.") +RULE_BOOL(Bots, CanCastPetOnlyOnOthersPets, false, "If True, bots will be able to cast pet only spells on other's pets. Default false.") +RULE_BOOL(Bots, RequirePetAffinity, true, "If True, bots will be need to have the Pet Affinity AA to allow their pets to be hit with group spells.") +RULE_INT(Bots, SpellResistLimit, 150, "150 Default. This is the resist cap where bots will refuse to cast spells on enemies due to a high resist chance.") +RULE_INT(Bots, StunCastChanceIfCasting, 50, "50 Default. Chance for non-Paladins to cast a stun spell if the target is casting.") +RULE_INT(Bots, StunCastChanceNormal, 15, "15 Default. Chance for non-Paladins to cast a stun spell on the target.") +RULE_INT(Bots, StunCastChancePaladins, 75, "75 Default. Chance for Paladins to cast a stun spell if the target is casting.") +RULE_INT(Bots, PercentChanceToCastAEs, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") +RULE_INT(Bots, PercentChanceToCastNuke, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") +RULE_INT(Bots, PercentChanceToCastGroupHeal, 90, "The chance for a bot to attempt to cast the given spell type in combat. Default 90%.") +RULE_INT(Bots, PercentChanceToCastHeal, 90, "The chance for a bot to attempt to cast the given spell type in combat. Default 90%.") +RULE_INT(Bots, PercentChanceToCastRoot, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") +RULE_INT(Bots, PercentChanceToCastBuff, 90, "The chance for a bot to attempt to cast the given spell type in combat. Default 90%.") +RULE_INT(Bots, PercentChanceToCastEscape, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") +RULE_INT(Bots, PercentChanceToCastLifetap, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") +RULE_INT(Bots, PercentChanceToCastSnare, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") +RULE_INT(Bots, PercentChanceToCastDOT, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") +RULE_INT(Bots, PercentChanceToCastDispel, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") +RULE_INT(Bots, PercentChanceToCastInCombatBuff, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") +RULE_INT(Bots, PercentChanceToCastHateLine, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") +RULE_INT(Bots, 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%.") +RULE_INT(Bots, PercentChanceToCastGroupCure, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") +RULE_INT(Bots, PercentChanceToCastHateRedux, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") +RULE_INT(Bots, PercentChanceToCastFear, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") +RULE_INT(Bots, PercentChanceToCastOtherType, 90, "The chance for a bot to attempt to cast the remaining spell types in combat. Default 0-%.") +RULE_INT(Bots, MinDelayBetweenInCombatCastAttempts, 500, "The minimum delay in milliseconds between cast attempts while in-combat. This is rolled between the min and max. Default 500ms.") +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, 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.") +RULE_INT(Bots, MezAEFailDelay, 4000, "4000 (4 sec) Default. Delay between failed AEMez attempts.") +RULE_INT(Bots, MinGroupHealTargets, 3, "Minimum number of targets in valid range that are required for a group heal to cast. Default 3.") +RULE_INT(Bots, MinGroupCureTargets, 3, "Minimum number of targets in valid range that are required for a cure heal to cast. Default 3.") +RULE_INT(Bots, MinTargetsForAESpell, 3, "Minimum number of targets in valid range that are required for an AE spell to cast. Default 3.") +RULE_INT(Bots, MinTargetsForGroupSpell, 3, "Minimum number of targets in valid range that are required for an group spell to cast. Default 3.") +RULE_BOOL(Bots, AllowBuffingHealingFamiliars, false, "Determines if bots are allowed to buff and heal familiars. Default false.") +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 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") +RULE_BOOL(Bots, AllowRangedPulling, true, "If enabled bots will pull with their ranged items if set to ranged.") +RULE_BOOL(Bots, AllowAISpellPulling, true, "If enabled bots will rely on their detrimental AI to pull when within range.") +RULE_BOOL(Bots, AllowBotEquipAnyClassGear, false, "Allows Bots to wear Equipment even if their class is not valid") +RULE_BOOL(Bots, BotArcheryConsumesAmmo, true, "Set to false to disable Archery Ammo Consumption") +RULE_BOOL(Bots, BotThrowingConsumesAmmo, true, "Set to false to disable Throwing Ammo Consumption") +RULE_INT(Bots, StackSizeMin, 20, "20 Default. -1 to disable and use default max stack size. Minimum stack size to give a bot (Arrows/Throwing).") +RULE_INT(Bots, 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, 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.") +RULE_BOOL(Bots, PreventBotSpawnOnFD, true, "True Default. If true, players will not be able to spawn bots while feign death.") +RULE_BOOL(Bots, PreventBotSpawnOnEngaged, true, "True Default. If true, players will not be able to spawn bots while you, your group or raid are engaged.") +RULE_BOOL(Bots, PreventBotCampOnEngaged, true, "True Default. If true, players will not be able to camp bots while you, your group or raid are engaged.") +RULE_BOOL(Bots, CopySettingsOwnBotsOnly, true, "Determines whether a bot you are copying settings from must be a bot you own or not, default true.") +RULE_BOOL(Bots, AllowCopySettingsAnon, false, "If player's are allowed to copy settings of bots owned by anonymous players.") +RULE_BOOL(Bots, AllowCharmedPetBuffs, true, "Whether or not bots are allowed to cast buff charmed pets, default true.") +RULE_BOOL(Bots, AllowCharmedPetHeals, true, "Whether or not bots are allowed to cast heal charmed pets, default true.") +RULE_BOOL(Bots, AllowCharmedPetCures, true, "Whether or not bots are allowed to cast cure charmed pets, default true.") +RULE_BOOL(Bots, ShowResistMessagesToOwner, true, "Default True. If enabled, when a bot's spell is resisted it will send a spell failure to their owner.") +RULE_BOOL(Bots, BotBuffLevelRestrictions, true, "Buffs will not land on low level bots like live players") +RULE_BOOL(Bots, BotsUseLiveBlockedMessage, true, "Setting whether detailed spell block messages should be used for bots as players do on the live servers") +RULE_BOOL(Bots, BotSoftDeletes, true, "When bots are deleted, they are only soft deleted") +RULE_INT(Bots, MinStatusToBypassSpawnLimit, 100, "Minimum status to bypass spawn limit. Default 100.") +RULE_INT(Bots, MinStatusBypassSpawnLimit, 120, "Spawn limit with status bypass. Default 120.") +RULE_INT(Bots, MinStatusToBypassCreateLimit, 100, "Minimum status to bypass create limit. Default 100.") +RULE_INT(Bots, MinStatusBypassCreateLimit, 120, "Create limit with status bypass. Default 120.") +RULE_INT(Bots, MinStatusToBypassBotLevelRequirement, 100, "Minimum status to bypass level requirement for bots. Default 100.") +RULE_BOOL(Bots, EnableBotTGB, true, "If enabled bots will cast group buffs as TGB.") +RULE_BOOL(Bots, DoResponseAnimations, true, "If enabled bots will do animations to certain responses or commands.") +RULE_INT(Bots, DefaultFollowDistance, 20, "Default 20. Distance a bot will follow behind.") +RULE_INT(Bots, MaxFollowDistance, 300, "Default 300. Max distance a bot can be set to follow behind.") +RULE_INT(Bots, MaxDistanceRanged, 300, "Default 300. Max distance a bot can be set to ranged.") +RULE_BOOL(Bots, AllowAIMez, true, "If enabled bots will automatically mez/AE mez eligible targets.") +RULE_BOOL(Bots, AllowCommandedCharm, true, "If enabled bots can be commanded to charm NPCs.") +RULE_BOOL(Bots, AllowCommandedMez, true, "If enabled bots can be commanded to mez NPCs.") +RULE_BOOL(Bots, AllowCommandedResurrect, true, "If enabled bots can be commanded to resurrect players.") +RULE_BOOL(Bots, AllowCommandedSummonCorpse, true, "If enabled bots can be commanded to summon other's corpses.") +RULE_BOOL(Bots, AllowCommandedLull, true, "If enabled bots can be commanded to lull targets.") +RULE_INT(Bots, CampTimer, 25, "Number of seconds after /camp has begun before bots camp out.") +RULE_BOOL(Bots, SendClassRaceOnHelp, true, "If enabled a reminder of how to check class/race IDs will be sent when using compatible commands.") +RULE_BOOL(Bots, AllowCrossGroupRaidAssist, true, "If enabled bots will autodefend group or raid members set as main assist.") +RULE_BOOL(Bots, AllowBotBlockedBuffs, true, "If enabled, you can create blocked buffs for each bot and for their pets.") +RULE_STRING(Bots, ZonesWithSpawnLimits, "", "Comma-delimited list of zones where different bot spawn limits apply. This is the max a zone allows.") +RULE_STRING(Bots, ZoneSpawnLimits, "", "Comma-delimited list of spawn limits for zones.") +RULE_STRING(Bots, ZonesWithForcedSpawnLimits, "", "Comma-delimited list of zones where bot spawn limits are forced. This will take priority over any other type of spawn limits.") +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) @@ -802,6 +931,7 @@ RULE_BOOL(Chat, AutoInjectSaylinksToSay, true, "Automatically injects saylinks i RULE_BOOL(Chat, AutoInjectSaylinksToClientMessage, true, "Automatically injects saylinks into dialogue that has [brackets in them]") RULE_BOOL(Chat, QuestDialogueUsesDialogueWindow, false, "Pipes all quest dialogue to dialogue window") RULE_BOOL(Chat, DialogueWindowAnimatesNPCsIfNoneSet, true, "If there is no animation specified in the dialogue window markdown then it will choose a random greet animation such as wave or salute") +RULE_BOOL(Chat, AlwaysCaptureCommandText, false, "Consume command text (# and ^ by default), regardless of which channel it is sent to") RULE_CATEGORY_END() RULE_CATEGORY(Merchant) @@ -826,6 +956,7 @@ RULE_REAL(Bazaar, ParcelDeliveryCostMod, 0.20, "Cost of parcel delivery for a ba RULE_INT(Bazaar, VoucherDeliveryCost, 200, "Number of vouchers for direct delivery for a bazaar purchase. Default is 200 vouchers. RoF+ Only.") RULE_BOOL(Bazaar, EnableParcelDelivery, true, "Enable bazaar purchases via parcel delivery. Default is True.") RULE_INT(Bazaar, MaxBuyerInventorySearchResults, 200, "Maximum number of search results when a Buyer searches the global item list. Default is 200. RoF+ Only.") +RULE_BOOL(Bazaar, UseAlternateBazaarSearch, false, "Allows the bazaar search window to search across bazaar shards. Default is false.") RULE_CATEGORY_END() RULE_CATEGORY(Mail) @@ -948,6 +1079,7 @@ RULE_CATEGORY(Logging) RULE_BOOL(Logging, PrintFileFunctionAndLine, false, "Ex: [World Server] [net.cpp::main:309] Loading variables...") RULE_BOOL(Logging, WorldGMSayLogging, true, "Relay worldserver logging to zone processes via GM say output") RULE_BOOL(Logging, PlayerEventsQSProcess, false, "Have query server process player events instead of world. Useful when wanting to use a dedicated server and database for processing player events on separate disk") +RULE_STRING(Logging, PlayerEventsIgnoreGMCommands, "help,show", "This is a comma delimited list of commands to ignore when recording GM command player events.") RULE_INT(Logging, BatchPlayerEventProcessIntervalSeconds, 5, "This is the interval in which player events are processed in world or qs") RULE_INT(Logging, BatchPlayerEventProcessChunkSize, 10000, "This is the cap of events that can be inserted into the queue before a force flush. This is to keep from hitting MySQL max_allowed_packet and killing the connection") RULE_CATEGORY_END() @@ -969,6 +1101,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) @@ -1004,6 +1137,7 @@ RULE_CATEGORY_END() RULE_CATEGORY(Command) RULE_BOOL(Command, DyeCommandRequiresDyes, false, "Enable this to require a Prismatic Dye (32557) each time someone uses #dye.") RULE_BOOL(Command, HideMeCommandDisablesTells, true, "Disable this to allow tells to be received when using #hideme.") +RULE_INT(Command, MaxHelpLineLength, 53, "Maximum length of a line before splitting it in to new lines for DiaWind. Default 53.") RULE_CATEGORY_END() RULE_CATEGORY(Doors) @@ -1022,6 +1156,8 @@ RULE_BOOL(Items, DisablePotionBelt, false, "Enable this to disable Potion Belt I RULE_BOOL(Items, DisableSpellFocusEffects, false, "Enable this to disable Spell Focus Effects on Items") RULE_BOOL(Items, SummonItemAllowInvisibleAugments, false, "Enable this to allow augments to be put in invisible augment slots of items in Client::SummonItem") RULE_BOOL(Items, AugmentItemAllowInvisibleAugments, false, "Enable this to allow augments to be put in invisible augment slots by players") +RULE_BOOL(Items, AlwaysReturnHandins, true, "Enable this to always return handins to the player") +RULE_BOOL(Items, NPCUseRecommendedLevels, false, "Enable to have NPCs scale item stats by recommended levels") RULE_CATEGORY_END() RULE_CATEGORY(Parcel) @@ -1034,6 +1170,14 @@ RULE_INT(Parcel, ParcelMaxItems, 50, "The maximum number of parcels a player is RULE_INT(Parcel, ParcelPruneDelay, 30, "The number of days after which a parcel is deleted. Items are lost!") 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 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 #undef RULE_INT #undef RULE_REAL diff --git a/common/server_reload_types.h b/common/server_reload_types.h new file mode 100644 index 000000000..af42bec36 --- /dev/null +++ b/common/server_reload_types.h @@ -0,0 +1,145 @@ +#ifndef EQEMU_SERVER_RELOAD_TYPES_H +#define EQEMU_SERVER_RELOAD_TYPES_H + +#include +#include +#include + +namespace ServerReload { + enum Type { + ReloadTypeNone = 0, + AAData, + AlternateCurrencies, + BaseData, + BlockedSpells, + Commands, + ContentFlags, + DataBucketsCache, + Doors, + DzTemplates, + Factions, + GroundSpawns, + LevelEXPMods, + Logs, + Loot, + Maps, + Merchants, + NPCEmotes, + NPCSpells, + Objects, + Opcodes, + PerlExportSettings, + Quests, + QuestsTimerReset, + Rules, + SkillCaps, + StaticZoneData, + Tasks, + Titles, + Traps, + Variables, + VeteranRewards, + WorldRepop, + WorldWithRespawn, + ZoneData, + ZonePoints, + Max + }; + + static const char *Name[ServerReload::Max] = { + "None", + "AA Data", + "Alternate Currencies", + "Base Data", + "Blocked Spells", + "Commands", + "Content Flags", + "Data Buckets Cache", + "Doors", + "DZ Templates", + "Factions", + "Ground Spawns", + "Level EXP Mods", + "Logs", + "Loot", + "Maps", + "Merchants", + "NPC Emotes", + "NPC Spells", + "Objects", + "Opcodes", + "Perl Event Export Settings", + "Quest", + "Quests With Timer (Resets timer events)", + "Rules", + "Skill Caps", + "Static Zone Data", + "Tasks", + "Titles", + "Traps", + "Variables", + "Veteran Rewards", + "World Repop", + "World Repop Timers (Clear Respawn Timers)", + "Zone Data", + "Zone Points" + }; + + inline std::string GetName(int reload_type) + { + if (reload_type < 0 || reload_type >= ServerReload::Type::Max) { + return "Unknown"; + } + + return ServerReload::Name[reload_type]; + } + + // Get a clean name without spaces or special characters + inline std::string GetNameClean(int reload_type) + { + if (reload_type < 0 || reload_type >= ServerReload::Type::Max) { + return "Unknown"; + } + + // get the name before parentheses + std::string name = ServerReload::Name[reload_type]; + size_t pos = name.find('('); + if (pos != std::string::npos) { + name = name.substr(0, pos); + } + + // Trim leading spaces + size_t start = name.find_first_not_of(' '); + if (start == std::string::npos) { + return ""; // If all spaces, return empty string + } + + // Trim trailing spaces + size_t end = name.find_last_not_of(' '); + + // Extract trimmed substring + return name.substr(start, end - start + 1); + + return name; + } + + inline std::vector GetTypes() + { + std::vector types; + types.reserve(ServerReload::Type::Max); + for (int i = 1; i < ServerReload::Type::Max; i++) { + types.push_back(static_cast(i)); + } + return types; + } + + struct Request { + int type = 0; + bool requires_zone_booted = false; + int64 reload_at_unix = 0; + int32 opt_param = 0; + uint32_t zone_server_id = 0; + }; +} + +#endif //EQEMU_SERVER_RELOAD_TYPES_H diff --git a/common/servertalk.h b/common/servertalk.h index 5761c1e79..9985c875c 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -170,18 +170,6 @@ #define ServerOP_LFPMatches 0x0214 #define ServerOP_ClientVersionSummary 0x0215 -// expedition -#define ServerOP_ExpeditionCreate 0x0400 -#define ServerOP_ExpeditionLockout 0x0403 -#define ServerOP_ExpeditionDzAddPlayer 0x0408 -#define ServerOP_ExpeditionDzMakeLeader 0x0409 -#define ServerOP_ExpeditionCharacterLockout 0x040d -#define ServerOP_ExpeditionSaveInvite 0x040e -#define ServerOP_ExpeditionRequestInvite 0x040f -#define ServerOP_ExpeditionReplayOnJoin 0x0410 -#define ServerOP_ExpeditionLockState 0x0411 -#define ServerOP_ExpeditionLockoutDuration 0x0414 - // dz #define ServerOP_DzAddRemoveMember 0x0450 #define ServerOP_DzRemoveAllMembers 0x0451 @@ -199,6 +187,16 @@ #define ServerOP_DzDeleted 0x045d #define ServerOP_DzSetSwitchID 0x045e #define ServerOP_DzMovePC 0x045f +#define ServerOP_DzLock 0x0460 +#define ServerOP_DzReplayOnJoin 0x0461 +#define ServerOP_DzLockout 0x0462 +#define ServerOP_DzLockoutDuration 0x0463 +#define ServerOP_DzCharacterLockout 0x0464 +#define ServerOP_DzAddPlayer 0x0465 +#define ServerOP_DzSaveInvite 0x0466 +#define ServerOP_DzRequestInvite 0x0467 +#define ServerOP_DzMakeLeader 0x0468 +#define ServerOP_DzGetBulkMemberStatuses 0x0469 #define ServerOP_LSInfo 0x1000 #define ServerOP_LSStatus 0x1001 @@ -251,37 +249,7 @@ #define ServerOP_UpdateSchedulerEvents 0x4007 #define ServerOP_DiscordWebhookMessage 0x4008 -#define ServerOP_ReloadAAData 0x4100 -#define ServerOP_ReloadAlternateCurrencies 0x4101 -#define ServerOP_ReloadBlockedSpells 0x4102 -#define ServerOP_ReloadCommands 0x4103 -#define ServerOP_ReloadContentFlags 0x4104 -#define ServerOP_ReloadDoors 0x4105 -#define ServerOP_ReloadGroundSpawns 0x4106 -#define ServerOP_ReloadLevelEXPMods 0x4107 -#define ServerOP_ReloadLogs 0x4108 -#define ServerOP_ReloadMerchants 0x4109 -#define ServerOP_ReloadNPCEmotes 0x4110 -#define ServerOP_ReloadObjects 0x4111 -#define ServerOP_ReloadOpcodes 0x4112 -#define ServerOP_ReloadPerlExportSettings 0x4113 -#define ServerOP_ReloadRules 0x4114 -#define ServerOP_ReloadStaticZoneData 0x4115 -#define ServerOP_ReloadTasks 0x4116 -#define ServerOP_ReloadTitles 0x4117 -#define ServerOP_ReloadTraps 0x4118 -#define ServerOP_ReloadVariables 0x4119 -#define ServerOP_ReloadVeteranRewards 0x4120 -#define ServerOP_ReloadWorld 0x4121 -#define ServerOP_ReloadZonePoints 0x4122 -#define ServerOP_ReloadDzTemplates 0x4123 -#define ServerOP_ReloadZoneData 0x4124 -#define ServerOP_ReloadDataBucketsCache 0x4125 -#define ServerOP_ReloadFactions 0x4126 -#define ServerOP_ReloadLoot 0x4127 -#define ServerOP_ReloadBaseData 0x4128 -#define ServerOP_ReloadSkillCaps 0x4129 -#define ServerOP_ReloadNPCSpells 0x4130 +#define ServerOP_ServerReloadRequest 0x4100 #define ServerOP_CZDialogueWindow 0x4500 #define ServerOP_CZLDoNUpdate 0x4501 @@ -304,22 +272,10 @@ #define ServerOP_WWSpell 0x4757 #define ServerOP_WWTaskUpdate 0x4758 -/** - * QueryServer - */ -#define ServerOP_QSPlayerLogTrades 0x5000 -#define ServerOP_QSPlayerLogHandins 0x5001 -#define ServerOP_QSPlayerLogNPCKills 0x5002 -#define ServerOP_QSPlayerLogDeletes 0x5003 -#define ServerOP_QSPlayerLogMoves 0x5004 -#define ServerOP_QSPlayerLogMerchantTransactions 0x5005 -#define ServerOP_QSSendQuery 0x5006 -#define ServerOP_QSPlayerDropItem 0x5007 - // player events +#define ServerOP_QSSendQuery 0x5000 #define ServerOP_PlayerEvent 0x5100 - -#define ServerOP_DataBucketCacheUpdate 0x5200 +#define ServerOP_SendPlayerEventSettings 0x5101 enum { CZUpdateType_Character, @@ -402,7 +358,6 @@ enum { QSG_LFGuild = 0 }; enum { QSG_LFGuild_PlayerMatches = 0, QSG_LFGuild_UpdatePlayerInfo, QSG_LFGuild_RequestPlayerInfo, QSG_LFGuild_UpdateGuildInfo, QSG_LFGuild_GuildMatches, QSG_LFGuild_RequestGuildInfo }; -#define ServerOP_Speech 0x5500 enum { UserToWorldStatusWorldUnavail = 0, @@ -660,7 +615,7 @@ struct ServerLSInfo_Struct { uint8 servertype; // 0=world, 1=chat, 2=login, 3=MeshLogin }; -struct ServerNewLSInfo_Struct { +struct LoginserverNewWorldRequest { char server_long_name[201]; // name the worldserver wants char server_short_name[50]; // shortname the worldserver wants char remote_ip_address[125]; // DNS address of the server @@ -672,21 +627,21 @@ struct ServerNewLSInfo_Struct { uint8 server_process_type; // 0=world, 1=chat, 2=login, 3=MeshLogin }; -struct ServerLSAccountUpdate_Struct { // for updating info on login server - char worldaccount[31]; // account name for the worldserver - char worldpassword[31]; // password for the name - uint32 useraccountid; // player account ID - char useraccount[31]; // player account name - char userpassword[51]; // player account password - char user_email[101]; // player account email address +struct LoginserverAccountUpdate { // for updating info on login server + char world_account[31]; // account name for the worldserver + char world_password[31]; // password for the name + uint32 user_account_id; // player account ID + char user_account_name[31]; // player account name + char user_account_password[51]; // player account password + char user_email[101]; // player account email address }; -struct ServerLSStatus_Struct { +struct LoginserverWorldStatusUpdate { int32 status; int32 num_players; int32 num_zones; }; -struct ZoneInfo_Struct { +struct LoginserverZoneInfoUpdate { uint32 zone; uint16 count; uint32 zone_wid; @@ -724,36 +679,53 @@ struct ServerLSPlayerZoneChange_Struct { uint32 to; // 0 = world }; -struct ClientAuth_Struct { +struct ClientAuth { uint32 loginserver_account_id; // ID# in login server's db - char loginserver_name[64]; - char account_name[30]; // username in login server's db - char key[30]; // the Key the client will present - uint8 lsadmin; // login server admin level - int16 is_world_admin; // login's suggested worldadmin level setting for this user, up to the world if they want to obey it - uint32 ip; - uint8 is_client_from_local_network; // 1 if the client is from the local network + char loginserver_name[64]; + char account_name[30]; // username in login server's db + char key[30]; // the key the client will present + uint8 lsadmin; // login server admin level + int16 is_world_admin; // login's suggested worldadmin level setting for this user, up to the world if they want to obey it + uint32 ip_address; + uint8 is_client_from_local_network; // 1 if the client is from the local network - template + template void serialize(Archive &ar) { - ar(loginserver_account_id, loginserver_name, account_name, key, lsadmin, is_world_admin, ip, is_client_from_local_network); + ar( + loginserver_account_id, + loginserver_name, + account_name, + key, + lsadmin, + is_world_admin, + ip_address, + is_client_from_local_network + ); } }; -struct ClientAuthLegacy_Struct { +struct ClientAuthLegacy { uint32 loginserver_account_id; // ID# in login server's db - char loginserver_account_name[30]; // username in login server's db - char key[30]; // the Key the client will present - uint8 loginserver_admin_level; // login server admin level - int16 is_world_admin; // login's suggested worldadmin level setting for this user, up to the world if they want to obey it - uint32 ip; - uint8 is_client_from_local_network; // 1 if the client is from the local network + char loginserver_account_name[30]; // username in login server's db + char key[30]; // the key the client will present + uint8 loginserver_admin_level; // login server admin level + int16 is_world_admin; // login's suggested worldadmin level setting for this user, up to the world if they want to obey it + uint32 ip_address; + uint8 is_client_from_local_network; // 1 if the client is from the local network - template + template void serialize(Archive &ar) { - ar(loginserver_account_id, loginserver_account_name, key, loginserver_admin_level, is_world_admin, ip, is_client_from_local_network); + ar( + loginserver_account_id, + loginserver_account_name, + key, + loginserver_admin_level, + is_world_admin, + ip_address, + is_client_from_local_network + ); } }; @@ -881,38 +853,38 @@ struct ServerSyncWorldList_Struct { bool placeholder; }; -struct UsertoWorldRequestLegacy_Struct { - uint32 lsaccountid; - uint32 worldid; - uint32 FromID; - uint32 ToID; - char IPAddr[64]; -}; - -struct UsertoWorldRequest_Struct { - uint32 lsaccountid; - uint32 worldid; - uint32 FromID; - uint32 ToID; - char IPAddr[64]; - char login[64]; -}; - -struct UsertoWorldResponseLegacy_Struct { +struct UsertoWorldRequestLegacy { uint32 lsaccountid; uint32 worldid; - int8 response; // -3) World Full, -2) Banned, -1) Suspended, 0) Denied, 1) Allowed + uint32 FromID; + uint32 ToID; + char IPAddr[64]; +}; + +struct UsertoWorldRequest { + uint32 lsaccountid; + uint32 worldid; + uint32 FromID; // appears to be unused today + uint32 ToID; // appears to be unused today + char IPAddr[64]; + char login[64]; +}; + +struct UsertoWorldResponseLegacy { + uint32 lsaccountid; + uint32 worldid; + int8 response; // -3) World Full, -2) Banned, -1) Suspended, 0) Denied, 1) Allowed uint32 FromID; uint32 ToID; }; -struct UsertoWorldResponse_Struct { +struct UsertoWorldResponse { uint32 lsaccountid; uint32 worldid; - int8 response; // -3) World Full, -2) Banned, -1) Suspended, 0) Denied, 1) Allowed - uint32 FromID; - uint32 ToID; - char login[64]; + int8 response; // -3) World Full, -2) Banned, -1) Suspended, 0) Denied, 1) Allowed + uint32 FromID; // appears to be unused today + uint32 ToID; // appears to be unused today + char login[64]; }; // generic struct to be used for alot of simple zone->world questions @@ -1360,171 +1332,11 @@ struct ServerMailMessageHeader_Struct { char message[0]; }; -struct Server_Speech_Struct { - char to[64]; - char from[64]; - uint32 guilddbid; - int16 minstatus; - uint32 type; - char message[0]; -}; - -struct PlayerLogTradeItemsEntry_Struct { - uint32 from_character_id; - uint16 from_slot; - uint32 to_character_id; - uint16 to_slot; - uint32 item_id; - uint16 charges; - uint32 aug_1; - uint32 aug_2; - uint32 aug_3; - uint32 aug_4; - uint32 aug_5; -}; - -struct PlayerLogTrade_Struct { - uint32 character_1_id; - MoneyUpdate_Struct character_1_money; - uint16 character_1_item_count; - uint32 character_2_id; - MoneyUpdate_Struct character_2_money; - uint16 character_2_item_count; - uint16 _detail_count; - PlayerLogTradeItemsEntry_Struct item_entries[0]; -}; - -struct QSDropItems_Struct { - uint32 item_id; - uint16 charges; - uint32 aug_1; - uint32 aug_2; - uint32 aug_3; - uint32 aug_4; - uint32 aug_5; -}; - -struct QSPlayerDropItem_Struct { - uint32 char_id; - bool pickup; // 0 drop, 1 pickup - uint32 zone_id; - int x; - int y; - int z; - uint16 _detail_count; - QSDropItems_Struct items[0]; -}; - -struct QSHandinItems_Struct { - char action_type[7]; // handin, return or reward - uint16 char_slot; - uint32 item_id; - uint16 charges; - uint32 aug_1; - uint32 aug_2; - uint32 aug_3; - uint32 aug_4; - uint32 aug_5; -}; - -struct QSPlayerLogHandin_Struct { - uint32 quest_id; - uint32 char_id; - MoneyUpdate_Struct char_money; - uint16 char_count; - uint32 npc_id; - MoneyUpdate_Struct npc_money; - uint16 npc_count; - uint16 _detail_count; - QSHandinItems_Struct items[0]; -}; - -struct QSPlayerLogNPCKillSub_Struct{ - uint32 NPCID; - uint32 ZoneID; - uint32 Type; -}; - -struct QSPlayerLogNPCKillsPlayers_Struct{ - uint32 char_id; -}; - -struct QSPlayerLogNPCKill_Struct{ - QSPlayerLogNPCKillSub_Struct s1; - QSPlayerLogNPCKillsPlayers_Struct Chars[0]; -}; - -struct QSDeleteItems_Struct { - uint16 char_slot; - uint32 item_id; - uint16 charges; - uint32 aug_1; - uint32 aug_2; - uint32 aug_3; - uint32 aug_4; - uint32 aug_5; -}; - -struct QSPlayerLogDelete_Struct { - uint32 char_id; - uint16 stack_size; // '0' indicates full stack or non-stackable item move - uint16 char_count; - QSDeleteItems_Struct items[0]; -}; - -struct QSMoveItems_Struct { - uint16 from_slot; - uint16 to_slot; - uint32 item_id; - uint16 charges; - uint32 aug_1; - uint32 aug_2; - uint32 aug_3; - uint32 aug_4; - uint32 aug_5; -}; - -struct QSPlayerLogMove_Struct { - uint32 char_id; - uint16 from_slot; - uint16 to_slot; - uint16 stack_size; // '0' indicates full stack or non-stackable item move - uint16 char_count; - bool postaction; - QSMoveItems_Struct items[0]; -}; - -struct QSTransactionItems_Struct { - uint16 char_slot; - uint32 item_id; - uint16 charges; - uint32 aug_1; - uint32 aug_2; - uint32 aug_3; - uint32 aug_4; - uint32 aug_5; -}; - -struct QSMerchantLogTransaction_Struct { - uint32 zone_id; - uint32 merchant_id; - MoneyUpdate_Struct merchant_money; - uint16 merchant_count; - uint32 char_id; - MoneyUpdate_Struct char_money; - uint16 char_count; - QSTransactionItems_Struct items[0]; -}; - struct DiscordWebhookMessage_Struct { uint32 webhook_id; char message[2000]; }; -struct QSGeneralQuery_Struct { - char QueryString[0]; -}; - struct CZClientMessageString_Struct { uint32 string_id; uint16 chat_type; @@ -1717,10 +1529,8 @@ struct UCSServerStatus_Struct { }; }; -struct ServerExpeditionID_Struct { - uint32 expedition_id; - uint32 sender_zone_id; - uint32 sender_instance_id; +struct ServerCharacterID_Struct { + uint32_t char_id; }; struct ServerDzLeaderID_Struct { @@ -1747,6 +1557,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; @@ -1754,45 +1571,42 @@ struct ServerDzMovePC_Struct { uint32 character_id; }; -struct ServerExpeditionLockout_Struct { - uint32 expedition_id; +struct ServerDzLockout_Struct { + uint32 dz_id; uint64 expire_time; uint32 duration; uint32 sender_zone_id; uint16 sender_instance_id; uint8 remove; uint8 members_only; - int seconds_adjust; + int seconds; char event_name[256]; }; -struct ServerExpeditionLockState_Struct { - uint32 expedition_id; +struct ServerDzLock_Struct { + uint32 dz_id; uint32 sender_zone_id; uint16 sender_instance_id; - uint8 enabled; + bool lock; uint8 lock_msg; // 0: none, 1: closing 2: trial begin + uint32 color; }; -struct ServerExpeditionSetting_Struct { - uint32 expedition_id; +struct ServerDzBool_Struct { + uint32 dz_id; uint32 sender_zone_id; uint16 sender_instance_id; - uint8 enabled; + bool enabled; }; -struct ServerExpeditionCharacterLockout_Struct { +struct ServerDzCharacterLockout_Struct { uint8 remove; - uint32 character_id; + uint32 char_id; uint64 expire_time; uint32 duration; char uuid[37]; - char expedition_name[128]; - char event_name[256]; -}; - -struct ServerExpeditionCharacterID_Struct { - uint32_t character_id; + char expedition[128]; + char event[256]; }; struct ServerDzExpireWarning_Struct { @@ -1801,7 +1615,7 @@ struct ServerDzExpireWarning_Struct { }; struct ServerDzCommand_Struct { - uint32 expedition_id; + uint32 dz_id; uint8 is_char_online; // 0: target name is offline, 1: online char requester_name[64]; char target_name[64]; @@ -1870,11 +1684,12 @@ struct ServerDzSetDuration_Struct { uint32 seconds; }; -struct ServerDzCreateSerialized_Struct { +struct ServerDzCreate_Struct { uint16_t origin_zone_id; uint16_t origin_instance_id; + uint32_t dz_id; uint32_t cereal_size; - char cereal_data[0]; + char cereal_data[1]; }; struct ServerSendPlayerEvent_Struct { @@ -1964,6 +1779,7 @@ struct BazaarPurchaseMessaging_Struct { uint32 id; }; + #pragma pack() #endif diff --git a/common/shared_tasks.h b/common/shared_tasks.h index b9ee0c065..b12053a4c 100644 --- a/common/shared_tasks.h +++ b/common/shared_tasks.h @@ -48,7 +48,7 @@ enum class SharedTaskRequestGroupType { struct ServerSharedTaskRequest_Struct { uint32 requested_character_id; uint32 requested_task_id; - uint32 requested_npc_type_id; // original task logic passthrough + uint32 requested_npc_entity_id; // original task logic passthrough uint32 accept_time; }; diff --git a/common/shareddb.cpp b/common/shareddb.cpp index ac7a3a173..0ef0cbe4e 100644 --- a/common/shareddb.cpp +++ b/common/shareddb.cpp @@ -37,30 +37,24 @@ #include "strings.h" #include "eqemu_config.h" #include "data_verification.h" +#include "evolving_items.h" #include "repositories/criteria/content_filter_criteria.h" #include "repositories/account_repository.h" #include "repositories/faction_association_repository.h" #include "repositories/starting_items_repository.h" #include "path_manager.h" +#include "../zone/client.h" #include "repositories/loottable_repository.h" #include "repositories/character_item_recast_repository.h" #include "repositories/character_corpses_repository.h" #include "repositories/skill_caps_repository.h" #include "repositories/inventory_repository.h" #include "repositories/books_repository.h" - -namespace ItemField -{ - enum { - source = 0, -#define F(x) x, -#include "item_fieldlist.h" -#undef F - updated, - minstatus, - comment, - }; -} +#include "repositories/sharedbank_repository.h" +#include "repositories/character_inspect_messages_repository.h" +#include "repositories/spells_new_repository.h" +#include "repositories/damageshieldtypes_repository.h" +#include "repositories/items_repository.h" SharedDatabase::SharedDatabase() : Database() @@ -128,327 +122,321 @@ bool SharedDatabase::SetGMFlymode(uint32 account_id, uint8 flymode) return a.id > 0; } -uint32 SharedDatabase::GetTotalTimeEntitledOnAccount(uint32 AccountID) { - uint32 EntitledTime = 0; - const std::string query = StringFormat("SELECT `time_played` FROM `character_data` WHERE `account_id` = %u", AccountID); - auto results = QueryDatabase(query); - for (auto& row = results.begin(); row != results.end(); ++row) { - EntitledTime += Strings::ToUnsignedInt(row[0]); +void SharedDatabase::SetMailKey(uint32 character_id, uint32 ip_address, uint32 mail_key) +{ + std::string full_mail_key; + + if (RuleB(Chat, EnableMailKeyIPVerification)) { + full_mail_key = fmt::format("{:08X}{:08X}", ip_address, mail_key); + } else { + full_mail_key = fmt::format("{:08X}", mail_key); + } + + auto e = CharacterDataRepository::FindOne(*this, character_id); + if (!e.id) { + LogError("Failed to find character_id [{}] when setting mailkey", character_id); + return; + } + + e.mailkey = full_mail_key; + + if (!CharacterDataRepository::UpdateOne(*this, e)) { + LogError("Failed to set mailkey to [{}] for character_id [{}]", full_mail_key, character_id); } - return EntitledTime; } -void SharedDatabase::SetMailKey(int CharID, int IPAddress, int MailKey) +SharedDatabase::MailKeys SharedDatabase::GetMailKey(uint32 character_id) { - char mail_key[17]; + auto e = CharacterDataRepository::FindOne(*this, character_id); - if (RuleB(Chat, EnableMailKeyIPVerification) == true) { - sprintf(mail_key, "%08X%08X", IPAddress, MailKey); - } - else { - sprintf(mail_key, "%08X", MailKey); + if (!e.id) { + return MailKeys{ }; } - const std::string query = StringFormat( - "UPDATE character_data SET mailkey = '%s' WHERE id = '%i'", - mail_key, CharID + return MailKeys{ + .mail_key = e.mailkey.substr(8), + .mail_key_full = e.mailkey + }; +} + +bool SharedDatabase::SaveCursor( + uint32 char_id, + std::list::const_iterator& start, + std::list::const_iterator& end +) +{ + const int deleted = InventoryRepository::DeleteWhere( + *this, + fmt::format( + "`character_id` = {} AND (`slot_id` = {} OR `slot_id` BETWEEN {} AND {})", + char_id, + EQ::invslot::slotCursor, + EQ::invbag::CURSOR_BAG_BEGIN, + EQ::invbag::CURSOR_BAG_END + ) ); - const auto results = QueryDatabase(query); - if (!results.Success()) { - LogError("SharedDatabase::SetMailKey({}, {}) : {}", CharID, mail_key, results.ErrorMessage().c_str()); - } -} + int16 i = EQ::invbag::CURSOR_BAG_BEGIN; + for (auto& it = start; it != end; ++it, i++) { + // shouldn't be anything in the queue that indexes this high + if (i > EQ::invbag::CURSOR_BAG_END) { + break; + } -SharedDatabase::MailKeys SharedDatabase::GetMailKey(int character_id) -{ - const std::string query = StringFormat("SELECT `mailkey` FROM `character_data` WHERE `id`='%i' LIMIT 1", character_id); - auto results = QueryDatabase(query); - if (!results.Success()) { - return MailKeys{}; - } - - if (!results.RowCount()) { - Log(Logs::General, - Logs::ClientLogin, - "Error: Mailkey for character id [%i] does not exist or could not be found", - character_id - ); - return MailKeys{}; - } - - auto &row = results.begin(); - if (row != results.end()) { - std::string mail_key = row[0]; - - return MailKeys{ - .mail_key = mail_key.substr(8), - .mail_key_full = mail_key - }; - } - - return MailKeys{}; -} - -bool SharedDatabase::SaveCursor(uint32 char_id, std::list::const_iterator &start, std::list::const_iterator &end) -{ - // Delete cursor items - const std::string query = StringFormat("DELETE FROM inventory WHERE charid = %i " - "AND ((slotid >= 8000 AND slotid <= 8999) " - "OR slotid = %i OR (slotid >= %i AND slotid <= %i) )", - char_id, EQ::invslot::slotCursor, - EQ::invbag::CURSOR_BAG_BEGIN, EQ::invbag::CURSOR_BAG_END); - const auto results = QueryDatabase(query); - if (!results.Success()) { - std::cout << "Clearing cursor failed: " << results.ErrorMessage() << std::endl; - return false; - } - - int i = 8000; - for(auto& it = start; it != end; ++it, i++) { - if (i > 8999) { break; } // shouldn't be anything in the queue that indexes this high - const EQ::ItemInstance *inst = *it; - const int16 use_slot = (i == 8000) ? EQ::invslot::slotCursor : i; + const EQ::ItemInstance* inst = *it; + const int16 use_slot = i == EQ::invbag::CURSOR_BAG_BEGIN ? EQ::invslot::slotCursor : i; if (!SaveInventory(char_id, inst, use_slot)) { return false; } - } + } return true; } bool SharedDatabase::VerifyInventory(uint32 account_id, int16 slot_id, const EQ::ItemInstance* inst) { - // Delete cursor items - const std::string query = StringFormat("SELECT itemid, charges FROM sharedbank " - "WHERE acctid = %d AND slotid = %d", - account_id, slot_id); - auto results = QueryDatabase(query); - if (!results.Success()) { - //returning true is less harmful in the face of a query error - return true; + if (!inst || !inst->GetItem()) { + return false; } - if (results.RowCount() == 0) - return false; + const auto& l = SharedbankRepository::GetWhere( + *this, + fmt::format( + "`account_id` = {} AND `slot_id` = {} LIMIT 1", + account_id, + slot_id + ) + ); - auto& row = results.begin(); + if (l.empty()) { + return false; + } - const uint32 id = Strings::ToUnsignedInt(row[0]); - const uint16 charges = Strings::ToUnsignedInt(row[1]); + const auto& e = l.front(); - uint16 expect_charges; + uint16 expect_charges = inst->GetCharges() >= 0 ? inst->GetCharges() : std::numeric_limits::max(); - if(inst->GetCharges() >= 0) - expect_charges = inst->GetCharges(); - else - expect_charges = 0x7FFF; - - if(id != inst->GetItem()->ID || charges != expect_charges) - return false; - - return true; + return e.item_id == inst->GetID() && e.charges == expect_charges; } -bool SharedDatabase::SaveInventory(uint32 char_id, const EQ::ItemInstance* inst, int16 slot_id) { - - //never save tribute slots: - if (slot_id >= EQ::invslot::TRIBUTE_BEGIN && slot_id <= EQ::invslot::TRIBUTE_END) - return true; - if (slot_id >= EQ::invslot::GUILD_TRIBUTE_BEGIN && slot_id <= EQ::invslot::GUILD_TRIBUTE_END) +bool SharedDatabase::SaveInventory(uint32 char_id, const EQ::ItemInstance* inst, int16 slot_id) +{ + // Don't save any Tribute slots + if ( + EQ::ValueWithin(slot_id, EQ::invslot::GUILD_TRIBUTE_BEGIN, EQ::invslot::GUILD_TRIBUTE_END) || + EQ::ValueWithin(slot_id, EQ::invslot::TRIBUTE_BEGIN, EQ::invslot::TRIBUTE_END) + ) { return true; + } - if (slot_id >= EQ::invslot::SHARED_BANK_BEGIN && slot_id <= EQ::invbag::SHARED_BANK_BAGS_END) { - // Shared bank inventory + if ( + EQ::ValueWithin(slot_id, EQ::invslot::SHARED_BANK_BEGIN, EQ::invslot::SHARED_BANK_END) || + EQ::ValueWithin(slot_id, EQ::invbag::SHARED_BANK_BAGS_BEGIN, EQ::invbag::SHARED_BANK_BAGS_END) + ) { if (!inst) { return DeleteSharedBankSlot(char_id, slot_id); - } - else { + } else { // Needed to clear out bag slots that 'REPLACE' in UpdateSharedBankSlot does not overwrite..otherwise, duplication occurs // (This requires that parent then child items be sent..which should be how they are currently passed) - if (EQ::InventoryProfile::SupportsContainers(slot_id)) + if (EQ::InventoryProfile::SupportsContainers(slot_id)) { DeleteSharedBankSlot(char_id, slot_id); + } + return UpdateSharedBankSlot(char_id, inst, slot_id); } - } - else if (!inst) { // All other inventory + } else if (!inst) { // All other inventory return DeleteInventorySlot(char_id, slot_id); } // Needed to clear out bag slots that 'REPLACE' in UpdateInventorySlot does not overwrite..otherwise, duplication occurs // (This requires that parent then child items be sent..which should be how they are currently passed) - if (EQ::InventoryProfile::SupportsContainers(slot_id)) + if (EQ::InventoryProfile::SupportsContainers(slot_id)) { DeleteInventorySlot(char_id, slot_id); - return UpdateInventorySlot(char_id, inst, slot_id); -} - -bool SharedDatabase::UpdateInventorySlot(uint32 char_id, const EQ::ItemInstance* inst, int16 slot_id) { - // need to check 'inst' argument for valid pointer - - uint32 augslot[EQ::invaug::SOCKET_COUNT] = { 0, 0, 0, 0, 0, 0 }; - if (inst->IsClassCommon()) { - for (int i = EQ::invaug::SOCKET_BEGIN; i <= EQ::invaug::SOCKET_END; i++) { - const EQ::ItemInstance *auginst = inst->GetItem(i); - augslot[i] = (auginst && auginst->GetItem()) ? auginst->GetItem()->ID : 0; - } } - uint16 charges; - if(inst->GetCharges() >= 0) - charges = inst->GetCharges(); - else - charges = 0x7FFF; - - // Update/Insert item - const std::string query = StringFormat("REPLACE INTO inventory " - "(charid, slotid, itemid, charges, instnodrop, custom_data, color, " - "augslot1, augslot2, augslot3, augslot4, augslot5, augslot6, ornamenticon, ornamentidfile, ornament_hero_model, guid) " - "VALUES( %lu, %lu, %lu, %lu, %lu, '%s', %lu, " - "%lu, %lu, %lu, %lu, %lu, %lu, %lu, %lu, %lu, %lu)", - static_cast(char_id), static_cast(slot_id), static_cast(inst->GetItem()->ID), - static_cast(charges), static_cast(inst->IsAttuned() ? 1 : 0), - inst->GetCustomDataString().c_str(), static_cast(inst->GetColor()), - static_cast(augslot[0]), static_cast(augslot[1]), static_cast(augslot[2]), - static_cast(augslot[3]), static_cast(augslot[4]), static_cast(augslot[5]), static_cast(inst->GetOrnamentationIcon()), - static_cast(inst->GetOrnamentationIDFile()), static_cast(inst->GetOrnamentHeroModel()), inst->GetSerialNumber()); - const auto results = QueryDatabase(query); - - // Save bag contents, if slot supports bag contents - if (inst->IsClassBag() && EQ::InventoryProfile::SupportsContainers(slot_id)) - // Limiting to bag slot count will get rid of 'hidden' duplicated items and 'Invalid Slot ID' - // messages through attrition (and the modded code in SaveInventory) - for (uint8 idx = EQ::invbag::SLOT_BEGIN; idx < inst->GetItem()->BagSlots && idx <= EQ::invbag::SLOT_END; idx++) { - const EQ::ItemInstance* baginst = inst->GetItem(idx); - SaveInventory(char_id, baginst, EQ::InventoryProfile::CalcSlotId(slot_id, idx)); - } - - if (!results.Success()) { - return false; - } - - return true; + return UpdateInventorySlot(char_id, inst, slot_id); } -bool SharedDatabase::UpdateSharedBankSlot(uint32 char_id, const EQ::ItemInstance* inst, int16 slot_id) { - // need to check 'inst' argument for valid pointer - - uint32 augslot[EQ::invaug::SOCKET_COUNT] = { 0, 0, 0, 0, 0, 0 }; - if (inst->IsClassCommon()) { - for (int i = EQ::invaug::SOCKET_BEGIN; i <= EQ::invaug::SOCKET_END; i++) { - const EQ::ItemInstance *auginst = inst->GetItem(i); - augslot[i] = (auginst && auginst->GetItem()) ? auginst->GetItem()->ID : 0; - } - } - -// Update/Insert item - const uint32 account_id = GetAccountIDByChar(char_id); - uint16 charges; - if(inst->GetCharges() >= 0) - charges = inst->GetCharges(); - else - charges = 0x7FFF; - - const std::string query = StringFormat("REPLACE INTO sharedbank " - "(acctid, slotid, itemid, charges, custom_data, " - "augslot1, augslot2, augslot3, augslot4, augslot5, augslot6) " - "VALUES( %lu, %lu, %lu, %lu, '%s', " - "%lu, %lu, %lu, %lu, %lu, %lu)", - static_cast(account_id), static_cast(slot_id), static_cast(inst->GetItem()->ID), - static_cast(charges), inst->GetCustomDataString().c_str(), static_cast(augslot[0]), - static_cast(augslot[1]), static_cast(augslot[2]), static_cast(augslot[3]), static_cast(augslot[4]), - static_cast(augslot[5])); - const auto results = QueryDatabase(query); - - // Save bag contents, if slot supports bag contents - if (inst->IsClassBag() && EQ::InventoryProfile::SupportsContainers(slot_id)) { - // Limiting to bag slot count will get rid of 'hidden' duplicated items and 'Invalid Slot ID' - // messages through attrition (and the modded code in SaveInventory) - for (uint8 idx = EQ::invbag::SLOT_BEGIN; idx < inst->GetItem()->BagSlots && idx <= EQ::invbag::SLOT_END; idx++) { - const EQ::ItemInstance* baginst = inst->GetItem(idx); - SaveInventory(char_id, baginst, EQ::InventoryProfile::CalcSlotId(slot_id, idx)); - } - } - - if (!results.Success()) { - return false; - } - - return true; -} - -bool SharedDatabase::DeleteInventorySlot(uint32 char_id, int16 slot_id) { - - // Delete item - std::string query = StringFormat("DELETE FROM inventory WHERE charid = %i AND slotid = %i", char_id, slot_id); - auto results = QueryDatabase(query); - if (!results.Success()) { - return false; - } - - // Delete bag slots, if need be - if (!EQ::InventoryProfile::SupportsContainers(slot_id)) - return true; - - const int16 base_slot_id = EQ::InventoryProfile::CalcSlotId(slot_id, EQ::invbag::SLOT_BEGIN); - query = StringFormat("DELETE FROM inventory WHERE charid = %i AND slotid >= %i AND slotid < %i", - char_id, base_slot_id, (base_slot_id+10)); - results = QueryDatabase(query); - if (!results.Success()) { - return false; - } - - // @merth: need to delete augments here - return true; -} - -bool SharedDatabase::DeleteSharedBankSlot(uint32 char_id, int16 slot_id) { - - // Delete item - const uint32 account_id = GetAccountIDByChar(char_id); - std::string query = StringFormat("DELETE FROM sharedbank WHERE acctid=%i AND slotid=%i", account_id, slot_id); - auto results = QueryDatabase(query); - if (!results.Success()) { - return false; - } - - // Delete bag slots, if need be - if (!EQ::InventoryProfile::SupportsContainers(slot_id)) - return true; - - const int16 base_slot_id = EQ::InventoryProfile::CalcSlotId(slot_id, EQ::invbag::SLOT_BEGIN); - query = StringFormat("DELETE FROM sharedbank WHERE acctid = %i " - "AND slotid >= %i AND slotid < %i", - account_id, base_slot_id, (base_slot_id+10)); - results = QueryDatabase(query); - if (!results.Success()) { - return false; - } - - // @merth: need to delete augments here - return true; -} - - -int32 SharedDatabase::GetSharedPlatinum(uint32 account_id) +bool SharedDatabase::UpdateInventorySlot(uint32 char_id, const EQ::ItemInstance* inst, int16 slot_id) { - const auto query = fmt::format("SELECT sharedplat FROM account WHERE id = {}", account_id); - auto results = QueryDatabase(query); - if (!results.Success() || !results.RowCount()) { - return 0; - } - - auto row = results.begin(); - return Strings::ToInt(row[0]); -} - -bool SharedDatabase::SetSharedPlatinum(uint32 account_id, int32 amount_to_add) { - const std::string query = StringFormat("UPDATE account SET sharedplat = sharedplat + %i WHERE id = %i", amount_to_add, account_id); - const auto results = QueryDatabase(query); - if (!results.Success()) { + if (!inst || !inst->GetItem()) { return false; } - return true; + std::vector augment_ids = inst->GetAugmentIDs(); + + uint16 charges = inst->GetCharges() >= 0 ? inst->GetCharges() : std::numeric_limits::max(); + + auto e = InventoryRepository::NewEntity(); + + e.character_id = char_id; + e.slot_id = slot_id; + e.item_id = inst->GetID(); + e.charges = charges; + e.color = inst->GetColor(); + e.augment_one = augment_ids[0]; + e.augment_two = augment_ids[1]; + e.augment_three = augment_ids[2]; + e.augment_four = augment_ids[3]; + e.augment_five = augment_ids[4]; + e.augment_six = augment_ids[5]; + e.instnodrop = inst->IsAttuned() ? 1 : 0; + e.custom_data = inst->GetCustomDataString(); + e.ornament_icon = inst->GetOrnamentationIcon(); + e.ornament_idfile = inst->GetOrnamentationIDFile(); + e.ornament_hero_model = inst->GetOrnamentHeroModel(); + e.guid = inst->GetSerialNumber(); + + const int replaced = InventoryRepository::ReplaceOne(*this, e); + + // Save bag contents, if slot supports bag contents + if (inst->IsClassBag() && EQ::InventoryProfile::SupportsContainers(slot_id)) { + // Limiting to bag slot count will get rid of 'hidden' duplicated items and 'Invalid Slot ID' + // messages through attrition (and the modded code in SaveInventory) + for ( + uint8 i = EQ::invbag::SLOT_BEGIN; + i < inst->GetItem()->BagSlots && i <= EQ::invbag::SLOT_END; + i++ + ) { + const EQ::ItemInstance* bag_inst = inst->GetItem(i); + SaveInventory(char_id, bag_inst, EQ::InventoryProfile::CalcSlotId(slot_id, i)); + } + } + + return replaced; +} + +bool SharedDatabase::UpdateSharedBankSlot(uint32 char_id, const EQ::ItemInstance* inst, int16 slot_id) +{ + if (!inst || !inst->GetItem()) { + return false; + } + + std::vector augment_ids = inst->GetAugmentIDs(); + + uint16 charges = inst->GetCharges() >= 0 ? inst->GetCharges() : std::numeric_limits::max(); + + const uint32 account_id = GetAccountIDByChar(char_id); + + auto e = SharedbankRepository::NewEntity(); + + e.account_id = account_id; + e.slot_id = slot_id; + e.item_id = inst->GetID(); + e.charges = charges; + e.color = inst->GetColor(); + e.augment_one = augment_ids[0]; + e.augment_two = augment_ids[1]; + e.augment_three = augment_ids[2]; + e.augment_four = augment_ids[3]; + e.augment_five = augment_ids[4]; + e.augment_six = augment_ids[5]; + e.custom_data = inst->GetCustomDataString(); + e.ornament_icon = inst->GetOrnamentationIcon(); + e.ornament_idfile = inst->GetOrnamentationIDFile(); + e.ornament_hero_model = inst->GetOrnamentHeroModel(); + e.guid = inst->GetSerialNumber(); + + const int replaced = SharedbankRepository::ReplaceOne(*this, e); + + // Save bag contents, if slot supports bag contents + if (inst->IsClassBag() && EQ::InventoryProfile::SupportsContainers(slot_id)) { + // Limiting to bag slot count will get rid of 'hidden' duplicated items and 'Invalid Slot ID' + // messages through attrition (and the modded code in SaveInventory) + for ( + uint8 i = EQ::invbag::SLOT_BEGIN; + i < inst->GetItem()->BagSlots && i <= EQ::invbag::SLOT_END; + i++ + ) { + const EQ::ItemInstance* bag_inst = inst->GetItem(i); + SaveInventory(char_id, bag_inst, EQ::InventoryProfile::CalcSlotId(slot_id, i)); + } + } + + return replaced; +} + +bool SharedDatabase::DeleteInventorySlot(uint32 char_id, int16 slot_id) +{ + const int deleted = InventoryRepository::DeleteWhere( + *this, + fmt::format( + "`character_id` = {} AND `slot_id` = {}", + char_id, + slot_id + ) + ); + + if (!deleted) { + return false; + } + + // Delete bag slots, if need be + if (!EQ::InventoryProfile::SupportsContainers(slot_id)) { + return true; + } + + const int16 base_slot_id = EQ::InventoryProfile::CalcSlotId(slot_id, EQ::invbag::SLOT_BEGIN); + + return InventoryRepository::DeleteWhere( + *this, + fmt::format( + "`character_id` = {} AND `slot_id` BETWEEN {} AND {}", + char_id, + base_slot_id, + base_slot_id + (EQ::invbag::SLOT_COUNT - 1) + ) + ); +} + +bool SharedDatabase::DeleteSharedBankSlot(uint32 char_id, int16 slot_id) +{ + const uint32 account_id = GetAccountIDByChar(char_id); + + const int deleted = SharedbankRepository::DeleteWhere( + *this, + fmt::format( + "`account_id` = {} AND `slot_id` = {}", + account_id, + slot_id + ) + ); + + if (!deleted) { + return false; + } + + if (!EQ::InventoryProfile::SupportsContainers(slot_id)) { + return true; + } + + const int16 base_slot_id = EQ::InventoryProfile::CalcSlotId(slot_id, EQ::invbag::SLOT_BEGIN); + + return SharedbankRepository::DeleteWhere( + *this, + fmt::format( + "`account_id` = {} AND `slot_id` BETWEEN {} AND {}", + account_id, + base_slot_id, + base_slot_id + (EQ::invbag::SLOT_COUNT - 1) + ) + ); +} + +int32 SharedDatabase::GetSharedPlatinum(uint32 account_id) +{ + const auto& e = AccountRepository::FindOne(*this, account_id); + + return e.sharedplat; +} + +bool SharedDatabase::AddSharedPlatinum(uint32 account_id, int amount) +{ + auto e = AccountRepository::FindOne(*this, account_id); + + e.sharedplat += amount; + + return AccountRepository::UpdateOne(*this, e); } bool SharedDatabase::SetStartingItems( @@ -550,169 +538,159 @@ bool SharedDatabase::SetStartingItems( // Retrieve shared bank inventory based on either account or character bool SharedDatabase::GetSharedBank(uint32 id, EQ::InventoryProfile *inv, bool is_charid) { - std::string query; + const uint32 account_id = is_charid ? GetAccountIDByChar(id) : id; - if (is_charid) { - query = fmt::format( - "SELECT sb.slotid, sb.itemid, sb.charges, " - "sb.augslot1, sb.augslot2, sb.augslot3, " - "sb.augslot4, sb.augslot5, sb.augslot6, sb.custom_data " - "FROM sharedbank sb INNER JOIN character_data ch " - "ON ch.account_id = sb.acctid WHERE ch.id = {} ORDER BY sb.slotid", - id - ); - } else { - query = fmt::format( - "SELECT slotid, itemid, charges, " - "augslot1, augslot2, augslot3, " - "augslot4, augslot5, augslot6, custom_data " - "FROM sharedbank WHERE acctid = {} ORDER BY slotid", - id - ); - } - - auto results = QueryDatabase(query); - // If we have no results we still need to return true - if (!results.Success()) { + if (!account_id) { return false; } - for (auto row : results) { - int16 slot_id = static_cast(Strings::ToInt(row[0])); - uint32 item_id = Strings::ToUnsignedInt(row[1]); - const int16 charges = static_cast(Strings::ToInt(row[2])); + const auto& l = SharedbankRepository::GetWhere( + *this, + fmt::format( + "`account_id` = {}", + account_id + ) + ); - uint32 aug[EQ::invaug::SOCKET_COUNT]; - aug[0] = Strings::ToUnsignedInt(row[3]); - aug[1] = Strings::ToUnsignedInt(row[4]); - aug[2] = Strings::ToUnsignedInt(row[5]); - aug[3] = Strings::ToUnsignedInt(row[6]); - aug[4] = Strings::ToUnsignedInt(row[7]); - aug[5] = Strings::ToUnsignedInt(row[8]); + if (l.empty()) { + return true; + } - const EQ::ItemData *item = GetItem(item_id); + for (const auto& e : l) { + uint32 augment_ids[EQ::invaug::SOCKET_COUNT] = { + e.augment_one, + e.augment_two, + e.augment_three, + e.augment_four, + e.augment_five, + e.augment_six + }; + + const EQ::ItemData* item = GetItem(e.item_id); if (!item) { LogError( - "Warning: [{}] [{}] has an invalid item_id [{}] in inventory slot [{}]", - is_charid ? "charid" : "acctid", + "Warning: {} [{}] has an invalid item_id [{}] in slot_id [{}]", + is_charid ? "character_id" : "account_id", id, - item_id, - slot_id + e.item_id, + e.slot_id ); continue; } - auto inst = CreateBaseItem(item, charges); + EQ::ItemInstance* inst = CreateBaseItem(item, e.charges); if (!inst) { continue; } - if (inst && item->IsClassCommon()) { + if (item->IsClassCommon()) { for (int i = EQ::invaug::SOCKET_BEGIN; i <= EQ::invaug::SOCKET_END; i++) { - if (aug[i]) { - inst->PutAugment(this, i, aug[i]); + if (augment_ids[i]) { + inst->PutAugment(this, i, augment_ids[i]); } } } - if (inst && row[9]) { - std::string data_str(row[9]); - inst->SetCustomDataString(data_str); + if (!e.custom_data.empty()) { + inst->SetCustomDataString(e.custom_data); } - // theoretically inst can be nullptr ... this would be very bad ... - const int16 put_slot_id = inv->PutItem(slot_id, *inst); + const int16 put_slot_id = inv->PutItem(e.slot_id, *inst); safe_delete(inst); - // Save ptr to item in inventory if (put_slot_id != INVALID_INDEX) { continue; } LogError( - "Warning: Invalid slot_id for item in shared bank inventory: [{}]=[{}], item_id=[{}], slot_id=[{}]", - is_charid ? "charid" : "acctid", + "Warning: Invalid slot_id for item in shared bank inventory for {} [{}] item_id [{}] slot_id [{}]", + is_charid ? "character_id" : "account_id", id, - item_id, - slot_id + e.item_id, + e.slot_id ); if (is_charid) { - SaveInventory(id, nullptr, slot_id); + SaveInventory(id, nullptr, e.slot_id); } } return true; } - // Overloaded: Retrieve character inventory based on character id (zone entry) -bool SharedDatabase::GetInventory(uint32 char_id, EQ::InventoryProfile *inv) +//bool SharedDatabase::GetInventory(uint32 char_id, EQ::InventoryProfile *inv) +bool SharedDatabase::GetInventory(Client *c) { - if (!char_id || !inv) + if (!c) { return false; + } + + uint32 char_id = c->CharacterID(); + EQ::InventoryProfile &inv = c->GetInv(); // Retrieve character inventory - auto results = InventoryRepository::GetWhere(*this, fmt::format("`charid` = '{}' ORDER BY `slotid`;", char_id)); + auto results = InventoryRepository::GetWhere(*this, fmt::format("`character_id` = '{}' ORDER BY `slot_id`", char_id)); + auto e_results = CharacterEvolvingItemsRepository::GetWhere( + *this, fmt::format("`character_id` = '{}' AND `deleted_at` IS NULL", char_id) + ); + if (results.empty()) { LogError("Error loading inventory for char_id {} from the database.", char_id); return false; } - for (auto const &row: results) { + for (auto const& row: results) { if (row.guid != 0) { EQ::ItemInstance::AddGUIDToMap(row.guid); } } - const auto timestamps = GetItemRecastTimestamps(char_id); - auto cv_conflict = false; - const auto pmask = inv->GetLookup()->PossessionsBitmask; - const auto bank_size = inv->GetLookup()->InventoryTypeSize.Bank; + const auto timestamps = GetItemRecastTimestamps(char_id); + auto cv_conflict = false; + const auto pmask = inv.GetLookup()->PossessionsBitmask; + const auto bank_size = inv.GetLookup()->InventoryTypeSize.Bank; - std::vector queue{}; - for (auto &row: results) { - const int16 slot_id = row.slotid; - const uint32 item_id = row.itemid; + std::vector queue{ }; + for (auto& row: results) { + const int16 slot_id = row.slot_id; + const uint32 item_id = row.item_id; const uint16 charges = row.charges; const uint32 color = row.color; - const bool instnodrop = row.instnodrop; - const uint32 ornament_icon = row.ornamenticon; - const uint32 ornament_idfile = row.ornamentidfile; + const bool instnodrop = row.instnodrop; + const uint32 ornament_icon = row.ornament_icon; + const uint32 ornament_idfile = row.ornament_idfile; const uint32 ornament_hero_model = row.ornament_hero_model; - uint32 aug[EQ::invaug::SOCKET_COUNT]; - aug[0] = row.augslot1; - aug[1] = row.augslot2; - aug[2] = row.augslot3; - aug[3] = row.augslot4; - aug[4] = row.augslot5; - aug[5] = row.augslot6; + uint32 augment_ids[EQ::invaug::SOCKET_COUNT] = { + row.augment_one, + row.augment_two, + row.augment_three, + row.augment_four, + row.augment_five, + row.augment_six + }; - if (slot_id <= EQ::invslot::POSSESSIONS_END && slot_id >= EQ::invslot::POSSESSIONS_BEGIN) { + if (EQ::ValueWithin(slot_id, EQ::invslot::POSSESSIONS_BEGIN, EQ::invslot::POSSESSIONS_END)) { // Titanium thru UF check if (((static_cast(1) << slot_id) & pmask) == 0) { cv_conflict = true; continue; } - } - else if (slot_id <= EQ::invbag::GENERAL_BAGS_END && slot_id >= EQ::invbag::GENERAL_BAGS_BEGIN) { + } else if (EQ::ValueWithin(slot_id, EQ::invbag::GENERAL_BAGS_BEGIN, EQ::invbag::GENERAL_BAGS_END)) { // Titanium thru UF check - const auto parent_slot = EQ::invslot::GENERAL_BEGIN + ( - (slot_id - EQ::invbag::GENERAL_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT); + const auto parent_slot = EQ::invslot::GENERAL_BEGIN + ((slot_id - EQ::invbag::GENERAL_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT); if (((static_cast(1) << parent_slot) & pmask) == 0) { cv_conflict = true; continue; } - } - else if (slot_id <= EQ::invslot::BANK_END && slot_id >= EQ::invslot::BANK_BEGIN) { + } else if (EQ::ValueWithin(slot_id, EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END)) { // Titanium check if ((slot_id - EQ::invslot::BANK_BEGIN) >= bank_size) { cv_conflict = true; continue; } - } - else if (slot_id <= EQ::invbag::BANK_BAGS_END && slot_id >= EQ::invbag::BANK_BAGS_BEGIN) { + } else if (EQ::ValueWithin(slot_id, EQ::invbag::BANK_BAGS_BEGIN, EQ::invbag::BANK_BAGS_END)) { // Titanium check const auto parent_index = ((slot_id - EQ::invbag::BANK_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT); if (parent_index >= bank_size) { @@ -721,7 +699,7 @@ bool SharedDatabase::GetInventory(uint32 char_id, EQ::InventoryProfile *inv) } } - auto *item = GetItem(item_id); + auto* item = GetItem(item_id); if (!item) { LogError( "Warning: charid [{}] has an invalid item_id [{}] in inventory slot [{}]", @@ -732,7 +710,7 @@ bool SharedDatabase::GetInventory(uint32 char_id, EQ::InventoryProfile *inv) continue; } - auto *inst = CreateBaseItem(item, charges); + auto* inst = CreateBaseItem(item, charges); if (!inst) { continue; } @@ -745,8 +723,13 @@ bool SharedDatabase::GetInventory(uint32 char_id, EQ::InventoryProfile *inv) inst->SetOrnamentationIDFile(ornament_idfile); inst->SetOrnamentHeroModel(item->HerosForgeModel); - if (instnodrop || (inst->GetItem()->Attuneable && slot_id >= EQ::invslot::EQUIPMENT_BEGIN && slot_id <= - EQ::invslot::EQUIPMENT_END)) { + if ( + instnodrop || + ( + inst->GetItem()->Attuneable && + EQ::ValueWithin(slot_id, EQ::invslot::EQUIPMENT_BEGIN, EQ::invslot::EQUIPMENT_END) + ) + ) { inst->SetAttuned(true); } @@ -754,53 +737,85 @@ bool SharedDatabase::GetInventory(uint32 char_id, EQ::InventoryProfile *inv) inst->SetColor(color); } - if (charges == 0x7FFF) { + if (charges == std::numeric_limits::max()) { inst->SetCharges(-1); - } - else if (charges == 0 && inst->IsStackable()) { + } else if (charges == 0 && inst->IsStackable()) { // Stackable items need a minimum charge of 1 remain moveable. inst->SetCharges(1); - } - else { + } else { inst->SetCharges(charges); } if (item->RecastDelay) { if (item->RecastType != RECAST_TYPE_UNLINKED_ITEM && timestamps.count(item->RecastType)) { inst->SetRecastTimestamp(timestamps.at(item->RecastType)); - } - else if (item->RecastType == RECAST_TYPE_UNLINKED_ITEM && timestamps.count(item->ID)) { + } else if (item->RecastType == RECAST_TYPE_UNLINKED_ITEM && timestamps.count(item->ID)) { inst->SetRecastTimestamp(timestamps.at(item->ID)); - } - else { + } else { inst->SetRecastTimestamp(0); } } if (item->IsClassCommon()) { for (int i = EQ::invaug::SOCKET_BEGIN; i <= EQ::invaug::SOCKET_END; i++) { - if (aug[i]) { - inst->PutAugment(this, i, aug[i]); + if (augment_ids[i]) { + inst->PutAugment(this, i, augment_ids[i]); } } } - int16 put_slot_id; - if (slot_id >= 8000 && slot_id <= 8999) { - put_slot_id = inv->PushCursor(*inst); - } - else if (slot_id >= 3111 && slot_id <= 3179) { - // Admins: please report any occurrences of this error - LogError( - "Warning: Defunct location for item in inventory: charid={}, item_id={}, slot_id={} .. pushing to cursor...", - char_id, - item_id, - slot_id + if (item->EvolvingItem) { + if (slot_id >= EQ::invslot::EQUIPMENT_BEGIN && slot_id <= EQ::invslot::EQUIPMENT_END) { + inst->SetEvolveEquipped(true); + } + + auto t = std::ranges::find_if( + e_results.cbegin(), + e_results.cend(), + [&](const CharacterEvolvingItemsRepository::CharacterEvolvingItems &x) { + return x.item_id == item_id; + } ); - put_slot_id = inv->PushCursor(*inst); + + if (t == std::end(e_results)) { + auto e = CharacterEvolvingItemsRepository::NewEntity(); + + e.character_id = char_id; + e.item_id = item_id; + e.equipped = inst->GetEvolveEquipped(); + e.final_item_id = EvolvingItemsManager::Instance()->GetFinalItemID(*inst); + + auto r = CharacterEvolvingItemsRepository::InsertOne(*this, e); + e.id = r.id; + e_results.push_back(e); + + inst->SetEvolveUniqueID(e.id); + inst->SetEvolveCharID(e.character_id); + inst->SetEvolveItemID(e.item_id); + inst->SetEvolveActivated(e.activated); + inst->SetEvolveEquipped(e.equipped); + inst->SetEvolveCurrentAmount(e.current_amount); + inst->CalculateEvolveProgression(); + inst->SetEvolveFinalItemID(e.final_item_id); + } + else { + inst->SetEvolveUniqueID(t->id); + inst->SetEvolveCharID(t->character_id); + inst->SetEvolveItemID(t->item_id); + inst->SetEvolveActivated(t->activated); + inst->SetEvolveEquipped(t->equipped); + inst->SetEvolveCurrentAmount(t->current_amount); + inst->CalculateEvolveProgression(); + inst->SetEvolveFinalItemID(t->final_item_id); + } } - else { - put_slot_id = inv->PutItem(slot_id, *inst); + + int16 put_slot_id; + // this had || slot_id == EQ::invslot::slotCursor ??s + if (EQ::ValueWithin(slot_id, EQ::invbag::CURSOR_BAG_BEGIN, EQ::invbag::CURSOR_BAG_END)) { + put_slot_id = inv.PushCursor(*inst); + } else { + put_slot_id = inv.PutItem(slot_id, *inst); } row.guid = inst->GetSerialNumber(); @@ -811,7 +826,7 @@ bool SharedDatabase::GetInventory(uint32 char_id, EQ::InventoryProfile *inv) // Save ptr to item in inventory if (put_slot_id == INVALID_INDEX) { LogError( - "Warning: Invalid slot_id for item in inventory: charid=[{}], item_id=[{}], slot_id=[{}]", + "Warning: Invalid slot_id for item in inventory for character_id [{}] item_id [{}] slot_id [{}]", char_id, item_id, slot_id @@ -820,13 +835,13 @@ bool SharedDatabase::GetInventory(uint32 char_id, EQ::InventoryProfile *inv) } if (cv_conflict) { - const std::string &char_name = GetCharName(char_id); + const std::string& char_name = GetCharName(char_id); LogError( "ClientVersion/Expansion conflict during inventory load at zone entry for [{}] (charid: [{}], inver: [{}], gmi: [{}])", char_name, char_id, - EQ::versions::MobVersionName(inv->InventoryVersion()), - (inv->GMInventory() ? "true" : "false") + EQ::versions::MobVersionName(inv.InventoryVersion()), + (inv.GMInventory() ? "true" : "false") ); } @@ -837,95 +852,7 @@ bool SharedDatabase::GetInventory(uint32 char_id, EQ::InventoryProfile *inv) EQ::ItemInstance::ClearGUIDMap(); // Retrieve shared inventory - return GetSharedBank(char_id, inv, true); -} - -// Overloaded: Retrieve character inventory based on account_id and character name (char select) -bool SharedDatabase::GetInventory(uint32 account_id, char *name, EQ::InventoryProfile *inv) // deprecated -{ - // Retrieve character inventory - const std::string query = - StringFormat("SELECT slotid, itemid, charges, color, augslot1, " - "augslot2, augslot3, augslot4, augslot5, augslot6, instnodrop, custom_data, ornamenticon, " - "ornamentidfile, ornament_hero_model " - "FROM inventory INNER JOIN character_data ch " - "ON ch.id = charid WHERE ch.name = '%s' AND ch.account_id = %i ORDER BY slotid", - name, account_id); - auto results = QueryDatabase(query); - if (!results.Success()) { - LogError("If you got an error related to the 'instnodrop' field, run the " - "following SQL Queries:\nalter table inventory add instnodrop " - "tinyint(1) unsigned default 0 not null;\n"); - return false; - } - - for (auto& row = results.begin(); row != results.end(); ++row) { - int16 slot_id = Strings::ToInt(row[0]); - uint32 item_id = Strings::ToUnsignedInt(row[1]); - const int8 charges = Strings::ToInt(row[2]); - const uint32 color = Strings::ToUnsignedInt(row[3]); - - uint32 aug[EQ::invaug::SOCKET_COUNT]; - aug[0] = Strings::ToUnsignedInt(row[4]); - aug[1] = Strings::ToUnsignedInt(row[5]); - aug[2] = Strings::ToUnsignedInt(row[6]); - aug[3] = Strings::ToUnsignedInt(row[7]); - aug[4] = Strings::ToUnsignedInt(row[8]); - aug[5] = Strings::ToUnsignedInt(row[9]); - - const bool instnodrop = (row[10] && static_cast(Strings::ToUnsignedInt(row[10]))); - const uint32 ornament_icon = Strings::ToUnsignedInt(row[12]); - const uint32 ornament_idfile = Strings::ToUnsignedInt(row[13]); - uint32 ornament_hero_model = Strings::ToUnsignedInt(row[14]); - - const EQ::ItemData *item = GetItem(item_id); - if (!item) - continue; - - EQ::ItemInstance *inst = CreateBaseItem(item, charges); - - if (inst == nullptr) - continue; - - inst->SetAttuned(instnodrop); - - if (row[11]) { - std::string data_str(row[11]); - inst->SetCustomDataString(data_str); - } - - inst->SetOrnamentIcon(ornament_icon); - inst->SetOrnamentationIDFile(ornament_idfile); - inst->SetOrnamentHeroModel(item->HerosForgeModel); - - if (color > 0) - inst->SetColor(color); - - inst->SetCharges(charges); - - if (item->IsClassCommon()) { - for (int i = EQ::invaug::SOCKET_BEGIN; i <= EQ::invaug::SOCKET_END; i++) { - if (aug[i]) - inst->PutAugment(this, i, aug[i]); - } - } - - int16 put_slot_id; - if (slot_id >= 8000 && slot_id <= 8999) - put_slot_id = inv->PushCursor(*inst); - else - put_slot_id = inv->PutItem(slot_id, *inst); - - safe_delete(inst); - - // Save ptr to item in inventory - if (put_slot_id == INVALID_INDEX) - LogError("Warning: Invalid slot_id for item in inventory: name={}, acctid={}, item_id={}, slot_id={}", - name, account_id, item_id, slot_id); - } - - // Retrieve shared inventory - return GetSharedBank(account_id, inv, false); + return GetSharedBank(char_id, &inv, true); } std::map SharedDatabase::GetItemRecastTimestamps(uint32 char_id) @@ -976,27 +903,10 @@ void SharedDatabase::ClearOldRecastTimestamps(uint32 char_id) ); } -void SharedDatabase::GetItemsCount(int32 &item_count, uint32 &max_id) +void SharedDatabase::GetItemsCount(int32& item_count, uint32& max_id) { - item_count = -1; - max_id = 0; - - const std::string query = "SELECT MAX(id), count(*) FROM items"; - auto results = QueryDatabase(query); - if (!results.Success()) { - return; - } - - if (results.RowCount() == 0) - return; - - auto& row = results.begin(); - - if (row[0]) - max_id = Strings::ToUnsignedInt(row[0]); - - if (row[1]) - item_count = Strings::ToUnsignedInt(row[1]); + max_id = ItemsRepository::GetMaxId(*this); + item_count = ItemsRepository::Count(*this); } bool SharedDatabase::LoadItems(const std::string &prefix) { @@ -1006,7 +916,7 @@ bool SharedDatabase::LoadItems(const std::string &prefix) { const auto Config = EQEmuConfig::get(); EQ::IPCMutex mutex("items"); mutex.Lock(); - std::string file_name = fmt::format("{}/{}{}", path.GetSharedMemoryPath(), prefix, std::string("items")); + std::string file_name = fmt::format("{}/{}{}", PathManager::Instance()->GetSharedMemoryPath(), prefix, std::string("items")); items_mmf = std::make_unique(file_name); items_hash = std::make_unique>(static_cast(items_mmf->Get()), items_mmf->Size()); mutex.Unlock(); @@ -1026,14 +936,14 @@ void SharedDatabase::LoadItems(void *data, uint32 size, int32 items, uint32 max_ std::string variable_buffer; - bool disable_attuneable = RuleB(Items, DisableAttuneable); - bool disable_bard_focus_effects = RuleB(Items, DisableBardFocusEffects); - bool disable_lore = RuleB(Items, DisableLore); - bool disable_no_drop = RuleB(Items, DisableNoDrop); - bool disable_no_pet = RuleB(Items, DisableNoPet); - bool disable_no_rent = RuleB(Items, DisableNoRent); - bool disable_no_transfer = RuleB(Items, DisableNoTransfer); - bool disable_potion_belt = RuleB(Items, DisablePotionBelt); + bool disable_attuneable = RuleB(Items, DisableAttuneable); + bool disable_bard_focus_effects = RuleB(Items, DisableBardFocusEffects); + bool disable_lore = RuleB(Items, DisableLore); + bool disable_no_drop = RuleB(Items, DisableNoDrop); + bool disable_no_pet = RuleB(Items, DisableNoPet); + bool disable_no_rent = RuleB(Items, DisableNoRent); + bool disable_no_transfer = RuleB(Items, DisableNoTransfer); + bool disable_potion_belt = RuleB(Items, DisablePotionBelt); bool disable_spell_focus_effects = RuleB(Items, DisableSpellFocusEffects); // Old Variable Code @@ -1064,206 +974,204 @@ void SharedDatabase::LoadItems(void *data, uint32 size, int32 items, uint32 max_ EQ::ItemData item; - const std::string query = "SELECT source," -#define F(x) "`"#x"`," -#include "item_fieldlist.h" -#undef F - "updated, minstatus, comment FROM items ORDER BY id"; - auto results = QueryDatabase(query); - if (!results.Success()) { + const auto& l = ItemsRepository::All(*this); + + if (l.empty()) { return; } - for (auto& row = results.begin(); row != results.end(); ++row) { + for (const auto& e : l) { memset(&item, 0, sizeof(EQ::ItemData)); // Unique Identifier - item.ID = Strings::ToUnsignedInt(row[ItemField::id]); + item.ID = e.id; // Minimum Status - item.MinStatus = static_cast(Strings::ToUnsignedInt(row[ItemField::minstatus])); + item.MinStatus = static_cast(e.minstatus); // Name, Lore, and Comment - strn0cpy(item.Name, row[ItemField::name], sizeof(item.Name)); - strn0cpy(item.Lore, row[ItemField::lore], sizeof(item.Lore)); - strn0cpy(item.Comment, row[ItemField::comment], sizeof(item.Comment)); + strn0cpy(item.Name, e.Name.c_str(), sizeof(item.Name)); + strn0cpy(item.Lore, e.lore.c_str(), sizeof(item.Lore)); + strn0cpy(item.Comment, e.comment.c_str(), sizeof(item.Comment)); // Flags - item.ArtifactFlag = Strings::ToBool(row[ItemField::artifactflag]); - item.Attuneable = !disable_attuneable && Strings::ToBool(row[ItemField::attuneable]); - item.BenefitFlag = Strings::ToBool(row[ItemField::benefitflag]); - item.FVNoDrop = Strings::ToBool(row[ItemField::fvnodrop]); - item.Magic = Strings::ToBool(row[ItemField::magic]); - item.NoDrop = disable_no_drop ? static_cast(255) : static_cast(Strings::ToUnsignedInt(row[ItemField::nodrop])); - item.NoPet = !disable_no_pet && Strings::ToBool(row[ItemField::nopet]); - item.NoRent = disable_no_rent ? static_cast(255) : static_cast(Strings::ToUnsignedInt(row[ItemField::norent])); - item.NoTransfer = !disable_no_transfer && Strings::ToBool(row[ItemField::notransfer]); - item.PendingLoreFlag = Strings::ToBool(row[ItemField::pendingloreflag]); - item.QuestItemFlag = Strings::ToBool(row[ItemField::questitemflag]); - item.Stackable = Strings::ToBool(row[ItemField::stackable]); - item.Tradeskills = Strings::ToBool(row[ItemField::tradeskills]); - item.SummonedFlag = Strings::ToBool(row[ItemField::summonedflag]); + item.ArtifactFlag = e.artifactflag; + item.Attuneable = !disable_attuneable && e.attuneable; + item.BenefitFlag = e.benefitflag; + item.FVNoDrop = e.fvnodrop; + item.Magic = e.magic; + item.NoDrop = disable_no_drop ? std::numeric_limits::max() : e.nodrop; + item.NoPet = !disable_no_pet && e.nopet; + item.NoRent = disable_no_rent ? std::numeric_limits::max() : e.norent; + item.NoTransfer = !disable_no_transfer && e.notransfer; + item.PendingLoreFlag = e.pendingloreflag; + item.QuestItemFlag = e.questitemflag; + item.Stackable = e.stackable; + item.Tradeskills = e.tradeskills; + item.SummonedFlag = e.summonedflag; // Lore - item.LoreGroup = disable_lore ? 0 : Strings::ToInt(row[ItemField::loregroup]); - item.LoreFlag = !disable_lore && item.LoreGroup != 0; + item.LoreGroup = disable_lore ? 0 : e.loregroup; + item.LoreFlag = !disable_lore && item.LoreGroup != 0; // Type - item.AugType = Strings::ToUnsignedInt(row[ItemField::augtype]); - item.ItemType = static_cast(Strings::ToUnsignedInt(row[ItemField::itemtype])); - item.SubType = Strings::ToInt(row[ItemField::subtype]); + item.AugType = e.augtype; + item.ItemType = static_cast(e.itemtype); + item.SubType = e.subtype; // Miscellaneous - item.ExpendableArrow = static_cast(Strings::ToUnsignedInt(row[ItemField::expendablearrow])); - item.Light = static_cast(Strings::ToInt(row[ItemField::light])); - item.MaxCharges = static_cast(Strings::ToInt(row[ItemField::maxcharges])); - item.Size = static_cast(Strings::ToUnsignedInt(row[ItemField::size])); - item.StackSize = static_cast(Strings::ToInt(row[ItemField::stacksize])); - item.Weight = Strings::ToInt(row[ItemField::weight]); + item.ExpendableArrow = e.expendablearrow; + item.Light = EQ::Clamp(e.light, -128, 127); + item.MaxCharges = e.maxcharges; + item.Size = static_cast(e.size); + item.StackSize = e.stacksize; + item.Weight = e.weight; // Potion Belt - item.PotionBelt = !disable_potion_belt && Strings::ToBool(row[ItemField::potionbelt]); - item.PotionBeltSlots = disable_potion_belt ? 0 : static_cast(Strings::ToUnsignedInt(row[ItemField::potionbeltslots])); + item.PotionBelt = !disable_potion_belt && e.potionbelt; + item.PotionBeltSlots = disable_potion_belt ? 0 : static_cast(e.potionbeltslots); // Merchant - item.Favor = Strings::ToUnsignedInt(row[ItemField::favor]); - item.GuildFavor = Strings::ToUnsignedInt(row[ItemField::guildfavor]); - item.Price = Strings::ToUnsignedInt(row[ItemField::price]); - item.SellRate = Strings::ToFloat(row[ItemField::sellrate]); + item.Favor = e.favor; + item.GuildFavor = e.guildfavor; + item.Price = e.price; + item.SellRate = e.sellrate; // Display - item.Color = Strings::ToUnsignedInt(row[ItemField::color]); - item.EliteMaterial = Strings::ToUnsignedInt(row[ItemField::elitematerial]); - item.HerosForgeModel = Strings::ToUnsignedInt(row[ItemField::herosforgemodel]); - item.Icon = Strings::ToUnsignedInt(row[ItemField::icon]); - strn0cpy(item.IDFile, row[ItemField::idfile], sizeof(item.IDFile)); - item.Material = static_cast(Strings::ToUnsignedInt(row[ItemField::material])); + item.Color = e.color; + item.EliteMaterial = e.elitematerial; + item.HerosForgeModel = e.herosforgemodel; + item.Icon = e.icon; + strn0cpy(item.IDFile, e.idfile.c_str(), sizeof(item.IDFile)); + item.Material = e.material; // Resists - item.CR = static_cast(EQ::Clamp(Strings::ToInt(row[ItemField::cr]), -128, 127)); - item.DR = static_cast(EQ::Clamp(Strings::ToInt(row[ItemField::dr]), -128, 127)); - item.FR = static_cast(EQ::Clamp(Strings::ToInt(row[ItemField::fr]), -128, 127)); - item.MR = static_cast(EQ::Clamp(Strings::ToInt(row[ItemField::mr]), -128, 127)); - item.PR = static_cast(EQ::Clamp(Strings::ToInt(row[ItemField::pr]), -128, 127)); - item.SVCorruption = static_cast(EQ::Clamp(Strings::ToInt(row[ItemField::svcorruption]), -128, 127)); + item.CR = EQ::Clamp(e.cr, -128, 127); + item.DR = EQ::Clamp(e.dr, -128, 127); + item.FR = EQ::Clamp(e.fr, -128, 127); + item.MR = EQ::Clamp(e.mr, -128, 127); + item.PR = EQ::Clamp(e.pr, -128, 127); + item.SVCorruption = EQ::Clamp(e.svcorruption, -128, 127); // Heroic Resists - item.HeroicCR = Strings::ToInt(row[ItemField::heroic_cr]); - item.HeroicDR = Strings::ToInt(row[ItemField::heroic_dr]); - item.HeroicFR = Strings::ToInt(row[ItemField::heroic_fr]); - item.HeroicMR = Strings::ToInt(row[ItemField::heroic_mr]); - item.HeroicPR = Strings::ToInt(row[ItemField::heroic_pr]); - item.HeroicSVCorrup = Strings::ToInt(row[ItemField::heroic_svcorrup]); + item.HeroicCR = e.heroic_cr; + item.HeroicDR = e.heroic_dr; + item.HeroicFR = e.heroic_fr; + item.HeroicMR = e.heroic_mr; + item.HeroicPR = e.heroic_pr; + item.HeroicSVCorrup = e.heroic_svcorrup; // Stats - item.AAgi = static_cast(EQ::Clamp(Strings::ToInt(row[ItemField::aagi]), -128, 127)); - item.ACha = static_cast(EQ::Clamp(Strings::ToInt(row[ItemField::acha]), -128, 127)); - item.ADex = static_cast(EQ::Clamp(Strings::ToInt(row[ItemField::adex]), -128, 127)); - item.AInt = static_cast(EQ::Clamp(Strings::ToInt(row[ItemField::aint]), -128, 127)); - item.ASta = static_cast(EQ::Clamp(Strings::ToInt(row[ItemField::asta]), -128, 127)); - item.AStr = static_cast(EQ::Clamp(Strings::ToInt(row[ItemField::astr]), -128, 127)); - item.AWis = static_cast(EQ::Clamp(Strings::ToInt(row[ItemField::awis]), -128, 127)); + item.AAgi = EQ::Clamp(e.aagi, -128, 127); + item.ACha = EQ::Clamp(e.acha, -128, 127); + item.ADex = EQ::Clamp(e.adex, -128, 127); + item.AInt = EQ::Clamp(e.aint, -128, 127); + item.ASta = EQ::Clamp(e.asta, -128, 127); + item.AStr = EQ::Clamp(e.astr, -128, 127); + item.AWis = EQ::Clamp(e.awis, -128, 127); // Heroic Stats - item.HeroicAgi = Strings::ToInt(row[ItemField::heroic_agi]); - item.HeroicCha = Strings::ToInt(row[ItemField::heroic_cha]); - item.HeroicDex = Strings::ToInt(row[ItemField::heroic_dex]); - item.HeroicInt = Strings::ToInt(row[ItemField::heroic_int]); - item.HeroicSta = Strings::ToInt(row[ItemField::heroic_sta]); - item.HeroicStr = Strings::ToInt(row[ItemField::heroic_str]); - item.HeroicWis = Strings::ToInt(row[ItemField::heroic_wis]); + item.HeroicAgi = e.heroic_agi; + item.HeroicCha = e.heroic_cha; + item.HeroicDex = e.heroic_dex; + item.HeroicInt = e.heroic_int; + item.HeroicSta = e.heroic_sta; + item.HeroicStr = e.heroic_str; + item.HeroicWis = e.heroic_wis; // Health, Mana, and Endurance - item.HP = Strings::ToInt(row[ItemField::hp]); - item.Regen = Strings::ToInt(row[ItemField::regen]); - item.Mana = Strings::ToInt(row[ItemField::mana]); - item.ManaRegen = Strings::ToInt(row[ItemField::manaregen]); - item.Endur = Strings::ToInt(row[ItemField::endur]); - item.EnduranceRegen = Strings::ToInt(row[ItemField::enduranceregen]); + item.HP = e.hp; + item.Regen = e.regen; + item.Mana = e.mana; + item.ManaRegen = e.manaregen; + item.Endur = e.endur; + item.EnduranceRegen = e.enduranceregen; // Bane Damage - item.BaneDmgAmt = Strings::ToInt(row[ItemField::banedmgamt]); - item.BaneDmgBody = Strings::ToUnsignedInt(row[ItemField::banedmgbody]); - item.BaneDmgRace = Strings::ToUnsignedInt(row[ItemField::banedmgrace]); - item.BaneDmgRaceAmt = Strings::ToUnsignedInt(row[ItemField::banedmgraceamt]); + item.BaneDmgAmt = e.banedmgamt; + item.BaneDmgBody = e.banedmgbody; + item.BaneDmgRace = e.banedmgrace; + item.BaneDmgRaceAmt = e.banedmgraceamt; // Elemental Damage - item.ElemDmgType = static_cast(Strings::ToUnsignedInt(row[ItemField::elemdmgtype])); - item.ElemDmgAmt = static_cast(Strings::ToUnsignedInt(row[ItemField::elemdmgamt])); + item.ElemDmgType = static_cast(e.elemdmgtype); + item.ElemDmgAmt = static_cast(e.elemdmgamt); // Combat - item.BackstabDmg = Strings::ToUnsignedInt(row[ItemField::backstabdmg]); - item.Damage = Strings::ToUnsignedInt(row[ItemField::damage]); - item.Delay = static_cast(Strings::ToUnsignedInt(row[ItemField::delay])); - item.Range = static_cast(Strings::ToUnsignedInt(row[ItemField::range])); + item.BackstabDmg = e.backstabdmg; + item.Damage = e.damage; + item.Delay = static_cast(e.delay); + item.Range = static_cast(e.range_); // Combat Stats - item.AC = Strings::ToInt(row[ItemField::ac]); - item.Accuracy = static_cast(EQ::Clamp(Strings::ToInt(row[ItemField::accuracy]), -128, 127)); - item.Attack = Strings::ToInt(row[ItemField::attack]); - item.Avoidance = static_cast(EQ::Clamp(Strings::ToInt(row[ItemField::avoidance]), -128, 127)); - item.Clairvoyance = Strings::ToUnsignedInt(row[ItemField::clairvoyance]); - item.CombatEffects = Strings::IsNumber(row[ItemField::combateffects]) ? static_cast(EQ::Clamp(Strings::ToInt(row[ItemField::combateffects]), -128, 127)) : 0; - item.DamageShield = Strings::ToInt(row[ItemField::damageshield]); - item.DotShielding = Strings::ToInt(row[ItemField::dotshielding]); - item.DSMitigation = Strings::ToUnsignedInt(row[ItemField::dsmitigation]); - item.Haste = Strings::ToInt(row[ItemField::haste]); - item.HealAmt = Strings::ToInt(row[ItemField::healamt]); - item.Purity = Strings::ToUnsignedInt(row[ItemField::purity]); - item.Shielding = static_cast(EQ::Clamp(Strings::ToInt(row[ItemField::shielding]), -128, 127)); - item.SpellDmg = Strings::ToInt(row[ItemField::spelldmg]); - item.SpellShield = static_cast(EQ::Clamp(Strings::ToInt(row[ItemField::spellshield]), -128, 127)); - item.StrikeThrough = static_cast(EQ::Clamp(Strings::ToInt(row[ItemField::strikethrough]), -128, 127)); - item.StunResist = static_cast(EQ::Clamp(Strings::ToInt(row[ItemField::stunresist]), -128, 127)); + item.AC = e.ac; + item.Accuracy = EQ::Clamp(e.accuracy, -128, 127); + item.Attack = e.attack; + item.Avoidance = EQ::Clamp(e.avoidance, -128, 127); + item.Clairvoyance = e.clairvoyance; + item.CombatEffects = Strings::IsNumber(e.combateffects) ? static_cast(EQ::Clamp(Strings::ToInt(e.combateffects), -128, 127)) : 0; + item.DamageShield = e.damageshield; + item.DotShielding = e.dotshielding; + item.DSMitigation = e.dsmitigation; + item.Haste = e.haste; + item.HealAmt = e.healamt; + item.Purity = e.purity; + item.Shielding = EQ::Clamp(e.shielding, -128, 127); + item.SpellDmg = e.spelldmg; + item.SpellShield = EQ::Clamp(e.spellshield, -128, 127); + item.StrikeThrough = EQ::Clamp(e.strikethrough, -128, 127); + item.StunResist = EQ::Clamp(e.stunresist, -128, 127); // Restrictions - item.AugRestrict = Strings::ToUnsignedInt(row[ItemField::augrestrict]); - item.Classes = Strings::ToUnsignedInt(row[ItemField::classes]); - item.Deity = Strings::ToUnsignedInt(row[ItemField::deity]); - item.ItemClass = static_cast(Strings::ToUnsignedInt(row[ItemField::itemclass])); - item.Races = Strings::ToUnsignedInt(row[ItemField::races]); - item.RecLevel = static_cast(Strings::ToUnsignedInt(row[ItemField::reclevel])); - item.RecSkill = static_cast(Strings::ToUnsignedInt(row[ItemField::recskill])); - item.ReqLevel = static_cast(Strings::ToUnsignedInt(row[ItemField::reqlevel])); - item.Slots = Strings::ToUnsignedInt(row[ItemField::slots]); + item.AugRestrict = e.augrestrict; + item.Classes = e.classes; + item.Deity = e.deity; + item.ItemClass = static_cast(e.itemclass); + item.Races = e.races; + item.RecLevel = static_cast(e.reclevel); + item.RecSkill = static_cast(e.recskill); + item.ReqLevel = static_cast(e.reqlevel); + item.Slots = e.slots; // Skill Modifier - item.SkillModValue = Strings::ToInt(row[ItemField::skillmodvalue]); - item.SkillModMax = Strings::ToInt(row[ItemField::skillmodmax]); - item.SkillModType = Strings::ToUnsignedInt(row[ItemField::skillmodtype]); + item.SkillModValue = e.skillmodvalue; + item.SkillModMax = e.skillmodmax; + item.SkillModType = e.skillmodtype; // Extra Damage Skill - item.ExtraDmgSkill = Strings::ToInt(row[ItemField::extradmgskill]); - item.ExtraDmgAmt = Strings::ToInt(row[ItemField::extradmgamt]); + item.ExtraDmgSkill = e.extradmgskill; + item.ExtraDmgAmt = e.extradmgamt; // Bard - item.BardType = Strings::ToUnsignedInt(row[ItemField::bardtype]); - item.BardValue = Strings::ToInt(row[ItemField::bardvalue]); + item.BardType = e.bardtype; + item.BardValue = e.bardvalue; // Faction - item.FactionAmt1 = Strings::ToInt(row[ItemField::factionamt1]); - item.FactionMod1 = Strings::ToInt(row[ItemField::factionmod1]); - item.FactionAmt2 = Strings::ToInt(row[ItemField::factionamt2]); - item.FactionMod2 = Strings::ToInt(row[ItemField::factionmod2]); - item.FactionAmt3 = Strings::ToInt(row[ItemField::factionamt3]); - item.FactionMod3 = Strings::ToInt(row[ItemField::factionmod3]); - item.FactionAmt4 = Strings::ToInt(row[ItemField::factionamt4]); - item.FactionMod4 = Strings::ToInt(row[ItemField::factionmod4]); + item.FactionAmt1 = e.factionamt1; + item.FactionMod1 = e.factionmod1; + item.FactionAmt2 = e.factionamt2; + item.FactionMod2 = e.factionmod2; + item.FactionAmt3 = e.factionamt3; + item.FactionMod3 = e.factionmod3; + item.FactionAmt4 = e.factionamt4; + item.FactionMod4 = e.factionmod4; - // Augment - item.AugDistiller = Strings::ToUnsignedInt(row[ItemField::augdistiller]); - item.AugSlotType[0] = static_cast(Strings::ToUnsignedInt(row[ItemField::augslot1type])); - item.AugSlotVisible[0] = static_cast(Strings::ToUnsignedInt(row[ItemField::augslot1visible])); - item.AugSlotType[1] = static_cast(Strings::ToUnsignedInt(row[ItemField::augslot2type])); - item.AugSlotVisible[1] = static_cast(Strings::ToUnsignedInt(row[ItemField::augslot2visible])); - item.AugSlotType[2] = static_cast(Strings::ToUnsignedInt(row[ItemField::augslot3type])); - item.AugSlotVisible[2] = static_cast(Strings::ToUnsignedInt(row[ItemField::augslot3visible])); - item.AugSlotType[3] = static_cast(Strings::ToUnsignedInt(row[ItemField::augslot4type])); - item.AugSlotVisible[3] = static_cast(Strings::ToUnsignedInt(row[ItemField::augslot4visible])); - item.AugSlotType[4] = static_cast(Strings::ToUnsignedInt(row[ItemField::augslot5type])); - item.AugSlotVisible[4] = static_cast(Strings::ToUnsignedInt(row[ItemField::augslot5visible])); - item.AugSlotType[5] = static_cast(Strings::ToUnsignedInt(row[ItemField::augslot6type])); - item.AugSlotVisible[5] = static_cast(Strings::ToUnsignedInt(row[ItemField::augslot6visible])); + // Augment Distiller + item.AugDistiller = e.augdistiller; + + // Augment Slots + item.AugSlotType[0] = static_cast(e.augslot1type); + item.AugSlotVisible[0] = static_cast(e.augslot1visible); + item.AugSlotType[1] = static_cast(e.augslot2type); + item.AugSlotVisible[1] = static_cast(e.augslot2visible); + item.AugSlotType[2] = static_cast(e.augslot3type); + item.AugSlotVisible[2] = static_cast(e.augslot3visible); + item.AugSlotType[3] = static_cast(e.augslot4type); + item.AugSlotVisible[3] = static_cast(e.augslot4visible); + item.AugSlotType[4] = static_cast(e.augslot5type); + item.AugSlotVisible[4] = static_cast(e.augslot5visible); + item.AugSlotType[5] = static_cast(e.augslot6type); + item.AugSlotVisible[5] = static_cast(e.augslot6visible); // Augment Unknowns for (uint8 i = EQ::invaug::SOCKET_BEGIN; i <= EQ::invaug::SOCKET_END; i++) { @@ -1271,79 +1179,79 @@ void SharedDatabase::LoadItems(void *data, uint32 size, int32 items, uint32 max_ } // LDoN - item.LDoNTheme = Strings::ToUnsignedInt(row[ItemField::ldontheme]); - item.LDoNPrice = Strings::ToUnsignedInt(row[ItemField::ldonprice]); - item.LDoNSellBackRate = Strings::ToUnsignedInt(row[ItemField::ldonsellbackrate]); - item.LDoNSold = Strings::ToUnsignedInt(row[ItemField::ldonsold]); - item.PointType = Strings::ToUnsignedInt(row[ItemField::pointtype]); + item.LDoNTheme = e.ldontheme; + item.LDoNPrice = e.ldonprice; + item.LDoNSellBackRate = e.ldonsellbackrate; + item.LDoNSold = e.ldonsold; + item.PointType = e.pointtype; // Bag - item.BagSize = static_cast(Strings::ToUnsignedInt(row[ItemField::bagsize])); - item.BagSlots = static_cast(EQ::Clamp(Strings::ToInt(row[ItemField::bagslots]), 0, 10)); // Will need to be changed from std::min to just use database value when bag slots are increased - item.BagType = static_cast(Strings::ToUnsignedInt(row[ItemField::bagtype])); - item.BagWR = static_cast(EQ::Clamp(Strings::ToInt(row[ItemField::bagwr]), 0, 100)); + item.BagSize = static_cast(e.bagsize); + item.BagSlots = EQ::Clamp(e.bagslots, 0, static_cast(EQ::invbag::SLOT_COUNT)); + item.BagType = static_cast(e.bagtype); + item.BagWR = EQ::Clamp(e.bagwr, 0, 100); // Bard Effect - item.Bard.Effect = disable_bard_focus_effects ? 0 : Strings::ToInt(row[ItemField::bardeffect]); - item.Bard.Type = disable_bard_focus_effects ? 0 : static_cast(Strings::ToUnsignedInt(row[ItemField::bardtype])); - item.Bard.Level = disable_bard_focus_effects ? 0 : static_cast(Strings::ToUnsignedInt(row[ItemField::bardlevel])); - item.Bard.Level2 = disable_bard_focus_effects ? 0 : static_cast(Strings::ToUnsignedInt(row[ItemField::bardlevel2])); + item.Bard.Effect = disable_bard_focus_effects ? 0 : e.bardeffect; + item.Bard.Type = disable_bard_focus_effects ? 0 : static_cast(e.bardtype); + item.Bard.Level = disable_bard_focus_effects ? 0 : static_cast(e.bardlevel); + item.Bard.Level2 = disable_bard_focus_effects ? 0 : static_cast(e.bardlevel2); // Book - item.Book = static_cast(Strings::ToUnsignedInt(row[ItemField::book])); - item.BookType = Strings::ToUnsignedInt(row[ItemField::booktype]); + item.Book = static_cast(e.book); + item.BookType = e.booktype; // Click Effect - item.CastTime = Strings::ToUnsignedInt(row[ItemField::casttime]); - item.CastTime_ = Strings::ToInt(row[ItemField::casttime_]); - item.Click.Effect = Strings::ToInt(row[ItemField::clickeffect]); - item.Click.Type = static_cast(Strings::ToUnsignedInt(row[ItemField::clicktype])); - item.Click.Level = static_cast(Strings::ToUnsignedInt(row[ItemField::clicklevel])); - item.Click.Level2 = static_cast(Strings::ToUnsignedInt(row[ItemField::clicklevel2])); - strn0cpy(item.ClickName, row[ItemField::clickname], sizeof(item.ClickName)); - item.RecastDelay = Strings::ToUnsignedInt(row[ItemField::recastdelay]); - item.RecastType = Strings::ToInt(row[ItemField::recasttype]); + item.CastTime = e.casttime; + item.CastTime_ = e.casttime_; + item.Click.Effect = e.clickeffect; + item.Click.Type = static_cast(e.clicktype); + item.Click.Level = static_cast(e.clicklevel); + item.Click.Level2 = static_cast(e.clicklevel2); + strn0cpy(item.ClickName, e.clickname.c_str(), sizeof(item.ClickName)); + item.RecastDelay = e.recastdelay; + item.RecastType = e.recasttype; // Focus Effect - item.Focus.Effect = disable_spell_focus_effects ? 0 : Strings::ToInt(row[ItemField::focuseffect]); - item.Focus.Type = disable_spell_focus_effects ? 0 : static_cast(Strings::ToUnsignedInt(row[ItemField::focustype])); - item.Focus.Level = disable_spell_focus_effects ? 0 : static_cast(Strings::ToUnsignedInt(row[ItemField::focuslevel])); - item.Focus.Level2 = disable_spell_focus_effects ? 0 : static_cast(Strings::ToUnsignedInt(row[ItemField::focuslevel2])); - strn0cpy(item.FocusName, disable_spell_focus_effects ? "" : row[ItemField::focusname], sizeof(item.FocusName)); + item.Focus.Effect = disable_spell_focus_effects ? 0 : e.focuseffect; + item.Focus.Type = disable_spell_focus_effects ? 0 : static_cast(e.focustype); + item.Focus.Level = disable_spell_focus_effects ? 0 : static_cast(e.focuslevel); + item.Focus.Level2 = disable_spell_focus_effects ? 0 : static_cast(e.focuslevel2); + strn0cpy(item.FocusName, disable_spell_focus_effects ? "" : e.focusname.c_str(), sizeof(item.FocusName)); // Proc Effect - item.Proc.Effect = Strings::ToInt(row[ItemField::proceffect]); - item.Proc.Type = static_cast(Strings::ToUnsignedInt(row[ItemField::proctype])); - item.Proc.Level = static_cast(Strings::ToUnsignedInt(row[ItemField::proclevel])); - item.Proc.Level2 = static_cast(Strings::ToUnsignedInt(row[ItemField::proclevel2])); - strn0cpy(item.ProcName, row[ItemField::procname], sizeof(item.ProcName)); - item.ProcRate = Strings::ToInt(row[ItemField::procrate]); + item.Proc.Effect = e.proceffect; + item.Proc.Type = static_cast(e.proctype); + item.Proc.Level = static_cast(e.proclevel); + item.Proc.Level2 = static_cast(e.proclevel2); + strn0cpy(item.ProcName, e.procname.c_str(), sizeof(item.ProcName)); + item.ProcRate = e.procrate; // Scroll Effect - item.Scroll.Effect = Strings::ToInt(row[ItemField::scrolleffect]); - item.Scroll.Type = static_cast(Strings::ToUnsignedInt(row[ItemField::scrolltype])); - item.Scroll.Level = static_cast(Strings::ToUnsignedInt(row[ItemField::scrolllevel])); - item.Scroll.Level2 = static_cast(Strings::ToUnsignedInt(row[ItemField::scrolllevel2])); - strn0cpy(item.ScrollName, row[ItemField::scrollname], sizeof(item.ScrollName)); + item.Scroll.Effect = e.scrolleffect; + item.Scroll.Type = static_cast(e.scrolltype); + item.Scroll.Level = static_cast(e.scrolllevel); + item.Scroll.Level2 = static_cast(e.scrolllevel2); + strn0cpy(item.ScrollName, e.scrollname.c_str(), sizeof(item.ScrollName)); // Worn Effect - item.Worn.Effect = Strings::ToInt(row[ItemField::worneffect]); - item.Worn.Type = static_cast(Strings::ToUnsignedInt(row[ItemField::worntype])); - item.Worn.Level = static_cast(Strings::ToUnsignedInt(row[ItemField::wornlevel])); - item.Worn.Level2 = static_cast(Strings::ToUnsignedInt(row[ItemField::wornlevel2])); - strn0cpy(item.WornName, row[ItemField::wornname], sizeof(item.WornName)); + item.Worn.Effect = e.worneffect; + item.Worn.Type = static_cast(e.worntype); + item.Worn.Level = static_cast(e.wornlevel); + item.Worn.Level2 = static_cast(e.wornlevel2); + strn0cpy(item.WornName, e.wornname.c_str(), sizeof(item.WornName)); // Evolving Item - item.EvolvingID = Strings::ToUnsignedInt(row[ItemField::evoid]); - item.EvolvingItem = static_cast(Strings::ToUnsignedInt(row[ItemField::evoitem])); - item.EvolvingLevel = static_cast(Strings::ToUnsignedInt(row[ItemField::evolvinglevel])); - item.EvolvingMax = static_cast(Strings::ToUnsignedInt(row[ItemField::evomax])); + item.EvolvingID = e.evoid; + item.EvolvingItem = static_cast(e.evoitem); + item.EvolvingLevel = static_cast(e.evolvinglevel); + item.EvolvingMax = static_cast(e.evomax); // Scripting - item.CharmFileID = Strings::IsNumber(row[ItemField::charmfileid]) ? Strings::ToUnsignedInt(row[ItemField::charmfileid]) : 0; - strn0cpy(item.CharmFile, row[ItemField::charmfile], sizeof(item.CharmFile)); - strn0cpy(item.Filename, row[ItemField::filename], sizeof(item.Filename)); - item.ScriptFileID = Strings::ToUnsignedInt(row[ItemField::scriptfileid]); + item.CharmFileID = Strings::IsNumber(e.charmfileid) ? Strings::ToUnsignedInt(e.charmfileid) : 0; + strn0cpy(item.CharmFile, e.charmfile.c_str(), sizeof(item.CharmFile)); + strn0cpy(item.Filename, e.filename.c_str(), sizeof(item.Filename)); + item.ScriptFileID = e.scriptfileid; try { hash.insert(item.ID, item); @@ -1576,12 +1484,60 @@ bool SharedDatabase::GetCommandSettings(std::map +inline std::vector join_pair( + const std::string &glue, + const std::pair &encapsulation, + const std::vector> &src +) +{ + if (src.empty()) { + return {}; + } + + std::vector output; + + for (const std::pair &src_iter: src) { + output.emplace_back( + + fmt::format( + "{}{}{}{}{}{}{}", + encapsulation.first, + src_iter.first, + encapsulation.second, + glue, + encapsulation.first, + src_iter.second, + encapsulation.second + ) + ); + } + + return output; +} + +template +inline std::string +ImplodePair(const std::string &glue, const std::pair &encapsulation, const std::vector &src) +{ + if (src.empty()) { + return {}; + } + std::ostringstream oss; + for (const T &src_iter: src) { + oss << encapsulation.first << src_iter << encapsulation.second << glue; + } + std::string output(oss.str()); + output.resize(output.size() - glue.size()); + return output; +} + bool SharedDatabase::UpdateInjectedCommandSettings(const std::vector> &injected) { if (injected.size()) { const std::string query = fmt::format( "REPLACE INTO `command_settings`(`command`, `access`) VALUES {}", - Strings::ImplodePair( + ImplodePair( ",", std::pair('(', ')'), join_pair(",", std::pair('\'', '\''), injected) @@ -1608,7 +1564,7 @@ bool SharedDatabase::UpdateOrphanedCommandSettings(const std::vector('\'', '\''), orphaned) + ImplodePair(",", std::pair('\'', '\''), orphaned) ); auto results = QueryDatabase(query); @@ -1618,7 +1574,7 @@ bool SharedDatabase::UpdateOrphanedCommandSettings(const std::vector('\'', '\''), orphaned) + ImplodePair(",", std::pair('\'', '\''), orphaned) ); auto results_two = QueryDatabase(query); @@ -1657,20 +1613,17 @@ bool SharedDatabase::GetCommandSubSettings(std::vector 0 " - "AND `spellid` <= %i", iMaxSpellID); - auto results = QueryDatabase(query); - if (!results.Success()) { - return; - } +void SharedDatabase::LoadDamageShieldTypes(SPDat_Spell_Struct* s) +{ + const auto& l = DamageshieldtypesRepository::All(*this); - for(auto& row = results.begin(); row != results.end(); ++row) { - const int spellID = Strings::ToInt(row[0]); - if((spellID > 0) && (spellID <= iMaxSpellID)) - sp[spellID].damage_shield_type = Strings::ToUnsignedInt(row[1]); - } + if (l.empty()) { + return; + } + for (const auto& e : l) { + s[e.spellid].damage_shield_type = e.type; + } } const EvolveInfo* SharedDatabase::GetEvolveInfo(uint32 loregroup) { @@ -1697,7 +1650,7 @@ bool SharedDatabase::LoadSpells(const std::string &prefix, int32 *records, const EQ::IPCMutex mutex("spells"); mutex.Lock(); - std::string file_name = fmt::format("{}/{}{}", path.GetSharedMemoryPath(), prefix, std::string("spells")); + std::string file_name = fmt::format("{}/{}{}", PathManager::Instance()->GetSharedMemoryPath(), prefix, std::string("spells")); spells_mmf = std::make_unique(file_name); LogInfo("Loading [{}]", file_name); *records = *static_cast(spells_mmf->Get()); @@ -1887,21 +1840,32 @@ void SharedDatabase::LoadSpells(void *data, int max_spells) { sp[tempid].damage_shield_type = 0; } - LoadDamageShieldTypes(sp, max_spells); + LoadDamageShieldTypes(sp); } -void SharedDatabase::LoadCharacterInspectMessage(uint32 character_id, InspectMessage_Struct* message) { - const std::string query = StringFormat("SELECT `inspect_message` FROM `character_inspect_messages` WHERE `id` = %u LIMIT 1", character_id); - auto results = QueryDatabase(query); - memset(message, '\0', sizeof(InspectMessage_Struct)); - for (auto& row = results.begin(); row != results.end(); ++row) { - memcpy(message, row[0], sizeof(InspectMessage_Struct)); +void SharedDatabase::LoadCharacterInspectMessage(uint32 character_id, InspectMessage_Struct* s) +{ + const auto& e = CharacterInspectMessagesRepository::FindOne(*this, character_id); + + memset(s, '\0', sizeof(InspectMessage_Struct)); + + if (!e.id) { + return; } + + memcpy(s, e.inspect_message.c_str(), sizeof(InspectMessage_Struct)); } -void SharedDatabase::SaveCharacterInspectMessage(uint32 character_id, const InspectMessage_Struct* message) { - const std::string query = StringFormat("REPLACE INTO `character_inspect_messages` (id, inspect_message) VALUES (%u, '%s')", character_id, Strings::Escape(message->text).c_str()); - auto results = QueryDatabase(query); +void SharedDatabase::SaveCharacterInspectMessage(uint32 character_id, const InspectMessage_Struct* s) +{ + auto e = CharacterInspectMessagesRepository::NewEntity(); + + e.id = character_id; + e.inspect_message = s->text; + + if (!CharacterInspectMessagesRepository::ReplaceOne(*this, e)) { + LogError("Failed to save character inspect message of [{}] for character_id [{}]", s->text, character_id); + } } uint32 SharedDatabase::GetSpellsCount() @@ -1922,18 +1886,7 @@ uint32 SharedDatabase::GetSpellsCount() uint32 SharedDatabase::GetItemsCount() { - auto results = QueryDatabase("SELECT count(*) FROM items"); - if (!results.Success() || !results.RowCount()) { - return 0; - } - - auto& row = results.begin(); - - if (row[0]) { - return Strings::ToUnsignedInt(row[0]); - } - - return 0; + return ItemsRepository::Count(*this); } void SharedDatabase::SetSharedItemsCount(uint32 shared_items_count) diff --git a/common/shareddb.h b/common/shareddb.h index 67c684206..2a78be670 100644 --- a/common/shareddb.h +++ b/common/shareddb.h @@ -28,6 +28,8 @@ #include "fixed_memory_variable_hash_set.h" #include "say_link.h" #include "repositories/command_subsettings_repository.h" +#include "repositories/items_evolving_details_repository.h" +#include "../common/repositories/character_evolving_items_repository.h" #include #include @@ -74,21 +76,20 @@ public: uint8 GetGMSpeed(uint32 account_id); bool SetHideMe(uint32 account_id, uint8 hideme); int DeleteStalePlayerCorpses(); - void LoadCharacterInspectMessage(uint32 character_id, InspectMessage_Struct *message); - void SaveCharacterInspectMessage(uint32 character_id, const InspectMessage_Struct *message); + void LoadCharacterInspectMessage(uint32 character_id, InspectMessage_Struct* s); + void SaveCharacterInspectMessage(uint32 character_id, const InspectMessage_Struct* s); bool GetCommandSettings(std::map>> &command_settings); bool UpdateInjectedCommandSettings(const std::vector> &injected); bool UpdateOrphanedCommandSettings(const std::vector &orphaned); bool GetCommandSubSettings(std::vector &command_subsettings); - uint32 GetTotalTimeEntitledOnAccount(uint32 AccountID); bool SetGMInvul(uint32 account_id, bool gminvul); bool SetGMFlymode(uint32 account_id, uint8 flymode); - void SetMailKey(int CharID, int IPAddress, int MailKey); + void SetMailKey(uint32 character_id, uint32 ip_address, uint32 mail_key); struct MailKeys { std::string mail_key; std::string mail_key_full; }; - MailKeys GetMailKey(int character_id); + MailKeys GetMailKey(uint32 character_id); bool SaveCursor( uint32 char_id, std::list::const_iterator &start, @@ -102,8 +103,8 @@ public: bool VerifyInventory(uint32 account_id, int16 slot_id, const EQ::ItemInstance *inst); bool GetSharedBank(uint32 id, EQ::InventoryProfile *inv, bool is_charid); int32 GetSharedPlatinum(uint32 account_id); - bool SetSharedPlatinum(uint32 account_id, int32 amount_to_add); - bool GetInventory(uint32 char_id, EQ::InventoryProfile *inv); + bool AddSharedPlatinum(uint32 account_id, int amount); + bool GetInventory(Client* c); bool GetInventory(uint32 account_id, char *name, EQ::InventoryProfile *inv); // deprecated std::map GetItemRecastTimestamps(uint32 char_id); uint32 GetItemRecastTimestamp(uint32 char_id, uint32 recast_type); @@ -156,7 +157,7 @@ public: ); EQ::ItemInstance *CreateBaseItem(const EQ::ItemData *item, int16 charges = 0); - void GetItemsCount(int32 &item_count, uint32 &max_id); + void GetItemsCount(int32& item_count, uint32& max_id); void LoadItems(void *data, uint32 size, int32 items, uint32 max_item_id); bool LoadItems(const std::string &prefix); const EQ::ItemData *IterateItems(uint32 *id) const; @@ -171,7 +172,7 @@ public: int GetMaxSpellID(); bool LoadSpells(const std::string &prefix, int32 *records, const SPDat_Spell_Struct **sp); void LoadSpells(void *data, int max_spells); - void LoadDamageShieldTypes(SPDat_Spell_Struct *sp, int32 iMaxSpellID); + void LoadDamageShieldTypes(SPDat_Spell_Struct* s); uint32 GetSharedSpellsCount() { return m_shared_spells_count; } uint32 GetSpellsCount(); diff --git a/common/skill_caps.h b/common/skill_caps.h index 9630ed7d7..6566a1104 100644 --- a/common/skill_caps.h +++ b/common/skill_caps.h @@ -16,12 +16,16 @@ public: static int32_t GetSkillCapMaxLevel(uint8 class_id, EQ::skills::SkillType skill_id); SkillCaps *SetContentDatabase(Database *db); + + static SkillCaps* Instance() + { + static SkillCaps instance; + return &instance; + } private: Database *m_content_database{}; std::map m_skill_caps = {}; }; -extern SkillCaps skill_caps; - #endif //CODE_SKILL_CAPS_H diff --git a/common/spdat.cpp b/common/spdat.cpp index 3acac1c8b..f4d3df255 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -93,12 +93,19 @@ bool IsTargetableAESpell(uint16 spell_id) return false; } - return spells[spell_id].target_type == ST_AETarget; + return ( + spells[spell_id].target_type == ST_AETarget || + spells[spell_id].target_type == ST_TargetAETap || + spells[spell_id].target_type == ST_AETargetHateList || + spells[spell_id].target_type == ST_TargetAENoPlayersPets || + spells[spell_id].target_type == ST_UndeadAE || + spells[spell_id].target_type == ST_SummonedAE + ); } bool IsSacrificeSpell(uint16 spell_id) { - return IsEffectInSpell(spell_id, SE_Sacrifice); + return IsEffectInSpell(spell_id, SpellEffect::Sacrifice); } bool IsLifetapSpell(uint16 spell_id) @@ -122,12 +129,17 @@ bool IsLifetapSpell(uint16 spell_id) bool IsMesmerizeSpell(uint16 spell_id) { - return IsEffectInSpell(spell_id, SE_Mez); + return IsEffectInSpell(spell_id, SpellEffect::Mez); +} + +bool SpellBreaksMez(uint16 spell_id) +{ + return (IsValidSpell(spell_id) && IsDetrimentalSpell(spell_id) && IsAnyDamageSpell(spell_id)); } bool IsStunSpell(uint16 spell_id) { - return IsEffectInSpell(spell_id, SE_Stun); + return (IsValidSpell(spell_id) && IsEffectInSpell(spell_id, SpellEffect::Stun) || IsEffectInSpell(spell_id, SpellEffect::SpinTarget)); } bool IsSummonSpell(uint16 spell_id) @@ -141,9 +153,9 @@ bool IsSummonSpell(uint16 spell_id) for (int i = 0; i < EFFECT_COUNT; i++) { const auto effect_id = spell.effect_id[i]; if ( - effect_id == SE_SummonPet || - effect_id == SE_SummonItem || - effect_id == SE_SummonPC + effect_id == SpellEffect::SummonPet || + effect_id == SpellEffect::SummonItem || + effect_id == SpellEffect::SummonPC ) { return true; } @@ -154,6 +166,10 @@ bool IsSummonSpell(uint16 spell_id) bool IsDamageSpell(uint16 spell_id) { + if (!IsValidSpell(spell_id)) { + return false; + } + if (IsLifetapSpell(spell_id)) { return false; } @@ -162,12 +178,38 @@ bool IsDamageSpell(uint16 spell_id) for (int i = 0; i < EFFECT_COUNT; i++) { const auto effect_id = spell.effect_id[i]; + if ( + spell.base_value[i] < 0 && + (effect_id == SpellEffect::CurrentHPOnce || effect_id == SpellEffect::CurrentHP) + ) { + return true; + } + } + + return false; +} + +bool IsAnyDamageSpell(uint16 spell_id) +{ + if (!IsValidSpell(spell_id)) { + return false; + } + + if (IsLifetapSpell(spell_id)) { + return false; + } + + const auto& spell = spells[spell_id]; + + for (int i = 0; i < EFFECT_COUNT; i++) { + const auto effect_id = spell.effect_id[i]; + if ( spell.base_value[i] < 0 && ( - effect_id == SE_CurrentHPOnce || + effect_id == SpellEffect::CurrentHPOnce || ( - effect_id == SE_CurrentHP && + effect_id == SpellEffect::CurrentHP && spell.buff_duration < 1 ) ) @@ -179,10 +221,39 @@ bool IsDamageSpell(uint16 spell_id) return false; } +bool IsDamageOverTimeSpell(uint16 spell_id) +{ + if (!IsValidSpell(spell_id)) { + return false; + } + + if (IsLifetapSpell(spell_id)) { + return false; + } + + const auto& spell = spells[spell_id]; + + if (spell.good_effect || !spell.buff_duration_formula) { + return false; + } + + for (int i = 0; i < EFFECT_COUNT; i++) { + const auto effect_id = spell.effect_id[i]; + if ( + spell.base_value[i] < 0 && + effect_id == SpellEffect::CurrentHP && + spell.buff_duration > 1 + ) { + return true; + } + } + + return false; +} bool IsFearSpell(uint16 spell_id) { - return IsEffectInSpell(spell_id, SE_Fear); + return IsEffectInSpell(spell_id, SpellEffect::Fear); } bool IsCureSpell(uint16 spell_id) @@ -197,10 +268,10 @@ bool IsCureSpell(uint16 spell_id) for (int i = 0; i < EFFECT_COUNT; i++) { if ( - spell.effect_id[i] == SE_DiseaseCounter || - spell.effect_id[i] == SE_PoisonCounter || - spell.effect_id[i] == SE_CurseCounter || - spell.effect_id[i] == SE_CorruptionCounter + spell.effect_id[i] == SpellEffect::DiseaseCounter || + spell.effect_id[i] == SpellEffect::PoisonCounter || + spell.effect_id[i] == SpellEffect::CurseCounter || + spell.effect_id[i] == SpellEffect::CorruptionCounter ) { has_cure_effect = true; } @@ -227,9 +298,9 @@ bool IsSlowSpell(uint16 spell_id) for (int i = 0; i < EFFECT_COUNT; i++) { const auto effect_id = spell.effect_id[i]; if ( - effect_id == SE_AttackSpeed4 || + effect_id == SpellEffect::AttackSpeed4 || ( - effect_id == SE_AttackSpeed && + effect_id == SpellEffect::AttackSpeed && spell.base_value[i] < 100 ) ) { @@ -249,7 +320,7 @@ bool IsHasteSpell(uint16 spell_id) const auto& spell = spells[spell_id]; for (int i = 0; i < EFFECT_COUNT; i++) { - if (spell.effect_id[i] == SE_AttackSpeed) { + if (spell.effect_id[i] == SpellEffect::AttackSpeed) { return (spell.base_value[i] < 100); } } @@ -260,15 +331,15 @@ bool IsHasteSpell(uint16 spell_id) bool IsHarmonySpell(uint16 spell_id) { return ( - IsEffectInSpell(spell_id, SE_ChangeFrenzyRad) || - IsEffectInSpell(spell_id, SE_Harmony) || - IsEffectInSpell(spell_id, SE_Lull) + IsEffectInSpell(spell_id, SpellEffect::ChangeFrenzyRad) || + IsEffectInSpell(spell_id, SpellEffect::Harmony) || + IsEffectInSpell(spell_id, SpellEffect::Lull) ); } bool IsPercentalHealSpell(uint16 spell_id) { - return IsEffectInSpell(spell_id, SE_PercentalHeal); + return IsEffectInSpell(spell_id, SpellEffect::PercentalHeal); } bool IsGroupOnlySpell(uint16 spell_id) @@ -288,13 +359,13 @@ bool IsBeneficialSpell(uint16 spell_id) // You'd think just checking goodEffect flag would be enough? if (spells[spell_id].good_effect == BENEFICIAL_EFFECT) { - // If the target type is ST_Self or ST_Pet and is a SE_CancleMagic spell + // If the target type is ST_Self or ST_Pet and is a SpellEffect::CancleMagic spell // it is not Beneficial const auto target_type = spells[spell_id].target_type; if ( target_type != ST_Self && target_type != ST_Pet && - IsEffectInSpell(spell_id, SE_CancelMagic) + IsEffectInSpell(spell_id, SpellEffect::CancelMagic) ) { return false; } @@ -316,7 +387,7 @@ bool IsBeneficialSpell(uint16 spell_id) // checking these SAI cause issues with the rng defensive proc line // So I guess instead of fixing it for real, just a quick hack :P if ( - spells[spell_id].effect_id[0] != SE_DefensiveProc && + spells[spell_id].effect_id[0] != SpellEffect::DefensiveProc && ( spell_affect_index == SAI_Calm || spell_affect_index == SAI_Dispell_Sight || @@ -332,16 +403,16 @@ bool IsBeneficialSpell(uint16 spell_id) if ( ( spell_affect_index == SAI_Calm && - IsEffectInSpell(spell_id, SE_Harmony) + IsEffectInSpell(spell_id, SpellEffect::Harmony) ) || ( spell_affect_index == SAI_Calm_Song && - IsEffectInSpell(spell_id, SE_BindSight) + IsEffectInSpell(spell_id, SpellEffect::BindSight) ) || ( spell_affect_index == SAI_Dispell_Sight && spells[spell_id].skill == EQ::skills::SkillDivination && - !IsEffectInSpell(spell_id, SE_VoiceGraft) + !IsEffectInSpell(spell_id, SpellEffect::VoiceGraft) ) ) { return false; @@ -365,51 +436,52 @@ bool IsDetrimentalSpell(uint16 spell_id) bool IsInvisibleSpell(uint16 spell_id) { return ( - IsEffectInSpell(spell_id, SE_Invisibility) || - IsEffectInSpell(spell_id, SE_Invisibility2) || - IsEffectInSpell(spell_id, SE_InvisVsUndead) || - IsEffectInSpell(spell_id, SE_InvisVsUndead2) || - IsEffectInSpell(spell_id, SE_InvisVsAnimals) || - IsEffectInSpell(spell_id, SE_ImprovedInvisAnimals) + IsEffectInSpell(spell_id, SpellEffect::Invisibility) || + IsEffectInSpell(spell_id, SpellEffect::Invisibility2) || + IsEffectInSpell(spell_id, SpellEffect::InvisVsUndead) || + IsEffectInSpell(spell_id, SpellEffect::InvisVsUndead2) || + IsEffectInSpell(spell_id, SpellEffect::InvisVsAnimals) || + IsEffectInSpell(spell_id, SpellEffect::ImprovedInvisAnimals) ); } bool IsInvulnerabilitySpell(uint16 spell_id) { - return IsEffectInSpell(spell_id, SE_DivineAura); + return IsEffectInSpell(spell_id, SpellEffect::DivineAura); } bool IsCompleteHealDurationSpell(uint16 spell_id) { - return IsEffectInSpell(spell_id, SE_CompleteHeal); + return IsEffectInSpell(spell_id, SpellEffect::CompleteHeal); } bool IsPoisonCounterSpell(uint16 spell_id) { - return IsEffectInSpell(spell_id, SE_PoisonCounter); + return IsEffectInSpell(spell_id, SpellEffect::PoisonCounter); } bool IsDiseaseCounterSpell(uint16 spell_id) { - return IsEffectInSpell(spell_id, SE_DiseaseCounter); + return IsEffectInSpell(spell_id, SpellEffect::DiseaseCounter); } bool IsSummonItemSpell(uint16 spell_id) { - return IsEffectInSpell(spell_id, SE_SummonItem); + return IsEffectInSpell(spell_id, SpellEffect::SummonItem); } bool IsSummonSkeletonSpell(uint16 spell_id) { - return IsEffectInSpell(spell_id, SE_NecPet); + return IsEffectInSpell(spell_id, SpellEffect::NecPet); } bool IsSummonPetSpell(uint16 spell_id) { return ( - IsEffectInSpell(spell_id, SE_SummonPet) || - IsEffectInSpell(spell_id, SE_SummonBSTPet) || - IsEffectInSpell(spell_id, SE_Familiar) + IsEffectInSpell(spell_id, SpellEffect::SummonPet) || + IsEffectInSpell(spell_id, SpellEffect::SummonBSTPet) || + IsEffectInSpell(spell_id, SpellEffect::Familiar) || + IsEffectInSpell(spell_id, SpellEffect::NecPet) ); } @@ -423,12 +495,12 @@ bool IsPetSpell(uint16 spell_id) bool IsSummonPCSpell(uint16 spell_id) { - return IsEffectInSpell(spell_id, SE_SummonPC); + return IsEffectInSpell(spell_id, SpellEffect::SummonPC); } bool IsCharmSpell(uint16 spell_id) { - return IsEffectInSpell(spell_id, SE_Charm); + return IsEffectInSpell(spell_id, SpellEffect::Charm); } bool IsResurrectionSicknessSpell(uint16 spell_id) { @@ -443,42 +515,42 @@ bool IsResurrectionSicknessSpell(uint16 spell_id) { bool IsBlindSpell(uint16 spell_id) { - return IsEffectInSpell(spell_id, SE_Blind); + return IsEffectInSpell(spell_id, SpellEffect::Blind); } bool IsHealthSpell(uint16 spell_id) { - return IsEffectInSpell(spell_id, SE_CurrentHP); + return IsEffectInSpell(spell_id, SpellEffect::CurrentHP); } bool IsCastTimeReductionSpell(uint16 spell_id) { - return IsEffectInSpell(spell_id, SE_IncreaseSpellHaste); + return IsEffectInSpell(spell_id, SpellEffect::IncreaseSpellHaste); } bool IsIncreaseDurationSpell(uint16 spell_id) { - return IsEffectInSpell(spell_id, SE_IncreaseSpellDuration); + return IsEffectInSpell(spell_id, SpellEffect::IncreaseSpellDuration); } bool IsManaCostReductionSpell(uint16 spell_id) { - return IsEffectInSpell(spell_id, SE_ReduceManaCost); + return IsEffectInSpell(spell_id, SpellEffect::ReduceManaCost); } bool IsIncreaseRangeSpell(uint16 spell_id) { - return IsEffectInSpell(spell_id, SE_IncreaseRange); + return IsEffectInSpell(spell_id, SpellEffect::IncreaseRange); } bool IsImprovedHealingSpell(uint16 spell_id) { - return IsEffectInSpell(spell_id, SE_ImprovedHeal); + return IsEffectInSpell(spell_id, SpellEffect::ImprovedHeal); } bool IsImprovedDamageSpell(uint16 spell_id) { - return IsEffectInSpell(spell_id, SE_ImprovedDamage); + return IsEffectInSpell(spell_id, SpellEffect::ImprovedDamage); } bool IsAEDurationSpell(uint16 spell_id) @@ -524,7 +596,7 @@ bool IsPureNukeSpell(uint16 spell_id) if ( effect_count == 1 && - IsEffectInSpell(spell_id, SE_CurrentHP) && + IsEffectInSpell(spell_id, SpellEffect::CurrentHP) && spells[spell_id].buff_duration == 0 && IsDamageSpell(spell_id) ) { @@ -560,8 +632,7 @@ bool IsPBAENukeSpell(uint16 spell_id) if ( IsPureNukeSpell(spell_id) && - spell.aoe_range > 0 && - spell.target_type == ST_AECaster + !IsTargetRequiredForSpell(spell_id) ) { return true; } @@ -588,6 +659,122 @@ bool IsAERainNukeSpell(uint16 spell_id) return false; } +bool IsAnyNukeOrStunSpell(uint16 spell_id) { + if (!IsValidSpell(spell_id)) { + return false; + } + + if (IsSelfConversionSpell(spell_id) || IsEscapeSpell(spell_id)) { + return false; + } + + if ( + IsPBAENukeSpell(spell_id) || + IsAERainNukeSpell(spell_id) || + IsPureNukeSpell(spell_id) || + IsStunSpell(spell_id) || + (IsDamageSpell(spell_id) && !IsDamageOverTimeSpell(spell_id)) + ) { + return true; + } + + return false; +} + +bool IsAnyAESpell(uint16 spell_id) { + if (!IsValidSpell(spell_id)) { + return false; + } + + return ( + IsTargetableAESpell(spell_id) || + IsAESpell(spell_id) || + IsPBAESpell(spell_id) || + IsAEDurationSpell(spell_id) || + IsAERainNukeSpell(spell_id) || + IsAERainSpell(spell_id) || + IsPBAENukeSpell(spell_id) + ); +} + +bool IsAESpell(uint16 spell_id) +{ + if (!IsValidSpell(spell_id)) { + return false; + } + + switch (spells[spell_id].target_type) { + case ST_TargetOptional: + case ST_GroupTeleport : + case ST_Target: + case ST_Self: + case ST_Animal: + case ST_Undead: + case ST_Summoned: + case ST_Tap: + case ST_Pet: + case ST_Corpse: + case ST_Plant: + case ST_Giant: + case ST_Dragon: + case ST_LDoNChest_Cursed: + case ST_Muramite: + case ST_SummonedPet: + case ST_GroupNoPets: + case ST_Group: + case ST_GroupClientAndPet: + case ST_TargetsTarget: + case ST_PetMaster: + return false; + default: + break; + } + + if ( + spells[spell_id].aoe_range > 0 + ) { + return true; + } + + return false; +} + +bool IsPBAESpell(uint16 spell_id) +{ + if (!IsValidSpell(spell_id)) { + return false; + } + + const auto& spell = spells[spell_id]; + + if ( + spell.aoe_range > 0 && + !IsTargetRequiredForSpell(spell_id) + ) { + return true; + } + + return false; +} + +bool IsAERainSpell(uint16 spell_id) +{ + if (!IsValidSpell(spell_id)) { + return false; + } + + const auto& spell = spells[spell_id]; + + if ( + spell.aoe_range > 0 && + spell.aoe_duration > 1000 + ) { + return true; + } + + return false; +} + bool IsPartialResistableSpell(uint16 spell_id) { if (!IsValidSpell(spell_id)) { @@ -612,8 +799,8 @@ bool IsPartialResistableSpell(uint16 spell_id) if ( spell.base_value[o] < 0 && ( - effect_id == SE_CurrentHPOnce || - effect_id == SE_CurrentHP + effect_id == SpellEffect::CurrentHPOnce || + effect_id == SpellEffect::CurrentHP ) ) { return true; @@ -659,7 +846,7 @@ bool IsTGBCompatibleSpell(uint16 spell_id) !IsDetrimentalSpell(spell_id) && spells[spell_id].buff_duration != 0 && !IsBardSong(spell_id) && - !IsEffectInSpell(spell_id, SE_Illusion) + !IsEffectInSpell(spell_id, SpellEffect::Illusion) ) { return true; } @@ -712,10 +899,10 @@ uint16 GetSpellTriggerSpellID(uint16 spell_id, int effect_id) for (int i = 0; i < EFFECT_COUNT; i++) { if ( - spell.effect_id[i] == SE_TriggerOnCast || - spell.effect_id[i] == SE_SpellTrigger || - spell.effect_id[i] == SE_ApplyEffect || - spell.effect_id[i] == SE_Trigger_Spell_Non_Item + spell.effect_id[i] == SpellEffect::TriggerOnCast || + spell.effect_id[i] == SpellEffect::SpellTrigger || + spell.effect_id[i] == SpellEffect::ApplyEffect || + spell.effect_id[i] == SpellEffect::Trigger_Spell_Non_Item ) { const auto trigger_spell_id = spell.limit_value[i]; if ( @@ -745,17 +932,17 @@ bool IsBlankSpellEffect(uint16 spell_id, int effect_index) const auto base_value = spell.base_value[effect_index]; const auto formula = spell.formula[effect_index]; - // SE_CHA is "spacer" - // SE_Stacking* are also considered blank where this is used + // SpellEffect::CHA is "spacer" + // SpellEffect::Stacking* are also considered blank where this is used if ( - effect == SE_Blank || + effect == SpellEffect::Blank || ( - effect == SE_CHA && + effect == SpellEffect::CHA && base_value == 0 && formula == 100 ) || - effect == SE_StackingCommand_Block || - effect == SE_StackingCommand_Overwrite + effect == SpellEffect::StackingCommand_Block || + effect == SpellEffect::StackingCommand_Overwrite ) { return true; } @@ -854,7 +1041,7 @@ int CalculatePoisonCounters(uint16 spell_id) for (int i = 0; i < EFFECT_COUNT; i++) { if ( - spell.effect_id[i] == SE_PoisonCounter && + spell.effect_id[i] == SpellEffect::PoisonCounter && spell.base_value[i] > 0 ) { counters += spell.base_value[i]; @@ -876,7 +1063,7 @@ int CalculateDiseaseCounters(uint16 spell_id) for (int i = 0; i < EFFECT_COUNT; i++) { if ( - spell.effect_id[i] == SE_DiseaseCounter && + spell.effect_id[i] == SpellEffect::DiseaseCounter && spell.base_value[i] > 0 ) { counters += spell.base_value[i]; @@ -898,7 +1085,7 @@ int CalculateCurseCounters(uint16 spell_id) for (int i = 0; i < EFFECT_COUNT; i++) { if ( - spell.effect_id[i] == SE_CurseCounter && + spell.effect_id[i] == SpellEffect::CurseCounter && spell.base_value[i] > 0 ) { counters += spell.base_value[i]; @@ -920,7 +1107,7 @@ int CalculateCorruptionCounters(uint16 spell_id) for (int i = 0; i < EFFECT_COUNT; i++) { if ( - spell.effect_id[i] == SE_CorruptionCounter && + spell.effect_id[i] == SpellEffect::CorruptionCounter && spell.base_value[i] > 0) { counters += spell.base_value[i]; @@ -1067,30 +1254,30 @@ int8 GetSpellResurrectionSicknessCheck(uint16 spell_id_one, uint16 spell_id_two) bool IsRuneSpell(uint16 spell_id) { - return IsEffectInSpell(spell_id, SE_Rune); + return IsEffectInSpell(spell_id, SpellEffect::Rune); } bool IsMagicRuneSpell(uint16 spell_id) { - return IsEffectInSpell(spell_id, SE_AbsorbMagicAtt); + return IsEffectInSpell(spell_id, SpellEffect::AbsorbMagicAtt); } bool IsManaTapSpell(uint16 spell_id) { return ( - IsEffectInSpell(spell_id, SE_CurrentMana) && + IsEffectInSpell(spell_id, SpellEffect::CurrentMana) && spells[spell_id].target_type == ST_Tap ); } bool IsAllianceSpell(uint16 spell_id) { - return IsEffectInSpell(spell_id, SE_AddFaction); + return IsEffectInSpell(spell_id, SpellEffect::AddFaction); } bool IsDeathSaveSpell(uint16 spell_id) { - return IsEffectInSpell(spell_id, SE_DeathSave); + return IsEffectInSpell(spell_id, SpellEffect::DeathSave); } // Deathsave spells with base of 1 are partial @@ -1104,7 +1291,7 @@ bool IsPartialDeathSaveSpell(uint16 spell_id) for (int i = 0; i < EFFECT_COUNT; i++) { if ( - spell.effect_id[i] == SE_DeathSave && + spell.effect_id[i] == SpellEffect::DeathSave && spell.base_value[i] == PARTIAL_DEATH_SAVE ) { return true; @@ -1125,7 +1312,7 @@ bool IsFullDeathSaveSpell(uint16 spell_id) for (int i = 0; i < EFFECT_COUNT; i++) { if ( - spell.effect_id[i] == SE_DeathSave && + spell.effect_id[i] == SpellEffect::DeathSave && spell.base_value[i] == FULL_DEATH_SAVE ) { return true; @@ -1137,33 +1324,33 @@ bool IsFullDeathSaveSpell(uint16 spell_id) bool IsShadowStepSpell(uint16 spell_id) { - return IsEffectInSpell(spell_id, SE_ShadowStep); + return IsEffectInSpell(spell_id, SpellEffect::ShadowStep); } bool IsSuccorSpell(uint16 spell_id) { - return IsEffectInSpell(spell_id, SE_Succor); + return IsEffectInSpell(spell_id, SpellEffect::Succor); } bool IsTeleportSpell(uint16 spell_id) { - return IsEffectInSpell(spell_id, SE_Teleport); + return IsEffectInSpell(spell_id, SpellEffect::Teleport); } bool IsTranslocateSpell(uint16 spell_id) { - return IsEffectInSpell(spell_id, SE_Translocate); + return IsEffectInSpell(spell_id, SpellEffect::Translocate); } bool IsGateSpell(uint16 spell_id) { - return IsEffectInSpell(spell_id, SE_Gate); + return IsEffectInSpell(spell_id, SpellEffect::Gate); } bool IsIllusionSpell(uint16 spell_id) { return ( - IsEffectInSpell(spell_id, SE_Illusion) && + IsEffectInSpell(spell_id, SpellEffect::Illusion) && spells[spell_id].target_type == ST_Self ); } @@ -1219,9 +1406,9 @@ DmgShieldType GetDamageShieldType(uint16 spell_id, int damage_shield_type) bool IsLDoNObjectSpell(uint16 spell_id) { return ( - IsEffectInSpell(spell_id, SE_AppraiseLDonChest) || - IsEffectInSpell(spell_id, SE_DisarmLDoNTrap) || - IsEffectInSpell(spell_id, SE_UnlockLDoNChest) + IsEffectInSpell(spell_id, SpellEffect::AppraiseLDonChest) || + IsEffectInSpell(spell_id, SpellEffect::DisarmLDoNTrap) || + IsEffectInSpell(spell_id, SpellEffect::UnlockLDoNChest) ); } @@ -1239,8 +1426,8 @@ bool IsHealOverTimeSpell(uint16 spell_id) { if ( ( - IsEffectInSpell(spell_id, SE_HealOverTime) || - GetSpellTriggerSpellID(spell_id, SE_HealOverTime) + IsEffectInSpell(spell_id, SpellEffect::HealOverTime) || + GetSpellTriggerSpellID(spell_id, SpellEffect::HealOverTime) ) && !IsGroupSpell(spell_id) ) { @@ -1255,9 +1442,9 @@ bool IsCompleteHealSpell(uint16 spell_id) if ( ( spell_id == SPELL_COMPLETE_HEAL || - IsEffectInSpell(spell_id, SE_CompleteHeal) || + IsEffectInSpell(spell_id, SpellEffect::CompleteHeal) || IsPercentalHealSpell(spell_id) || - GetSpellTriggerSpellID(spell_id, SE_CompleteHeal) + GetSpellTriggerSpellID(spell_id, SpellEffect::CompleteHeal) ) && !IsGroupSpell(spell_id) ) { @@ -1265,27 +1452,28 @@ bool IsCompleteHealSpell(uint16 spell_id) } return false; + } -bool IsFastHealSpell(uint16 spell_id) -{ +bool IsFastHealSpell(uint16 spell_id) { spell_id = ( - IsEffectInSpell(spell_id, SE_CurrentHP) ? - spell_id : - GetSpellTriggerSpellID(spell_id, SE_CurrentHP) + IsEffectInSpell(spell_id, SpellEffect::CurrentHP) ? + spell_id : + GetSpellTriggerSpellID(spell_id, SpellEffect::CurrentHP) ); if (!spell_id) { spell_id = ( - IsEffectInSpell(spell_id, SE_CurrentHPOnce) ? - spell_id : - GetSpellTriggerSpellID(spell_id, SE_CurrentHPOnce) + IsEffectInSpell(spell_id, SpellEffect::CurrentHPOnce) ? + spell_id : + GetSpellTriggerSpellID(spell_id, SpellEffect::CurrentHPOnce) ); } - if (spell_id) { + if (IsValidSpell(spell_id)) { if ( - spells[spell_id].cast_time <= MAX_FAST_HEAL_CASTING_TIME && + spell_id != SPELL_MINOR_HEALING && + (spells[spell_id].cast_time > MAX_VERY_FAST_HEAL_CASTING_TIME && spells[spell_id].cast_time <= MAX_FAST_HEAL_CASTING_TIME) && spells[spell_id].good_effect && !IsGroupSpell(spell_id) ) { @@ -1293,8 +1481,8 @@ bool IsFastHealSpell(uint16 spell_id) if ( spells[spell_id].base_value[i] > 0 && ( - spells[spell_id].effect_id[i] == SE_CurrentHP || - spells[spell_id].effect_id[i] == SE_CurrentHPOnce + spells[spell_id].effect_id[i] == SpellEffect::CurrentHP || + spells[spell_id].effect_id[i] == SpellEffect::CurrentHPOnce ) ) { return true; @@ -1309,21 +1497,22 @@ bool IsFastHealSpell(uint16 spell_id) bool IsVeryFastHealSpell(uint16 spell_id) { spell_id = ( - IsEffectInSpell(spell_id, SE_CurrentHP) ? + IsEffectInSpell(spell_id, SpellEffect::CurrentHP) ? spell_id : - GetSpellTriggerSpellID(spell_id, SE_CurrentHP) + GetSpellTriggerSpellID(spell_id, SpellEffect::CurrentHP) ); if (!spell_id) { spell_id = ( - IsEffectInSpell(spell_id, SE_CurrentHPOnce) ? + IsEffectInSpell(spell_id, SpellEffect::CurrentHPOnce) ? spell_id : - GetSpellTriggerSpellID(spell_id, SE_CurrentHPOnce) + GetSpellTriggerSpellID(spell_id, SpellEffect::CurrentHPOnce) ); } - 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) @@ -1332,8 +1521,8 @@ bool IsVeryFastHealSpell(uint16 spell_id) if ( spells[spell_id].base_value[i] > 0 && ( - spells[spell_id].effect_id[i] == SE_CurrentHP || - spells[spell_id].effect_id[i] == SE_CurrentHPOnce + spells[spell_id].effect_id[i] == SpellEffect::CurrentHP || + spells[spell_id].effect_id[i] == SpellEffect::CurrentHPOnce ) ) { return true; @@ -1348,21 +1537,26 @@ bool IsVeryFastHealSpell(uint16 spell_id) bool IsRegularSingleTargetHealSpell(uint16 spell_id) { spell_id = ( - IsEffectInSpell(spell_id, SE_CurrentHP) ? + IsEffectInSpell(spell_id, SpellEffect::CurrentHP) ? spell_id : - GetSpellTriggerSpellID(spell_id, SE_CurrentHP) + GetSpellTriggerSpellID(spell_id, SpellEffect::CurrentHP) ); if (!spell_id) { spell_id = ( - IsEffectInSpell(spell_id, SE_CurrentHPOnce) ? + IsEffectInSpell(spell_id, SpellEffect::CurrentHPOnce) ? spell_id : - GetSpellTriggerSpellID(spell_id, SE_CurrentHPOnce) + GetSpellTriggerSpellID(spell_id, SpellEffect::CurrentHPOnce) ); } - 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) && @@ -1373,8 +1567,54 @@ bool IsRegularSingleTargetHealSpell(uint16 spell_id) spells[spell_id].base_value[i] > 0 && spells[spell_id].buff_duration == 0 && ( - spells[spell_id].effect_id[i] == SE_CurrentHP || - spells[spell_id].effect_id[i] == SE_CurrentHPOnce + spells[spell_id].effect_id[i] == SpellEffect::CurrentHP || + spells[spell_id].effect_id[i] == SpellEffect::CurrentHPOnce + ) + ) { + return true; + } + } + } + } + + return false; +} + +bool IsRegularPetHealSpell(uint16 spell_id) +{ + spell_id = ( + IsEffectInSpell(spell_id, SpellEffect::CurrentHP) ? + spell_id : + GetSpellTriggerSpellID(spell_id, SpellEffect::CurrentHP) + ); + + if (!spell_id) { + spell_id = ( + IsEffectInSpell(spell_id, SpellEffect::CurrentHPOnce) ? + spell_id : + GetSpellTriggerSpellID(spell_id, SpellEffect::CurrentHPOnce) + ); + } + + 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_Pet || spells[spell_id].target_type == ST_SummonedPet) && + !IsCompleteHealSpell(spell_id) && + !IsHealOverTimeSpell(spell_id) && + !IsGroupSpell(spell_id) + ) { + for (int i = 0; i < EFFECT_COUNT; i++) { + if ( + spells[spell_id].base_value[i] > 0 && + spells[spell_id].buff_duration == 0 && + ( + spells[spell_id].effect_id[i] == SpellEffect::CurrentHP || + spells[spell_id].effect_id[i] == SpellEffect::CurrentHPOnce ) ) { return true; @@ -1389,20 +1629,20 @@ bool IsRegularSingleTargetHealSpell(uint16 spell_id) bool IsRegularGroupHealSpell(uint16 spell_id) { spell_id = ( - IsEffectInSpell(spell_id, SE_CurrentHP) ? + IsEffectInSpell(spell_id, SpellEffect::CurrentHP) ? spell_id : - GetSpellTriggerSpellID(spell_id, SE_CurrentHP) + GetSpellTriggerSpellID(spell_id, SpellEffect::CurrentHP) ); if (!spell_id) { spell_id = ( - IsEffectInSpell(spell_id, SE_CurrentHPOnce) ? + IsEffectInSpell(spell_id, SpellEffect::CurrentHPOnce) ? spell_id : - GetSpellTriggerSpellID(spell_id, SE_CurrentHPOnce) + GetSpellTriggerSpellID(spell_id, SpellEffect::CurrentHPOnce) ); } - if (spell_id) { + if (IsValidSpell(spell_id)) { if ( IsGroupSpell(spell_id) && !IsCompleteHealSpell(spell_id) && @@ -1413,8 +1653,8 @@ bool IsRegularGroupHealSpell(uint16 spell_id) spells[spell_id].base_value[i] > 0 && spells[spell_id].buff_duration == 0 && ( - spells[spell_id].effect_id[i] == SE_CurrentHP || - spells[spell_id].effect_id[i] == SE_CurrentHPOnce + spells[spell_id].effect_id[i] == SpellEffect::CurrentHP || + spells[spell_id].effect_id[i] == SpellEffect::CurrentHPOnce ) ) { return true; @@ -1426,11 +1666,60 @@ bool IsRegularGroupHealSpell(uint16 spell_id) return false; } -bool IsGroupCompleteHealSpell(uint16 spell_id) -{ +bool IsGroupCompleteHealSpell(uint16 spell_id) { + if ( + IsValidSpell(spell_id) && + ( + spell_id == SPELL_COMPLETE_HEAL || + IsEffectInSpell(spell_id, SpellEffect::CompleteHeal) || + IsPercentalHealSpell(spell_id) || + GetSpellTriggerSpellID(spell_id, SpellEffect::CompleteHeal) + ) && + IsGroupSpell(spell_id) + ) { + return true; + } + + return false; +} + +bool IsGroupHealOverTimeSpell(uint16 spell_id) { + if ( + IsValidSpell(spell_id) && + ( + IsEffectInSpell(spell_id, SpellEffect::HealOverTime) || + GetSpellTriggerSpellID(spell_id, SpellEffect::HealOverTime) + ) && + IsGroupSpell(spell_id) + ) { + return true; + } + + return false; +} + +bool IsAnyHealSpell(uint16 spell_id) { + if (!IsValidSpell(spell_id)) { + return false; + } + + if (spell_id == SPELL_NATURES_RECOVERY) { + return false; + } + + //spell_id != SPELL_ADRENALINE_SWELL && + //spell_id != SPELL_ADRENALINE_SWELL_RK2 && + //spell_id != SPELL_ADRENALINE_SWELL_RK3 && if ( - IsGroupSpell(spell_id) && - IsCompleteHealSpell(spell_id) + IsHealOverTimeSpell(spell_id) || + IsGroupHealOverTimeSpell(spell_id) || + IsFastHealSpell(spell_id) || + IsVeryFastHealSpell(spell_id) || + IsRegularSingleTargetHealSpell(spell_id) || + IsRegularGroupHealSpell(spell_id) || + IsCompleteHealSpell(spell_id) || + IsGroupCompleteHealSpell(spell_id) || + IsRegularPetHealSpell(spell_id) ) { return true; } @@ -1438,89 +1727,145 @@ bool IsGroupCompleteHealSpell(uint16 spell_id) return false; } -bool IsGroupHealOverTimeSpell(uint16 spell_id) -{ +bool IsAnyBuffSpell(uint16 spell_id) { + if (!IsValidSpell(spell_id)) { + return false; + } + if ( - IsGroupSpell(spell_id) && - IsHealOverTimeSpell(spell_id) && - spells[spell_id].buff_duration < 10 + spell_id == SPELL_NATURES_RECOVERY || + IsBuffSpell(spell_id) && + IsBeneficialSpell(spell_id) && + !IsBardSong(spell_id) && + !IsEscapeSpell(spell_id) && + (!IsSummonPetSpell(spell_id) && !IsEffectInSpell(spell_id, SpellEffect::TemporaryPets)) ) { return true; } return false; } +bool IsDispelSpell(uint16 spell_id) { + if (!IsValidSpell(spell_id)) { + return false; + } + + if ( + IsEffectInSpell(spell_id, SpellEffect::CancelMagic) || + IsEffectInSpell(spell_id, SpellEffect::DispelBeneficial) || + IsEffectInSpell(spell_id, SpellEffect::DispelBeneficial) + ) { + return true; + } + + return false; +} + +bool IsEscapeSpell(uint16 spell_id) { + if (!IsValidSpell(spell_id)) { + return false; + } + + return ( + IsInvulnerabilitySpell(spell_id) || + IsEffectInSpell(spell_id, SpellEffect::FeignDeath) || + IsEffectInSpell(spell_id, SpellEffect::DeathSave) || + IsEffectInSpell(spell_id, SpellEffect::Destroy) || + ( + IsEffectInSpell(spell_id, SpellEffect::WipeHateList) && + spells[spell_id].base_value[GetSpellEffectIndex(spell_id, SpellEffect::WipeHateList)] > 0 + ) + ); +} bool IsDebuffSpell(uint16 spell_id) { - if ( + if (!IsValidSpell(spell_id)) { + return false; + } + + return !( IsBeneficialSpell(spell_id) || IsHealthSpell(spell_id) || IsStunSpell(spell_id) || IsMesmerizeSpell(spell_id) || IsCharmSpell(spell_id) || IsSlowSpell(spell_id) || - IsEffectInSpell(spell_id, SE_Root) || - IsEffectInSpell(spell_id, SE_CancelMagic) || - IsEffectInSpell(spell_id, SE_MovementSpeed) || + IsEffectInSpell(spell_id, SpellEffect::Root) || + IsEffectInSpell(spell_id, SpellEffect::CancelMagic) || + IsEffectInSpell(spell_id, SpellEffect::MovementSpeed) || IsFearSpell(spell_id) || - IsEffectInSpell(spell_id, SE_InstantHate) - ) { + IsEffectInSpell(spell_id, SpellEffect::InstantHate) || + IsEffectInSpell(spell_id, SpellEffect::TossUp) + ); +} + +bool IsHateReduxSpell(uint16 spell_id) { + if (!IsValidSpell(spell_id)) { return false; } - return true; + return ( + ( + IsEffectInSpell(spell_id, SpellEffect::InstantHate) && + spells[spell_id].base_value[GetSpellEffectIndex(spell_id, SpellEffect::InstantHate)] < 0 + ) || + ( + IsEffectInSpell(spell_id, SpellEffect::Hate) && + spells[spell_id].base_value[GetSpellEffectIndex(spell_id, SpellEffect::Hate)] < 0 + ) || + ( + IsEffectInSpell(spell_id, SpellEffect::ReduceHate) && + spells[spell_id].base_value[GetSpellEffectIndex(spell_id, SpellEffect::ReduceHate)] < 0 + ) + ); } bool IsResistDebuffSpell(uint16 spell_id) { - if ( - !IsBeneficialSpell(spell_id) && - ( - IsEffectInSpell(spell_id, SE_ResistFire) || - IsEffectInSpell(spell_id, SE_ResistCold) || - IsEffectInSpell(spell_id, SE_ResistPoison) || - IsEffectInSpell(spell_id, SE_ResistDisease) || - IsEffectInSpell(spell_id, SE_ResistMagic) || - IsEffectInSpell(spell_id, SE_ResistAll) || - IsEffectInSpell(spell_id, SE_ResistCorruption) - ) - ) { - return true; + if (!IsValidSpell(spell_id)) { + return false; } - return false; + return ( + !IsBeneficialSpell(spell_id) && + ( + IsEffectInSpell(spell_id, SpellEffect::ResistFire) || + IsEffectInSpell(spell_id, SpellEffect::ResistCold) || + IsEffectInSpell(spell_id, SpellEffect::ResistPoison) || + IsEffectInSpell(spell_id, SpellEffect::ResistDisease) || + IsEffectInSpell(spell_id, SpellEffect::ResistMagic) || + IsEffectInSpell(spell_id, SpellEffect::ResistAll) || + IsEffectInSpell(spell_id, SpellEffect::ResistCorruption) + ) + ); } bool IsSelfConversionSpell(uint16 spell_id) { - if ( - GetSpellTargetType(spell_id) == ST_Self && - IsEffectInSpell(spell_id, SE_CurrentMana) && - IsEffectInSpell(spell_id, SE_CurrentHP) && - spells[spell_id].base_value[GetSpellEffectIndex(spell_id, SE_CurrentMana)] > 0 && - spells[spell_id].base_value[GetSpellEffectIndex(spell_id, SE_CurrentHP)] < 0 - ) { - return true; + if (!IsValidSpell(spell_id)) { + return false; } - return false; + return ( + GetSpellTargetType(spell_id) == ST_Self && + IsEffectInSpell(spell_id, SpellEffect::CurrentMana) && + IsEffectInSpell(spell_id, SpellEffect::CurrentHP) && + spells[spell_id].base_value[GetSpellEffectIndex(spell_id, SpellEffect::CurrentMana)] > 0 && + spells[spell_id].base_value[GetSpellEffectIndex(spell_id, SpellEffect::CurrentHP)] < 0 + ); } // returns true for both detrimental and beneficial buffs bool IsBuffSpell(uint16 spell_id) { - if ( + return ( IsValidSpell(spell_id) && ( spells[spell_id].buff_duration || spells[spell_id].buff_duration_formula ) - ) { - return true; - } - - return false; + ); } bool IsPersistDeathSpell(uint16 spell_id) @@ -1551,9 +1896,9 @@ bool IsCastOnFadeDurationSpell(uint16 spell_id) { for (int i = 0; i < EFFECT_COUNT; ++i) { if ( - spells[spell_id].effect_id[i] == SE_CastOnFadeEffect || - spells[spell_id].effect_id[i] == SE_CastOnFadeEffectNPC || - spells[spell_id].effect_id[i] == SE_CastOnFadeEffectAlways + spells[spell_id].effect_id[i] == SpellEffect::CastOnFadeEffect || + spells[spell_id].effect_id[i] == SpellEffect::CastOnFadeEffectNPC || + spells[spell_id].effect_id[i] == SpellEffect::CastOnFadeEffectAlways ) { return true; } @@ -1581,7 +1926,7 @@ bool IsDistanceModifierSpell(uint16 spell_id) int GetSpellPartialMeleeRuneReduction(uint16 spell_id) { for (int i = 0; i < EFFECT_COUNT; ++i) { - if (spells[spell_id].effect_id[i] == SE_MitigateMeleeDamage) { + if (spells[spell_id].effect_id[i] == SpellEffect::MitigateMeleeDamage) { return spells[spell_id].base_value[i]; } } @@ -1592,7 +1937,7 @@ int GetSpellPartialMeleeRuneReduction(uint16 spell_id) int GetSpellPartialMagicRuneReduction(uint16 spell_id) { for (int i = 0; i < EFFECT_COUNT; ++i) { - if (spells[spell_id].effect_id[i] == SE_MitigateSpellDamage) { + if (spells[spell_id].effect_id[i] == SpellEffect::MitigateSpellDamage) { return spells[spell_id].base_value[i]; } } @@ -1603,7 +1948,7 @@ int GetSpellPartialMagicRuneReduction(uint16 spell_id) int GetSpellPartialMeleeRuneAmount(uint16 spell_id) { for (int i = 0; i < EFFECT_COUNT; ++i) { - if (spells[spell_id].effect_id[i] == SE_MitigateMeleeDamage) { + if (spells[spell_id].effect_id[i] == SpellEffect::MitigateMeleeDamage) { return spells[spell_id].max_value[i]; } } @@ -1614,7 +1959,7 @@ int GetSpellPartialMeleeRuneAmount(uint16 spell_id) int GetSpellPartialMagicRuneAmount(uint16 spell_id) { for (int i = 0; i < EFFECT_COUNT; ++i) { - if (spells[spell_id].effect_id[i] == SE_MitigateSpellDamage) { + if (spells[spell_id].effect_id[i] == SpellEffect::MitigateSpellDamage) { return spells[spell_id].max_value[i]; } } @@ -1657,15 +2002,15 @@ bool IsStackableDOT(uint16 spell_id) } return ( - IsEffectInSpell(spell_id, SE_CurrentHP) || - IsEffectInSpell(spell_id, SE_GravityEffect) + IsEffectInSpell(spell_id, SpellEffect::CurrentHP) || + IsEffectInSpell(spell_id, SpellEffect::GravityEffect) ); } bool IsBardOnlyStackEffect(int effect_id) { switch (effect_id) { - case SE_BardAEDot: + case SpellEffect::BardAEDot: return true; default: return false; @@ -1694,91 +2039,91 @@ bool IsEffectIgnoredInStacking(int effect_id) { // this should match RoF2 switch (effect_id) { - case SE_SeeInvis: - case SE_DiseaseCounter: - case SE_PoisonCounter: - case SE_Levitate: - case SE_InfraVision: - case SE_UltraVision: - case SE_CurrentHPOnce: - case SE_CurseCounter: - case SE_ImprovedDamage: - case SE_ImprovedHeal: - case SE_SpellResistReduction: - case SE_IncreaseSpellHaste: - case SE_IncreaseSpellDuration: - case SE_IncreaseRange: - case SE_SpellHateMod: - case SE_ReduceReagentCost: - case SE_ReduceManaCost: - case SE_FcStunTimeMod: - case SE_LimitMaxLevel: - case SE_LimitResist: - case SE_LimitTarget: - case SE_LimitEffect: - case SE_LimitSpellType: - case SE_LimitSpell: - case SE_LimitMinDur: - case SE_LimitInstant: - case SE_LimitMinLevel: - case SE_LimitCastTimeMin: - case SE_LimitCastTimeMax: - case SE_StackingCommand_Block: - case SE_StackingCommand_Overwrite: - case SE_PetPowerIncrease: - case SE_SkillDamageAmount: - case SE_ChannelChanceSpells: - case SE_Blank: - case SE_FcDamageAmt: - case SE_SpellDurationIncByTic: - case SE_FcSpellVulnerability: - case SE_FcDamageAmtIncoming: - case SE_FcDamagePctCrit: - case SE_FcDamageAmtCrit: - case SE_ReduceReuseTimer: - case SE_LimitCombatSkills: - case SE_BlockNextSpellFocus: - case SE_SpellTrigger: - case SE_LimitManaMin: - case SE_CorruptionCounter: - case SE_ApplyEffect: - case SE_NegateSpellEffect: - case SE_LimitSpellGroup: - case SE_LimitManaMax: - case SE_FcHealAmt: - case SE_FcHealPctIncoming: - case SE_FcHealAmtIncoming: - case SE_FcHealPctCritIncoming: - case SE_FcHealAmtCrit: - case SE_LimitClass: - case SE_LimitRace: - case SE_FcBaseEffects: - case SE_FFItemClass: - case SE_SkillDamageAmount2: - case SE_FcLimitUse: - case SE_FcIncreaseNumHits: - case SE_LimitUseMin: - case SE_LimitUseType: - case SE_GravityEffect: - case SE_Display: + case SpellEffect::SeeInvis: + case SpellEffect::DiseaseCounter: + case SpellEffect::PoisonCounter: + case SpellEffect::Levitate: + case SpellEffect::InfraVision: + case SpellEffect::UltraVision: + case SpellEffect::CurrentHPOnce: + case SpellEffect::CurseCounter: + case SpellEffect::ImprovedDamage: + case SpellEffect::ImprovedHeal: + case SpellEffect::SpellResistReduction: + case SpellEffect::IncreaseSpellHaste: + case SpellEffect::IncreaseSpellDuration: + case SpellEffect::IncreaseRange: + case SpellEffect::SpellHateMod: + case SpellEffect::ReduceReagentCost: + case SpellEffect::ReduceManaCost: + case SpellEffect::FcStunTimeMod: + case SpellEffect::LimitMaxLevel: + case SpellEffect::LimitResist: + case SpellEffect::LimitTarget: + case SpellEffect::LimitEffect: + case SpellEffect::LimitSpellType: + case SpellEffect::LimitSpell: + case SpellEffect::LimitMinDur: + case SpellEffect::LimitInstant: + case SpellEffect::LimitMinLevel: + case SpellEffect::LimitCastTimeMin: + case SpellEffect::LimitCastTimeMax: + case SpellEffect::StackingCommand_Block: + case SpellEffect::StackingCommand_Overwrite: + case SpellEffect::PetPowerIncrease: + case SpellEffect::SkillDamageAmount: + case SpellEffect::ChannelChanceSpells: + case SpellEffect::Blank: + case SpellEffect::FcDamageAmt: + case SpellEffect::SpellDurationIncByTic: + case SpellEffect::FcSpellVulnerability: + case SpellEffect::FcDamageAmtIncoming: + case SpellEffect::FcDamagePctCrit: + case SpellEffect::FcDamageAmtCrit: + case SpellEffect::ReduceReuseTimer: + case SpellEffect::LimitCombatSkills: + case SpellEffect::BlockNextSpellFocus: + case SpellEffect::SpellTrigger: + case SpellEffect::LimitManaMin: + case SpellEffect::CorruptionCounter: + case SpellEffect::ApplyEffect: + case SpellEffect::NegateSpellEffect: + case SpellEffect::LimitSpellGroup: + case SpellEffect::LimitManaMax: + case SpellEffect::FcHealAmt: + case SpellEffect::FcHealPctIncoming: + case SpellEffect::FcHealAmtIncoming: + case SpellEffect::FcHealPctCritIncoming: + case SpellEffect::FcHealAmtCrit: + case SpellEffect::LimitClass: + case SpellEffect::LimitRace: + case SpellEffect::FcBaseEffects: + case SpellEffect::FFItemClass: + case SpellEffect::SkillDamageAmount2: + case SpellEffect::FcLimitUse: + case SpellEffect::FcIncreaseNumHits: + case SpellEffect::LimitUseMin: + case SpellEffect::LimitUseType: + case SpellEffect::GravityEffect: + case SpellEffect::Display: //Spell effects implemented after ROF2, following same pattern, lets assume these should go here. - case SE_Fc_Spell_Damage_Pct_IncomingPC: - case SE_Fc_Spell_Damage_Amt_IncomingPC: - case SE_Ff_CasterClass: - case SE_Ff_Same_Caster: - case SE_Proc_Timer_Modifier: - case SE_Weapon_Stance: - case SE_TwinCastBlocker: - case SE_Fc_CastTimeAmt: - case SE_Fc_CastTimeMod2: - case SE_Ff_DurationMax: - case SE_Ff_Endurance_Max: - case SE_Ff_Endurance_Min: - case SE_Ff_ReuseTimeMin: - case SE_Ff_ReuseTimeMax: - case SE_Ff_Value_Min: - case SE_Ff_Value_Max: - case SE_Ff_FocusTimerMin: + case SpellEffect::Fc_Spell_Damage_Pct_IncomingPC: + case SpellEffect::Fc_Spell_Damage_Amt_IncomingPC: + case SpellEffect::Ff_CasterClass: + case SpellEffect::Ff_Same_Caster: + case SpellEffect::Proc_Timer_Modifier: + case SpellEffect::Weapon_Stance: + case SpellEffect::TwinCastBlocker: + case SpellEffect::Fc_CastTimeAmt: + case SpellEffect::Fc_CastTimeMod2: + case SpellEffect::Ff_DurationMax: + case SpellEffect::Ff_Endurance_Max: + case SpellEffect::Ff_Endurance_Min: + case SpellEffect::Ff_ReuseTimeMin: + case SpellEffect::Ff_ReuseTimeMax: + case SpellEffect::Ff_Value_Min: + case SpellEffect::Ff_Value_Max: + case SpellEffect::Ff_FocusTimerMin: return true; default: return false; @@ -1788,40 +2133,40 @@ bool IsEffectIgnoredInStacking(int effect_id) bool IsFocusLimit(int effect_id) { switch (effect_id) { - case SE_LimitMaxLevel: - case SE_LimitResist: - case SE_LimitTarget: - case SE_LimitEffect: - case SE_LimitSpellType: - case SE_LimitSpell: - case SE_LimitMinDur: - case SE_LimitInstant: - case SE_LimitMinLevel: - case SE_LimitCastTimeMin: - case SE_LimitCastTimeMax: - case SE_LimitCombatSkills: - case SE_LimitManaMin: - case SE_LimitSpellGroup: - case SE_LimitManaMax: - case SE_LimitSpellClass: - case SE_LimitSpellSubclass: - case SE_LimitClass: - case SE_LimitRace: - case SE_LimitCastingSkill: - case SE_LimitUseMin: - case SE_LimitUseType: - case SE_Ff_Override_NotFocusable: - case SE_Ff_CasterClass: - case SE_Ff_Same_Caster: - case SE_Ff_DurationMax: - case SE_Ff_Endurance_Max: - case SE_Ff_Endurance_Min: - case SE_Ff_ReuseTimeMin: - case SE_Ff_ReuseTimeMax: - case SE_Ff_Value_Min: - case SE_Ff_Value_Max: - case SE_Ff_FocusTimerMin: - case SE_FFItemClass: + case SpellEffect::LimitMaxLevel: + case SpellEffect::LimitResist: + case SpellEffect::LimitTarget: + case SpellEffect::LimitEffect: + case SpellEffect::LimitSpellType: + case SpellEffect::LimitSpell: + case SpellEffect::LimitMinDur: + case SpellEffect::LimitInstant: + case SpellEffect::LimitMinLevel: + case SpellEffect::LimitCastTimeMin: + case SpellEffect::LimitCastTimeMax: + case SpellEffect::LimitCombatSkills: + case SpellEffect::LimitManaMin: + case SpellEffect::LimitSpellGroup: + case SpellEffect::LimitManaMax: + case SpellEffect::LimitSpellClass: + case SpellEffect::LimitSpellSubclass: + case SpellEffect::LimitClass: + case SpellEffect::LimitRace: + case SpellEffect::LimitCastingSkill: + case SpellEffect::LimitUseMin: + case SpellEffect::LimitUseType: + case SpellEffect::Ff_Override_NotFocusable: + case SpellEffect::Ff_CasterClass: + case SpellEffect::Ff_Same_Caster: + case SpellEffect::Ff_DurationMax: + case SpellEffect::Ff_Endurance_Max: + case SpellEffect::Ff_Endurance_Min: + case SpellEffect::Ff_ReuseTimeMin: + case SpellEffect::Ff_ReuseTimeMax: + case SpellEffect::Ff_Value_Min: + case SpellEffect::Ff_Value_Max: + case SpellEffect::Ff_FocusTimerMin: + case SpellEffect::FFItemClass: return true; default: return false; @@ -1847,10 +2192,10 @@ int GetSpellFuriousBash(uint16 spell_id) auto mod = 0; for (int i = 0; i < EFFECT_COUNT; ++i) { - if (spells[spell_id].effect_id[i] == SE_SpellHateMod) { + if (spells[spell_id].effect_id[i] == SpellEffect::SpellHateMod) { mod = spells[spell_id].base_value[i]; } else if ( - spells[spell_id].effect_id[i] == SE_LimitEffect && + spells[spell_id].effect_id[i] == SpellEffect::LimitEffect && spells[spell_id].base_value[i] == 999 ) { found_effect_limit = true; @@ -1930,47 +2275,47 @@ bool IsInstrumentModifierAppliedToSpellEffect(uint16 spell_id, int effect_id) //Effects that are verified modifiable by bard instrument/singing mods, or highly likely due to similiar type of effect. switch (effect_id) { //Only modify instant endurance or mana effects (Ie. Mana drain, Crescendo line) - case SE_CurrentEndurance: - case SE_CurrentMana: { + case SpellEffect::CurrentEndurance: + case SpellEffect::CurrentMana: { return spells[spell_id].buff_duration == 0; } - case SE_CurrentHP: - case SE_ArmorClass: - case SE_ACv2: - case SE_MovementSpeed: - case SE_ATK: - case SE_STR: - case SE_DEX: - case SE_AGI: - case SE_STA: - case SE_INT: - case SE_WIS: - case SE_CHA: - case SE_AllStats: - case SE_ResistFire: - case SE_ResistCold: - case SE_ResistPoison: - case SE_ResistDisease: - case SE_ResistMagic: - case SE_ResistAll: - case SE_ResistCorruption: - case SE_Rune: - case SE_AbsorbMagicAtt: - case SE_DamageShield: - case SE_MitigateDamageShield: - case SE_Amplification: //On live Amplification is modified by singing mods, including itself. - case SE_TripleAttackChance: - case SE_Flurry: - case SE_DamageModifier: - case SE_DamageModifier2: - case SE_MinDamageModifier: - case SE_ProcChance: - case SE_PetFlurry: // ? Need verified - case SE_DiseaseCounter: - case SE_PoisonCounter: - case SE_CurseCounter: - case SE_CorruptionCounter: + case SpellEffect::CurrentHP: + case SpellEffect::ArmorClass: + case SpellEffect::ACv2: + case SpellEffect::MovementSpeed: + case SpellEffect::ATK: + case SpellEffect::STR: + case SpellEffect::DEX: + case SpellEffect::AGI: + case SpellEffect::STA: + case SpellEffect::INT: + case SpellEffect::WIS: + case SpellEffect::CHA: + case SpellEffect::AllStats: + case SpellEffect::ResistFire: + case SpellEffect::ResistCold: + case SpellEffect::ResistPoison: + case SpellEffect::ResistDisease: + case SpellEffect::ResistMagic: + case SpellEffect::ResistAll: + case SpellEffect::ResistCorruption: + case SpellEffect::Rune: + case SpellEffect::AbsorbMagicAtt: + case SpellEffect::DamageShield: + case SpellEffect::MitigateDamageShield: + case SpellEffect::Amplification: //On live Amplification is modified by singing mods, including itself. + case SpellEffect::TripleAttackChance: + case SpellEffect::Flurry: + case SpellEffect::DamageModifier: + case SpellEffect::DamageModifier2: + case SpellEffect::MinDamageModifier: + case SpellEffect::ProcChance: + case SpellEffect::PetFlurry: // ? Need verified + case SpellEffect::DiseaseCounter: + case SpellEffect::PoisonCounter: + case SpellEffect::CurseCounter: + case SpellEffect::CorruptionCounter: return true; /* @@ -1978,45 +2323,45 @@ bool IsInstrumentModifierAppliedToSpellEffect(uint16 spell_id, int effect_id) Focus Effects, Proc Effects, Spell Triggers are not modified but handled elsewhere, not neccessary to checked here. */ - case SE_AttackSpeed: //(Haste AND Slow not modifiable) - case SE_AttackSpeed2: - case SE_AttackSpeed3: - case SE_Lull: - case SE_ChangeFrenzyRad: - case SE_Harmony: - case SE_AddFaction: - //case SE_CurrentMana: // duration only - case SE_ManaRegen_v2: - //case SE_CurrentEndurance: // duration only - case SE_PersistentEffect: - case SE_ReduceReuseTimer: - case SE_Stun: - case SE_Mez: - case SE_WipeHateList: //? - case SE_CancelMagic: - case SE_ManaAbsorbPercentDamage: - case SE_ResistSpellChance: - case SE_Reflect: - case SE_MitigateSpellDamage: - case SE_MitigateMeleeDamage: - case SE_AllInstrumentMod: - case SE_AddSingingMod: - case SE_SongModCap: - case SE_BardSongRange: - case SE_TemporaryPets: - case SE_SpellOnDeath: - case SE_Invisibility: - case SE_Invisibility2: - case SE_InvisVsUndead: - case SE_InvisVsUndead2: - case SE_InvisVsAnimals: - case SE_ImprovedInvisAnimals: - case SE_SeeInvis: - case SE_Levitate: - case SE_WaterBreathing: - case SE_ModelSize: - case SE_ChangeHeight: - case SE_MakeDrunk: + case SpellEffect::AttackSpeed: //(Haste AND Slow not modifiable) + case SpellEffect::AttackSpeed2: + case SpellEffect::AttackSpeed3: + case SpellEffect::Lull: + case SpellEffect::ChangeFrenzyRad: + case SpellEffect::Harmony: + case SpellEffect::AddFaction: + //case SpellEffect::CurrentMana: // duration only + case SpellEffect::ManaRegen_v2: + //case SpellEffect::CurrentEndurance: // duration only + case SpellEffect::PersistentEffect: + case SpellEffect::ReduceReuseTimer: + case SpellEffect::Stun: + case SpellEffect::Mez: + case SpellEffect::WipeHateList: //? + case SpellEffect::CancelMagic: + case SpellEffect::ManaAbsorbPercentDamage: + case SpellEffect::ResistSpellChance: + case SpellEffect::Reflect: + case SpellEffect::MitigateSpellDamage: + case SpellEffect::MitigateMeleeDamage: + case SpellEffect::AllInstrumentMod: + case SpellEffect::AddSingingMod: + case SpellEffect::SongModCap: + case SpellEffect::BardSongRange: + case SpellEffect::TemporaryPets: + case SpellEffect::SpellOnDeath: + case SpellEffect::Invisibility: + case SpellEffect::Invisibility2: + case SpellEffect::InvisVsUndead: + case SpellEffect::InvisVsUndead2: + case SpellEffect::InvisVsAnimals: + case SpellEffect::ImprovedInvisAnimals: + case SpellEffect::SeeInvis: + case SpellEffect::Levitate: + case SpellEffect::WaterBreathing: + case SpellEffect::ModelSize: + case SpellEffect::ChangeHeight: + case SpellEffect::MakeDrunk: return false; default: return true; @@ -2034,8 +2379,8 @@ bool IsPulsingBardSong(uint16 spell_id) spells[spell_id].buff_duration == 0xFFFF || spells[spell_id].recast_time > 0 || spells[spell_id].mana > 0 || - IsEffectInSpell(spell_id, SE_TemporaryPets) || - IsEffectInSpell(spell_id, SE_Familiar) + IsEffectInSpell(spell_id, SpellEffect::TemporaryPets) || + IsEffectInSpell(spell_id, SpellEffect::Familiar) ) { return false; } @@ -2210,28 +2555,28 @@ int GetSpellProcLimitTimer(uint16 spell_id, int proc_type) if (proc_type == ProcType::MELEE_PROC) { if ( - spells[spell_id].effect_id[i] == SE_WeaponProc || - spells[spell_id].effect_id[i] == SE_AddMeleeProc + spells[spell_id].effect_id[i] == SpellEffect::WeaponProc || + spells[spell_id].effect_id[i] == SpellEffect::AddMeleeProc ) { use_next_timer = true; } } if (proc_type == ProcType::RANGED_PROC) { - if (spells[spell_id].effect_id[i] == SE_RangedProc) { + if (spells[spell_id].effect_id[i] == SpellEffect::RangedProc) { use_next_timer = true; } } if (proc_type == ProcType::DEFENSIVE_PROC) { - if (spells[spell_id].effect_id[i] == SE_DefensiveProc) { + if (spells[spell_id].effect_id[i] == SpellEffect::DefensiveProc) { use_next_timer = true; } } if ( use_next_timer && - spells[spell_id].effect_id[i] == SE_Proc_Timer_Modifier + spells[spell_id].effect_id[i] == SpellEffect::Proc_Timer_Modifier ) { return spells[spell_id].limit_value[i]; } @@ -2338,23 +2683,23 @@ bool IsAegolismSpell(uint16 spell_id) { for (int i = 0; i < EFFECT_COUNT; ++i) { - if (i == 0 && spells[spell_id].effect_id[i] != SE_StackingCommand_Block) { + if (i == 0 && spells[spell_id].effect_id[i] != SpellEffect::StackingCommand_Block) { return 0; } - if (i == 1 && spells[spell_id].effect_id[i] == SE_TotalHP) { + if (i == 1 && spells[spell_id].effect_id[i] == SpellEffect::TotalHP) { has_max_hp = true; } - if (i == 2 && spells[spell_id].effect_id[i] == SE_CurrentHPOnce) { + if (i == 2 && spells[spell_id].effect_id[i] == SpellEffect::CurrentHPOnce) { has_current_hp = true; } - if (i == 3 && spells[spell_id].effect_id[i] == SE_ArmorClass) { + if (i == 3 && spells[spell_id].effect_id[i] == SpellEffect::ArmorClass) { has_ac = true; } - if (i == 4 && spells[spell_id].effect_id[i] != SE_StackingCommand_Overwrite) { + if (i == 4 && spells[spell_id].effect_id[i] != SpellEffect::StackingCommand_Overwrite) { return 0; } } @@ -2367,7 +2712,7 @@ bool IsAegolismSpell(uint16 spell_id) { bool AegolismStackingIsSymbolSpell(uint16 spell_id) { - + /* This is hardcoded to be specific to the type of HP buffs that are removed if a mob has an Aegolism buff. */ @@ -2381,16 +2726,16 @@ bool AegolismStackingIsSymbolSpell(uint16 spell_id) { for (int i = 0; i < EFFECT_COUNT; ++i) { - if ((i < 2 && spells[spell_id].effect_id[i] != SE_CHA) || - i > 3 && spells[spell_id].effect_id[i] != SE_Blank) { - return 0;; + if ((i < 2 && spells[spell_id].effect_id[i] != SpellEffect::CHA) || + i > 3 && spells[spell_id].effect_id[i] != SpellEffect::Blank) { + return 0; } - if (i == 2 && spells[spell_id].effect_id[i] == SE_TotalHP) { + if (i == 2 && spells[spell_id].effect_id[i] == SpellEffect::TotalHP) { has_max_hp = true; } - if (i == 3 && spells[spell_id].effect_id[i] == SE_CurrentHPOnce) { + if (i == 3 && spells[spell_id].effect_id[i] == SpellEffect::CurrentHPOnce) { has_current_hp = true; } } @@ -2414,12 +2759,12 @@ bool AegolismStackingIsArmorClassSpell(uint16 spell_id) { for (int i = 0; i < EFFECT_COUNT; ++i) { - if ((i < 3 && spells[spell_id].effect_id[i] != SE_CHA) || - i > 3 && spells[spell_id].effect_id[i] != SE_Blank) { + if ((i < 3 && spells[spell_id].effect_id[i] != SpellEffect::CHA) || + i > 3 && spells[spell_id].effect_id[i] != SpellEffect::Blank) { return 0; } - if (i == 3 && spells[spell_id].effect_id[i] == SE_ArmorClass) { + if (i == 3 && spells[spell_id].effect_id[i] == SpellEffect::ArmorClass) { has_ac = true; } } @@ -2430,3 +2775,236 @@ bool AegolismStackingIsArmorClassSpell(uint16 spell_id) { return 0; } + +int8 SpellEffectsCount(uint16 spell_id) { + if (!IsValidSpell(spell_id)) { + return false; + } + + int8 x = 0; + + for (int i = 0; i < EFFECT_COUNT; i++) { + if (!IsBlankSpellEffect(spell_id, i)) { + ++x; + } + } + + return x; +} + +bool IsLichSpell(uint16 spell_id) +{ + if (!IsValidSpell(spell_id)) { + return false; + } + + return ( + GetSpellTargetType(spell_id) == ST_Self && + IsEffectInSpell(spell_id, SpellEffect::CurrentMana) && + IsEffectInSpell(spell_id, SpellEffect::CurrentHP) && + spells[spell_id].base_value[GetSpellEffectIndex(spell_id, SpellEffect::CurrentMana)] > 0 && + spells[spell_id].base_value[GetSpellEffectIndex(spell_id, SpellEffect::CurrentHP)] < 0 && + spells[spell_id].buff_duration > 0 + ); +} + +bool IsInstantHealSpell(uint32 spell_id) { + if (!IsValidSpell(spell_id)) { + return false; + } + + return ( + IsRegularSingleTargetHealSpell(spell_id) || + IsRegularGroupHealSpell(spell_id) || + IsRegularPetHealSpell(spell_id) || + IsRegularGroupHealSpell(spell_id) || + spell_id == SPELL_COMPLETE_HEAL + ); +} + +bool IsResurrectSpell(uint16 spell_id) +{ + if (!IsValidSpell(spell_id)) { + return false; + } + + return IsEffectInSpell(spell_id, SpellEffect::Revive); +} + +bool IsResistanceBuffSpell(uint16 spell_id) { + if (!IsValidSpell(spell_id)) { + return false; + } + + const auto& spell = spells[spell_id]; + + for (int i = 0; i < EFFECT_COUNT; i++) { + if (IsBlankSpellEffect(spell_id, i)) { + continue; + } + + if ( + spell.effect_id[i] == SpellEffect::ResistFire || + spell.effect_id[i] == SpellEffect::ResistCold || + spell.effect_id[i] == SpellEffect::ResistPoison || + spell.effect_id[i] == SpellEffect::ResistDisease || + spell.effect_id[i] == SpellEffect::ResistMagic || + spell.effect_id[i] == SpellEffect::ResistCorruption || + spell.effect_id[i] == SpellEffect::ResistAll + ) { + return true; + } + } + + return false; +} + +bool IsResistanceOnlySpell(uint16 spell_id) { + if (!IsValidSpell(spell_id)) { + return false; + } + + const auto& spell = spells[spell_id]; + + for (int i = 0; i < EFFECT_COUNT; i++) { + if (IsBlankSpellEffect(spell_id, i)) { + continue; + } + + if ( + spell.effect_id[i] == SpellEffect::ResistFire || + spell.effect_id[i] == SpellEffect::ResistCold || + spell.effect_id[i] == SpellEffect::ResistPoison || + spell.effect_id[i] == SpellEffect::ResistDisease || + spell.effect_id[i] == SpellEffect::ResistMagic || + spell.effect_id[i] == SpellEffect::ResistCorruption || + spell.effect_id[i] == SpellEffect::ResistAll + ) { + continue; + } + + return false; + } + + return true; +} + +bool IsDamageShieldOnlySpell(uint16 spell_id) { + if (!IsValidSpell(spell_id)) { + return false; + } + + const auto& spell = spells[spell_id]; + + for (int i = 0; i < EFFECT_COUNT; i++) { + if (IsBlankSpellEffect(spell_id, i)) { + continue; + } + + if ( + spell.effect_id[i] != SpellEffect::DamageShield + ) { + return false; + } + } + + return true; +} + +bool IsDamageShieldAndResistSpell(uint16 spell_id) { + if (!IsValidSpell(spell_id)) { + return false; + } + + const auto& spell = spells[spell_id]; + + for (int i = 0; i < EFFECT_COUNT; i++) { + if (IsBlankSpellEffect(spell_id, i)) { + continue; + } + + if ( + spell.effect_id[i] != SpellEffect::DamageShield && + spell.effect_id[i] != SpellEffect::ResistFire && + spell.effect_id[i] != SpellEffect::ResistCold && + spell.effect_id[i] != SpellEffect::ResistPoison && + spell.effect_id[i] != SpellEffect::ResistDisease && + spell.effect_id[i] != SpellEffect::ResistMagic && + spell.effect_id[i] != SpellEffect::ResistCorruption && + spell.effect_id[i] != SpellEffect::ResistAll + ) { + return false; + } + } + + return true; +} + +bool IsHateSpell(uint16 spell_id) { + if (!IsValidSpell(spell_id)) { + return false; + } + + return ( + ( + IsEffectInSpell(spell_id, SpellEffect::Hate) && + spells[spell_id].base_value[GetSpellEffectIndex(spell_id, SpellEffect::Hate)] > 0 + ) || + ( + IsEffectInSpell(spell_id, SpellEffect::InstantHate) && + spells[spell_id].base_value[GetSpellEffectIndex(spell_id, SpellEffect::InstantHate)] > 0 + ) + ); +} + + +bool IsDisciplineTome(const EQ::ItemData* item) +{ + if (!item->IsClassCommon() || item->ItemType != EQ::item::ItemTypeSpell) { + return false; + } + + //Need a way to determine the difference between a spell and a tome + //so they cant turn in a spell and get it as a discipline + //this is kinda a hack: + + const std::string item_name = item->Name; + + if ( + !Strings::BeginsWith(item_name, "Tome of ") && + !Strings::BeginsWith(item_name, "Skill: ") + ) { + return false; + } + + //we know for sure none of the int casters get disciplines + uint32 class_bit = 0; + class_bit |= 1 << (Class::Wizard - 1); + class_bit |= 1 << (Class::Enchanter - 1); + class_bit |= 1 << (Class::Magician - 1); + class_bit |= 1 << (Class::Necromancer - 1); + if (item->Classes & class_bit) { + return false; + } + + const auto& spell_id = static_cast(item->Scroll.Effect); + if (!IsValidSpell(spell_id)) { + return false; + } + + if (!IsDiscipline(spell_id)) { + return false; + } + + const auto &spell = spells[spell_id]; + if ( + spell.classes[Class::Wizard - 1] != 255 && + spell.classes[Class::Enchanter - 1] != 255 && + spell.classes[Class::Magician - 1] != 255 && + spell.classes[Class::Necromancer - 1] != 255 + ) { + return false; + } + + return true; +} diff --git a/common/spdat.h b/common/spdat.h index d597c79fa..11130d6a4 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -20,6 +20,7 @@ #include "classes.h" #include "skills.h" +#include "item_data.h" #define SPELL_UNKNOWN 0xFFFF #define POISON_PROC 0xFFFE @@ -213,6 +214,11 @@ #define SPELL_BLOODTHIRST 8476 #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 // discipline IDs. #define DISC_UNHOLY_AURA 4520 @@ -623,38 +629,297 @@ enum ProcType enum SpellTypes : uint32 { - SpellType_Nuke = (1 << 0), - SpellType_Heal = (1 << 1), - SpellType_Root = (1 << 2), - SpellType_Buff = (1 << 3), - SpellType_Escape = (1 << 4), - SpellType_Pet = (1 << 5), - SpellType_Lifetap = (1 << 6), - SpellType_Snare = (1 << 7), - SpellType_DOT = (1 << 8), - SpellType_Dispel = (1 << 9), - SpellType_InCombatBuff = (1 << 10), - SpellType_Mez = (1 << 11), - SpellType_Charm = (1 << 12), - SpellType_Slow = (1 << 13), - SpellType_Debuff = (1 << 14), - SpellType_Cure = (1 << 15), - SpellType_Resurrect = (1 << 16), - SpellType_HateRedux = (1 << 17), - SpellType_InCombatBuffSong = (1 << 18), // bard in-combat group/ae buffs - SpellType_OutOfCombatBuffSong = (1 << 19), // bard out-of-combat group/ae buffs - SpellType_PreCombatBuff = (1 << 20), - SpellType_PreCombatBuffSong = (1 << 21) + SpellType_Nuke = (1 << 0), + SpellType_Heal = (1 << 1), + SpellType_Root = (1 << 2), + SpellType_Buff = (1 << 3), + SpellType_Escape = (1 << 4), + SpellType_Pet = (1 << 5), + SpellType_Lifetap = (1 << 6), + SpellType_Snare = (1 << 7), + SpellType_DOT = (1 << 8), + SpellType_Dispel = (1 << 9), + SpellType_InCombatBuff = (1 << 10), + SpellType_Mez = (1 << 11), + SpellType_Charm = (1 << 12), + SpellType_Slow = (1 << 13), + SpellType_Debuff = (1 << 14), + SpellType_Cure = (1 << 15), + SpellType_Resurrect = (1 << 16), + SpellType_HateRedux = (1 << 17), + SpellType_InCombatBuffSong = (1 << 18), // bard in-combat group/ae buffs + SpellType_OutOfCombatBuffSong = (1 << 19), // bard out-of-combat group/ae buffs + SpellType_PreCombatBuff = (1 << 20), + SpellType_PreCombatBuffSong = (1 << 21) }; -const uint32 SPELL_TYPE_MIN = (SpellType_Nuke << 1) - 1; -const uint32 SPELL_TYPE_MAX = (SpellType_PreCombatBuffSong << 1) - 1; -const uint32 SPELL_TYPE_ANY = 0xFFFFFFFF; +namespace BotSpellTypes +{ + constexpr uint16 Nuke = 0; + constexpr uint16 RegularHeal = 1; + constexpr uint16 Root = 2; + constexpr uint16 Buff = 3; + constexpr uint16 Escape = 4; + constexpr uint16 Pet = 5; + constexpr uint16 Lifetap = 6; + constexpr uint16 Snare = 7; + constexpr uint16 DOT = 8; + constexpr uint16 Dispel = 9; + constexpr uint16 InCombatBuff = 10; + constexpr uint16 Mez = 11; + constexpr uint16 Charm = 12; + constexpr uint16 Slow = 13; + constexpr uint16 Debuff = 14; + constexpr uint16 Cure = 15; + constexpr uint16 Resurrect = 16; + constexpr uint16 HateRedux = 17; + constexpr uint16 InCombatBuffSong = 18; + constexpr uint16 OutOfCombatBuffSong = 19; + constexpr uint16 PreCombatBuff = 20; + constexpr uint16 PreCombatBuffSong = 21; + constexpr uint16 Fear = 22; + constexpr uint16 Stun = 23; + constexpr uint16 HateLine = 24; + constexpr uint16 GroupCures = 25; + constexpr uint16 CompleteHeal = 26; + constexpr uint16 FastHeals = 27; + constexpr uint16 VeryFastHeals = 28; + constexpr uint16 GroupHeals = 29; + constexpr uint16 GroupCompleteHeals = 30; + constexpr uint16 GroupHoTHeals = 31; + constexpr uint16 HoTHeals = 32; + constexpr uint16 AENukes = 33; + constexpr uint16 AERains = 34; + constexpr uint16 AEMez = 35; + constexpr uint16 AEStun = 36; + constexpr uint16 AEDebuff = 37; + constexpr uint16 AESlow = 38; + constexpr uint16 AESnare = 39; + constexpr uint16 AEFear = 40; + constexpr uint16 AEDispel = 41; + constexpr uint16 AERoot = 42; + constexpr uint16 AEDoT = 43; + constexpr uint16 AELifetap = 44; + constexpr uint16 AEHateLine = 45; + constexpr uint16 PBAENuke = 46; + constexpr uint16 PetBuffs = 47; + constexpr uint16 PetRegularHeals = 48; + constexpr uint16 PetCompleteHeals = 49; + constexpr uint16 PetFastHeals = 50; + constexpr uint16 PetVeryFastHeals = 51; + constexpr uint16 PetHoTHeals = 52; + constexpr uint16 PetCures = 53; + constexpr uint16 DamageShields = 54; + constexpr uint16 ResistBuffs = 55; + constexpr uint16 PetDamageShields = 56; + constexpr uint16 PetResistBuffs = 57; + + // Command Spell Types + constexpr uint16 Teleport = 100; // this is handled by ^depart so uses other logic + constexpr uint16 Lull = 101; + constexpr uint16 Succor = 102; + constexpr uint16 BindAffinity = 103; + constexpr uint16 Identify = 104; + constexpr uint16 Levitate = 105; + constexpr uint16 Rune = 106; + constexpr uint16 WaterBreathing = 107; + constexpr uint16 Size = 108; + constexpr uint16 Invisibility = 109; + constexpr uint16 MovementSpeed = 110; + constexpr uint16 SendHome = 111; + constexpr uint16 SummonCorpse = 112; + constexpr uint16 AELull = 113; + + // Discipline Types + constexpr uint16 Discipline = 200; + constexpr uint16 DiscAggressive = 201; + constexpr uint16 DiscDefensive = 202; + 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 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 spell_type_names = { + { BotSpellTypes::Nuke, "Nuke" }, + { BotSpellTypes::RegularHeal, "Regular Heal" }, + { BotSpellTypes::Root, "Root" }, + { BotSpellTypes::Buff, "Buff" }, + { BotSpellTypes::Escape, "Escape" }, + { BotSpellTypes::Pet, "Pet" }, + { BotSpellTypes::Lifetap, "Lifetap" }, + { BotSpellTypes::Snare, "Snare" }, + { BotSpellTypes::DOT, "DoT" }, + { BotSpellTypes::Dispel, "Dispel" }, + { BotSpellTypes::InCombatBuff, "In-Combat Buff" }, + { BotSpellTypes::Mez, "Mez" }, + { BotSpellTypes::Charm, "Charm" }, + { BotSpellTypes::Slow, "Slow" }, + { BotSpellTypes::Debuff, "Debuff" }, + { BotSpellTypes::Cure, "Cure" }, + { BotSpellTypes::GroupCures, "Group Cure" }, + { BotSpellTypes::PetCures, "Pet Cure" }, + { BotSpellTypes::Resurrect, "Resurrect" }, + { BotSpellTypes::HateRedux, "Hate Reduction" }, + { BotSpellTypes::InCombatBuffSong, "In-Combat Buff Song" }, + { BotSpellTypes::OutOfCombatBuffSong, "Out-of-Combat Buff Song" }, + { BotSpellTypes::PreCombatBuff, "Pre-Combat Buff" }, + { BotSpellTypes::PreCombatBuffSong, "Pre-Combat Buff Song" }, + { BotSpellTypes::Fear, "Fear" }, + { BotSpellTypes::Stun, "Stun" }, + { BotSpellTypes::CompleteHeal, "Complete Heal" }, + { BotSpellTypes::FastHeals, "Fast Heal" }, + { BotSpellTypes::VeryFastHeals, "Very Fast Heal" }, + { BotSpellTypes::GroupHeals, "Group Heal" }, + { BotSpellTypes::GroupCompleteHeals, "Group Complete Heal" }, + { BotSpellTypes::GroupHoTHeals, "Group HoT Heal" }, + { BotSpellTypes::HoTHeals, "HoT Heal" }, + { BotSpellTypes::AENukes, "AE Nuke" }, + { BotSpellTypes::AERains, "AE Rain" }, + { BotSpellTypes::AEMez, "AE Mez" }, + { BotSpellTypes::AEStun, "AE Stun" }, + { BotSpellTypes::AEDebuff, "AE Debuff" }, + { BotSpellTypes::AESlow, "AE Slow" }, + { BotSpellTypes::AESnare, "AE Snare" }, + { BotSpellTypes::AEFear, "AE Fear" }, + { BotSpellTypes::AEDispel, "AE Dispel" }, + { BotSpellTypes::AERoot, "AE Root" }, + { BotSpellTypes::AEDoT, "AE DoT" }, + { BotSpellTypes::AELifetap, "AE Lifetap" }, + { BotSpellTypes::PBAENuke, "PBAE Nuke" }, + { BotSpellTypes::PetBuffs, "Pet Buff" }, + { BotSpellTypes::PetRegularHeals, "Pet Regular Heal" }, + { BotSpellTypes::PetCompleteHeals, "Pet Complete Heal" }, + { BotSpellTypes::PetFastHeals, "Pet Fast Heal" }, + { BotSpellTypes::PetVeryFastHeals, "Pet Very Fast Heal" }, + { BotSpellTypes::PetHoTHeals, "Pet HoT Heal" }, + { BotSpellTypes::DamageShields, "Damage Shield" }, + { BotSpellTypes::ResistBuffs, "Resist Buff" }, + { BotSpellTypes::PetDamageShields, "Pet Damage Shield" }, + { BotSpellTypes::PetResistBuffs, "Pet Resist Buff" }, + { BotSpellTypes::HateLine, "Hate Line" }, + { BotSpellTypes::AEHateLine, "AE Hate Line" }, + { BotSpellTypes::Lull, "Lull" }, + { BotSpellTypes::Teleport, "Teleport" }, + { BotSpellTypes::Succor, "Succor" }, + { BotSpellTypes::BindAffinity, "Bind Affinity" }, + { BotSpellTypes::Identify, "Identify" }, + { BotSpellTypes::Levitate, "Levitate" }, + { BotSpellTypes::Rune, "Rune" }, + { BotSpellTypes::WaterBreathing, "Water Breathing" }, + { BotSpellTypes::Size, "Size" }, + { BotSpellTypes::Invisibility, "Invisibility" }, + { BotSpellTypes::MovementSpeed, "Movement Speed" }, + { BotSpellTypes::SendHome, "Send Home" }, + { BotSpellTypes::SummonCorpse, "Summon Corpse" }, + { BotSpellTypes::AELull, "AE Lull" } +}; + +static std::map spell_type_short_names = { + { BotSpellTypes::Nuke, "nukes" }, + { BotSpellTypes::RegularHeal, "regularheals" }, + { BotSpellTypes::Root, "roots" }, + { BotSpellTypes::Buff, "buffs" }, + { BotSpellTypes::Escape, "escapes" }, + { BotSpellTypes::Pet, "pets" }, + { BotSpellTypes::Lifetap, "lifetaps" }, + { BotSpellTypes::Snare, "snares" }, + { BotSpellTypes::DOT, "dots" }, + { BotSpellTypes::Dispel, "dispels" }, + { BotSpellTypes::InCombatBuff, "incombatbuffs" }, + { BotSpellTypes::Mez, "mez" }, + { BotSpellTypes::Charm, "charms" }, + { BotSpellTypes::Slow, "slows" }, + { BotSpellTypes::Debuff, "debuffs" }, + { BotSpellTypes::Cure, "cures" }, + { BotSpellTypes::GroupCures, "groupcures" }, + { BotSpellTypes::PetCures, "petcures" }, + { BotSpellTypes::Resurrect, "resurrects" }, + { BotSpellTypes::HateRedux, "hateredux" }, + { BotSpellTypes::InCombatBuffSong, "incombatbuffsongs" }, + { BotSpellTypes::OutOfCombatBuffSong, "outofcombatbuffsongs" }, + { BotSpellTypes::PreCombatBuff, "precombatbuffs" }, + { BotSpellTypes::PreCombatBuffSong, "precombatbuffsongs" }, + { BotSpellTypes::Fear, "fears" }, + { BotSpellTypes::Stun, "stuns" }, + { BotSpellTypes::CompleteHeal, "completeheals" }, + { BotSpellTypes::FastHeals, "fastheals" }, + { BotSpellTypes::VeryFastHeals, "veryfastheals" }, + { BotSpellTypes::GroupHeals, "groupheals" }, + { BotSpellTypes::GroupCompleteHeals, "groupcompleteheals" }, + { BotSpellTypes::GroupHoTHeals, "grouphotheals" }, + { BotSpellTypes::HoTHeals, "hotheals" }, + { BotSpellTypes::AENukes, "aenukes" }, + { BotSpellTypes::AERains, "aerains" }, + { BotSpellTypes::AEMez, "aemez" }, + { BotSpellTypes::AEStun, "aestuns" }, + { BotSpellTypes::AEDebuff, "aedebuffs" }, + { BotSpellTypes::AESlow, "aeslows" }, + { BotSpellTypes::AESnare, "aesnares" }, + { BotSpellTypes::AEFear, "aefears" }, + { BotSpellTypes::AEDispel, "aedispels" }, + { BotSpellTypes::AERoot, "aeroots" }, + { BotSpellTypes::AEDoT, "aedots" }, + { BotSpellTypes::AELifetap, "aelifetaps" }, + { BotSpellTypes::PBAENuke, "pbaenukes" }, + { BotSpellTypes::PetBuffs, "petbuffs" }, + { BotSpellTypes::PetRegularHeals, "petregularheals" }, + { BotSpellTypes::PetCompleteHeals, "petcompleteheals" }, + { BotSpellTypes::PetFastHeals, "petfastheals" }, + { BotSpellTypes::PetVeryFastHeals, "petveryfastheals" }, + { BotSpellTypes::PetHoTHeals, "pethotheals" }, + { BotSpellTypes::DamageShields, "damageshields" }, + { BotSpellTypes::ResistBuffs, "resistbuffs" }, + { BotSpellTypes::PetDamageShields, "petdamageshields" }, + { BotSpellTypes::PetResistBuffs, "petresistbuffs" }, + { BotSpellTypes::HateLine, "hatelines" }, + { BotSpellTypes::AEHateLine, "aehatelines" }, + { BotSpellTypes::Lull, "lull" }, + { BotSpellTypes::Teleport, "teleport" }, + { BotSpellTypes::Succor, "succor" }, + { BotSpellTypes::BindAffinity, "bindaffinity" }, + { BotSpellTypes::Identify, "identify" }, + { BotSpellTypes::Levitate, "levitate" }, + { BotSpellTypes::Rune, "rune" }, + { BotSpellTypes::WaterBreathing, "waterbreathing" }, + { BotSpellTypes::Size, "size" }, + { BotSpellTypes::Invisibility, "invisibility" }, + { BotSpellTypes::MovementSpeed, "movementspeed" }, + { BotSpellTypes::SendHome, "sendhome" }, + { BotSpellTypes::SummonCorpse, "summoncorpse" }, + { BotSpellTypes::AELull, "aelull" } +}; const uint32 SPELL_TYPES_DETRIMENTAL = (SpellType_Nuke | SpellType_Root | SpellType_Lifetap | SpellType_Snare | SpellType_DOT | SpellType_Dispel | SpellType_Mez | SpellType_Charm | SpellType_Debuff | SpellType_Slow); const uint32 SPELL_TYPES_BENEFICIAL = (SpellType_Heal | SpellType_Buff | SpellType_Escape | SpellType_Pet | SpellType_InCombatBuff | SpellType_Cure | SpellType_HateRedux | SpellType_InCombatBuffSong | SpellType_OutOfCombatBuffSong | SpellType_PreCombatBuff | SpellType_PreCombatBuffSong); 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 BotSpellTypeUsesTargetSettings(uint16 spell_type); +bool IsBotSpellTypeInnate (uint16 spell_type); +bool IsAEBotSpellType(uint16 spell_type); +bool IsGroupBotSpellType(uint16 spell_type); +bool IsGroupTargetOnlyBotSpellType(uint16 spell_type); +bool IsPetBotSpellType(uint16 spell_type); +bool IsClientBotSpellType(uint16 spell_type); +bool IsHealBotSpellType(uint16 spell_type); +bool BotSpellTypeRequiresLoS(uint16 spell_type); +bool BotSpellTypeRequiresTarget(uint16 spell_type); +bool BotSpellTypeRequiresAEChecks(uint16 spell_type); +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 // TODO: import sai list @@ -792,539 +1057,538 @@ typedef enum { } DmgShieldType; //Spell Effect IDs -// https://forums.daybreakgames.com/eq/index.php?threads/enumerated-spa-list.206288/ +// https://web.archive.org/web/20250816011656/https://forums.everquest.com/index.php?threads/enumerated-spa-list.206288/ // mirror: http://pastebin.com/MYeQqGwe -#define SE_CurrentHP 0 // implemented - Heals and nukes, repeates every tic if in a buff -#define SE_ArmorClass 1 // implemented -#define SE_ATK 2 // implemented -#define SE_MovementSpeed 3 // implemented - SoW, SoC, etc -#define SE_STR 4 // implemented -#define SE_DEX 5 // implemented -#define SE_AGI 6 // implemented -#define SE_STA 7 // implemented -#define SE_INT 8 // implemented -#define SE_WIS 9 // implemented -#define SE_CHA 10 // implemented - used as a spacer -#define SE_AttackSpeed 11 // implemented -#define SE_Invisibility 12 // implemented - TO DO: Implemented Invisiblity Levels -#define SE_SeeInvis 13 // implemented - TO DO: Implemented See Invisiblity Levels -#define SE_WaterBreathing 14 // implemented -#define SE_CurrentMana 15 // implemented -//#define SE_NPCFrenzy 16 // not used -//#define SE_NPCAwareness 17 // not used -#define SE_Lull 18 // implemented - Reaction Radius -#define SE_AddFaction 19 // implemented - Alliance line -#define SE_Blind 20 // implemented -#define SE_Stun 21 // implemented -#define SE_Charm 22 // implemented -#define SE_Fear 23 // implemented -#define SE_Stamina 24 // implemented - Invigor and such -#define SE_BindAffinity 25 // implemented - TO DO: Implement 2nd and 3rd Recall (value 2,3 ect). Sets additional bind points. -#define SE_Gate 26 // implemented - Gate to bind point -#define SE_CancelMagic 27 // implemented -#define SE_InvisVsUndead 28 // implemented -#define SE_InvisVsAnimals 29 // implemented -#define SE_ChangeFrenzyRad 30 // implemented - Pacify -#define SE_Mez 31 // implemented -#define SE_SummonItem 32 // implemented -#define SE_SummonPet 33 // implemented -//#define SE_Confuse 34 // not used (Nimbus of Temporal Rifting) ? -#define SE_DiseaseCounter 35 // implemented -#define SE_PoisonCounter 36 // implemented -//#define SE_DetectHostile 37 // not used -//#define SE_DetectMagic 38 // not used -#define SE_TwinCastBlocker 39 // implemented - If present in spell, then the spell can not be twincast. -#define SE_DivineAura 40 // implemented -#define SE_Destroy 41 // implemented - Disintegrate, Banishment of Shadows -#define SE_ShadowStep 42 // implemented -#define SE_Berserk 43 // implemented (*not used in any known live spell) Makes client 'Berserk' giving crip blow chance. -#define SE_Lycanthropy 44 // implemented -#define SE_Vampirism 45 // implemented (*not used in any known live spell) Stackable lifetap from melee. -#define SE_ResistFire 46 // implemented -#define SE_ResistCold 47 // implemented -#define SE_ResistPoison 48 // implemented -#define SE_ResistDisease 49 // implemented -#define SE_ResistMagic 50 // implemented -//#define SE_DetectTraps 51 // not used -#define SE_SenseDead 52 // implemented -#define SE_SenseSummoned 53 // implemented -#define SE_SenseAnimals 54 // implemented -#define SE_Rune 55 // implemented -#define SE_TrueNorth 56 // implemented -#define SE_Levitate 57 // implemented -#define SE_Illusion 58 // implemented -#define SE_DamageShield 59 // implemented -//#define SE_TransferItem 60 // not used -#define SE_Identify 61 // implemented -//#define SE_ItemID 62 // not used -#define SE_WipeHateList 63 // implemented, @Memblur, chance to wipe hate list of target, base: pct chance, limit: none, max: ? (not implemented), Note: caster level and CHA add to pct chance -#define SE_SpinTarget 64 // implemented - TO DO: Not sure stun portion is working correctly -#define SE_InfraVision 65 // implemented -#define SE_UltraVision 66 // implemented -#define SE_EyeOfZomm 67 // implemented -#define SE_ReclaimPet 68 // implemented -#define SE_TotalHP 69 // implemented -//#define SE_CorpseBomb 70 // not used -#define SE_NecPet 71 // implemented -//#define SE_PreserveCorpse 72 // not used -#define SE_BindSight 73 // implemented, @Vision, see through the eyes of your target, click off buff to end effect, base: 1, limit: none, max: none -#define SE_FeignDeath 74 // implemented -#define SE_VoiceGraft 75 // implemented -#define SE_Sentinel 76 // *not implemented?(just seems to send a message) -#define SE_LocateCorpse 77 // implemented -#define SE_AbsorbMagicAtt 78 // implemented - Rune for spells -#define SE_CurrentHPOnce 79 // implemented - Heals and nukes, non-repeating if in a buff -//#define SE_EnchantLight 80 // not used -#define SE_Revive 81 // implemented - Resurrect -#define SE_SummonPC 82 // implemented -#define SE_Teleport 83 // implemented -#define SE_TossUp 84 // implemented - Gravity Flux -#define SE_WeaponProc 85 // implemented - i.e. Call of Fire -#define SE_Harmony 86 // implemented -#define SE_MagnifyVision 87 // implemented - Telescope -#define SE_Succor 88 // implemented - Evacuate/Succor lines -#define SE_ModelSize 89 // implemented - Shrink, Growth -//#define SE_Cloak 90 // *not implemented - Used in only 2 spells -#define SE_SummonCorpse 91 // implemented -#define SE_InstantHate 92 // implemented - add hate -#define SE_StopRain 93 // implemented - Wake of Karana -#define SE_NegateIfCombat 94 // implemented -#define SE_Sacrifice 95 // implemented -#define SE_Silence 96 // implemented -#define SE_ManaPool 97 // implemented -#define SE_AttackSpeed2 98 // implemented - Melody of Ervaj -#define SE_Root 99 // implemented -#define SE_HealOverTime 100 // implemented -#define SE_CompleteHeal 101 // implemented -#define SE_Fearless 102 // implemented - Valiant Companion -#define SE_CallPet 103 // implemented - Summon Companion -#define SE_Translocate 104 // implemented -#define SE_AntiGate 105 // implemented - Translocational Anchor -#define SE_SummonBSTPet 106 // implemented -#define SE_AlterNPCLevel 107 // implemented - not used on live -#define SE_Familiar 108 // implemented -#define SE_SummonItemIntoBag 109 // implemented - summons stuff into container -#define SE_IncreaseArchery 110 // implemented -#define SE_ResistAll 111 // implemented - Note: Physical Resists are not modified by this effect. -#define SE_CastingLevel 112 // implemented -#define SE_SummonHorse 113 // implemented -#define SE_ChangeAggro 114 // implemented - Hate modifing buffs(ie horrifying visage) -#define SE_Hunger 115 // implemented - Song of Sustenance -#define SE_CurseCounter 116 // implemented -#define SE_MagicWeapon 117 // implemented - makes weapon magical -#define SE_Amplification 118 // implemented, @Song, stackable singing mod, base: mod%, limit: none, max: none, Note: Can focus itself. -#define SE_AttackSpeed3 119 // implemented -#define SE_HealRate 120 // implemented - reduces healing by a % -#define SE_ReverseDS 121 // implemented -#define SE_ReduceSkill 122 // implemented - base: skill id, limit: none, max: none, formula: % skill is reduced (positive) -#define SE_Screech 123 // implemented Spell Blocker(If have buff with value +1 will block any effect with -1) -#define SE_ImprovedDamage 124 // implemented -#define SE_ImprovedHeal 125 // implemented -#define SE_SpellResistReduction 126 // implemented -#define SE_IncreaseSpellHaste 127 // implemented, @Fc, On Caster, cast time mod pct, base: pct -#define SE_IncreaseSpellDuration 128 // implemented, @Fc, On Caster, spell duration mod pct, base: pct -#define SE_IncreaseRange 129 // implemented, @Fc, On Caster, spell range mod pct, base: pct -#define SE_SpellHateMod 130 // implemented, @Fc, On Caster, spell hate mod pct, base: min pct, limit: max pct -#define SE_ReduceReagentCost 131 // implemented, @Fc, On Caster, do not consume reagent pct chance, base: min pct, limit: max pct -#define SE_ReduceManaCost 132 // implemented, @Fc, On Caster, reduce mana cost by pct, base: min pct, limt: max pct -#define SE_FcStunTimeMod 133 // implemented, @Fc, On Caster, spell range mod pct, base: pct -#define SE_LimitMaxLevel 134 // implemented, @Ff, Max level of spell that can be focused, if base2 then decrease effectiviness by base2 % per level over max, base: lv, base2: effectiveness pct -#define SE_LimitResist 135 // implemented, @Ff, Resist Type(s) that a spell focus can require or exclude, base1: resist type, Include: Positive Exclude: Negative -#define SE_LimitTarget 136 // implemented, @Ff, Target Type(s) that a spell focus can require or exclude, base1: target type, Include: Positive Exclude: Negative -#define SE_LimitEffect 137 // implemented, @Ff, Spell effect(s) that a spell focus can require or exclude, base1: SPA id, Include: Positive Exclude: Negative -#define SE_LimitSpellType 138 // implemented, @Ff, Only allow focus spells that are Beneficial or Detrimental, base1: 0=det 1=bene -#define SE_LimitSpell 139 // implemented, @Ff, Specific spell id(s) that a spell focus can require or exclude, base1: SPA id, Include: Positive Exclude: Negative -#define SE_LimitMinDur 140 // implemented, @Ff, Mininum duration of spell that can be focused, base1: tics -#define SE_LimitInstant 141 // implemented, @Ff, Include or exclude if an isntant cast spell can be focused, base1: 0=Exclude if Instant 1=Allow only if Instant -#define SE_LimitMinLevel 142 // implemented, @Ff, Mininum level of spell that can be focused, base1: lv -#define SE_LimitCastTimeMin 143 // implemented, @Ff, Mininum cast time of spell that can be focused, base1: milliseconds -#define SE_LimitCastTimeMax 144 // implemented, @Ff, Max cast time of spell that can be focused, base1: milliseconds -#define SE_Teleport2 145 // implemented - Banishment of the Pantheon -//#define SE_ElectricityResist 146 // *not implemented TODO: Now used on live, xyz for teleport spells? also in temp pets? -#define SE_PercentalHeal 147 // implemented -#define SE_StackingCommand_Block 148 // implemented? -#define SE_StackingCommand_Overwrite 149 // implemented? -#define SE_DeathSave 150 // implemented -#define SE_SuspendPet 151 // implemented, @Pet, allow caster to have an extra suspended pet, base: 0=no buffs/items 1=buffs+items, limit: none, max: none -#define SE_TemporaryPets 152 // implemented -#define SE_BalanceHP 153 // implemented -#define SE_DispelDetrimental 154 // implemented, @Dispel, removes only detrimental effects on a target, base: pct chance (950=95%), limit: none, max: none -#define SE_SpellCritDmgIncrease 155 // implemented - no known live spells use this currently -#define SE_IllusionCopy 156 // implemented - Deception -#define SE_SpellDamageShield 157 // implemented, @DS, causes non-melee damage on caster of a spell, base: Amt DS (negative), limit: none, max: unknown (same as base but +) -#define SE_Reflect 158 // implemented, @SpellMisc, reflect casted detrimental spell back at caster, base: chance pct, limit: resist modifier (positive value reduces resists), max: pct of base dmg mod (50=50pct of base) -#define SE_AllStats 159 // implemented -#define SE_MakeDrunk 160 // *not implemented - Effect works entirely client side (Should check against tolerance) -#define SE_MitigateSpellDamage 161 // implemented, @Runes, mitigate incoming spell damage by percentage until rune fades, base: percent mitigation, limit: max dmg absorbed per hit, max: rune amt, Note: If placed on item or AA, will provide stackable percent mitigation. -#define SE_MitigateMeleeDamage 162 // implemented - rune with max value -#define SE_NegateAttacks 163 // implemented -#define SE_AppraiseLDonChest 164 // implemented -#define SE_DisarmLDoNTrap 165 // implemented -#define SE_UnlockLDoNChest 166 // implemented -#define SE_PetPowerIncrease 167 // implemented, @Fc, On Caster, pet power mod, base: value -#define SE_MeleeMitigation 168 // implemented -#define SE_CriticalHitChance 169 // implemented -#define SE_SpellCritChance 170 // implemented -#define SE_CrippBlowChance 171 // implemented -#define SE_AvoidMeleeChance 172 // implemented -#define SE_RiposteChance 173 // implemented -#define SE_DodgeChance 174 // implemented -#define SE_ParryChance 175 // implemented -#define SE_DualWieldChance 176 // implemented -#define SE_DoubleAttackChance 177 // implemented -#define SE_MeleeLifetap 178 // implemented -#define SE_AllInstrumentMod 179 // implemented, @Song, set mod for ALL instrument/singing skills that will be used if higher then item mods, base: mod%, limit: none, max: none -#define SE_ResistSpellChance 180 // implemented -#define SE_ResistFearChance 181 // implemented -#define SE_HundredHands 182 // implemented -#define SE_MeleeSkillCheck 183 // implemented -#define SE_HitChance 184 // implemented -#define SE_DamageModifier 185 // implemented -#define SE_MinDamageModifier 186 // implemented -#define SE_BalanceMana 187 // implemented - Balances party mana -#define SE_IncreaseBlockChance 188 // implemented -#define SE_CurrentEndurance 189 // implemented -#define SE_EndurancePool 190 // implemented -#define SE_Amnesia 191 // implemented - Silence vs Melee Effect -#define SE_Hate 192 // implemented - Instant and hate over time. -#define SE_SkillAttack 193 // implemented, -#define SE_FadingMemories 194 // implemented, @Aggro, Remove from hate lists and make invisible. Can set max level of NPCs that can be affected. base: success chance, limit: max level (ROF2), max: max level (modern client), Note: Support for max level requires Rule (Spells, UseFadingMemoriesMaxLevel) to be true. If used from limit field, then it set as the level, ie. max level of 75 would use limit value of 75. If set from max field, max level 75 would use max value of 1075, if you want to set it so it checks a level range above the spell target then for it to only work on mobs 5 levels or below you set max value to 5. -#define SE_StunResist 195 // implemented -#define SE_StrikeThrough 196 // implemented -#define SE_SkillDamageTaken 197 // implemented -#define SE_CurrentEnduranceOnce 198 // implemented -#define SE_Taunt 199 // implemented - % chance to taunt the target -#define SE_ProcChance 200 // implemented -#define SE_RangedProc 201 // implemented -#define SE_IllusionOther 202 // implemented - Project Illusion -#define SE_MassGroupBuff 203 // implemented -#define SE_GroupFearImmunity 204 // implemented - (Does not use bonus) -#define SE_Rampage 205 // implemented, @Combat Instant, Perform a primary slot combat rounds on all creatures within a 40 foot radius, base: number of attack rounds, limit: max entities hit per round, max: none, Note: AE range is 40 by default. Custom: Set field 'aoe_range' to override default. Adding additional attacks and hit count limit. -#define SE_AETaunt 206 // implemented -#define SE_FleshToBone 207 // implemented -//#define SE_PurgePoison 208 // not used -#define SE_DispelBeneficial 209 // implemented, @Dispel, removes only beneficial effects on a target, base: pct chance (950=95%), limit: none, max: none -#define SE_PetShield 210 // implmented, @ShieldAbility, allows pet to 'shield' owner for 50 pct of damage taken for a duration, base: Time multiplier 1=12 seconds, 2=24 ect, limit: mitigation on pet owner override (not on live), max: mitigation on pet overide (not on live) -#define SE_AEMelee 211 // implemented TO DO: Implement to allow NPC use (client only atm). -#define SE_FrenziedDevastation 212 // implemented - increase spell criticals + all DD spells cast 2x mana. -#define SE_PetMaxHP 213 // implemented[AA] - increases the maximum hit points of your pet -#define SE_MaxHPChange 214 // implemented -#define SE_PetAvoidance 215 // implemented[AA] - increases pet ability to avoid melee damage -#define SE_Accuracy 216 // implemented -#define SE_HeadShot 217 // implemented - ability to head shot (base2 = damage) -#define SE_PetCriticalHit 218 // implemented[AA] - gives pets a baseline critical hit chance -#define SE_SlayUndead 219 // implemented - Allow extra damage against undead (base1 = rate, base2 = damage mod). -#define SE_SkillDamageAmount 220 // implemented -#define SE_Packrat 221 // implemented as bonus -#define SE_BlockBehind 222 // implemented - Chance to block from behind (with our without Shield) -#define SE_DoubleRiposte 223 // implemented - Chance to double riposte [not used on live] -#define SE_GiveDoubleRiposte 224 // implemented[AA] -#define SE_GiveDoubleAttack 225 // implemented[AA] - Allow any class to double attack with set chance. -#define SE_TwoHandBash 226 // *not implemented as bonus -#define SE_ReduceSkillTimer 227 // implemented -#define SE_ReduceFallDamage 228 // implented - reduce the damage that you take from falling -#define SE_PersistantCasting 229 // implemented -#define SE_ExtendedShielding 230 // implemented, @ShieldAbility, extends the range of your /shield ability by an amount of distance, base: distance units, limit: none, max: none -#define SE_StunBashChance 231 // implemented - increase chance to stun from bash. -#define SE_DivineSave 232 // implemented (base1 == % chance on death to insta-res) (base2 == spell cast on save) -#define SE_Metabolism 233 // implemented - Modifies food/drink consumption rates. -#define SE_ReduceApplyPoisonTime 234 // not implemented as bonus - reduces the time to apply poison -#define SE_ChannelChanceSpells 235 // implemented[AA] - chance to channel from SPELLS *No longer used on live. -//#define SE_FreePet 236 // not used -#define SE_GivePetGroupTarget 237 // implemented[AA] - (Pet Affinity) -#define SE_IllusionPersistence 238 // implemented - lends persistence to your illusionary disguises, causing them to last until you die or the illusion is forcibly removed. -#define SE_FeignedCastOnChance 239 // implemented - ability gives you an increasing chance for your feigned deaths to not be revealed by spells cast upon you. -//#define SE_StringUnbreakable 240 // not used [Likely related to above - you become immune to feign breaking on a resisted spell and have a good chance of feigning through a spell that successfully lands upon you.] -#define SE_ImprovedReclaimEnergy 241 // implemented - increase the amount of mana returned to you when reclaiming your pet. -#define SE_IncreaseChanceMemwipe 242 // implemented - @Memblur, increases the chance to wipe hate with memory blurr, base: chance pct, limit: none, max: none, Note: Mods final blur chance after other bonuses added. -#define SE_CharmBreakChance 243 // implemented - Total Domination -#define SE_RootBreakChance 244 // implemented[AA] reduce the chance that your root will break. -#define SE_TrapCircumvention 245 // implemented, @Traps, decreases the chance that you will set off a trap when opening a chest or other similar container by percentage, base: chance modifer, limit: none, max: none -#define SE_SetBreathLevel 246 // *not implemented as bonus -#define SE_RaiseSkillCap 247 // implemented[AA] - adds skill over the skill cap. -#define SE_SecondaryForte 248 // not implemented as bonus(gives you a 2nd specialize skill that can go past 50 to 100) -#define SE_SecondaryDmgInc 249 // implemented[AA] Allows off hand weapon to recieve a damage bonus (Sinister Strikes) -#define SE_SpellProcChance 250 // implemented - Increase chance to proc from melee proc spells (ie Spirit of Panther) -#define SE_ConsumeProjectile 251 // implemented[AA] - chance to not consume an arrow (ConsumeProjectile = 100) -#define SE_FrontalBackstabChance 252 // implemented[AA] - chance to perform a full damage backstab from front. -#define SE_FrontalBackstabMinDmg 253 // implemented[AA] - allow a frontal backstab for mininum damage. -#define SE_Blank 254 // implemented -#define SE_ShieldDuration 255 // implemented, , @ShieldAbility, extends the duration of your /shield ability, base: seconds, limit: none, max: none -#define SE_ShroudofStealth 256 // implemented -#define SE_PetDiscipline 257 // not implemented as bonus - /pet hold - official name is GivePetHold -#define SE_TripleBackstab 258 // implemented[AA] - chance to perform a triple backstab -#define SE_CombatStability 259 // implemented[AA] - damage mitigation -#define SE_AddSingingMod 260 // implemented, @Song, set mod for specific instrument/singing skills that will be used if higher then item mods, base: mod%, limit: ItemType ID, max: none -#define SE_SongModCap 261 // implemented, @Song, raise max song modifier cap, base: amt, limit: none, max: none, Note: No longer used on live -#define SE_RaiseStatCap 262 // implemented -#define SE_TradeSkillMastery 263 // implemented - lets you raise more than one tradeskill above master. -#define SE_HastenedAASkill 264 // implemented -#define SE_MasteryofPast 265 // implemented[AA] - Spells less than effect values level can not be fizzled -#define SE_ExtraAttackChance 266 // implemented, @OffBonus, gives your double attacks a percent chance to perform an extra attack with 2-handed primary weapon, base: chance, limit: amt attacks, max: none -#define SE_AddPetCommand 267 // implemented - sets command base2 to base1 -#define SE_ReduceTradeskillFail 268 // implemented - reduces chance to fail with given tradeskill by a percent chance -#define SE_MaxBindWound 269 // implemented[AA] - Increase max HP you can bind wound. -#define SE_BardSongRange 270 // implemented, @Song, increase range of beneficial bard songs, base: mod%, limit: none, max: none , Note: example Sionachie's Crescendo -#define SE_BaseMovementSpeed 271 // implemented[AA] - mods basemove speed, doesn't stack with other move mods -#define SE_CastingLevel2 272 // implemented -#define SE_CriticalDoTChance 273 // implemented -#define SE_CriticalHealChance 274 // implemented -#define SE_CriticalMend 275 // implemented[AA] - chance to critical monk mend -#define SE_Ambidexterity 276 // implemented[AA] - increase chance to duel weild by adding bonus 'skill' -#define SE_UnfailingDivinity 277 // implemented[AA] - ability grants your Death Pact-type spells a second chance to successfully heal their target, also can cause said spells to do a portion of their healing value even on a complete failure. -#define SE_FinishingBlow 278 // implemented[AA] - chance to do massive damage under 10% HP (base1 = chance, base2 = damage) -#define SE_Flurry 279 // implemented -#define SE_PetFlurry 280 // implemented[AA] -#define SE_FeignedMinion 281 // implemented, ability allows you to instruct your pet to feign death via the '/pet feign' command, base: succeed chance, limit: none, max: none, Note: Only implemented as an AA. -#define SE_ImprovedBindWound 282 // implemented[AA] - increase bind wound amount by percent. -#define SE_DoubleSpecialAttack 283 // implemented[AA] - Chance to perform second special attack as monk -//#define SE_LoHSetHeal 284 // not used -#define SE_NimbleEvasion 285 // *not implemented - base1 = 100 for max -#define SE_FcDamageAmt 286 // implemented, @Fc, On Caster, spell damage mod flat amt, base: amt -#define SE_SpellDurationIncByTic 287 // implemented, @Fc, SPA: 287, SE_SpellDurationIncByTic, On Caster, spell buff duration mod, base: tics -#define SE_SkillAttackProc 288 // implemented, @Procs, chance to cast a spell when using a skill, base: chance, limit: skill, max: spellid, note: if used in AA the spell id is set in aa_ranks spell field, chance is calculated as 100% = value 1000. -#define SE_CastOnFadeEffect 289 // implemented - Triggers only if fades after natural duration. -#define SE_IncreaseRunSpeedCap 290 // implemented[AA] - increases run speed over the hard cap -#define SE_Purify 291 // implemented, @Dispel, remove up specified amount of detiremental spells, base: amt removed, limit: none, max: none, Note: excluding charm, fear, resurrection, and revival sickness -#define SE_StrikeThrough2 292 // implemented[AA] - increasing chance of bypassing an opponent's special defenses, such as dodge, block, parry, and riposte. -#define SE_FrontalStunResist 293 // implemented[AA] - Reduce chance to be stunned from front. -- live descriptions sounds like this isn't limited to frontal anymore -#define SE_CriticalSpellChance 294 // implemented - increase chance to critical hit and critical damage modifier. -//#define SE_ReduceTimerSpecial 295 // not used -#define SE_FcSpellVulnerability 296 // implemented, @Fc, On Target, spell damage taken mod pct, base: min pct, limit: max pct -#define SE_FcDamageAmtIncoming 297 // implemetned, @Fc, On Target, damage taken flat amt, base: amt -#define SE_ChangeHeight 298 // implemented -#define SE_WakeTheDead 299 // implemented, @Pets, summon one temporary pet from nearby corpses that last a set duration, base: none, limit: none, max: duration (seconds). Note: max range of corpse is 250. -#define SE_Doppelganger 300 // implemented -#define SE_ArcheryDamageModifier 301 // implemented[AA] - increase archery damage by percent -#define SE_FcDamagePctCrit 302 // implemented, @Fc, On Caster, spell damage mod pct, base: min pct, limit: max pct, Note: applied after critical hits has been calculated. -#define SE_FcDamageAmtCrit 303 // implemented, @Fc, On Caster, spell damage mod flat amt, base: amt -#define SE_OffhandRiposteFail 304 // implemented as bonus - enemy cannot riposte offhand attacks -#define SE_MitigateDamageShield 305 // implemented - off hand attacks only (Shielding Resistance) -#define SE_ArmyOfTheDead 306 // implemented, @Pets, summon multiple temporary pets from nearby corpses that last a set duration, base: amount of corpses that a pet can summon from, limit: none, max: duration (seconds). Note: max range of corpse is 250. -//#define SE_Appraisal 307 // *not implemented Rogue AA - This ability allows you to estimate the selling price of an item you are holding on your cursor. -#define SE_ZoneSuspendMinion 308 // implemented, @Pet, allow suspended pets to be resummoned upon zoning, base: 1, limit: none, max: none, Calc: Bool -#define SE_GateCastersBindpoint 309 // implemented - Gate to casters bind point -#define SE_ReduceReuseTimer 310 // implemented, @Fc, On Caster, spell and disc reuse time mod by amount, base: milliseconds -#define SE_LimitCombatSkills 311 // implemented, @Ff, Include or exclude combat skills or procs from being focused, base1: 0=Exclude if proc 1=Allow only if proc. -#define SE_Sanctuary 312 // implemented - Places caster at bottom hate list, effect fades if cast cast spell on targets other than self. -#define SE_ForageAdditionalItems 313 // implemented[AA] - chance to forage additional items -#define SE_Invisibility2 314 // implemented - fixed duration invisible -#define SE_InvisVsUndead2 315 // implemented - fixed duration ITU -#define SE_ImprovedInvisAnimals 316 // implemented -#define SE_ItemHPRegenCapIncrease 317 // implemented[AA] - increases amount of health regen gained via items -#define SE_ItemManaRegenCapIncrease 318 // implemented - increases amount of mana regen you can gain via items -#define SE_CriticalHealOverTime 319 // implemented -#define SE_ShieldBlock 320 // implemented - Block attacks with shield -#define SE_ReduceHate 321 // implemented -#define SE_GateToHomeCity 322 // implemented -#define SE_DefensiveProc 323 // implemented -#define SE_HPToMana 324 // implemented -#define SE_NoBreakAESneak 325 // implemented[AA] - [AA Nerves of Steel] increasing chance to remain hidden when they are an indirect target of an AoE spell. -#define SE_SpellSlotIncrease 326 // *not implemented as bonus - increases your spell slot availability -#define SE_MysticalAttune 327 // implemented - increases amount of buffs that a player can have -#define SE_DelayDeath 328 // implemented - increases how far you can fall below 0 hp before you die -#define SE_ManaAbsorbPercentDamage 329 // implemented -#define SE_CriticalDamageMob 330 // implemented -#define SE_Salvage 331 // implemented - chance to recover items that would be destroyed in failed tradeskill combine -#define SE_SummonToCorpse 332 // *not implemented AA - Call of the Wild (Druid/Shaman Res spell with no exp) TOOD: implement this. -#define SE_CastOnRuneFadeEffect 333 // implemented -#define SE_BardAEDot 334 // implemented -#define SE_BlockNextSpellFocus 335 // implemented, @Fc, On Caster, chance to block next spell, base: chance -//#define SE_IllusionaryTarget 336 // not used -#define SE_PercentXPIncrease 337 // implemented -#define SE_SummonAndResAllCorpses 338 // implemented -#define SE_TriggerOnCast 339 // implemented, @Fc, On Caster, cast on spell use, base: chance pct limit: spellid -#define SE_SpellTrigger 340 // implemented - chance to trigger spell [Share rolls with 469] All base2 spells share roll chance, only 1 cast. -#define SE_ItemAttackCapIncrease 341 // implemented[AA] - increases the maximum amount of attack you can gain from items. -#define SE_ImmuneFleeing 342 // implemented - stop mob from fleeing -#define SE_InterruptCasting 343 // implemented - % chance to interrupt spells being cast every tic. Cacophony (8272) -#define SE_ChannelChanceItems 344 // implemented[AA] - chance to not have ITEM effects interrupted when you take damage. -#define SE_AssassinateLevel 345 // implemented as bonus - AA Assisination max level to kill -#define SE_HeadShotLevel 346 // implemented[AA] - HeadShot max level to kill -#define SE_DoubleRangedAttack 347 // implemented - chance at an additional archery attack (consumes arrow) -#define SE_LimitManaMin 348 // implemented, @Ff, Mininum mana of spell that can be focused, base1: mana amt -#define SE_ShieldEquipDmgMod 349 // implemented[AA] Increase melee base damage (indirectly increasing hate) when wearing a shield. -#define SE_ManaBurn 350 // implemented - Drains mana for damage/heal at a defined ratio up to a defined maximum amount of mana. -#define SE_PersistentEffect 351 // *not implemented. creates a trap/totem that casts a spell (spell id + base1?) when anything comes near it. can probably make a beacon for this -#define SE_IncreaseTrapCount 352 // *not implemented - looks to be some type of invulnerability? Test ITC (8755) -#define SE_AdditionalAura 353 // *not implemented - allows use of more than 1 aura, aa effect -//#define SE_DeactivateAllTraps 354 // *not implemented - looks to be some type of invulnerability? Test DAT (8757) -//#define SE_LearnTrap 355 // *not implemented - looks to be some type of invulnerability? Test LT (8758) -//#define SE_ChangeTriggerType 356 // not used -#define SE_FcMute 357 // implemented, @Fc, On Caster, prevents spell casting, base: chance pct -#define SE_CurrentManaOnce 358 // implemented -//#define SE_PassiveSenseTrap 359 // *not implemented - Invulnerability (Brell's Blessing) -#define SE_ProcOnKillShot 360 // implemented - a buff that has a base1 % to cast spell base2 when you kill a "challenging foe" base3 min level -#define SE_SpellOnDeath 361 // implemented - casts spell on death of buffed -#define SE_PotionBeltSlots 362 // *not implemented[AA] 'Quick Draw' expands the potion belt by one additional available item slot per rank. -#define SE_BandolierSlots 363 // *not implemented[AA] 'Battle Ready' expands the bandolier by one additional save slot per rank. -#define SE_TripleAttackChance 364 // implemented -#define SE_ProcOnSpellKillShot 365 // implemented - chance to trigger a spell on kill when the kill is caused by a specific spell with this effect in it (10470 Venin) -//#define SE_GroupShielding 366 // *not implemented[AA] This gives you /shieldgroup -#define SE_SetBodyType 367 // implemented - set body type of base1 so it can be affected by spells that are limited to that type (Plant, Animal, Undead, etc) -//#define SE_FactionMod 368 // *not implemented - increases faction with base1 (faction id, live won't match up w/ ours) by base2 -#define SE_CorruptionCounter 369 // implemented -#define SE_ResistCorruption 370 // implemented -#define SE_AttackSpeed4 371 // implemented - stackable slow effect 'Inhibit Melee' -#define SE_ForageSkill 372 // implemented[AA] Will increase the skill cap for those that have the Forage skill and grant the skill and raise the cap to those that do not. -#define SE_CastOnFadeEffectAlways 373 // implemented - Triggers if fades after natural duration OR from rune/numhits fades. -#define SE_ApplyEffect 374 // implemented -#define SE_DotCritDmgIncrease 375 // implemented - Increase damage of DoT critical amount -//#define SE_Fling 376 // *not implemented - used in 2 test spells (12945 | Movement Test Spell 1) -#define SE_CastOnFadeEffectNPC 377 // implemented - Triggers only if fades after natural duration (On live these are usually players spells that effect an NPC). -#define SE_SpellEffectResistChance 378 // implemented - Increase chance to resist specific spell effect (base1=value, base2=spell effect id) -#define SE_ShadowStepDirectional 379 // implemented - handled by client -#define SE_Knockdown 380 // implemented - small knock back(handled by client) -//#define SE_KnockTowardCaster 381 // *not implemented (Call of Hither) knocks you back to caster (value) distance units infront -#define SE_NegateSpellEffect 382 // implemented, @Debuff, negates specific spell effect benefits for the duration of the debuff and prevent non-duration spell effect from working, base: see NegateSpellEffecttype Enum, limit: SPA id, max: none -#define SE_SympatheticProc 383 // implemented, @Fc, On Caster, cast on spell use, base: variable proc chance on cast time, limit: spellid -#define SE_Leap 384 // implemented - Leap effect, ie stomping leap -#define SE_LimitSpellGroup 385 // implemented, @Ff, Spell group(s) that a spell focus can require or exclude, base1: spellgroup id, Include: Positive Exclude: Negative -#define SE_CastOnCurer 386 // implemented - Casts a spell on the person curing -#define SE_CastOnCure 387 // implemented - Casts a spell on the cured person -#define SE_SummonCorpseZone 388 // implemented - summons a corpse from any zone(nec AA) -#define SE_FcTimerRefresh 389 // implemented, @Fc, On Caster, reset all recast timers, base: 1, Note: Applied from casted spells only -#define SE_FcTimerLockout 390 // implemented, @Fc, On Caster, set a spell to be on recast timer, base: recast duration milliseconds, Note: Applied from casted spells only -#define SE_LimitManaMax 391 // implemented, @Ff, Mininum mana of spell that can be focused, base1: mana amt -#define SE_FcHealAmt 392 // implemented, @Fc, On Caster, spell healing mod flat amt, base: amt -#define SE_FcHealPctIncoming 393 // implemented, @Fc, On Target, heal received mod pct, base: pct, limit: random max pct -#define SE_FcHealAmtIncoming 394 // implemented, @Fc, On Target, heal received mod flat amt, base: amt -#define SE_FcHealPctCritIncoming 395 // implemented, @Fc, On Target, heal received mod pct, base: pct, limit: random max pct -#define SE_FcHealAmtCrit 396 // implemented, @Fc, On Caster, spell healing mod flat amt, base: amt -#define SE_PetMeleeMitigation 397 // implemented[AA] - additional mitigation to your pets. Adds AC -#define SE_SwarmPetDuration 398 // implemented - Affects the duration of swarm pets -#define SE_FcTwincast 399 // implemented - cast 2 spells for every 1 -#define SE_HealGroupFromMana 400 // implemented - Drains mana and heals for each point of mana drained -#define SE_ManaDrainWithDmg 401 // implemented - Deals damage based on the amount of mana drained -#define SE_EndDrainWithDmg 402 // implemented - Deals damage for the amount of endurance drained -#define SE_LimitSpellClass 403 // implemented, @Ff, 'Spell Category' using table field 'spell_class' that a spell focus can require or exclude, base1: category type, Include: Positive Exclude: Negative -#define SE_LimitSpellSubclass 404 // implemented, @Ff, 'Spell Category Subclass' using table field 'spell_subclass' that a spell focus can require or exclude, base1: category type, Include: Positive Exclude: Negative -#define SE_TwoHandBluntBlock 405 // implemented - chance to block attacks when using two hand blunt weapons (similiar to shield block) -#define SE_CastonNumHitFade 406 // implemented - casts a spell when a buff fades due to its numhits being depleted -#define SE_CastonFocusEffect 407 // implemented - casts a spell if focus limits are met (ie triggers when a focus effects is applied) -#define SE_LimitHPPercent 408 // implemented - limited to a certain percent of your hp(ie heals up to 50%) -#define SE_LimitManaPercent 409 // implemented - limited to a certain percent of your mana -#define SE_LimitEndPercent 410 // implemented - limited to a certain percent of your end -#define SE_LimitClass 411 // implemented, @Ff, Class(es) that can use the spell focus, base1: class(es), Note: The class value in dbase is +1 in relation to item class value, set as you would item for multiple classes -#define SE_LimitRace 412 // implemented, @Ff, Race that can use the spell focus, base1: race, Note: not used in any known live spells. Use only single race at a time. -#define SE_FcBaseEffects 413 // implemented, @Fc, On Caster, base spell effectiveness mod pct, base: pct -#define SE_LimitCastingSkill 414 // implemented, @Ff, Spell and singing skills(s) that a spell focus can require or exclude, base1: skill id, Include: Positive Exclude: Negative -#define SE_FFItemClass 415 // implemented, @Ff, Limits focuses to be applied only from item click. base1: item ItemType (-1 to include for all ItemTypes,-1000 to exclude clicks from getting the focus, or exclude specific SubTypes or Slots if set), limit: item SubType (-1 for all SubTypes), max: item Slots (bitmask of valid slots, -1 ALL slots), Note: not used on live. See comments in Mob::CalcFocusEffect for more details. -#define SE_ACv2 416 // implemented - New AC spell effect -#define SE_ManaRegen_v2 417 // implemented - New mana regen effect -#define SE_SkillDamageAmount2 418 // implemented - adds skill damage directly to certain attacks -#define SE_AddMeleeProc 419 // implemented - Adds a proc -#define SE_FcLimitUse 420 // implemented, @Fc, On Caster, numhits mod pct, base: pct, Note: not used in any known live spells -#define SE_FcIncreaseNumHits 421 // implemented, @Fc, On Caster, numhits mod flat amt, base: amt -#define SE_LimitUseMin 422 // implemented, @Ff Minium amount of numhits for a spell to be focused, base: numhit amt -#define SE_LimitUseType 423 // implemented, @Ff Focus will only affect if has this numhits type, base: numhit type -#define SE_GravityEffect 424 // implemented - Pulls/pushes you toward/away the mob at a set pace -#define SE_Display 425 // *not implemented - Illusion: Flying Dragon(21626) -#define SE_IncreaseExtTargetWindow 426 // *not implmented[AA] - increases the capacity of your extended target window -#define SE_SkillProcAttempt 427 // implemented - chance to proc when using a skill(ie taunt) -#define SE_LimitToSkill 428 // implemented, @Procs, limits what combat skills will effect a skill proc, base: skill value, limit: none, max: none -#define SE_SkillProcSuccess 429 // implemented - chance to proc when tje skill in use successfully fires. -//#define SE_PostEffect 430 // *not implemented - Fear of the Dark(27641) - Alters vision -//#define SE_PostEffectData 431 // *not implemented - Fear of the Dark(27641) - Alters vision -//#define SE_ExpandMaxActiveTrophyBen 432 // not used -#define SE_CriticalDotDecay 433 // implemented - increase critical dot chance, effect decays based on level of spell it effects. (12266) -#define SE_CriticalHealDecay 434 // implemented - increase critical heal chance, effect decays based on level of spell it effects. -#define SE_CriticalRegenDecay 435 // implemented - increase critical heal over time chance, effect decays based on level of spell it effects. -//#define SE_BeneficialCountDownHold 436 // not used ( 23491 | ABTest Buff Hold) -//#define SE_TeleporttoAnchor 437 // *not implemented - Teleport Guild Hall Anchor(33099) -//#define SE_TranslocatetoAnchor 438 // *not implemented - Translocate Primary Anchor (27750) -#define SE_Assassinate 439 // implemented[AA] - Assassinate damage -#define SE_FinishingBlowLvl 440 // implemented[AA] - Sets the level Finishing blow can be triggered on an NPC -#define SE_DistanceRemoval 441 // implemented - Buff is removed from target when target moves X amount of distance away from where initially hit. -#define SE_TriggerOnReqTarget 442 // implemented, @SpellTrigger, triggers a spell when Target Requirement conditions are met (see enum SpellRestriction for IDs), base: spellid, limit: SpellRestriction ID, max: none, Note: Usually cast on a target -#define SE_TriggerOnReqCaster 443 // implemented, @SpellTrigger, triggers a spell when Caster Requirement conditions are met (see enum SpellRestriction for IDs), base: spellid, limit: SpellRestriction ID, max: none, Note: Usually self only -#define SE_ImprovedTaunt 444 // implemented - Locks Aggro On Caster and Decrease other Players Aggro by X% on NPC targets below level Y -//#define SE_AddMercSlot 445 // *not implemented[AA] - [Hero's Barracks] Allows you to conscript additional mercs. -#define SE_AStacker 446 // implementet - bufff stacking blocker (26219 | Qirik's Watch) -#define SE_BStacker 447 // implemented -#define SE_CStacker 448 // implemented -#define SE_DStacker 449 // implemented -#define SE_MitigateDotDamage 450 // implemented, @Runes, mitigate incoming dot damage by percentage until rune fades, base: percent mitigation, limit: max dmg absorbed per hit, max: rune amt, Note: If placed on item or AA, will provide stackable percent mitigation. -#define SE_MeleeThresholdGuard 451 // implemented Partial Melee Rune that only is lowered if melee hits are over X amount of damage -#define SE_SpellThresholdGuard 452 // implemented Partial Spell Rune that only is lowered if spell hits are over X amount of damage -#define SE_TriggerMeleeThreshold 453 // implemented Trigger effect on X amount of melee damage taken in a single hit -#define SE_TriggerSpellThreshold 454 // implemented Trigger effect on X amount of spell damage taken in a single hit -#define SE_AddHatePct 455 // implemented Modify total hate by % -#define SE_AddHateOverTimePct 456 // implemented Modify total hate by % over time. -#define SE_ResourceTap 457 // implemented Coverts a percent of dmg from dmg spells(DD/DoT) to hp/mana/end. -#define SE_FactionModPct 458 // implemented Modifies faction gains and losses by percent. -#define SE_DamageModifier2 459 // implemented - Modifies melee damage by skill type -#define SE_Ff_Override_NotFocusable 460 // implemented, @Fc, Allow spell to be focused event if flagged with 'not_focusable' in spell table, base: 1 -#define SE_ImprovedDamage2 461 // implemented, @Fc, On Caster, spell damage mod pct, base: min pct, limit: max pct -#define SE_FcDamageAmt2 462 // implemented, @Fc, On Caster, spell damage mod flat amt, base: amt -#define SE_Shield_Target 463 // implemented, Base1 % damage shielded on target -#define SE_PC_Pet_Rampage 464 // implemented - Base1 % chance to do rampage for base2 % of damage each melee round -#define SE_PC_Pet_AE_Rampage 465 // implemented - Base1 % chance to do AE rampage for base2 % of damage each melee round -#define SE_PC_Pet_Flurry_Chance 466 // implemented - Base1 % chance to do flurry from double attack hit. -#define SE_DS_Mitigation_Amount 467 // implemented - Modify incoming damage shield damage by a flat amount -#define SE_DS_Mitigation_Percentage 468 // implemented - Modify incoming damage shield damage by percentage -#define SE_Chance_Best_in_Spell_Grp 469 // implemented - Chance to cast highest scribed spell within a spell group. All base2 spells share roll chance, only 1 cast. -#define SE_Trigger_Best_in_Spell_Grp 470 // implemented - Chance to cast highest scribed spell within a spell group. Each spell has own chance. -#define SE_Double_Melee_Round 471 // implemented, @OffBonus, percent chance to repeat primary weapon round with a percent damage modifier, base: pct chance repeat, limit: pct dmg mod, max: none -#define SE_Buy_AA_Rank 472 // implemented, @Special, Used in AA abilities that have Enable/Disable toggle. Spell on Disabled Rank has this effect in it, base: 1, limit: none, max: none, Note: This will not just buy an AA -#define SE_Double_Backstab_Front 473 // implemented - Chance to double backstab from front -#define SE_Pet_Crit_Melee_Damage_Pct_Owner 474 // implemenetd - Critical damage mod applied to pets from owner -#define SE_Trigger_Spell_Non_Item 475 // implemented - Trigger spell on cast only if not from item click. -#define SE_Weapon_Stance 476 // implemented, @Misc, Apply a specific spell buffs automatically depending 2Hander, Shield or Dual Wield is equipped, base: spellid, base: 0=2H 1=Shield 2=DW, max: none -#define SE_Hatelist_To_Top_Index 477 // Implemented - Chance to be set to top of rampage list -#define SE_Hatelist_To_Tail_Index 478 // Implemented - Chance to be set to bottom of rampage list -#define SE_Ff_Value_Min 479 // implemented, @Ff, Minimum base value of a spell that can be focused, base: spells to be focused base1 value -#define SE_Ff_Value_Max 480 // implemented, @Ff, Max base value of a spell that can be focused, base: spells to be focused base1 value -#define SE_Fc_Cast_Spell_On_Land 481 // implemented, @Fc, On Target, cast spell if hit by spell, base: chance pct, limit: spellid -#define SE_Skill_Base_Damage_Mod 482 // implemented, @OffBonus, modify base melee damage by percent, base: pct, limit: skill(-1=ALL), max: none -#define SE_Fc_Spell_Damage_Pct_IncomingPC 483 // implemented, @Fc, On Target, spell damage taken mod pct, base: min pct, limit: max pct -#define SE_Fc_Spell_Damage_Amt_IncomingPC 484 // implemented, @Fc, On Target, damage taken flat amt, base: amt -#define SE_Ff_CasterClass 485 // implemented, @Ff, Caster of spell on target with a focus effect that is checked by incoming spells must be specified class(es). base1: class(es), Note: Set multiple classes same as would for items -#define SE_Ff_Same_Caster 486 // implemented, @Ff, Caster of spell on target with a focus effect that is checked by incoming spells, base1: 0=Must be different caster 1=Must be same caster -//#define SE_Extend_Tradeskill_Cap 487 // -//#define SE_Defender_Melee_Force_Pct_PC 488 // -#define SE_Worn_Endurance_Regen_Cap 489 // implemented, modify worn regen cap, base: amt, limit: none, max: none -#define SE_Ff_ReuseTimeMin 490 // implemented, @Ff, Minimum recast time of a spell that can be focused, base: recast time -#define SE_Ff_ReuseTimeMax 491 // implemented, @Ff, Max recast time of a spell that can be focused, base: recast time -#define SE_Ff_Endurance_Min 492 // implemented, @Ff, Minimum endurance cost of a spell that can be focused, base: endurance cost -#define SE_Ff_Endurance_Max 493 // implemented, @Ff, Max endurance cost of a spell that can be focused, base: endurance cost -#define SE_Pet_Add_Atk 494 // implemented - Bonus on pet owner which gives their pet increased attack stat -#define SE_Ff_DurationMax 495 // implemented, @Ff, Max duration of spell that can be focused, base: tics -#define SE_Critical_Melee_Damage_Mod_Max 496 // implemented - increase or decrease by percent critical damage (not stackable) -//#define SE_Ff_FocusCastProcNoBypass 497 // -#define SE_AddExtraAttackPct_1h_Primary 498 // implemented, @OffBonus, gives your double attacks a percent chance to perform an extra attack with 1-handed primary weapon, base: chance, limit: amt attacks, max: none -#define SE_AddExtraAttackPct_1h_Secondary 499 //implemented, @OffBonus, gives your double attacks a percent chance to perform an extra attack with 1-handed secondary weapon, base: chance, limit: amt attacks, max: none -#define SE_Fc_CastTimeMod2 500 // implemented, @Fc, On Caster, cast time mod pct, base: pct, Note: Can reduce to instant cast -#define SE_Fc_CastTimeAmt 501 // implemented, @Fc, On Caster, cast time mod flat amt, base: milliseconds, Note: Can reduce to instant cast -#define SE_Fearstun 502 // implemented - Stun with a max level limit. Normal stun restrictions don't apply. -#define SE_Melee_Damage_Position_Mod 503 // implemented, @OffBonus, modify melee damage by percent if done from Front or Behind opponent, base: pct, limit: 0=back 1=front, max: none -#define SE_Melee_Damage_Position_Amt 504 // implemented, @OffBonus, modify melee damage by flat amount if done from Front or Behind opponent, base: amt, limit: 0=back 1=front, max: none -#define SE_Damage_Taken_Position_Mod 505 // implemented, @DefBonus, modify melee damage by percent if dmg taken from Front or Behind, base: pct, limit: 0=back 1=front, max: none -#define SE_Damage_Taken_Position_Amt 506 // implemented -@DefBonus, modify melee damage by flat amount if dmg taken from your Front or Behind, base: amt, limit: 0=back 1=front, max: none -#define SE_Fc_Amplify_Mod 507 // implemented, @Fc, On Caster, damage-heal-dot mod pct, base: pct -#define SE_Fc_Amplify_Amt 508 // implemented, @Fc, On Caster, damage-heal-dot mod flat amt, base: amt -#define SE_Health_Transfer 509 // implemented - exchange health for damage or healing on a target. ie Lifeburn/Act of Valor -#define SE_Fc_ResistIncoming 510 // implemented, @Fc, On Target, resist modifier, base: amt -#define SE_Ff_FocusTimerMin 511 // implemented, @Ff, sets a recast time until focus can be used again, base: 1, limit: time ms, Note: ie. limit to 1 trigger every 1.5 seconds -#define SE_Proc_Timer_Modifier 512 // implemented - limits procs per amount of a time based on timer value, base: 1, limit: time ms, Note:, ie limit to 1 proc every 55 seconds) -//#define SE_Mana_Max_Percent 513 // -//#define SE_Endurance_Max_Percent 514 // -#define SE_AC_Avoidance_Max_Percent 515 // implemented - stackable avoidance modifier -#define SE_AC_Mitigation_Max_Percent 516 // implemented - stackable defense modifier -//#define SE_Attack_Offense_Max_Percent 517 // -#define SE_Attack_Accuracy_Max_Percent 518 // implemented - stackable accurary modifer -//#define SE_Luck_Amount 519 // -//#define SE_Luck_Percent 520 // -#define SE_Endurance_Absorb_Pct_Damage 521 // implemented - Reduces % of Damage using Endurance, drains endurance at a ratio (ie. 0.05 Endurance per Hit Point) -#define SE_Instant_Mana_Pct 522 // implemented - Increase/Decrease mana by percent of max mana -#define SE_Instant_Endurance_Pct 523 // implemented - Increase/Decrease mana by percent of max endurance -#define SE_Duration_HP_Pct 524 // implemented - Decrease Current Hit Points by % of Total Hit Points per Tick, up to a MAX per tick -#define SE_Duration_Mana_Pct 525 // implemented - Decrease Current Mana by % of Total Mana per Tick, up to a MAX per tick -#define SE_Duration_Endurance_Pct 526 // implemented - Decrease Current Endurance by % of Total Hit Points per Tick, up to a MAX per tick - - -// LAST +namespace SpellEffect { + constexpr int CurrentHP = 0; // implemented - Heals and nukes, repeates every tic if in a buff + constexpr int ArmorClass = 1; // implemented + constexpr int ATK = 2; // implemented + constexpr int MovementSpeed = 3; // implemented - SoW, SoC, etc + constexpr int STR = 4; // implemented + constexpr int DEX = 5; // implemented + constexpr int AGI = 6; // implemented + constexpr int STA = 7; // implemented + constexpr int INT = 8; // implemented + constexpr int WIS = 9; // implemented + constexpr int CHA = 10; // implemented - used as a spacer + constexpr int AttackSpeed = 11; // implemented + constexpr int Invisibility = 12; // implemented - TO DO: Implemented Invisiblity Levels + constexpr int SeeInvis = 13; // implemented - TO DO: Implemented See Invisiblity Levels + constexpr int WaterBreathing = 14; // implemented + constexpr int CurrentMana = 15; // implemented + constexpr int NPCFrenzy = 16; // not used + constexpr int NPCAwareness = 17; // not used + constexpr int Lull = 18; // implemented - Reaction Radius + constexpr int AddFaction = 19; // implemented - Alliance line + constexpr int Blind = 20; // implemented + constexpr int Stun = 21; // implemented + constexpr int Charm = 22; // implemented + constexpr int Fear = 23; // implemented + constexpr int Stamina = 24; // implemented - Invigor and such + constexpr int BindAffinity = 25; // implemented - TO DO: Implement 2nd and 3rd Recall (value 2,3 ect). Sets additional bind points. + constexpr int Gate = 26; // implemented - Gate to bind point + constexpr int CancelMagic = 27; // implemented + constexpr int InvisVsUndead = 28; // implemented + constexpr int InvisVsAnimals = 29; // implemented + constexpr int ChangeFrenzyRad = 30; // implemented - Pacify + constexpr int Mez = 31; // implemented + constexpr int SummonItem = 32; // implemented + constexpr int SummonPet = 33; // implemented + constexpr int Confuse = 34; // not used (Nimbus of Temporal Rifting) ? + constexpr int DiseaseCounter = 35; // implemented + constexpr int PoisonCounter = 36; // implemented + constexpr int DetectHostile = 37; // not used + constexpr int DetectMagic = 38; // not used + constexpr int TwinCastBlocker = 39; // implemented - If present in spell, then the spell can not be twincast. + constexpr int DivineAura = 40; // implemented + constexpr int Destroy = 41; // implemented - Disintegrate, Banishment of Shadows + constexpr int ShadowStep = 42; // implemented + constexpr int Berserk = 43; // implemented (*not used in any known live spell) Makes client 'Berserk' giving crip blow chance. + constexpr int Lycanthropy = 44; // implemented + constexpr int Vampirism = 45; // implemented (*not used in any known live spell) Stackable lifetap from melee. + constexpr int ResistFire = 46; // implemented + constexpr int ResistCold = 47; // implemented + constexpr int ResistPoison = 48; // implemented + constexpr int ResistDisease = 49; // implemented + constexpr int ResistMagic = 50; // implemented + constexpr int DetectTraps = 51; // not used + constexpr int SenseDead = 52; // implemented + constexpr int SenseSummoned = 53; // implemented + constexpr int SenseAnimals = 54; // implemented + constexpr int Rune = 55; // implemented + constexpr int TrueNorth = 56; // implemented + constexpr int Levitate = 57; // implemented + constexpr int Illusion = 58; // implemented + constexpr int DamageShield = 59; // implemented + constexpr int TransferItem = 60; // not used + constexpr int Identify = 61; // implemented + constexpr int ItemID = 62; // not used + constexpr int WipeHateList = 63; // implemented, @Memblur, chance to wipe hate list of target, base: pct chance, limit: none, max: ? (not implemented), Note: caster level and CHA add to pct chance + constexpr int SpinTarget = 64; // implemented - TO DO: Not sure stun portion is working correctly + constexpr int InfraVision = 65; // implemented + constexpr int UltraVision = 66; // implemented + constexpr int EyeOfZomm = 67; // implemented + constexpr int ReclaimPet = 68; // implemented + constexpr int TotalHP = 69; // implemented + constexpr int CorpseBomb = 70; // not used + constexpr int NecPet = 71; // implemented + constexpr int PreserveCorpse = 72; // not used + constexpr int BindSight = 73; // implemented, @Vision, see through the eyes of your target, click off buff to end effect, base: 1, limit: none, max: none + constexpr int FeignDeath = 74; // implemented + constexpr int VoiceGraft = 75; // implemented + constexpr int Sentinel = 76; // *not implemented?(just seems to send a message) + constexpr int LocateCorpse = 77; // implemented + constexpr int AbsorbMagicAtt = 78; // implemented - Rune for spells + constexpr int CurrentHPOnce = 79; // implemented - Heals and nukes, non-repeating if in a buff + constexpr int EnchantLight = 80; // not used + constexpr int Revive = 81; // implemented - Resurrect + constexpr int SummonPC = 82; // implemented + constexpr int Teleport = 83; // implemented + constexpr int TossUp = 84; // implemented - Gravity Flux + constexpr int WeaponProc = 85; // implemented - i.e. Call of Fire + constexpr int Harmony = 86; // implemented + constexpr int MagnifyVision = 87; // implemented - Telescope + constexpr int Succor = 88; // implemented - Evacuate/Succor lines + constexpr int ModelSize = 89; // implemented - Shrink, Growth + constexpr int Cloak = 90; // *not implemented - Used in only 2 spells + constexpr int SummonCorpse = 91; // implemented + constexpr int InstantHate = 92; // implemented - add hate + constexpr int StopRain = 93; // implemented - Wake of Karana + constexpr int NegateIfCombat = 94; // implemented + constexpr int Sacrifice = 95; // implemented + constexpr int Silence = 96; // implemented + constexpr int ManaPool = 97; // implemented + constexpr int AttackSpeed2 = 98; // implemented - Melody of Ervaj + constexpr int Root = 99; // implemented + constexpr int HealOverTime = 100; // implemented + constexpr int CompleteHeal = 101; // implemented + constexpr int Fearless = 102; // implemented - Valiant Companion + constexpr int CallPet = 103; // implemented - Summon Companion + constexpr int Translocate = 104; // implemented + constexpr int AntiGate = 105; // implemented - Translocational Anchor + constexpr int SummonBSTPet = 106; // implemented + constexpr int AlterNPCLevel = 107; // implemented - not used on live + constexpr int Familiar = 108; // implemented + constexpr int SummonItemIntoBag = 109; // implemented - summons stuff into container + constexpr int IncreaseArchery = 110; // implemented + constexpr int ResistAll = 111; // implemented - Note: Physical Resists are not modified by this effect. + constexpr int CastingLevel = 112; // implemented + constexpr int SummonHorse = 113; // implemented + constexpr int ChangeAggro = 114; // implemented - Hate modifing buffs(ie horrifying visage) + constexpr int Hunger = 115; // implemented - Song of Sustenance + constexpr int CurseCounter = 116; // implemented + constexpr int MagicWeapon = 117; // implemented - makes weapon magical + constexpr int Amplification = 118; // implemented, @Song, stackable singing mod, base: mod%, limit: none, max: none, Note: Can focus itself. + constexpr int AttackSpeed3 = 119; // implemented + constexpr int HealRate = 120; // implemented - reduces healing by a % + constexpr int ReverseDS = 121; // implemented + constexpr int ReduceSkill = 122; // implemented - base: skill id, limit: none, max: none, formula: % skill is reduced (positive) + constexpr int Screech = 123; // implemented Spell Blocker(If have buff with value +1 will block any effect with -1) + constexpr int ImprovedDamage = 124; // implemented + constexpr int ImprovedHeal = 125; // implemented + constexpr int SpellResistReduction = 126; // implemented + constexpr int IncreaseSpellHaste = 127; // implemented, @Fc, On Caster, cast time mod pct, base: pct + constexpr int IncreaseSpellDuration = 128; // implemented, @Fc, On Caster, spell duration mod pct, base: pct + constexpr int IncreaseRange = 129; // implemented, @Fc, On Caster, spell range mod pct, base: pct + constexpr int SpellHateMod = 130; // implemented, @Fc, On Caster, spell hate mod pct, base: min pct, limit: max pct + constexpr int ReduceReagentCost = 131; // implemented, @Fc, On Caster, do not consume reagent pct chance, base: min pct, limit: max pct + constexpr int ReduceManaCost = 132; // implemented, @Fc, On Caster, reduce mana cost by pct, base: min pct, limt: max pct + constexpr int FcStunTimeMod = 133; // implemented, @Fc, On Caster, spell range mod pct, base: pct + constexpr int LimitMaxLevel = 134; // implemented, @Ff, Max level of spell that can be focused, if base2 then decrease effectiviness by base2 % per level over max, base: lv, base2: effectiveness pct + constexpr int LimitResist = 135; // implemented, @Ff, Resist Type(s) that a spell focus can require or exclude, base1: resist type, Include: Positive Exclude: Negative + constexpr int LimitTarget = 136; // implemented, @Ff, Target Type(s) that a spell focus can require or exclude, base1: target type, Include: Positive Exclude: Negative + constexpr int LimitEffect = 137; // implemented, @Ff, Spell effect(s) that a spell focus can require or exclude, base1: SPA id, Include: Positive Exclude: Negative + constexpr int LimitSpellType = 138; // implemented, @Ff, Only allow focus spells that are Beneficial or Detrimental, base1: 0=det 1=bene + constexpr int LimitSpell = 139; // implemented, @Ff, Specific spell id(s) that a spell focus can require or exclude, base1: SPA id, Include: Positive Exclude: Negative + constexpr int LimitMinDur = 140; // implemented, @Ff, Mininum duration of spell that can be focused, base1: tics + constexpr int LimitInstant = 141; // implemented, @Ff, Include or exclude if an isntant cast spell can be focused, base1: 0=Exclude if Instant 1=Allow only if Instant + constexpr int LimitMinLevel = 142; // implemented, @Ff, Mininum level of spell that can be focused, base1: lv + constexpr int LimitCastTimeMin = 143; // implemented, @Ff, Mininum cast time of spell that can be focused, base1: milliseconds + constexpr int LimitCastTimeMax = 144; // implemented, @Ff, Max cast time of spell that can be focused, base1: milliseconds + constexpr int Teleport2 = 145; // implemented - Banishment of the Pantheon + constexpr int ElectricityResist = 146; // *not implemented TODO: Now used on live, xyz for teleport spells? also in temp pets? + constexpr int PercentalHeal = 147; // implemented + constexpr int StackingCommand_Block = 148; // implemented? + constexpr int StackingCommand_Overwrite = 149; // implemented? + constexpr int DeathSave = 150; // implemented + constexpr int SuspendPet = 151; // implemented, @Pet, allow caster to have an extra suspended pet, base: 0=no buffs/items 1=buffs+items, limit: none, max: none + constexpr int TemporaryPets = 152; // implemented + constexpr int BalanceHP = 153; // implemented + constexpr int DispelDetrimental = 154; // implemented, @Dispel, removes only detrimental effects on a target, base: pct chance (950=95%), limit: none, max: none + constexpr int SpellCritDmgIncrease = 155; // implemented - no known live spells use this currently + constexpr int IllusionCopy = 156; // implemented - Deception + constexpr int SpellDamageShield = 157; // implemented, @DS, causes non-melee damage on caster of a spell, base: Amt DS (negative), limit: none, max: unknown (same as base but +) + constexpr int Reflect = 158; // implemented, @SpellMisc, reflect casted detrimental spell back at caster, base: chance pct, limit: resist modifier (positive value reduces resists), max: pct of base dmg mod (50=50pct of base) + constexpr int AllStats = 159; // implemented + constexpr int MakeDrunk = 160; // *not implemented - Effect works entirely client side (Should check against tolerance) + constexpr int MitigateSpellDamage = 161; // implemented, @Runes, mitigate incoming spell damage by percentage until rune fades, base: percent mitigation, limit: max dmg absorbed per hit, max: rune amt, Note: If placed on item or AA, will provide stackable percent mitigation. + constexpr int MitigateMeleeDamage = 162; // implemented - rune with max value + constexpr int NegateAttacks = 163; // implemented + constexpr int AppraiseLDonChest = 164; // implemented + constexpr int DisarmLDoNTrap = 165; // implemented + constexpr int UnlockLDoNChest = 166; // implemented + constexpr int PetPowerIncrease = 167; // implemented, @Fc, On Caster, pet power mod, base: value + constexpr int MeleeMitigation = 168; // implemented + constexpr int CriticalHitChance = 169; // implemented + constexpr int SpellCritChance = 170; // implemented + constexpr int CrippBlowChance = 171; // implemented + constexpr int AvoidMeleeChance = 172; // implemented + constexpr int RiposteChance = 173; // implemented + constexpr int DodgeChance = 174; // implemented + constexpr int ParryChance = 175; // implemented + constexpr int DualWieldChance = 176; // implemented + constexpr int DoubleAttackChance = 177; // implemented + constexpr int MeleeLifetap = 178; // implemented + constexpr int AllInstrumentMod = 179; // implemented, @Song, set mod for ALL instrument/singing skills that will be used if higher then item mods, base: mod%, limit: none, max: none + constexpr int ResistSpellChance = 180; // implemented + constexpr int ResistFearChance = 181; // implemented + constexpr int HundredHands = 182; // implemented + constexpr int MeleeSkillCheck = 183; // implemented + constexpr int HitChance = 184; // implemented + constexpr int DamageModifier = 185; // implemented + constexpr int MinDamageModifier = 186; // implemented + constexpr int BalanceMana = 187; // implemented - Balances party mana + constexpr int IncreaseBlockChance = 188; // implemented + constexpr int CurrentEndurance = 189; // implemented + constexpr int EndurancePool = 190; // implemented + constexpr int Amnesia = 191; // implemented - Silence vs Melee Effect + constexpr int Hate = 192; // implemented - Instant and hate over time. + constexpr int SkillAttack = 193; // implemented, + constexpr int FadingMemories = 194; // implemented, @Aggro, Remove from hate lists and make invisible. Can set max level of NPCs that can be affected. base: success chance, limit: max level (ROF2), max: max level (modern client), Note: Support for max level requires Rule (Spells, UseFadingMemoriesMaxLevel) to be true. If used from limit field, then it set as the level, ie. max level of 75 would use limit value of 75. If set from max field, max level 75 would use max value of 1075, if you want to set it so it checks a level range above the spell target then for it to only work on mobs 5 levels or below you set max value to 5. + constexpr int StunResist = 195; // implemented + constexpr int StrikeThrough = 196; // implemented + constexpr int SkillDamageTaken = 197; // implemented + constexpr int CurrentEnduranceOnce = 198; // implemented + constexpr int Taunt = 199; // implemented - % chance to taunt the target + constexpr int ProcChance = 200; // implemented + constexpr int RangedProc = 201; // implemented + constexpr int IllusionOther = 202; // implemented - Project Illusion + constexpr int MassGroupBuff = 203; // implemented + constexpr int GroupFearImmunity = 204; // implemented - (Does not use bonus) + constexpr int Rampage = 205; // implemented, @Combat Instant, Perform a primary slot combat rounds on all creatures within a 40 foot radius, base: number of attack rounds, limit: max entities hit per round, max: none, Note: AE range is 40 by default. Custom: Set field 'aoe_range' to override default. Adding additional attacks and hit count limit. + constexpr int AETaunt = 206; // implemented + constexpr int FleshToBone = 207; // implemented + constexpr int PurgePoison = 208; // not used + constexpr int DispelBeneficial = 209; // implemented, @Dispel, removes only beneficial effects on a target, base: pct chance (950=95%), limit: none, max: none + constexpr int PetShield = 210; // implmented, @ShieldAbility, allows pet to 'shield' owner for 50 pct of damage taken for a duration, base: Time multiplier 1=12 seconds, 2=24 ect, limit: mitigation on pet owner override (not on live), max: mitigation on pet overide (not on live) + constexpr int AEMelee = 211; // implemented TO DO: Implement to allow NPC use (client only atm). + constexpr int FrenziedDevastation = 212; // implemented - increase spell criticals + all DD spells cast 2x mana. + constexpr int PetMaxHP = 213; // implemented[AA] - increases the maximum hit points of your pet + constexpr int MaxHPChange = 214; // implemented + constexpr int PetAvoidance = 215; // implemented[AA] - increases pet ability to avoid melee damage + constexpr int Accuracy = 216; // implemented + constexpr int HeadShot = 217; // implemented - ability to head shot (base2 = damage) + constexpr int PetCriticalHit = 218; // implemented[AA] - gives pets a baseline critical hit chance + constexpr int SlayUndead = 219; // implemented - Allow extra damage against undead (base1 = rate, base2 = damage mod). + constexpr int SkillDamageAmount = 220; // implemented + constexpr int Packrat = 221; // implemented as bonus + constexpr int BlockBehind = 222; // implemented - Chance to block from behind (with our without Shield) + constexpr int DoubleRiposte = 223; // implemented - Chance to double riposte [not used on live] + constexpr int GiveDoubleRiposte = 224; // implemented[AA] + constexpr int GiveDoubleAttack = 225; // implemented[AA] - Allow any class to double attack with set chance. + constexpr int TwoHandBash = 226; // *not implemented as bonus + constexpr int ReduceSkillTimer = 227; // implemented + constexpr int ReduceFallDamage = 228; // implented - reduce the damage that you take from falling + constexpr int PersistantCasting = 229; // implemented + constexpr int ExtendedShielding = 230; // implemented, @ShieldAbility, extends the range of your /shield ability by an amount of distance, base: distance units, limit: none, max: none + constexpr int StunBashChance = 231; // implemented - increase chance to stun from bash. + constexpr int DivineSave = 232; // implemented (base1 == % chance on death to insta-res) (base2 == spell cast on save) + constexpr int Metabolism = 233; // implemented - Modifies food/drink consumption rates. + constexpr int ReduceApplyPoisonTime = 234; // not implemented as bonus - reduces the time to apply poison + constexpr int ChannelChanceSpells = 235; // implemented[AA] - chance to channel from SPELLS *No longer used on live. + constexpr int FreePet = 236; // not used + constexpr int GivePetGroupTarget = 237; // implemented[AA] - (Pet Affinity) + constexpr int IllusionPersistence = 238; // implemented - lends persistence to your illusionary disguises, causing them to last until you die or the illusion is forcibly removed. + constexpr int FeignedCastOnChance = 239; // implemented - ability gives you an increasing chance for your feigned deaths to not be revealed by spells cast upon you. + constexpr int StringUnbreakable = 240; // not used [Likely related to above - you become immune to feign breaking on a resisted spell and have a good chance of feigning through a spell that successfully lands upon you.] + constexpr int ImprovedReclaimEnergy = 241; // implemented - increase the amount of mana returned to you when reclaiming your pet. + constexpr int IncreaseChanceMemwipe = 242; // implemented - @Memblur, increases the chance to wipe hate with memory blurr, base: chance pct, limit: none, max: none, Note: Mods final blur chance after other bonuses added. + constexpr int CharmBreakChance = 243; // implemented - Total Domination + constexpr int RootBreakChance = 244; // implemented[AA] reduce the chance that your root will break. + constexpr int TrapCircumvention = 245; // implemented, @Traps, decreases the chance that you will set off a trap when opening a chest or other similar container by percentage, base: chance modifer, limit: none, max: none + constexpr int SetBreathLevel = 246; // *not implemented as bonus + constexpr int RaiseSkillCap = 247; // implemented[AA] - adds skill over the skill cap. + constexpr int SecondaryForte = 248; // not implemented as bonus(gives you a 2nd specialize skill that can go past 50 to 100) + constexpr int SecondaryDmgInc = 249; // implemented[AA] Allows off hand weapon to recieve a damage bonus (Sinister Strikes) + constexpr int SpellProcChance = 250; // implemented - Increase chance to proc from melee proc spells (ie Spirit of Panther) + constexpr int ConsumeProjectile = 251; // implemented[AA] - chance to not consume an arrow (ConsumeProjectile = 100) + constexpr int FrontalBackstabChance = 252; // implemented[AA] - chance to perform a full damage backstab from front. + constexpr int FrontalBackstabMinDmg = 253; // implemented[AA] - allow a frontal backstab for mininum damage. + constexpr int Blank = 254; // implemented + constexpr int ShieldDuration = 255; // implemented, , @ShieldAbility, extends the duration of your /shield ability, base: seconds, limit: none, max: none + constexpr int ShroudofStealth = 256; // implemented + constexpr int PetDiscipline = 257; // not implemented as bonus - /pet hold - official name is GivePetHold + constexpr int TripleBackstab = 258; // implemented[AA] - chance to perform a triple backstab + constexpr int CombatStability = 259; // implemented[AA] - damage mitigation + constexpr int AddSingingMod = 260; // implemented, @Song, set mod for specific instrument/singing skills that will be used if higher then item mods, base: mod%, limit: ItemType ID, max: none + constexpr int SongModCap = 261; // implemented, @Song, raise max song modifier cap, base: amt, limit: none, max: none, Note: No longer used on live + constexpr int RaiseStatCap = 262; // implemented + constexpr int TradeSkillMastery = 263; // implemented - lets you raise more than one tradeskill above master. + constexpr int HastenedAASkill = 264; // implemented + constexpr int MasteryofPast = 265; // implemented[AA] - Spells less than effect values level can not be fizzled + constexpr int ExtraAttackChance = 266; // implemented, @OffBonus, gives your double attacks a percent chance to perform an extra attack with 2-handed primary weapon, base: chance, limit: amt attacks, max: none + constexpr int AddPetCommand = 267; // implemented - sets command base2 to base1 + constexpr int ReduceTradeskillFail = 268; // implemented - reduces chance to fail with given tradeskill by a percent chance + constexpr int MaxBindWound = 269; // implemented[AA] - Increase max HP you can bind wound. + constexpr int BardSongRange = 270; // implemented, @Song, increase range of beneficial bard songs, base: mod%, limit: none, max: none , Note: example Sionachie's Crescendo + constexpr int BaseMovementSpeed = 271; // implemented[AA] - mods basemove speed, doesn't stack with other move mods + constexpr int CastingLevel2 = 272; // implemented + constexpr int CriticalDoTChance = 273; // implemented + constexpr int CriticalHealChance = 274; // implemented + constexpr int CriticalMend = 275; // implemented[AA] - chance to critical monk mend + constexpr int Ambidexterity = 276; // implemented[AA] - increase chance to duel weild by adding bonus 'skill' + constexpr int UnfailingDivinity = 277; // implemented[AA] - ability grants your Death Pact-type spells a second chance to successfully heal their target, also can cause said spells to do a portion of their healing value even on a complete failure. + constexpr int FinishingBlow = 278; // implemented[AA] - chance to do massive damage under 10% HP (base1 = chance, base2 = damage) + constexpr int Flurry = 279; // implemented + constexpr int PetFlurry = 280; // implemented[AA] + constexpr int FeignedMinion = 281; // implemented, ability allows you to instruct your pet to feign death via the '/pet feign' command, base: succeed chance, limit: none, max: none, Note: Only implemented as an AA. + constexpr int ImprovedBindWound = 282; // implemented[AA] - increase bind wound amount by percent. + constexpr int DoubleSpecialAttack = 283; // implemented[AA] - Chance to perform second special attack as monk + constexpr int LoHSetHeal = 284; // not used + constexpr int NimbleEvasion = 285; // *not implemented - base1 = 100 for max + constexpr int FcDamageAmt = 286; // implemented, @Fc, On Caster, spell damage mod flat amt, base: amt + constexpr int SpellDurationIncByTic = 287; // implemented, @Fc, SPA: 287, SpellEffect::SpellDurationIncByTic, = On Caster, spell buff duration mod, base: tics + constexpr int SkillAttackProc = 288; // implemented, @Procs, chance to cast a spell when using a skill, base: chance, limit: skill, max: spellid, note: if used in AA the spell id is set in aa_ranks spell field, chance is calculated as 100% = value 1000. + constexpr int CastOnFadeEffect = 289; // implemented - Triggers only if fades after natural duration. + constexpr int IncreaseRunSpeedCap = 290; // implemented[AA] - increases run speed over the hard cap + constexpr int Purify = 291; // implemented, @Dispel, remove up specified amount of detiremental spells, base: amt removed, limit: none, max: none, Note: excluding charm, fear, resurrection, and revival sickness + constexpr int StrikeThrough2 = 292; // implemented[AA] - increasing chance of bypassing an opponent's special defenses, such as dodge, block, parry, and riposte. + constexpr int FrontalStunResist = 293; // implemented[AA] - Reduce chance to be stunned from front. -- live descriptions sounds like this isn't limited to frontal anymore + constexpr int CriticalSpellChance = 294; // implemented - increase chance to critical hit and critical damage modifier. + constexpr int ReduceTimerSpecial = 295; // not used + constexpr int FcSpellVulnerability = 296; // implemented, @Fc, On Target, spell damage taken mod pct, base: min pct, limit: max pct + constexpr int FcDamageAmtIncoming = 297; // implemetned, @Fc, On Target, damage taken flat amt, base: amt + constexpr int ChangeHeight = 298; // implemented + constexpr int WakeTheDead = 299; // implemented, @Pets, summon one temporary pet from nearby corpses that last a set duration, base: none, limit: none, max: duration (seconds). Note: max range of corpse is 250. + constexpr int Doppelganger = 300; // implemented + constexpr int ArcheryDamageModifier = 301; // implemented[AA] - increase archery damage by percent + constexpr int FcDamagePctCrit = 302; // implemented, @Fc, On Caster, spell damage mod pct, base: min pct, limit: max pct, Note: applied after critical hits has been calculated. + constexpr int FcDamageAmtCrit = 303; // implemented, @Fc, On Caster, spell damage mod flat amt, base: amt + constexpr int OffhandRiposteFail = 304; // implemented as bonus - enemy cannot riposte offhand attacks + constexpr int MitigateDamageShield = 305; // implemented - off hand attacks only (Shielding Resistance) + constexpr int ArmyOfTheDead = 306; // implemented, @Pets, summon multiple temporary pets from nearby corpses that last a set duration, base: amount of corpses that a pet can summon from, limit: none, max: duration (seconds). Note: max range of corpse is 250. + constexpr int Appraisal = 307; // *not implemented Rogue AA - This ability allows you to estimate the selling price of an item you are holding on your cursor. + constexpr int ZoneSuspendMinion = 308; // implemented, @Pet, allow suspended pets to be resummoned upon zoning, base: 1, limit: none, max: none, Calc: Bool + constexpr int GateCastersBindpoint = 309; // implemented - Gate to casters bind point + constexpr int ReduceReuseTimer = 310; // implemented, @Fc, On Caster, spell and disc reuse time mod by amount, base: milliseconds + constexpr int LimitCombatSkills = 311; // implemented, @Ff, Include or exclude combat skills or procs from being focused, base1: 0=Exclude if proc 1=Allow only if proc. + constexpr int Sanctuary = 312; // implemented - Places caster at bottom hate list, effect fades if cast cast spell on targets other than self. + constexpr int ForageAdditionalItems = 313; // implemented[AA] - chance to forage additional items + constexpr int Invisibility2 = 314; // implemented - fixed duration invisible + constexpr int InvisVsUndead2 = 315; // implemented - fixed duration ITU + constexpr int ImprovedInvisAnimals = 316; // implemented + constexpr int ItemHPRegenCapIncrease = 317; // implemented[AA] - increases amount of health regen gained via items + constexpr int ItemManaRegenCapIncrease = 318; // implemented - increases amount of mana regen you can gain via items + constexpr int CriticalHealOverTime = 319; // implemented + constexpr int ShieldBlock = 320; // implemented - Block attacks with shield + constexpr int ReduceHate = 321; // implemented + constexpr int GateToHomeCity = 322; // implemented + constexpr int DefensiveProc = 323; // implemented + constexpr int HPToMana = 324; // implemented + constexpr int NoBreakAESneak = 325; // implemented[AA] - [AA Nerves of Steel] increasing chance to remain hidden when they are an indirect target of an AoE spell. + constexpr int SpellSlotIncrease = 326; // *not implemented as bonus - increases your spell slot availability + constexpr int MysticalAttune = 327; // implemented - increases amount of buffs that a player can have + constexpr int DelayDeath = 328; // implemented - increases how far you can fall below 0 hp before you die + constexpr int ManaAbsorbPercentDamage = 329; // implemented + constexpr int CriticalDamageMob = 330; // implemented + constexpr int Salvage = 331; // implemented - chance to recover items that would be destroyed in failed tradeskill combine + constexpr int SummonToCorpse = 332; // *not implemented AA - Call of the Wild (Druid/Shaman Res spell with no exp) TOOD: implement this. + constexpr int CastOnRuneFadeEffect = 333; // implemented + constexpr int BardAEDot = 334; // implemented + constexpr int BlockNextSpellFocus = 335; // implemented, @Fc, On Caster, chance to block next spell, base: chance + constexpr int IllusionaryTarget = 336; // not used + constexpr int PercentXPIncrease = 337; // implemented + constexpr int SummonAndResAllCorpses = 338; // implemented + constexpr int TriggerOnCast = 339; // implemented, @Fc, On Caster, cast on spell use, base: chance pct limit: spellid + constexpr int SpellTrigger = 340; // implemented - chance to trigger spell [Share rolls with 469] All base2 spells share roll chance, only 1 cast. + constexpr int ItemAttackCapIncrease = 341; // implemented[AA] - increases the maximum amount of attack you can gain from items. + constexpr int ImmuneFleeing = 342; // implemented - stop mob from fleeing + constexpr int InterruptCasting = 343; // implemented - % chance to interrupt spells being cast every tic. Cacophony (8272) + constexpr int ChannelChanceItems = 344; // implemented[AA] - chance to not have ITEM effects interrupted when you take damage. + constexpr int AssassinateLevel = 345; // implemented as bonus - AA Assisination max level to kill + constexpr int HeadShotLevel = 346; // implemented[AA] - HeadShot max level to kill + constexpr int DoubleRangedAttack = 347; // implemented - chance at an additional archery attack (consumes arrow) + constexpr int LimitManaMin = 348; // implemented, @Ff, Mininum mana of spell that can be focused, base1: mana amt + constexpr int ShieldEquipDmgMod = 349; // implemented[AA] Increase melee base damage (indirectly increasing hate) when wearing a shield. + constexpr int ManaBurn = 350; // implemented - Drains mana for damage/heal at a defined ratio up to a defined maximum amount of mana. + constexpr int PersistentEffect = 351; // *not implemented. creates a trap/totem that casts a spell (spell id + base1?) when anything comes near it. can probably make a beacon for this + constexpr int IncreaseTrapCount = 352; // *not implemented - looks to be some type of invulnerability? Test ITC (8755) + constexpr int AdditionalAura = 353; // *not implemented - allows use of more than 1 aura, aa effect + constexpr int DeactivateAllTraps = 354; // *not implemented - looks to be some type of invulnerability? Test DAT (8757) + constexpr int LearnTrap = 355; // *not implemented - looks to be some type of invulnerability? Test LT (8758) + constexpr int ChangeTriggerType = 356; // not used + constexpr int FcMute = 357; // implemented, @Fc, On Caster, prevents spell casting, base: chance pct + constexpr int CurrentManaOnce = 358; // implemented + constexpr int PassiveSenseTrap = 359; // *not implemented - Invulnerability (Brell's Blessing) + constexpr int ProcOnKillShot = 360; // implemented - a buff that has a base1 % to cast spell base2 when you kill a "challenging foe" base3 min level + constexpr int SpellOnDeath = 361; // implemented - casts spell on death of buffed + constexpr int PotionBeltSlots = 362; // *not implemented[AA] 'Quick Draw' expands the potion belt by one additional available item slot per rank. + constexpr int BandolierSlots = 363; // *not implemented[AA] 'Battle Ready' expands the bandolier by one additional save slot per rank. + constexpr int TripleAttackChance = 364; // implemented + constexpr int ProcOnSpellKillShot = 365; // implemented - chance to trigger a spell on kill when the kill is caused by a specific spell with this effect in it (10470 Venin) + constexpr int GroupShielding = 366; // *not implemented[AA] This gives you /shieldgroup + constexpr int SetBodyType = 367; // implemented - set body type of base1 so it can be affected by spells that are limited to that type (Plant, Animal, Undead, etc) + constexpr int FactionMod = 368; // *not implemented - increases faction with base1 (faction id, live won't match up w/ ours) by base2 + constexpr int CorruptionCounter = 369; // implemented + constexpr int ResistCorruption = 370; // implemented + constexpr int AttackSpeed4 = 371; // implemented - stackable slow effect 'Inhibit Melee' + constexpr int ForageSkill = 372; // implemented[AA] Will increase the skill cap for those that have the Forage skill and grant the skill and raise the cap to those that do not. + constexpr int CastOnFadeEffectAlways = 373; // implemented - Triggers if fades after natural duration OR from rune/numhits fades. + constexpr int ApplyEffect = 374; // implemented + constexpr int DotCritDmgIncrease = 375; // implemented - Increase damage of DoT critical amount + constexpr int Fling = 376; // *not implemented - used in 2 test spells (12945 | Movement Test Spell 1) + constexpr int CastOnFadeEffectNPC = 377; // implemented - Triggers only if fades after natural duration (On live these are usually players spells that effect an NPC). + constexpr int SpellEffectResistChance = 378; // implemented - Increase chance to resist specific spell effect (base1=value, base2=spell effect id) + constexpr int ShadowStepDirectional = 379; // implemented - handled by client + constexpr int Knockdown = 380; // implemented - small knock back(handled by client) + constexpr int KnockTowardCaster = 381; // *not implemented (Call of Hither) knocks you back to caster (value) distance units infront + constexpr int NegateSpellEffect = 382; // implemented, @Debuff, negates specific spell effect benefits for the duration of the debuff and prevent non-duration spell effect from working, base: see NegateSpellEffecttype Enum, limit: SPA id, max: none + constexpr int SympatheticProc = 383; // implemented, @Fc, On Caster, cast on spell use, base: variable proc chance on cast time, limit: spellid + constexpr int Leap = 384; // implemented - Leap effect, ie stomping leap + constexpr int LimitSpellGroup = 385; // implemented, @Ff, Spell group(s) that a spell focus can require or exclude, base1: spellgroup id, Include: Positive Exclude: Negative + constexpr int CastOnCurer = 386; // implemented - Casts a spell on the person curing + constexpr int CastOnCure = 387; // implemented - Casts a spell on the cured person + constexpr int SummonCorpseZone = 388; // implemented - summons a corpse from any zone(nec AA) + constexpr int FcTimerRefresh = 389; // implemented, @Fc, On Caster, reset all recast timers, base: 1, Note: Applied from casted spells only + constexpr int FcTimerLockout = 390; // implemented, @Fc, On Caster, set a spell to be on recast timer, base: recast duration milliseconds, Note: Applied from casted spells only + constexpr int LimitManaMax = 391; // implemented, @Ff, Mininum mana of spell that can be focused, base1: mana amt + constexpr int FcHealAmt = 392; // implemented, @Fc, On Caster, spell healing mod flat amt, base: amt + constexpr int FcHealPctIncoming = 393; // implemented, @Fc, On Target, heal received mod pct, base: pct, limit: random max pct + constexpr int FcHealAmtIncoming = 394; // implemented, @Fc, On Target, heal received mod flat amt, base: amt + constexpr int FcHealPctCritIncoming = 395; // implemented, @Fc, On Target, heal received mod pct, base: pct, limit: random max pct + constexpr int FcHealAmtCrit = 396; // implemented, @Fc, On Caster, spell healing mod flat amt, base: amt + constexpr int PetMeleeMitigation = 397; // implemented[AA] - additional mitigation to your pets. Adds AC + constexpr int SwarmPetDuration = 398; // implemented - Affects the duration of swarm pets + constexpr int FcTwincast = 399; // implemented - cast 2 spells for every 1 + constexpr int HealGroupFromMana = 400; // implemented - Drains mana and heals for each point of mana drained + constexpr int ManaDrainWithDmg = 401; // implemented - Deals damage based on the amount of mana drained + constexpr int EndDrainWithDmg = 402; // implemented - Deals damage for the amount of endurance drained + constexpr int LimitSpellClass = 403; // implemented, @Ff, 'Spell Category' using table field 'spell_class' that a spell focus can require or exclude, base1: category type, Include: Positive Exclude: Negative + constexpr int LimitSpellSubclass = 404; // implemented, @Ff, 'Spell Category Subclass' using table field 'spell_subclass' that a spell focus can require or exclude, base1: category type, Include: Positive Exclude: Negative + constexpr int TwoHandBluntBlock = 405; // implemented - chance to block attacks when using two hand blunt weapons (similiar to shield block) + constexpr int CastonNumHitFade = 406; // implemented - casts a spell when a buff fades due to its numhits being depleted + constexpr int CastonFocusEffect = 407; // implemented - casts a spell if focus limits are met (ie triggers when a focus effects is applied) + constexpr int LimitHPPercent = 408; // implemented - limited to a certain percent of your hp(ie heals up to 50%) + constexpr int LimitManaPercent = 409; // implemented - limited to a certain percent of your mana + constexpr int LimitEndPercent = 410; // implemented - limited to a certain percent of your end + constexpr int LimitClass = 411; // implemented, @Ff, Class(es) that can use the spell focus, base1: class(es), Note: The class value in dbase is +1 in relation to item class value, set as you would item for multiple classes + constexpr int LimitRace = 412; // implemented, @Ff, Race that can use the spell focus, base1: race, Note: not used in any known live spells. Use only single race at a time. + constexpr int FcBaseEffects = 413; // implemented, @Fc, On Caster, base spell effectiveness mod pct, base: pct + constexpr int LimitCastingSkill = 414; // implemented, @Ff, Spell and singing skills(s) that a spell focus can require or exclude, base1: skill id, Include: Positive Exclude: Negative + constexpr int FFItemClass = 415; // implemented, @Ff, Limits focuses to be applied only from item click. base1: item ItemType (-1 to include for all ItemTypes,-1000 to exclude clicks from getting the focus, or exclude specific SubTypes or Slots if set), limit: item SubType (-1 for all SubTypes), max: item Slots (bitmask of valid slots, -1 ALL slots), Note: not used on live. See comments in Mob::CalcFocusEffect for more details. + constexpr int ACv2 = 416; // implemented - New AC spell effect + constexpr int ManaRegen_v2 = 417; // implemented - New mana regen effect + constexpr int SkillDamageAmount2 = 418; // implemented - adds skill damage directly to certain attacks + constexpr int AddMeleeProc = 419; // implemented - Adds a proc + constexpr int FcLimitUse = 420; // implemented, @Fc, On Caster, numhits mod pct, base: pct, Note: not used in any known live spells + constexpr int FcIncreaseNumHits = 421; // implemented, @Fc, On Caster, numhits mod flat amt, base: amt + constexpr int LimitUseMin = 422; // implemented, @Ff Minium amount of numhits for a spell to be focused, base: numhit amt + constexpr int LimitUseType = 423; // implemented, = @Ff Focus will only affect if has this numhits type, base: numhit type + constexpr int GravityEffect = 424; // implemented - Pulls/pushes you toward/away the mob at a set pace + constexpr int Display = 425; // *not implemented - Illusion: Flying Dragon(21626) + constexpr int IncreaseExtTargetWindow = 426; // *not implmented[AA] - increases the capacity of your extended target window + constexpr int SkillProcAttempt = 427; // implemented - chance to proc when using a skill(ie taunt) + constexpr int LimitToSkill = 428; // implemented, @Procs, limits what combat skills will effect a skill proc, base: skill value, limit: none, max: none + constexpr int SkillProcSuccess = 429; // implemented - chance to proc when tje skill in use successfully fires. + constexpr int PostEffect = 430; // *not implemented - Fear of the Dark(27641) - Alters vision + constexpr int PostEffectData = 431; // *not implemented - Fear of the Dark(27641) - Alters vision + constexpr int ExpandMaxActiveTrophyBen = 432; // not used + constexpr int CriticalDotDecay = 433; // implemented - increase critical dot chance, effect decays based on level of spell it effects. (12266) + constexpr int CriticalHealDecay = 434; // implemented - increase critical heal chance, effect decays based on level of spell it effects. + constexpr int CriticalRegenDecay = 435; // implemented - increase critical heal over time chance, effect decays based on level of spell it effects. + constexpr int BeneficialCountDownHold = 436; // not used ( 23491 | ABTest Buff Hold) + constexpr int TeleporttoAnchor = 437; // *not implemented - Teleport Guild Hall Anchor(33099) + constexpr int TranslocatetoAnchor = 438; // *not implemented - Translocate Primary Anchor (27750) + constexpr int Assassinate = 439; // implemented[AA] - Assassinate damage + constexpr int FinishingBlowLvl = 440; // implemented[AA] - Sets the level Finishing blow can be triggered on an NPC + constexpr int DistanceRemoval = 441; // implemented - Buff is removed from target when target moves X amount of distance away from where initially hit. + constexpr int TriggerOnReqTarget = 442; // implemented, @SpellTrigger, triggers a spell when Target Requirement conditions are met (see enum SpellRestriction for IDs), base: spellid, limit: SpellRestriction ID, max: none, Note: Usually cast on a target + constexpr int TriggerOnReqCaster = 443; // implemented, @SpellTrigger, triggers a spell when Caster Requirement conditions are met (see enum SpellRestriction for IDs), base: spellid, limit: SpellRestriction ID, max: none, Note: Usually self only + constexpr int ImprovedTaunt = 444; // implemented - Locks Aggro On Caster and Decrease other Players Aggro by X% on NPC targets below level Y + constexpr int AddMercSlot = 445; // *not implemented[AA] - [Hero's Barracks] Allows you to conscript additional mercs. + constexpr int AStacker = 446; // implementet - bufff stacking blocker (26219 | Qirik's Watch) + constexpr int BStacker = 447; // implemented + constexpr int CStacker = 448; // implemented + constexpr int DStacker = 449; // implemented + constexpr int MitigateDotDamage = 450; // implemented, @Runes, mitigate incoming dot damage by percentage until rune fades, base: percent mitigation, limit: max dmg absorbed per hit, max: rune amt, Note: If placed on item or AA, will provide stackable percent mitigation. + constexpr int MeleeThresholdGuard = 451; // implemented Partial Melee Rune that only is lowered if melee hits are over X amount of damage + constexpr int SpellThresholdGuard = 452; // implemented Partial Spell Rune that only is lowered if spell hits are over X amount of damage + constexpr int TriggerMeleeThreshold = 453; // implemented Trigger effect on X amount of melee damage taken in a single hit + constexpr int TriggerSpellThreshold = 454; // implemented Trigger effect on X amount of spell damage taken in a single hit + constexpr int AddHatePct = 455; // implemented Modify total hate by % + constexpr int AddHateOverTimePct = 456; // implemented Modify total hate by % over time. + constexpr int ResourceTap = 457; // implemented Coverts a percent of dmg from dmg spells(DD/DoT) to hp/mana/end. + constexpr int FactionModPct = 458; // implemented Modifies faction gains and losses by percent. + constexpr int DamageModifier2 = 459; // implemented - Modifies melee damage by skill type + constexpr int Ff_Override_NotFocusable = 460; // implemented, @Fc, Allow spell to be focused event if flagged with 'not_focusable' in spell table, base: 1 + constexpr int ImprovedDamage2 = 461; // implemented, @Fc, On Caster, spell damage mod pct, base: min pct, limit: max pct + constexpr int FcDamageAmt2 = 462; // implemented, @Fc, On Caster, spell damage mod flat amt, base: amt + constexpr int Shield_Target = 463; // implemented, Base1 % damage shielded on target + constexpr int PC_Pet_Rampage = 464; // implemented - Base1 % chance to do rampage for base2 % of damage each melee round + constexpr int PC_Pet_AE_Rampage = 465; // implemented - Base1 % chance to do AE rampage for base2 % of damage each melee round + constexpr int PC_Pet_Flurry_Chance = 466; // implemented - Base1 % chance to do flurry from double attack hit. + constexpr int DS_Mitigation_Amount = 467; // implemented - Modify incoming damage shield damage by a flat amount + constexpr int DS_Mitigation_Percentage = 468; // implemented - Modify incoming damage shield damage by percentage + constexpr int Chance_Best_in_Spell_Grp = 469; // implemented - Chance to cast highest scribed spell within a spell group. All base2 spells share roll chance, only 1 cast. + constexpr int Trigger_Best_in_Spell_Grp = 470; // implemented - Chance to cast highest scribed spell within a spell group. Each spell has own chance. + constexpr int Double_Melee_Round = 471; // implemented, @OffBonus, percent chance to repeat primary weapon round with a percent damage modifier, base: pct chance repeat, limit: pct dmg mod, max: none + constexpr int Buy_AA_Rank = 472; // implemented, @Special, Used in AA abilities that have Enable/Disable toggle. Spell on Disabled Rank has this effect in it, base: 1, limit: none, max: none, Note: This will not just buy an AA + constexpr int Double_Backstab_Front = 473; // implemented - Chance to double backstab from front + constexpr int Pet_Crit_Melee_Damage_Pct_Owner = 474; // implemenetd - Critical damage mod applied to pets from owner + constexpr int Trigger_Spell_Non_Item = 475; // implemented - Trigger spell on cast only if not from item click. + constexpr int Weapon_Stance = 476; // implemented, @Misc, Apply a specific spell buffs automatically depending 2Hander, Shield or Dual Wield is equipped, base: spellid, base: 0=2H 1=Shield 2=DW, max: none + constexpr int Hatelist_To_Top_Index = 477; // Implemented - Chance to be set to top of rampage list + constexpr int Hatelist_To_Tail_Index = 478; // Implemented - Chance to be set to bottom of rampage list + constexpr int Ff_Value_Min = 479; // implemented, @Ff, Minimum base value of a spell that can be focused, base: spells to be focused base1 value + constexpr int Ff_Value_Max = 480; // implemented, @Ff, Max base value of a spell that can be focused, base: spells to be focused base1 value + constexpr int Fc_Cast_Spell_On_Land = 481; // implemented, @Fc, On Target, cast spell if hit by spell, base: chance pct, limit: spellid + constexpr int Skill_Base_Damage_Mod = 482; // implemented, @OffBonus, modify base melee damage by percent, base: pct, limit: skill(-1=ALL), max: none + constexpr int Fc_Spell_Damage_Pct_IncomingPC = 483; // implemented, @Fc, On Target, spell damage taken mod pct, base: min pct, limit: max pct + constexpr int Fc_Spell_Damage_Amt_IncomingPC = 484; // implemented, @Fc, On Target, damage taken flat amt, base: amt + constexpr int Ff_CasterClass = 485; // implemented, @Ff, Caster of spell on target with a focus effect that is checked by incoming spells must be specified class(es). base1: class(es), Note: Set multiple classes same as would for items + constexpr int Ff_Same_Caster = 486; // implemented, @Ff, Caster of spell on target with a focus effect that is checked by incoming spells, base1: 0=Must be different caster 1=Must be same caster + constexpr int Extend_Tradeskill_Cap = 487; // + constexpr int Defender_Melee_Force_Pct_PC = 488; // + constexpr int Worn_Endurance_Regen_Cap = 489; // implemented, modify worn regen cap, base: amt, limit: none, max: none + constexpr int Ff_ReuseTimeMin = 490; // implemented, @Ff, Minimum recast time of a spell that can be focused, base: recast time + constexpr int Ff_ReuseTimeMax = 491; // implemented, @Ff, Max recast time of a spell that can be focused, base: recast time + constexpr int Ff_Endurance_Min = 492; // implemented, @Ff, Minimum endurance cost of a spell that can be focused, base: endurance cost + constexpr int Ff_Endurance_Max = 493; // implemented, @Ff, Max endurance cost of a spell that can be focused, base: endurance cost + constexpr int Pet_Add_Atk = 494; // implemented - Bonus on pet owner which gives their pet increased attack stat + constexpr int Ff_DurationMax = 495; // implemented, @Ff, Max duration of spell that can be focused, base: tics + constexpr int Critical_Melee_Damage_Mod_Max = 496; // implemented - increase or decrease by percent critical damage (not stackable) + constexpr int Ff_FocusCastProcNoBypass = 497; // + constexpr int AddExtraAttackPct_1h_Primary = 498; // implemented, @OffBonus, gives your double attacks a percent chance to perform an extra attack with 1-handed primary weapon, base: chance, limit: amt attacks, max: none + constexpr int AddExtraAttackPct_1h_Secondary = 499; //implemented, @OffBonus, gives your double attacks a percent chance to perform an extra attack with 1-handed secondary weapon, base: chance, limit: amt attacks, max: none + constexpr int Fc_CastTimeMod2 = 500; // implemented, @Fc, On Caster, cast time mod pct, base: pct, Note: Can reduce to instant cast + constexpr int Fc_CastTimeAmt = 501; // implemented, @Fc, On Caster, cast time mod flat amt, base: milliseconds, Note: Can reduce to instant cast + constexpr int Fearstun = 502; // implemented - Stun with a max level limit. Normal stun restrictions don't apply. + constexpr int Melee_Damage_Position_Mod = 503; // implemented, @OffBonus, modify melee damage by percent if done from Front or Behind opponent, base: pct, limit: 0=back 1=front, max: none + constexpr int Melee_Damage_Position_Amt = 504; // implemented, @OffBonus, modify melee damage by flat amount if done from Front or Behind opponent, base: amt, limit: 0=back 1=front, max: none + constexpr int Damage_Taken_Position_Mod = 505; // implemented, @DefBonus, modify melee damage by percent if dmg taken from Front or Behind, base: pct, limit: 0=back 1=front, max: none + constexpr int Damage_Taken_Position_Amt = 506; // implemented -@DefBonus, modify melee damage by flat amount if dmg taken from your Front or Behind, base: amt, limit: 0=back 1=front, max: none + constexpr int Fc_Amplify_Mod = 507; // implemented, @Fc, On Caster, damage-heal-dot mod pct, base: pct + constexpr int Fc_Amplify_Amt = 508; // implemented, @Fc, On Caster, damage-heal-dot mod flat amt, base: amt + constexpr int Health_Transfer = 509; // implemented - exchange health for damage or healing on a target. ie Lifeburn/Act of Valor + constexpr int Fc_ResistIncoming = 510; // implemented, @Fc, On Target, resist modifier, base: amt + constexpr int Ff_FocusTimerMin = 511; // implemented, @Ff, sets a recast time until focus can be used again, base: 1, limit: time ms, Note: ie. limit to 1 trigger every 1.5 seconds + constexpr int Proc_Timer_Modifier = 512; // implemented - limits procs per amount of a time based on timer value, base: 1, limit: time ms, Note:, ie limit to 1 proc every 55 seconds) + constexpr int Mana_Max_Percent = 513; // + constexpr int Endurance_Max_Percent = 514; // + constexpr int AC_Avoidance_Max_Percent = 515; // implemented - stackable avoidance modifier + constexpr int AC_Mitigation_Max_Percent = 516; // implemented - stackable defense modifier + constexpr int Attack_Offense_Max_Percent = 517; // + constexpr int Attack_Accuracy_Max_Percent = 518; // implemented - stackable accurary modifer + constexpr int Luck_Amount = 519; // + constexpr int Luck_Percent = 520; // + constexpr int Endurance_Absorb_Pct_Damage = 521; // implemented - Reduces % of Damage using Endurance, drains endurance at a ratio (ie. 0.05 Endurance per Hit Point) + constexpr int Instant_Mana_Pct = 522; // implemented - Increase/Decrease mana by percent of max mana + constexpr int Instant_Endurance_Pct = 523; // implemented - Increase/Decrease mana by percent of max endurance + constexpr int Duration_HP_Pct = 524; // implemented - Decrease Current Hit Points by % of Total Hit Points per Tick, up to a MAX per tick + constexpr int Duration_Mana_Pct = 525; // implemented - Decrease Current Mana by % of Total Mana per Tick, up to a MAX per tick + constexpr int Duration_Endurance_Pct = 526; // implemented - Decrease Current Endurance by % of Total Hit Points per Tick, up to a MAX per tick +} #define DF_Permanent 50 #define DF_Aura 51 @@ -1433,7 +1697,7 @@ struct SPDat_Spell_Struct /* 181 */ int pvp_duration; // buffdurationformula for PvP -- PVP_DURATION /* 182 */ int pvp_duration_cap; // buffduration for PvP -- PVP_DURATION_CAP /* 183 */ int pcnpc_only_flag; // valid values are 0, 1 = PCs (and mercs), and 2 = NPCs (and not mercs) -- PCNPC_ONLY_FLAG -/* 184 */ bool cast_not_standing; // this is checked in the client's EQ_Spell::IsCastWhileInvisSpell, this also blocks SE_InterruptCasting from affecting this spell -- CAST_NOT_STANDING (Allows casting if DA, stun, mezed, charm? fear?, damage to invul targets) +/* 184 */ bool cast_not_standing; // this is checked in the client's EQ_Spell::IsCastWhileInvisSpell, this also blocks SpellEffect::InterruptCasting from affecting this spell -- CAST_NOT_STANDING (Allows casting if DA, stun, mezed, charm? fear?, damage to invul targets) /* 185 */ bool can_mgb; // 0=no, -1 or 1 = yes -- CAN_MGB /* 186 */ int dispel_flag; // -- NO_DISPELL /* 187 */ //int npc_category; // -- NPC_MEM_CATEGORY @@ -1503,6 +1767,7 @@ bool IsTargetableAESpell(uint16 spell_id); bool IsSacrificeSpell(uint16 spell_id); bool IsLifetapSpell(uint16 spell_id); bool IsMesmerizeSpell(uint16 spell_id); +bool SpellBreaksMez(uint16 spell_id); bool IsStunSpell(uint16 spell_id); bool IsSlowSpell(uint16 spell_id); bool IsHasteSpell(uint16 spell_id); @@ -1536,6 +1801,11 @@ bool IsPureNukeSpell(uint16 spell_id); bool IsAENukeSpell(uint16 spell_id); bool IsPBAENukeSpell(uint16 spell_id); bool IsAERainNukeSpell(uint16 spell_id); +bool IsAnyNukeOrStunSpell(uint16 spell_id); +bool IsAnyAESpell(uint16 spell_id); +bool IsAESpell(uint16 spell_id); +bool IsPBAESpell(uint16 spell_id); +bool IsAERainSpell(uint16 spell_id); bool IsPartialResistableSpell(uint16 spell_id); bool IsResistableSpell(uint16 spell_id); bool IsGroupSpell(uint16 spell_id); @@ -1547,6 +1817,8 @@ bool IsBlankSpellEffect(uint16 spell_id, int effect_index); bool IsValidSpell(uint32 spell_id); bool IsSummonSpell(uint16 spell_id); bool IsDamageSpell(uint16 spell_id); +bool IsAnyDamageSpell(uint16 spell_id); +bool IsDamageOverTimeSpell(uint16 spell_i); bool IsFearSpell(uint16 spell_id); bool IsCureSpell(uint16 spell_id); bool IsHarmTouchSpell(uint16 spell_id); @@ -1585,10 +1857,16 @@ bool IsCompleteHealSpell(uint16 spell_id); bool IsFastHealSpell(uint16 spell_id); bool IsVeryFastHealSpell(uint16 spell_id); bool IsRegularSingleTargetHealSpell(uint16 spell_id); +bool IsRegularPetHealSpell(uint16 spell_id); bool IsRegularGroupHealSpell(uint16 spell_id); bool IsGroupCompleteHealSpell(uint16 spell_id); bool IsGroupHealOverTimeSpell(uint16 spell_id); +bool IsAnyHealSpell(uint16 spell_id); +bool IsAnyBuffSpell(uint16 spell_id); +bool IsDispelSpell(uint16 spell_id); +bool IsEscapeSpell(uint16 spell_id); bool IsDebuffSpell(uint16 spell_id); +bool IsHateReduxSpell(uint16 spell_id); bool IsResistDebuffSpell(uint16 spell_id); bool IsSelfConversionSpell(uint16 spell_id); bool IsBuffSpell(uint16 spell_id); @@ -1628,5 +1906,16 @@ bool IsCastRestrictedSpell(uint16 spell_id); bool IsAegolismSpell(uint16 spell_id); bool AegolismStackingIsSymbolSpell(uint16 spell_id); bool AegolismStackingIsArmorClassSpell(uint16 spell_id); +int8 SpellEffectsCount(uint16 spell_id); +bool IsLichSpell(uint16 spell_id); +bool IsInstantHealSpell(uint32 spell_id); +bool IsResurrectSpell(uint16 spell_id); +bool RequiresStackCheck(uint16 spell_type); +bool IsResistanceBuffSpell(uint16 spell_id); +bool IsResistanceOnlySpell(uint16 spell_id); +bool IsDamageShieldOnlySpell(uint16 spell_id); +bool IsDamageShieldAndResistSpell(uint16 spell_id); +bool IsHateSpell(uint16 spell_id); +bool IsDisciplineTome(const EQ::ItemData* item); #endif diff --git a/common/spdat_bot.cpp b/common/spdat_bot.cpp new file mode 100644 index 000000000..3a9ecf71e --- /dev/null +++ b/common/spdat_bot.cpp @@ -0,0 +1,499 @@ +#include "spdat.h" +#include "../zone/bot.h" + +bool IsBotSpellTypeDetrimental(uint16 spell_type) { + switch (spell_type) { + case BotSpellTypes::Nuke: + case BotSpellTypes::Root: + case BotSpellTypes::Lifetap: + case BotSpellTypes::Snare: + case BotSpellTypes::DOT: + case BotSpellTypes::Dispel: + case BotSpellTypes::Mez: + case BotSpellTypes::Charm: + case BotSpellTypes::Slow: + case BotSpellTypes::Debuff: + case BotSpellTypes::HateRedux: + case BotSpellTypes::Fear: + case BotSpellTypes::Stun: + case BotSpellTypes::AENukes: + case BotSpellTypes::AERains: + case BotSpellTypes::AEMez: + case BotSpellTypes::AEStun: + case BotSpellTypes::AEDebuff: + case BotSpellTypes::AESlow: + case BotSpellTypes::AESnare: + case BotSpellTypes::AEFear: + case BotSpellTypes::AEDispel: + case BotSpellTypes::AERoot: + case BotSpellTypes::AEDoT: + case BotSpellTypes::AELifetap: + case BotSpellTypes::PBAENuke: + case BotSpellTypes::Lull: + case BotSpellTypes::AELull: + case BotSpellTypes::HateLine: + case BotSpellTypes::AEHateLine: + return true; + default: + return false; + } + + return false; +} + +bool IsBotSpellTypeBeneficial(uint16 spell_type) { + switch (spell_type) { + case BotSpellTypes::RegularHeal: + case BotSpellTypes::CompleteHeal: + case BotSpellTypes::GroupCompleteHeals: + case BotSpellTypes::FastHeals: + case BotSpellTypes::VeryFastHeals: + case BotSpellTypes::GroupHeals: + case BotSpellTypes::GroupHoTHeals: + case BotSpellTypes::HoTHeals: + case BotSpellTypes::PetRegularHeals: + case BotSpellTypes::PetCompleteHeals: + case BotSpellTypes::PetFastHeals: + case BotSpellTypes::PetVeryFastHeals: + case BotSpellTypes::PetHoTHeals: + case BotSpellTypes::Buff: + case BotSpellTypes::Cure: + case BotSpellTypes::GroupCures: + case BotSpellTypes::PetCures: + case BotSpellTypes::DamageShields: + case BotSpellTypes::InCombatBuffSong: + case BotSpellTypes::OutOfCombatBuffSong: + case BotSpellTypes::Pet: + case BotSpellTypes::PetBuffs: + case BotSpellTypes::PreCombatBuff: + case BotSpellTypes::PreCombatBuffSong: + case BotSpellTypes::PetDamageShields: + case BotSpellTypes::PetResistBuffs: + case BotSpellTypes::ResistBuffs: + case BotSpellTypes::Resurrect: + case BotSpellTypes::Teleport: + case BotSpellTypes::Succor: + case BotSpellTypes::BindAffinity: + case BotSpellTypes::Identify: + case BotSpellTypes::Levitate: + case BotSpellTypes::Rune: + case BotSpellTypes::WaterBreathing: + case BotSpellTypes::Size: + case BotSpellTypes::Invisibility: + case BotSpellTypes::MovementSpeed: + case BotSpellTypes::SendHome: + case BotSpellTypes::SummonCorpse: + return true; + default: + return false; + } + + return false; +} + +bool BotSpellTypeUsesTargetSettings(uint16 spell_type) { + switch (spell_type) { + case BotSpellTypes::RegularHeal: + case BotSpellTypes::CompleteHeal: + case BotSpellTypes::GroupCompleteHeals: + case BotSpellTypes::FastHeals: + case BotSpellTypes::VeryFastHeals: + case BotSpellTypes::GroupHeals: + case BotSpellTypes::GroupHoTHeals: + case BotSpellTypes::HoTHeals: + case BotSpellTypes::PetRegularHeals: + case BotSpellTypes::PetCompleteHeals: + case BotSpellTypes::PetFastHeals: + case BotSpellTypes::PetVeryFastHeals: + case BotSpellTypes::PetHoTHeals: + case BotSpellTypes::Buff: + case BotSpellTypes::Cure: + case BotSpellTypes::GroupCures: + case BotSpellTypes::PetCures: + case BotSpellTypes::DamageShields: + case BotSpellTypes::PetDamageShields: + case BotSpellTypes::PetBuffs: + case BotSpellTypes::ResistBuffs: + case BotSpellTypes::PetResistBuffs: + case BotSpellTypes::Teleport: + case BotSpellTypes::Succor: + case BotSpellTypes::BindAffinity: + case BotSpellTypes::Identify: + case BotSpellTypes::Levitate: + case BotSpellTypes::Rune: + case BotSpellTypes::WaterBreathing: + case BotSpellTypes::Size: + case BotSpellTypes::Invisibility: + case BotSpellTypes::MovementSpeed: + case BotSpellTypes::SendHome: + case BotSpellTypes::SummonCorpse: + return true; + default: + return false; + } + + return false; +} + +bool IsBotSpellTypeInnate(uint16 spell_type) { + switch (spell_type) { + case BotSpellTypes::AENukes: + case BotSpellTypes::AERains: + case BotSpellTypes::PBAENuke: + case BotSpellTypes::Nuke: + case BotSpellTypes::AEDispel: + case BotSpellTypes::Dispel: + case BotSpellTypes::AERoot: + case BotSpellTypes::Root: + case BotSpellTypes::AESlow: + case BotSpellTypes::Slow: + case BotSpellTypes::Charm: + case BotSpellTypes::AEDebuff: + case BotSpellTypes::Debuff: + case BotSpellTypes::AEDoT: + case BotSpellTypes::DOT: + case BotSpellTypes::AELifetap: + case BotSpellTypes::Lifetap: + case BotSpellTypes::AEStun: + case BotSpellTypes::Stun: + case BotSpellTypes::AEMez: + case BotSpellTypes::Mez: + case BotSpellTypes::Lull: + case BotSpellTypes::AELull: + case BotSpellTypes::HateLine: + case BotSpellTypes::AEHateLine: + return true; + default: + return false; + } + + return false; +} + +bool IsAEBotSpellType(uint16 spell_type) { + switch (spell_type) { + case BotSpellTypes::AEDebuff: + case BotSpellTypes::AEFear: + case BotSpellTypes::AEMez: + case BotSpellTypes::AENukes: + case BotSpellTypes::AERains: + case BotSpellTypes::AESlow: + case BotSpellTypes::AESnare: + case BotSpellTypes::AEStun: + case BotSpellTypes::AEDispel: + case BotSpellTypes::AEDoT: + case BotSpellTypes::PBAENuke: + case BotSpellTypes::AELifetap: + case BotSpellTypes::AERoot: + case BotSpellTypes::AEHateLine: + case BotSpellTypes::AELull: + return true; + default: + return false; + } + + return false; +} + +bool IsGroupBotSpellType(uint16 spell_type) { + switch (spell_type) { + case BotSpellTypes::GroupCures: + case BotSpellTypes::GroupCompleteHeals: + case BotSpellTypes::GroupHeals: + case BotSpellTypes::GroupHoTHeals: + return true; + default: + return false; + } + + return false; +} + +bool IsGroupTargetOnlyBotSpellType(uint16 spell_type) { + switch (spell_type) { + case BotSpellTypes::GroupCures: + case BotSpellTypes::GroupCompleteHeals: + case BotSpellTypes::GroupHeals: + return true; + default: + return false; + } + + return false; +} + +bool IsPetBotSpellType(uint16 spell_type) { + switch (spell_type) { + case BotSpellTypes::PetBuffs: + case BotSpellTypes::PetRegularHeals: + case BotSpellTypes::PetCompleteHeals: + case BotSpellTypes::PetFastHeals: + case BotSpellTypes::PetVeryFastHeals: + case BotSpellTypes::PetHoTHeals: + case BotSpellTypes::PetDamageShields: + case BotSpellTypes::PetResistBuffs: + case BotSpellTypes::PetCures: + return true; + default: + return false; + } + + return false; +} + +bool IsClientBotSpellType(uint16 spell_type) { + switch (spell_type) { + case BotSpellTypes::RegularHeal: + case BotSpellTypes::CompleteHeal: + case BotSpellTypes::GroupCompleteHeals: + case BotSpellTypes::FastHeals: + case BotSpellTypes::VeryFastHeals: + case BotSpellTypes::GroupHeals: + case BotSpellTypes::GroupHoTHeals: + case BotSpellTypes::HoTHeals: + case BotSpellTypes::PetRegularHeals: + case BotSpellTypes::PetCompleteHeals: + case BotSpellTypes::PetFastHeals: + case BotSpellTypes::PetVeryFastHeals: + case BotSpellTypes::PetHoTHeals: + case BotSpellTypes::Buff: + case BotSpellTypes::Cure: + case BotSpellTypes::GroupCures: + case BotSpellTypes::PetCures: + case BotSpellTypes::DamageShields: + case BotSpellTypes::PetDamageShields: + case BotSpellTypes::PetBuffs: + case BotSpellTypes::ResistBuffs: + case BotSpellTypes::PetResistBuffs: + return true; + default: + return false; + } + + return false; +} + +bool IsHealBotSpellType(uint16 spell_type) { + switch (spell_type) { + case BotSpellTypes::VeryFastHeals: + case BotSpellTypes::FastHeals: + case BotSpellTypes::RegularHeal: + case BotSpellTypes::GroupHeals: + case BotSpellTypes::CompleteHeal: + case BotSpellTypes::GroupCompleteHeals: + case BotSpellTypes::HoTHeals: + case BotSpellTypes::GroupHoTHeals: + case BotSpellTypes::PetRegularHeals: + case BotSpellTypes::PetCompleteHeals: + case BotSpellTypes::PetFastHeals: + case BotSpellTypes::PetVeryFastHeals: + case BotSpellTypes::PetHoTHeals: + return true; + default: + return false; + } + + return false; +} + +bool BotSpellTypeRequiresLoS(uint16 spell_type) { + if (IsAEBotSpellType(spell_type)) { // These gather their own targets later + return false; + } + + switch (spell_type) { + case BotSpellTypes::RegularHeal: + case BotSpellTypes::GroupCompleteHeals: + case BotSpellTypes::CompleteHeal: + case BotSpellTypes::FastHeals: + case BotSpellTypes::VeryFastHeals: + case BotSpellTypes::GroupHeals: + case BotSpellTypes::GroupHoTHeals: + case BotSpellTypes::HoTHeals: + case BotSpellTypes::PetRegularHeals: + case BotSpellTypes::PetCompleteHeals: + case BotSpellTypes::PetFastHeals: + case BotSpellTypes::PetVeryFastHeals: + case BotSpellTypes::PetHoTHeals: + case BotSpellTypes::InCombatBuff: + return false; + default: + return true; + } + + return true; +} + +bool BotSpellTypeRequiresTarget(uint16 spell_type) { + switch (spell_type) { + case BotSpellTypes::Pet: + case BotSpellTypes::Succor: + return false; + default: + return true; + } + + return true; +} + +bool BotSpellTypeRequiresAEChecks(uint16 spell_type) { + switch (spell_type) { + case BotSpellTypes::AEMez: + return false; + default: + return true; + } + + return true; +} + +bool RequiresStackCheck(uint16 spell_type) { + switch (spell_type) { + case BotSpellTypes::VeryFastHeals: + case BotSpellTypes::PetVeryFastHeals: + case BotSpellTypes::FastHeals: + case BotSpellTypes::PetFastHeals: + case BotSpellTypes::RegularHeal: + case BotSpellTypes::PetRegularHeals: + case BotSpellTypes::CompleteHeal: + case BotSpellTypes::PetCompleteHeals: + case BotSpellTypes::GroupCompleteHeals: + case BotSpellTypes::Resurrect: + return false; + default: + return true; + } + + return true; +} + +bool IsCommandedBotSpellType(uint16 spell_type) { + switch (spell_type) { + case BotSpellTypes::Charm: + case BotSpellTypes::AEFear: + case BotSpellTypes::Fear: + case BotSpellTypes::Resurrect: + case BotSpellTypes::AELull: + case BotSpellTypes::Lull: + case BotSpellTypes::Teleport: + case BotSpellTypes::Succor: + case BotSpellTypes::BindAffinity: + case BotSpellTypes::Identify: + case BotSpellTypes::Levitate: + case BotSpellTypes::Rune: + case BotSpellTypes::WaterBreathing: + case BotSpellTypes::Size: + case BotSpellTypes::Invisibility: + case BotSpellTypes::MovementSpeed: + case BotSpellTypes::SendHome: + case BotSpellTypes::SummonCorpse: + return true; + default: + return false; + } + + return false; +} + +bool IsPullingBotSpellType(uint16 spell_type) { + switch (spell_type) { + case BotSpellTypes::Nuke: + case BotSpellTypes::Lifetap: + case BotSpellTypes::Snare: + case BotSpellTypes::DOT: + case BotSpellTypes::Dispel: + case BotSpellTypes::Slow: + case BotSpellTypes::Debuff: + case BotSpellTypes::Stun: + case BotSpellTypes::HateLine: + return true; + default: + return false; + } + + return false; +} + +uint16 GetCorrectBotSpellType(uint16 spell_type, uint16 spell_id) { + if (!IsValidSpell(spell_id)) { + return UINT16_MAX; + } + + uint16 correct_type = spell_type; + + if (!Bot::IsValidSpellTypeBySpellID(spell_type, spell_id)) { + correct_type = UINT16_MAX; + + 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 = end; i >= start; --i) { + if (!Bot::IsValidBotSpellType(i) || i == BotSpellTypes::InCombatBuff) { + continue; + } + + if (Bot::IsValidSpellTypeBySpellID(i, spell_id)) { + correct_type = i; + + break; + } + } + } + + return correct_type; +} + +uint16 GetPetBotSpellType(uint16 spell_type) { + switch (spell_type) { + case BotSpellTypes::Buff: + return BotSpellTypes::PetBuffs; + case BotSpellTypes::RegularHeal: + return BotSpellTypes::PetRegularHeals; + case BotSpellTypes::CompleteHeal: + return BotSpellTypes::PetCompleteHeals; + case BotSpellTypes::FastHeals: + return BotSpellTypes::PetFastHeals; + case BotSpellTypes::VeryFastHeals: + return BotSpellTypes::PetVeryFastHeals; + case BotSpellTypes::HoTHeals: + return BotSpellTypes::PetHoTHeals; + case BotSpellTypes::Cure: + return BotSpellTypes::PetCures; + case BotSpellTypes::DamageShields: + return BotSpellTypes::PetDamageShields; + case BotSpellTypes::ResistBuffs: + return BotSpellTypes::PetResistBuffs; + default: + return spell_type; + } + + return spell_type; +} + +bool IsBotBuffSpellType(uint16 spell_type) { + switch (spell_type) { + case BotSpellTypes::Buff: + case BotSpellTypes::PetBuffs: + case BotSpellTypes::ResistBuffs: + case BotSpellTypes::PetResistBuffs: + case BotSpellTypes::DamageShields: + case BotSpellTypes::PetDamageShields: + return true; + default: + return false; + } + + return false; +} + +bool BotRequiresLoSToCast(uint16 spell_type, uint16 spell_id) { + if (!BotSpellTypeRequiresTarget(spell_type)) { + return false; + } + + if (!IsTargetRequiredForSpell(spell_id)) { + return false; + } + + return true; +} diff --git a/common/strings.cpp b/common/strings.cpp index 1f35e30eb..eb56ab2f5 100644 --- a/common/strings.cpp +++ b/common/strings.cpp @@ -34,6 +34,7 @@ */ #include "strings.h" +#include #include #include #include @@ -41,6 +42,7 @@ #include #include #include +#include #include #include @@ -49,6 +51,12 @@ #include "strings_legacy.cpp" // legacy c functions #include "strings_misc.cpp" // anything non "Strings" scoped +#ifdef _WINDOWS +#include +#include +#include +#endif + std::string Strings::Random(size_t length) { static auto &chrs = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; @@ -305,6 +313,12 @@ std::string Strings::Commify(const std::string &number) auto string_length = static_cast(number.length()); + if (string_length == 3) { + return number; + } else if (string_length == 4 && number.starts_with("-")) { + return number; + } + int i = 0; for (i = string_length - 3; i >= 0; i -= 3) { if (i > 0) { @@ -701,6 +715,18 @@ std::string &Strings::Trim(std::string &str, const std::string &chars) return LTrim(RTrim(str, chars), chars); } +const std::string NUM_TO_ENGLISH_X[] = { + "", "One ", "Two ", "Three ", "Four ", + "Five ", "Six ", "Seven ", "Eight ", "Nine ", "Ten ", "Eleven ", + "Twelve ", "Thirteen ", "Fourteen ", "Fifteen ", + "Sixteen ", "Seventeen ", "Eighteen ", "Nineteen " +}; + +const std::string NUM_TO_ENGLISH_Y[] = { + "", "", "Twenty ", "Thirty ", "Forty ", + "Fifty ", "Sixty ", "Seventy ", "Eighty ", "Ninety " +}; + // Function to convert single digit or two digit number into words std::string Strings::ConvertToDigit(int n, const std::string& suffix) { @@ -912,3 +938,35 @@ std::string Strings::ZoneTime(const uint8 hours, const uint8 minutes) hours >= 13 ? "PM" : "AM" ); } + +std::string Strings::Slugify(const std::string& input, const std::string& separator) { + std::string slug; + bool last_was_hyphen = false; + + for (char c : input) { + if (std::isalnum(c)) { + slug += std::tolower(c); + last_was_hyphen = false; + } else if (c == ' ' || c == '_' || c == '-') { + if (!last_was_hyphen && !slug.empty()) { + slug += separator; + last_was_hyphen = true; + } + } + } + + // Remove trailing hyphen if present + if (!slug.empty() && slug.back() == '-') { + slug.pop_back(); + } + + return slug; +} + +bool Strings::IsValidJson(const std::string &json) +{ + rapidjson::Document doc; + rapidjson::ParseResult result = doc.Parse(json.c_str()); + + return result; +} diff --git a/common/strings.h b/common/strings.h index 7c6f6462a..8ae8fee25 100644 --- a/common/strings.h +++ b/common/strings.h @@ -36,52 +36,19 @@ #define _STRINGUTIL_H_ #include -#include -#include +#include #include +#include #include #include -#include #include -#include - -#ifndef _WIN32 -// this doesn't appear to affect linux-based systems..need feedback for _WIN64 - -#endif - -#ifdef _WINDOWS +#ifdef _WIN32 #include -#include -#include #endif #include "types.h" -namespace detail { - // template magic to check if std::from_chars floating point functions exist - template - struct has_from_chars_float : std::false_type { }; - - // basically it "uses" this template if they do exist because reasons - template - struct has_from_chars_float < T, - std::void_t(), std::declval(), - std::declval()))>> : std::true_type { }; -}; // namespace detail - -namespace EQ { -// lame -- older GCC's didn't define this, clang's libc++ however does, even though they lack FP support -#if defined(__GNUC__) && (__GNUC__ < 11) && !defined(__clang__) - enum class chars_format { - scientific = 1, fixed = 2, hex = 4, general = fixed | scientific - }; -#else - using chars_format = std::chars_format; -#endif -}; // namespace EQ - class Strings { public: static bool Contains(std::vector container, const std::string& element); @@ -132,60 +99,8 @@ public: static bool BeginsWith(const std::string& subject, const std::string& search); static bool EndsWith(const std::string& subject, const std::string& search); static std::string ZoneTime(const uint8 hours, const uint8 minutes); - - template - static std::string - ImplodePair(const std::string &glue, const std::pair &encapsulation, const std::vector &src) - { - if (src.empty()) { - return {}; - } - std::ostringstream oss; - for (const T &src_iter: src) { - oss << encapsulation.first << src_iter << encapsulation.second << glue; - } - std::string output(oss.str()); - output.resize(output.size() - glue.size()); - return output; - } - - // basic string_view overloads that just use std stuff since they work! - template - std::enable_if_t && detail::has_from_chars_float::value, std::from_chars_result> - static from_chars(std::string_view str, T& value, EQ::chars_format fmt = EQ::chars_format::general) - { - return std::from_chars(str.data(), str.data() + str.size(), value, fmt); - } - - template - std::enable_if_t, std::from_chars_result> - static from_chars(std::string_view str, T& value, int base = 10) - { - return std::from_chars(str.data(), str.data() + str.size(), value, base); - } - - // fallback versions of floating point in case they're not implemented - // TODO: add error handling ... - // This does have to allocate since from_chars doesn't need a null terminated string and neither does string_view - template - std::enable_if_t && !detail::has_from_chars_float::value && std::is_same_v, std::from_chars_result> - static from_chars(std::string_view str, T& value, EQ::chars_format fmt = EQ::chars_format::general) - { - std::from_chars_result res{}; - std::string tmp_str(str.data(), str.size()); - value = strtof(tmp_str.data(), nullptr); - return res; - } - - template - std::enable_if_t && !detail::has_from_chars_float::value && std::is_same_v, std::from_chars_result> - static from_chars(std::string_view str, T& value, EQ::chars_format fmt = EQ::chars_format::general) - { - std::from_chars_result res{}; - std::string tmp_str(str.data(), str.size()); - value = strtod(tmp_str.data(), nullptr); - return res; - } + 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, ...); @@ -195,93 +110,6 @@ const std::string vStringFormat(const char *format, va_list args); // Used for grid nodes, as NPC names remove numerals. // But general purpose -const std::string NUM_TO_ENGLISH_X[] = { - "", "One ", "Two ", "Three ", "Four ", - "Five ", "Six ", "Seven ", "Eight ", "Nine ", "Ten ", "Eleven ", - "Twelve ", "Thirteen ", "Fourteen ", "Fifteen ", - "Sixteen ", "Seventeen ", "Eighteen ", "Nineteen " -}; - -const std::string NUM_TO_ENGLISH_Y[] = { - "", "", "Twenty ", "Thirty ", "Forty ", - "Fifty ", "Sixty ", "Seventy ", "Eighty ", "Ninety " -}; - -// _WIN32 builds require that #include be included in whatever code file the invocation is made from (no header files) -template -std::vector join_pair( - const std::string &glue, - const std::pair &encapsulation, - const std::vector> &src -) -{ - if (src.empty()) { - return {}; - } - - std::vector output; - - for (const std::pair &src_iter: src) { - output.emplace_back( - - fmt::format( - "{}{}{}{}{}{}{}", - encapsulation.first, - src_iter.first, - encapsulation.second, - glue, - encapsulation.first, - src_iter.second, - encapsulation.second - ) - ); - } - - return output; -} - -// _WIN32 builds require that #include be included in whatever code file the invocation is made from (no header files) -template -std::vector join_tuple( - const std::string &glue, - const std::pair &encapsulation, - const std::vector> &src -) -{ - if (src.empty()) { - return {}; - } - - std::vector output; - - for (const std::tuple &src_iter: src) { - - output.emplace_back( - - fmt::format( - "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", - encapsulation.first, - std::get<0>(src_iter), - encapsulation.second, - glue, - encapsulation.first, - std::get<1>(src_iter), - encapsulation.second, - glue, - encapsulation.first, - std::get<2>(src_iter), - encapsulation.second, - glue, - encapsulation.first, - std::get<3>(src_iter), - encapsulation.second - ) - ); - } - - return output; -} - // misc functions std::string SanitizeWorldServerName(std::string server_long_name); std::vector GetBadWords(); @@ -306,18 +134,4 @@ std::string FormatName(const std::string &char_name); bool IsAllowedWorldServerCharacterList(char c); void SanitizeWorldServerName(char *name); -template -auto CleanMobName(InputIterator first, InputIterator last, OutputIterator result) -{ - for (; first != last; ++first) { - if (*first == '_') { - *result = ' '; - } - else if (isalpha(*first) || *first == '`') { - *result = *first; - } - } - return result; -} - #endif diff --git a/common/timer.cpp b/common/timer.cpp index 280b82518..191074618 100644 --- a/common/timer.cpp +++ b/common/timer.cpp @@ -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; +} diff --git a/common/timer.h b/common/timer.h index 6678be952..cd550c9df 100644 --- a/common/timer.h +++ b/common/timer.h @@ -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(); @@ -86,6 +87,9 @@ struct BenchTimer void reset() { start_time = clock::now(); } // this is seconds double elapsed() { return std::chrono::duration (clock::now() - start_time).count(); } + std::chrono::milliseconds::rep elapsedMilliseconds() { return std::chrono::duration_cast(clock::now() - start_time).count(); } + std::chrono::microseconds::rep elapsedMicroseconds() { return std::chrono::duration_cast(clock::now() - start_time).count(); } + std::chrono::nanoseconds::rep elapsedNanoseconds() { return std::chrono::duration_cast(clock::now() - start_time).count(); } private: std::chrono::time_point start_time; }; diff --git a/common/version.h b/common/version.h index a144157a7..c119346d4 100644 --- a/common/version.h +++ b/common/version.h @@ -25,7 +25,7 @@ // Build variables // these get injected during the build pipeline -#define CURRENT_VERSION "22.60.0-dev" // always append -dev to the current version for custom-builds +#define CURRENT_VERSION "23.10.3-dev" // always append -dev to the current version for custom-builds #define LOGIN_VERSION "0.8.0" #define COMPILE_DATE __DATE__ #define COMPILE_TIME __TIME__ @@ -42,8 +42,9 @@ * Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt */ -#define CURRENT_BINARY_DATABASE_VERSION 9285 -#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9045 +#define CURRENT_BINARY_DATABASE_VERSION 9328 +#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9054 +#define CUSTOM_BINARY_DATABASE_VERSION 0 #endif diff --git a/common/zone_store.cpp b/common/zone_store.cpp index 23a59c31e..fa64bd492 100644 --- a/common/zone_store.cpp +++ b/common/zone_store.cpp @@ -676,12 +676,6 @@ int ZoneStore::GetZoneNPCMaximumAggroDistance(uint32 zone_id, int version) return z ? z->npc_max_aggro_dist : DEFAULT_ZONE_MAX_AGGRO_DISTANCE; } -uint32 ZoneStore::GetZoneMaximumMovementUpdateRange(uint32 zone_id, int version) -{ - const auto& z = GetZoneVersionWithFallback(zone_id, version); - return z ? z->max_movement_update_range : DEFAULT_ZONE_MAX_MOVEMENT_UPDATE_RANGE; -} - int8 ZoneStore::GetZoneMinimumExpansion(uint32 zone_id, int version) { const auto& z = GetZoneVersionWithFallback(zone_id, version); diff --git a/common/zone_store.h b/common/zone_store.h index b63e807f2..4a6940f4d 100644 --- a/common/zone_store.h +++ b/common/zone_store.h @@ -94,7 +94,6 @@ public: int GetZoneFastRegenMana(uint32 zone_id, int version = 0); int GetZoneFastRegenEndurance(uint32 zone_id, int version = 0); int GetZoneNPCMaximumAggroDistance(uint32 zone_id, int version = 0); - uint32 GetZoneMaximumMovementUpdateRange(uint32 zone_id, int version = 0); int8 GetZoneMinimumExpansion(uint32 zone_id, int version = 0); int8 GetZoneMaximumExpansion(uint32 zone_id, int version = 0); const std::string GetZoneContentFlags(uint32 zone_id, int version = 0); @@ -105,36 +104,39 @@ public: uint8 GetZoneIdleWhenEmpty(uint32 zone_id, int version = 0); uint32 GetZoneSecondsBeforeIdle(uint32 zone_id, int version = 0); + static ZoneStore* Instance() + { + static ZoneStore instance; + return &instance; + } private: std::vector m_zones; }; -extern ZoneStore zone_store; - /** * Global helpers */ -inline uint32 ZoneID(const char *in_zone_name) { return zone_store.GetZoneID(in_zone_name); } -inline uint32 ZoneID(const std::string& zone_name) { return zone_store.GetZoneID(zone_name); } +inline uint32 ZoneID(const char *in_zone_name) { return ZoneStore::Instance()->GetZoneID(in_zone_name); } +inline uint32 ZoneID(const std::string& zone_name) { return ZoneStore::Instance()->GetZoneID(zone_name); } inline const char *ZoneName(uint32 zone_id, bool error_unknown = false) { - return zone_store.GetZoneName( + return ZoneStore::Instance()->GetZoneName( zone_id, error_unknown ); } inline const char *ZoneLongName(uint32 zone_id, bool error_unknown = false) { - return zone_store.GetZoneLongName( + return ZoneStore::Instance()->GetZoneLongName( zone_id, error_unknown ); } -inline ZoneRepository::Zone *GetZone(uint32 zone_id, int version = 0) { return zone_store.GetZone(zone_id, version); }; -inline ZoneRepository::Zone *GetZone(const char *in_zone_name) { return zone_store.GetZone(in_zone_name); }; +inline ZoneRepository::Zone *GetZone(uint32 zone_id, int version = 0) { return ZoneStore::Instance()->GetZone(zone_id, version); }; +inline ZoneRepository::Zone *GetZone(const char *in_zone_name) { return ZoneStore::Instance()->GetZone(in_zone_name); }; inline ZoneRepository::Zone *GetZone(const char *in_zone_name, int version = 0) { - return zone_store.GetZone( + return ZoneStore::Instance()->GetZone( ZoneID( in_zone_name ), version @@ -142,7 +144,7 @@ inline ZoneRepository::Zone *GetZone(const char *in_zone_name, int version = 0) }; inline ZoneRepository::Zone *GetZoneVersionWithFallback(uint32 zone_id, int version = 0) { - return zone_store.GetZoneWithFallback( + return ZoneStore::Instance()->GetZoneWithFallback( zone_id, version ); diff --git a/eqlaunch/eqlaunch.cpp b/eqlaunch/eqlaunch.cpp index ea41b1803..edc50aea7 100644 --- a/eqlaunch/eqlaunch.cpp +++ b/eqlaunch/eqlaunch.cpp @@ -33,19 +33,16 @@ #include #include -EQEmuLogSys LogSys; -PathManager path; - bool RunLoops = false; void CatchSignal(int sig_num); int main(int argc, char *argv[]) { RegisterExecutablePlatform(ExePlatformLaunch); - LogSys.LoadLogSettingsDefaults(); + EQEmuLogSys::Instance()->LoadLogSettingsDefaults(); set_exception_handler(); - path.LoadPaths(); + PathManager::Instance()->Init(); std::string launcher_name; if(argc == 2) { @@ -169,7 +166,7 @@ int main(int argc, char *argv[]) { delete zone->second; } - LogSys.CloseFileLogs(); + EQEmuLogSys::Instance()->CloseFileLogs(); return 0; } diff --git a/hc/eq.cpp b/hc/eq.cpp index aab481e82..d8f2877d8 100644 --- a/hc/eq.cpp +++ b/hc/eq.cpp @@ -33,7 +33,7 @@ EverQuest::EverQuest(const std::string &host, int port, const std::string &user, } else { m_host = addr; - m_login_connection_manager.reset(new EQ::Net::DaybreakConnectionManager()); + m_login_connection_manager.reset(new EQ::Net::ReliableStreamConnectionManager()); m_login_connection_manager->OnNewConnection(std::bind(&EverQuest::LoginOnNewConnection, this, std::placeholders::_1)); m_login_connection_manager->OnConnectionStateChange(std::bind(&EverQuest::LoginOnStatusChangeReconnectEnabled, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); @@ -48,13 +48,13 @@ EverQuest::~EverQuest() { } -void EverQuest::LoginOnNewConnection(std::shared_ptr connection) +void EverQuest::LoginOnNewConnection(std::shared_ptr connection) { m_login_connection = connection; Log.OutF(Logs::General, Logs::Headless_Client, "Connecting..."); } -void EverQuest::LoginOnStatusChangeReconnectEnabled(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to) +void EverQuest::LoginOnStatusChangeReconnectEnabled(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to) { if (to == EQ::Net::StatusConnected) { Log.OutF(Logs::General, Logs::Headless_Client, "Login connected."); @@ -70,14 +70,14 @@ void EverQuest::LoginOnStatusChangeReconnectEnabled(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to) +void EverQuest::LoginOnStatusChangeReconnectDisabled(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to) { if (to == EQ::Net::StatusDisconnected) { m_login_connection.reset(); } } -void EverQuest::LoginOnPacketRecv(std::shared_ptr conn, const EQ::Net::Packet & p) +void EverQuest::LoginOnPacketRecv(std::shared_ptr conn, const EQ::Net::Packet & p) { auto opcode = p.GetUInt16(0); switch (opcode) { @@ -251,20 +251,20 @@ void EverQuest::LoginDisableReconnect() void EverQuest::ConnectToWorld() { - m_world_connection_manager.reset(new EQ::Net::DaybreakConnectionManager()); + m_world_connection_manager.reset(new EQ::Net::ReliableStreamConnectionManager()); m_world_connection_manager->OnNewConnection(std::bind(&EverQuest::WorldOnNewConnection, this, std::placeholders::_1)); m_world_connection_manager->OnConnectionStateChange(std::bind(&EverQuest::WorldOnStatusChangeReconnectEnabled, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); m_world_connection_manager->OnPacketRecv(std::bind(&EverQuest::WorldOnPacketRecv, this, std::placeholders::_1, std::placeholders::_2)); m_world_connection_manager->Connect(m_host, 9000); } -void EverQuest::WorldOnNewConnection(std::shared_ptr connection) +void EverQuest::WorldOnNewConnection(std::shared_ptr connection) { m_world_connection = connection; Log.OutF(Logs::General, Logs::Headless_Client, "Connecting to world..."); } -void EverQuest::WorldOnStatusChangeReconnectEnabled(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to) +void EverQuest::WorldOnStatusChangeReconnectEnabled(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to) { if (to == EQ::Net::StatusConnected) { Log.OutF(Logs::General, Logs::Headless_Client, "World connected."); @@ -278,14 +278,14 @@ void EverQuest::WorldOnStatusChangeReconnectEnabled(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to) +void EverQuest::WorldOnStatusChangeReconnectDisabled(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to) { if (to == EQ::Net::StatusDisconnected) { m_world_connection.reset(); } } -void EverQuest::WorldOnPacketRecv(std::shared_ptr conn, const EQ::Net::Packet & p) +void EverQuest::WorldOnPacketRecv(std::shared_ptr conn, const EQ::Net::Packet & p) { auto opcode = p.GetUInt16(0); switch (opcode) { diff --git a/hc/eq.h b/hc/eq.h index 7cdbfe1cd..63467b318 100644 --- a/hc/eq.h +++ b/hc/eq.h @@ -1,7 +1,7 @@ #pragma once #include "../common/eqemu_logsys.h" -#include "../common/net/daybreak_connection.h" +#include "../common/net/reliable_stream_connection.h" #include "../common/event/timer.h" #include #include @@ -26,10 +26,10 @@ public: private: //Login - void LoginOnNewConnection(std::shared_ptr connection); - void LoginOnStatusChangeReconnectEnabled(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to); - void LoginOnStatusChangeReconnectDisabled(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to); - void LoginOnPacketRecv(std::shared_ptr conn, const EQ::Net::Packet &p); + void LoginOnNewConnection(std::shared_ptr connection); + void LoginOnStatusChangeReconnectEnabled(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to); + void LoginOnStatusChangeReconnectDisabled(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to); + void LoginOnPacketRecv(std::shared_ptr conn, const EQ::Net::Packet &p); void LoginSendSessionReady(); void LoginSendLogin(); @@ -41,25 +41,25 @@ private: void LoginDisableReconnect(); - std::unique_ptr m_login_connection_manager; - std::shared_ptr m_login_connection; + std::unique_ptr m_login_connection_manager; + std::shared_ptr m_login_connection; std::map m_world_servers; //World void ConnectToWorld(); - void WorldOnNewConnection(std::shared_ptr connection); - void WorldOnStatusChangeReconnectEnabled(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to); - void WorldOnStatusChangeReconnectDisabled(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to); - void WorldOnPacketRecv(std::shared_ptr conn, const EQ::Net::Packet &p); + void WorldOnNewConnection(std::shared_ptr connection); + void WorldOnStatusChangeReconnectEnabled(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to); + void WorldOnStatusChangeReconnectDisabled(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to); + void WorldOnPacketRecv(std::shared_ptr conn, const EQ::Net::Packet &p); void WorldSendClientAuth(); void WorldSendEnterWorld(const std::string &character); void WorldProcessCharacterSelect(const EQ::Net::Packet &p); - std::unique_ptr m_world_connection_manager; - std::shared_ptr m_world_connection; + std::unique_ptr m_world_connection_manager; + std::shared_ptr m_world_connection; //Variables std::string m_host; diff --git a/hc/login.cpp b/hc/login.cpp index 170a32bf7..f6dca7c78 100644 --- a/hc/login.cpp +++ b/hc/login.cpp @@ -26,7 +26,7 @@ LoginConnection::LoginConnection(const std::string &username, const std::string m_host_port = host_port; m_server = server; - m_connection_manager.reset(new EQ::Net::DaybreakConnectionManager()); + m_connection_manager.reset(new EQ::Net::ReliableStreamConnectionManager()); m_connection_manager->OnNewConnection(std::bind(&LoginConnection::OnNewConnection, this, std::placeholders::_1)); m_connection_manager->OnConnectionStateChange(std::bind(&LoginConnection::OnStatusChangeActive, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); @@ -39,13 +39,13 @@ LoginConnection::~LoginConnection() { } -void LoginConnection::OnNewConnection(std::shared_ptr connection) +void LoginConnection::OnNewConnection(std::shared_ptr connection) { m_connection = connection; Log.OutF(Logs::General, Logs::Headless_Client, "Connecting..."); } -void LoginConnection::OnStatusChangeActive(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to) +void LoginConnection::OnStatusChangeActive(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to) { if (to == EQ::Net::StatusConnected) { Log.OutF(Logs::General, Logs::Headless_Client, "Login connected."); @@ -61,7 +61,7 @@ void LoginConnection::OnStatusChangeActive(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to) +void LoginConnection::OnStatusChangeInactive(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to) { if (to == EQ::Net::StatusDisconnected) { m_key.clear(); @@ -70,7 +70,7 @@ void LoginConnection::OnStatusChangeInactive(std::shared_ptr conn, const EQ::Net::Packet &p) +void LoginConnection::OnPacketRecv(std::shared_ptr conn, const EQ::Net::Packet &p) { auto opcode = p.GetUInt16(0); switch (opcode) { diff --git a/hc/login.h b/hc/login.h index 31a448b70..bec7c3cdd 100644 --- a/hc/login.h +++ b/hc/login.h @@ -1,6 +1,6 @@ #pragma once -#include "../common/net/daybreak_connection.h" +#include "../common/net/reliable_stream_connection.h" #include "../common/event/timer.h" #include @@ -23,10 +23,10 @@ public: ~LoginConnection(); private: - void OnNewConnection(std::shared_ptr connection); - void OnStatusChangeActive(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to); - void OnStatusChangeInactive(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to); - void OnPacketRecv(std::shared_ptr conn, const EQ::Net::Packet &p); + void OnNewConnection(std::shared_ptr connection); + void OnStatusChangeActive(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to); + void OnStatusChangeInactive(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to); + void OnPacketRecv(std::shared_ptr conn, const EQ::Net::Packet &p); void Kill(); void Start(); @@ -38,8 +38,8 @@ private: void ProcessServerPacketList(const EQ::Net::Packet &p); void ProcessServerPlayResponse(const EQ::Net::Packet &p); - std::unique_ptr m_connection_manager; - std::shared_ptr m_connection; + std::unique_ptr m_connection_manager; + std::shared_ptr m_connection; bool m_connecting; std::unique_ptr m_connect_timer; diff --git a/hc/world.cpp b/hc/world.cpp index 7be7cecb4..c9d690f62 100644 --- a/hc/world.cpp +++ b/hc/world.cpp @@ -8,7 +8,7 @@ WorldConnection::WorldConnection(const std::string &key, uint32_t dbid, const st m_key = key; m_dbid = dbid; - m_connection_manager.reset(new EQ::Net::DaybreakConnectionManager()); + m_connection_manager.reset(new EQ::Net::ReliableStreamConnectionManager()); m_connection_manager->OnNewConnection(std::bind(&WorldConnection::OnNewConnection, this, std::placeholders::_1)); m_connection_manager->OnConnectionStateChange(std::bind(&WorldConnection::OnStatusChangeActive, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); m_connection_manager->OnPacketRecv(std::bind(&WorldConnection::OnPacketRecv, this, std::placeholders::_1, std::placeholders::_2)); @@ -18,13 +18,13 @@ WorldConnection::WorldConnection(const std::string &key, uint32_t dbid, const st WorldConnection::~WorldConnection() { } -void WorldConnection::OnNewConnection(std::shared_ptr connection) +void WorldConnection::OnNewConnection(std::shared_ptr connection) { m_connection = connection; Log.OutF(Logs::General, Logs::Headless_Client, "Connecting to world..."); } -void WorldConnection::OnStatusChangeActive(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to) +void WorldConnection::OnStatusChangeActive(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to) { if (to == EQ::Net::StatusConnected) { Log.OutF(Logs::General, Logs::Headless_Client, "World connected."); @@ -38,14 +38,14 @@ void WorldConnection::OnStatusChangeActive(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to) +void WorldConnection::OnStatusChangeInactive(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to) { if (to == EQ::Net::StatusDisconnected) { m_connection.reset(); } } -void WorldConnection::OnPacketRecv(std::shared_ptr conn, const EQ::Net::Packet &p) +void WorldConnection::OnPacketRecv(std::shared_ptr conn, const EQ::Net::Packet &p) { auto opcode = p.GetUInt16(0); Log.OutF(Logs::General, Logs::Headless_Client, "Packet in:\n{0}", p.ToString()); diff --git a/hc/world.h b/hc/world.h index 7ca0e96d7..b1df83894 100644 --- a/hc/world.h +++ b/hc/world.h @@ -1,6 +1,6 @@ #pragma once -#include "../common/net/daybreak_connection.h" +#include "../common/net/reliable_stream_connection.h" #include "../common/event/timer.h" #include @@ -10,17 +10,17 @@ public: WorldConnection(const std::string &key, uint32_t dbid, const std::string &host); ~WorldConnection(); private: - void OnNewConnection(std::shared_ptr connection); - void OnStatusChangeActive(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to); - void OnStatusChangeInactive(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to); - void OnPacketRecv(std::shared_ptr conn, const EQ::Net::Packet &p); + void OnNewConnection(std::shared_ptr connection); + void OnStatusChangeActive(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to); + void OnStatusChangeInactive(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to); + void OnPacketRecv(std::shared_ptr conn, const EQ::Net::Packet &p); void Kill(); void Start(); void SendClientAuth(); - std::unique_ptr m_connection_manager; - std::shared_ptr m_connection; + std::unique_ptr m_connection_manager; + std::shared_ptr m_connection; bool m_connecting; std::unique_ptr m_connect_timer; diff --git a/loginserver/CMakeLists.txt b/loginserver/CMakeLists.txt index e55c32c1d..5f2b43248 100644 --- a/loginserver/CMakeLists.txt +++ b/loginserver/CMakeLists.txt @@ -4,12 +4,11 @@ SET(eqlogin_sources account_management.cpp client.cpp client_manager.cpp - database.cpp encryption.cpp loginserver_command_handler.cpp loginserver_webserver.cpp main.cpp - server_manager.cpp + world_server_manager.cpp world_server.cpp ) @@ -17,14 +16,13 @@ SET(eqlogin_headers account_management.h client.h client_manager.h - database.h encryption.h loginserver_command_handler.h loginserver_webserver.h login_server.h login_types.h options.h - server_manager.h + world_server_manager.h world_server.h ) diff --git a/loginserver/account_management.cpp b/loginserver/account_management.cpp index b95000ed1..f7c0ed535 100644 --- a/loginserver/account_management.cpp +++ b/loginserver/account_management.cpp @@ -1,259 +1,114 @@ #include "account_management.h" #include "login_server.h" #include "../common/event/task_scheduler.h" -#include "../common/event/event_loop.h" -#include "../common/net/dns.h" -#include "../common/strings.h" +#include "../common/repositories/login_accounts_repository.h" -extern LoginServer server; EQ::Event::TaskScheduler task_runner; -/** - * @param username - * @param password - * @param email - * @param source_loginserver - * @param login_account_id - * @return - */ -int32 AccountManagement::CreateLoginServerAccount( - std::string username, - std::string password, - std::string email, - const std::string &source_loginserver, - uint32 login_account_id -) +uint64 AccountManagement::CreateLoginServerAccount(LoginAccountContext c) { - auto mode = server.options.GetEncryptionMode(); - auto hash = eqcrypt_hash(username, password, mode); - - LogInfo( - "Attempting to create local login account for user [{0}] encryption algorithm [{1}] ({2})", - username, - GetEncryptionByModeId(mode), - mode - ); - - unsigned int db_id = 0; - if (server.db->DoesLoginServerAccountExist(username, hash, source_loginserver, 1)) { + if (LoginAccountsRepository::GetAccountFromContext(database, c).id > 0) { LogWarning( - "Attempting to create local login account for user [{0}] login [{1}] but already exists!", - username, - source_loginserver + "Attempting to create local login account for user [{}] but already exists!", + c.username ); return -1; } - uint32 created_account_id = 0; - if (login_account_id > 0) { - created_account_id = server.db->CreateLoginDataWithID(username, hash, source_loginserver, login_account_id); - } - else { - created_account_id = server.db->CreateLoginAccount(username, hash, source_loginserver, email); + auto a = LoginAccountsRepository::CreateAccountFromContext(database, c); + if (a.id > 0) { + return (int64) a.id; } - if (created_account_id > 0) { - LogInfo( - "Account creation success for user [{0}] encryption algorithm [{1}] ({2}) id: [{3}]", - username, - GetEncryptionByModeId(mode), - mode, - created_account_id - ); - - return (int32) created_account_id; - } - - LogError("Failed to create local login account for user [{0}]!", username); + LogError("Failed to create local login account for user [{}] !", c.username); return 0; } -/** - * @param username - * @param password - * @param email - * @return - */ -bool AccountManagement::CreateLoginserverWorldAdminAccount( - const std::string &username, - const std::string &password, - const std::string &email, - const std::string &first_name, - const std::string &last_name, - const std::string &ip_address -) +uint64 AccountManagement::CheckLoginserverUserCredentials(LoginAccountContext c) { auto mode = server.options.GetEncryptionMode(); - auto hash = eqcrypt_hash(username, password, mode); - - LogInfo( - "Attempting to create world admin account | username [{0}] encryption algorithm [{1}] ({2})", - username, - GetEncryptionByModeId(mode), - mode - ); - - if (server.db->DoesLoginserverWorldAdminAccountExist(username)) { - LogWarning( - "Attempting to create world admin account for user [{0}] but already exists!", - username - ); - - return false; - } - - uint32 created_world_admin_id = server.db->CreateLoginserverWorldAdminAccount( - username, - hash, - first_name, - last_name, - email, - ip_address - ); - - if (created_world_admin_id > 0) { - LogInfo( - "Account creation success for user [{0}] encryption algorithm [{1}] ({2}) new admin id [{3}]", - username, - GetEncryptionByModeId(mode), - mode, - created_world_admin_id - ); - return true; - } - - LogError("Failed to create world admin account account for user [{0}]!", username); - - return false; -} - -/** - * @param in_account_username - * @param in_account_password - * @return - */ -uint32 AccountManagement::CheckLoginserverUserCredentials( - const std::string &in_account_username, - const std::string &in_account_password, - const std::string &source_loginserver -) -{ - auto mode = server.options.GetEncryptionMode(); - - Database::DbLoginServerAccount - login_server_admin = server.db->GetLoginServerAccountByAccountName( - in_account_username, - source_loginserver - ); - - if (!login_server_admin.loaded) { + auto a = LoginAccountsRepository::GetAccountFromContext(database, c); + if (!a.id) { LogError( - "account [{0}] source_loginserver [{1}] not found!", - in_account_username, - source_loginserver + "account [{}] source_loginserver [{}] not found!", + c.username, + c.source_loginserver ); - return false; + return 0; } - bool validated_credentials = eqcrypt_verify_hash( - in_account_username, - in_account_password, - login_server_admin.account_password, - mode - ); - + bool validated_credentials = eqcrypt_verify_hash(c.username, c.password, a.account_password, mode); if (!validated_credentials) { LogError( - "account [{0}] source_loginserver [{1}] invalid credentials!", - in_account_username, - source_loginserver + "account [{}] source_loginserver [{}] invalid credentials!", + c.username, + c.source_loginserver ); return 0; } LogInfo( - "account [{0}] source_loginserver [{1}] credentials validated success!", - in_account_username, - source_loginserver + "account [{}] source_loginserver [{}] credentials validated success!", + c.username, + c.source_loginserver ); - return login_server_admin.id; + return a.id; } - -/** - * @param in_account_username - * @param in_account_password - * @return - */ -bool AccountManagement::UpdateLoginserverUserCredentials( - const std::string &in_account_username, - const std::string &in_account_password, - const std::string &source_loginserver -) +bool AccountManagement::UpdateLoginserverUserCredentials(LoginAccountContext c) { - auto mode = server.options.GetEncryptionMode(); - - Database::DbLoginServerAccount - login_server_account = server.db->GetLoginServerAccountByAccountName( - in_account_username, - source_loginserver - ); - - if (!login_server_account.loaded) { + auto a = LoginAccountsRepository::GetAccountFromContext(database, c); + if (!a.id) { LogError( - "account [{0}] source_loginserver [{1}] not found!", - in_account_username, - source_loginserver + "account [{}] source_loginserver [{}] not found!", + c.username, + c.source_loginserver ); return false; } - server.db->UpdateLoginserverAccountPasswordHash( - in_account_username, - source_loginserver, - eqcrypt_hash( - in_account_username, - in_account_password, - mode - ) - ); + LoginAccountsRepository::UpdateAccountPassword(database, a, c.password); LogInfo( - "account [{0}] source_loginserver [{1}] credentials updated!", - in_account_username, - source_loginserver + "account [{}] source_loginserver [{}] credentials updated!", + c.username, + c.source_loginserver ); return true; } -/** - * @param in_account_username - * @param in_account_password - */ -bool AccountManagement::UpdateLoginserverWorldAdminAccountPasswordByName( - const std::string &in_account_username, - const std::string &in_account_password -) +bool AccountManagement::UpdateLoginserverWorldAdminAccountPasswordByName(LoginAccountContext c) { auto mode = server.options.GetEncryptionMode(); - auto hash = eqcrypt_hash(in_account_username, in_account_password, mode); - - bool updated_account = server.db->UpdateLoginWorldAdminAccountPasswordByUsername( - in_account_username, - hash + auto hash = eqcrypt_hash( + c.username, + c.password, + mode ); + auto a = LoginServerAdminsRepository::GetByName(database, c.username); + if (!a.id) { + LogError( + "account_name [{}] not found!", + c.username + ); + + return false; + } + + a.account_password = hash; + auto updated_account = LoginServerAdminsRepository::UpdateOne(database, a); + LogInfo( - "[{}] account_name [{}] status [{}]", - __func__, - in_account_username, + "account_name [{}] status [{}]", + c.username, (updated_account ? "success" : "failed") ); @@ -262,33 +117,25 @@ bool AccountManagement::UpdateLoginserverWorldAdminAccountPasswordByName( constexpr int REQUEST_TIMEOUT_MS = 1500; -/** - * @param in_account_username - * @param in_account_password - * @return - */ -uint32 AccountManagement::CheckExternalLoginserverUserCredentials( - const std::string &in_account_username, - const std::string &in_account_password -) +uint64 AccountManagement::CheckExternalLoginserverUserCredentials(LoginAccountContext c) { auto res = task_runner.Enqueue( [&]() -> uint32 { bool running = true; uint32 ret = 0; - EQ::Net::DaybreakConnectionManager mgr; - std::shared_ptr c; + EQ::Net::ReliableStreamConnectionManager mgr; + std::shared_ptr conn; mgr.OnNewConnection( - [&](std::shared_ptr connection) { - c = connection; + [&](std::shared_ptr connection) { + conn = connection; } ); mgr.OnConnectionStateChange( [&]( - std::shared_ptr conn, + std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to ) { @@ -296,7 +143,7 @@ uint32 AccountManagement::CheckExternalLoginserverUserCredentials( EQ::Net::DynamicPacket p; p.PutUInt16(0, 1); //OP_SessionReady p.PutUInt32(2, 2); - c->QueuePacket(p); + conn->QueuePacket(p); } else if (EQ::Net::StatusDisconnected == to) { running = false; @@ -305,18 +152,17 @@ uint32 AccountManagement::CheckExternalLoginserverUserCredentials( ); mgr.OnPacketRecv( - [&](std::shared_ptr conn, const EQ::Net::Packet &p) { + [&](std::shared_ptr conn, const EQ::Net::Packet &p) { auto opcode = p.GetUInt16(0); switch (opcode) { case 0x0017: //OP_ChatMessage { - size_t buffer_len = - in_account_username.length() + in_account_password.length() + 2; + size_t buffer_len = c.username.length() + c.password.length() + 2; std::unique_ptr buffer(new char[buffer_len]); - strcpy(&buffer[0], in_account_username.c_str()); - strcpy(&buffer[in_account_username.length() + 1], in_account_password.c_str()); + strcpy(&buffer[0], c.username.c_str()); + strcpy(&buffer[c.username.length() + 1], c.password.c_str()); size_t encrypted_len = buffer_len; @@ -330,11 +176,11 @@ uint32 AccountManagement::CheckExternalLoginserverUserCredentials( p.PutUInt32(2, 3); eqcrypt_block(&buffer[0], buffer_len, (char *) p.Data() + 12, true); - c->QueuePacket(p); + conn->QueuePacket(p); break; } case 0x0018: { - auto encrypt_size = p.Length() - 12; + auto encrypt_size = p.Length() - 12; if (encrypt_size % 8 > 0) { encrypt_size = (encrypt_size / 8) * 8; } @@ -394,28 +240,28 @@ uint32 AccountManagement::CheckExternalLoginserverUserCredentials( return res.get(); } -uint32 AccountManagement::HealthCheckUserLogin() +uint64 AccountManagement::HealthCheckUserLogin() { std::string in_account_username = "healthcheckuser"; std::string in_account_password = "healthcheckpassword"; auto res = task_runner.Enqueue( - [&]() -> uint32 { + [&]() -> uint64 { bool running = true; - uint32 ret = 0; + uint64 ret = 0; - EQ::Net::DaybreakConnectionManager mgr; - std::shared_ptr c; + EQ::Net::ReliableStreamConnectionManager mgr; + std::shared_ptr c; mgr.OnNewConnection( - [&](std::shared_ptr connection) { + [&](std::shared_ptr connection) { c = connection; } ); mgr.OnConnectionStateChange( [&]( - std::shared_ptr conn, + std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to ) { @@ -432,7 +278,7 @@ uint32 AccountManagement::HealthCheckUserLogin() ); mgr.OnPacketRecv( - [&](std::shared_ptr conn, const EQ::Net::Packet &p) { + [&](std::shared_ptr conn, const EQ::Net::Packet &p) { auto opcode = p.GetUInt16(0); switch (opcode) { case 0x0017: //OP_ChatMessage @@ -461,7 +307,7 @@ uint32 AccountManagement::HealthCheckUserLogin() break; } case 0x0018: { - auto encrypt_size = p.Length() - 12; + auto encrypt_size = p.Length() - 12; if (encrypt_size % 8 > 0) { encrypt_size = (encrypt_size / 8) * 8; } @@ -487,11 +333,11 @@ uint32 AccountManagement::HealthCheckUserLogin() mgr.Connect("127.0.0.1", 5999); std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); - auto &loop = EQ::EventLoop::Get(); + auto &loop = EQ::EventLoop::Get(); while (running) { std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); if (std::chrono::duration_cast(end - begin).count() > 2000) { - ret = 0; + ret = 0; running = false; } @@ -504,3 +350,59 @@ uint32 AccountManagement::HealthCheckUserLogin() return res.get(); } + +bool AccountManagement::CreateLoginserverWorldAdminAccount( + const std::string &username, + const std::string &password, + const std::string &email, + const std::string &first_name, + const std::string &last_name, + const std::string &ip_address +) +{ + auto mode = server.options.GetEncryptionMode(); + auto hash = eqcrypt_hash(username, password, mode); + + LogInfo( + "Attempting to create world admin account | username [{}] encryption algorithm [{}] ({})", + username, + GetEncryptionByModeId(mode), + mode + ); + + auto a = LoginServerAdminsRepository::GetByName(database, username); + if (a.id > 0) { + LogWarning( + "Attempting to create world admin account for user [{}] but already exists!", + username + ); + + return false; + } + + a = LoginServerAdminsRepository::NewEntity(); + a.account_name = username; + a.account_password = hash; + a.first_name = first_name; + a.last_name = last_name; + a.email = email; + a.registration_ip_address = ip_address; + a.registration_date = std::time(nullptr); + + a = LoginServerAdminsRepository::InsertOne(database, a); + + if (a.id > 0) { + LogInfo( + "Account creation success for user [{}] encryption algorithm [{}] ({}) new admin id [{}]", + username, + GetEncryptionByModeId(mode), + mode, + a.id + ); + return true; + } + + LogError("Failed to create world admin account account for user [{}] !", username); + + return false; +} diff --git a/loginserver/account_management.h b/loginserver/account_management.h index 190c26437..e047e4468 100644 --- a/loginserver/account_management.h +++ b/loginserver/account_management.h @@ -3,32 +3,22 @@ #include "iostream" #include "../common/types.h" +#include "login_types.h" +#include "encryption.h" +#include "login_server.h" + +extern LoginServer server; +extern Database database; class AccountManagement { public: + static uint64 CreateLoginServerAccount(LoginAccountContext c); + static uint64 CheckLoginserverUserCredentials(LoginAccountContext c); + static bool UpdateLoginserverUserCredentials(LoginAccountContext c); + static uint64 CheckExternalLoginserverUserCredentials(LoginAccountContext c); + static bool UpdateLoginserverWorldAdminAccountPasswordByName(LoginAccountContext c); + static uint64 HealthCheckUserLogin(); - /** - * @param username - * @param password - * @param email - * @param source_loginserver - * @param login_account_id - * @return - */ - static int32 CreateLoginServerAccount( - std::string username, - std::string password, - std::string email = "", - const std::string &source_loginserver = "local", - uint32 login_account_id = 0 - ); - - /** - * @param username - * @param password - * @param email - * @return - */ static bool CreateLoginserverWorldAdminAccount( const std::string &username, const std::string &password, @@ -37,50 +27,6 @@ public: const std::string &last_name = "", const std::string &ip_address = "" ); - - /** - * @param in_account_username - * @param in_account_password - * @return - */ - static uint32 CheckLoginserverUserCredentials( - const std::string &in_account_username, - const std::string &in_account_password, - const std::string &source_loginserver = "local" - ); - - /** - * @param in_account_username - * @param in_account_password - * @return - */ - static bool UpdateLoginserverUserCredentials( - const std::string &in_account_username, - const std::string &in_account_password, - const std::string &source_loginserver = "local" - ); - - /** - * @param in_account_username - * @param in_account_password - * @return - */ - static uint32 CheckExternalLoginserverUserCredentials( - const std::string &in_account_username, - const std::string &in_account_password - ); - - /** - * @param in_account_username - * @param in_account_password - * @return - */ - static bool UpdateLoginserverWorldAdminAccountPasswordByName( - const std::string &in_account_username, - const std::string &in_account_password - ); - - static uint32 HealthCheckUserLogin(); }; diff --git a/loginserver/client.cpp b/loginserver/client.cpp index 62f7fb1f2..754c83ab5 100644 --- a/loginserver/client.cpp +++ b/loginserver/client.cpp @@ -1,25 +1,18 @@ #include "client.h" #include "login_server.h" -#include "../common/misc_functions.h" -#include "../common/eqemu_logsys.h" -#include "../common/strings.h" #include "encryption.h" #include "account_management.h" extern LoginServer server; -/** - * @param c - * @param v - */ Client::Client(std::shared_ptr c, LSClientVersion v) { - m_connection = c; - m_client_version = v; - m_client_status = cs_not_sent_session_ready; - m_account_id = 0; - m_play_server_id = 0; - m_play_sequence_id = 0; + m_connection = c; + m_client_version = v; + m_client_status = cs_not_sent_session_ready; + m_account_id = 0; + m_selected_play_server_id = 0; + m_play_sequence_id = 0; } bool Client::Process() @@ -32,7 +25,7 @@ bool Client::Process() OpcodeManager::EmuToName(app->GetOpcode()), o->EmuToEQ(app->GetOpcode()) == 0 ? app->GetProtocolOpcode() : o->EmuToEQ(app->GetOpcode()), app->Size(), - (LogSys.IsLogEnabled(Logs::Detail, Logs::PacketClientServer) ? DumpPacketToString(app) : "") + (EQEmuLogSys::Instance()->IsLogEnabled(Logs::Detail, Logs::PacketClientServer) ? DumpPacketToString(app) : "") ); if (m_client_status == cs_failed_to_login) { @@ -43,8 +36,8 @@ bool Client::Process() switch (app->GetOpcode()) { case OP_SessionReady: { - LogInfo("Session ready received from client account {}", GetClientDescription()); - Handle_SessionReady((const char *) app->pBuffer, app->Size()); + LogInfo("Session ready received from client account {}", GetClientLoggingDescription()); + HandleSessionReady((const char *) app->pBuffer, app->Size()); break; } case OP_Login: { @@ -53,9 +46,9 @@ bool Client::Process() break; } - LogInfo("Login received from client {}", GetClientDescription()); + LogInfo("Login received from client {}", GetClientLoggingDescription()); - Handle_Login((const char *) app->pBuffer, app->Size()); + HandleLogin((const char *) app->pBuffer, app->Size()); break; } case OP_ServerListRequest: { @@ -64,18 +57,18 @@ bool Client::Process() break; } - LogInfo("Server list request received from client {}", GetClientDescription()); + LogInfo("Server list request received from client {}", GetClientLoggingDescription()); SendServerListPacket(*(uint32_t *) app->pBuffer); break; } case OP_PlayEverquestRequest: { - if (app->Size() < sizeof(PlayEverquestRequest_Struct)) { + if (app->Size() < sizeof(PlayEverquestRequest)) { LogError("Play received but it is too small, discarding"); break; } - Handle_Play((const char *) app->pBuffer); + SendPlayToWorld((const char *) app->pBuffer); break; } } @@ -87,13 +80,7 @@ bool Client::Process() return true; } -/** - * Sends our reply to session ready packet - * - * @param data - * @param size - */ -void Client::Handle_SessionReady(const char *data, unsigned int size) +void Client::HandleSessionReady(const char *data, unsigned int size) { if (m_client_status != cs_not_sent_session_ready) { LogError("Session ready received again after already being received"); @@ -107,11 +94,8 @@ void Client::Handle_SessionReady(const char *data, unsigned int size) m_client_status = cs_waiting_for_login; - /** - * The packets are identical between the two versions - */ - auto *outapp = new EQApplicationPacket(OP_ChatMessage, sizeof(LoginHandShakeReply_Struct)); - auto buf = reinterpret_cast(outapp->pBuffer); + auto *outapp = new EQApplicationPacket(OP_ChatMessage, sizeof(LoginHandShakeReply)); + auto buf = reinterpret_cast(outapp->pBuffer); buf->base_header.sequence = 0x02; buf->base_reply.success = true; buf->base_reply.error_str_id = 0x65; // 101 "No Error" @@ -120,13 +104,7 @@ void Client::Handle_SessionReady(const char *data, unsigned int size) delete outapp; } -/** - * Verifies login and send a reply - * - * @param data - * @param size - */ -void Client::Handle_Login(const char *data, unsigned int size) +void Client::HandleLogin(const char *data, unsigned int size) { if (m_client_status != cs_waiting_for_login) { LogError("Login received after already having logged in"); @@ -134,41 +112,37 @@ void Client::Handle_Login(const char *data, unsigned int size) } // login user/pass are variable length after unencrypted opcode and base message header (size includes opcode) - constexpr int header_size = sizeof(uint16_t) + sizeof(LoginBaseMessage_Struct); - int data_size = size - header_size; + constexpr int header_size = sizeof(uint16_t) + sizeof(LoginBaseMessage); + int data_size = size - header_size; if (size <= header_size) { - LogError("Login received packet of size: {0}, this would cause a buffer overflow, discarding", size); + LogError("Login received packet of size: {}, this would cause a buffer overflow, discarding", size); return; } if (data_size % 8 != 0) { - LogError("Login received packet of size: {0}, this would cause a block corruption, discarding", size); + LogError("Login received packet of size: {}, this would cause a block corruption, discarding", size); return; } - char *login_packet_buffer = nullptr; - unsigned int db_account_id = 0; std::string db_loginserver = "local"; - if (server.options.CanAutoLinkAccounts()) { + if (std::getenv("LSPX")) { db_loginserver = "eqemu"; } - std::string db_account_password_hash; - std::string outbuffer; outbuffer.resize(data_size); - if (outbuffer.length() == 0) { + if (outbuffer.empty()) { LogError("Corrupt buffer sent to server, no length"); return; } // data starts at base message header (opcode not included) - auto r = eqcrypt_block(data + sizeof(LoginBaseMessage_Struct), data_size, &outbuffer[0], 0); + auto r = eqcrypt_block(data + sizeof(LoginBaseMessage), data_size, &outbuffer[0], false); if (r == nullptr) { LogError("Failed to decrypt eqcrypt block"); return; @@ -182,131 +156,114 @@ void Client::Handle_Login(const char *data, unsigned int size) return; } +// std::cout << "User: " << user << std::endl; + // only need to copy the base header for reply options, ignore login info - memcpy(&m_llrs, data, sizeof(LoginBaseMessage_Struct)); + memcpy(&m_login_base_message, data, sizeof(LoginBaseMessage)); - bool result = false; - if (outbuffer[0] == 0 && outbuffer[1] == 0) { +// std::cout << "Seq: " << m_login_base_message.sequence << std::endl; +// std::cout << "compressed: " << m_login_base_message.compressed << std::endl; +// std::cout << "encrypt_type: " << m_login_base_message.encrypt_type << std::endl; +// std::cout << "unk3: " << m_login_base_message.unk3 << std::endl; + + bool login_success = false; + bool token_login = outbuffer[0] == 0 && outbuffer[1] == 0; + if (token_login) { if (server.options.IsTokenLoginAllowed()) { - cred = (&outbuffer[2 + user.length()]); - result = server.db->GetLoginTokenDataFromToken( - cred, - m_connection->GetRemoteAddr(), - db_account_id, - db_loginserver, - user - ); + cred = (&outbuffer[2 + user.length()]); + // todo: implement token login + // SELECT login_server, username, account_id FROM login_tickets WHERE expires > NOW() AND id='{}' AND ip_address='{}' LIMIT 1 +// login_success ? DoSuccessfulLogin(user, db_account_id, db_loginserver) : SendFailedLogin(); + SendFailedLogin(); } + + return; } - else { - if (server.options.IsPasswordLoginAllowed()) { - cred = (&outbuffer[1 + user.length()]); - auto components = Strings::Split(user, ':'); - if (components.size() == 2) { - db_loginserver = components[0]; - user = components[1]; - } - // health checks - if (ProcessHealthCheck(user)) { - DoFailedLogin(); - return; - } + // normal login + cred = (&outbuffer[1 + user.length()]); + auto components = Strings::Split(user, ':'); + if (components.size() == 2) { + db_loginserver = components[0]; + user = components[1]; + } - LogInfo( - "Attempting password based login [{0}] login [{1}]", - user, - db_loginserver - ); + // health checks + if (ProcessHealthCheck(user)) { + SendFailedLogin(); + return; + } - ParseAccountString(user, user, db_loginserver); + LogInfo( + "Attempting password based login [{}] login [{}]", + user, + db_loginserver + ); - if (server.db->GetLoginDataFromAccountInfo(user, db_loginserver, db_account_password_hash, db_account_id)) { - result = VerifyLoginHash(user, db_loginserver, cred, db_account_password_hash); + ParseAccountString(user, user, db_loginserver); -#ifdef LSPX - // if user updated their password on the login server, update it here by validating their credentials with the login server - if (!result && db_loginserver == "eqemu") { - uint32 account_id = AccountManagement::CheckExternalLoginserverUserCredentials(user, cred); - if (account_id > 0) { - auto encryption_mode = server.options.GetEncryptionMode(); - server.db->UpdateLoginserverAccountPasswordHash( - user, - db_loginserver, - eqcrypt_hash(user, cred, encryption_mode) - ); - LogInfo("Updating eqemu account [{}] password hash", account_id); - result = true; - } + LoginAccountContext c = {}; + c.username = user; + c.password = cred; + c.source_loginserver = db_loginserver; + + auto a = LoginAccountsRepository::GetAccountFromContext(database, c); + if (a.id > 0) { + login_success = VerifyAndUpdateLoginHash(c, a); + + // if user updated their password on the login server, update it here by validating their credentials with the login server + if (std::getenv("LSPX") && !login_success && db_loginserver == "eqemu") { + LogInfo("LSPX | Attempting login account via [{}]", db_loginserver); + uint32 account_id = AccountManagement::CheckExternalLoginserverUserCredentials(c); + LogInfo("LSPX | External login account id [{}]", account_id); + if (account_id > 0) { + auto updated_account = LoginAccountsRepository::UpdateAccountPassword(database, a, cred); + if (!updated_account.id) { + LogError("Failed to update eqemu account [{}] password hash", account_id); + SendFailedLogin(); + return; } -#endif - - LogDebug("Success [{0}]", (result ? "true" : "false")); - } - else { - m_client_status = cs_creating_account; - AttemptLoginAccountCreation(user, cred, db_loginserver); + LogInfo("Updating eqemu account [{}] password hash", account_id); + DoSuccessfulLogin(updated_account); return; } } + + LogInfo("Successful login [{}]", (login_success ? "true" : "false")); + login_success ? DoSuccessfulLogin(a) : SendFailedLogin(); + return; } - /** - * Login accepted - */ - if (result) { - LogInfo( - "login [{0}] user [{1}] Login succeeded", - db_loginserver, - user - ); - - DoSuccessfulLogin(user, db_account_id, db_loginserver); - } - else { - LogInfo( - "login [{0}] user [{1}] Login failed", - db_loginserver, - user - ); - - DoFailedLogin(); - } + // if we are here, the account does not exist + m_client_status = cs_creating_account; + AttemptLoginAccountCreation(c); } -/** - * Sends a packet to the requested server to see if the client is allowed or not - * - * @param data - */ -void Client::Handle_Play(const char *data) +void Client::SendPlayToWorld(const char *data) { if (m_client_status != cs_logged_in) { LogError("Client sent a play request when they were not logged in, discarding"); return; } - const auto *play = (const PlayEverquestRequest_Struct *) data; + const auto *play = (const PlayEverquestRequest *) data; auto server_id_in = (unsigned int) play->server_number; auto sequence_in = (unsigned int) play->base_header.sequence; LogInfo( - "[Handle_Play] Play received from client [{}] server number [{}] sequence [{}]", + "[SendPlayToWorld] Play received from client [{}] server number [{}] sequence [{}]", GetAccountName(), server_id_in, sequence_in ); - m_play_server_id = (unsigned int) play->server_number; - m_play_sequence_id = sequence_in; - m_play_server_id = server_id_in; - server.server_manager->SendUserToWorldRequest(server_id_in, m_account_id, m_loginserver_name); + m_selected_play_server_id = (unsigned int) play->server_number; + m_play_sequence_id = sequence_in; + m_selected_play_server_id = server_id_in; + server.server_manager->SendUserLoginToWorldRequest(server_id_in, m_account_id, m_loginserver_name); } -/** - * @param seq - */ void Client::SendServerListPacket(uint32 seq) { auto app = server.server_manager->CreateServerListPacket(this, seq); @@ -316,11 +273,11 @@ void Client::SendServerListPacket(uint32 seq) void Client::SendPlayResponse(EQApplicationPacket *outapp) { - LogInfo("Sending play response for {}", GetClientDescription()); + LogInfo("Sending play response for {}", GetClientLoggingDescription()); m_connection->QueuePacket(outapp); } -void Client::GenerateKey() +void Client::GenerateRandomLoginKey() { m_key.clear(); int count = 0; @@ -339,229 +296,193 @@ void Client::GenerateKey() } } -/** - * @param user - * @param pass - * @param loginserver - */ -void Client::AttemptLoginAccountCreation( - const std::string &user, - const std::string &pass, - const std::string &loginserver -) +void Client::AttemptLoginAccountCreation(LoginAccountContext c) { - LogInfo("user [{}] loginserver [{}]", user, loginserver); + LogInfo("user [{}] loginserver [{}]", c.username, c.source_loginserver); -#ifdef LSPX - if (loginserver == "eqemu") { - LogInfo("Attempting login account creation via '{0}'", loginserver); - - if (!server.options.CanAutoLinkAccounts()) { - LogInfo("CanAutoLinkAccounts disabled - sending failed login"); - DoFailedLogin(); - return; - } - - - uint32 account_id = AccountManagement::CheckExternalLoginserverUserCredentials( - user, - pass - ); + if (std::getenv("LSPX") && c.source_loginserver == "eqemu") { + LogInfo("LSPX | Attempting login account creation via [{}]", c.source_loginserver); + uint32 account_id = AccountManagement::CheckExternalLoginserverUserCredentials(c); + c.login_account_id = account_id; if (account_id > 0) { - LogInfo("Found and creating eqemu account [{}]", account_id); - CreateEQEmuAccount(user, pass, account_id); + LogInfo("LSPX | Found and creating eqemu account [{}]", account_id); + auto a = LoginAccountsRepository::CreateAccountFromContext(database, c); + if (a.id > 0) { + DoSuccessfulLogin(a); + return; + } + } + + LogInfo("LSPX | External authentication failed for user [{}]", c.username); + + SendFailedLogin(); + return; + } + + if (server.options.CanAutoCreateAccounts() && c.source_loginserver == "local") { + LogInfo("CanAutoCreateAccounts enabled, attempting to crate account [{}]", c.username); + auto a = LoginAccountsRepository::CreateAccountFromContext(database, c); + if (a.id > 0) { + DoSuccessfulLogin(a); return; } - DoFailedLogin(); - return; - } -#endif - - if (server.options.CanAutoCreateAccounts() && loginserver == "local") { - LogInfo("CanAutoCreateAccounts enabled, attempting to creating account [{0}]", user); - CreateLocalAccount(user, pass); return; } - DoFailedLogin(); + SendFailedLogin(); } -void Client::DoFailedLogin() +void Client::SendFailedLogin() { - m_stored_user.clear(); - m_stored_pass.clear(); + m_stored_username.clear(); + m_stored_password.clear(); // unencrypted - LoginBaseMessage_Struct base_header{}; - base_header.sequence = m_llrs.sequence; // login (3) - base_header.encrypt_type = m_llrs.encrypt_type; + LoginBaseMessage h{}; + h.sequence = m_login_base_message.sequence; // login (3) + h.encrypt_type = m_login_base_message.encrypt_type; // encrypted - PlayerLoginReply_Struct login_reply{}; - login_reply.base_reply.success = false; - login_reply.base_reply.error_str_id = 105; // Error - The username and/or password were not valid + PlayerLoginReply r{}; + r.base_reply.success = false; + r.base_reply.error_str_id = 105; // Error - The username and/or password were not valid char encrypted_buffer[80] = {0}; - auto rc = eqcrypt_block((const char*)&login_reply, sizeof(login_reply), encrypted_buffer, 1); + auto rc = eqcrypt_block((const char *) &r, sizeof(r), encrypted_buffer, 1); if (rc == nullptr) { LogDebug("Failed to encrypt eqcrypt block for failed login"); } - constexpr int outsize = sizeof(LoginBaseMessage_Struct) + sizeof(encrypted_buffer); + constexpr int outsize = sizeof(LoginBaseMessage) + sizeof(encrypted_buffer); EQApplicationPacket outapp(OP_LoginAccepted, outsize); - outapp.WriteData(&base_header, sizeof(base_header)); + outapp.WriteData(&h, sizeof(h)); outapp.WriteData(&encrypted_buffer, sizeof(encrypted_buffer)); m_connection->QueuePacket(&outapp); m_client_status = cs_failed_to_login; } -/** - * Verifies a login hash, will also attempt to update a login hash if needed - * - * @param account_username - * @param source_loginserver - * @param account_password - * @param password_hash - * @return - */ -bool Client::VerifyLoginHash( - const std::string &account_username, - const std::string &source_loginserver, - const std::string &account_password, - const std::string &password_hash -) +bool Client::VerifyAndUpdateLoginHash(LoginAccountContext c, const LoginAccountsRepository::LoginAccounts &a) { auto encryption_mode = server.options.GetEncryptionMode(); - if (eqcrypt_verify_hash(account_username, account_password, password_hash, encryption_mode)) { + if (eqcrypt_verify_hash(a.account_name, c.password, a.account_password, encryption_mode)) { return true; } - else { - if (server.options.IsUpdatingInsecurePasswords()) { - if (encryption_mode < EncryptionModeArgon2) { - encryption_mode = EncryptionModeArgon2; - } - uint32 insecure_source_encryption_mode = 0; - if (password_hash.length() == CryptoHash::md5_hash_length) { - for (int i = EncryptionModeMD5; i <= EncryptionModeMD5Triple; ++i) { - if (i != encryption_mode && - eqcrypt_verify_hash(account_username, account_password, password_hash, i)) { - insecure_source_encryption_mode = i; - } - } - } - else if (password_hash.length() == CryptoHash::sha1_hash_length && insecure_source_encryption_mode == 0) { - for (int i = EncryptionModeSHA; i <= EncryptionModeSHATriple; ++i) { - if (i != encryption_mode && - eqcrypt_verify_hash(account_username, account_password, password_hash, i)) { - insecure_source_encryption_mode = i; - } - } - } - else if (password_hash.length() == CryptoHash::sha512_hash_length && insecure_source_encryption_mode == 0) { - for (int i = EncryptionModeSHA512; i <= EncryptionModeSHA512Triple; ++i) { - if (i != encryption_mode && - eqcrypt_verify_hash(account_username, account_password, password_hash, i)) { - insecure_source_encryption_mode = i; - } - } - } + if (encryption_mode < EncryptionModeArgon2) { + encryption_mode = EncryptionModeArgon2; + } - if (insecure_source_encryption_mode > 0) { - LogInfo( - "[{}] Updated insecure password user [{}] loginserver [{}] from mode [{}] ({}) to mode [{}] ({})", - __func__, - account_username, - source_loginserver, - GetEncryptionByModeId(insecure_source_encryption_mode), - insecure_source_encryption_mode, - GetEncryptionByModeId(encryption_mode), - encryption_mode - ); + uint32 insecure_source_encryption_mode = 0; - server.db->UpdateLoginserverAccountPasswordHash( - account_username, - source_loginserver, - eqcrypt_hash( - account_username, - account_password, - encryption_mode - ) - ); - - return true; + auto verify_encryption_mode = [&](int start, int end) { + for (int i = start; i <= end; ++i) { + if (i != encryption_mode && eqcrypt_verify_hash(a.account_name, c.password, a.account_password, i)) { + insecure_source_encryption_mode = i; } } + }; + + switch (a.account_password.length()) { + case CryptoHash::md5_hash_length: + verify_encryption_mode(EncryptionModeMD5, EncryptionModeMD5Triple); + break; + case CryptoHash::sha1_hash_length: + if (insecure_source_encryption_mode == 0) { + verify_encryption_mode(EncryptionModeSHA, EncryptionModeSHATriple); + } + break; + case CryptoHash::sha512_hash_length: + if (insecure_source_encryption_mode == 0) { + verify_encryption_mode(EncryptionModeSHA512, EncryptionModeSHA512Triple); + } + break; + } + + if (insecure_source_encryption_mode > 0) { + LogInfo( + "Updated insecure password user [{}] loginserver [{}] from mode [{}] ({}) to mode [{}] ({})", + c.username, + c.source_loginserver, + GetEncryptionByModeId(insecure_source_encryption_mode), + insecure_source_encryption_mode, + GetEncryptionByModeId(encryption_mode), + encryption_mode + ); + + LoginAccountsRepository::UpdateAccountPassword(database, a, c.password); + + return true; } return false; } -/** - * @param in_account_name - * @param db_account_id - * @param db_loginserver - */ -void Client::DoSuccessfulLogin( - const std::string& in_account_name, - int db_account_id, - const std::string &db_loginserver -) +void Client::DoSuccessfulLogin(LoginAccountsRepository::LoginAccounts &a) { - m_stored_user.clear(); - m_stored_pass.clear(); + m_stored_username.clear(); + m_stored_password.clear(); - server.client_manager->RemoveExistingClient(db_account_id, db_loginserver); + LogInfo( + "Successful login for user id [{}] account name [{}] login server [{}]", + a.id, + a.account_name, + a.source_loginserver + ); + + server.client_manager->RemoveExistingClient(a.id, a.source_loginserver); in_addr in{}; in.s_addr = m_connection->GetRemoteIP(); - server.db->UpdateLSAccountData(db_account_id, std::string(inet_ntoa(in))); - GenerateKey(); + a.last_ip_address = std::string(inet_ntoa(in)); + LoginAccountsRepository::UpdateOne(database, a); - m_account_id = db_account_id; - m_account_name = in_account_name; - m_loginserver_name = db_loginserver; + GenerateRandomLoginKey(); + + m_account_id = a.id; + m_account_name = a.account_name; + m_loginserver_name = a.source_loginserver; // unencrypted - LoginBaseMessage_Struct base_header{}; - base_header.sequence = m_llrs.sequence; - base_header.compressed = false; - base_header.encrypt_type = m_llrs.encrypt_type; - base_header.unk3 = m_llrs.unk3; + LoginBaseMessage h{}; + h.sequence = m_login_base_message.sequence; + h.compressed = false; + h.encrypt_type = m_login_base_message.encrypt_type; + h.unk3 = m_login_base_message.unk3; // not serializing any of the variable length strings so just use struct directly - PlayerLoginReply_Struct login_reply{}; - login_reply.base_reply.success = true; - login_reply.base_reply.error_str_id = 101; // No Error - login_reply.unk1 = 0; - login_reply.unk2 = 0; - login_reply.lsid = db_account_id; - login_reply.failed_attempts = 0; - login_reply.show_player_count = server.options.IsShowPlayerCountEnabled(); - login_reply.offer_min_days = 99; - login_reply.offer_min_views = -1; - login_reply.offer_cooldown_minutes = 0; - login_reply.web_offer_number = 0; - login_reply.web_offer_min_days = 99; - login_reply.web_offer_min_views = -1; - login_reply.web_offer_cooldown_minutes = 0; - memcpy(login_reply.key, m_key.c_str(), m_key.size()); + PlayerLoginReply r{}; + r.base_reply.success = true; + r.base_reply.error_str_id = 101; // No Error + r.unk1 = 0; + r.unk2 = 0; + r.lsid = a.id; + r.failed_attempts = 0; + r.show_player_count = server.options.IsShowPlayerCountEnabled(); + r.offer_min_days = 99; + r.offer_min_views = -1; + r.offer_cooldown_minutes = 0; + r.web_offer_number = 0; + r.web_offer_min_days = 99; + r.web_offer_min_views = -1; + r.web_offer_cooldown_minutes = 0; + memcpy(r.key, m_key.c_str(), m_key.size()); - SendExpansionPacketData(login_reply); + SendExpansionPacketData(r); char encrypted_buffer[80] = {0}; - auto rc = eqcrypt_block((const char*)&login_reply, sizeof(login_reply), encrypted_buffer, 1); + + auto rc = eqcrypt_block((const char *) &r, sizeof(r), encrypted_buffer, 1); if (rc == nullptr) { LogDebug("Failed to encrypt eqcrypt block"); } - constexpr int outsize = sizeof(LoginBaseMessage_Struct) + sizeof(encrypted_buffer); - auto outapp = std::make_unique(OP_LoginAccepted, outsize); - outapp->WriteData(&base_header, sizeof(base_header)); + constexpr int outsize = sizeof(LoginBaseMessage) + sizeof(encrypted_buffer); + auto outapp = std::make_unique(OP_LoginAccepted, outsize); + outapp->WriteData(&h, sizeof(h)); outapp->WriteData(&encrypted_buffer, sizeof(encrypted_buffer)); m_connection->QueuePacket(outapp.get()); @@ -569,19 +490,21 @@ void Client::DoSuccessfulLogin( m_client_status = cs_logged_in; } -void Client::SendExpansionPacketData(PlayerLoginReply_Struct& plrs) +void Client::SendExpansionPacketData(PlayerLoginReply &plrs) { SerializeBuffer buf; //from eqlsstr_us.txt id of each expansion, excluding 'Everquest' - int ExpansionLookup[20] = { 3007, 3008, 3009, 3010, 3012, - 3014, 3031, 3033, 3036, 3040, - 3045, 3046, 3047, 3514, 3516, - 3518, 3520, 3522, 3524 }; + int ExpansionLookup[20] = { + 3007, 3008, 3009, 3010, 3012, + 3014, 3031, 3033, 3036, 3040, + 3045, 3046, 3047, 3514, 3516, + 3518, 3520, 3522, 3524 + }; if (server.options.IsDisplayExpansions()) { - int32_t expansion = server.options.GetMaxExpansions(); + int32_t expansion = server.options.GetMaxExpansions(); int32_t owned_expansion = (expansion << 1) | 1; if (m_client_version == cv_sod) { @@ -593,15 +516,14 @@ void Client::SendExpansionPacketData(PlayerLoginReply_Struct& plrs) buf.WriteInt32(19); //number of expansions to include in packet //generate expansion data - for (int i = 0; i < 19; i++) - { - buf.WriteInt32(i); //sequenctial number - buf.WriteInt32((expansion & (1 << i)) == (1 << i) ? 0x01 : 0x00); //1 own 0 not own + for (int i = 0; i < 19; i++) { + buf.WriteInt32(i); //sequenctial number + buf.WriteInt32((expansion & (1 << i)) == (1 << i) ? 0x01 : 0x00); //1 own 0 not own buf.WriteInt8(0x00); - buf.WriteInt32(ExpansionLookup[i]); //from eqlsstr_us.txt - buf.WriteInt32(0x179E); //from eqlsstr_us.txt for buttons/order - buf.WriteInt32(0xFFFFFFFF); //end identification - buf.WriteInt8(0x0); //force order window to appear 1 appear 0 not appear + buf.WriteInt32(ExpansionLookup[i]); //from eqlsstr_us.txt + buf.WriteInt32(0x179E); //from eqlsstr_us.txt for buttons/order + buf.WriteInt32(0xFFFFFFFF); //end identification + buf.WriteInt8(0x0); //force order window to appear 1 appear 0 not appear buf.WriteInt8(0x0); buf.WriteInt32(0x0000); buf.WriteInt32(0x0000); @@ -612,79 +534,21 @@ void Client::SendExpansionPacketData(PlayerLoginReply_Struct& plrs) m_connection->QueuePacket(out.get()); } - else if (m_client_version == cv_titanium) - { - if (expansion >= EQ::expansions::bitPoR) - { + else if (m_client_version == cv_titanium) { + if (expansion >= EQ::expansions::bitPoR) { // Titanium shipped with 10 expansions. Set owned expansions to be max 10. plrs.offer_min_days = ((EQ::expansions::bitDoD << 2) | 1) - 2; } - else - { + else { plrs.offer_min_days = owned_expansion; } // Titanium shipped with 10 expansions. Set owned expansions to be max 10. plrs.web_offer_min_views = ((EQ::expansions::bitDoD << 2) | 1) - 2; } - - } - -} - -/** - * @param username - * @param password - */ -void Client::CreateLocalAccount(const std::string &username, const std::string &password) -{ - auto mode = server.options.GetEncryptionMode(); - auto hash = eqcrypt_hash(username, password, mode); - unsigned int db_id = 0; - if (!server.db->CreateLoginData(username, hash, "local", db_id)) { - DoFailedLogin(); - } - else { - DoSuccessfulLogin(username, db_id, "local"); } } -/** - * @param in_account_name - * @param in_account_password - * @param loginserver_account_id - */ -void Client::CreateEQEmuAccount( - const std::string &in_account_name, - const std::string &in_account_password, - unsigned int loginserver_account_id -) -{ - auto mode = server.options.GetEncryptionMode(); - auto hash = eqcrypt_hash(in_account_name, in_account_password, mode); - - if (server.db->DoesLoginServerAccountExist(in_account_name, hash, "eqemu", loginserver_account_id)) { - DoSuccessfulLogin(in_account_name, loginserver_account_id, "eqemu"); - return; - } - - if (!server.db->CreateLoginDataWithID(in_account_name, hash, "eqemu", loginserver_account_id)) { - DoFailedLogin(); - } - else { - DoSuccessfulLogin(in_account_name, loginserver_account_id, "eqemu"); - } -} - -bool Client::ProcessHealthCheck(std::string username) -{ - if (username == "healthcheckuser") { - return true; - } - - return false; -} - -std::string Client::GetClientDescription() +std::string Client::GetClientLoggingDescription() { in_addr in{}; in.s_addr = GetConnection()->GetRemoteIP(); diff --git a/loginserver/client.h b/loginserver/client.h index cc24ebe23..0dc71d00c 100644 --- a/loginserver/client.h +++ b/loginserver/client.h @@ -6,206 +6,62 @@ #include "../common/random.h" #include "../common/eq_stream_intf.h" #include "../common/net/dns.h" -#include "../common/net/daybreak_connection.h" +#include "../common/net/reliable_stream_connection.h" #include "login_types.h" +#include "../common/repositories/login_accounts_repository.h" #include -/** - * Client class, controls a single client and it's connection to the login server - */ class Client { public: - - /** - * Constructor, sets our connection to c and version to v - * - * @param c - * @param v - */ Client(std::shared_ptr c, LSClientVersion v); - - /** - * Destructor - */ ~Client() {} - - /** - * Processes the client's connection and does various actions - * - * @return - */ bool Process(); + void HandleSessionReady(const char *data, unsigned int size); + void HandleLogin(const char *data, unsigned int size); - /** - * Sends our reply to session ready packet - * - * @param data - * @param size - */ - void Handle_SessionReady(const char *data, unsigned int size); - - /** - * Verifies login and send a reply - * - * @param data - * @param size - */ - void Handle_Login(const char *data, unsigned int size); - - /** - * Sends the expansion data packet - * - * Titanium uses the encrypted data block to contact the expansion (You own xxx:) and the max expansions (of yyy) - * Rof uses a seperate data packet specifically for the expansion data - * Live, as of July 2021 uses a similar but slightly different seperate data packet - * - * @param PlayerLoginReply_Struct - * - */ - void SendExpansionPacketData(PlayerLoginReply_Struct& plrs); - - /** - * Sends a packet to the requested server to see if the client is allowed or not - * - * @param data - */ - void Handle_Play(const char *data); - - /** - * Sends a server list packet to the client - * - * @param seq - */ + // Sends the expansion data packet + // Titanium uses the encrypted data block to contact the expansion (You own xxx:) and the max expansions (of yyy) + // Rof uses a separate data packet specifically for the expansion data + // Live, as of July 2021 uses a similar but slightly different seperate data packet + void SendExpansionPacketData(PlayerLoginReply &plrs); + void SendPlayToWorld(const char *data); void SendServerListPacket(uint32 seq); - - /** - * Sends the input packet to the client and clears our play response states - * - * @param outapp - */ void SendPlayResponse(EQApplicationPacket *outapp); - - /** - * Generates a random login key for the client during login - */ - void GenerateKey(); - - /** - * Gets the account id of this client - * - * @return - */ + void GenerateRandomLoginKey(); unsigned int GetAccountID() const { return m_account_id; } - - /** - * Gets the loginserver name of this client - * - * @return - */ std::string GetLoginServerName() const { return m_loginserver_name; } - - /** - * Gets the account name of this client - * - * @return - */ std::string GetAccountName() const { return m_account_name; } - - /** - * Returns a description for the client for logging - * @return std::string - */ - std::string GetClientDescription(); - - /** - * Gets the key generated at login for this client - * - * @return - */ - std::string GetKey() const { return m_key; } - - /** - * Gets the server selected to be played on for this client - * - * @return - */ - unsigned int GetPlayServerID() const { return m_play_server_id; } - - /** - * Gets the play sequence state for this client - * - * @return - */ - unsigned int GetPlaySequence() const { return m_play_sequence_id; } - - /** - * Gets the client version - * - * @return - */ + std::string GetClientLoggingDescription(); + std::string GetLoginKey() const { return m_key; } + unsigned int GetSelectedPlayServerID() const { return m_selected_play_server_id; } + unsigned int GetCurrentPlaySequence() const { return m_play_sequence_id; } LSClientVersion GetClientVersion() const { return m_client_version; } - - /** - * Gets the connection for this client - * - * @return - */ std::shared_ptr GetConnection() { return m_connection; } - /** - * Attempts to create a login account - * - * @param user - * @param pass - * @param loginserver - */ - void AttemptLoginAccountCreation(const std::string &user, const std::string &pass, const std::string &loginserver); - - /** - * Does a failed login - */ - void DoFailedLogin(); - - /** - * Verifies a login hash, will also attempt to update a login hash if needed - * - * @param account_username - * @param source_loginserver - * @param account_password - * @param password_hash - * @return - */ - bool VerifyLoginHash( - const std::string &account_username, - const std::string &source_loginserver, - const std::string &account_password, - const std::string &password_hash - ); - - void DoSuccessfulLogin(const std::string& in_account_name, int db_account_id, const std::string &db_loginserver); - void CreateLocalAccount(const std::string &username, const std::string &password); - void CreateEQEmuAccount(const std::string &in_account_name, const std::string &in_account_password, unsigned int loginserver_account_id); + void AttemptLoginAccountCreation(LoginAccountContext c); + void SendFailedLogin(); + bool VerifyAndUpdateLoginHash(LoginAccountContext c, const LoginAccountsRepository::LoginAccounts& a); + void DoSuccessfulLogin(LoginAccountsRepository::LoginAccounts& a); private: - EQ::Random m_random; - std::shared_ptr m_connection; - LSClientVersion m_client_version; - LSClientStatus m_client_status; - - std::string m_account_name; - unsigned int m_account_id; - std::string m_loginserver_name; - unsigned int m_play_server_id; - unsigned int m_play_sequence_id; - std::string m_key; - - std::unique_ptr m_login_connection_manager; - std::shared_ptr m_login_connection; - LoginBaseMessage_Struct m_llrs; - - std::string m_stored_user; - std::string m_stored_pass; - static bool ProcessHealthCheck(std::string username); + EQ::Random m_random; + std::shared_ptr m_connection; + LSClientVersion m_client_version; + LSClientStatus m_client_status; + std::string m_account_name; + unsigned int m_account_id; + std::string m_loginserver_name; + unsigned int m_selected_play_server_id; + unsigned int m_play_sequence_id; + std::string m_key; + std::unique_ptr m_login_connection_manager; + std::shared_ptr m_login_connection; + LoginBaseMessage m_login_base_message; + std::string m_stored_username; + std::string m_stored_password; + static bool ProcessHealthCheck(std::string username) { + return username == "healthcheckuser"; + } }; #endif diff --git a/loginserver/client_manager.cpp b/loginserver/client_manager.cpp index c82eb0de8..5767f538b 100644 --- a/loginserver/client_manager.cpp +++ b/loginserver/client_manager.cpp @@ -4,12 +4,12 @@ extern LoginServer server; extern bool run_server; -#include "../common/eqemu_logsys.h" #include "../common/misc.h" #include "../common/path_manager.h" #include "../common/file.h" -void CheckTitaniumOpcodeFile(const std::string &path) { +void CheckTitaniumOpcodeFile(const std::string &path) +{ if (File::Exists(path)) { return; } @@ -32,7 +32,8 @@ void CheckTitaniumOpcodeFile(const std::string &path) { } } -void CheckSoDOpcodeFile(const std::string& path) { +void CheckSoDOpcodeFile(const std::string &path) +{ if (File::Exists(path)) { return; } @@ -56,7 +57,8 @@ void CheckSoDOpcodeFile(const std::string& path) { } } -void CheckLarionOpcodeFile(const std::string& path) { +void CheckLarionOpcodeFile(const std::string &path) +{ if (File::Exists(path)) { return; } @@ -87,74 +89,74 @@ ClientManager::ClientManager() EQStreamManagerInterfaceOptions titanium_opts(titanium_port, false, false); - titanium_stream = new EQ::Net::EQStreamManager(titanium_opts); - titanium_ops = new RegularOpcodeManager; + m_titanium_stream = new EQ::Net::EQStreamManager(titanium_opts); + m_titanium_ops = new RegularOpcodeManager; std::string opcodes_path = fmt::format( "{}/{}", - path.GetOpcodePath(), + PathManager::Instance()->GetOpcodePath(), "login_opcodes.conf" ); CheckTitaniumOpcodeFile(opcodes_path); - if (!titanium_ops->LoadOpcodes(opcodes_path.c_str())) { + if (!m_titanium_ops->LoadOpcodes(opcodes_path.c_str())) { LogError( - "ClientManager fatal error: couldn't load opcodes for Titanium file [{0}]", + "ClientManager fatal error: couldn't load opcodes for Titanium file [{}]", server.config.GetVariableString("client_configuration", "titanium_opcodes", "login_opcodes.conf") ); run_server = false; } - titanium_stream->OnNewConnection( + m_titanium_stream->OnNewConnection( [this](std::shared_ptr stream) { LogInfo( - "New Titanium client connection from [{0}:{1}]", + "New Titanium client connection from [{}:{}]", long2ip(stream->GetRemoteIP()), stream->GetRemotePort() ); - stream->SetOpcodeManager(&titanium_ops); + stream->SetOpcodeManager(&m_titanium_ops); Client *c = new Client(stream, cv_titanium); - clients.push_back(c); + m_clients.push_back(c); } ); int sod_port = server.config.GetVariableInt("client_configuration", "sod_port", 5999); EQStreamManagerInterfaceOptions sod_opts(sod_port, false, false); - sod_stream = new EQ::Net::EQStreamManager(sod_opts); - sod_ops = new RegularOpcodeManager; + m_sod_stream = new EQ::Net::EQStreamManager(sod_opts); + m_sod_ops = new RegularOpcodeManager; opcodes_path = fmt::format( "{}/{}", - path.GetOpcodePath(), + PathManager::Instance()->GetOpcodePath(), "login_opcodes_sod.conf" ); CheckSoDOpcodeFile(opcodes_path); - if (!sod_ops->LoadOpcodes(opcodes_path.c_str())) { + if (!m_sod_ops->LoadOpcodes(opcodes_path.c_str())) { LogError( - "ClientManager fatal error: couldn't load opcodes for SoD file {0}", + "ClientManager fatal error: couldn't load opcodes for SoD file {}", server.config.GetVariableString("client_configuration", "sod_opcodes", "login_opcodes.conf").c_str() ); run_server = false; } - sod_stream->OnNewConnection( + m_sod_stream->OnNewConnection( [this](std::shared_ptr stream) { LogInfo( - "New SoD+ client connection from [{0}:{1}]", + "New SoD+ client connection from [{}:{}]", long2ip(stream->GetRemoteIP()), stream->GetRemotePort() ); - stream->SetOpcodeManager(&sod_ops); + stream->SetOpcodeManager(&m_sod_ops); auto *c = new Client(stream, cv_sod); - clients.push_back(c); + m_clients.push_back(c); } ); @@ -162,126 +164,106 @@ ClientManager::ClientManager() EQStreamManagerInterfaceOptions larion_opts(larion_port, false, false); - larion_stream = new EQ::Net::EQStreamManager(larion_opts); - larion_ops = new RegularOpcodeManager; + m_larion_stream = new EQ::Net::EQStreamManager(larion_opts); + m_larion_ops = new RegularOpcodeManager; opcodes_path = fmt::format( "{}/{}", - path.GetOpcodePath(), + PathManager::Instance()->GetOpcodePath(), "login_opcodes_larion.conf" ); CheckLarionOpcodeFile(opcodes_path); - if (!larion_ops->LoadOpcodes(opcodes_path.c_str())) { + if (!m_larion_ops->LoadOpcodes(opcodes_path.c_str())) { LogError( - "ClientManager fatal error: couldn't load opcodes for Larion file [{0}]", + "ClientManager fatal error: couldn't load opcodes for Larion file [{}]", server.config.GetVariableString("client_configuration", "larion_opcodes", "login_opcodes.conf") ); run_server = false; } - larion_stream->OnNewConnection( + m_larion_stream->OnNewConnection( [this](std::shared_ptr stream) { LogInfo( - "New Larion client connection from [{0}:{1}]", + "New Larion client connection from [{}:{}]", long2ip(stream->GetRemoteIP()), stream->GetRemotePort() ); - stream->SetOpcodeManager(&larion_ops); - Client* c = new Client(stream, cv_larion); - clients.push_back(c); + stream->SetOpcodeManager(&m_larion_ops); + Client *c = new Client(stream, cv_larion); + m_clients.push_back(c); } ); } ClientManager::~ClientManager() { - if (titanium_stream) { - delete titanium_stream; - } - - if (titanium_ops) { - delete titanium_ops; - } - - if (sod_stream) { - delete sod_stream; - } - - if (sod_ops) { - delete sod_ops; - } + delete m_titanium_stream; + delete m_titanium_ops; + delete m_sod_stream; + delete m_sod_ops; } void ClientManager::Process() { ProcessDisconnect(); - auto iter = clients.begin(); - while (iter != clients.end()) { - if ((*iter)->Process() == false) { + for (auto it = m_clients.begin(); it != m_clients.end();) { + Client *c = *it; + if (!c->Process()) { LogWarning("Client had a fatal error and had to be removed from the login"); - delete (*iter); - iter = clients.erase(iter); + delete c; + it = m_clients.erase(it); } else { - ++iter; + ++it; } } } void ClientManager::ProcessDisconnect() { - auto iter = clients.begin(); - while (iter != clients.end()) { - std::shared_ptr c = (*iter)->GetConnection(); - if (c->CheckState(CLOSED)) { + auto it = m_clients.begin(); + while (it != m_clients.end()) { + Client *c = *it; + if (c->GetConnection()->CheckState(CLOSED)) { LogInfo("Client disconnected from the server, removing client"); - delete (*iter); - iter = clients.erase(iter); + delete c; + it = m_clients.erase(it); } else { - ++iter; + ++it; } } } -/** - * @param account_id - * @param loginserver - */ void ClientManager::RemoveExistingClient(unsigned int account_id, const std::string &loginserver) { - auto iter = clients.begin(); - while (iter != clients.end()) { - if ((*iter)->GetAccountID() == account_id && (*iter)->GetLoginServerName().compare(loginserver) == 0) { + auto it = m_clients.begin(); + while (it != m_clients.end()) { + Client *c = *it; + if (c->GetAccountID() == account_id && c->GetLoginServerName() == loginserver) { LogInfo("Client attempting to log in existing client already logged in, removing existing client"); - delete (*iter); - iter = clients.erase(iter); + delete c; + it = m_clients.erase(it); } else { - ++iter; + ++it; } } } -/** - * @param account_id - * @param loginserver - * @return - */ Client *ClientManager::GetClient(unsigned int account_id, const std::string &loginserver) { - auto iter = clients.begin(); - while (iter != clients.end()) { - if ((*iter)->GetAccountID() == account_id && (*iter)->GetLoginServerName().compare(loginserver) == 0) { - return (*iter); + auto iter = std::find_if( + m_clients.begin(), m_clients.end(), + [&](Client *c) { + return c->GetAccountID() == account_id && c->GetLoginServerName() == loginserver; } - ++iter; - } + ); - return nullptr; + return (iter != m_clients.end()) ? *iter : nullptr; } diff --git a/loginserver/client_manager.h b/loginserver/client_manager.h index 23aff232e..17c87b43d 100644 --- a/loginserver/client_manager.h +++ b/loginserver/client_manager.h @@ -7,56 +7,23 @@ #include "client.h" #include -/** -* Client manager class, holds all the client objects and does basic processing. -*/ class ClientManager { public: - /** - * Constructor: sets up the stream factories and opcode managers - */ ClientManager(); - - /** - * Destructor: shuts down the streams and opcode managers - */ ~ClientManager(); - - /** - * Processes every client in the internal list, removes them if necessary. - */ void Process(); - - /** - * Removes a client with a certain account id - * - * @param account_id - * @param loginserver - */ - void RemoveExistingClient(unsigned int account_id, const std::string &loginserver); - - /** - * Gets a client (if exists) by their account id - * - * @param account_id - * @param loginserver - * @return - */ - Client *GetClient(unsigned int account_id, const std::string &loginserver); + void RemoveExistingClient(unsigned int c, const std::string &loginserver); + Client *GetClient(unsigned int c, const std::string &loginserver); private: - - /** - * Processes disconnected clients, removes them if necessary - */ void ProcessDisconnect(); - std::list clients; - OpcodeManager *titanium_ops; - EQ::Net::EQStreamManager *titanium_stream; - OpcodeManager *sod_ops; - EQ::Net::EQStreamManager *sod_stream; - OpcodeManager *larion_ops; - EQ::Net::EQStreamManager* larion_stream; + std::list m_clients; + OpcodeManager *m_titanium_ops; + EQ::Net::EQStreamManager *m_titanium_stream; + OpcodeManager *m_sod_ops; + EQ::Net::EQStreamManager *m_sod_stream; + OpcodeManager *m_larion_ops; + EQ::Net::EQStreamManager *m_larion_stream; }; #endif diff --git a/loginserver/database.cpp b/loginserver/database.cpp deleted file mode 100644 index 7c5e6b382..000000000 --- a/loginserver/database.cpp +++ /dev/null @@ -1,680 +0,0 @@ -#include "../common/global_define.h" - -#include "database.h" -#include "login_server.h" -#include "../common/eqemu_logsys.h" -#include "../common/strings.h" -#include "../common/util/uuid.h" - -extern LoginServer server; - -/** - * Initial connect - * - * @param user - * @param pass - * @param host - * @param port - * @param name - */ -Database::Database( - std::string user, - std::string pass, - std::string host, - std::string port, - std::string name -) -{ - uint32 errnum = 0; - char errbuf[MYSQL_ERRMSG_SIZE]; - if (!Open( - host.c_str(), - user.c_str(), - pass.c_str(), - name.c_str(), - Strings::ToUnsignedInt(port), - &errnum, - errbuf - ) - ) { - LogError("Failed to connect to database: Error: [{0}]", errbuf); - exit(1); - } - else { - LogInfo("Using database [{0}] at [{1}:{2}]", name, host, port); - } -} - -/** - * Deconstructor - */ -Database::~Database() -{ - if (m_database) { - mysql_close(m_database); - } -} - -/** - * @param name - * @param loginserver - * @param password - * @param id - * @return - */ -bool Database::GetLoginDataFromAccountInfo( - const std::string &name, - const std::string &loginserver, - std::string &password, - unsigned int &id -) -{ - auto query = fmt::format( - "SELECT id, account_password FROM login_accounts WHERE account_name = '{0}' AND source_loginserver = '{1}' LIMIT 1", - Strings::Escape(name), - Strings::Escape(loginserver) - ); - - auto results = QueryDatabase(query); - - if (results.RowCount() != 1) { - LogDebug( - "Could not find account for name [{0}] login [{1}]", - name, - loginserver - ); - - return false; - } - - if (!results.Success()) { - return false; - } - - auto row = results.begin(); - - id = Strings::ToUnsignedInt(row[0]); - password = row[1]; - - LogDebug( - "Found account for name [{0}] login [{1}]", - name, - loginserver - ); - - return true; -} - -/** - * @param token - * @param ip - * @param db_account_id - * @param db_loginserver - * @param user - * @return - */ -bool Database::GetLoginTokenDataFromToken( - const std::string &token, - const std::string &ip, - unsigned int &db_account_id, - std::string &db_loginserver, - std::string &user -) -{ - auto query = fmt::format("SELECT login_server, username, account_id FROM login_tickets WHERE expires > NOW()" - " AND id='{0}' AND ip_address='{1}' LIMIT 1", - Strings::Escape(token), - Strings::Escape(ip)); - - auto results = QueryDatabase(query); - if (results.RowCount() == 0 || !results.Success()) { - return false; - } - - for (auto row = results.begin(); row != results.end(); ++row) { - db_loginserver = row[0]; - user = row[1]; - db_account_id = Strings::ToUnsignedInt(row[2]); - - return true; - } - - return false; -} - -/** - * @param loginserver - * @return - */ -unsigned int Database::GetFreeID(const std::string &loginserver) -{ - auto query = fmt::format( - "SELECT IFNULL(MAX(id), 0) + 1 FROM login_accounts WHERE source_loginserver = '{0}'", - Strings::Escape(loginserver) - ); - - auto results = QueryDatabase(query); - if (!results.Success() || results.RowCount() != 1) { - return 0; - } - - auto row = results.begin(); - - return Strings::ToUnsignedInt(row[0]); -} - -/** - * @param name - * @param password - * @param loginserver - * @param id - * @return - */ -bool Database::CreateLoginData( - const std::string &name, - const std::string &password, - const std::string &loginserver, - unsigned int &id -) -{ - uint32 free_id = GetFreeID(loginserver); - id = free_id; - - return CreateLoginDataWithID(name, password, loginserver, free_id); -} - -/** - * @param name - * @param password - * @param loginserver - * @param email - * @return - */ -uint32 Database::CreateLoginAccount( - const std::string &name, - const std::string &password, - const std::string &loginserver, - const std::string &email -) -{ - uint32 free_id = GetFreeID(loginserver); - - if (free_id <= 0) { - return 0; - } - - auto query = fmt::format( - "INSERT INTO login_accounts (id, source_loginserver, account_name, account_password, account_email, last_login_date, last_ip_address, created_at) " - "VALUES ({0}, '{1}', '{2}', '{3}', '{4}', NOW(), '127.0.0.1', NOW())", - free_id, - Strings::Escape(loginserver), - Strings::Escape(name), - Strings::Escape(password), - Strings::Escape(email) - ); - - auto results = QueryDatabase(query); - - return (results.Success() ? free_id : 0); -} - -/** - * @param in_account_name - * @param in_account_password - * @param loginserver - * @param id - * @return - */ -bool Database::CreateLoginDataWithID( - const std::string &in_account_name, - const std::string &in_account_password, - const std::string &loginserver, - unsigned int id -) -{ - if (id == 0) { - return false; - } - - auto query = fmt::format( - "INSERT INTO login_accounts (id, source_loginserver, account_name, account_password, account_email, last_login_date, last_ip_address, created_at) " - "VALUES ({0}, '{1}', '{2}', '{3}', 'local_creation', NOW(), '127.0.0.1', NOW())", - id, - Strings::Escape(loginserver), - Strings::Escape(in_account_name), - Strings::Escape(in_account_password) - ); - - auto results = QueryDatabase(query); - - return results.Success(); -} - -/** - * @param name - * @param password - * @param loginserver - * @param id - * @return - */ -bool Database::DoesLoginServerAccountExist( - const std::string &name, - const std::string &password, - const std::string &loginserver, - unsigned int id -) -{ - if (id == 0) { - return false; - } - - auto query = fmt::format( - "SELECT account_name FROM login_accounts WHERE account_name = '{0}' AND source_loginserver = '{1}'", - Strings::Escape(name), - Strings::Escape(loginserver) - ); - - auto results = QueryDatabase(query); - if (!results.Success() || results.RowCount() != 1) { - return false; - } - - return true; -} - -/** - * @param name - * @param loginserver - * @param hash - */ -void Database::UpdateLoginserverAccountPasswordHash( - const std::string &name, - const std::string &loginserver, - const std::string &hash -) -{ - LogDebug( - "name [{0}] loginserver [{1}] hash [{2}]", - name, - loginserver, - hash - ); - - auto query = fmt::format( - "UPDATE login_accounts SET account_password = '{0}' WHERE account_name = '{1}' AND source_loginserver = '{2}'", - hash, - Strings::Escape(name), - Strings::Escape(loginserver) - ); - - QueryDatabase(query); -} - -/** - * @param short_name - * @param long_name - * @param login_world_server_admin_id - * @return - */ -Database::DbWorldRegistration Database::GetWorldRegistration( - const std::string &short_name, - const std::string &long_name, - uint32 login_world_server_admin_id -) -{ - auto query = fmt::format( - "SELECT\n" - " WSR.id,\n" - " WSR.tag_description,\n" - " WSR.is_server_trusted,\n" - " SLT.id,\n" - " SLT.description,\n" - " ifnull(WSR.login_server_admin_id, 0) AS login_server_admin_id\n" - "FROM\n" - " login_world_servers AS WSR\n" - " JOIN login_server_list_types AS SLT ON WSR.login_server_list_type_id = SLT.id\n" - "WHERE\n" - " WSR.short_name = '{}' AND WSR.long_name = '{}' AND WSR.login_server_admin_id = {} LIMIT 1", - Strings::Escape(short_name), - Strings::Escape(long_name), - login_world_server_admin_id - ); - - Database::DbWorldRegistration r{}; - - auto results = QueryDatabase(query); - if (!results.Success() || results.RowCount() != 1) { - return r; - } - - auto row = results.begin(); - - r.loaded = true; - r.server_id = Strings::ToInt(row[0]); - r.server_description = row[1]; - r.server_list_type = Strings::ToInt(row[3]); - r.is_server_trusted = Strings::ToInt(row[2]) > 0; - r.server_list_description = row[4]; - r.server_admin_id = Strings::ToUnsignedInt(row[5]); - - if (r.server_admin_id <= 0) { - return r; - } - - auto world_registration_query = fmt::format( - "SELECT account_name, account_password FROM login_server_admins WHERE id = {0} LIMIT 1", - r.server_admin_id - ); - - auto world_registration_results = QueryDatabase(world_registration_query); - if (world_registration_results.Success() && world_registration_results.RowCount() == 1) { - auto world_registration_row = world_registration_results.begin(); - r.server_admin_account_name = world_registration_row[0]; - r.server_admin_account_password = world_registration_row[1]; - } - - return r; -} - -/** - * @param id - * @param ip_address - */ -void Database::UpdateLSAccountData(unsigned int id, std::string ip_address) -{ - auto query = fmt::format( - "UPDATE login_accounts SET last_ip_address = '{0}', last_login_date = NOW() where id = {1}", - ip_address, - id - ); - - QueryDatabase(query); -} - -/** - * @param id - * @param name - * @param password - * @param email - */ -void Database::UpdateLSAccountInfo( - unsigned int id, - std::string name, - std::string password, - std::string email -) -{ - auto query = fmt::format( - "REPLACE login_accounts SET id = {0}, account_name = '{1}', account_password = sha('{2}'), " - "account_email = '{3}', last_ip_address = '0.0.0.0', last_login_date = now()", - id, - Strings::Escape(name), - Strings::Escape(password), - Strings::Escape(email) - ); - - QueryDatabase(query); -} - -/** - * @param id - * @param long_name - * @param ip_address - */ -void Database::UpdateWorldRegistration(unsigned int id, std::string long_name, std::string ip_address) -{ - auto query = fmt::format( - "UPDATE login_world_servers SET last_login_date = NOW(), last_ip_address = '{0}', long_name = '{1}' WHERE id = {2}", - ip_address, - Strings::Escape(long_name), - id - ); - - QueryDatabase(query); -} - -/** - * @param id - * @param admin_account_password_hash - */ -bool Database::UpdateLoginWorldAdminAccountPassword( - unsigned int id, - const std::string &admin_account_password_hash -) -{ - auto results = QueryDatabase( - fmt::format( - "UPDATE login_server_admins SET account_password = '{}' WHERE id = {}", - Strings::Escape(admin_account_password_hash), - id - ) - ); - - return results.Success(); -} - -/** - * - * @param admin_account_username - * @param admin_account_password_hash - */ -bool Database::UpdateLoginWorldAdminAccountPasswordByUsername( - const std::string &admin_account_username, - const std::string &admin_account_password_hash -) -{ - auto results = QueryDatabase( - fmt::format( - "UPDATE login_server_admins SET account_password = '{}' WHERE account_name = '{}'", - Strings::Escape(admin_account_password_hash), - Strings::Escape(admin_account_username) - ) - ); - - return results.Success(); -} - -/** - * @param server_long_name - * @param server_short_name - * @param id - * @return - */ -bool Database::CreateWorldRegistration( - std::string server_long_name, - std::string server_short_name, - std::string server_remote_ip, - unsigned int &id, - unsigned int &server_admin_id -) -{ - auto results = QueryDatabase("SELECT IFNULL(max(id), 0) + 1 FROM login_world_servers"); - if (!results.Success() || results.RowCount() != 1) { - return false; - } - - auto row = results.begin(); - - id = Strings::ToUnsignedInt(row[0]); - auto insert_query = fmt::format( - "INSERT INTO login_world_servers SET id = {0}, long_name = '{1}', short_name = '{2}', last_ip_address = '{3}', \n" - "login_server_list_type_id = 3, login_server_admin_id = {4}, is_server_trusted = 0, tag_description = ''", - id, - Strings::Escape(server_long_name), - Strings::Escape(server_short_name), - server_remote_ip, - server_admin_id - ); - - auto insert_results = QueryDatabase(insert_query); - if (!insert_results.Success()) { - LogError( - "Failed to register world server {0} - {1}", - server_long_name, - server_short_name - ); - - return false; - } - - return true; -} - -/** - * @param long_name - * @param short_name - * @param id - * @return - */ -std::string Database::CreateLoginserverApiToken( - bool write_mode, - bool read_mode -) -{ - std::string token = EQ::Util::UUID::Generate().ToString(); - auto query = fmt::format( - "INSERT INTO login_api_tokens (token, can_write, can_read, created_at) VALUES ('{0}', {1}, {2}, NOW())", - token, - (write_mode ? "1" : "0"), - (read_mode ? "1" : "0") - ); - - auto results = QueryDatabase(query); - if (!results.Success()) { - return ""; - } - - return token; -} - -/** - * @param long_name - * @param short_name - * @param id - * @return - */ -MySQLRequestResult Database::GetLoginserverApiTokens() -{ - return QueryDatabase("SELECT token, can_write, can_read FROM login_api_tokens"); -} - -/** - * @param account_name - * @param account_password - * @param first_name - * @param last_name - * @param email - * @param ip_address - * @return - */ -uint32 Database::CreateLoginserverWorldAdminAccount( - const std::string &account_name, - const std::string &account_password, - const std::string &first_name, - const std::string &last_name, - const std::string &email, - const std::string &ip_address -) -{ - auto query = fmt::format( - "INSERT INTO login_server_admins (account_name, account_password, first_name, last_name, email, registration_date, " - "registration_ip_address) " - "VALUES ('{0}', '{1}', '{2}', '{3}', '{4}', NOW(), '{5}')", - Strings::Escape(account_name), - Strings::Escape(account_password), - Strings::Escape(first_name), - Strings::Escape(last_name), - Strings::Escape(email), - ip_address - ); - - auto results = QueryDatabase(query); - - return (results.Success() ? results.LastInsertedID() : 0); -} - -/** - * @param account_name - * @return - */ -bool Database::DoesLoginserverWorldAdminAccountExist( - const std::string &account_name -) -{ - auto query = fmt::format( - "SELECT account_name FROM login_server_admins WHERE account_name = '{0}' LIMIT 1", - Strings::Escape(account_name) - ); - - auto results = QueryDatabase(query); - - return (results.RowCount() == 1); -} - -/** - * @param account_name - * @return - */ -Database::DbLoginServerAdmin Database::GetLoginServerAdmin(const std::string &account_name) -{ - auto query = fmt::format( - "SELECT id, account_name, account_password, first_name, last_name, email, registration_date, registration_ip_address" - " FROM login_server_admins WHERE account_name = '{0}' LIMIT 1", - Strings::Escape(account_name) - ); - - auto results = QueryDatabase(query); - - Database::DbLoginServerAdmin r{}; - if (results.RowCount() == 1) { - auto row = results.begin(); - r.loaded = true; - r.id = Strings::ToUnsignedInt(row[0]); - r.account_name = row[1]; - r.account_password = row[2]; - r.first_name = row[3]; - r.last_name = row[4]; - r.email = row[5]; - r.registration_date = row[7]; - r.registration_ip_address = row[8]; - } - - return r; -} - -/** - * @param account_name - * @return - */ -Database::DbLoginServerAccount Database::GetLoginServerAccountByAccountName( - const std::string &account_name, - const std::string &source_loginserver -) -{ - auto query = fmt::format( - "SELECT id, account_name, account_password, account_email, source_loginserver, last_ip_address, last_login_date, " - "created_at, updated_at" - " FROM login_accounts WHERE account_name = '{0}' and source_loginserver = '{1}' LIMIT 1", - Strings::Escape(account_name), - Strings::Escape(source_loginserver) - ); - - auto results = QueryDatabase(query); - - Database::DbLoginServerAccount r{}; - if (results.RowCount() == 1) { - auto row = results.begin(); - r.loaded = true; - r.id = Strings::ToUnsignedInt(row[0]); - r.account_name = row[1]; - r.account_password = row[2]; - r.account_email = row[3]; - r.source_loginserver = row[4]; - r.last_ip_address = row[5]; - r.last_login_date = row[6]; - r.created_at = row[7]; - r.updated_at = row[8]; - } - - return r; -} diff --git a/loginserver/database.h b/loginserver/database.h deleted file mode 100644 index f194934a9..000000000 --- a/loginserver/database.h +++ /dev/null @@ -1,286 +0,0 @@ -#ifndef EQEMU_DATABASEMYSQL_H -#define EQEMU_DATABASEMYSQL_H - -#include "../common/dbcore.h" -#include "../common/eqemu_logsys.h" - -#include -#include -#include -#include - -class Database : public DBcore { -public: - - Database() { m_database = nullptr; } - - /** - * Constructor, tries to set our database to connect to the supplied options. - * - * @param user - * @param pass - * @param host - * @param port - * @param name - */ - Database(std::string user, std::string pass, std::string host, std::string port, std::string name); - - /** - * Destructor, frees our database if needed. - */ - ~Database(); - - /** - * Retrieves the login data (password hash and account id) from the account name provided needed for client login procedure. - * @param name - * @param loginserver - * @param password - * @param id - * @return - */ - bool GetLoginDataFromAccountInfo( - const std::string &name, - const std::string &loginserver, - std::string &password, - unsigned int &id - ); - - /** - * @param token - * @param ip - * @param db_account_id - * @param db_loginserver - * @param user - * @return - */ - bool GetLoginTokenDataFromToken( - const std::string &token, - const std::string &ip, - unsigned int &db_account_id, - std::string &db_loginserver, - std::string &user - ); - - /** - * @param loginserver - * @return - */ - unsigned int GetFreeID(const std::string &loginserver); - - /** - * @param name - * @param password - * @param loginserver - * @param id - * @return - */ - bool CreateLoginData( - const std::string &name, - const std::string &password, - const std::string &loginserver, - unsigned int &id - ); - - /** - * @param in_account_name - * @param in_account_password - * @param loginserver - * @param id - * @return - */ - bool CreateLoginDataWithID( - const std::string &in_account_name, - const std::string &in_account_password, - const std::string &loginserver, - unsigned int id - ); - - /** - * @param name - * @param loginserver - * @param hash - */ - void UpdateLoginserverAccountPasswordHash( - const std::string &name, - const std::string &loginserver, - const std::string &hash); - - /** - * @param name - * @param password - * @param loginserver - * @param id - * @return - */ - bool DoesLoginServerAccountExist( - const std::string &name, - const std::string &password, - const std::string &loginserver, - unsigned int id - ); - - struct DbWorldRegistration { - bool loaded = false; - int32 server_id = 0; - int8 server_list_type = 3; - bool is_server_trusted = false; - std::string server_description; - std::string server_list_description; - std::string server_admin_account_name; - std::string server_admin_account_password; - uint32 server_admin_id; - }; - - /** - * Retrieves the world registration from the long and short names provided - * Needed for world login procedure - * Returns true if the record was found, false otherwise - * - * @param short_name - * @param long_name - * @param login_world_server_admin_id - * @return - */ - Database::DbWorldRegistration GetWorldRegistration( - const std::string &short_name, - const std::string &long_name, - uint32 login_world_server_admin_id - ); - - /** - * @param id - * @param ip_address - */ - void UpdateLSAccountData(unsigned int id, std::string ip_address); - - /** - * @param id - * @param name - * @param password - * @param email - */ - void UpdateLSAccountInfo(unsigned int id, std::string name, std::string password, std::string email); - - /** - * @param id - * @param long_name - * @param ip_address - */ - void UpdateWorldRegistration(unsigned int id, std::string long_name, std::string ip_address); - - /** - * @param server_long_name - * @param server_short_name - * @param id - * @return - */ - bool CreateWorldRegistration( - std::string server_long_name, - std::string server_short_name, - std::string server_remote_ip, - unsigned int &id, - unsigned int &server_admin_id - ); - - /** - * @param write_mode - * @param read_mode - * @return - */ - std::string CreateLoginserverApiToken(bool write_mode, bool read_mode); - MySQLRequestResult GetLoginserverApiTokens(); - - /** - * @param account_name - * @param account_password - * @param first_name - * @param last_name - * @param email - * @param ip_address - * @return - */ - uint32 CreateLoginserverWorldAdminAccount( - const std::string &account_name, - const std::string &account_password, - const std::string &first_name, - const std::string &last_name, - const std::string &email, - const std::string &ip_address - ); - - /** - * @param account_name - * @return - */ - bool DoesLoginserverWorldAdminAccountExist(const std::string &account_name); - - struct DbLoginServerAdmin { - bool loaded = false; - uint32 id; - std::string account_name; - std::string account_password; - std::string first_name; - std::string last_name; - std::string email; - std::string registration_date; - std::string registration_ip_address; - }; - - Database::DbLoginServerAdmin GetLoginServerAdmin(const std::string &account_name); - - struct DbLoginServerAccount { - bool loaded = false; - uint32 id; - std::string account_name; - std::string account_password; - std::string account_email; - std::string source_loginserver; - std::string last_login_date; - std::string last_ip_address; - std::string created_at; - std::string updated_at; - }; - - Database::DbLoginServerAccount GetLoginServerAccountByAccountName( - const std::string &account_name, - const std::string &source_loginserver = "local" - ); - - /** - * @param id - * @param admin_account_password_hash - */ - bool UpdateLoginWorldAdminAccountPassword( - unsigned int id, - const std::string& admin_account_password_hash - ); - - /** - * @param admin_account_username - * @param admin_account_password_hash - */ - bool UpdateLoginWorldAdminAccountPasswordByUsername( - const std::string &admin_account_username, - const std::string &admin_account_password_hash - ); - - /** - * @param name - * @param password - * @param loginserver - * @param email - * @return - */ - uint32 CreateLoginAccount( - const std::string &name, - const std::string &password, - const std::string &loginserver = "local", - const std::string &email = "local_creation" - ); - -protected: - MYSQL *m_database{}; -}; - -#endif - diff --git a/loginserver/encryption.cpp b/loginserver/encryption.cpp index 7080f1996..5c15f46d4 100644 --- a/loginserver/encryption.cpp +++ b/loginserver/encryption.cpp @@ -21,10 +21,6 @@ #endif -/** - * @param mode - * @return - */ std::string GetEncryptionByModeId(uint32 mode) { switch (mode) { @@ -61,13 +57,6 @@ std::string GetEncryptionByModeId(uint32 mode) } } -/** - * @param buffer_in - * @param buffer_in_sz - * @param buffer_out - * @param enc - * @return - */ const char *eqcrypt_block(const char *buffer_in, size_t buffer_in_sz, char *buffer_out, bool enc) { #ifdef EQEMU_USE_MBEDTLS @@ -113,7 +102,7 @@ const char *eqcrypt_block(const char *buffer_in, size_t buffer_in_sz, char *buff unsigned char iv[8]; memset(&key, 0, MBEDTLS_DES_KEY_SIZE); memset(&iv, 0, 8); - + mbedtls_des_context context; mbedtls_des_setkey_dec(&context, key); mbedtls_des_crypt_cbc(&context, MBEDTLS_DES_DECRYPT, buffer_in_sz, iv, (const unsigned char*)buffer_in, (unsigned char*)buffer_out); @@ -123,23 +112,19 @@ const char *eqcrypt_block(const char *buffer_in, size_t buffer_in_sz, char *buff #ifdef EQEMU_USE_OPENSSL DES_key_schedule k; DES_cblock v; - + memset(&k, 0, sizeof(DES_key_schedule)); memset(&v, 0, sizeof(DES_cblock)); - + if (!enc && buffer_in_sz && buffer_in_sz % 8 != 0) { return nullptr; } - + DES_ncbc_encrypt((const unsigned char*)buffer_in, (unsigned char*)buffer_out, (long)buffer_in_sz, &k, &v, enc); #endif return buffer_out; } -/** - * @param msg - * @return - */ std::string eqcrypt_md5(const std::string &msg) { std::string ret; @@ -174,10 +159,6 @@ std::string eqcrypt_md5(const std::string &msg) return ret; } -/** - * @param msg - * @return - */ std::string eqcrypt_sha1(const std::string &msg) { std::string ret; @@ -212,10 +193,6 @@ std::string eqcrypt_sha1(const std::string &msg) return ret; } -/** - * @param msg - * @return - */ std::string eqcrypt_sha512(const std::string &msg) { std::string ret; @@ -252,10 +229,6 @@ std::string eqcrypt_sha512(const std::string &msg) #ifdef ENABLE_SECURITY -/** - * @param msg - * @return - */ std::string eqcrypt_argon2(const std::string &msg) { char buffer[crypto_pwhash_STRBYTES] = {0}; @@ -275,10 +248,6 @@ std::string eqcrypt_argon2(const std::string &msg) return ret; } -/** - * @param msg - * @return - */ std::string eqcrypt_scrypt(const std::string &msg) { char buffer[crypto_pwhash_scryptsalsa208sha256_STRBYTES] = {0}; @@ -300,12 +269,6 @@ std::string eqcrypt_scrypt(const std::string &msg) #endif -/** - * @param username - * @param password - * @param mode - * @return - */ std::string eqcrypt_hash(const std::string &username, const std::string &password, int mode) { switch (mode) { @@ -346,13 +309,6 @@ std::string eqcrypt_hash(const std::string &username, const std::string &passwor } } -/** - * @param username - * @param password - * @param pwhash - * @param mode - * @return - */ bool eqcrypt_verify_hash(const std::string &username, const std::string &password, const std::string &pwhash, int mode) { switch (mode) { diff --git a/loginserver/encryption.h b/loginserver/encryption.h index d8337c25b..43d556d53 100644 --- a/loginserver/encryption.h +++ b/loginserver/encryption.h @@ -2,6 +2,9 @@ #include #include "../common/types.h" +#include "login_types.h" +#include "../common/eqemu_logsys.h" +#include "../common/strings.h" enum EncryptionMode { EncryptionModeMD5 = 1, @@ -26,11 +29,34 @@ namespace CryptoHash { const int sha512_hash_length = 128; } -/** - * @param mode - * @return - */ std::string GetEncryptionByModeId(uint32 mode); const char *eqcrypt_block(const char *buffer_in, size_t buffer_in_sz, char *buffer_out, bool enc); std::string eqcrypt_hash(const std::string &username, const std::string &password, int mode); bool eqcrypt_verify_hash(const std::string &username, const std::string &password, const std::string &pwhash, int mode); + +struct EncryptionResult { + std::string password; + int mode = 0; + std::string mode_name; +}; + +static EncryptionResult EncryptPasswordFromContext(LoginAccountContext c, int mode = EncryptionModeSCrypt) +{ + if (mode == 0) { + LogError("Encryption mode not set!"); + return {}; + } + + EncryptionResult r; + r.password = eqcrypt_hash( + c.username, + c.password, + mode + ); + r.mode = mode; + r.mode_name = GetEncryptionByModeId(r.mode); + + LogInfo("Encrypted password for user [{}] using mode [{}] ({})", c.username, r.mode_name, r.mode); + + return r; +} diff --git a/loginserver/login_server.h b/loginserver/login_server.h index b80cf6ee5..5104a48e0 100644 --- a/loginserver/login_server.h +++ b/loginserver/login_server.h @@ -3,29 +3,24 @@ #include #include "../common/json_config.h" -#include "database.h" #include "encryption.h" #include "options.h" -#include "server_manager.h" +#include "world_server_manager.h" #include "client_manager.h" #include "loginserver_webserver.h" -/** - * Login server struct, Contains every variable for the server that needs to exist outside the scope of main() - */ -struct LoginServer -{ +struct LoginServer { public: - LoginServer() : db(nullptr), server_manager(nullptr) { + LoginServer() : server_manager(nullptr) + { } EQ::JsonConfigFile config; - Database *db; LoginserverWebserver::TokenManager *token_manager{}; Options options; - ServerManager *server_manager; + WorldServerManager *server_manager; ClientManager *client_manager{}; }; diff --git a/loginserver/login_types.h b/loginserver/login_types.h index 1c8054a37..127514696 100644 --- a/loginserver/login_types.h +++ b/loginserver/login_types.h @@ -4,30 +4,30 @@ #pragma pack(1) // unencrypted base message header in all packets -struct LoginBaseMessage_Struct { +struct LoginBaseMessage { int32_t sequence; // request type/login sequence (2: handshake, 3: login, 4: serverlist, ...) bool compressed; // true: deflated int8_t encrypt_type; // 1: invert (unused) 2: des (2 for encrypted player logins and order expansions) (client uses what it sent, ignores in reply) int32_t unk3; // unused? }; -struct LoginBaseReplyMessage_Struct { +struct LoginBaseReplyMessage { bool success; // 0: failure (shows error string) 1: success int32_t error_str_id; // last error eqlsstr id, default: 101 (no error) char str[1]; // variable length, unknown (may be unused, this struct is a common pattern elsewhere) }; -struct LoginHandShakeReply_Struct { - LoginBaseMessage_Struct base_header; - LoginBaseReplyMessage_Struct base_reply; - char unknown[1]; // variable length string +struct LoginHandShakeReply { + LoginBaseMessage base_header; + LoginBaseReplyMessage base_reply; + char unknown[1]; // variable length string }; // variable length, can use directly if not serializing strings -struct PlayerLoginReply_Struct { +struct PlayerLoginReply { // base header excluded to make struct data easier to encrypt - //LoginBaseMessage_Struct base_header; - LoginBaseReplyMessage_Struct base_reply; + //LoginBaseMessage base_header; + LoginBaseReplyMessage base_reply; int8_t unk1; // (default: 0) int8_t unk2; // (default: 0) @@ -47,7 +47,7 @@ struct PlayerLoginReply_Struct { }; // variable length, for reference -struct LoginClientServerData_Struct { +struct LoginClientServerData { char ip[1]; int32_t server_type; // legends, preferred, standard int32_t server_id; @@ -59,24 +59,24 @@ struct LoginClientServerData_Struct { }; // variable length, for reference -struct ServerListReply_Struct { - LoginBaseMessage_Struct base_header; - LoginBaseReplyMessage_Struct base_reply; +struct ServerListReply { + LoginBaseMessage base_header; + LoginBaseReplyMessage base_reply; - int32_t server_count; - LoginClientServerData_Struct servers[0]; + int32_t server_count; + LoginClientServerData servers[0]; }; -struct PlayEverquestRequest_Struct { - LoginBaseMessage_Struct base_header; - uint32 server_number; +struct PlayEverquestRequest { + LoginBaseMessage base_header; + uint32 server_number; }; // SCJoinServerReply -struct PlayEverquestResponse_Struct { - LoginBaseMessage_Struct base_header; - LoginBaseReplyMessage_Struct base_reply; - uint32 server_number; +struct PlayEverquestResponse { + LoginBaseMessage base_header; + LoginBaseReplyMessage base_reply; + uint32 server_number; }; #pragma pack() @@ -95,6 +95,34 @@ enum LSClientStatus { cs_logged_in }; +struct LoginWorldContext { + std::string long_name; + std::string short_name; + std::string password; + std::string password_hash; + int64 admin_id = 0; +}; + +struct LoginWorldAdminAccountContext { + int64 id; + std::string username; + std::string password; + std::string password_hash; + std::string email; + std::string first_name; + std::string last_name; + std::string ip_address; +}; + +struct LoginAccountContext { + std::string username; + std::string password; + std::string email; + std::string source_loginserver = "local"; + uint32 login_account_id = 0; + bool password_is_encrypted = false; +}; + namespace LS { namespace ServerStatusFlags { enum eServerStatusFlags { @@ -123,13 +151,13 @@ namespace LS { }; namespace ErrStr { - constexpr static int ERROR_NONE = 101; // No Error - constexpr static int ERROR_UNKNOWN = 102; // Error - Unknown Error Occurred - constexpr static int ERROR_ACTIVE_CHARACTER = 111; // Error 1018: You currently have an active character on that EverQuest Server, please allow a minute for synchronization and try again. - constexpr static int ERROR_SERVER_UNAVAILABLE = 326; // That server is currently unavailable. Please check the EverQuest webpage for current server status and try again later. - constexpr static int ERROR_ACCOUNT_SUSPENDED = 337; // This account is currently suspended. Please contact customer service for more information. - constexpr static int ERROR_ACCOUNT_BANNED = 338; // This account is currently banned. Please contact customer service for more information. - constexpr static int ERROR_WORLD_MAX_CAPACITY = 339; // The world server is currently at maximum capacity and not allowing further logins until the number of players online decreases. Please try again later. + constexpr static int ERROR_NONE = 101; // No Error + constexpr static int ERROR_UNKNOWN = 102; // Error - Unknown Error Occurred + constexpr static int ERROR_ACTIVE_CHARACTER = 111; // Error 1018: You currently have an active character on that EverQuest Server, please allow a minute for synchronization and try again. + constexpr static int ERROR_SERVER_UNAVAILABLE = 326; // That server is currently unavailable. Please check the EverQuest webpage for current server status and try again later. + constexpr static int ERROR_ACCOUNT_SUSPENDED = 337; // This account is currently suspended. Please contact customer service for more information. + constexpr static int ERROR_ACCOUNT_BANNED = 338; // This account is currently banned. Please contact customer service for more information. + constexpr static int ERROR_WORLD_MAX_CAPACITY = 339; // The world server is currently at maximum capacity and not allowing further logins until the number of players online decreases. Please try again later. }; } diff --git a/loginserver/login_util/login_schema.sql b/loginserver/login_util/login_schema.sql index ad9805cf8..f491c4af1 100644 --- a/loginserver/login_util/login_schema.sql +++ b/loginserver/login_util/login_schema.sql @@ -33,10 +33,6 @@ CREATE TABLE `login_server_list_types` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; -INSERT INTO `login_server_list_types` (`id`, `description`) VALUES ('1', 'Legends'), -('2', 'Preferred'), -('3', 'Standard'); - DROP TABLE IF EXISTS `login_world_servers`; CREATE TABLE `login_world_servers` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, diff --git a/loginserver/loginserver_command_handler.cpp b/loginserver/loginserver_command_handler.cpp index 152266902..a445bcb4e 100644 --- a/loginserver/loginserver_command_handler.cpp +++ b/loginserver/loginserver_command_handler.cpp @@ -5,15 +5,11 @@ #include "login_server.h" #include "loginserver_webserver.h" #include "account_management.h" +#include "../common/repositories/login_api_tokens_repository.h" extern LoginServer server; namespace LoginserverCommandHandler { - - /** - * @param argc - * @param argv - */ void CommandHandler(int argc, char **argv) { if (argc == 1) { return; } @@ -22,14 +18,7 @@ namespace LoginserverCommandHandler { cmd.parse(argc, argv, argh::parser::PREFER_PARAM_FOR_UNREG_OPTION); EQEmuCommand::DisplayDebug(cmd); - /** - * Declare command mapping - */ auto function_map = EQEmuCommand::function_map; - - /** - * Register commands - */ function_map["login-user:check-credentials"] = &LoginserverCommandHandler::CheckLoginserverUserCredentials; function_map["login-user:check-external-credentials"] = &LoginserverCommandHandler::CheckExternalLoginserverUserCredentials; function_map["login-user:create"] = &LoginserverCommandHandler::CreateLocalLoginserverAccount; @@ -43,12 +32,6 @@ namespace LoginserverCommandHandler { EQEmuCommand::HandleMenu(function_map, cmd, argc, argv); } - /** - * @param argc - * @param argv - * @param cmd - * @param description - */ void CreateLoginserverApiToken(int argc, char **argv, argh::parser &cmd, std::string &description) { description = "Creates Loginserver API Token"; @@ -69,22 +52,22 @@ namespace LoginserverCommandHandler { bool can_write = cmd[{"-w", "--write"}]; if (!can_read || !can_write) { - LogInfo("[{0}] --read or --write must be set or both!", __func__); + LogInfo("--read or --write must be set or both!"); exit(1); } - std::string token = server.db->CreateLoginserverApiToken(can_write, can_read); - if (!token.empty()) { - LogInfo("[{0}] Created Loginserver API token [{1}]", __func__, token); + auto t = LoginApiTokensRepository::NewEntity(); + t.can_read = can_read; + t.can_write = can_write; + t.token = EQ::Util::UUID::Generate().ToString(); + t.created_at = std::time(nullptr); + + auto created = LoginApiTokensRepository::InsertOne(database, t); + if (created.id) { + LogInfo("Created Loginserver API token [{}] [{}]", created.id, created.token); } } - /** - * @param argc - * @param argv - * @param cmd - * @param description - */ void ListLoginserverApiTokens(int argc, char **argv, argh::parser &cmd, std::string &description) { description = "Lists Loginserver API Tokens"; @@ -96,9 +79,10 @@ namespace LoginserverCommandHandler { server.token_manager = new LoginserverWebserver::TokenManager; server.token_manager->LoadApiTokens(); - for (auto &it : server.token_manager->loaded_api_tokens) { + for (auto &it: server.token_manager->loaded_api_tokens) { LogInfo( - "token [{0}] can_write [{1}] can_read [{2}]", + "token id [{}] [{}] can_write [{}] can_read [{}]", + it.second.id, it.second.token, it.second.can_write, it.second.can_read @@ -106,12 +90,6 @@ namespace LoginserverCommandHandler { } } - /** - * @param argc - * @param argv - * @param cmd - * @param description - */ void CreateLocalLoginserverAccount(int argc, char **argv, argh::parser &cmd, std::string &description) { description = "Creates Local Loginserver Account"; @@ -130,19 +108,14 @@ namespace LoginserverCommandHandler { EQEmuCommand::ValidateCmdInput(arguments, options, cmd, argc, argv); - AccountManagement::CreateLoginServerAccount( - cmd(2).str(), - cmd(3).str(), - cmd("--email").str() - ); + LoginAccountContext c; + c.username = cmd(2).str(); + c.password = cmd(3).str(); + c.email = cmd("--email").str(); + + AccountManagement::CreateLoginServerAccount(c); } - /** - * @param argc - * @param argv - * @param cmd - * @param description - */ void CreateLoginserverWorldAdminAccount(int argc, char **argv, argh::parser &cmd, std::string &description) { description = "Creates Loginserver World Administrator Account"; @@ -167,12 +140,6 @@ namespace LoginserverCommandHandler { ); } - /** - * @param argc - * @param argv - * @param cmd - * @param description - */ void CheckLoginserverUserCredentials(int argc, char **argv, argh::parser &cmd, std::string &description) { description = "Check user login credentials"; @@ -189,20 +156,15 @@ namespace LoginserverCommandHandler { EQEmuCommand::ValidateCmdInput(arguments, options, cmd, argc, argv); - auto res = AccountManagement::CheckLoginserverUserCredentials( - cmd(2).str(), - cmd(3).str() - ); + LoginAccountContext c; + c.username = cmd(2).str(); + c.password = cmd(3).str(); - LogInfo("Credentials were {0}", res != 0 ? "accepted" : "not accepted"); + auto res = AccountManagement::CheckLoginserverUserCredentials(c); + + LogInfo("Credentials were {}", res != 0 ? "accepted" : "not accepted"); } - /** - * @param argc - * @param argv - * @param cmd - * @param description - */ void UpdateLoginserverUserCredentials(int argc, char **argv, argh::parser &cmd, std::string &description) { description = "Change user login credentials"; @@ -219,18 +181,12 @@ namespace LoginserverCommandHandler { EQEmuCommand::ValidateCmdInput(arguments, options, cmd, argc, argv); - AccountManagement::UpdateLoginserverUserCredentials( - cmd(2).str(), - cmd(3).str() - ); + LoginAccountContext c; + c.username = cmd(2).str(); + c.password = cmd(3).str(); + AccountManagement::UpdateLoginserverUserCredentials(c); } - /** - * @param argc - * @param argv - * @param cmd - * @param description - */ void CheckExternalLoginserverUserCredentials(int argc, char **argv, argh::parser &cmd, std::string &description) { description = "Check user external login credentials"; @@ -247,20 +203,14 @@ namespace LoginserverCommandHandler { EQEmuCommand::ValidateCmdInput(arguments, options, cmd, argc, argv); - auto res = AccountManagement::CheckExternalLoginserverUserCredentials( - cmd(2).str(), - cmd(3).str() - ); + LoginAccountContext c; + c.username = cmd(2).str(); + c.password = cmd(3).str(); + auto res = AccountManagement::CheckExternalLoginserverUserCredentials(c); - LogInfo("Credentials were {0}", res ? "accepted" : "not accepted"); + LogInfo("Credentials were {}", res ? "accepted" : "not accepted"); } - /** - * @param argc - * @param argv - * @param cmd - * @param description - */ void UpdateLoginserverWorldAdminAccountPassword(int argc, char **argv, argh::parser &cmd, std::string &description) { description = "Update world admin account password"; @@ -277,18 +227,12 @@ namespace LoginserverCommandHandler { EQEmuCommand::ValidateCmdInput(arguments, options, cmd, argc, argv); - AccountManagement::UpdateLoginserverWorldAdminAccountPasswordByName( - cmd(2).str(), - cmd(3).str() - ); + LoginAccountContext c; + c.username = cmd(2).str(); + c.password = cmd(3).str(); + AccountManagement::UpdateLoginserverWorldAdminAccountPasswordByName(c); } - /** - * @param argc - * @param argv - * @param cmd - * @param description - */ void HealthCheckLogin(int argc, char **argv, argh::parser &cmd, std::string &description) { description = "Checks login health using a test user"; diff --git a/loginserver/loginserver_webserver.cpp b/loginserver/loginserver_webserver.cpp index f7c353181..a880feb85 100644 --- a/loginserver/loginserver_webserver.cpp +++ b/loginserver/loginserver_webserver.cpp @@ -1,9 +1,10 @@ #include "loginserver_webserver.h" -#include "server_manager.h" +#include "world_server_manager.h" #include "login_server.h" #include "../common/json/json.h" #include "../common/strings.h" #include "account_management.h" +#include "../common/repositories/login_api_tokens_repository.h" extern LoginServer server; @@ -13,9 +14,6 @@ namespace LoginserverWebserver { constexpr static int HTTP_RESPONSE_BAD_REQUEST = 400; constexpr static int HTTP_RESPONSE_UNAUTHORIZED = 401; - /** - * @param api - */ void RegisterRoutes(httplib::Server &api) { server.token_manager = new LoginserverWebserver::TokenManager; @@ -27,21 +25,21 @@ namespace LoginserverWebserver { return; } - Json::Value response; - auto iter = server.server_manager->getWorldServers().begin(); - while (iter != server.server_manager->getWorldServers().end()) { + Json::Value response; + auto iter = server.server_manager->GetWorldServers().begin(); + for (const auto &s: server.server_manager->GetWorldServers()) { Json::Value row; - row["server_long_name"] = (*iter)->GetServerLongName(); - row["server_short_name"] = (*iter)->GetServerShortName(); - row["server_list_type_id"] = (*iter)->GetServerListID(); - row["server_status"] = (*iter)->GetStatus(); - row["zones_booted"] = (*iter)->GetZonesBooted(); - row["local_ip"] = (*iter)->GetLocalIP(); - row["remote_ip"] = (*iter)->GetRemoteIP(); - row["players_online"] = (*iter)->GetPlayersOnline(); - row["world_id"] = (*iter)->GetServerId(); + row["server_long_name"] = s->GetServerLongName(); + row["server_short_name"] = s->GetServerShortName(); + row["server_list_type_id"] = s->GetServerListID(); + row["server_status"] = s->GetStatus(); + row["zones_booted"] = s->GetZonesBooted(); + row["local_ip"] = s->GetLocalIP(); + row["remote_ip"] = s->GetRemoteIP(); + row["players_online"] = s->GetPlayersOnline(); + row["world_id"] = s->GetServerId(); + response.append(row); - ++iter; } LoginserverWebserver::SendResponse(response, res); @@ -54,10 +52,10 @@ namespace LoginserverWebserver { return; } - Json::Value request_body = LoginserverWebserver::ParseRequestBody(request); - std::string username = request_body.get("username", "").asString(); - std::string password = request_body.get("password", "").asString(); - std::string email = request_body.get("email", "").asString(); + Json::Value req = LoginserverWebserver::ParseRequestBody(request); + std::string username = req.get("username", "").asString(); + std::string password = req.get("password", "").asString(); + std::string email = req.get("email", "").asString(); Json::Value response; if (username.empty() || password.empty()) { @@ -67,7 +65,12 @@ namespace LoginserverWebserver { return; } - int32 account_created_id = AccountManagement::CreateLoginServerAccount(username, password, email); + LoginAccountContext c; + c.username = username; + c.password = password; + c.email = email; + + int32 account_created_id = AccountManagement::CreateLoginServerAccount(c); if (account_created_id > 0) { response["message"] = "Account created successfully!"; response["data"]["account_id"] = account_created_id; @@ -91,11 +94,11 @@ namespace LoginserverWebserver { return; } - Json::Value request_body = LoginserverWebserver::ParseRequestBody(request); - std::string username = request_body.get("username", "").asString(); - std::string password = request_body.get("password", "").asString(); - std::string email = request_body.get("email", "").asString(); - uint32 login_account_id = request_body.get("login_account_id", "").asInt(); + Json::Value req = LoginserverWebserver::ParseRequestBody(request); + std::string username = req.get("username", "").asString(); + std::string password = req.get("password", "").asString(); + std::string email = req.get("email", "").asString(); + uint32 login_account_id = req.get("login_account_id", "").asInt(); Json::Value response; if (username.empty() || password.empty()) { @@ -105,14 +108,14 @@ namespace LoginserverWebserver { return; } - std::string source_loginserver = "eqemu"; - int32 account_created_id = AccountManagement::CreateLoginServerAccount( - username, - password, - email, - source_loginserver, - login_account_id - ); + LoginAccountContext c; + c.username = username; + c.password = password; + c.email = email; + c.source_loginserver = "eqemu"; + c.login_account_id = login_account_id; + + int32 account_created_id = AccountManagement::CreateLoginServerAccount(c); if (account_created_id > 0) { response["message"] = "Account created successfully!"; @@ -137,9 +140,9 @@ namespace LoginserverWebserver { return; } - Json::Value request_body = LoginserverWebserver::ParseRequestBody(request); - std::string username = request_body.get("username", "").asString(); - std::string password = request_body.get("password", "").asString(); + Json::Value req = LoginserverWebserver::ParseRequestBody(request); + std::string username = req.get("username", "").asString(); + std::string password = req.get("password", "").asString(); Json::Value response; if (username.empty() || password.empty()) { @@ -149,11 +152,11 @@ namespace LoginserverWebserver { return; } - uint32 login_account_id = AccountManagement::CheckLoginserverUserCredentials( - username, - password - ); + LoginAccountContext c; + c.username = username; + c.password = password; + uint32 login_account_id = AccountManagement::CheckLoginserverUserCredentials(c); if (login_account_id > 0) { response["message"] = "Credentials valid!"; response["data"]["account_id"] = login_account_id; @@ -173,9 +176,9 @@ namespace LoginserverWebserver { return; } - Json::Value request_body = LoginserverWebserver::ParseRequestBody(request); - std::string username = request_body.get("username", "").asString(); - std::string password = request_body.get("password", "").asString(); + Json::Value req = LoginserverWebserver::ParseRequestBody(request); + std::string username = req.get("username", "").asString(); + std::string password = req.get("password", "").asString(); Json::Value response; if (username.empty() || password.empty()) { @@ -185,24 +188,20 @@ namespace LoginserverWebserver { return; } - Database::DbLoginServerAccount - login_server_account = server.db->GetLoginServerAccountByAccountName( - username - ); + LoginAccountContext c; + c.username = username; + c.password = password; - if (!login_server_account.loaded) { + auto a = LoginAccountsRepository::GetAccountFromContext(database, c); + if (!a.id) { res.status = HTTP_RESPONSE_BAD_REQUEST; response["error"] = "Failed to find associated loginserver account!"; LoginserverWebserver::SendResponse(response, res); return; } - bool credentials_valid = AccountManagement::UpdateLoginserverUserCredentials( - username, - password - ); - - if (credentials_valid) { + bool success = AccountManagement::UpdateLoginserverUserCredentials(c); + if (success) { response["message"] = "Loginserver account credentials updated!"; } else { @@ -214,16 +213,15 @@ namespace LoginserverWebserver { } ); - api.Post( "/v1/account/credentials/update/external", [](const httplib::Request &request, httplib::Response &res) { if (!LoginserverWebserver::TokenManager::AuthCanWrite(request, res)) { return; } - Json::Value request_body = LoginserverWebserver::ParseRequestBody(request); - std::string username = request_body.get("username", "").asString(); - std::string password = request_body.get("password", "").asString(); + Json::Value req = LoginserverWebserver::ParseRequestBody(request); + std::string username = req.get("username", "").asString(); + std::string password = req.get("password", "").asString(); Json::Value response; if (username.empty() || password.empty()) { @@ -234,25 +232,20 @@ namespace LoginserverWebserver { std::string source_loginserver = "eqemu"; - Database::DbLoginServerAccount - login_server_account = server.db->GetLoginServerAccountByAccountName( - username, - source_loginserver - ); + LoginAccountContext c; + c.username = username; + c.password = password; + c.source_loginserver = source_loginserver; - if (!login_server_account.loaded) { + auto a = LoginAccountsRepository::GetAccountFromContext(database, c); + if (!a.id) { response["error"] = "Failed to find associated loginserver account!"; LoginserverWebserver::SendResponse(response, res); return; } - bool credentials_valid = AccountManagement::UpdateLoginserverUserCredentials( - username, - password, - source_loginserver - ); - - if (credentials_valid) { + bool success = AccountManagement::UpdateLoginserverUserCredentials(c); + if (success) { response["message"] = "Loginserver account credentials updated!"; } else { @@ -269,9 +262,9 @@ namespace LoginserverWebserver { return; } - Json::Value request_body = LoginserverWebserver::ParseRequestBody(request); - std::string username = request_body.get("username", "").asString(); - std::string password = request_body.get("password", "").asString(); + Json::Value req = LoginserverWebserver::ParseRequestBody(request); + std::string username = req.get("username", "").asString(); + std::string password = req.get("password", "").asString(); Json::Value response; if (username.empty() || password.empty()) { @@ -280,10 +273,10 @@ namespace LoginserverWebserver { return; } - uint32 account_id = AccountManagement::CheckExternalLoginserverUserCredentials( - username, - password - ); + LoginAccountContext c; + c.username = username; + c.password = password; + uint32 account_id = AccountManagement::CheckExternalLoginserverUserCredentials(c); if (account_id > 0) { response["message"] = "Credentials valid!"; @@ -301,7 +294,7 @@ namespace LoginserverWebserver { api.Get( "/probes/healthcheck", [](const httplib::Request &request, httplib::Response &res) { Json::Value response; - uint32 login_response = AccountManagement::HealthCheckUserLogin(); + uint32 login_response = AccountManagement::HealthCheckUserLogin(); response["status"] = login_response; if (login_response == 0) { @@ -317,10 +310,6 @@ namespace LoginserverWebserver { ); } - /** - * @param payload - * @param res - */ void SendResponse(const Json::Value &payload, httplib::Response &res) { if (res.get_header_value("response_set") == "true") { @@ -342,10 +331,6 @@ namespace LoginserverWebserver { res.set_content(response_payload.str(), "application/json"); } - /** - * @param payload - * @param res - */ Json::Value ParseRequestBody(const httplib::Request &request) { Json::Value request_body; @@ -364,13 +349,9 @@ namespace LoginserverWebserver { return request_body; } - /** - * @param request - * @param res - */ bool LoginserverWebserver::TokenManager::AuthCanRead(const httplib::Request &request, httplib::Response &res) { - LoginserverWebserver::TokenManager::token_data + LoginserverWebserver::TokenManager::Token user_token = LoginserverWebserver::TokenManager::CheckApiAuthorizationHeaders(request); if (!user_token.can_read) { @@ -384,7 +365,7 @@ namespace LoginserverWebserver { res.set_header("response_set", "true"); LogWarning( - "AuthCanRead access failure remote_address [{0}] user_agent [{1}]", + "AuthCanRead access failure remote_address [{}] user_agent [{}]", user_token.remote_address, user_token.user_agent ); @@ -395,13 +376,9 @@ namespace LoginserverWebserver { return true; } - /** - * @param request - * @param res - */ bool LoginserverWebserver::TokenManager::AuthCanWrite(const httplib::Request &request, httplib::Response &res) { - LoginserverWebserver::TokenManager::token_data + LoginserverWebserver::TokenManager::Token user_token = LoginserverWebserver::TokenManager::CheckApiAuthorizationHeaders(request); if (!user_token.can_write) { @@ -415,7 +392,7 @@ namespace LoginserverWebserver { res.set_header("response_set", "true"); LogWarning( - "AuthCanWrite access failure remote_address [{0}] user_agent [{1}]", + "AuthCanWrite access failure remote_address [{}] user_agent [{}]", user_token.remote_address, user_token.user_agent ); @@ -426,20 +403,16 @@ namespace LoginserverWebserver { return true; } - /** - * @param request - * @return - */ - LoginserverWebserver::TokenManager::token_data + LoginserverWebserver::TokenManager::Token LoginserverWebserver::TokenManager::CheckApiAuthorizationHeaders( const httplib::Request &request ) { std::string authorization_key; - LoginserverWebserver::TokenManager::token_data user_token{}; + LoginserverWebserver::TokenManager::Token user_token{}; - for (const auto &header : request.headers) { + for (const auto &header: request.headers) { auto header_key = header.first; auto header_value = header.second; if (header_key == "Authorization") { @@ -460,7 +433,7 @@ namespace LoginserverWebserver { } LogDebug( - "Authentication Request | remote_address [{0}] user_agent [{1}] authorization_key [{2}] request_path [{3}]", + "Authentication Request | remote_address [{}] user_agent [{}] authorization_key [{}] request_path [{}]", user_token.remote_address, user_token.user_agent, authorization_key, @@ -470,43 +443,24 @@ namespace LoginserverWebserver { return user_token; } - /** - * Loads API Tokens - */ void TokenManager::LoadApiTokens() { - auto results = server.db->GetLoginserverApiTokens(); - int token_count = 0; - for (auto row = results.begin(); row != results.end(); ++row) { - LoginserverWebserver::TokenManager::token_data token_data; - token_data.token = row[0]; - token_data.can_write = Strings::ToInt(row[1]) > 0; - token_data.can_read = Strings::ToInt(row[2]) > 0; + int token_count = 0; - LogDebug( - "Inserting api token to internal list [{0}] write {1} read {2}", - token_data.token, - token_data.can_read, - token_data.can_write - ); - - server.token_manager->loaded_api_tokens.emplace( - std::make_pair( - token_data.token, - token_data - ) - ); + for (auto &t: LoginApiTokensRepository::GetWhere(database, "TRUE ORDER BY id ASC")) { + LoginserverWebserver::TokenManager::Token td; + td.id = t.id; + td.token = t.token; + td.can_write = t.can_write; + td.can_read = t.can_read; + server.token_manager->loaded_api_tokens.emplace(std::make_pair(td.token, td)); token_count++; } LogInfo("Loaded [{}] API token(s)", token_count); } - /** - * @param token - * @return - */ bool TokenManager::TokenExists(const std::string &token) { auto it = server.token_manager->loaded_api_tokens.find(token); @@ -514,11 +468,7 @@ namespace LoginserverWebserver { return !(it == server.token_manager->loaded_api_tokens.end()); } - /** - * @param token - * @return - */ - LoginserverWebserver::TokenManager::token_data TokenManager::GetToken( + LoginserverWebserver::TokenManager::Token TokenManager::GetToken( const std::string &token ) { diff --git a/loginserver/loginserver_webserver.h b/loginserver/loginserver_webserver.h index 7eb852892..9de510152 100644 --- a/loginserver/loginserver_webserver.h +++ b/loginserver/loginserver_webserver.h @@ -12,7 +12,8 @@ namespace LoginserverWebserver { public: TokenManager() = default; - struct token_data { + struct Token { + int id; std::string token; bool can_read; bool can_write; @@ -20,12 +21,12 @@ namespace LoginserverWebserver { std::string remote_address; }; - std::map loaded_api_tokens{}; + std::map loaded_api_tokens{}; void LoadApiTokens(); static bool TokenExists(const std::string &token); - token_data GetToken(const std::string &token); - static token_data CheckApiAuthorizationHeaders(const httplib::Request &request); + Token GetToken(const std::string &token); + static Token CheckApiAuthorizationHeaders(const httplib::Request &request); static bool AuthCanRead(const httplib::Request &request, httplib::Response &res); static bool AuthCanWrite(const httplib::Request &request, httplib::Response &res); }; diff --git a/loginserver/main.cpp b/loginserver/main.cpp index af02b878b..9047db8c8 100644 --- a/loginserver/main.cpp +++ b/loginserver/main.cpp @@ -12,18 +12,19 @@ #include "loginserver_command_handler.h" #include "../common/strings.h" #include "../common/path_manager.h" +#include "../common/database.h" +#include "../common/events/player_event_logs.h" +#include "../common/zone_store.h" #include #include #include #include #include -LoginServer server; -EQEmuLogSys LogSys; -bool run_server = true; -PathManager path; +LoginServer server; +bool run_server = true; +Database database; -void ResolveAddresses(); void CatchSignal(int sig_num) { } @@ -32,26 +33,25 @@ void LoadDatabaseConnection() { LogInfo("MySQL Database Init"); - server.db = new Database( + if (!database.Connect( + server.config.GetVariableString("database", "host", "localhost"), server.config.GetVariableString("database", "user", "root"), server.config.GetVariableString("database", "password", ""), - server.config.GetVariableString("database", "host", "localhost"), - server.config.GetVariableString("database", "port", "3306"), - server.config.GetVariableString("database", "db", "peq") - ); - + server.config.GetVariableString("database", "db", "peq"), + server.config.GetVariableInt("database", "port", 3306) + )) { + LogError("Cannot continue without a database connection"); + std::exit(1); + } } void LoadServerConfig() { server.config = EQ::JsonConfigFile::Load( - fmt::format("{}/login.json", path.GetServerPath()) + fmt::format("{}/login.json", PathManager::Instance()->GetServerPath()) ); LogInfo("Config System Init"); - /** - * Worldservers - */ server.options.RejectDuplicateServers( server.config.GetVariableBool( "worldservers", @@ -82,41 +82,33 @@ void LoadServerConfig() ) ); - /** - * Expansion Display Settings - */ server.options.DisplayExpansions( server.config.GetVariableBool( "client_configuration", "display_expansions", false - )); //disable by default + ) + ); server.options.MaxExpansions( server.config.GetVariableInt( "client_configuration", "max_expansions_mask", 67108863 - )); //enable display of all expansions - - /** - * Account - */ - server.options.AutoCreateAccounts(server.config.GetVariableBool("account", "auto_create_accounts", true)); - server.options.AutoLinkAccounts(server.config.GetVariableBool("account", "auto_link_accounts", false)); - -#ifdef LSPX - server.options.EQEmuLoginServerAddress( - server.config.GetVariableString( - "general", - "eqemu_loginserver_address", - "login.eqemulator.net:5999" ) ); -#endif - /** - * Default Loginserver Name (Don't change) - */ + server.options.AutoCreateAccounts(server.config.GetVariableBool("account", "auto_create_accounts", true)); + + if (std::getenv("LSPX")) { + server.options.EQEmuLoginServerAddress( + server.config.GetVariableString( + "general", + "eqemu_loginserver_address", + "login.eqemulator.net:5999" + ) + ); + } + server.options.DefaultLoginServerName( server.config.GetVariableString( "general", @@ -125,10 +117,6 @@ void LoadServerConfig() ) ); - /** - * Security - */ - #ifdef ENABLE_SECURITY server.options.EncryptionMode(server.config.GetVariableInt("security", "mode", 13)); #else @@ -136,14 +124,6 @@ void LoadServerConfig() #endif server.options.AllowTokenLogin(server.config.GetVariableBool("security", "allow_token_login", false)); - server.options.AllowPasswordLogin(server.config.GetVariableBool("security", "allow_password_login", true)); - server.options.UpdateInsecurePasswords( - server.config.GetVariableBool( - "security", - "update_insecure_passwords", - true - ) - ); } void start_web_server() @@ -151,7 +131,7 @@ void start_web_server() Sleep(1); int web_api_port = server.config.GetVariableInt("web_api", "port", 6000); - LogInfo("Webserver API now listening on port [{0}]", web_api_port); + LogInfo("Webserver API now listening on port [{}]", web_api_port); httplib::Server api; @@ -175,65 +155,43 @@ int main(int argc, char **argv) LogInfo("Logging System Init"); if (argc == 1) { - LogSys.LoadLogSettingsDefaults(); + EQEmuLogSys::Instance()->LoadLogSettingsDefaults(); } - path.LoadPaths(); + PathManager::Instance()->Init(); - /** - * Command handler - */ + // command handler if (argc > 1) { - LogSys.SilenceConsoleLogging(); + EQEmuLogSys::Instance()->SilenceConsoleLogging(); LoadServerConfig(); LoadDatabaseConnection(); - LogSys.LoadLogSettingsDefaults(); - LogSys.log_settings[Logs::Debug].log_to_console = static_cast(Logs::General); - LogSys.log_settings[Logs::Debug].is_category_enabled = 1; + EQEmuLogSys::Instance()->LoadLogSettingsDefaults(); + EQEmuLogSys::Instance()->log_settings[Logs::Debug].log_to_console = static_cast(Logs::General); + EQEmuLogSys::Instance()->log_settings[Logs::Debug].is_category_enabled = 1; LoginserverCommandHandler::CommandHandler(argc, argv); } LoadServerConfig(); - - /** - * mysql connect - */ LoadDatabaseConnection(); if (argc == 1) { - LogSys.SetDatabase(server.db) + EQEmuLogSys::Instance()->SetDatabase(&database) ->SetLogPath("logs") ->LoadLogDatabaseSettings() ->StartFileLogs(); } - /** - * make sure our database got created okay, otherwise cleanup and exit - */ - if (!server.db) { - LogError("Database Initialization Failure"); - LogInfo("Log System Shutdown"); - return 1; - } - - /** - * create server manager - */ LogInfo("Server Manager Init"); - server.server_manager = new ServerManager(); + server.server_manager = new WorldServerManager(); if (!server.server_manager) { LogError("Server Manager Failed to Start"); LogInfo("Database System Shutdown"); - delete server.db; return 1; } - /** - * create client manager - */ LogInfo("Client Manager Init"); server.client_manager = new ClientManager(); if (!server.client_manager) { @@ -242,7 +200,6 @@ int main(int argc, char **argv) delete server.server_manager; LogInfo("Database System Shutdown"); - delete server.db; return 1; } @@ -256,37 +213,33 @@ int main(int argc, char **argv) LogInfo("Server Started"); - /** - * Web API - */ bool web_api_enabled = server.config.GetVariableBool("web_api", "enabled", true); if (web_api_enabled) { std::thread web_api_thread(start_web_server); web_api_thread.detach(); } - LogInfo("[Config] [Account] CanAutoCreateAccounts [{0}]", server.options.CanAutoCreateAccounts()); - LogInfo("[Config] [ClientConfiguration] DisplayExpansions [{0}]", server.options.IsDisplayExpansions()); - LogInfo("[Config] [ClientConfiguration] MaxExpansions [{0}]", server.options.GetMaxExpansions()); + LogInfo("[Config] [Account] CanAutoCreateAccounts [{}]", server.options.CanAutoCreateAccounts()); + LogInfo("[Config] [ClientConfiguration] DisplayExpansions [{}]", server.options.IsDisplayExpansions()); + LogInfo("[Config] [ClientConfiguration] MaxExpansions [{}]", server.options.GetMaxExpansions()); -#ifdef LSPX - LogInfo("[Config] [Account] CanAutoLinkAccounts [{0}]", server.options.CanAutoLinkAccounts()); -#endif - LogInfo("[Config] [WorldServer] IsRejectingDuplicateServers [{0}]", server.options.IsRejectingDuplicateServers()); - LogInfo("[Config] [WorldServer] IsUnregisteredAllowed [{0}]", server.options.IsUnregisteredAllowed()); - LogInfo("[Config] [WorldServer] ShowPlayerCount [{0}]", server.options.IsShowPlayerCountEnabled()); + if (std::getenv("LSPX")) { + LogInfo("[Config] [Account] LSPX [on]"); + } + + LogInfo("[Config] [WorldServer] IsRejectingDuplicateServers [{}]", server.options.IsRejectingDuplicateServers()); + LogInfo("[Config] [WorldServer] IsUnregisteredAllowed [{}]", server.options.IsUnregisteredAllowed()); + LogInfo("[Config] [WorldServer] ShowPlayerCount [{}]", server.options.IsShowPlayerCountEnabled()); LogInfo( - "[Config] [WorldServer] DevAndTestServersListBottom [{0}]", + "[Config] [WorldServer] DevAndTestServersListBottom [{}]", server.options.IsWorldDevTestServersListBottom() ); LogInfo( - "[Config] [WorldServer] SpecialCharactersStartListBottom [{0}]", + "[Config] [WorldServer] SpecialCharactersStartListBottom [{}]", server.options.IsWorldSpecialCharacterStartListBottom() ); - LogInfo("[Config] [Security] GetEncryptionMode [{0}]", server.options.GetEncryptionMode()); - LogInfo("[Config] [Security] IsTokenLoginAllowed [{0}]", server.options.IsTokenLoginAllowed()); - LogInfo("[Config] [Security] IsPasswordLoginAllowed [{0}]", server.options.IsPasswordLoginAllowed()); - LogInfo("[Config] [Security] IsUpdatingInsecurePasswords [{0}]", server.options.IsUpdatingInsecurePasswords()); + LogInfo("[Config] [Security] GetEncryptionMode [{}]", server.options.GetEncryptionMode()); + LogInfo("[Config] [Security] IsTokenLoginAllowed [{}]", server.options.IsTokenLoginAllowed()); Timer keepalive(INTERSERVER_TIMER); // does auto-reconnect @@ -295,7 +248,7 @@ int main(int argc, char **argv) if (keepalive.Check()) { keepalive.Start(); - server.db->ping(); + database.ping(); } if (!run_server) { @@ -319,8 +272,5 @@ int main(int argc, char **argv) LogInfo("Server Manager Shutdown"); delete server.server_manager; - LogInfo("Database System Shutdown"); - delete server.db; - return 0; } diff --git a/loginserver/options.h b/loginserver/options.h index 5d4546f81..73d6f6216 100644 --- a/loginserver/options.h +++ b/loginserver/options.h @@ -1,124 +1,55 @@ #ifndef EQEMU_OPTIONS_H #define EQEMU_OPTIONS_H -/** - * Collects options on one object, because having a bunch of global variables floating around is - * really ugly and just a little dangerous. - */ class Options { public: - - /** - * Constructor: Default options - */ Options() : - allow_unregistered(true), - display_expansions(false), - max_expansions_mask(0), - encryption_mode(5), - reject_duplicate_servers(false), - allow_password_login(true), - allow_token_login(false), - auto_create_accounts(false) {} + m_allow_unregistered(true), + m_display_expansions(false), + m_max_expansions_mask(0), + m_encryption_mode(14), + m_reject_duplicate_servers(false), + m_allow_token_login(false), + m_auto_create_accounts(false) {} - /** - * Sets allow_unregistered. - */ - inline void AllowUnregistered(bool b) { allow_unregistered = b; } - - /** - * Returns the value of expansion display settings. - */ - inline void DisplayExpansions(bool b) { display_expansions = b; } - inline void MaxExpansions(int i) { max_expansions_mask = i; } - inline bool IsDisplayExpansions() const { return display_expansions; } - inline int GetMaxExpansions() const { return max_expansions_mask; } - - /** - * Returns the value of allow_unregistered. - */ - inline bool IsUnregisteredAllowed() const { return allow_unregistered; } - - /** - * Sets encryption_mode. - */ - inline void EncryptionMode(int m) { encryption_mode = m; } - - /** - * Returns the value of encryption_mode. - */ - inline int GetEncryptionMode() const { return encryption_mode; } - - /** - * Sets whether we are rejecting duplicate servers or not. - */ - inline void RejectDuplicateServers(bool b) { reject_duplicate_servers = b; } - - /** - * Returns whether we are rejecting duplicate servers or not. - */ - inline bool IsRejectingDuplicateServers() { return reject_duplicate_servers; } - - inline void AllowTokenLogin(bool b) { allow_token_login = b; } - inline bool IsTokenLoginAllowed() const { return allow_token_login; } - - inline void AllowPasswordLogin(bool b) { allow_password_login = b; } - inline bool IsPasswordLoginAllowed() const { return allow_password_login; } - - inline void AutoCreateAccounts(bool b) { auto_create_accounts = b; } - inline bool CanAutoCreateAccounts() const { return auto_create_accounts; } - - inline void AutoLinkAccounts(bool b) { auto_link_accounts = b; } - inline bool CanAutoLinkAccounts() const { return auto_link_accounts; } - - inline void EQEmuLoginServerAddress(const std::string& v) { eqemu_loginserver_address = v; } - inline std::string GetEQEmuLoginServerAddress() const { return eqemu_loginserver_address; } - - inline void DefaultLoginServerName(const std::string& v) { default_loginserver_name = v; } - inline std::string GetDefaultLoginServerName() const { return default_loginserver_name; } - - inline void UpdateInsecurePasswords(bool b) { update_insecure_passwords = b; } - inline bool IsUpdatingInsecurePasswords() const { return update_insecure_passwords; } - - inline bool IsShowPlayerCountEnabled() const - { - return show_player_count; - } - inline void SetShowPlayerCount(bool show_player_count) - { - Options::show_player_count = show_player_count; - } - inline bool IsWorldDevTestServersListBottom() const { return world_dev_test_servers_list_bottom; } - inline void SetWorldDevTestServersListBottom(bool dev_test_servers_list_bottom) - { - Options::world_dev_test_servers_list_bottom = dev_test_servers_list_bottom; - } - - inline bool IsWorldSpecialCharacterStartListBottom() const - { - return world_special_character_start_list_bottom; - } - inline void SetWorldSpecialCharacterStartListBottom(bool world_special_character_start_list_bottom) - { - Options::world_special_character_start_list_bottom = world_special_character_start_list_bottom; - } + inline void AllowUnregistered(bool b) { m_allow_unregistered = b; } + inline void DisplayExpansions(bool b) { m_display_expansions = b; } + inline void MaxExpansions(int i) { m_max_expansions_mask = i; } + inline bool IsDisplayExpansions() const { return m_display_expansions; } + inline int GetMaxExpansions() const { return m_max_expansions_mask; } + inline bool IsUnregisteredAllowed() const { return m_allow_unregistered; } + inline void EncryptionMode(int m) { m_encryption_mode = m; } + inline int GetEncryptionMode() const { return m_encryption_mode; } + inline void RejectDuplicateServers(bool b) { m_reject_duplicate_servers = b; } + inline bool IsRejectingDuplicateServers() { return m_reject_duplicate_servers; } + inline void AllowTokenLogin(bool b) { m_allow_token_login = b; } + inline bool IsTokenLoginAllowed() const { return m_allow_token_login; } + inline void AutoCreateAccounts(bool b) { m_auto_create_accounts = b; } + inline bool CanAutoCreateAccounts() const { return m_auto_create_accounts; } + inline void EQEmuLoginServerAddress(const std::string &v) { m_eqemu_loginserver_address = v; } + inline std::string GetEQEmuLoginServerAddress() const { return m_eqemu_loginserver_address; } + inline void DefaultLoginServerName(const std::string &v) { m_default_loginserver_name = v; } + inline std::string GetDefaultLoginServerName() const { return m_default_loginserver_name; } + inline bool IsShowPlayerCountEnabled() const { return m_show_player_count; } + inline void SetShowPlayerCount(bool show_player_count) { m_show_player_count = show_player_count; } + inline bool IsWorldDevTestServersListBottom() const { return m_world_dev_list_bottom; } + inline void SetWorldDevTestServersListBottom(bool list_bottom) { m_world_dev_list_bottom = list_bottom; } + inline bool IsWorldSpecialCharacterStartListBottom() const { return m_special_char_list_bottom; } + inline void SetWorldSpecialCharacterStartListBottom(bool list_bottom) { m_special_char_list_bottom = list_bottom; } private: - bool allow_unregistered; - bool display_expansions; - bool reject_duplicate_servers; - bool world_dev_test_servers_list_bottom; - bool world_special_character_start_list_bottom; - bool allow_token_login; - bool allow_password_login; - bool show_player_count; - bool auto_create_accounts; - bool auto_link_accounts; - bool update_insecure_passwords; - int encryption_mode; - int max_expansions_mask; - std::string eqemu_loginserver_address; - std::string default_loginserver_name; + bool m_allow_unregistered; + bool m_display_expansions; + bool m_reject_duplicate_servers; + bool m_world_dev_list_bottom; + bool m_special_char_list_bottom; + bool m_allow_token_login; + bool m_show_player_count; + bool m_auto_create_accounts; + int m_encryption_mode; + int m_max_expansions_mask; + std::string m_eqemu_loginserver_address; + std::string m_default_loginserver_name; }; diff --git a/loginserver/server_manager.cpp b/loginserver/server_manager.cpp deleted file mode 100644 index f46a6c925..000000000 --- a/loginserver/server_manager.cpp +++ /dev/null @@ -1,248 +0,0 @@ -#include "server_manager.h" -#include "login_server.h" -#include "login_types.h" -#include - -#include "../common/eqemu_logsys.h" -#include "../common/ip_util.h" - -extern LoginServer server; -extern bool run_server; - -ServerManager::ServerManager() -{ - int listen_port = server.config.GetVariableInt("general", "listen_port", 5998); - - m_server_connection = std::make_unique(); - EQ::Net::ServertalkServerOptions opts; - opts.port = listen_port; - opts.ipv6 = false; - m_server_connection->Listen(opts); - - LogInfo("Loginserver now listening on port [{0}]", listen_port); - - m_server_connection->OnConnectionIdentified( - "World", [this](std::shared_ptr world_connection) { - LogInfo( - "New World Server connection from {0}:{1}", - world_connection->Handle()->RemoteIP(), - world_connection->Handle()->RemotePort() - ); - - auto iter = m_world_servers.begin(); - while (iter != m_world_servers.end()) { - if ((*iter)->GetConnection()->Handle()->RemoteIP().compare(world_connection->Handle()->RemoteIP()) == - 0 && - (*iter)->GetConnection()->Handle()->RemotePort() == world_connection->Handle()->RemotePort()) { - - LogInfo( - "World server already existed for {0}:{1}, removing existing connection.", - world_connection->Handle()->RemoteIP(), - world_connection->Handle()->RemotePort() - ); - - m_world_servers.erase(iter); - break; - } - - ++iter; - } - - m_world_servers.push_back(std::make_unique(world_connection)); - } - ); - - m_server_connection->OnConnectionRemoved( - "World", [this](std::shared_ptr c) { - auto iter = m_world_servers.begin(); - while (iter != m_world_servers.end()) { - if ((*iter)->GetConnection()->GetUUID() == c->GetUUID()) { - LogInfo( - "World server {0} has been disconnected, removing.", - (*iter)->GetServerLongName() - ); - m_world_servers.erase(iter); - return; - } - - ++iter; - } - } - ); -} - -ServerManager::~ServerManager() = default; - -/** - * @param client - * @param sequence - * @return - */ -std::unique_ptr ServerManager::CreateServerListPacket(Client *client, uint32 sequence) -{ - unsigned int server_count = 0; - in_addr in{}; - in.s_addr = client->GetConnection()->GetRemoteIP(); - std::string client_ip = inet_ntoa(in); - - LogDebug("ServerManager::CreateServerListPacket via client address [{0}]", client_ip); - - for (const auto& world_server : m_world_servers) - { - if (world_server->IsAuthorized()) { - ++server_count; - } - } - - SerializeBuffer buf; - - // LoginBaseMessage_Struct header - buf.WriteInt32(sequence); - buf.WriteInt8(0); - buf.WriteInt8(0); - buf.WriteInt32(0); - - // LoginBaseReplyMessage_Struct - buf.WriteInt8(true); // success (no error) - buf.WriteInt32(0x65); // 101 "No Error" eqlsstr - buf.WriteString(""); - - // ServerListReply_Struct - buf.WriteInt32(server_count); - - for (const auto& world_server : m_world_servers) - { - if (!world_server->IsAuthorized()) { - LogDebug( - "ServerManager::CreateServerListPacket | Server [{}] via IP [{}] is not authorized to be listed", - world_server->GetServerLongName(), - world_server->GetConnection()->Handle()->RemoteIP() - ); - continue; - } - - bool use_local_ip = false; - - std::string world_ip = world_server->GetConnection()->Handle()->RemoteIP(); - if (world_ip == client_ip || IpUtil::IsIpInPrivateRfc1918(client_ip)) { - use_local_ip = true; - } - - LogDebug( - "CreateServerListPacket | Building list entry | Client [{}] IP [{}] Server Long Name [{}] Server IP [{}] ({})", - client->GetAccountName(), - client_ip, - world_server->GetServerLongName(), - use_local_ip ? world_server->GetLocalIP() : world_server->GetRemoteIP(), - use_local_ip ? "Local" : "Remote" - ); - - world_server->SerializeForClientServerList(buf, use_local_ip, client->GetClientVersion()); - } - - return std::make_unique(OP_ServerListResponse, buf); -} - -/** - * @param server_id - * @param client_account_id - * @param client_loginserver - */ -void ServerManager::SendUserToWorldRequest( - unsigned int server_id, - unsigned int client_account_id, - const std::string &client_loginserver -) -{ - auto iter = m_world_servers.begin(); - bool found = false; - while (iter != m_world_servers.end()) { - if ((*iter)->GetServerId() == server_id) { - EQ::Net::DynamicPacket outapp; - outapp.Resize(sizeof(UsertoWorldRequest_Struct)); - - auto *r = (UsertoWorldRequest_Struct *) outapp.Data(); - r->worldid = server_id; - r->lsaccountid = client_account_id; - strncpy(r->login, &client_loginserver[0], 64); - (*iter)->GetConnection()->Send(ServerOP_UsertoWorldReq, outapp); - found = true; - - LogNetcode( - "[UsertoWorldRequest] [Size: {}]\n{}", - outapp.Length(), - outapp.ToString() - ); - } - ++iter; - } - - if (!found) { - LogError("Client requested a user to world but supplied an invalid id of {0}", server_id); - } -} - -/** - * @param server_long_name - * @param server_short_name - * @param ignore - * @return - */ -bool ServerManager::ServerExists( - std::string server_long_name, - std::string server_short_name, - WorldServer *ignore -) -{ - auto iter = m_world_servers.begin(); - while (iter != m_world_servers.end()) { - if ((*iter).get() == ignore) { - ++iter; - continue; - } - - if ((*iter)->GetServerLongName() == server_long_name && (*iter)->GetServerShortName() == server_short_name) { - return true; - } - - ++iter; - } - return false; -} - -/** - * @param server_long_name - * @param server_short_name - * @param ignore - */ -void ServerManager::DestroyServerByName( - std::string server_long_name, - std::string server_short_name, - WorldServer *ignore -) -{ - auto iter = m_world_servers.begin(); - while (iter != m_world_servers.end()) { - if ((*iter).get() == ignore) { - ++iter; - continue; - } - - if ((*iter)->GetServerLongName().compare(server_long_name) == 0 && - (*iter)->GetServerShortName().compare(server_short_name) == 0) { - (*iter)->GetConnection()->Handle()->Disconnect(); - iter = m_world_servers.erase(iter); - continue; - } - - ++iter; - } -} - -/** - * @return - */ -const std::list> &ServerManager::getWorldServers() const -{ - return m_world_servers; -} diff --git a/loginserver/server_manager.h b/loginserver/server_manager.h deleted file mode 100644 index 24711c6ac..000000000 --- a/loginserver/server_manager.h +++ /dev/null @@ -1,81 +0,0 @@ -#ifndef EQEMU_SERVERMANAGER_H -#define EQEMU_SERVERMANAGER_H - -#include "../common/global_define.h" -#include "../common/servertalk.h" -#include "../common/packet_dump.h" -#include "../common/net/servertalk_server.h" -#include "world_server.h" -#include "client.h" -#include - -/** - * Server manager class, deals with management of the world servers - */ -class ServerManager { -public: - - /** - * Constructor, sets up the TCP server and starts listening - */ - ServerManager(); - - /** - * Destructor, shuts down the TCP server. - */ - ~ServerManager(); - - /** - * Sends a request to world to see if the client is banned or suspended - * - * @param server_id - * @param client_account_id - * @param client_loginserver - */ - void SendUserToWorldRequest( - unsigned int server_id, - unsigned int client_account_id, - const std::string &client_loginserver - ); - - /** - * Creates a server list packet for the client - * - * @param client - * @param sequence - * @return - */ - std::unique_ptr CreateServerListPacket(Client *client, uint32 sequence); - - /** - * Checks to see if there is a server exists with this name, ignoring option - * - * @param server_long_name - * @param server_short_name - * @param ignore - * @return - */ - bool ServerExists(std::string server_long_name, std::string server_short_name, WorldServer *ignore = nullptr); - - /** - * Destroys a server with this name, ignoring option - * - * @param server_long_name - * @param server_short_name - * @param ignore - */ - void DestroyServerByName(std::string server_long_name, std::string server_short_name, WorldServer *ignore = nullptr); - - /** - * @return - */ - const std::list> &getWorldServers() const; - -private: - std::unique_ptr m_server_connection; - std::list> m_world_servers; - -}; - -#endif - diff --git a/loginserver/world_server.cpp b/loginserver/world_server.cpp index b43651fd7..44e6049d1 100644 --- a/loginserver/world_server.cpp +++ b/loginserver/world_server.cpp @@ -3,24 +3,24 @@ #include "login_types.h" #include "../common/ip_util.h" #include "../common/strings.h" +#include "../common/repositories/login_world_servers_repository.h" +#include "../common/repositories/login_server_admins_repository.h" extern LoginServer server; +extern Database database; -/** - * @param worldserver_connection - */ WorldServer::WorldServer(std::shared_ptr worldserver_connection) { - m_connection = worldserver_connection; - m_zones_booted = 0; - m_players_online = 0; - m_server_status = 0; - m_server_id = 0; - m_server_list_type_id = 0; - m_server_process_type = 0; - m_is_server_authorized = false; - m_is_server_trusted = false; - m_is_server_logged_in = false; + m_connection = worldserver_connection; + m_zones_booted = 0; + m_players_online = 0; + m_server_status = 0; + m_server_id = 0; + m_server_list_type_id = 0; + m_server_process_type = 0; + m_is_server_authorized_to_list = false; + m_is_server_trusted = false; + m_is_server_logged_in = false; worldserver_connection->OnMessage( ServerOP_NewLSInfo, @@ -51,77 +51,59 @@ WorldServer::WorldServer(std::shared_ptr wo ServerOP_LSAccountUpdate, std::bind(&WorldServer::ProcessLSAccountUpdate, this, std::placeholders::_1, std::placeholders::_2) ); - - m_keepalive = std::make_unique( - 1000, - true, - std::bind(&WorldServer::OnKeepAlive, this, std::placeholders::_1) - ); } WorldServer::~WorldServer() = default; void WorldServer::Reset() { - m_server_id = 0; - m_zones_booted = 0; - m_players_online = 0; - m_server_status = 0; - m_server_list_type_id = 0; - m_server_process_type = 0; - m_is_server_authorized = false; - m_is_server_logged_in = false; + m_server_id = 0; + m_zones_booted = 0; + m_players_online = 0; + m_server_status = 0; + m_server_list_type_id = 0; + m_server_process_type = 0; + m_is_server_authorized_to_list = false; + m_is_server_logged_in = false; } -/** - * @param opcode - * @param packet - */ void WorldServer::ProcessNewLSInfo(uint16_t opcode, const EQ::Net::Packet &packet) { LogNetcode( "Application packet received from server [{:#04x}] [Size: {}]\n{}", - opcode, - packet.Length(), - packet.ToString() + opcode, packet.Length(), packet.ToString() ); - if (packet.Length() < sizeof(ServerNewLSInfo_Struct)) { + if (packet.Length() < sizeof(LoginserverNewWorldRequest)) { LogError( - "Received application packet from server that had opcode ServerOP_NewLSInfo, " - "but was too small. Discarded to avoid buffer overrun" + "Received application packet with opcode ServerOP_NewLSInfo, but it was too small. Discarded to avoid buffer overrun." ); - return; } - auto *info = (ServerNewLSInfo_Struct *) packet.Data(); + auto *r = (LoginserverNewWorldRequest *) packet.Data(); - // if for whatever reason the world server is not sending an address, use the local address it sends - std::string remote_ip_addr = info->remote_ip_address; - std::string local_ip_addr = info->local_ip_address; - if (remote_ip_addr.empty() && !local_ip_addr.empty() && local_ip_addr != "127.0.0.1") { - strcpy(info->remote_ip_address, local_ip_addr.c_str()); + // If remote IP is missing, use local IP unless it's 127.0.0.1 + if (r->remote_ip_address[0] == '\0' && r->local_ip_address[0] != '\0' && + strcmp(r->local_ip_address, "127.0.0.1") != 0) { + strncpy(r->remote_ip_address, r->local_ip_address, sizeof(r->remote_ip_address) - 1); + r->remote_ip_address[sizeof(r->remote_ip_address) - 1] = '\0'; // Ensure null termination } LogInfo( - "New World Server Info | name [{0}] shortname [{1}] remote_address [{2}] local_address [{3}] account [{4}] password [{5}] server_type [{6}]", - info->server_long_name, - info->server_short_name, - info->remote_ip_address, - info->local_ip_address, - info->account_name, - info->account_password, - info->server_process_type + "New World Server Info | name [{}] shortname [{}] remote_address [{}] local_address [{}] account [{}] password [{}] server_type [{}]", + r->server_long_name, + r->server_short_name, + r->remote_ip_address, + r->local_ip_address, + r->account_name, + r->account_password, + r->server_process_type ); - Handle_NewLSInfo(info); + HandleNewWorldserver(r); } -/** - * @param opcode - * @param packet - */ void WorldServer::ProcessLSStatus(uint16_t opcode, const EQ::Net::Packet &packet) { LogNetcode( @@ -131,7 +113,7 @@ void WorldServer::ProcessLSStatus(uint16_t opcode, const EQ::Net::Packet &packet packet.ToString() ); - if (packet.Length() < sizeof(ServerLSStatus_Struct)) { + if (packet.Length() < sizeof(LoginserverWorldStatusUpdate)) { LogError( "Received application packet from server that had opcode ServerOP_LSStatus, but was too small. Discarded to avoid buffer overrun" ); @@ -139,23 +121,19 @@ void WorldServer::ProcessLSStatus(uint16_t opcode, const EQ::Net::Packet &packet return; } - auto *ls_status = (ServerLSStatus_Struct *) packet.Data(); + auto *ls_status = (LoginserverWorldStatusUpdate *) packet.Data(); LogDebug( - "World Server Status Update Received | Server [{0}] Status [{1}] Players [{2}] Zones [{3}]", - GetServerLongName(), + "World Server Status Update Received | Server [{}] Status [{}] Players [{}] Zones [{}]", + m_server_long_name, ls_status->status, ls_status->num_players, ls_status->num_zones ); - Handle_LSStatus(ls_status); + HandleWorldserverStatusUpdate(ls_status); } -/** - * @param opcode - * @param packet - */ void WorldServer::ProcessUserToWorldResponseLegacy(uint16_t opcode, const EQ::Net::Packet &packet) { LogNetcode( @@ -165,7 +143,7 @@ void WorldServer::ProcessUserToWorldResponseLegacy(uint16_t opcode, const EQ::Ne packet.ToString() ); - if (packet.Length() < sizeof(UsertoWorldResponseLegacy_Struct)) { + if (packet.Length() < sizeof(UsertoWorldResponseLegacy)) { LogError( "Received application packet from server that had opcode ServerOP_UsertoWorldResp, " "but was too small. Discarded to avoid buffer overrun" @@ -174,84 +152,78 @@ void WorldServer::ProcessUserToWorldResponseLegacy(uint16_t opcode, const EQ::Ne return; } - auto *r = (UsertoWorldResponseLegacy_Struct *) packet.Data(); + auto *res = (UsertoWorldResponseLegacy *) packet.Data(); - LogDebug("Trying to find client with user id of [{0}]", r->lsaccountid); - Client *client = server.client_manager->GetClient(r->lsaccountid, "eqemu"); - if (client) { + LogDebug("Trying to find client with user id of [{}]", res->lsaccountid); + std::string db_loginserver = "local"; + if (std::getenv("LSPX")) { + db_loginserver = "eqemu"; + } + Client *c = server.client_manager->GetClient(res->lsaccountid, db_loginserver); + if (c) { LogDebug( - "Found client with user id of [{0}] and account name of [{1}]", - r->lsaccountid, - client->GetAccountName() + "Found client with user id of [{}] and account name of [{}]", + res->lsaccountid, + c->GetAccountName() ); auto *outapp = new EQApplicationPacket( OP_PlayEverquestResponse, - sizeof(PlayEverquestResponse_Struct) + sizeof(PlayEverquestResponse) ); - auto *per = (PlayEverquestResponse_Struct *) outapp->pBuffer; - per->base_header.sequence = client->GetPlaySequence(); - per->server_number = client->GetPlayServerID(); + auto *play = (PlayEverquestResponse *) outapp->pBuffer; + play->base_header.sequence = c->GetCurrentPlaySequence(); + play->server_number = c->GetSelectedPlayServerID(); - if (r->response > 0) { - per->base_reply.success = true; - SendClientAuth( - client->GetConnection()->GetRemoteAddr(), - client->GetAccountName(), - client->GetKey(), - client->GetAccountID(), - client->GetLoginServerName() - ); + if (res->response > 0) { + play->base_reply.success = true; + SendClientAuthToWorld(c); } - switch (r->response) { + switch (res->response) { case UserToWorldStatusSuccess: - per->base_reply.error_str_id = LS::ErrStr::ERROR_NONE; + play->base_reply.error_str_id = LS::ErrStr::ERROR_NONE; break; case UserToWorldStatusWorldUnavail: - per->base_reply.error_str_id = LS::ErrStr::ERROR_SERVER_UNAVAILABLE; + play->base_reply.error_str_id = LS::ErrStr::ERROR_SERVER_UNAVAILABLE; break; case UserToWorldStatusSuspended: - per->base_reply.error_str_id = LS::ErrStr::ERROR_ACCOUNT_SUSPENDED; + play->base_reply.error_str_id = LS::ErrStr::ERROR_ACCOUNT_SUSPENDED; break; case UserToWorldStatusBanned: - per->base_reply.error_str_id = LS::ErrStr::ERROR_ACCOUNT_BANNED; + play->base_reply.error_str_id = LS::ErrStr::ERROR_ACCOUNT_BANNED; break; case UserToWorldStatusWorldAtCapacity: - per->base_reply.error_str_id = LS::ErrStr::ERROR_WORLD_MAX_CAPACITY; + play->base_reply.error_str_id = LS::ErrStr::ERROR_WORLD_MAX_CAPACITY; break; case UserToWorldStatusAlreadyOnline: - per->base_reply.error_str_id = LS::ErrStr::ERROR_ACTIVE_CHARACTER; + play->base_reply.error_str_id = LS::ErrStr::ERROR_ACTIVE_CHARACTER; break; default: - per->base_reply.error_str_id = LS::ErrStr::ERROR_UNKNOWN; + play->base_reply.error_str_id = LS::ErrStr::ERROR_UNKNOWN; break; } LogDebug( - "Sending play response: allowed [{0}] sequence [{1}] server number [{2}] message [{3}]", - per->base_reply.success, - per->base_header.sequence, - per->server_number, - per->base_reply.error_str_id + "Sending play response: allowed [{}] sequence [{}] server number [{}] message [{}]", + play->base_reply.success, + play->base_header.sequence, + play->server_number, + play->base_reply.error_str_id ); - client->SendPlayResponse(outapp); + c->SendPlayResponse(outapp); delete outapp; } else { LogError( - "Received User-To-World Response for [{0}] but could not find the client referenced!", - r->lsaccountid + "Received User-To-World Response for [{}] but could not find the client referenced!", + res->lsaccountid ); } } -/** - * @param opcode - * @param packet - */ void WorldServer::ProcessUserToWorldResponse(uint16_t opcode, const EQ::Net::Packet &packet) { LogNetcode( @@ -261,7 +233,7 @@ void WorldServer::ProcessUserToWorldResponse(uint16_t opcode, const EQ::Net::Pac packet.ToString() ); - if (packet.Length() < sizeof(UsertoWorldResponse_Struct)) { + if (packet.Length() < sizeof(UsertoWorldResponse)) { LogError( "Received application packet from server that had opcode ServerOP_UsertoWorldResp, " "but was too small. Discarded to avoid buffer overrun" @@ -270,49 +242,44 @@ void WorldServer::ProcessUserToWorldResponse(uint16_t opcode, const EQ::Net::Pac return; } - auto user_to_world_response = (UsertoWorldResponse_Struct *) packet.Data(); - LogDebug("Trying to find client with user id of [{0}]", user_to_world_response->lsaccountid); + auto res = (UsertoWorldResponse *) packet.Data(); + LogDebug("Trying to find client with user id of [{}]", res->lsaccountid); Client *c = server.client_manager->GetClient( - user_to_world_response->lsaccountid, - user_to_world_response->login + res->lsaccountid, + res->login ); if (c) { - LogDebug("Found client with user id of [{0}] and account name of {1}", - user_to_world_response->lsaccountid, - c->GetAccountName().c_str() + LogDebug( + "Found client with user id of [{}] and account name of {}", + res->lsaccountid, + c->GetAccountName().c_str() ); auto *outapp = new EQApplicationPacket( OP_PlayEverquestResponse, - sizeof(PlayEverquestResponse_Struct) + sizeof(PlayEverquestResponse) ); - auto *r = (PlayEverquestResponse_Struct *) outapp->pBuffer; - r->base_header.sequence = c->GetPlaySequence(); - r->server_number = c->GetPlayServerID(); + auto *r = (PlayEverquestResponse *) outapp->pBuffer; + r->base_header.sequence = c->GetCurrentPlaySequence(); + r->server_number = c->GetSelectedPlayServerID(); LogDebug( - "Found sequence and play of [{0}] [{1}]", - c->GetPlaySequence(), - c->GetPlayServerID() + "Found sequence and play of [{}] [{}]", + c->GetCurrentPlaySequence(), + c->GetSelectedPlayServerID() ); - LogDebug("[Size: [{0}]] {1}", outapp->size, DumpPacketToString(outapp)); + LogDebug("[Size: [{}]] {}", outapp->size, DumpPacketToString(outapp)); - if (user_to_world_response->response > 0) { + if (res->response > 0) { r->base_reply.success = true; - SendClientAuth( - c->GetConnection()->GetRemoteAddr(), - c->GetAccountName(), - c->GetKey(), - c->GetAccountID(), - c->GetLoginServerName() - ); + SendClientAuthToWorld(c); } - switch (user_to_world_response->response) { + switch (res->response) { case UserToWorldStatusSuccess: r->base_reply.error_str_id = LS::ErrStr::ERROR_NONE; break; @@ -337,7 +304,7 @@ void WorldServer::ProcessUserToWorldResponse(uint16_t opcode, const EQ::Net::Pac } LogDebug( - "Sending play response with following data, allowed [{0}], sequence {1}, server number {2}, message {3}", + "Sending play response with following data, allowed [{}], sequence {}, server number {}, message {}", r->base_reply.success, r->base_header.sequence, r->server_number, @@ -349,16 +316,12 @@ void WorldServer::ProcessUserToWorldResponse(uint16_t opcode, const EQ::Net::Pac } else { LogError( - "Received User-To-World Response for [{0}] but could not find the client referenced!.", - user_to_world_response->lsaccountid + "Received User-To-World Response for [{}] but could not find the client referenced!.", + res->lsaccountid ); } } -/** - * @param opcode - * @param packet - */ void WorldServer::ProcessLSAccountUpdate(uint16_t opcode, const EQ::Net::Packet &packet) { LogNetcode( @@ -368,7 +331,7 @@ void WorldServer::ProcessLSAccountUpdate(uint16_t opcode, const EQ::Net::Packet packet.ToString() ); - if (packet.Length() < sizeof(ServerLSAccountUpdate_Struct)) { + if (packet.Length() < sizeof(LoginserverAccountUpdate)) { LogError( "Received application packet from server that had opcode ServerLSAccountUpdate_Struct, " "but was too small. Discarded to avoid buffer overrun" @@ -377,197 +340,182 @@ void WorldServer::ProcessLSAccountUpdate(uint16_t opcode, const EQ::Net::Packet return; } - LogDebug("ServerOP_LSAccountUpdate packet received from [{0}]", m_short_name); + LogDebug("ServerOP_LSAccountUpdate packet received from [{}]", m_server_short_name); - auto *loginserver_update = (ServerLSAccountUpdate_Struct *) packet.Data(); - if (IsServerTrusted()) { - LogDebug("ServerOP_LSAccountUpdate update processed for: [{0}]", loginserver_update->useraccount); - std::string name; - std::string password; - std::string email; + auto *r = (LoginserverAccountUpdate *) packet.Data(); + if (m_is_server_trusted) { + LogDebug("ServerOP_LSAccountUpdate update processed for: [{}]", r->user_account_name); - name.assign(loginserver_update->useraccount); - password.assign(loginserver_update->userpassword); + LoginAccountContext c{}; + c.username = r->user_account_name; + c.source_loginserver = "local"; + auto a = LoginAccountsRepository::GetAccountFromContext(database, c); + if (a.id > 0) { + a.account_email = r->user_email; + a.account_password = r->user_account_password; + a.last_ip_address = "0.0.0.0"; + LoginAccountsRepository::UpdateOne(database, a); + } + } +} - if (loginserver_update->user_email[0] != '\0') { - email.assign(loginserver_update->user_email); +void WorldServer::HandleNewWorldserver(LoginserverNewWorldRequest *req) +{ + if (m_is_server_logged_in) { + LogInfo("Login server was already marked as logged in, returning"); + return; + } + + if (!HandleNewWorldserverValidation(req)) { + LogError("failed validation rules"); + return; + } + + SanitizeWorldServerName(req->server_long_name); + + m_server_long_name = req->server_long_name; + m_server_short_name = req->server_short_name; + m_account_password = req->account_password; + m_account_name = req->account_name; + m_local_ip = req->local_ip_address; + m_remote_ip_address = req->remote_ip_address; + m_server_version = req->server_version; + m_protocol = req->protocol_version; + m_server_process_type = req->server_process_type; + m_is_server_logged_in = true; + + // Handle Duplicate Servers + if (server.server_manager->DoesServerExist(m_server_long_name, m_server_short_name, this)) { + if (server.options.IsRejectingDuplicateServers()) { + LogError("World tried to login but a server with that name already exists"); + return; + } + LogInfo("World tried to login but a server with that name already exists, destroying [{}]", m_server_long_name); + server.server_manager->DestroyServerByName(m_server_long_name, m_server_short_name, this); + } + + LoginWorldContext c; + c.long_name = m_server_long_name; + c.short_name = m_server_short_name; + + LoginServerAdminsRepository::LoginServerAdmins admin; + + // Handle Admin Authentication + if (!m_account_name.empty() && !m_account_password.empty()) { + admin = LoginServerAdminsRepository::GetByName(database, m_account_name); + + LoginWorldAdminAccountContext ac; + ac.id = admin.id; + ac.username = m_account_name; + ac.password = m_account_password; + ac.password_hash = admin.account_password; + + if (admin.id && WorldServer::ValidateWorldServerAdminLogin(ac, admin)) { + LogDebug( + "Authenticated world admin [{}] ({}) for world [{}]", + m_account_name, + admin.id, + m_server_short_name + ); + c.admin_id = admin.id; + m_is_server_authorized_to_list = true; + } + } + + auto world = LoginWorldServersRepository::GetFromWorldContext(database, c); + if (!world.id) { + if (!server.options.IsUnregisteredAllowed()) { + LogError("WorldServer [{}] is not registered, and unregistered servers are not allowed", + m_server_long_name); + return; } - server.db->UpdateLSAccountInfo( - loginserver_update->useraccountid, - name, - password, - email + LogInfo("Server [{}] is not registered, handling as unregistered", m_server_long_name); + m_is_server_authorized_to_list = true; + + auto w = LoginWorldServersRepository::NewEntity(); + w.long_name = m_server_long_name; + w.short_name = m_server_short_name; + w.last_ip_address = m_remote_ip_address; + w.login_server_list_type_id = LS::ServerType::Standard; + w.last_login_date = std::time(nullptr); + auto created = LoginWorldServersRepository::InsertOne(database, w); + if (!created.id) { + LogError("Failed to auto-register world server [{}]", m_server_long_name); + return; + } + + LogInfo( + "Auto-registered world server [{}] with ID [{}]", + m_server_long_name, + created.id ); } -} - -/** - * When a worldserver first messages the loginserver telling them who they are - * - * @param new_world_server_info_packet - */ -void WorldServer::Handle_NewLSInfo(ServerNewLSInfo_Struct *new_world_server_info_packet) -{ - if (IsServerLoggedIn()) { - LogError("WorldServer::Handle_NewLSInfo called but the login server was already marked as logged in, aborting"); - return; - } - - if (!HandleNewLoginserverInfoValidation(new_world_server_info_packet)) { - LogError("WorldServer::Handle_NewLSInfo failed validation rules"); - return; - } - - SanitizeWorldServerName(new_world_server_info_packet->server_long_name); - - SetAccountPassword(new_world_server_info_packet->account_password) - ->SetLongName(new_world_server_info_packet->server_long_name) - ->SetShortName(new_world_server_info_packet->server_short_name) - ->SetLocalIp(new_world_server_info_packet->local_ip_address) - ->SetRemoteIp(new_world_server_info_packet->remote_ip_address) - ->SetVersion(new_world_server_info_packet->server_version) - ->SetProtocol(new_world_server_info_packet->protocol_version) - ->SetServerProcessType(new_world_server_info_packet->server_process_type) - ->SetIsServerLoggedIn(true) - ->SetAccountName(new_world_server_info_packet->account_name); - - if (server.options.IsRejectingDuplicateServers()) { - if (server.server_manager->ServerExists(GetServerLongName(), GetServerShortName(), this)) { - LogError("World tried to login but there already exists a server that has that name"); - return; - } - } else { - if (server.server_manager->ServerExists(GetServerLongName(), GetServerShortName(), this)) { - LogInfo("World tried to login but there already exists a server that has that name, destroying [{}]", - m_long_name); - server.server_manager->DestroyServerByName(m_long_name, m_short_name, this); - } + m_server_description = world.tag_description; + m_server_id = world.id; + m_is_server_trusted = world.is_server_trusted; + m_server_list_type_id = world.login_server_list_type_id; + m_is_server_authorized_to_list = true; + + LogInfo( + "Server ID [{}] long_name [{}] short_name [{}] successfully authenticated", + world.id, + world.long_name, + world.short_name + ); } - uint32 world_server_admin_id = 0; - - /** - * If our world is trying to authenticate, let's try and pull the owner first to try associating - * with a world short_name - */ - if (!GetAccountName().empty() && !GetAccountPassword().empty()) { - Database::DbLoginServerAdmin - login_server_admin = server.db->GetLoginServerAdmin(GetAccountName()); - - if (login_server_admin.loaded) { - LogDebug( - "Attempting to authenticate world admin... [{0}] ({1}) against worldserver [{2}]", - GetAccountName(), - login_server_admin.id, - GetServerShortName() - ); - - if (WorldServer::ValidateWorldServerAdminLogin( - login_server_admin.id, - GetAccountName(), - GetAccountPassword(), - login_server_admin.account_password - )) { - LogDebug( - "Authenticating world admin... [{0}] ({1}) success! World ({2})", - GetAccountName(), - login_server_admin.id, - GetServerShortName() - ); - world_server_admin_id = login_server_admin.id; - - SetIsServerAuthorized(true); - } - } - } - - Database::DbWorldRegistration - world_registration = server.db->GetWorldRegistration( - GetServerShortName(), - GetServerLongName(), - world_server_admin_id + LogInfo( + "World registration id [{}] for server [{}] ip_address [{}]", + m_server_id, + m_server_long_name, + m_remote_ip_address ); - if (!server.options.IsUnregisteredAllowed()) { - if (!HandleNewLoginserverRegisteredOnly(world_registration)) { - LogError( - "WorldServer::HandleNewLoginserverRegisteredOnly checks failed with server [{0}]", - GetServerLongName() - ); - return; - } - } - else { - if (!HandleNewLoginserverInfoUnregisteredAllowed(world_registration)) { - LogError( - "WorldServer::HandleNewLoginserverInfoUnregisteredAllowed checks failed with server [{0}]", - GetServerLongName() - ); - return; - } - } - - server.db->UpdateWorldRegistration( - GetServerId(), - GetServerLongName(), - GetRemoteIp() - ); + // Update the last login date and IP address + world.last_login_date = std::time(nullptr); + world.last_ip_address = m_remote_ip_address; + LoginWorldServersRepository::UpdateOne(database, world); WorldServer::FormatWorldServerName( - new_world_server_info_packet->server_long_name, - world_registration.server_list_type + req->server_long_name, + m_server_list_type_id ); - SetLongName(new_world_server_info_packet->server_long_name); + + m_server_long_name = req->server_long_name; } -/** - * @param server_login_status - */ -void WorldServer::Handle_LSStatus(ServerLSStatus_Struct *server_login_status) +void WorldServer::HandleWorldserverStatusUpdate(LoginserverWorldStatusUpdate *u) { - SetPlayersOnline(server_login_status->num_players); - SetZonesBooted(server_login_status->num_zones); - SetServerStatus(server_login_status->status); + m_players_online = u->num_players; + m_zones_booted = u->num_zones; + m_server_status = u->status; } -/** - * @param ip - * @param account - * @param key - * @param account_id - * @param loginserver_name - */ -void WorldServer::SendClientAuth( - std::string ip, - std::string account, - std::string key, - unsigned int account_id, - const std::string &loginserver_name -) +void WorldServer::SendClientAuthToWorld(Client *c) { EQ::Net::DynamicPacket outapp; - ClientAuth_Struct a{}; + ClientAuth a{}; - a.loginserver_account_id = account_id; + a.loginserver_account_id = c->GetAccountID(); - strncpy(a.account_name, account.c_str(), 30); - strncpy(a.key, key.c_str(), 30); + strncpy(a.account_name, c->GetAccountName().c_str(), 30); + strncpy(a.key, c->GetLoginKey().c_str(), 30); a.lsadmin = 0; a.is_world_admin = 0; - a.ip = inet_addr(ip.c_str()); - strncpy(a.loginserver_name, &loginserver_name[0], 64); + a.ip_address = inet_addr(c->GetConnection()->GetRemoteAddr().c_str()); + strncpy(a.loginserver_name, &c->GetLoginServerName()[0], 64); - const std::string &client_address(ip); + const std::string &client_address(c->GetConnection()->GetRemoteAddr()); std::string world_address(m_connection->Handle()->RemoteIP()); if (client_address == world_address) { a.is_client_from_local_network = 1; } else if (IpUtil::IsIpInPrivateRfc1918(client_address)) { - LogInfo("Client is authenticating from a local address [{0}]", client_address); + LogInfo("Client is authenticating from a local address [{}]", client_address); a.is_client_from_local_network = 1; } else { @@ -575,17 +523,17 @@ void WorldServer::SendClientAuth( } struct in_addr ip_addr{}; - ip_addr.s_addr = a.ip; + ip_addr.s_addr = a.ip_address; LogInfo( - "Client authentication response: world_address [{0}] client_address [{1}]", + "Client authentication response: world_address [{}] client_address [{}]", world_address, client_address ); LogInfo( - "Sending Client Authentication Response ls_account_id [{0}] ls_name [{1}] name [{2}] key [{3}] ls_admin [{4}] " - "world_admin [{5}] ip [{6}] local [{7}]", + "Sending Client Authentication Response ls_account_id [{}] ls_name [{}] name [{}] key [{}] ls_admin [{}] " + "world_admin [{}] ip [{}] local [{}]", a.loginserver_account_id, a.loginserver_name, a.account_name, @@ -616,71 +564,66 @@ constexpr static int MAX_SERVER_REMOTE_ADDRESS_LENGTH = 125; constexpr static int MAX_SERVER_VERSION_LENGTH = 64; constexpr static int MAX_SERVER_PROTOCOL_VERSION = 25; -/** - * @param new_world_server_info_packet - * @return - */ -bool WorldServer::HandleNewLoginserverInfoValidation( - ServerNewLSInfo_Struct *new_world_server_info_packet -) +bool WorldServer::HandleNewWorldserverValidation(LoginserverNewWorldRequest *r) { - if (strlen(new_world_server_info_packet->account_name) >= MAX_ACCOUNT_NAME_LENGTH) { - LogError("Handle_NewLSInfo error [account_name] was too long | max [{0}]", MAX_ACCOUNT_NAME_LENGTH); + if (strlen(r->account_name) >= MAX_ACCOUNT_NAME_LENGTH) { + LogError("HandleNewWorldserver error [account_name] was too long | max [{}]", MAX_ACCOUNT_NAME_LENGTH); return false; } - else if (strlen(new_world_server_info_packet->account_password) >= MAX_ACCOUNT_PASSWORD_LENGTH) { - LogError("Handle_NewLSInfo error [account_password] was too long | max [{0}]", MAX_ACCOUNT_PASSWORD_LENGTH); + else if (strlen(r->account_password) >= MAX_ACCOUNT_PASSWORD_LENGTH) { + LogError("HandleNewWorldserver error [account_password] was too long | max [{}]", MAX_ACCOUNT_PASSWORD_LENGTH); return false; } - else if (strlen(new_world_server_info_packet->server_long_name) >= MAX_SERVER_LONG_NAME_LENGTH) { - LogError("Handle_NewLSInfo error [server_long_name] was too long | max [{0}]", MAX_SERVER_LONG_NAME_LENGTH); + else if (strlen(r->server_long_name) >= MAX_SERVER_LONG_NAME_LENGTH) { + LogError("HandleNewWorldserver error [server_long_name] was too long | max [{}]", MAX_SERVER_LONG_NAME_LENGTH); return false; } - else if (strlen(new_world_server_info_packet->server_short_name) >= MAX_SERVER_SHORT_NAME_LENGTH) { - LogError("Handle_NewLSInfo error [server_short_name] was too long | max [{0}]", MAX_SERVER_SHORT_NAME_LENGTH); + else if (strlen(r->server_short_name) >= MAX_SERVER_SHORT_NAME_LENGTH) { + LogError("HandleNewWorldserver error [server_short_name] was too long | max [{}]", + MAX_SERVER_SHORT_NAME_LENGTH); return false; } - else if (strlen(new_world_server_info_packet->server_version) >= MAX_SERVER_VERSION_LENGTH) { - LogError("Handle_NewLSInfo error [server_version] was too long | max [{0}]", MAX_SERVER_VERSION_LENGTH); + else if (strlen(r->server_version) >= MAX_SERVER_VERSION_LENGTH) { + LogError("HandleNewWorldserver error [server_version] was too long | max [{}]", MAX_SERVER_VERSION_LENGTH); return false; } - else if (strlen(new_world_server_info_packet->protocol_version) >= MAX_SERVER_PROTOCOL_VERSION) { - LogError("Handle_NewLSInfo error [protocol_version] was too long | max [{0}]", MAX_SERVER_PROTOCOL_VERSION); + else if (strlen(r->protocol_version) >= MAX_SERVER_PROTOCOL_VERSION) { + LogError("HandleNewWorldserver error [protocol_version] was too long | max [{}]", MAX_SERVER_PROTOCOL_VERSION); return false; } - if (strlen(new_world_server_info_packet->local_ip_address) <= MAX_SERVER_LOCAL_ADDRESS_LENGTH) { - if (strlen(new_world_server_info_packet->local_ip_address) == 0) { - LogError("Handle_NewLSInfo error, local address was null, defaulting to localhost"); - SetLocalIp("127.0.0.1"); + if (strlen(r->local_ip_address) <= MAX_SERVER_LOCAL_ADDRESS_LENGTH) { + if (strlen(r->local_ip_address) == 0) { + LogError("HandleNewWorldserver error, local address was null, defaulting to localhost"); + m_local_ip = "127.0.0.1"; } else { - SetLocalIp(new_world_server_info_packet->local_ip_address); + m_local_ip = r->local_ip_address; } } else { - LogError("Handle_NewLSInfo error, local address was too long | max [{0}]", MAX_SERVER_LOCAL_ADDRESS_LENGTH); + LogError("HandleNewWorldserver error, local address was too long | max [{}]", MAX_SERVER_LOCAL_ADDRESS_LENGTH); return false; } - if (strlen(new_world_server_info_packet->remote_ip_address) <= MAX_SERVER_REMOTE_ADDRESS_LENGTH) { - if (strlen(new_world_server_info_packet->remote_ip_address) == 0) { - SetRemoteIp(GetConnection()->Handle()->RemoteIP()); + if (strlen(r->remote_ip_address) <= MAX_SERVER_REMOTE_ADDRESS_LENGTH) { + if (strlen(r->remote_ip_address) == 0) { + m_remote_ip_address = GetConnection()->Handle()->RemoteIP(); LogWarning( - "Remote address was null, defaulting to stream address [{0}]", + "Remote address was null, defaulting to stream address [{}]", m_remote_ip_address ); } else { - SetRemoteIp(new_world_server_info_packet->remote_ip_address); + m_remote_ip_address = r->remote_ip_address; } } else { - SetRemoteIp(GetConnection()->Handle()->RemoteIP()); + m_remote_ip_address = GetConnection()->Handle()->RemoteIP(); LogWarning( - "Handle_NewLSInfo remote address was too long, defaulting to stream address [{0}]", + "HandleNewWorldserver remote address was too long, defaulting to stream address [{}]", m_remote_ip_address ); } @@ -688,303 +631,69 @@ bool WorldServer::HandleNewLoginserverInfoValidation( return true; } -/** - * @param world_registration - * @return - */ -bool WorldServer::HandleNewLoginserverRegisteredOnly( - Database::DbWorldRegistration &world_registration -) -{ - if (!GetAccountName().empty() && !GetAccountPassword().empty()) { - if (world_registration.loaded) { - bool does_world_server_not_require_authentication = ( - world_registration.server_admin_account_name.empty() || - world_registration.server_admin_account_password.empty() - ); - - bool does_world_server_pass_authentication_check = ( - world_registration.server_admin_account_name == GetAccountName() && - WorldServer::ValidateWorldServerAdminLogin( - world_registration.server_admin_id, - GetAccountName(), - GetAccountPassword(), - world_registration.server_admin_account_password - ) - ); - - SetServerDescription(world_registration.server_description) - ->SetServerId(world_registration.server_id) - ->SetIsServerTrusted(world_registration.is_server_trusted) - ->SetServerListTypeId(world_registration.server_list_type); - - if (does_world_server_not_require_authentication) { - - SetIsServerAuthorized(true); - - LogInfo( - "Server long_name [{0}] short_name [{1}] successfully logged into account that had no user/password requirement", - GetServerLongName(), - GetServerShortName() - ); - } - else if (does_world_server_pass_authentication_check) { - - SetIsServerAuthorized(true); - - LogInfo( - "Server long_name [{0}] short_name [{1}] successfully logged in", - GetServerLongName(), - GetServerShortName() - ); - - if (IsServerTrusted()) { - LogDebug("WorldServer::HandleNewLoginserverRegisteredOnly | ServerOP_LSAccountUpdate sent to world"); - EQ::Net::DynamicPacket outapp; - m_connection->Send(ServerOP_LSAccountUpdate, outapp); - } - } - else { - LogInfo( - "Server long_name [{0}] short_name [{1}] attempted to log in but account and password did not " - "match the entry in the database, and only registered servers are allowed", - GetServerLongName(), - GetServerShortName() - ); - - return false; - } - } - else { - LogInfo( - "Server long_name [{0}] short_name [{1}] attempted to log in but database couldn't find an entry and only registered servers are allowed", - GetServerLongName(), - GetServerShortName() - ); - - return false; - } - } - else { - LogInfo( - "Server long_name [{0}] short_name [{1}] did not attempt to log in but only registered servers are allowed", - GetServerLongName(), - GetServerShortName() - ); - - return false; - } - - return true; -} - -/** - * @param world_registration - * @return - */ -bool WorldServer::HandleNewLoginserverInfoUnregisteredAllowed( - Database::DbWorldRegistration &world_registration -) -{ - if (world_registration.loaded) { - SetServerDescription(world_registration.server_description) - ->SetServerId(world_registration.server_id) - ->SetIsServerTrusted(world_registration.is_server_trusted) - ->SetServerListTypeId(world_registration.server_list_type); - - SetIsServerAuthorized(true); - - bool does_world_server_pass_authentication_check = ( - world_registration.server_admin_account_name == GetAccountName() && - WorldServer::ValidateWorldServerAdminLogin( - world_registration.server_admin_id, - GetAccountName(), - GetAccountPassword(), - world_registration.server_admin_account_password - ) - ); - - bool does_world_server_have_non_empty_credentials = ( - !GetAccountName().empty() && - !GetAccountPassword().empty() - ); - - if (does_world_server_have_non_empty_credentials) { - if (does_world_server_pass_authentication_check) { - SetIsServerLoggedIn(true); - - LogInfo( - "Server long_name [{0}] short_name [{1}] successfully logged in", - GetServerLongName(), - GetServerShortName() - ); - - if (IsServerTrusted()) { - LogDebug("WorldServer::HandleNewLoginserverRegisteredOnly | ServerOP_LSAccountUpdate sent to world"); - EQ::Net::DynamicPacket outapp; - m_connection->Send(ServerOP_LSAccountUpdate, outapp); - } - } - else { - // server is authorized to be on the loginserver list but didn't pass login check - LogInfo( - "Server long_name [{0}] short_name [{1}] attempted to log in but account and password did not match the entry in the database. Unregistered still allowed", - GetServerLongName(), - GetServerShortName() - ); - } - } - else { - - // server is authorized to be on the loginserver list but didn't pass login check - if (!GetAccountName().empty() || !GetAccountPassword().empty()) { - LogInfo( - "Server [{0}] [{1}] did not login but this server required a password to login", - GetServerLongName(), - GetServerShortName() - ); - } - else { - LogInfo( - "Server [{0}] [{1}] did not login but unregistered servers are allowed", - GetServerLongName(), - GetServerShortName() - ); - } - } - } - else { - LogInfo( - "Server [{0}] ({1}) is not registered but unregistered servers are allowed", - GetServerLongName(), - GetServerShortName() - ); - - SetIsServerAuthorized(true); - - if (world_registration.loaded) { - return true; - } - - Database::DbLoginServerAdmin login_server_admin = server.db->GetLoginServerAdmin(GetAccountName()); - - uint32 server_admin_id = 0; - if (login_server_admin.loaded) { - if (WorldServer::ValidateWorldServerAdminLogin( - login_server_admin.id, - GetAccountName(), - GetAccountPassword(), - login_server_admin.account_password - )) { - server_admin_id = login_server_admin.id; - } - } - - // Auto create a registration - if (!server.db->CreateWorldRegistration( - GetServerLongName(), - GetServerShortName(), - GetRemoteIp(), - m_server_id, - server_admin_id - )) { - return false; - } - } - - return true; -} - -/** - * @param world_admin_id - * @param world_admin_username - * @param world_admin_password - * @param world_admin_password_hash - * @return - */ bool WorldServer::ValidateWorldServerAdminLogin( - int world_admin_id, - const std::string &world_admin_username, - const std::string &world_admin_password, - const std::string &world_admin_password_hash + LoginWorldAdminAccountContext &c, + LoginServerAdminsRepository::LoginServerAdmins &admin ) { auto encryption_mode = server.options.GetEncryptionMode(); - if (eqcrypt_verify_hash(world_admin_username, world_admin_password, world_admin_password_hash, encryption_mode)) { + if (eqcrypt_verify_hash(c.username, c.password, c.password_hash, encryption_mode)) { return true; } - else { - if (server.options.IsUpdatingInsecurePasswords()) { - if (encryption_mode < EncryptionModeArgon2) { - encryption_mode = EncryptionModeArgon2; - } - uint32 insecure_source_encryption_mode = 0; - if (world_admin_password_hash.length() == CryptoHash::md5_hash_length) { - for (int i = EncryptionModeMD5; i <= EncryptionModeMD5Triple; ++i) { - if (i != encryption_mode && - eqcrypt_verify_hash(world_admin_username, world_admin_password, world_admin_password_hash, i)) { - LogDebug("[{}] Checking for [{}] world admin", __func__, GetEncryptionByModeId(i)); - insecure_source_encryption_mode = i; - } - } - } - else if (world_admin_password_hash.length() == CryptoHash::sha1_hash_length && - insecure_source_encryption_mode == 0) { - for (int i = EncryptionModeSHA; i <= EncryptionModeSHATriple; ++i) { - if (i != encryption_mode && - eqcrypt_verify_hash(world_admin_username, world_admin_password, world_admin_password_hash, i)) { - LogDebug("[{}] Checking for [{}] world admin", __func__, GetEncryptionByModeId(i)); - insecure_source_encryption_mode = i; - } - } - } - else if (world_admin_password_hash.length() == CryptoHash::sha512_hash_length && - insecure_source_encryption_mode == 0) { - for (int i = EncryptionModeSHA512; i <= EncryptionModeSHA512Triple; ++i) { - if (i != encryption_mode && - eqcrypt_verify_hash(world_admin_username, world_admin_password, world_admin_password_hash, i)) { - LogDebug("[{}] Checking for [{}] world admin", __func__, GetEncryptionByModeId(i)); - insecure_source_encryption_mode = i; - } - } - } + if (encryption_mode < EncryptionModeArgon2) { + encryption_mode = EncryptionModeArgon2; + } - if (insecure_source_encryption_mode > 0) { - LogInfo( - "[{}] Updated insecure world_admin_username [{}] from mode [{}] ({}) to mode [{}] ({})", - __func__, - world_admin_username, - GetEncryptionByModeId(insecure_source_encryption_mode), - insecure_source_encryption_mode, - GetEncryptionByModeId(encryption_mode), - encryption_mode - ); - - std::string new_password_hash = eqcrypt_hash( - world_admin_username, - world_admin_password, - encryption_mode - ); - - server.db->UpdateLoginWorldAdminAccountPassword(world_admin_id, new_password_hash); - - return true; + uint32 insecure_source_encryption_mode = 0; + auto verify_encryption = [&](int start, int end) { + for (int i = start; i <= end; ++i) { + if (i != encryption_mode && eqcrypt_verify_hash(c.username, c.password, c.password_hash, i)) { + LogDebug("Checking for [{}] world admin", GetEncryptionByModeId(i)); + insecure_source_encryption_mode = i; } } + }; + + switch (c.password_hash.length()) { + case CryptoHash::md5_hash_length: + verify_encryption(EncryptionModeMD5, EncryptionModeMD5Triple); + break; + case CryptoHash::sha1_hash_length: + verify_encryption(EncryptionModeSHA, EncryptionModeSHATriple); + break; + case CryptoHash::sha512_hash_length: + verify_encryption(EncryptionModeSHA512, EncryptionModeSHA512Triple); + break; + } + + if (insecure_source_encryption_mode > 0) { + LogInfo( + "Updated insecure world_admin_username [{}] from mode [{}] ({}) to mode [{}] ({})", + c.username, + GetEncryptionByModeId(insecure_source_encryption_mode), + insecure_source_encryption_mode, + GetEncryptionByModeId(encryption_mode), + encryption_mode + ); + + admin.account_password = eqcrypt_hash(c.username, c.password, encryption_mode); + LoginServerAdminsRepository::UpdateOne(database, admin); + + return true; } return false; } -void WorldServer::SerializeForClientServerList(SerializeBuffer& out, bool use_local_ip, LSClientVersion version) const +void WorldServer::SerializeForClientServerList(SerializeBuffer &out, bool use_local_ip, LSClientVersion version) const { // see LoginClientServerData_Struct if (use_local_ip) { out.WriteString(GetLocalIP()); } else { - out.WriteString(GetRemoteIP()); + out.WriteString(m_remote_ip_address); } if (version == cv_larion) { @@ -992,27 +701,27 @@ void WorldServer::SerializeForClientServerList(SerializeBuffer& out, bool use_lo } switch (GetServerListID()) { - case LS::ServerType::Legends: - out.WriteInt32(LS::ServerTypeFlags::Legends); - break; - case LS::ServerType::Preferred: - out.WriteInt32(LS::ServerTypeFlags::Preferred); - break; - default: - out.WriteInt32(LS::ServerTypeFlags::Standard); - break; + case LS::ServerType::Legends: + out.WriteInt32(LS::ServerTypeFlags::Legends); + break; + case LS::ServerType::Preferred: + out.WriteInt32(LS::ServerTypeFlags::Preferred); + break; + default: + out.WriteInt32(LS::ServerTypeFlags::Standard); + break; } if (version == cv_larion) { - auto server_id = GetServerId(); + auto server_id = m_server_id; //if this is 0, the client will not show the server in the list out.WriteUInt32(1); out.WriteUInt32(server_id); } else { - out.WriteUInt32(GetServerId()); + out.WriteUInt32(m_server_id); } - out.WriteString(GetServerLongName()); + out.WriteString(m_server_long_name); out.WriteString("us"); // country code out.WriteString("en"); // language code @@ -1032,287 +741,6 @@ void WorldServer::SerializeForClientServerList(SerializeBuffer& out, bool use_lo out.WriteUInt32(GetPlayersOnline()); } -/** - * @param in_server_list_id - * @return - */ -WorldServer *WorldServer::SetServerListTypeId(unsigned int in_server_list_id) -{ - m_server_list_type_id = in_server_list_id; - - return this; -} - -/** - * @return - */ -const std::string &WorldServer::GetServerDescription() const -{ - return m_server_description; -} - -/** - * @param in_server_description - */ -WorldServer *WorldServer::SetServerDescription(const std::string &in_server_description) -{ - WorldServer::m_server_description = in_server_description; - - return this; -} - -/** - * @return - */ -bool WorldServer::IsServerAuthorized() const -{ - return m_is_server_authorized; -} - -/** - * @param in_is_server_authorized - */ -WorldServer *WorldServer::SetIsServerAuthorized(bool in_is_server_authorized) -{ - WorldServer::m_is_server_authorized = in_is_server_authorized; - - return this; -} - -/** - * @return - */ -bool WorldServer::IsServerLoggedIn() const -{ - return m_is_server_logged_in; -} - -/** - * @param in_is_server_logged_in - */ -WorldServer *WorldServer::SetIsServerLoggedIn(bool in_is_server_logged_in) -{ - WorldServer::m_is_server_logged_in = in_is_server_logged_in; - - return this; -} - -/** - * @return - */ -bool WorldServer::IsServerTrusted() const -{ - return m_is_server_trusted; -} - -/** - * @param in_is_server_trusted - */ -WorldServer *WorldServer::SetIsServerTrusted(bool in_is_server_trusted) -{ - WorldServer::m_is_server_trusted = in_is_server_trusted; - - return this; -} - -/** - * @param in_zones_booted - */ -WorldServer *WorldServer::SetZonesBooted(unsigned int in_zones_booted) -{ - WorldServer::m_zones_booted = in_zones_booted; - - return this; -} - -/** - * @param in_players_online - */ -WorldServer *WorldServer::SetPlayersOnline(unsigned int in_players_online) -{ - WorldServer::m_players_online = in_players_online; - - return this; -} - -/** - * @param in_server_status - */ -WorldServer *WorldServer::SetServerStatus(int in_server_status) -{ - WorldServer::m_server_status = in_server_status; - - return this; -} - -/** - * @param in_server_process_type - */ -WorldServer *WorldServer::SetServerProcessType(unsigned int in_server_process_type) -{ - WorldServer::m_server_process_type = in_server_process_type; - - return this; -} - -/** - * @param in_long_name - */ -WorldServer *WorldServer::SetLongName(const std::string &in_long_name) -{ - WorldServer::m_long_name = in_long_name; - - return this; -} - -/** - * @param in_short_name - */ -WorldServer *WorldServer::SetShortName(const std::string &in_short_name) -{ - WorldServer::m_short_name = in_short_name; - - return this; -} - -/** - * @param in_account_name - */ -WorldServer *WorldServer::SetAccountName(const std::string &in_account_name) -{ - WorldServer::m_account_name = in_account_name; - - return this; -} - -/** - * @param in_account_password - */ -WorldServer *WorldServer::SetAccountPassword(const std::string &in_account_password) -{ - WorldServer::m_account_password = in_account_password; - - return this; -} - -/** - * @param in_remote_ip - */ -WorldServer *WorldServer::SetRemoteIp(const std::string &in_remote_ip) -{ - WorldServer::m_remote_ip_address = in_remote_ip; - - return this; -} - -/** - * @param in_local_ip - */ -WorldServer *WorldServer::SetLocalIp(const std::string &in_local_ip) -{ - WorldServer::m_local_ip = in_local_ip; - - return this; -} - -/** - * @param in_protocol - */ -WorldServer *WorldServer::SetProtocol(const std::string &in_protocol) -{ - WorldServer::m_protocol = in_protocol; - - return this; -} - -/** - * @param in_version - */ -WorldServer *WorldServer::SetVersion(const std::string &in_version) -{ - WorldServer::m_version = in_version; - - return this; -} - -/** - * @return - */ -int WorldServer::GetServerStatus() const -{ - return m_server_status; -} - -/** - * @return - */ -unsigned int WorldServer::GetServerListTypeId() const -{ - return m_server_list_type_id; -} - -/** - * @return - */ -unsigned int WorldServer::GetServerProcessType() const -{ - return m_server_process_type; -} - -/** - * @return - */ -const std::string &WorldServer::GetAccountName() const -{ - return m_account_name; -} - -/** - * @return - */ -const std::string &WorldServer::GetAccountPassword() const -{ - return m_account_password; -} - -/** - * @return - */ -const std::string &WorldServer::GetRemoteIp() const -{ - return m_remote_ip_address; -} - -/** - * @return - */ -const std::string &WorldServer::GetLocalIp() const -{ - return m_local_ip; -} - -/** - * @return - */ -const std::string &WorldServer::GetProtocol() const -{ - return m_protocol; -} - -/** - * @return - */ -const std::string &WorldServer::GetVersion() const -{ - return m_version; -} - -void WorldServer::OnKeepAlive(EQ::Timer *t) -{ - ServerPacket pack(ServerOP_KeepAlive, 0); - m_connection->SendPacket(&pack); -} - void WorldServer::FormatWorldServerName(char *name, int8 server_list_type) { std::string server_long_name = name; diff --git a/loginserver/world_server.h b/loginserver/world_server.h index df5324240..f5120d38e 100644 --- a/loginserver/world_server.h +++ b/loginserver/world_server.h @@ -5,9 +5,10 @@ #include "../common/net/servertalk_server_connection.h" #include "../common/servertalk.h" #include "../common/packet_dump.h" -#include "database.h" #include "../common/event/timer.h" #include "login_types.h" +#include "client.h" +#include "../common/repositories/login_server_admins_repository.h" #include #include @@ -17,25 +18,10 @@ class WorldServer { public: WorldServer(std::shared_ptr worldserver_connection); - - /** - * Destructor, frees our connection if it exists - */ ~WorldServer(); - - /** - * Resets the basic stats of this server. - */ void Reset(); - /** - * Accesses connection, it is intentional that this is not const (trust me). - */ std::shared_ptr GetConnection() { return m_connection; } - - /** - * @return - */ unsigned int GetServerId() const { return m_server_id; } WorldServer *SetServerId(unsigned int id) { @@ -43,25 +29,13 @@ public: return this; } - /** - * @return - */ - std::string GetServerLongName() const { return m_long_name; } - std::string GetServerShortName() const { return m_short_name; } - - /** - * Gets whether the server is authorized to show up on the server list or not - * @return - */ - bool IsAuthorized() const { return m_is_server_authorized; } + std::string GetServerLongName() const { return m_server_long_name; } + std::string GetServerShortName() const { return m_server_short_name; } + bool IsAuthorizedToList() const { return m_is_server_authorized_to_list; } std::string GetLocalIP() const { return m_local_ip; } std::string GetRemoteIP() const { return m_remote_ip_address; } - /** - * Gets what kind of server this server is (legends, preferred, normal) - * - * @return - */ + // Gets what kind of server this server is (legends, preferred, normal) unsigned int GetServerListID() const { return m_server_list_type_id; } WorldServer *SetServerListTypeId(unsigned int in_server_list_id); @@ -69,97 +43,18 @@ public: unsigned int GetZonesBooted() const { return m_zones_booted; } unsigned int GetPlayersOnline() const { return m_players_online; } - /** - * Takes the info struct we received from world and processes it - * - * @param new_world_server_info_packet - */ - void Handle_NewLSInfo(ServerNewLSInfo_Struct *new_world_server_info_packet); - - /** - * Takes the status struct we received from world and processes it - * - * @param server_login_status - */ - void Handle_LSStatus(ServerLSStatus_Struct *server_login_status); - - bool HandleNewLoginserverInfoValidation(ServerNewLSInfo_Struct *new_world_server_info_packet); - - /** - * Informs world that there is a client incoming with the following data. - * - * @param ip - * @param account - * @param key - * @param account_id - * @param loginserver_name - */ - void SendClientAuth( - std::string ip, - std::string account, - std::string key, - unsigned int account_id, - const std::string &loginserver_name - ); - - /** - * @param world_admin_id - * @param world_admin_username - * @param world_admin_password - * @param world_admin_password_hash - * @return - */ + void HandleNewWorldserver(LoginserverNewWorldRequest *req); + void HandleWorldserverStatusUpdate(LoginserverWorldStatusUpdate *u); + bool HandleNewWorldserverValidation(LoginserverNewWorldRequest *r); + void SendClientAuthToWorld(Client *c); static bool ValidateWorldServerAdminLogin( - int world_admin_id, - const std::string &world_admin_username, - const std::string &world_admin_password, - const std::string &world_admin_password_hash + LoginWorldAdminAccountContext &c, + LoginServerAdminsRepository::LoginServerAdmins &admin ); - - WorldServer *SetZonesBooted(unsigned int in_zones_booted); - WorldServer *SetPlayersOnline(unsigned int in_players_online); - WorldServer *SetServerStatus(int in_server_status); - WorldServer *SetServerProcessType(unsigned int in_server_process_type); - WorldServer *SetLongName(const std::string &in_long_name); - WorldServer *SetShortName(const std::string &in_short_name); - WorldServer *SetAccountName(const std::string &in_account_name); - WorldServer *SetAccountPassword(const std::string &in_account_password); - WorldServer *SetRemoteIp(const std::string &in_remote_ip); - WorldServer *SetLocalIp(const std::string &in_local_ip); - WorldServer *SetProtocol(const std::string &in_protocol); - WorldServer *SetVersion(const std::string &in_version); - WorldServer *SetServerDescription(const std::string &in_server_description); - WorldServer *SetIsServerAuthorized(bool in_is_server_authorized); - WorldServer *SetIsServerLoggedIn(bool in_is_server_logged_in); - WorldServer *SetIsServerTrusted(bool in_is_server_trusted); - - bool IsServerAuthorized() const; - bool IsServerLoggedIn() const; - bool IsServerTrusted() const; - const std::string &GetAccountName() const; - const std::string &GetAccountPassword() const; - const std::string &GetLocalIp() const; - const std::string &GetProtocol() const; - const std::string &GetRemoteIp() const; - const std::string &GetServerDescription() const; - const std::string &GetVersion() const; - int GetServerStatus() const; - unsigned int GetServerListTypeId() const; - unsigned int GetServerProcessType() const; - - bool HandleNewLoginserverRegisteredOnly(Database::DbWorldRegistration &world_registration); - bool HandleNewLoginserverInfoUnregisteredAllowed(Database::DbWorldRegistration &world_registration); - - void SerializeForClientServerList(class SerializeBuffer& out, bool use_local_ip, LSClientVersion version) const; + void SerializeForClientServerList(class SerializeBuffer &out, bool use_local_ip, LSClientVersion version) const; private: - /** - * Packet processing functions - * - * @param opcode - * @param packet - */ void ProcessNewLSInfo(uint16_t opcode, const EQ::Net::Packet &packet); void ProcessLSStatus(uint16_t opcode, const EQ::Net::Packet &packet); void ProcessUserToWorldResponseLegacy(uint16_t opcode, const EQ::Net::Packet &packet); @@ -175,24 +70,17 @@ private: unsigned int m_server_list_type_id; unsigned int m_server_process_type; std::string m_server_description; - std::string m_long_name; - std::string m_short_name; + std::string m_server_long_name; + std::string m_server_short_name; std::string m_account_name; std::string m_account_password; std::string m_remote_ip_address; std::string m_local_ip; std::string m_protocol; - std::string m_version; - bool m_is_server_authorized; + std::string m_server_version; + bool m_is_server_authorized_to_list; bool m_is_server_logged_in; - bool m_is_server_trusted; - - /** - * Keepalive - * @param t - */ - void OnKeepAlive(EQ::Timer *t); - std::unique_ptr m_keepalive; + bool m_is_server_trusted; // this is primarily for worldserver being able to push updates to the loginserver static void FormatWorldServerName(char *name, int8 server_list_type); }; diff --git a/loginserver/world_server_manager.cpp b/loginserver/world_server_manager.cpp new file mode 100644 index 000000000..f18b48537 --- /dev/null +++ b/loginserver/world_server_manager.cpp @@ -0,0 +1,218 @@ +#include "world_server_manager.h" +#include "login_server.h" +#include "login_types.h" +#include + +#include "../common/eqemu_logsys.h" +#include "../common/ip_util.h" + +extern LoginServer server; +extern bool run_server; + +WorldServerManager::WorldServerManager() +{ + int listen_port = server.config.GetVariableInt("general", "listen_port", 5998); + + m_server_connection = std::make_unique(); + EQ::Net::ServertalkServerOptions opts; + opts.port = listen_port; + opts.ipv6 = false; + m_server_connection->Listen(opts); + + LogInfo("Loginserver now listening on port [{}]", listen_port); + + m_server_connection->OnConnectionIdentified( + "World", [this](std::shared_ptr c) { + LogInfo( + "New World Server connection from remote_ip [{}] port [{}]", + c->Handle()->RemoteIP(), + c->Handle()->RemotePort() + ); + + auto iter = std::find_if( + m_world_servers.begin(), m_world_servers.end(), + [&](const std::unique_ptr &s) { + return s->GetConnection()->Handle()->RemoteIP() == c->Handle()->RemoteIP() && + s->GetConnection()->Handle()->RemotePort() == c->Handle()->RemotePort(); + } + ); + + if (iter != m_world_servers.end()) { + LogInfo( + "World server already existed for remote_ip [{}] port [{}] removing existing connection.", + c->Handle()->RemoteIP(), + c->Handle()->RemotePort() + ); + + m_world_servers.erase(iter); + } + + m_world_servers.push_back(std::make_unique(c)); + } + ); + + m_server_connection->OnConnectionRemoved( + "World", [this](std::shared_ptr c) { + auto iter = std::find_if( + m_world_servers.begin(), m_world_servers.end(), + [&](const std::unique_ptr &server) { + return server->GetConnection()->GetUUID() == c->GetUUID(); + } + ); + + if (iter != m_world_servers.end()) { + LogInfo( + "World server ID [{}] long_name [{}] short_name [{}] has been disconnected, removing.", + (*iter)->GetServerId(), + (*iter)->GetServerLongName(), + (*iter)->GetServerShortName() + ); + m_world_servers.erase(iter); + } + } + ); + +} + +WorldServerManager::~WorldServerManager() = default; + +std::unique_ptr WorldServerManager::CreateServerListPacket(Client *client, uint32 sequence) +{ + unsigned int server_count = 0; + in_addr in{}; + in.s_addr = client->GetConnection()->GetRemoteIP(); + std::string client_ip = inet_ntoa(in); + + LogDebug("ServerManager::CreateServerListPacket via client address [{}]", client_ip); + + for (const auto &world_server: m_world_servers) { + if (world_server->IsAuthorizedToList()) { + ++server_count; + } + } + + SerializeBuffer buf; + + // LoginBaseMessage_Struct header + buf.WriteInt32(sequence); + buf.WriteInt8(0); + buf.WriteInt8(0); + buf.WriteInt32(0); + + // LoginBaseReplyMessage_Struct + buf.WriteInt8(true); // success (no error) + buf.WriteInt32(0x65); // 101 "No Error" eqlsstr + buf.WriteString(""); + + // ServerListReply_Struct + buf.WriteInt32(server_count); + + for (const auto &s: m_world_servers) { + if (!s->IsAuthorizedToList()) { + LogDebug( + "ServerManager::CreateServerListPacket | Server [{}] via IP [{}] is not authorized to be listed", + s->GetServerLongName(), + s->GetConnection()->Handle()->RemoteIP() + ); + continue; + } + + bool use_local_ip = false; + + std::string world_ip = s->GetConnection()->Handle()->RemoteIP(); + if (world_ip == client_ip || IpUtil::IsIpInPrivateRfc1918(client_ip)) { + use_local_ip = true; + } + + LogDebug( + "CreateServerListPacket | Building list entry | Client [{}] IP [{}] Server Long Name [{}] Server IP [{}] ({})", + client->GetAccountName(), + client_ip, + s->GetServerLongName(), + use_local_ip ? s->GetLocalIP() : s->GetRemoteIP(), + use_local_ip ? "Local" : "Remote" + ); + + s->SerializeForClientServerList(buf, use_local_ip, client->GetClientVersion()); + } + + return std::make_unique(OP_ServerListResponse, buf); +} + +void WorldServerManager::SendUserLoginToWorldRequest( + unsigned int server_id, + unsigned int client_account_id, + const std::string &client_loginserver +) +{ + auto iter = std::find_if( + m_world_servers.begin(), m_world_servers.end(), + [&](const std::unique_ptr &server) { + return server->GetServerId() == server_id; + } + ); + + if (iter != m_world_servers.end()) { + EQ::Net::DynamicPacket outapp; + outapp.Resize(sizeof(UsertoWorldRequest)); + + auto *r = reinterpret_cast(outapp.Data()); + r->worldid = server_id; + r->lsaccountid = client_account_id; + strncpy(r->login, client_loginserver.c_str(), 64); + + (*iter)->GetConnection()->Send(ServerOP_UsertoWorldReq, outapp); + + LogNetcode("[UsertoWorldRequest] [Size: {}]\n{}", outapp.Length(), outapp.ToString()); + } + else { + LogError("Client requested a user to world but supplied an invalid id of {}", server_id); + } +} + +bool WorldServerManager::DoesServerExist( + const std::string &server_long_name, + const std::string &server_short_name, + WorldServer *ignore +) +{ + return std::any_of( + m_world_servers.begin(), m_world_servers.end(), [&](const std::unique_ptr &s) { + return s.get() != ignore && + s->GetServerLongName() == server_long_name && + s->GetServerShortName() == server_short_name; + } + ); +} + +void WorldServerManager::DestroyServerByName( + std::string server_long_name, + std::string server_short_name, + WorldServer *ignore +) +{ + std::erase_if( + m_world_servers, [&](const std::unique_ptr &s) { + if (s.get() == ignore) { + return false; + } + if (s->GetServerLongName() == server_long_name && + s->GetServerShortName() == server_short_name) { + s->GetConnection()->Handle()->Disconnect(); + LogInfo( + "Removing world server ID [{}] long name [{}] short name [{}]", + s->GetServerId(), + server_long_name, + server_short_name + ); + return true; + } + return false; + } + ); +} + +const std::list> &WorldServerManager::GetWorldServers() const +{ + return m_world_servers; +} diff --git a/loginserver/world_server_manager.h b/loginserver/world_server_manager.h new file mode 100644 index 000000000..711bc7560 --- /dev/null +++ b/loginserver/world_server_manager.h @@ -0,0 +1,33 @@ +#ifndef EQEMU_SERVERMANAGER_H +#define EQEMU_SERVERMANAGER_H + +#include "../common/global_define.h" +#include "../common/servertalk.h" +#include "../common/packet_dump.h" +#include "../common/net/servertalk_server.h" +#include "world_server.h" +#include "client.h" +#include + +class WorldServerManager { +public: + WorldServerManager(); + ~WorldServerManager(); + void SendUserLoginToWorldRequest( + unsigned int server_id, + unsigned int client_account_id, + const std::string &client_loginserver + ); + std::unique_ptr CreateServerListPacket(Client *client, uint32 sequence); + bool DoesServerExist(const std::string &s, const std::string &server_short_name, WorldServer *ignore = nullptr); + void DestroyServerByName(std::string s, std::string server_short_name, WorldServer *ignore = nullptr); + const std::list> &GetWorldServers() const; + +private: + std::unique_ptr m_server_connection; + std::list> m_world_servers; + +}; + +#endif + diff --git a/package.json b/package.json index 26dfe3d29..985a21c38 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eqemu-server", - "version": "22.60.0", + "version": "23.10.3", "repository": { "type": "git", "url": "https://github.com/EQEmu/Server.git" diff --git a/queryserv/CMakeLists.txt b/queryserv/CMakeLists.txt index bd6e37ecd..82613065a 100644 --- a/queryserv/CMakeLists.txt +++ b/queryserv/CMakeLists.txt @@ -6,6 +6,8 @@ SET(qserv_sources queryserv.cpp queryservconfig.cpp worldserver.cpp + zonelist.cpp + zoneserver.cpp ) SET(qserv_headers @@ -13,6 +15,8 @@ SET(qserv_headers lfguild.h queryservconfig.h worldserver.h + zonelist.h + zoneserver.h ) ADD_EXECUTABLE(queryserv ${qserv_sources} ${qserv_headers}) diff --git a/queryserv/database.cpp b/queryserv/database.cpp index 5bd21ac5e..db6be3ce2 100644 --- a/queryserv/database.cpp +++ b/queryserv/database.cpp @@ -1,386 +1,9 @@ -/* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2008 EQEMu Development Team (http://eqemulator.net) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; version 2 of the License. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY except by those people which sell it, which - are required to give you total support for your newly bought product; - without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -*/ - - #include "../common/global_define.h" #include "../common/eqemu_logsys.h" #include -#include -#include -#include -#include -#include -#include -#include #include -#include - -// Disgrace: for windows compile -#ifdef _WINDOWS -#include -#define snprintf _snprintf -#define strncasecmp _strnicmp -#define strcasecmp _stricmp -#else - -#include "../common/unix.h" -#include - -#endif - #include "database.h" -#include "../common/eq_packet_structs.h" #include "../common/strings.h" -#include "../common/servertalk.h" - -void QSDatabase::AddSpeech( - const char *from, - const char *to, - const char *message, - uint16 minstatus, - uint32 guilddbid, - uint8 type -) -{ - - auto escapedFrom = new char[strlen(from) * 2 + 1]; - auto escapedTo = new char[strlen(to) * 2 + 1]; - auto escapedMessage = new char[strlen(message) * 2 + 1]; - DoEscapeString(escapedFrom, from, strlen(from)); - DoEscapeString(escapedTo, to, strlen(to)); - DoEscapeString(escapedMessage, message, strlen(message)); - - std::string query = StringFormat( - "INSERT INTO `qs_player_speech` " - "SET `from` = '%s', `to` = '%s', `message`='%s', " - "`minstatus`='%i', `guilddbid`='%i', `type`='%i'", - escapedFrom, escapedTo, escapedMessage, minstatus, guilddbid, type - ); - safe_delete_array(escapedFrom); - safe_delete_array(escapedTo); - safe_delete_array(escapedMessage); - auto results = QueryDatabase(query); - if (!results.Success()) { - LogInfo("Failed Speech Entry Insert: [{}]", results.ErrorMessage().c_str()); - LogInfo("[{}]", query.c_str()); - } - - -} - -void QSDatabase::LogPlayerDropItem(QSPlayerDropItem_Struct *QS) -{ - - std::string query = StringFormat( - "INSERT INTO `qs_player_drop_record` SET `time` = NOW(), " - "`char_id` = '%i', `pickup` = '%i', " - "`zone_id` = '%i', `x` = '%i', `y` = '%i', `z` = '%i' ", - QS->char_id, QS->pickup, QS->zone_id, QS->x, QS->y, QS->z - ); - - auto results = QueryDatabase(query); - if (!results.Success()) { - LogInfo("Failed Drop Record Insert: [{}]", results.ErrorMessage().c_str()); - LogInfo("[{}]", query.c_str()); - } - - if (QS->_detail_count == 0) { - return; - } - - int lastIndex = results.LastInsertedID(); - - for (int i = 0; i < QS->_detail_count; i++) { - query = StringFormat( - "INSERT INTO `qs_player_drop_record_entries` SET `event_id` = '%i', " - "`item_id` = '%i', `charges` = '%i', `aug_1` = '%i', `aug_2` = '%i', " - "`aug_3` = '%i', `aug_4` = '%i', `aug_5` = '%i'", - lastIndex, QS->items[i].item_id, - QS->items[i].charges, QS->items[i].aug_1, QS->items[i].aug_2, - QS->items[i].aug_3, QS->items[i].aug_4, QS->items[i].aug_5 - ); - results = QueryDatabase(query); - if (!results.Success()) { - LogInfo("Failed Drop Record Entry Insert: [{}]", results.ErrorMessage().c_str()); - LogInfo("[{}]", query.c_str()); - } - } -} - -void QSDatabase::LogPlayerTrade(PlayerLogTrade_Struct *QS, uint32 detailCount) -{ - - std::string query = StringFormat( - "INSERT INTO `qs_player_trade_record` SET `time` = NOW(), " - "`char1_id` = '%i', `char1_pp` = '%i', `char1_gp` = '%i', " - "`char1_sp` = '%i', `char1_cp` = '%i', `char1_items` = '%i', " - "`char2_id` = '%i', `char2_pp` = '%i', `char2_gp` = '%i', " - "`char2_sp` = '%i', `char2_cp` = '%i', `char2_items` = '%i'", - QS->character_1_id, QS->character_1_money.platinum, QS->character_1_money.gold, - QS->character_1_money.silver, QS->character_1_money.copper, QS->character_1_item_count, - QS->character_2_id, QS->character_2_money.platinum, QS->character_2_money.gold, - QS->character_2_money.silver, QS->character_2_money.copper, QS->character_2_item_count - ); - auto results = QueryDatabase(query); - if (!results.Success()) { - LogInfo("Failed Trade Log Record Insert: [{}]", results.ErrorMessage().c_str()); - LogInfo("[{}]", query.c_str()); - } - - if (detailCount == 0) { - return; - } - - int lastIndex = results.LastInsertedID(); - - for (int i = 0; i < detailCount; i++) { - query = StringFormat( - "INSERT INTO `qs_player_trade_record_entries` SET `event_id` = '%i', " - "`from_id` = '%i', `from_slot` = '%i', `to_id` = '%i', `to_slot` = '%i', " - "`item_id` = '%i', `charges` = '%i', `aug_1` = '%i', `aug_2` = '%i', " - "`aug_3` = '%i', `aug_4` = '%i', `aug_5` = '%i'", - lastIndex, QS->item_entries[i].from_character_id, QS->item_entries[i].from_slot, - QS->item_entries[i].to_character_id, QS->item_entries[i].to_slot, QS->item_entries[i].item_id, - QS->item_entries[i].charges, QS->item_entries[i].aug_1, QS->item_entries[i].aug_2, - QS->item_entries[i].aug_3, QS->item_entries[i].aug_4, QS->item_entries[i].aug_5 - ); - results = QueryDatabase(query); - if (!results.Success()) { - LogInfo("Failed Trade Log Record Entry Insert: [{}]", results.ErrorMessage().c_str()); - LogInfo("[{}]", query.c_str()); - } - - } - -} - -void QSDatabase::LogPlayerHandin(QSPlayerLogHandin_Struct *QS, uint32 detailCount) -{ - - std::string query = StringFormat( - "INSERT INTO `qs_player_handin_record` SET `time` = NOW(), " - "`quest_id` = '%i', `char_id` = '%i', `char_pp` = '%i', " - "`char_gp` = '%i', `char_sp` = '%i', `char_cp` = '%i', " - "`char_items` = '%i', `npc_id` = '%i', `npc_pp` = '%i', " - "`npc_gp` = '%i', `npc_sp` = '%i', `npc_cp` = '%i', " - "`npc_items`='%i'", - QS->quest_id, QS->char_id, QS->char_money.platinum, - QS->char_money.gold, QS->char_money.silver, QS->char_money.copper, - QS->char_count, QS->npc_id, QS->npc_money.platinum, - QS->npc_money.gold, QS->npc_money.silver, QS->npc_money.copper, - QS->npc_count - ); - auto results = QueryDatabase(query); - if (!results.Success()) { - LogInfo("Failed Handin Log Record Insert: [{}]", results.ErrorMessage().c_str()); - LogInfo("[{}]", query.c_str()); - } - - if (detailCount == 0) { - return; - } - - int lastIndex = results.LastInsertedID(); - - for (int i = 0; i < detailCount; i++) { - query = StringFormat( - "INSERT INTO `qs_player_handin_record_entries` SET `event_id` = '%i', " - "`action_type` = '%s', `char_slot` = '%i', `item_id` = '%i', " - "`charges` = '%i', `aug_1` = '%i', `aug_2` = '%i', `aug_3` = '%i', " - "`aug_4` = '%i', `aug_5` = '%i'", - lastIndex, QS->items[i].action_type, QS->items[i].char_slot, - QS->items[i].item_id, QS->items[i].charges, QS->items[i].aug_1, - QS->items[i].aug_2, QS->items[i].aug_3, QS->items[i].aug_4, - QS->items[i].aug_5 - ); - auto results = QueryDatabase(query); - - if (!results.Success()) { - LogInfo("Failed Handin Log Record Entry Insert: [{}]", results.ErrorMessage().c_str()); - LogInfo("[{}]", query.c_str()); - } - } - -} - -void QSDatabase::LogPlayerNPCKill(QSPlayerLogNPCKill_Struct *QS, uint32 members) -{ - - std::string query = StringFormat( - "INSERT INTO `qs_player_npc_kill_record` " - "SET `npc_id` = '%i', `type` = '%i', " - "`zone_id` = '%i', `time` = NOW()", - QS->s1.NPCID, QS->s1.Type, QS->s1.ZoneID - ); - auto results = QueryDatabase(query); - if (!results.Success()) { - LogInfo("Failed NPC Kill Log Record Insert: [{}]", results.ErrorMessage().c_str()); - LogInfo("[{}]", query.c_str()); - } - - if (members == 0) { - return; - } - - int lastIndex = results.LastInsertedID(); - - for (int i = 0; i < members; i++) { - query = StringFormat( - "INSERT INTO `qs_player_npc_kill_record_entries` " - "SET `event_id` = '%i', `char_id` = '%i'", - lastIndex, QS->Chars[i].char_id - ); - auto results = QueryDatabase(query); - if (!results.Success()) { - LogInfo("Failed NPC Kill Log Entry Insert: [{}]", results.ErrorMessage().c_str()); - LogInfo("[{}]", query.c_str()); - } - - } - -} - -void QSDatabase::LogPlayerDelete(QSPlayerLogDelete_Struct *QS, uint32 items) -{ - - std::string query = StringFormat( - "INSERT INTO `qs_player_delete_record` SET `time` = NOW(), " - "`char_id` = '%i', `stack_size` = '%i', `char_items` = '%i'", - QS->char_id, QS->stack_size, QS->char_count, QS->char_count - ); - auto results = QueryDatabase(query); - if (!results.Success()) { - LogInfo("Failed Delete Log Record Insert: [{}]", results.ErrorMessage().c_str()); - LogInfo("[{}]", query.c_str()); - } - - if (items == 0) { - return; - } - - int lastIndex = results.LastInsertedID(); - - for (int i = 0; i < items; i++) { - query = StringFormat( - "INSERT INTO `qs_player_delete_record_entries` SET `event_id` = '%i', " - "`char_slot` = '%i', `item_id` = '%i', `charges` = '%i', `aug_1` = '%i', " - "`aug_2` = '%i', `aug_3` = '%i', `aug_4` = '%i', `aug_5` = '%i'", - lastIndex, QS->items[i].char_slot, QS->items[i].item_id, QS->items[i].charges, - QS->items[i].aug_1, QS->items[i].aug_2, QS->items[i].aug_3, QS->items[i].aug_4, - QS->items[i].aug_5 - ); - results = QueryDatabase(query); - if (!results.Success()) { - LogInfo("Failed Delete Log Record Entry Insert: [{}]", results.ErrorMessage().c_str()); - LogInfo("[{}]", query.c_str()); - } - - } - -} - -void QSDatabase::LogPlayerMove(QSPlayerLogMove_Struct *QS, uint32 items) -{ - /* These are item moves */ - - std::string query = StringFormat( - "INSERT INTO `qs_player_move_record` SET `time` = NOW(), " - "`char_id` = '%i', `from_slot` = '%i', `to_slot` = '%i', " - "`stack_size` = '%i', `char_items` = '%i', `postaction` = '%i'", - QS->char_id, QS->from_slot, QS->to_slot, QS->stack_size, - QS->char_count, QS->postaction - ); - auto results = QueryDatabase(query); - if (!results.Success()) { - LogInfo("Failed Move Log Record Insert: [{}]", results.ErrorMessage().c_str()); - LogInfo("[{}]", query.c_str()); - } - - if (items == 0) { - return; - } - - int lastIndex = results.LastInsertedID(); - - for (int i = 0; i < items; i++) { - query = StringFormat( - "INSERT INTO `qs_player_move_record_entries` SET `event_id` = '%i', " - "`from_slot` = '%i', `to_slot` = '%i', `item_id` = '%i', `charges` = '%i', " - "`aug_1` = '%i', `aug_2` = '%i', `aug_3` = '%i', `aug_4` = '%i', `aug_5` = '%i'", - lastIndex, QS->items[i].from_slot, QS->items[i].to_slot, QS->items[i].item_id, - QS->items[i].charges, QS->items[i].aug_1, QS->items[i].aug_2, - QS->items[i].aug_3, QS->items[i].aug_4, QS->items[i].aug_5 - ); - results = QueryDatabase(query); - if (!results.Success()) { - LogInfo("Failed Move Log Record Entry Insert: [{}]", results.ErrorMessage().c_str()); - LogInfo("[{}]", query.c_str()); - } - } -} - -void QSDatabase::LogMerchantTransaction(QSMerchantLogTransaction_Struct *QS, uint32 items) -{ - /* Merchant transactions are from the perspective of the merchant, not the player */ - std::string query = StringFormat( - "INSERT INTO `qs_merchant_transaction_record` SET `time` = NOW(), " - "`zone_id` = '%i', `merchant_id` = '%i', `merchant_pp` = '%i', " - "`merchant_gp` = '%i', `merchant_sp` = '%i', `merchant_cp` = '%i', " - "`merchant_items` = '%i', `char_id` = '%i', `char_pp` = '%i', " - "`char_gp` = '%i', `char_sp` = '%i', `char_cp` = '%i', " - "`char_items` = '%i'", - QS->zone_id, QS->merchant_id, QS->merchant_money.platinum, - QS->merchant_money.gold, QS->merchant_money.silver, - QS->merchant_money.copper, QS->merchant_count, QS->char_id, - QS->char_money.platinum, QS->char_money.gold, QS->char_money.silver, - QS->char_money.copper, QS->char_count - ); - auto results = QueryDatabase(query); - if (!results.Success()) { - LogInfo("Failed Transaction Log Record Insert: [{}]", results.ErrorMessage().c_str()); - LogInfo("[{}]", query.c_str()); - } - - if (items == 0) { - return; - } - - int lastIndex = results.LastInsertedID(); - - for (int i = 0; i < items; i++) { - query = StringFormat( - "INSERT INTO `qs_merchant_transaction_record_entries` SET `event_id` = '%i', " - "`char_slot` = '%i', `item_id` = '%i', `charges` = '%i', `aug_1` = '%i', " - "`aug_2` = '%i', `aug_3` = '%i', `aug_4` = '%i', `aug_5` = '%i'", - lastIndex, QS->items[i].char_slot, QS->items[i].item_id, QS->items[i].charges, - QS->items[i].aug_1, QS->items[i].aug_2, QS->items[i].aug_3, QS->items[i].aug_4, - QS->items[i].aug_5 - ); - results = QueryDatabase(query); - if (!results.Success()) { - LogInfo("Failed Transaction Log Record Entry Insert: [{}]", results.ErrorMessage().c_str()); - LogInfo("[{}]", query.c_str()); - } - - } - -} // this function does not delete the ServerPacket, so it must be handled at call site void QSDatabase::GeneralQueryReceive(ServerPacket *pack) @@ -388,7 +11,7 @@ void QSDatabase::GeneralQueryReceive(ServerPacket *pack) /* These are general queries passed from anywhere in zone instead of packing structures and breaking them down again and again */ - auto queryBuffer = new char[pack->ReadUInt32() + 1]; + auto queryBuffer = new char[pack->ReadUInt32() + 1]; pack->ReadString(queryBuffer); std::string query(queryBuffer); diff --git a/queryserv/database.h b/queryserv/database.h index 5072f5b55..69b0b8e36 100644 --- a/queryserv/database.h +++ b/queryserv/database.h @@ -1,22 +1,3 @@ -/* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2008 EQEMu Development Team (http://eqemulator.net) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; version 2 of the License. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY except by those people which sell it, which - are required to give you total support for your newly bought product; - without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -*/ - #ifndef CHATSERVER_DATABASE_H #define CHATSERVER_DATABASE_H @@ -33,17 +14,8 @@ #include #include - class QSDatabase : public Database { public: - void AddSpeech(const char* from, const char* to, const char* message, uint16 minstatus, uint32 guilddbid, uint8 type); - void LogPlayerTrade(PlayerLogTrade_Struct* QS, uint32 DetailCount); - void LogPlayerDropItem(QSPlayerDropItem_Struct* QS); - void LogPlayerHandin(QSPlayerLogHandin_Struct* QS, uint32 DetailCount); - void LogPlayerNPCKill(QSPlayerLogNPCKill_Struct* QS, uint32 Members); - void LogPlayerDelete(QSPlayerLogDelete_Struct* QS, uint32 Items); - void LogPlayerMove(QSPlayerLogMove_Struct* QS, uint32 Items); - void LogMerchantTransaction(QSMerchantLogTransaction_Struct* QS, uint32 Items); void GeneralQueryReceive(ServerPacket *pack); }; diff --git a/queryserv/lfguild.cpp b/queryserv/lfguild.cpp index 5e80a8511..688102ed1 100644 --- a/queryserv/lfguild.cpp +++ b/queryserv/lfguild.cpp @@ -1,14 +1,11 @@ -#include #include "lfguild.h" #include "database.h" #include "worldserver.h" -#include "../common/eqemu_logsys.h" #include "../common/strings.h" -#include "../common/packet_dump.h" #include "../common/rulesys.h" extern WorldServer *worldserver; -extern QSDatabase database; +extern QSDatabase qs_database; PlayerLookingForGuild::PlayerLookingForGuild(char *name, char *comments, uint32 level, uint32 classes, uint32 aa_count, uint32 time_zone, uint32 time_posted) { @@ -38,7 +35,7 @@ bool LFGuildManager::LoadDatabase() std::string query = "SELECT `type`,`name`,`comment`, " "`fromlevel`, `tolevel`, `classes`, " "`aacount`, `timezone`, `timeposted` FROM `lfguild`"; - auto results = database.QueryDatabase(query); + auto results = qs_database.QueryDatabase(query); if (!results.Success()) { return false; } @@ -239,7 +236,7 @@ void LFGuildManager::TogglePlayer(uint32 FromZoneID, uint32 FromInstanceID, char } std::string query = StringFormat("DELETE FROM `lfguild` WHERE `type` = 0 AND `name` = '%s'", From); - auto results = database.QueryDatabase(query); + auto results = qs_database.QueryDatabase(query); uint32 Now = time(nullptr); @@ -252,7 +249,7 @@ void LFGuildManager::TogglePlayer(uint32 FromZoneID, uint32 FromInstanceID, char "`classes`, `aacount`, `timezone`, `timeposted`) " "VALUES (0, '%s', '%s', %u, 0, %u, %u, %u, %u)", From, Comments, Level, Class, AAPoints, TimeZone, Now); - auto results = database.QueryDatabase(query); + auto results = qs_database.QueryDatabase(query); } auto pack = new ServerPacket(ServerOP_QueryServGeneric, strlen(From) + strlen(Comments) + 30); @@ -281,7 +278,7 @@ void LFGuildManager::ToggleGuild(uint32 FromZoneID, uint32 FromInstanceID, char } std::string query = StringFormat("DELETE FROM `lfguild` WHERE `type` = 1 AND `name` = '%s'", GuildName); - auto results = database.QueryDatabase(query); + auto results = qs_database.QueryDatabase(query); uint32 Now = time(nullptr); @@ -296,7 +293,7 @@ void LFGuildManager::ToggleGuild(uint32 FromZoneID, uint32 FromInstanceID, char "VALUES (1, '%s', '%s', %u, %u, %u, %u, %u, %u)", GuildName, Comments, FromLevel, ToLevel, Classes, AACount, TimeZone, Now); - auto results = database.QueryDatabase(query); + auto results = qs_database.QueryDatabase(query); } @@ -324,7 +321,7 @@ void LFGuildManager::ExpireEntries() continue; std::string query = StringFormat("DELETE from `lfguild` WHERE `type` = 0 AND `name` = '%s'", (*it).Name.c_str()); - auto results = database.QueryDatabase(query); + auto results = qs_database.QueryDatabase(query); if(!results.Success()) it = Players.erase(it); @@ -336,7 +333,7 @@ void LFGuildManager::ExpireEntries() continue; std::string query = StringFormat("DELETE from `lfguild` WHERE `type` = 1 AND `name` = '%s'", (*it2).Name.c_str()); - auto results = database.QueryDatabase(query); + auto results = qs_database.QueryDatabase(query); if(!results.Success()) it2 = Guilds.erase(it2); diff --git a/queryserv/lfguild.h b/queryserv/lfguild.h index c9c59ded3..1c0cb1cdd 100644 --- a/queryserv/lfguild.h +++ b/queryserv/lfguild.h @@ -44,6 +44,12 @@ public: bool LoadDatabase(); void HandlePacket(ServerPacket *pack); void ExpireEntries(); + + static LFGuildManager* Instance() + { + static LFGuildManager instance; + return &instance; + } private: void SendPlayerMatches(uint32 FromZoneID, uint32 FromInstanceID, char *From, uint32 FromLevel, uint32 ToLevel, uint32 MinAA, uint32 TimeZone, uint32 Classes); void SendGuildMatches(uint32 FromZoneID, uint32 FromInstanceID, char *From, uint32 Level, uint32 AAPoints, uint32 TimeZone, uint32 Class); diff --git a/queryserv/queryserv.cpp b/queryserv/queryserv.cpp index a504b8e77..b6bf21a71 100644 --- a/queryserv/queryserv.cpp +++ b/queryserv/queryserv.cpp @@ -1,27 +1,7 @@ -/* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2008 EQEMu Development Team (http://eqemulator.net) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; version 2 of the License. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY except by those people which sell it, which - are required to give you total support for your newly bought product; - without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -*/ - #include "../common/global_define.h" #include "../common/eqemu_logsys.h" #include "../common/opcodemgr.h" #include "../common/rulesys.h" -#include "../common/servertalk.h" #include "../common/platform.h" #include "../common/crash.h" #include "../common/strings.h" @@ -31,24 +11,24 @@ #include "queryservconfig.h" #include "lfguild.h" #include "worldserver.h" -#include "../common/path_manager.h" #include "../common/zone_store.h" #include "../common/events/player_event_logs.h" #include #include #include +#include "../common/net/servertalk_server.h" +#include "../common/net/console_server.h" +#include "../queryserv/zonelist.h" +#include "../queryserv/zoneserver.h" volatile bool RunLoops = true; -QSDatabase database; -LFGuildManager lfguildmanager; +QSDatabase qs_database; +Database database; std::string WorldShortName; const queryservconfig *Config; WorldServer *worldserver = 0; -EQEmuLogSys LogSys; -PathManager path; -ZoneStore zone_store; -PlayerEventLogs player_event_logs; +uint32 numzones = 0; void CatchSignal(int sig_num) { @@ -58,11 +38,11 @@ void CatchSignal(int sig_num) int main() { RegisterExecutablePlatform(ExePlatformQueryServ); - LogSys.LoadLogSettingsDefaults(); + EQEmuLogSys::Instance()->LoadLogSettingsDefaults(); set_exception_handler(); Timer LFGuildExpireTimer(60000); - path.LoadPaths(); + PathManager::Instance()->Init(); LogInfo("Starting EQEmu QueryServ"); if (!queryservconfig::LoadConfig()) { @@ -76,19 +56,29 @@ int main() LogInfo("Connecting to MySQL"); /* MySQL Connection */ - if (!database.Connect( + if (!qs_database.Connect( Config->QSDatabaseHost.c_str(), Config->QSDatabaseUsername.c_str(), Config->QSDatabasePassword.c_str(), Config->QSDatabaseDB.c_str(), Config->QSDatabasePort + )) { + LogInfo("Cannot continue without a qs database connection"); + return 1; + } + if (!database.Connect( + Config->DatabaseHost.c_str(), + Config->DatabaseUsername.c_str(), + Config->DatabasePassword.c_str(), + Config->DatabaseDB.c_str(), + Config->DatabasePort )) { LogInfo("Cannot continue without a database connection"); return 1; } - LogSys.SetDatabase(&database) - ->SetLogPath(path.GetLogPath()) + EQEmuLogSys::Instance()->SetDatabase(&database) + ->SetLogPath(PathManager::Instance()->GetLogPath()) ->LoadLogDatabaseSettings() ->StartFileLogs(); @@ -101,17 +91,72 @@ int main() return 1; } + //rules: + { + std::string tmp; + if (database.GetVariable("RuleSet", tmp)) { + LogInfo("Loading rule set [{}]", tmp.c_str()); + if (!RuleManager::Instance()->LoadRules(&database, tmp.c_str(), false)) { + LogError("Failed to load ruleset [{}], falling back to defaults", tmp.c_str()); + } + } + else { + if (!RuleManager::Instance()->LoadRules(&database, "default", false)) { + LogInfo("No rule set configured, using default rules"); + } + } + + EQ::InitializeDynamicLookups(); + } + + std::unique_ptr console; + EQ::Net::ServertalkServerOptions server_opts; + auto server_connection = std::make_unique(); + server_opts.port = Config->QSPort; + server_opts.ipv6 = false; + server_opts.credentials = Config->SharedKey; + server_connection->Listen(server_opts); + LogInfo("Server (TCP) listener started on port [{}]", Config->QSPort); + + server_connection->OnConnectionIdentified( + "Zone", [&console](std::shared_ptr connection) { + numzones++; + ZSList::Instance()->Add(new ZoneServer(connection, console.get())); + + LogInfo( + "New Zone Server connection from [{}] at [{}:{}] zone_count [{}]", + connection->Handle()->RemoteIP(), + connection->Handle()->RemotePort(), + connection->GetUUID(), + numzones + ); + } + ); + + server_connection->OnConnectionRemoved( + "Zone", [](std::shared_ptr connection) { + numzones--; + ZSList::Instance()->Remove(connection->GetUUID()); + + LogInfo( + "Removed Zone Server connection from [{}] total zone_count [{}]", + connection->GetUUID(), + numzones + ); + } + ); + /* Initial Connection to Worldserver */ worldserver = new WorldServer; worldserver->Connect(); /* Load Looking For Guild Manager */ - lfguildmanager.LoadDatabase(); + LFGuildManager::Instance()->LoadDatabase(); Timer player_event_process_timer(1000); - player_event_logs.SetDatabase(&database)->Init(); + PlayerEventLogs::Instance()->SetDatabase(&qs_database)->Init(); - auto loop_fn = [&](EQ::Timer* t) { + auto loop_fn = [&](EQ::Timer *t) { Timer::SetCurrentTime(); if (!RunLoops) { @@ -120,11 +165,11 @@ int main() } if (LFGuildExpireTimer.Check()) { - lfguildmanager.ExpireEntries(); + LFGuildManager::Instance()->ExpireEntries(); } if (player_event_process_timer.Check()) { - player_event_logs.Process(); + PlayerEventLogs::Instance()->Process(); } }; @@ -133,7 +178,8 @@ int main() EQ::EventLoop::Get().Run(); - LogSys.CloseFileLogs(); + safe_delete(worldserver); + EQEmuLogSys::Instance()->CloseFileLogs(); } void UpdateWindowTitle(char *iNewTitle) diff --git a/queryserv/worldserver.cpp b/queryserv/worldserver.cpp index f46baf983..051a8f009 100644 --- a/queryserv/worldserver.cpp +++ b/queryserv/worldserver.cpp @@ -1,22 +1,3 @@ -/* EQEMu: Everquest Server Emulator -Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org) - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; version 2 of the License. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY except by those people which sell it, which -are required to give you total support for your newly bought product; -without even the implied warranty of MERCHANTABILITY or FITNESS FOR -A PARTICULAR PURPOSE. See the GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - - #include "../common/global_define.h" #include "../common/eqemu_logsys.h" #include "../common/md5.h" @@ -31,6 +12,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "worldserver.h" #include "../common/events/player_events.h" #include "../common/events/player_event_logs.h" +#include "../common/server_reload_types.h" #include #include #include @@ -39,10 +21,11 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include +#include "zonelist.h" + extern WorldServer worldserver; extern const queryservconfig *Config; -extern QSDatabase database; -extern LFGuildManager lfguildmanager; +extern QSDatabase qs_database; WorldServer::WorldServer() { @@ -91,65 +74,14 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) case 0: { break; } - case ServerOP_PlayerEvent: { - auto n = PlayerEvent::PlayerEventContainer{}; - auto s = (ServerSendPlayerEvent_Struct *) p.Data(); - EQ::Util::MemoryStreamReader ss(s->cereal_data, s->cereal_size); - cereal::BinaryInputArchive archive(ss); - archive(n); + case ServerOP_ServerReloadRequest: { + auto o = (ServerReload::Request*) p.Data(); + if (o->type == ServerReload::Type::Logs) { + EQEmuLogSys::Instance()->LoadLogDatabaseSettings(); + PlayerEventLogs::Instance()->ReloadSettings(); + ZSList::Instance()->SendPlayerEventLogSettings(); + } - player_event_logs.AddToQueue(n.player_event_log); - - break; - } - case ServerOP_KeepAlive: { - break; - } - case ServerOP_Speech: { - Server_Speech_Struct *SSS = (Server_Speech_Struct *) p.Data(); - std::string tmp1 = SSS->from; - std::string tmp2 = SSS->to; - database.AddSpeech(tmp1.c_str(), tmp2.c_str(), SSS->message, SSS->minstatus, SSS->guilddbid, SSS->type); - break; - } - case ServerOP_QSPlayerLogTrades: { - PlayerLogTrade_Struct *QS = (PlayerLogTrade_Struct *) p.Data(); - database.LogPlayerTrade(QS, QS->_detail_count); - break; - } - case ServerOP_QSPlayerDropItem: { - QSPlayerDropItem_Struct *QS = (QSPlayerDropItem_Struct *) p.Data(); - database.LogPlayerDropItem(QS); - break; - } - case ServerOP_QSPlayerLogHandins: { - QSPlayerLogHandin_Struct *QS = (QSPlayerLogHandin_Struct *) p.Data(); - database.LogPlayerHandin(QS, QS->_detail_count); - break; - } - case ServerOP_QSPlayerLogNPCKills: { - QSPlayerLogNPCKill_Struct *QS = (QSPlayerLogNPCKill_Struct *) p.Data(); - uint32 Members = (uint32) (p.Length() - sizeof(QSPlayerLogNPCKill_Struct)); - if (Members > 0) { Members = Members / sizeof(QSPlayerLogNPCKillsPlayers_Struct); } - database.LogPlayerNPCKill(QS, Members); - break; - } - case ServerOP_QSPlayerLogDeletes: { - QSPlayerLogDelete_Struct *QS = (QSPlayerLogDelete_Struct *) p.Data(); - uint32 Items = QS->char_count; - database.LogPlayerDelete(QS, Items); - break; - } - case ServerOP_QSPlayerLogMoves: { - QSPlayerLogMove_Struct *QS = (QSPlayerLogMove_Struct *) p.Data(); - uint32 Items = QS->char_count; - database.LogPlayerMove(QS, Items); - break; - } - case ServerOP_QSPlayerLogMerchantTransactions: { - QSMerchantLogTransaction_Struct *QS = (QSMerchantLogTransaction_Struct *) p.Data(); - uint32 Items = QS->char_count + QS->merchant_count; - database.LogMerchantTransaction(QS, Items); break; } case ServerOP_QueryServGeneric: { @@ -182,7 +114,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) pack.pBuffer = (uchar *) p.Data(); pack.opcode = opcode; pack.size = (uint32) p.Length(); - lfguildmanager.HandlePacket(&pack); + LFGuildManager::Instance()->HandlePacket(&pack); pack.pBuffer = nullptr; break; } @@ -199,9 +131,12 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) pack.opcode = opcode; pack.size = (uint32) p.Length(); - database.GeneralQueryReceive(&pack); + qs_database.GeneralQueryReceive(&pack); pack.pBuffer = nullptr; break; } + default: + LogInfo("Unhandled opcode: {}", opcode); + break; } } diff --git a/queryserv/zonelist.cpp b/queryserv/zonelist.cpp new file mode 100644 index 000000000..16ad65371 --- /dev/null +++ b/queryserv/zonelist.cpp @@ -0,0 +1,29 @@ +#include "zonelist.h" +#include "zoneserver.h" +#include "../common/strings.h" + +void ZSList::Add(ZoneServer* zoneserver) { + zone_server_list.emplace_back(std::unique_ptr(zoneserver)); + zoneserver->SetIsZoneConnected(true); + + zoneserver->SendPlayerEventLogSettings(); +} + +void ZSList::Remove(const std::string &uuid) +{ + auto iter = zone_server_list.begin(); + while (iter != zone_server_list.end()) { + if ((*iter)->GetUUID().compare(uuid) == 0) { + zone_server_list.erase(iter); + return; + } + iter++; + } +} + +void ZSList::SendPlayerEventLogSettings() +{ + for (auto &zs : zone_server_list) { + zs->SendPlayerEventLogSettings(); + } +} diff --git a/queryserv/zonelist.h b/queryserv/zonelist.h new file mode 100644 index 000000000..5bcf1b29b --- /dev/null +++ b/queryserv/zonelist.h @@ -0,0 +1,30 @@ +#ifndef ZONELIST_H_ +#define ZONELIST_H_ + +#include "../common/types.h" +#include +#include +#include +#include +#include "zoneserver.h" + +class WorldTCPConnection; + +class ZSList { +public: + std::list>& GetZsList() { return zone_server_list; } + void Add(ZoneServer* zoneserver); + void Remove(const std::string& uuid); + void SendPlayerEventLogSettings(); + + static ZSList* Instance() + { + static ZSList instance; + return &instance; + } + +private: + std::list> zone_server_list; +}; + +#endif /*ZONELIST_H_*/ diff --git a/queryserv/zoneserver.cpp b/queryserv/zoneserver.cpp new file mode 100644 index 000000000..2aa9b9b9d --- /dev/null +++ b/queryserv/zoneserver.cpp @@ -0,0 +1,70 @@ +#include "zoneserver.h" +#include "../common/global_define.h" +#include "../common/eqemu_logsys.h" +#include "../common/repositories/player_event_logs_repository.h" +#include "../common/events/player_event_logs.h" +#include "../common/discord/discord_manager.h" + +ZoneServer::ZoneServer( + std::shared_ptr in_connection, + EQ::Net::ConsoleServer *in_console +) + : m_connection(in_connection) +{ + + m_connection->OnMessage(std::bind(&ZoneServer::HandleMessage, this, std::placeholders::_1, std::placeholders::_2)); + m_console = in_console; +} + +ZoneServer::~ZoneServer() +{ +} + +void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) +{ + ServerPacket tpack(opcode, p); + auto pack = &tpack; + + switch (opcode) { + case ServerOP_PlayerEvent: { + auto n = PlayerEvent::PlayerEventContainer{}; + auto s = reinterpret_cast(pack->pBuffer); + EQ::Util::MemoryStreamReader ss(s->cereal_data, s->cereal_size); + cereal::BinaryInputArchive archive(ss); + archive(n); + + PlayerEventLogs::Instance()->AddToQueue(n.player_event_log); + + DiscordManager::Instance()->QueuePlayerEventMessage(n); + break; + } + default: { + LogInfo("Unknown ServerOP Received [{}]", opcode); + break; + } + } +} + +void ZoneServer::SendPlayerEventLogSettings() +{ + EQ::Net::DynamicPacket dyn_pack; + std::vector settings( + PlayerEventLogs::Instance()->GetSettings(), + PlayerEventLogs::Instance()->GetSettings() + PlayerEvent::EventType::MAX + ); + + dyn_pack.PutSerialize(0, settings); + + auto packet_size = sizeof(ServerSendPlayerEvent_Struct) + dyn_pack.Length(); + + auto pack = std::make_unique( + ServerOP_SendPlayerEventSettings, + static_cast(packet_size) + ); + + auto* buf = reinterpret_cast(pack->pBuffer); + buf->cereal_size = static_cast(dyn_pack.Length()); + memcpy(buf->cereal_data, dyn_pack.Data(), dyn_pack.Length()); + + SendPacket(pack.release()); +} diff --git a/queryserv/zoneserver.h b/queryserv/zoneserver.h new file mode 100644 index 000000000..f4a4b08e6 --- /dev/null +++ b/queryserv/zoneserver.h @@ -0,0 +1,33 @@ +#ifndef ZONESERVER_H +#define ZONESERVER_H + +#include "../world/world_tcp_connection.h" +#include "../common/net/servertalk_server.h" +#include "../common/event/timer.h" +#include "../common/timer.h" +#include "../common/emu_constants.h" +#include "../world/console.h" +#include + +class Client; +class ServerPacket; + +class ZoneServer : public WorldTCPConnection { +public: + ZoneServer(std::shared_ptr in_connection, EQ::Net::ConsoleServer *in_console); + ~ZoneServer(); + void SendPacket(ServerPacket *pack) { m_connection->SendPacket(pack); } + void SetIsZoneConnected(bool in) { m_is_zone_connected = in; } + bool GetIsZoneConnected() { return m_is_zone_connected; } + void HandleMessage(uint16 opcode, const EQ::Net::Packet &p); + std::string GetUUID() const { return m_connection->GetUUID(); } + void SendPlayerEventLogSettings(); + +private: + std::shared_ptr m_connection{}; + bool m_is_zone_connected = false; + EQ::Net::ConsoleServer *m_console; +}; + +#endif + diff --git a/shared_memory/main.cpp b/shared_memory/main.cpp index c598b39f5..d29bd31e4 100644 --- a/shared_memory/main.cpp +++ b/shared_memory/main.cpp @@ -33,12 +33,7 @@ #include "../common/zone_store.h" #include "../common/path_manager.h" #include "../common/events/player_event_logs.h" - -EQEmuLogSys LogSys; -WorldContentService content_service; -ZoneStore zone_store; -PathManager path; -PlayerEventLogs player_event_logs; +#include "../common/evolving_items.h" #ifdef _WINDOWS #include @@ -79,10 +74,10 @@ inline bool MakeDirectory(const std::string &directory_name) int main(int argc, char **argv) { RegisterExecutablePlatform(ExePlatformSharedMemory); - LogSys.LoadLogSettingsDefaults(); + EQEmuLogSys::Instance()->LoadLogSettingsDefaults(); set_exception_handler(); - path.LoadPaths(); + PathManager::Instance()->Init(); LogInfo("Shared Memory Loader Program"); if (!EQEmuConfig::LoadConfig()) { @@ -124,8 +119,8 @@ int main(int argc, char **argv) content_db.SetMySQL(database); } - LogSys.SetDatabase(&database) - ->SetLogPath(path.GetLogPath()) + EQEmuLogSys::Instance()->SetDatabase(&database) + ->SetLogPath(PathManager::Instance()->GetLogPath()) ->LoadLogDatabaseSettings() ->StartFileLogs(); @@ -167,16 +162,16 @@ int main(int argc, char **argv) } - content_service.SetCurrentExpansion(RuleI(Expansion, CurrentExpansion)); - content_service.SetDatabase(&database) + WorldContentService::Instance()->SetCurrentExpansion(RuleI(Expansion, CurrentExpansion)); + WorldContentService::Instance()->SetDatabase(&database) ->SetContentDatabase(&content_db) ->SetExpansionContext() ->ReloadContentFlags(); LogInfo( "Current expansion is [{}] ({})", - content_service.GetCurrentExpansion(), - content_service.GetCurrentExpansionName() + WorldContentService::Instance()->GetCurrentExpansion(), + WorldContentService::Instance()->GetCurrentExpansionName() ); std::string hotfix_name = ""; @@ -242,6 +237,6 @@ int main(int argc, char **argv) } } - LogSys.CloseFileLogs(); + EQEmuLogSys::Instance()->CloseFileLogs(); return 0; } diff --git a/submodules/libuv b/submodules/libuv index 0c1fa696a..8fb9cb919 160000 --- a/submodules/libuv +++ b/submodules/libuv @@ -1 +1 @@ -Subproject commit 0c1fa696aa502eb749c2c4735005f41ba00a27b8 +Subproject commit 8fb9cb919489a48880680a56efecff6a7dfb4504 diff --git a/tests/main.cpp b/tests/main.cpp index 50ddf890b..9c9af40c9 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -34,14 +34,12 @@ #include "task_state_test.h" const EQEmuConfig *Config; -EQEmuLogSys LogSys; -PathManager path; int main() { RegisterExecutablePlatform(ExePlatformClientImport); - LogSys.LoadLogSettingsDefaults(); - path.LoadPaths(); + EQEmuLogSys::Instance()->LoadLogSettingsDefaults(); + PathManager::Instance()->Init(); auto ConfigLoadResult = EQEmuConfig::LoadConfig(); Config = EQEmuConfig::get(); diff --git a/tests/string_util_test.h b/tests/string_util_test.h index af88a5a03..37bd539f7 100644 --- a/tests/string_util_test.h +++ b/tests/string_util_test.h @@ -30,7 +30,6 @@ public: TEST_ADD(StringUtilTest::EscapeStringTest); TEST_ADD(StringUtilTest::SearchDeliminatedStringTest); TEST_ADD(StringUtilTest::SplitStringTest); - TEST_ADD(StringUtilTest::FromCharsTest); TEST_ADD(StringUtilTest::TestIsFloat); TEST_ADD(StringUtilTest::TestIsNumber); } @@ -108,21 +107,6 @@ public: TEST_ASSERT(v[2] == "789"); } - void FromCharsTest() { - char int_chars[] = "123"; - int int_value = 0; - - char float_chars[] = "3.14"; - float float_value = 0.0f; - - Strings::from_chars(int_chars, int_value); - TEST_ASSERT(int_value == 123); - - Strings::from_chars(float_chars, float_value); - TEST_ASSERT(float_value == 3.14f); - - } - void TestIsFloat() { TEST_ASSERT_EQUALS(Strings::IsFloat("0.23424523"), true); TEST_ASSERT_EQUALS(Strings::IsFloat("12312312313.23424523"), true); diff --git a/ucs/chatchannel.cpp b/ucs/chatchannel.cpp index a8a7cc797..1e0e0598f 100644 --- a/ucs/chatchannel.cpp +++ b/ucs/chatchannel.cpp @@ -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())); diff --git a/ucs/clientlist.cpp b/ucs/clientlist.cpp index cbb6e0910..749b9276f 100644 --- a/ucs/clientlist.cpp +++ b/ucs/clientlist.cpp @@ -469,11 +469,11 @@ static void ProcessCommandIgnore(Client *c, std::string Ignoree) { Clientlist::Clientlist(int ChatPort) { EQStreamManagerInterfaceOptions chat_opts(ChatPort, false, false); chat_opts.opcode_size = 1; - chat_opts.daybreak_options.stale_connection_ms = 600000; - chat_opts.daybreak_options.resend_delay_ms = RuleI(Network, ResendDelayBaseMS); - chat_opts.daybreak_options.resend_delay_factor = RuleR(Network, ResendDelayFactor); - chat_opts.daybreak_options.resend_delay_min = RuleI(Network, ResendDelayMinMS); - chat_opts.daybreak_options.resend_delay_max = RuleI(Network, ResendDelayMaxMS); + chat_opts.reliable_stream_options.stale_connection_ms = 600000; + chat_opts.reliable_stream_options.resend_delay_ms = RuleI(Network, ResendDelayBaseMS); + chat_opts.reliable_stream_options.resend_delay_factor = RuleR(Network, ResendDelayFactor); + chat_opts.reliable_stream_options.resend_delay_min = RuleI(Network, ResendDelayMinMS); + chat_opts.reliable_stream_options.resend_delay_max = RuleI(Network, ResendDelayMaxMS); chatsf = new EQ::Net::EQStreamManager(chat_opts); @@ -482,7 +482,7 @@ Clientlist::Clientlist(int ChatPort) { const ucsconfig *Config = ucsconfig::get(); - std::string opcodes_file = fmt::format("{}/{}", path.GetServerPath(), Config->MailOpCodesFile); + std::string opcodes_file = fmt::format("{}/{}", PathManager::Instance()->GetServerPath(), Config->MailOpCodesFile); LogInfo("Loading [{}]", opcodes_file); if (!ChatOpMgr->LoadOpcodes(opcodes_file.c_str())) @@ -651,7 +651,7 @@ void Clientlist::Process() OpcodeManager::EmuToName(app->GetOpcode()), o->EmuToEQ(app->GetOpcode()) == 0 ? app->GetProtocolOpcode() : o->EmuToEQ(app->GetOpcode()), app->Size(), - (LogSys.IsLogEnabled(Logs::Detail, Logs::PacketClientServer) ? DumpPacketToString(app) : "") + (EQEmuLogSys::Instance()->IsLogEnabled(Logs::Detail, Logs::PacketClientServer) ? DumpPacketToString(app) : "") ); switch (opcode) { diff --git a/ucs/ucs.cpp b/ucs/ucs.cpp index c89c3f4fd..f74bb79c1 100644 --- a/ucs/ucs.cpp +++ b/ucs/ucs.cpp @@ -44,13 +44,8 @@ ChatChannelList *ChannelList; Clientlist *g_Clientlist; -EQEmuLogSys LogSys; UCSDatabase database; WorldServer *worldserver = nullptr; -DiscordManager discord_manager; -PathManager path; -ZoneStore zone_store; -PlayerEventLogs player_event_logs; const ucsconfig *Config; @@ -75,7 +70,7 @@ void Shutdown() { LogInfo("Shutting down..."); ChannelList->RemoveAllChannels(); g_Clientlist->CloseAllConnections(); - LogSys.CloseFileLogs(); + EQEmuLogSys::Instance()->CloseFileLogs(); } int caught_loop = 0; @@ -90,24 +85,24 @@ void CatchSignal(int sig_num) { LogInfo("In a signal handler loop and process is incapable of exiting properly, forcefully cleaning up"); ChannelList->RemoveAllChannels(); g_Clientlist->CloseAllConnections(); - LogSys.CloseFileLogs(); + EQEmuLogSys::Instance()->CloseFileLogs(); std::exit(0); } } void PlayerEventQueueListener() { while (caught_loop == 0) { - discord_manager.ProcessMessageQueue(); + DiscordManager::Instance()->ProcessMessageQueue(); Sleep(100); } } int main() { RegisterExecutablePlatform(ExePlatformUCS); - LogSys.LoadLogSettingsDefaults(); + EQEmuLogSys::Instance()->LoadLogSettingsDefaults(); set_exception_handler(); - path.LoadPaths(); + PathManager::Instance()->Init(); // Check every minute for unused channels we can delete // @@ -139,12 +134,13 @@ int main() { return 1; } - LogSys.SetDatabase(&database) - ->SetLogPath(path.GetLogPath()) + EQEmuLogSys::Instance() + ->SetDatabase(&database) + ->SetLogPath(PathManager::Instance()->GetLogPath()) ->LoadLogDatabaseSettings() ->StartFileLogs(); - player_event_logs.SetDatabase(&database)->Init(); + PlayerEventLogs::Instance()->SetDatabase(&database)->Init(); char tmp[64]; diff --git a/ucs/worldserver.cpp b/ucs/worldserver.cpp index 91439dbf5..48e85f181 100644 --- a/ucs/worldserver.cpp +++ b/ucs/worldserver.cpp @@ -28,6 +28,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "database.h" #include "../common/discord/discord_manager.h" #include "../common/events/player_event_logs.h" +#include "../common/server_reload_types.h" #include #include @@ -41,7 +42,6 @@ extern WorldServer worldserver; extern Clientlist *g_Clientlist; extern const ucsconfig *Config; extern UCSDatabase database; -extern DiscordManager discord_manager; void ProcessMailTo(Client *c, const std::string& from, const std::string& subject, const std::string& message); @@ -75,9 +75,13 @@ void WorldServer::ProcessMessage(uint16 opcode, EQ::Net::Packet &p) { break; } - case ServerOP_ReloadLogs: { - LogSys.LoadLogDatabaseSettings(); - player_event_logs.ReloadSettings(); + case ServerOP_ServerReloadRequest: { + auto o = (ServerReload::Request*) pack->pBuffer; + if (o->type == ServerReload::Type::Logs) { + EQEmuLogSys::Instance()->LoadLogDatabaseSettings(); + PlayerEventLogs::Instance()->ReloadSettings(); + } + break; } case ServerOP_PlayerEvent: { @@ -87,14 +91,14 @@ void WorldServer::ProcessMessage(uint16 opcode, EQ::Net::Packet &p) cereal::BinaryInputArchive archive(ss); archive(n); - discord_manager.QueuePlayerEventMessage(n); + DiscordManager::Instance()->QueuePlayerEventMessage(n); break; } case ServerOP_DiscordWebhookMessage: { auto *q = (DiscordWebhookMessage_Struct *) p.Data(); - discord_manager.QueueWebhookMessage( + DiscordManager::Instance()->QueueWebhookMessage( q->webhook_id, q->message ); diff --git a/utils/patches/patch_RoF2.conf b/utils/patches/patch_RoF2.conf index 0995b83ba..3533aaa87 100644 --- a/utils/patches/patch_RoF2.conf +++ b/utils/patches/patch_RoF2.conf @@ -261,6 +261,7 @@ OP_ItemPacket=0x368e OP_ItemLinkResponse=0x70c0 OP_ItemLinkClick=0x4cef OP_ItemPreview=0x6b5c +OP_ItemPreviewRequest=0x7f80 OP_NewSpawn=0x6097 OP_Track=0x17e5 OP_TrackTarget=0x695e @@ -566,7 +567,7 @@ OP_RaidUpdate=0x3973 OP_RaidJoin=0x0000 OP_RaidDelegateAbility=0x2b33 OP_MarkRaidNPC=0x5a58 -OP_RaidClearNPCMarks=0x20d3 +OP_RaidClearNPCMarks=0x20d3 # Button-push commands OP_Taunt=0x2703 @@ -728,3 +729,23 @@ OP_InitialHPUpdate=0x0000 #aura related OP_UpdateAura=0x1456 OP_RemoveTrap=0x71da + +OP_PickZoneWindow=0x72d8 +OP_PickZone=0xaaba + +#evolve item related +OP_EvolveItem=0x7cfb + +OP_TradeSkillRecipeInspect=0x4f7e + +# This is bugged in ROF2 +# OP_InvokeChangePetNameImmediate is supposed to write DisablePetNameChangeReminder=0 to reset the 'nag reminder' +# It actually sets DisableNameChangeReminder=0 (player name change nag reminder). Additionally, clicking the +# 'Don't remind me later' button sets DisableNameChangeReminder=1 +# This can be fixed with a client patch. +OP_InvokeChangePetNameImmediate=0x046d +OP_InvokeChangePetName=0x4506 +OP_ChangePetName=0x5dab + +OP_InvokeNameChangeImmediate=0x4fe2 +OP_InvokeNameChangeLazy=0x2f2e diff --git a/utils/scripts/build/linux-build.sh b/utils/scripts/build/linux-build.sh index 6bb2ae8bb..767d06046 100755 --- a/utils/scripts/build/linux-build.sh +++ b/utils/scripts/build/linux-build.sh @@ -5,27 +5,65 @@ set -ex sudo chown eqemu:eqemu /drone/src/ * -R sudo chown eqemu:eqemu /home/eqemu/.ccache/ * -R +chmod +x ./utils/scripts/build/source-db-build.sh +utils/scripts/build/source-db-build.sh & + git submodule init && git submodule update perl utils/scripts/build/tag-version.pl mkdir -p build && cd build && \ cmake -DEQEMU_BUILD_TESTS=ON \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DEQEMU_BUILD_STATIC=ON \ -DEQEMU_BUILD_LOGIN=ON \ -DEQEMU_BUILD_LUA=ON \ -DEQEMU_BUILD_PERL=ON \ - -DCMAKE_CXX_FLAGS:STRING="-O1 -g -Wno-everything" \ - -DCMAKE_CXX_FLAGS_RELWITHDEBINFO:STRING="-O1 -g -Wno-everything" \ + -DCMAKE_CXX_FLAGS_RELWITHDEBINFO:STRING="-g -Wno-everything" \ -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ -G 'Unix Makefiles' \ - .. && make -j$((`nproc`-4)) + .. && make -j$((`nproc`-2)) curl https://raw.githubusercontent.com/Akkadius/eqemu-install-v2/master/eqemu_config.json --output eqemu_config.json ./bin/tests ldd ./bin/zone +echo "Waiting for MariaDB to be ready..." +while ! mysqladmin ping -uroot -peqemu -hlocalhost --silent; do + sleep 1 +done + +echo "# Cloning quests repository" +git -C ./quests pull 2> /dev/null || git clone https://github.com/ProjectEQ/projecteqquests.git quests + +mkdir maps +mkdir logs + +ln -s ./quests/lua_modules ./lua_modules +ln -s ./quests/plugins ./plugins + +while [ ! -f /tmp/import-done ]; do + sleep 1 # Wait for 1 second before checking again +done + +echo "# Running world database updates" +FORCE_INTERACTIVE=1 ./bin/world database:updates --skip-backup --force + +echo "# Running shared_memory" +./bin/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|FAILED" test_output.log; then + echo "Error found in test output! Failing build." + exit 1 +fi + # shellcheck disable=SC2164 cd /drone/src/ diff --git a/utils/scripts/build/should-release/go.mod b/utils/scripts/build/should-release/go.mod index d963fcd5f..df899a9e1 100644 --- a/utils/scripts/build/should-release/go.mod +++ b/utils/scripts/build/should-release/go.mod @@ -1,16 +1,15 @@ module should-release -go 1.18 +go 1.23.0 + +toolchain go1.23.5 require ( github.com/google/go-github/v41 v41.0.0 - golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be + golang.org/x/oauth2 v0.27.0 ) require ( - github.com/golang/protobuf v1.3.2 // indirect github.com/google/go-querystring v1.1.0 // indirect - golang.org/x/crypto v0.21.0 // indirect - golang.org/x/net v0.23.0 // indirect - google.golang.org/appengine v1.6.7 // indirect + golang.org/x/crypto v0.36.0 // indirect ) diff --git a/utils/scripts/build/should-release/go.sum b/utils/scripts/build/should-release/go.sum index cdad01fc4..d821c4071 100644 --- a/utils/scripts/build/should-release/go.sum +++ b/utils/scripts/build/should-release/go.sum @@ -1,23 +1,22 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github/v41 v41.0.0 h1:HseJrM2JFf2vfiZJ8anY2hqBjdfY1Vlj/K27ueww4gg= github.com/google/go-github/v41 v41.0.0/go.mod h1:XgmCA5H323A9rtgExdTcnDkcqp6S30AVACCBDOonIxg= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= 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.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -27,5 +26,4 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= diff --git a/utils/scripts/build/source-db-build.sh b/utils/scripts/build/source-db-build.sh new file mode 100644 index 000000000..69d2b2856 --- /dev/null +++ b/utils/scripts/build/source-db-build.sh @@ -0,0 +1,92 @@ +# Variables +ROOT_PASSWORD="eqemu" +MARIADB_CONFIG="/etc/mysql/mariadb.conf.d/50-server.cnf" + +# Update and install MariaDB +echo "Installing MariaDB..." +sudo apt update +sudo apt install -y mariadb-server mariadb-client + +# Ensure MariaDB is stopped before configuration +echo "Stopping MariaDB service..." +sudo systemctl stop mariadb + +# Initialize the data directory (in case it's not already initialized) +echo "Initializing MariaDB data directory..." +sudo mysqld --initialize --user=mysql --datadir=/var/lib/mysql + +# Start MariaDB in safe mode +echo "Starting MariaDB in safe mode..." +sudo mysqld_safe --skip-grant-tables --skip-networking & +sleep 5 + +# Reset root password and configure authentication +echo "Resetting root password and configuring authentication..." +mariadb < 0) { foreach my $cat (@cats) { print "#define Log" . $cat . "(message, ...) do {\\ - if (LogSys.IsLogEnabled(Logs::General, Logs::" . $cat . "))\\ + if (EQEmuLogSys::Instance()->IsLogEnabled(Logs::General, Logs::" . $cat . "))\\ OutF(LogSys, Logs::General, Logs::" . $cat . ", __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\\ } while (0) #define Log" . $cat . "Detail(message, ...) do {\\ - if (LogSys.IsLogEnabled(Logs::Detail, Logs::" . $cat . "))\\ + if (EQEmuLogSys::Instance()->IsLogEnabled(Logs::Detail, Logs::" . $cat . "))\\ OutF(LogSys, Logs::Detail, Logs::" . $cat . ", __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\\ } while (0) diff --git a/utils/scripts/generators/repository-generator.pl b/utils/scripts/generators/repository-generator.pl index 492bf3c96..3def50506 100644 --- a/utils/scripts/generators/repository-generator.pl +++ b/utils/scripts/generators/repository-generator.pl @@ -114,7 +114,8 @@ if ($requested_table_to_generate ne "all") { my @cereal_enabled_tables = ( "data_buckets", - "player_event_logs" + "player_event_logs", + "player_event_log_settings" ); my $generated_base_repository_files = ""; @@ -142,7 +143,6 @@ foreach my $table_to_generate (@tables) { "guild_bank", "inventory_versions", "raid_leaders", - "sharedbank", "trader_audit", "eqtime", "db_version", @@ -336,6 +336,10 @@ foreach my $table_to_generate (@tables) { $all_entries .= sprintf("\t\t\te.%-${longest_column_length}s = row[%s] ? static_cast<%s>(strtoul(row[%s], nullptr, 10)) : %s;\n", $column_name_formatted, $index, $struct_data_type, $index, $default_value); $find_one_entries .= sprintf("\t\t\te.%-${longest_column_length}s = row[%s] ? static_cast<%s>(strtoul(row[%s], nullptr, 10)) : %s;\n", $column_name_formatted, $index, $struct_data_type, $index, $default_value); } + elsif ($data_type =~ /float|decimal/) { + $all_entries .= sprintf("\t\t\te.%-${longest_column_length}s = row[%s] ? (strtof(row[%s], nullptr) > 0.0f ? strtof(row[%s], nullptr) : %s) : %s;\n", $column_name_formatted, $index, $index, $index, $default_value, $default_value); + $find_one_entries .= sprintf("\t\t\te.%-${longest_column_length}s = row[%s] ? (strtof(row[%s], nullptr) > 0.0f ? strtof(row[%s], nullptr) : %s) : %s;\n", $column_name_formatted, $index, $index, $index, $default_value, $default_value); + } } elsif ($data_type =~ /bigint/) { $all_entries .= sprintf("\t\t\te.%-${longest_column_length}s = row[%s] ? strtoll(row[%s], nullptr, 10) : %s;\n", $column_name_formatted, $index, $index, $default_value); diff --git a/utils/scripts/opcode_scripts/opcodelist.txt b/utils/scripts/opcode_scripts/opcodelist.txt index 8a4ebf87d..6e324c40d 100644 --- a/utils/scripts/opcode_scripts/opcodelist.txt +++ b/utils/scripts/opcode_scripts/opcodelist.txt @@ -1,317 +1,317 @@ -RoF2 Built May 10 2013 23:30:08 -0x04d6 -0x5efd -0x0cc1 -0x2c3d -0x7b25 -0x4f44 -0x7592 -0x2b1c +RoF2 Built May 10 2013 23:30:08 +0x04d6 +0x5efd +0x0cc1 +0x2c3d +0x7b25 +0x4f44 +0x7592 +0x2b1c 0x1fd1 OP_Consent -0x15a5 -0x3c7e -0x6262 +0x15a5 +0x3c7e +0x6262 0x471d OP_AckPacket -0x64a8 -0x7c06 -0x2c5e -0x24ab -0x19b5 -0x72fa -0x0770 +0x64a8 +0x7c06 +0x2c5e +0x24ab +0x19b5 +0x72fa +0x0770 0x4cef OP_ItemLinkClick 0x08c1 OP_GMServers 0x318f OP_BeginCast 0x5ec8 OP_Disarm -0x6ae3 +0x6ae3 0x2b2d OP_ChannelMessage -0x1cb1 +0x1cb1 0x5070 OP_TimeOfDay 0x7dfc OP_ClientUpdate -0x35ea -0x3b9a -0x148d +0x35ea +0x3b9a +0x148d 0x345d OP_ClientReady 0x5ae2 OP_WorldObjectsSent -0x03a4 +0x03a4 0x69e2 OP_TradeAcceptClick 0x354c OP_CancelTrade 0x3993 OP_FinishTrade -0x165b -0x2b95 -0x6fe2 -0x42fd -0x25d7 -0x7436 +0x165b +0x2b95 +0x6fe2 +0x42fd +0x25d7 +0x7436 0x4206 OP_TradeCoins 0x14bf OP_TradeRequestAck 0x7349 OP_FinishWindow -0x71fd +0x71fd 0x0b9c OP_SetGuildRank -0x0f7d -0x1bd3 +0x0f7d +0x1bd3 0x5053 OP_GuildPublicNote -0x01f9 +0x01f9 0x2c84 OP_MobUpdate -0x279d -0x5a94 +0x279d +0x5a94 0x579a OP_TradeSkillCombine -0x56b1 -0x0f25 -0x3c14 -0x0c16 +0x56b1 +0x0f25 +0x3c14 +0x0c16 0x4a8f OP_GMFind -0x2ad6 -0x0160 -0x7852 -0x41bf -0x40dd -0x3ec6 -0x6af4 -0x6d9d -0x2538 -0x0017 -0x017 -0x691a -0x0520 -0x200f -0x65ab +0x2ad6 +0x0160 +0x7852 +0x41bf +0x40dd +0x3ec6 +0x6af4 +0x6d9d +0x2538 +0x0017 +0x017 +0x691a +0x0520 +0x200f +0x65ab 0x674b OP_WhoAllRequest -0x6a02 -0x0e48 +0x6a02 +0x0e48 0x12a6 OP_GuildMemberList -0x0eae -0x2921 -0x7056 -0x507a +0x0eae +0x2921 +0x7056 +0x507a 0x6279 OP_GuildList -0x5a26 -0x5b51 -0x0bad -0x59ad -0x2264 -0x004c +0x5a26 +0x5b51 +0x0bad +0x59ad +0x2264 +0x004c 0x04c OP_SkillUpdate 0x0ddd OP_ShopPlayerBuy -0x4101 -0x5f03 -0x3de3 +0x4101 +0x5f03 +0x3de3 0x1901 OP_Petition -0x791b -0x3a5d -0x7013 -0x47f1 +0x791b +0x3a5d +0x7013 +0x47f1 0x6506 OP_PlayerProfile 0x30a8 OP_ShopEnd 0x724f OP_ShopDelItem -0x5829 -0x1ce1 -0x47d6 -0x07cf -0x40c9 -0x38d4 -0x7eb9 -0x1e2c +0x5829 +0x1ce1 +0x47d6 +0x07cf +0x40c9 +0x38d4 +0x7eb9 +0x1e2c 0x3ae1 OP_GMBecomeNPC -0x4849 -0x376b +0x4849 +0x376b 0x1e2a OP_Fishing 0x1eec OP_LevelUpdate -0x4958 +0x4958 0x20ed OP_ExpUpdate -0x3ed4 +0x3ed4 0x701c OP_RezzAnswer 0x640e OP_InstillDoubt 0x0ecf OP_Mend -0x09d3 +0x09d3 0x70a3 OP_IncreaseStats -0x178e -0x27e8 -0x61f8 -0x4322 -0x3f84 +0x178e +0x27e8 +0x61f8 +0x4322 +0x3f84 0x40ef OP_FinishWindow2 -0x166c +0x166c 0x106b OP_RandomReply -0x5bfd -0x11c8 -0x1753 +0x5bfd +0x11c8 +0x1753 0x009f OP_SetRunMode -0x09f -0x4ae6 -0x3dbf -0x0399 -0x5c89 -0x012c -0x381c -0x4b36 -0x36d5 -0x5f21 -0x56f7 -0x46b1 +0x09f +0x4ae6 +0x3dbf +0x0399 +0x5c89 +0x012c +0x381c +0x4b36 +0x36d5 +0x5f21 +0x56f7 +0x46b1 0x357c OP_GMSearchCorpse -0x2422 -0x084e +0x2422 +0x084e 0x67e3 OP_GuildPeace -0x0f83 +0x0f83 0x4577 OP_TrackUnknown 0x5d55 OP_Sneak 0x67fe OP_Hide -0x12e5 -0x2c7a +0x12e5 +0x2c7a 0x600d OP_SaveOnZoneReq -0x16ce +0x16ce 0x46ce OP_GMLastName 0x1ffb OP_GuildWar -0x6718 -0x2f3e +0x6718 +0x2f3e 0x7e09 OP_GuildLeader -0x5ce4 -0x5bcc -0x551d -0x6dbc +0x5ce4 +0x5bcc +0x551d +0x6dbc 0x2219 OP_SafeFallSuccess -0x2686 -0x2022 -0x1c53 -0x33a2 -0x3541 -0x582e -0x3cba -0x190c -0x5067 -0x46d2 -0x49cf -0x1b0c - +0x2686 +0x2022 +0x1c53 +0x33a2 +0x3541 +0x582e +0x3cba +0x190c +0x5067 +0x46d2 +0x49cf +0x1b0c + 0x31e6 OP_ApplyPoison 0x4211 OP_BoardBoat 0x7617 OP_LeaveBoat -0x4466 +0x4466 0x1287 OP_CastSpell 0x5467 OP_ManaChange 0x43af OP_ColoredText -0x6a0d +0x6a0d 0x217c OP_MemorizeSpell 0x260a OP_SenseHeading -0x5886 -0x7e8c -0x1a37 -0x0b6f -0x62ca -0x34a4 -0x6c83 -0x5d10 -0x21a0 -0x3537 -0x2acd -0x7c6d -0x6618 -0x6c65 - - +0x5886 +0x7e8c +0x1a37 +0x0b6f +0x62ca +0x34a4 +0x6c83 +0x5d10 +0x21a0 +0x3537 +0x2acd +0x7c6d +0x6618 +0x6c65 + + 0x32c2 OP_GroupInvite2 0x2a50 OP_CancelInvite 0x2060 OP_GroupFollow2 0x31f4 OP_Jump -0x4420 -0x1256 -0x4d38 -0x50f5 - +0x4420 +0x1256 +0x4d38 +0x50f5 + 0x79c5 OP_GMHideMe -0x2fab -0x57a5 -0x32a4 +0x2fab +0x57a5 +0x32a4 0x590d OP_ExpansionInfo 0x6f15 OP_Damage -0x42ea -0x3091 -0x6b5a -0x3234 -0x30b4 +0x42ea +0x3091 +0x6b5a +0x3234 +0x30b4 0x51fd OP_EnvDamage -0x15f4 +0x15f4 0x1808 OP_DeleteCharacter 0x1795 OP_NewZone 0x7887 OP_ReqNewZone -0x2414 -0x2eb3 -0x3db3 -0x4008 - +0x2414 +0x2eb3 +0x3db3 +0x4008 + 0x373b OP_Emote 0x7280 OP_DeleteSpawn -0x4bc2 -0x15bb +0x4bc2 +0x15bb 0x4fed OP_ShopRequest -0x0c2f +0x0c2f 0x4aa1 OP_ClickObject 0x6fca OP_GroundSpawn -0x1045 -0x2a9e -0x2523 -0x6213 +0x1045 +0x2a9e +0x2523 +0x6213 0x4a39 OP_Save -0x0797 +0x0797 0x35fa OP_ReqClientSpawn 0x5f8e OP_SendExpZonein 0x4c10 OP_GroupDisband 0x747c OP_SomeItemPacketMaybe 0x744c OP_Action 0x00d2 OP_SendCharInfo -0x0d2 -0x11d4 +0x0d2 +0x11d4 0x6bbf OP_CharacterCreate 0x6517 OP_Death -0x19f6 -0x360f -0x1951 +0x19f6 +0x360f +0x1951 0x51d3 OP_GMKill 0x26a7 OP_GMKick 0x7d8e OP_GMGoto -0x475e -0x5253 -0x3c79 -0x5065 -0x0337 -0x2ded -0x777b -0x4434 -0x0e90 -0x57da -0x150e -0x1ba8 -0x70eb +0x475e +0x5253 +0x3c79 +0x5065 +0x0337 +0x2ded +0x777b +0x4434 +0x0e90 +0x57da +0x150e +0x1ba8 +0x70eb 0x0adf OP_LootRequest 0x30f7 OP_EndLootRequest 0x5f44 OP_MoneyOnCorpse -0x3d54 -0x482d -0x58b2 -0x7361 -0x604f -0x3d87 -0x6909 -0x4f73 -0x0a0f +0x3d54 +0x482d +0x58b2 +0x7361 +0x604f +0x3d87 +0x6909 +0x4f73 +0x0a0f 0x56a2 OP_ApproveName -0x69e6 +0x69e6 0x3a8f OP_ClickDoor 0x08e8 OP_MoveDoor -0x7dd1 -0x5c66 +0x7dd1 +0x5c66 0x312a OP_Illusion -0x2807 -0x4fe2 -0x2f2e -0x5824 -0x6661 -0x4742 -0x4d02 -0x2105 -0x19e9 -0x2268 +0x2807 +0x4fe2 +0x2f2e +0x5824 +0x6661 +0x4742 +0x4d02 +0x2105 +0x19e9 +0x2268 0x7994 OP_WearChange 0x0386 OP_Bind_Wound 0x5306 OP_Forage @@ -320,210 +320,210 @@ RoF2 Built May 10 2013 23:30:08 0x7053 OP_GuildInviteAccept 0x1444 OP_GuildRemove 0x3708 OP_GuildDelete -0x7b22 -0x610f -0x324a -0x2889 -0x6f2a -0x2998 +0x7b22 +0x610f +0x324a +0x2889 +0x6f2a +0x2998 0x700c OP_RemoveAllDoors 0x1966 OP_GMTraining 0x4d6b OP_GMEndTraining -0x6e8d -0x2b41 +0x6e8d +0x2b41 0x4dc9 OP_LootItem 0x7177 OP_Animation -0x650e +0x650e 0x2d18 OP_ZoneChange -0x707b -0x0803 -0x3fc3 +0x707b +0x0803 +0x3fc3 0x7326 OP_GuildStatus -0x0804 -0x3f86 -0x6d20 -0x04a3 -0x4714 +0x0804 +0x3f86 +0x6d20 +0x04a3 +0x4714 0x6703 OP_Begging 0x0ae7 OP_ControlBoat -0x27d7 +0x27d7 0x3c21 OP_RezzRequest -0x6f22 +0x6f22 0x32ee OP_MoveItem 0x0bcf OP_MoveCoin -0x2452 -0x0186 -0x0522 +0x2452 +0x0186 +0x0522 0x3a54 OP_Split 0x659c OP_Buff -0x225b - +0x225b + 0x12cc OP_World_Client_CRC1 0x661e OP_Weather 0x742b OP_Consider -0x102e +0x102e 0x0f13 OP_World_Client_CRC2 -0x6944 +0x6944 0x2703 OP_Taunt 0x5602 OP_Feedback 0x68c2 OP_TradeMoneyUpdate -0x07f7 -0x3fe7 -0x43a0 -0x314c +0x07f7 +0x3fe7 +0x43a0 +0x314c 0x4b70 OP_Consume 0x2a79 OP_Stamina -0x402e -0x750e -0x0927 +0x402e +0x750e +0x0927 0x36a4 OP_Stun -0x2b03 +0x2b03 0x68d3 OP_DuelResponse2 0x6a46 OP_DuelResponse 0x5237 OP_ZoneSpawns 0x3eba OP_CombatAbility 0x109d OP_AutoAttack 0x075d OP_TargetMouse -0x05d2 +0x05d2 0x2a85 OP_GMTrainSkill 0x43a3 OP_ConfirmDelete -0x6c57 -0x3906 +0x6c57 +0x3906 0x55c4 OP_LootComplete 0x3196 OP_ShopEndConfirm 0x18ad OP_DeleteItem 0x01b8 OP_DeleteCharge -0x01d7 -0x4d28 -0x4fd9 -0x02c6 -0x3e30 +0x01d7 +0x4d28 +0x4fd9 +0x02c6 +0x3e30 0x3fcf OP_RequestClientZoneChange -0x4e0e -0x1dee +0x4e0e +0x1dee 0x62ac OP_GMZoneRequest 0x4ac6 OP_Logout 0x3526 OP_AutoAttack2 0x7ceb OP_LogServer 0x0423 OP_Surname 0x3956 OP_FriendsWho -0x45dc -0x35e0 -0x173e -0x4853 -0x393b +0x45dc +0x35e0 +0x173e +0x4853 +0x393b 0x0efa OP_SwapSpell -0x3588 -0x26e9 +0x3588 +0x26e9 0x4e56 OP_YellForHelp -0x2551 -0x38c8 +0x2551 +0x38c8 0x7499 OP_ApproveWorld -0x603a +0x603a 0x7b10 OP_RandomReq -0x208f +0x208f 0x607e OP_GMDelCorpse 0x1821 OP_Sacrifice -0x30d6 +0x30d6 0x760d OP_RezzComplete -0x4dd5 -0x0fe8 -0x6e5c -0x2228 -0x21bc -0x78c3 -0x3e57 -0x69d0 -0x1e3a +0x4dd5 +0x0fe8 +0x6e5c +0x2228 +0x21bc +0x78c3 +0x3e57 +0x69d0 +0x1e3a 0x1a30 OP_Sound -0x61a0 +0x61a0 0x048c OP_InterruptCast -0x6e80 -0x3d29 +0x6e80 +0x3d29 0x5d92 OP_Charm 0x0159 OP_PetCommands -0x486e -0x579e -0x49b3 +0x486e +0x579e +0x49b3 0x6db5 OP_GMApproval -0x4759 +0x4759 0x0c22 OP_MOTD 0x2097 OP_GMToggle -0x72f7 +0x72f7 0x640c OP_MoneyUpdate -0x51e5 -0x1802 -0x5de1 -0x2e0c +0x51e5 +0x1802 +0x5de1 +0x2e0c 0x58e2 OP_TargetCommand 0x444d OP_SetServerFilter 0x4478 OP_Assist -0x2a21 -0x7e59 +0x2a21 +0x7e59 0x0b0b OP_SetGuildMOTD 0x3e13 OP_GuildMOTD 0x7a11 OP_ClearObject 0x6580 OP_Translocate 0x28ec OP_Camp 0x61b3 OP_BecomeTrader -0x31af +0x31af 0x0876 OP_TGB -0x4ee1 -0x7e92 +0x4ee1 +0x7e92 0x7d14 OP_AAExpUpdate - + 0x5578 OP_FaceChange -0x1a80 -0x42ff -0x7c2d +0x1a80 +0x42ff +0x7c2d 0x2c57 OP_MobRename -0x6dab -0x4568 -0x7776 -0x5029 -0x696c +0x6dab +0x4568 +0x7776 +0x5029 +0x696c 0x23b9 OP_Dye 0x5204 OP_ConsiderCorpse 0x213f OP_SimpleMessage 0x1024 OP_FormattedMessage -0x2ce5 -0x5cc9 -0x3358 +0x2ce5 +0x5cc9 +0x3358 0x52e5 OP_DeleteSpell 0x48c1 OP_Shielding -0x6f14 +0x6f14 0x1414 OP_Report -0x0e1c -0x1219 +0x0e1c +0x1219 0x6857 OP_KeyRing 0x55ac OP_RaidInvite 0x3973 OP_RaidUpdate -0x56fe -0x7f2d +0x56fe +0x7f2d 0x31df OP_TraderShop 0x4ef5 OP_Trader -0x735d -0x019b -0x5930 -0x2d73 +0x735d +0x019b +0x5930 +0x2d73 0x39d6 OP_BazaarSearch -0x6a96 +0x6a96 0x424e OP_AAAction 0x7a27 OP_RespondAA -0x5eca -0x70c6 -0x0dd4 -0x4598 -0x19b6 -0x728a -0x5232 -0x77bd - +0x5eca +0x70c6 +0x0dd4 +0x4598 +0x19b6 +0x728a +0x5232 +0x77bd + 0x70c0 OP_ItemLinkResponse -0x633c -0x63eb -0x4765 -0x6290 +0x633c +0x63eb +0x4765 +0x6290 0x1db6 OP_RecipesSearch 0x6e02 OP_RecipeReply 0x40d7 OP_RecipeDetails @@ -546,248 +546,248 @@ RoF2 Built May 10 2013 23:30:08 0x0272 OP_TargetHoTT 0x2003 OP_ClearNPCMarks 0x20d3 OP_ClearRaidNPCMarks -0x1c89 -0x4ee2 +0x1c89 +0x4ee2 0x2797 OP_LeadershipExpUpdate 0x6da5 OP_ClearLeadershipAbilities -0x0ca6 -0x7717 -0x509d -0x6bee -0x0b04 +0x0ca6 +0x7717 +0x509d +0x6bee +0x0b04 0x6097 OP_NewSpawn -0x758c -0x2b10 -0x4d09 +0x758c +0x2b10 +0x4d09 0x0083 OP_SpecialMesg 0x3714 OP_TaskDescription 0x08d3 OP_TaskActivity 0x39f0 OP_CancelTask -0x5f7a +0x5f7a 0x006a OP_ItemScriptAdjustment -0x7465 -0x31c0 -0x14ba -0x25e0 -0x62a0 -0x407a -0x7c88 -0x51b8 +0x7465 +0x31c0 +0x14ba +0x25e0 +0x62a0 +0x407a +0x7c88 +0x51b8 0x578c OP_WhoAllResponse -0x1197 -0x2dd3 -0x333a -0x2925 +0x1197 +0x2dd3 +0x333a +0x2925 0x37b1 OP_MobHealth -0x7314 +0x7314 0x2404 OP_MobManaUpdate -0x5f5e +0x5f5e 0x1c81 OP_MobEnduranceUpdate -0x4a78 -0x37a2 -0x5565 -0x2d09 -0x3141 -0x695e +0x4a78 +0x37a2 +0x5565 +0x2d09 +0x3141 +0x695e 0x0029 OP_TrackTarget -0x029 -0x1612 -0x3e69 -0x5c59 +0x029 +0x1612 +0x3e69 +0x5c59 0x7e1a OP_GMZoneRequest2 -0x24d9 +0x24d9 0x5089 OP_ZoneEntry -0x61db -0x542f -0x2006 +0x61db +0x542f +0x2006 0x52fa OP_FeignDeath 0x39e8 OP_PickPocket -0x7c2e -0x2cdd -0x5e3a -0x4214 +0x7c2e +0x2cdd +0x5e3a +0x4214 0x2828 OP_HPUpdate -0x14b8 +0x14b8 0x3791 OP_ManaUpdate -0x0698 +0x0698 0x5f42 OP_EnduranceUpdate -0x7eb5 +0x7eb5 0x73f4 OP_Bug 0x69a4 OP_SendZonepoints 0x57bc OP_InspectRequest 0x71ac OP_InspectAnswer 0x4229 OP_GroupMakeLeader -0x54f7 -0x322b -0x3264 -0x202c -0x29ae +0x54f7 +0x322b +0x3264 +0x202c +0x29ae 0x7a09 OP_SendLoginInfo -0x5206 -0x7fd2 +0x5206 +0x7fd2 0x6259 OP_PostEnterWorld -0x0f70 -0x00d1 -0x0d1 -0x1eac -0x1b54 -0x5e23 -0x7910 -0x152e -0x7555 -0x05ce +0x0f70 +0x00d1 +0x0d1 +0x1eac +0x1b54 +0x5e23 +0x7910 +0x152e +0x7555 +0x05ce 0x1649 OP_GroupFollow 0x6110 OP_GroupInvite -0x0641 +0x0641 0x578f OP_EnterWorld -0x6cb8 -0x6ef5 +0x6cb8 +0x6ef5 0x4c44 OP_ZoneServerInfo 0x4cb4 OP_ZoneUnavail -0x74fb -0xbe6b -0x5ac7 -0x6281 +0x74fb +0xbe6b +0x5ac7 +0x6281 0x1bc5 OP_SetChatServer -0x48c8 -0x677b -0x6820 -0x2623 +0x48c8 +0x677b +0x6820 +0x2623 0x69b9 OP_GuildMemberUpdate -0x2dcb -0x5e04 -0x1d3c +0x2dcb +0x5e04 +0x1d3c 0x6060 OP_LFGCommand 0x0340 OP_LFGGetMatchesRequest 0x49a9 OP_LFPCommand 0x4d7d OP_LFPGetMatchesRequest 0x5048 OP_LFGGetMatchesResponse 0x22c6 OP_LFPGetMatchesResponse -0x499e +0x499e 0x2d4e OP_GuildDemote -0x2009 -0x3b26 -0x69d4 -0x40b1 -0x587d -0x3747 -0x7a66 -0x2f03 -0x01ed +0x2009 +0x3b26 +0x69d4 +0x40b1 +0x587d +0x3747 +0x7a66 +0x2f03 +0x01ed 0x36e0 OP_GetGuildMOTD 0x4f1f OP_GetGuildMOTDReply -0x6087 -0x5f92 -0x439a -0x4334 -0x13c2 +0x6087 +0x5f92 +0x439a +0x4334 +0x13c2 0x7cb8 OP_TestBuff 0x17e5 OP_Track -0x5265 -0x1440 -0x7a4b -0x0648 -0x2529 +0x5265 +0x1440 +0x7a4b +0x0648 +0x2529 0x486f OP_GMSummon 0x458e OP_GMEmoteWorld -0x2549 +0x2549 0x1cfd OP_GMEmoteZone 0x5ca6 OP_CharInventory -0x2710 +0x2710 0x7291 OP_SpawnDoor -0x7afc -0x1f65 -0x3ddd -0x85fc +0x7afc +0x1f65 +0x3ddd +0x85fc 0x72df OP_ReadBook 0x3af1 OP_RequestDuel -0x52f7 +0x52f7 0x77b5 OP_TradeRequest -0x70f2 -0x207d +0x70f2 +0x207d 0x384a OP_ConsentResponse 0x5505 OP_TradeBusy 0x0c1e OP_ClickObjectAction -0x51eb -0x466b -0x41a3 -0x0682 -0x1242 +0x51eb +0x466b +0x41a3 +0x0682 +0x1242 0x261d OP_LoadSpellSet -0x32a6 +0x32a6 0x2c6c OP_AdventureRequest -0x39e5 +0x39e5 0x5648 OP_AdventureDetails 0x5327 OP_LDoNButton -0x4524 +0x4524 0x5954 OP_RandomNameGenerator -0x327d -0x2945 -0x33d7 -0x2651 -0x3b86 -0x0c6c -0x24d4 -0x4441 -0x4e42 -0x50d8 -0x1afb -0x6bd4 +0x327d +0x2945 +0x33d7 +0x2651 +0x3b86 +0x0c6c +0x24d4 +0x4441 +0x4e42 +0x50d8 +0x1afb +0x6bd4 0x3cb0 OP_AdventureInfoRequest 0x4c54 OP_AdventureInfo 0x7171 OP_AdventureData -0x35e8 -0x7d49 -0x1848 -0x2599 -0x0dab -0x6493 -0x34f2 -0x7d40 -0x0e00 -0x1cf3 -0x71f4 +0x35e8 +0x7d49 +0x1848 +0x2599 +0x0dab +0x6493 +0x34f2 +0x7d40 +0x0e00 +0x1cf3 +0x71f4 0x5d18 OP_LeaveAdventure -0x4e25 -0x50c2 +0x4e25 +0x50c2 0x400f OP_AdventureFinish -0x6368 -0x6192 -0x3aa4 +0x6368 +0x6192 +0x3aa4 0x502e OP_Marquee 0x1b01 OP_AdventureUpdate -0x0f24 -0x1015 -0x23a6 -0x5a74 -0x037a +0x0f24 +0x1015 +0x23a6 +0x5a74 +0x037a 0x7a45 OP_ConsentDeny -0x71bc +0x71bc 0x2382 OP_DenyResponse -0x02a0 +0x02a0 0x1126 OP_VetClaimRequest -0x0668 +0x0668 0x16d4 OP_VetClaimReply 0x5cea OP_FindPersonRequest -0x0cc3 +0x0cc3 0x7e58 OP_FindPersonReply 0x9be3 OP_PickLock -0x0438 +0x0438 0x3d5c OP_LDoNOpen -0x15e7 +0x15e7 0x368e OP_ItemPacket -0x7331 -0x4936 -0x2c4e +0x7331 +0x4936 +0x2c4e 0x78bf OP_DisarmTraps 0x02af OP_SenseTraps 0x65c3 OP_AdventurePointsUpdate -0x0dcb +0x0dcb 0x661b OP_AugmentItem -0x5f68 -0x5278 -0x6b53 -0x5dd1 +0x5f68 +0x5278 +0x6b53 +0x5dd1 0x559a OP_WeaponEquip2 0x2d25 OP_WeaponUnequip2 0x23c1 OP_WorldClientReady @@ -795,7 +795,7 @@ RoF2 Built May 10 2013 23:30:08 0x7093 OP_AdventureLeaderboardRequest 0x2370 OP_AdventureStatsReply 0x7f79 OP_AdventureLeaderboardReply -0x4eb3 +0x4eb3 0x3377 OP_BuffCreate 0x5882 OP_PetBuffWindow 0x4f4b OP_TargetBuffs @@ -806,31 +806,31 @@ RoF2 Built May 10 2013 23:30:08 0x4254 OP_TributeInfo 0x79fc OP_SelectTribute 0x073d OP_TributeTimer -0x08bf -0x7697 -0x4b65 -0x51ae -0x51a9 +0x08bf +0x7697 +0x4b65 +0x51ae +0x51a9 0x7666 OP_OpenTributeMaster -0x4290 +0x4290 0x759e OP_DisciplineUpdate -0x01c6 +0x01c6 0x6989 OP_DisciplineTimer 0x58fb OP_TributeMoney -0x7422 -0x0d25 -0x4de1 -0x46d0 -0x4f50 -0x2ddc -0x7441 -0x6a12 -0x29a8 -0x3f7f -0x150b -0x11e3 -0x1ad3 - +0x7422 +0x0d25 +0x4de1 +0x46d0 +0x4f50 +0x2ddc +0x7441 +0x6a12 +0x29a8 +0x3f7f +0x150b +0x11e3 +0x1ad3 + 0x59ca OP_DzAddPlayer 0x4701 OP_DzRemovePlayer 0x1abc OP_DzSwapPlayer @@ -838,96 +838,96 @@ RoF2 Built May 10 2013 23:30:08 0x543d OP_DzPlayerList 0x14c6 OP_DzJoinExpeditionConfirm 0x7f4b OP_DzJoinExpeditionReply -0x1950 -0x64b5 +0x1950 +0x64b5 0x0398 OP_DzListTimers -0x7b68 +0x7b68 0x4f7e OP_DzExpeditionInfo 0x9119 OP_DzExpeditionList 0x205f OP_DzQuit 0xb2e3 OP_DzMemberStatus 0x32f0 OP_DzLeaderStatus 0x3de9 OP_DzMemberList -0x5ae4 +0x5ae4 0x4d6e OP_OnLevelMessage -0x4fd0 -0x575b +0x4fd0 +0x575b 0x7e94 OP_DzExpeditionEndsWarning -0x5189 -0x383c +0x5189 +0x383c 0x791e OP_BankerChange -0x5c74 +0x5c74 0x71b1 OP_RecipesFavorite -0x20ab -0x025f -0x214a +0x20ab +0x025f +0x214a 0x08a6 OP_PopupResponse 0x15a9 OP_ItemRecastDelay 0x3707 OP_PVPLeaderBoardDetailsRequest 0x04aa OP_PVPLeaderBoardRequest 0x25b7 OP_PVPLeaderBoardDetailsReply 0x071f OP_PVPLeaderBoardReply -0x2dee -0x4e62 -0x0c91 -0x18d3 +0x2dee +0x4e62 +0x0c91 +0x18d3 0x6f4b OP_Weblink 0x4b15 OP_PVPStats -0x6755 -0x5c32 +0x6755 +0x5c32 0x5770 OP_PlayMP3 -0x7425 -0x5eed -0x574e -0x11b4 -0x4ed6 -0x0d9f -0x7d23 +0x7425 +0x5eed +0x574e +0x11b4 +0x4ed6 +0x0d9f +0x7d23 0x3fb0 OP_ClearSurname -0xc693 +0xc693 0x7b1e OP_RemoveNimbusEffect -0x20ae -0x0727 -0x3771 -0x7fe0 -0x4d5e +0x20ae +0x0727 +0x3771 +0x7fe0 +0x4d5e 0x1877 OP_SendGuildTributes -0x649f -0x5f3f -0x5bcb -0x43d2 -0x49ea +0x649f +0x5f3f +0x5bcb +0x43d2 +0x49ea 0x378d OP_OpenGuildTributeMaster -0x7f8e -0x02bc -0x7dd2 -0x7c1d -0x7a41 -0x7db5 +0x7f8e +0x02bc +0x7dd2 +0x7c1d +0x7a41 +0x7db5 0x7eec OP_SetChatServer2 0x0904 OP_CorpseDrag 0x7037 OP_CorpseDrop 0x5e19 OP_TaskActivityComplete -0x4e32 +0x4e32 0x241d OP_TributeToggle -0x756a -0x7745 -0x039d +0x756a +0x7745 +0x039d 0x0f50 OP_ClearAA 0x66b5 OP_SendAATable 0x0afb OP_AugmentInfo -0x10f6 -0x1013 - +0x10f6 +0x1013 + 0x6344 OP_RequestTitles 0x2d08 OP_SendTitleList -0x3719 -0x7a48 +0x3719 +0x7a48 0x0a23 OP_AcceptNewTask -0x705b +0x705b 0x3bc9 OP_LevelAppearance -0x60ef -0x1619 +0x60ef +0x1619 0x17fd OP_VoiceMacroIn 0x409a OP_VoiceMacroOut 0x4493 OP_WorldComplete @@ -936,437 +936,437 @@ RoF2 Built May 10 2013 23:30:08 0x127f OP_CameraEffect 0x0d32 OP_NewTitlesAvailable 0x34a7 OP_WeaponEquip1 -0x34cd +0x34cd 0x5936 OP_SpellEffect 0x241e OP_AutoFire 0x66f0 OP_UpdateAA 0x100e OP_CustomTitles -0x6b65 -0x12f5 +0x6b65 +0x12f5 0x7677 OP_Bandolier -0x688f -0x7adf -0x0ed4 +0x688f +0x7adf +0x0ed4 0x243a OP_Barter -0x1a6a -0x5623 +0x1a6a +0x5623 0x43c8 OP_SendAAStats -0x655e +0x655e 0x1a3e OP_PotionBelt 0x6326 OP_SetStartCity -0x7485 -0x5416 -0x3282 -0x3752 -0x425b -0x27c8 -0x2b19 -0x70ce -0x3165 -0x786b -0x0f26 -0x3500 -0x3d04 +0x7485 +0x5416 +0x3282 +0x3752 +0x425b +0x27c8 +0x2b19 +0x70ce +0x3165 +0x786b +0x0f26 +0x3500 +0x3d04 0x5134 OP_GuildBank -0x0521 -0x7850 -0x108b -0x5671 -0x6d2b -0x732f +0x0521 +0x7850 +0x108b +0x5671 +0x6d2b +0x732f 0x748f OP_GuildManageBanker -0x6858 -0x5e74 -0x3f35 -0x35e9 -0x2056 +0x6858 +0x5e74 +0x3f35 +0x35e9 +0x2056 0x6922 OP_AdventureMerchantRequest 0x5b72 OP_AdventureMerchantPurchase 0x2f9b OP_AdventureMerchantSell 0x3e47 OP_AdventureMerchantResponse 0x0b7d OP_DzChooseZone -0x2818 -0x35bd -0x51df -0x1ff7 -0x3926 -0x6265 -0x4ab0 -0x5e6c -0x1350 -0x6288 -0x7348 +0x2818 +0x35bd +0x51df +0x1ff7 +0x3926 +0x6265 +0x4ab0 +0x5e6c +0x1350 +0x6288 +0x7348 0x48a2 OP_OpenNewTasksWindow -0x3010 -0x45db +0x3010 +0x45db 0x36e8 OP_AvaliableTask -0x4865 -0x322e -0x7582 +0x4865 +0x322e +0x7582 0x5727 OP_TaskMemberList -0x6646 -0x37f2 -0x3444 -0x5ffc -0x5cb5 -0x0119 -0x35b5 -0x6cc6 -0x4926 -0x1b1d -0x7299 -0x0bf1 -0x08b4 -0x7f7a -0x3dab -0x1e7d -0x610a -0x04c8 -0x4811 -0x609e -0x65f0 +0x6646 +0x37f2 +0x3444 +0x5ffc +0x5cb5 +0x0119 +0x35b5 +0x6cc6 +0x4926 +0x1b1d +0x7299 +0x0bf1 +0x08b4 +0x7f7a +0x3dab +0x1e7d +0x610a +0x04c8 +0x4811 +0x609e +0x65f0 0x467f OP_CrystalCountUpdate 0x7aee OP_CrystalCreate 0x2439 OP_CrystalReclaim -0x39c1 -0x555e -0x7c9c -0x2c94 -0x2fc2 -0x2067 -0x059e -0x7f74 -0x68b2 -0x6f2b -0x01d6 -0x5182 -0x1da2 -0x5147 -0x51f8 -0x11f3 -0x0d07 -0x272f -0x413f -0x5968 +0x39c1 +0x555e +0x7c9c +0x2c94 +0x2fc2 +0x2067 +0x059e +0x7f74 +0x68b2 +0x6f2b +0x01d6 +0x5182 +0x1da2 +0x5147 +0x51f8 +0x11f3 +0x0d07 +0x272f +0x413f +0x5968 0x3e0e OP_DzCompass -0x282a +0x282a 0x035f OP_GMNameChange -0x4013 -0x0e2f -0x099e +0x4013 +0x0e2f +0x099e 0x4eba OP_CompletedTasks 0x5f1c OP_TaskHistoryRequest 0x3d05 OP_TaskHistoryReply -0x2b8e -0x66d6 -0x0143 -0x6a5d -0x6e61 -0x2b0f +0x2b8e +0x66d6 +0x0143 +0x6a5d +0x6e61 +0x2b0f 0x46c6 OP_FloatListThing -0x3a8d -0x15e5 -0x7d89 -0x4085 -0x1507 -0x5d93 +0x3a8d +0x15e5 +0x7d89 +0x4085 +0x1507 +0x5d93 0x1669 OP_ResetAA -0x4664 -0x312d -0x2215 +0x4664 +0x312d +0x2215 0x1745 OP_Rewind -0x0cf1 -0x6567 -0x4405 -0x72d8 -0xaaba +0x0cf1 +0x6567 +0x4405 +0x72d8 +0xaaba 0x27f8 OP_AssistGroup -0x7cfb -0x1a32 -0x14fd -0x77bb -0x36d1 -0x6193 -0x184c -0x3c47 -0x4e0b -0x7c15 +0x7cfb +0x1a32 +0x14fd +0x77bb +0x36d1 +0x6193 +0x184c +0x3c47 +0x4e0b +0x7c15 0x2ec3 OP_ShroudSelectionWindow 0x2b37 OP_ShroudRequestStats 0x6c04 OP_ShroudRespondStats 0x1cd0 OP_ShroudSelect 0x79da OP_ShroudSelectCancel -0x7806 +0x7806 0x541d OP_ShroudProgress 0x3823 OP_ShroudProgress2 0x6562 OP_Shroud -0x4b25 -0x2507 -0x3acc -0x006e -0x06e -0x0728 -0x66cd -0x3c54 -0x2e8d -0x4eea -0x5a67 -0x3d20 -0x649c -0x21a6 +0x4b25 +0x2507 +0x3acc +0x006e +0x06e +0x0728 +0x66cd +0x3c54 +0x2e8d +0x4eea +0x5a67 +0x3d20 +0x649c +0x21a6 0x28bc OP_RespawnWindow 0x0ecb OP_ZonePlayerToBind -0x08d8 -0x7b71 -0x1115 -0x18cb -0x34f4 -0x5f08 -0x5bd2 -0x5052 -0x5e69 -0x2ccc -0x655c +0x08d8 +0x7b71 +0x1115 +0x18cb +0x34f4 +0x5f08 +0x5bd2 +0x5052 +0x5e69 +0x2ccc +0x655c 0x6773 OP_CharacterCreateRequest 0x590e OP_VetRewardsAvaliable -0x6b0b -0x555a -0x7786 +0x6b0b +0x555a +0x7786 0x67e8 OP_ShroudRemove -0x6381 -0x5628 -0xb52f -0x6084 +0x6381 +0x5628 +0xb52f +0x6084 0x2958 OP_GuildUpdateURLAndChannel 0x2301 OP_WorldUnknown001 -0x610b -0x6f8b +0x610b +0x6f8b 0x4d25 OP_InspectMessageUpdate 0x3033 OP_BlockedBuffs 0x0de7 OP_RemoveBlockedBuffs -0x30e5 -0x618c -0x58e6 +0x30e5 +0x618c +0x58e6 0x1456 OP_ShroudUnknown1 0x053c OP_Untargetable -0x71da -0x333f -0x49bc +0x71da +0x333f +0x49bc 0x64f2 OP_BuffRemoveRequest -0x6b3d +0x6b3d 0x34cb OP_ClearBlockedBuffs -0x5646 -0x7d13 -0x15e0 -0x5710 -0x172b -0x1888 -0x2956 -0x6a68 -0x7631 -0x298e -0x003c -0x03c -0x0b0f -0x5a72 -0x0767 -0x2f1a -0x6c7a -0x1660 -0x344f -0x028b -0x6eea -0x7707 -0x3fb2 -0x289e +0x5646 +0x7d13 +0x15e0 +0x5710 +0x172b +0x1888 +0x2956 +0x6a68 +0x7631 +0x298e +0x003c +0x03c +0x0b0f +0x5a72 +0x0767 +0x2f1a +0x6c7a +0x1660 +0x344f +0x028b +0x6eea +0x7707 +0x3fb2 +0x289e 0x3342 OP_GroupMentor 0x5892 OP_NPCMoveUpdate 0x189c OP_ItemVerifyRequest 0x097b OP_ItemVerifyReply -0x2115 -0x6411 -0x6471 -0x134a -0x1304 +0x2115 +0x6411 +0x6471 +0x134a +0x1304 0x5a79 OP_ShieldGroup -0x2dde -0x7d50 -0x1d47 -0x10ec +0x2dde +0x7d50 +0x1d47 +0x10ec 0x000f OP_RestState -0x00f -0x0f +0x00f +0x0f 0x465b OP_ItemViewUnknown -0x2289 -0x023b -0x4223 -0x7261 -0x2af9 -0x19aa -0x66dd +0x2289 +0x023b +0x4223 +0x7261 +0x2af9 +0x19aa +0x66dd 0x4b64 OP_GMTrainSkillConfirm -0x319e -0x1af3 -0x449c -0x8582 +0x319e +0x1af3 +0x449c +0x8582 0x4b8d #OP_LoginUnknown1 0x298d #OP_LoginUnknown2 -0x06c8 -0x4f93 -0x412d -0x001f -0x01f -0x60f6 -0x1a9e -0x798e -0x17b7 -0x3042 -0x61bd -0x1f6e -0x65a6 -0x740d -0x48da -0x20d9 -0x5258 -0x1b5d -0x49f4 -0x7aa9 -0xb350 -0x6e2a -0x5d4e -0x6e4d -0x4ffc -0x1d15 -0x6f23 -0x2296 -0x765b -0x2e01 -0x26dd -0x72d3 -0x6981 -0x3b30 -0x14ac -0x0d92 -0x0001 -0x001 -0x01 -0x09bb -0x9e18 -0x0d9d -0x7f2b -0x3651 -0x645d -0x3af2 -0x4377 -0x39c9 -0x4924 -0x1e50 -0x4683 -0x0276 -0x6dec -0x56c9 -0x3ee6 -0x7121 -0x62ab -0x5d88 -0x05f0 +0x06c8 +0x4f93 +0x412d +0x001f +0x01f +0x60f6 +0x1a9e +0x798e +0x17b7 +0x3042 +0x61bd +0x1f6e +0x65a6 +0x740d +0x48da +0x20d9 +0x5258 +0x1b5d +0x49f4 +0x7aa9 +0xb350 +0x6e2a +0x5d4e +0x6e4d +0x4ffc +0x1d15 +0x6f23 +0x2296 +0x765b +0x2e01 +0x26dd +0x72d3 +0x6981 +0x3b30 +0x14ac +0x0d92 +0x0001 +0x001 +0x01 +0x09bb +0x9e18 +0x0d9d +0x7f2b +0x3651 +0x645d +0x3af2 +0x4377 +0x39c9 +0x4924 +0x1e50 +0x4683 +0x0276 +0x6dec +0x56c9 +0x3ee6 +0x7121 +0x62ab +0x5d88 +0x05f0 0x6b6d OP_AltCurrency -0x61cb -0x0165 +0x61cb +0x0165 0x74ec OP_MercenaryAssign 0x5409 OP_AltCurrencyMerchantRequest 0x3788 OP_AltCurrencyPurchase 0x40b6 OP_AltCurrencySell 0x27a2 OP_AltCurrencyMerchantReply 0x532a OP_AltCurrencySellSelection -0x3899 -0x7567 -0x4820 +0x3899 +0x7567 +0x4820 0x0339 OP_AltCurrencyReclaim -0x0c12 -0x074d -0x47ba -0x55c8 -0x4c89 -0x18b7 -0x2950 -0x44cb -0x4477 -0x6146 -0x40cf -0x2405 -0x49e2 -0x2aff -0x0bfa -0x26c0 -0x7d39 -0x259f -0x086b -0x6dc1 -0x6f80 -0x042e -0x2ad4 -0x317b -0x73ac -0x18d7 -0x5b8b -0x5c83 -0x1f51 -0x62f7 -0x1c46 -0x2a44 -0x3e7a -0x4374 -0x3b23 -0x1df4 -0x3ed8 -0x4b50 -0x36eb -0x65c7 -0x5620 -0x587e -0x3897 -0x7e62 -0x67ae -0x74f4 +0x0c12 +0x074d +0x47ba +0x55c8 +0x4c89 +0x18b7 +0x2950 +0x44cb +0x4477 +0x6146 +0x40cf +0x2405 +0x49e2 +0x2aff +0x0bfa +0x26c0 +0x7d39 +0x259f +0x086b +0x6dc1 +0x6f80 +0x042e +0x2ad4 +0x317b +0x73ac +0x18d7 +0x5b8b +0x5c83 +0x1f51 +0x62f7 +0x1c46 +0x2a44 +0x3e7a +0x4374 +0x3b23 +0x1df4 +0x3ed8 +0x4b50 +0x36eb +0x65c7 +0x5620 +0x587e +0x3897 +0x7e62 +0x67ae +0x74f4 0x4613 OP_SendFindableNPCs -0x2a92 -0x6d6e -0x2c01 -0x1243 -0x133e -0x67fc - +0x2a92 +0x6d6e +0x2c01 +0x1243 +0x133e +0x67fc + 0x49e1 OP_HideCorpse -0x0e44 -0x239a -0x1a0a -0x398f -0x1ff4 -0x88a1 +0x0e44 +0x239a +0x1a0a +0x398f +0x1ff4 +0x88a1 0x74da OP_GroupDisbandOther 0x21b4 OP_GroupLeaderChange -0x62b7 -0x4ced +0x62b7 +0x4ced 0x3abb OP_GroupUpdate 0x0f6c OP_GroupDelete -0x0cbc +0x0cbc 0x6194 OP_GroupUpdateB -0x7fe6 -0x04d0 +0x7fe6 +0x04d0 0x7323 OP_GroupAcknowledge 0x1ae5 OP_GroupDisbandYou -0x4d9f +0x4d9f 0x70e2 OP_GroupRoles -0x6875 -0x6298 +0x6875 +0x6298 0x02cf OP_GroupLeadershipAAUpdate 0x22b8 OP_MercenarySuspend2 -0x7bff -0x5512 -0x2b57 -0x1380 -0x2bcb +0x7bff +0x5512 +0x2b57 +0x1380 +0x2bcb 0x7b89 OP_MercenaryDataUpdateRequest 0x61a4 OP_MercenaryDataUpdate 0x11c1 OP_MercenaryDataRequest @@ -1375,297 +1375,297 @@ RoF2 Built May 10 2013 23:30:08 0x6e83 OP_MercenaryDismiss 0x31e4 OP_MercenaryTimerRequest 0x0763 OP_MercenaryTimer -0x78f6 -0x1b37 +0x78f6 +0x1b37 0x27f2 OP_MercenaryCommand -0x5df8 -0x4333 -0x69ca -0x6e9f +0x5df8 +0x4333 +0x69ca +0x6e9f 0x4407 OP_MercenarySuspendRequest 0x6f03 OP_MercenarySuspendResponse 0x27a0 OP_MercenaryUnsuspendResponse -0x5f88 -0x2749 -0x038f -0x0e52 -0x20b9 +0x5f88 +0x2749 +0x038f +0x0e52 +0x20b9 0x5d26 OP_MercenaryUnknown1 -0x69e7 -0x66b9 -0x0b72 -0x7e6f -0x29ec -0x6248 -0x702b -0x2b4f -0x6e6d -0x1e9f -0x5bd5 -0x4b7f -0x1490 -0x075e -0x4263 -0x1eba -0x4a74 -0x0a37 -0x3289 -0x3171 -0x0114 -0x5148 -0x76c3 -0x09bf -0x356f -0x77a7 -0x479a -0x209f -0x54e6 -0x6c3e -0xee80 -0x40e5 +0x69e7 +0x66b9 +0x0b72 +0x7e6f +0x29ec +0x6248 +0x702b +0x2b4f +0x6e6d +0x1e9f +0x5bd5 +0x4b7f +0x1490 +0x075e +0x4263 +0x1eba +0x4a74 +0x0a37 +0x3289 +0x3171 +0x0114 +0x5148 +0x76c3 +0x09bf +0x356f +0x77a7 +0x479a +0x209f +0x54e6 +0x6c3e +0xee80 +0x40e5 0x76d9 OP_GuildCreate -0x1dc8 -0x794a -0x35c5 -0x137d -0x004a -0x04a -0x29b4 -0x18f1 -0x17fc +0x1dc8 +0x794a +0x35c5 +0x137d +0x004a +0x04a +0x29b4 +0x18f1 +0x17fc 0x4707 OP_ChangeSize -0x3e15 -0x7248 -0x57c6 -0x7679 -0x6c8b -0x14c3 -0x3a02 -0x7900 -0x5688 -0x3a58 -0x75dd -0x39e1 -0x47cb -0x171e -0xdab0 -0x618f -0x27b1 -0x3d0c -0x0d2a -0x8c30 -0x2b42 -0x17f8 -0x1665 -0x059d -0x72c9 -0x675d -0x28e0 -0x61df -0x3ef8 -0x4d59 -0x3763 +0x3e15 +0x7248 +0x57c6 +0x7679 +0x6c8b +0x14c3 +0x3a02 +0x7900 +0x5688 +0x3a58 +0x75dd +0x39e1 +0x47cb +0x171e +0xdab0 +0x618f +0x27b1 +0x3d0c +0x0d2a +0x8c30 +0x2b42 +0x17f8 +0x1665 +0x059d +0x72c9 +0x675d +0x28e0 +0x61df +0x3ef8 +0x4d59 +0x3763 0x672f OP_XTargetResponse 0x45be OP_XTargetRequest 0x792c OP_XTargetAutoAddHaters -0x66bf -0x66df -0x2aa7 -0x76c6 -0x6032 -0x3e00 -0x0c13 -0x0a59 -0x393a -0x45ed -0x507f -0x68ba -0x5a63 -0x6fd0 -0x63fd -0x4f09 -0x485d -0x3968 -0x69e1 -0x3d94 -0x0351 -0x5f0a -0x36be -0x59f9 -0x7075 -0x6ee6 -0x2691 -0x3c8b -0x01df -0x218c -0x233b -0x2cf7 -0x1097 -0x1baf -0x6f35 -0x1228 -0x1cef -0x7d28 -0x087f -0x1967 -0x6917 -0x613d -0x37d9 -0x58c7 -0x21ec -0x3424 -0x2036 -0x7ad1 -0x01c3 -0x626e -0x711a -0x5b41 -0x21ba -0x2933 -0x050c -0x51e3 -0x64a3 -0x3146 -0x2aa6 -0x16df -0x2698 -0x21ea -0x796c -0x7bb1 -0x6af6 -0x499a -0x3c44 -0x3cbc -0x6561 -0x12f7 -0x6292 -0x3de0 -0x1409 -0x4604 -0x0ca1 -0x754e -0x4f2b -0x6995 -0x3745 -0x789b -0xcadf -0x4abe -0x6014 -0x7fff -0x6228 -0x10e0 -0x7452 -0x6d9f -0x7f80 -0xb07f +0x66bf +0x66df +0x2aa7 +0x76c6 +0x6032 +0x3e00 +0x0c13 +0x0a59 +0x393a +0x45ed +0x507f +0x68ba +0x5a63 +0x6fd0 +0x63fd +0x4f09 +0x485d +0x3968 +0x69e1 +0x3d94 +0x0351 +0x5f0a +0x36be +0x59f9 +0x7075 +0x6ee6 +0x2691 +0x3c8b +0x01df +0x218c +0x233b +0x2cf7 +0x1097 +0x1baf +0x6f35 +0x1228 +0x1cef +0x7d28 +0x087f +0x1967 +0x6917 +0x613d +0x37d9 +0x58c7 +0x21ec +0x3424 +0x2036 +0x7ad1 +0x01c3 +0x626e +0x711a +0x5b41 +0x21ba +0x2933 +0x050c +0x51e3 +0x64a3 +0x3146 +0x2aa6 +0x16df +0x2698 +0x21ea +0x796c +0x7bb1 +0x6af6 +0x499a +0x3c44 +0x3cbc +0x6561 +0x12f7 +0x6292 +0x3de0 +0x1409 +0x4604 +0x0ca1 +0x754e +0x4f2b +0x6995 +0x3745 +0x789b +0xcadf +0x4abe +0x6014 +0x7fff +0x6228 +0x10e0 +0x7452 +0x6d9f +0x7f80 OP_ItemPreviewRequest +0xb07f 0x6b5c OP_ItemPreview -0x02b4 -0x3bd3 -0x3036 -0x5c3f -0x433d -0x78e2 -0x543f -0x58b4 -0x2563 -0x1820 -0x40be -0x62d6 -0x0d68 -0x6d10 -0x3a32 -0x2fed -0x1303 -0x49df -0x3e24 -0x2b35 -0x0910 -0x3849 -0x6873 -0x15e2 -0x2d85 -0x39bb -0x42e9 -0x6860 -0x15a8 -0x52b5 -0x3311 -0x58df -0x2a7f -0x6573 -0x7a4d -0x7497 -0x1fcc -0x7c23 -0x2d28 -0x5dab -0x005a -0x05a -0x4506 -0x046d -0x36db -0x5a40 -0x4cd9 -0x63d7 -0x48d4 -0x035d -0x11f5 -0x7b84 -0x4f05 -0x7369 -0x7b32 -0x4fe6 -0x6cd0 -0x6770 -0x5c24 -0x063a -0x0d93 -0x4c2a -0x2235 -0x7b95 -0x6a1f -0x46f0 -0x2de2 -0xadd7 -0x2cc6 -0x7db7 -0x7588 -0x4957 -0x6a98 - +0x02b4 +0x3bd3 +0x3036 +0x5c3f +0x433d +0x78e2 +0x543f +0x58b4 +0x2563 +0x1820 +0x40be +0x62d6 +0x0d68 +0x6d10 +0x3a32 +0x2fed +0x1303 +0x49df +0x3e24 +0x2b35 +0x0910 +0x3849 +0x6873 +0x15e2 +0x2d85 +0x39bb +0x42e9 +0x6860 +0x15a8 +0x52b5 +0x3311 +0x58df +0x2a7f +0x6573 +0x7a4d +0x7497 +0x1fcc +0x7c23 +0x2d28 +0x5dab +0x005a +0x05a +0x4506 +0x046d +0x36db +0x5a40 +0x4cd9 +0x63d7 +0x48d4 +0x035d +0x11f5 +0x7b84 +0x4f05 +0x7369 +0x7b32 +0x4fe6 +0x6cd0 +0x6770 +0x5c24 +0x063a +0x0d93 +0x4c2a +0x2235 +0x7b95 +0x6a1f +0x46f0 +0x2de2 +0xadd7 +0x2cc6 +0x7db7 +0x7588 +0x4957 +0x6a98 + 0x057b OP_SendMembershipDetails 0x7acc OP_SendMembership -0x431f -0x42c4 -0x05a8 +0x431f +0x42c4 +0x05a8 0x5475 OP_SendMaxCharacters -0x4ad2 -0x03d9 -0x771e -0x2a34 -0x7273 -0x1c9e -0x5f17 -0x37a9 -0x6530 -0x3157 -0x1f5e -0x2bc8 -0x593a -0x1643 -0x16bc -0x1781 -0x53d3 -0x5369 -0x25b3 -0x2fa0 -0x73a9 -0x1595 -0x6b6f -0x65f2 -0x3562 -0x309d -0x4efa -0x1da9 -0x6678 -0x16e8 \ No newline at end of file +0x4ad2 +0x03d9 +0x771e +0x2a34 +0x7273 +0x1c9e +0x5f17 +0x37a9 +0x6530 +0x3157 +0x1f5e +0x2bc8 +0x593a +0x1643 +0x16bc +0x1781 +0x53d3 +0x5369 +0x25b3 +0x2fa0 +0x73a9 +0x1595 +0x6b6f +0x65f2 +0x3562 +0x309d +0x4efa +0x1da9 +0x6678 +0x16e8 diff --git a/utils/sql/git/optional/2025_01_09_drop_qs_tables.sql b/utils/sql/git/optional/2025_01_09_drop_qs_tables.sql new file mode 100644 index 000000000..7117d8e55 --- /dev/null +++ b/utils/sql/git/optional/2025_01_09_drop_qs_tables.sql @@ -0,0 +1,17 @@ +-- Drop all qs_ tables +DROP TABLE IF EXISTS + qs_merchant_transaction_record, + qs_merchant_transaction_record_entries, + qs_player_aa_rate_hourly, + qs_player_delete_record, + qs_player_delete_record_entries, + qs_player_events, + qs_player_handin_record, + qs_player_handin_record_entries, + qs_player_move_record, + qs_player_move_record_entries, + qs_player_npc_kill_record, + qs_player_npc_kill_record_entries, + qs_player_speech, + qs_player_trade_record, + qs_player_trade_record_entries; diff --git a/utils/sql/git/optional/2025_01_09_qs_to_player_event_convert.sql b/utils/sql/git/optional/2025_01_09_qs_to_player_event_convert.sql new file mode 100644 index 000000000..dc808bca9 --- /dev/null +++ b/utils/sql/git/optional/2025_01_09_qs_to_player_event_convert.sql @@ -0,0 +1,94 @@ +-- ================================ +-- Conversion of Merchant Transactions +-- ================================ +INSERT INTO player_event_merchant_sell ( + npc_id, merchant_name, merchant_type, item_id, item_name, charges, cost, alternate_currency_id, + player_money_balance, player_currency_balance, created_at +) +SELECT + r.merchant_id, NULL AS merchant_name, NULL AS merchant_type, e.item_id, NULL AS item_name, e.charges, r.merchant_cp AS cost, NULL AS alternate_currency_id, + r.char_cp AS player_money_balance, NULL AS player_currency_balance, r.time AS created_at +FROM qs_merchant_transaction_record AS r + JOIN qs_merchant_transaction_record_entries AS e + ON r.transaction_id = e.event_id; + +-- ================================ +-- Conversion of Player Trades +-- ================================ +INSERT INTO player_event_trade ( + char1_id, char2_id, char1_copper, char1_silver, char1_gold, char1_platinum, + char2_copper, char2_silver, char2_gold, char2_platinum, created_at +) +SELECT + char1_id, char2_id, char1_cp, char1_sp, char1_gp, char1_pp, + char2_cp, char2_sp, char2_gp, char2_pp, time AS created_at +FROM qs_player_trade_record; + +INSERT INTO player_event_trade_entries ( + player_event_trade_id, char_id, slot, item_id, charges, augment_1_id, augment_2_id, augment_3_id, + augment_4_id, augment_5_id, augment_6_id, in_bag, created_at +) +SELECT + event_id, from_id AS char_id, from_slot AS slot, item_id, charges, aug_1 AS augment_1_id, aug_2 AS augment_2_id, aug_3 AS augment_3_id, + aug_4 AS augment_4_id, aug_5 AS augment_5_id, NULL AS augment_6_id, 0 AS in_bag, NULL AS created_at +FROM qs_player_trade_record_entries; + +-- ================================ +-- Conversion of NPC Hand-ins +-- ================================ +INSERT INTO player_event_npc_handin ( + npc_id, npc_name, handin_copper, handin_silver, handin_gold, handin_platinum, + return_copper, return_silver, return_gold, return_platinum, is_quest_handin, created_at +) +SELECT + npc_id, NULL AS npc_name, char_cp AS handin_copper, char_sp AS handin_silver, char_gp AS handin_gold, char_pp AS handin_platinum, + npc_cp AS return_copper, npc_sp AS return_silver, npc_gp AS return_gold, npc_pp AS return_platinum, + 0 AS is_quest_handin, time AS created_at +FROM qs_player_handin_record; + +INSERT INTO player_event_npc_handin_entries ( + player_event_npc_handin_id, type, item_id, charges, evolve_level, evolve_amount, + augment_1_id, augment_2_id, augment_3_id, augment_4_id, augment_5_id, augment_6_id, created_at +) +SELECT + event_id, NULL AS type, item_id, charges, 0 AS evolve_level, 0 AS evolve_amount, + aug_1 AS augment_1_id, aug_2 AS augment_2_id, aug_3 AS augment_3_id, aug_4 AS augment_4_id, aug_5 AS augment_5_id, 0 AS augment_6_id, NULL AS created_at +FROM qs_player_handin_record_entries; + +-- ================================ +-- Conversion of NPC Kill Records +-- ================================ +INSERT INTO player_event_killed_npc ( + npc_id, npc_name, combat_time_seconds, total_damage_per_second_taken, total_heal_per_second_taken, created_at +) +SELECT + npc_id, NULL AS npc_name, NULL AS combat_time_seconds, NULL AS total_damage_per_second_taken, NULL AS total_heal_per_second_taken, time AS created_at +FROM qs_player_npc_kill_record; + +INSERT INTO player_event_killed_named_npc ( + npc_id, npc_name, combat_time_seconds, total_damage_per_second_taken, total_heal_per_second_taken, created_at +) +SELECT + npc_id, NULL AS npc_name, NULL AS combat_time_seconds, NULL AS total_damage_per_second_taken, NULL AS total_heal_per_second_taken, time AS created_at +FROM qs_player_npc_kill_record WHERE type = 1; -- Example condition for named NPCs + +-- ================================ +-- Conversion of Player Speech +-- ================================ +INSERT INTO player_event_speech ( + from_char_id, to_char_id, guild_id, type, min_status, message, created_at +) +SELECT + `from` AS from_char_id, `to` AS to_char_id, guilddbid AS guild_id, type, minstatus AS min_status, message, + timerecorded AS created_at +FROM qs_player_speech; + +-- ================================ +-- Conversion of AA Purchases +-- ================================ +INSERT INTO player_event_aa_purchase ( + aa_ability_id, cost, previous_id, next_id, created_at +) +SELECT + NULL AS aa_ability_id, NULL AS cost, NULL AS previous_id, NULL AS next_id, NULL AS created_at +FROM qs_player_aa_rate_hourly; -- Adjust as necessary for the source table and logic diff --git a/utils/sql/peq-dump/peq-dump.sh b/utils/sql/peq-dump/peq-dump.sh index cdab383ad..453bd4beb 100755 --- a/utils/sql/peq-dump/peq-dump.sh +++ b/utils/sql/peq-dump/peq-dump.sh @@ -33,7 +33,6 @@ bash -c "${world_bin} database:dump --login-tables --drop-table-syntax-only --du bash -c "${world_bin} database:dump --player-tables --drop-table-syntax-only --dump-output-to-console > ${dump_path}drop_tables_player.sql" bash -c "${world_bin} database:dump --system-tables --drop-table-syntax-only --dump-output-to-console > ${dump_path}drop_tables_system.sql" bash -c "${world_bin} database:dump --state-tables --drop-table-syntax-only --dump-output-to-console > ${dump_path}drop_tables_state.sql" -bash -c "${world_bin} database:dump --query-serv-tables --drop-table-syntax-only --dump-output-to-console > ${dump_path}drop_tables_queryserv.sql" ############################################# # generate "create_" table files @@ -45,7 +44,6 @@ bash -c "${world_bin} database:dump --login-tables --table-structure-only --dump bash -c "${world_bin} database:dump --player-tables --table-structure-only --dump-output-to-console | sed 's/ AUTO_INCREMENT=[0-9]*\b//g' > ${dump_path}create_tables_player.sql" bash -c "${world_bin} database:dump --state-tables --table-structure-only --dump-output-to-console | sed 's/ AUTO_INCREMENT=[0-9]*\b//g' > ${dump_path}create_tables_state.sql" bash -c "${world_bin} database:dump --static-instance-data --dump-output-to-console >> ${dump_path}create_tables_state.sql" -bash -c "${world_bin} database:dump --query-serv-tables --table-structure-only --dump-output-to-console | sed 's/ AUTO_INCREMENT=[0-9]*\b//g' > ${dump_path}create_tables_queryserv.sql" # with content bash -c "${world_bin} database:dump --content-tables --dump-output-to-console > ${dump_path}create_tables_content.sql" diff --git a/utils/sql/svn/2268_QueryServ.sql b/utils/sql/svn/2268_QueryServ.sql deleted file mode 100644 index 95b7232ac..000000000 --- a/utils/sql/svn/2268_QueryServ.sql +++ /dev/null @@ -1,22 +0,0 @@ -SET FOREIGN_KEY_CHECKS=0; --- ---------------------------- --- Table structure for `qs_player_npc_kill_record` --- ---------------------------- -DROP TABLE IF EXISTS `qs_player_npc_kill_record`; -CREATE TABLE `qs_player_npc_kill_record` ( - `fight_id` int(11) NOT NULL AUTO_INCREMENT, - `npc_id` int(11) DEFAULT NULL, - `type` int(11) DEFAULT NULL, - `zone_id` int(11) DEFAULT NULL, - `time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`fight_id`) -) ENGINE=InnoDB AUTO_INCREMENT=67 DEFAULT CHARSET=utf8; - --- ---------------------------- --- Table structure for `qs_player_npc_kill_record_entries` --- ---------------------------- -DROP TABLE IF EXISTS `qs_player_npc_kill_record_entries`; -CREATE TABLE `qs_player_npc_kill_record_entries` ( - `event_id` int(11) DEFAULT '0', - `char_id` int(11) DEFAULT '0' -) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/utils/sql/svn/2304_QueryServ.sql b/utils/sql/svn/2304_QueryServ.sql deleted file mode 100644 index 6f2c158c5..000000000 --- a/utils/sql/svn/2304_QueryServ.sql +++ /dev/null @@ -1,41 +0,0 @@ -SET FOREIGN_KEY_CHECKS=0; --- ---------------------------- --- Table structure for `qs_player_npc_kill_record` --- ---------------------------- -DROP TABLE IF EXISTS `qs_player_trade_record`; -CREATE TABLE `qs_player_trade_record` ( - `trade_id` int(11) NOT NULL AUTO_INCREMENT, - `time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, - `char1_id` int(11) DEFAULT '0', - `char1_pp` int(11) DEFAULT '0', - `char1_gp` int(11) DEFAULT '0', - `char1_sp` int(11) DEFAULT '0', - `char1_cp` int(11) DEFAULT '0', - `char1_items` mediumint(7) DEFAULT '0', - `char2_id` int(11) DEFAULT '0', - `char2_pp` int(11) DEFAULT '0', - `char2_gp` int(11) DEFAULT '0', - `char2_sp` int(11) DEFAULT '0', - `char2_cp` int(11) DEFAULT '0', - `char2_items` mediumint(7) DEFAULT '0', - PRIMARY KEY (`trade_id`) -) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; - --- ---------------------------- --- Table structure for `qs_player_npc_kill_record_entries` --- ---------------------------- -DROP TABLE IF EXISTS `qs_player_trade_record_entries`; -CREATE TABLE `qs_player_trade_record_entries` ( - `event_id` int(11) DEFAULT '0', - `from_id` int(11) DEFAULT '0', - `from_slot` mediumint(7) DEFAULT '0', - `to_id` int(11) DEFAULT '0', - `to_slot` mediumint(7) DEFAULT '0', - `item_id` int(11) DEFAULT '0', - `charges` mediumint(7) DEFAULT '0', - `aug_1` int(11) DEFAULT '0', - `aug_2` int(11) DEFAULT '0', - `aug_3` int(11) DEFAULT '0', - `aug_4` int(11) DEFAULT '0', - `aug_5` int(11) DEFAULT '0' -) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/utils/sql/svn/2361_QueryServ.sql b/utils/sql/svn/2361_QueryServ.sql deleted file mode 100644 index faa195a32..000000000 --- a/utils/sql/svn/2361_QueryServ.sql +++ /dev/null @@ -1,141 +0,0 @@ -SET FOREIGN_KEY_CHECKS=0; --- ---------------------------- --- Table structure for `qs_merchant_transaction_record` --- ---------------------------- -DROP TABLE IF EXISTS `qs_merchant_transaction_record`; -CREATE TABLE `qs_merchant_transaction_record` ( - `transaction_id` int(11) NOT NULL AUTO_INCREMENT, - `time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, - `zone_id` int(11) DEFAULT '0', - `merchant_id` int(11) DEFAULT '0', - `merchant_pp` int(11) DEFAULT '0', - `merchant_gp` int(11) DEFAULT '0', - `merchant_sp` int(11) DEFAULT '0', - `merchant_cp` int(11) DEFAULT '0', - `merchant_items` mediumint(7) DEFAULT '0', - `char_id` int(11) DEFAULT '0', - `char_pp` int(11) DEFAULT '0', - `char_gp` int(11) DEFAULT '0', - `char_sp` int(11) DEFAULT '0', - `char_cp` int(11) DEFAULT '0', - `char_items` mediumint(7) DEFAULT '0', - PRIMARY KEY (`transaction_id`) -) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; - --- ---------------------------- --- Table structure for `qs_merchant_transaction_record_entries` --- ---------------------------- -DROP TABLE IF EXISTS `qs_merchant_transaction_record_entries`; -CREATE TABLE `qs_merchant_transaction_record_entries` ( - `event_id` int(11) DEFAULT '0', - `char_slot` mediumint(7) DEFAULT '0', - `item_id` int(11) DEFAULT '0', - `charges` mediumint(7) DEFAULT '0', - `aug_1` int(11) DEFAULT '0', - `aug_2` int(11) DEFAULT '0', - `aug_3` int(11) DEFAULT '0', - `aug_4` int(11) DEFAULT '0', - `aug_5` int(11) DEFAULT '0' -) ENGINE=InnoDB DEFAULT CHARSET=utf8; - --- ---------------------------- --- Table structure for `qs_player_delete_record` --- ---------------------------- -DROP TABLE IF EXISTS `qs_player_delete_record`; -CREATE TABLE `qs_player_delete_record` ( - `delete_id` int(11) NOT NULL AUTO_INCREMENT, - `time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, - `char_id` int(11) DEFAULT '0', - `stack_size` mediumint(7) DEFAULT '0', - `char_items` mediumint(7) DEFAULT '0', - PRIMARY KEY (`delete_id`) -) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; - --- ---------------------------- --- Table structure for `qs_player_delete_record_entries` --- ---------------------------- -DROP TABLE IF EXISTS `qs_player_delete_record_entries`; -CREATE TABLE `qs_player_delete_record_entries` ( - `event_id` int(11) DEFAULT '0', - `char_slot` mediumint(7) DEFAULT '0', - `item_id` int(11) DEFAULT '0', - `charges` mediumint(7) DEFAULT '0', - `aug_1` int(11) DEFAULT '0', - `aug_2` int(11) DEFAULT '0', - `aug_3` int(11) DEFAULT '0', - `aug_4` int(11) DEFAULT '0', - `aug_5` int(11) DEFAULT '0' -) ENGINE=InnoDB DEFAULT CHARSET=utf8; - --- ---------------------------- --- Table structure for `qs_player_handin_record` --- ---------------------------- -DROP TABLE IF EXISTS `qs_player_handin_record`; -CREATE TABLE `qs_player_handin_record` ( - `handin_id` int(11) NOT NULL AUTO_INCREMENT, - `time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, - `quest_id` int(11) DEFAULT '0', - `char_id` int(11) DEFAULT '0', - `char_pp` int(11) DEFAULT '0', - `char_gp` int(11) DEFAULT '0', - `char_sp` int(11) DEFAULT '0', - `char_cp` int(11) DEFAULT '0', - `char_items` mediumint(7) DEFAULT '0', - `npc_id` int(11) DEFAULT '0', - `npc_pp` int(11) DEFAULT '0', - `npc_gp` int(11) DEFAULT '0', - `npc_sp` int(11) DEFAULT '0', - `npc_cp` int(11) DEFAULT '0', - `npc_items` mediumint(7) DEFAULT '0', - PRIMARY KEY (`handin_id`) -) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; - --- ---------------------------- --- Table structure for `qs_player_handin_record_entries` --- ---------------------------- -DROP TABLE IF EXISTS `qs_player_handin_record_entries`; -CREATE TABLE `qs_player_handin_record_entries` ( - `event_id` int(11) DEFAULT '0', - `action_type` char(6) Default 'action', - `char_slot` mediumint(7) DEFAULT '0', - `item_id` int(11) DEFAULT '0', - `charges` mediumint(7) DEFAULT '0', - `aug_1` int(11) DEFAULT '0', - `aug_2` int(11) DEFAULT '0', - `aug_3` int(11) DEFAULT '0', - `aug_4` int(11) DEFAULT '0', - `aug_5` int(11) DEFAULT '0' -) ENGINE=InnoDB DEFAULT CHARSET=utf8; - --- ---------------------------- --- Table structure for `qs_player_move_record` --- ---------------------------- -DROP TABLE IF EXISTS `qs_player_move_record`; -CREATE TABLE `qs_player_move_record` ( - `move_id` int(11) NOT NULL AUTO_INCREMENT, - `time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, - `char_id` int(11) DEFAULT '0', - `from_slot` mediumint(7) DEFAULT '0', - `to_slot` mediumint(7) DEFAULT '0', - `stack_size` mediumint(7) DEFAULT '0', - `char_items` mediumint(7) DEFAULT '0', - `postaction` tinyint(1) DEFAULT '0', - PRIMARY KEY (`move_id`) -) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; - --- ---------------------------- --- Table structure for `qs_player_move_record_entries` --- ---------------------------- -DROP TABLE IF EXISTS `qs_player_move_record_entries`; -CREATE TABLE `qs_player_move_record_entries` ( - `event_id` int(11) DEFAULT '0', - `from_slot` mediumint(7) DEFAULT '0', - `to_slot` mediumint(7) DEFAULT '0', - `item_id` int(11) DEFAULT '0', - `charges` mediumint(7) DEFAULT '0', - `aug_1` int(11) DEFAULT '0', - `aug_2` int(11) DEFAULT '0', - `aug_3` int(11) DEFAULT '0', - `aug_4` int(11) DEFAULT '0', - `aug_5` int(11) DEFAULT '0' -) ENGINE=InnoDB DEFAULT CHARSET=utf8; \ No newline at end of file diff --git a/world/CMakeLists.txt b/world/CMakeLists.txt index 2d4c6ebaa..1963c83ae 100644 --- a/world/CMakeLists.txt +++ b/world/CMakeLists.txt @@ -7,12 +7,11 @@ SET(world_sources cliententry.cpp clientlist.cpp console.cpp + ../common/data_bucket.cpp dynamic_zone.cpp dynamic_zone_manager.cpp eql_config.cpp eqemu_api_world_data_service.cpp - expedition_database.cpp - expedition_message.cpp launcher_link.cpp launcher_list.cpp lfplist.cpp @@ -44,12 +43,11 @@ SET(world_headers cliententry.h clientlist.h console.h + ../common/data_bucket.h dynamic_zone.h dynamic_zone_manager.h eql_config.h eqemu_api_world_data_service.h - expedition_database.h - expedition_message.h launcher_link.h launcher_list.h lfplist.h @@ -79,7 +77,7 @@ ADD_EXECUTABLE(world ${world_sources} ${world_headers}) INSTALL(TARGETS world RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) IF (WIN32 AND EQEMU_BUILD_PCH) - TARGET_PRECOMPILE_HEADERS(world PRIVATE ../common/pch/pch.h) + TARGET_PRECOMPILE_HEADERS(world PRIVATE ../common/pch/std-pch.h) ENDIF () ADD_DEFINITIONS(-DWORLD) diff --git a/world/adventure.cpp b/world/adventure.cpp index ae923b3be..5249f1c47 100644 --- a/world/adventure.cpp +++ b/world/adventure.cpp @@ -15,9 +15,6 @@ #include "../common/zone_store.h" #include "../common/repositories/character_corpses_repository.h" -extern ZSList zoneserver_list; -extern ClientList client_list; -extern AdventureManager adventure_manager; extern EQ::Random emu_random; Adventure::Adventure(AdventureTemplate *t) @@ -177,7 +174,7 @@ void Adventure::SetStatus(AdventureStatus new_status) ut->instance_id = instance_id; ut->new_duration = adventure_template->duration + 60; - zoneserver_list.SendPacket(0, instance_id, pack); + ZSList::Instance()->SendPacket(0, instance_id, pack); safe_delete(pack); } else if(new_status == AS_WaitingForSecondaryEndTime) @@ -191,7 +188,7 @@ void Adventure::SetStatus(AdventureStatus new_status) ut->instance_id = instance_id; ut->new_duration = 1860; - zoneserver_list.SendPacket(0, instance_id, pack); + ZSList::Instance()->SendPacket(0, instance_id, pack); safe_delete(pack); } else if(new_status == AS_Finished) @@ -205,7 +202,7 @@ void Adventure::SetStatus(AdventureStatus new_status) ut->instance_id = instance_id; ut->new_duration = 1860; - zoneserver_list.SendPacket(0, instance_id, pack); + ZSList::Instance()->SendPacket(0, instance_id, pack); safe_delete(pack); } else @@ -216,7 +213,7 @@ void Adventure::SetStatus(AdventureStatus new_status) auto iter = players.begin(); while(iter != players.end()) { - adventure_manager.GetAdventureData((*iter).c_str()); + AdventureManager::Instance()->GetAdventureData((*iter).c_str()); ++iter; } } @@ -230,11 +227,11 @@ void Adventure::SendAdventureMessage(uint32 type, const char *msg) auto iter = players.begin(); while(iter != players.end()) { - ClientListEntry *current = client_list.FindCharacter((*iter).c_str()); + ClientListEntry *current = ClientList::Instance()->FindCharacter((*iter).c_str()); if(current) { strcpy(sms->to, (*iter).c_str()); - zoneserver_list.SendPacket(current->zone(), current->instance(), pack); + ZSList::Instance()->SendPacket(current->zone(), current->instance(), pack); } ++iter; } @@ -283,7 +280,7 @@ void Adventure::Finished(AdventureWinStatus ws) { auto iter = players.begin(); while (iter != players.end()) { - ClientListEntry *current = client_list.FindCharacter((*iter).c_str()); + ClientListEntry *current = ClientList::Instance()->FindCharacter((*iter).c_str()); auto character_id = database.GetCharacterID(*iter); if (character_id == 0) { @@ -310,7 +307,7 @@ void Adventure::Finished(AdventureWinStatus ws) af->win = false; af->points = 0; } - zoneserver_list.SendPacket(current->zone(), current->instance(), pack); + ZSList::Instance()->SendPacket(current->zone(), current->instance(), pack); database.UpdateAdventureStatsEntry(character_id, GetTemplate()->theme, (ws != AWS_Lose) ? true : false); delete pack; } @@ -331,7 +328,7 @@ void Adventure::Finished(AdventureWinStatus ws) afe.win = false; afe.points = 0; } - adventure_manager.AddFinishedEvent(afe); + AdventureManager::Instance()->AddFinishedEvent(afe); database.UpdateAdventureStatsEntry(character_id, GetTemplate()->theme, (ws != AWS_Lose) ? true : false); } } @@ -352,12 +349,12 @@ void Adventure::Finished(AdventureWinStatus ws) afe.win = false; afe.points = 0; } - adventure_manager.AddFinishedEvent(afe); + AdventureManager::Instance()->AddFinishedEvent(afe); database.UpdateAdventureStatsEntry(character_id, GetTemplate()->theme, (ws != AWS_Lose) ? true : false); } ++iter; } - adventure_manager.GetAdventureData(this); + AdventureManager::Instance()->GetAdventureData(this); } void Adventure::MoveCorpsesToGraveyard() @@ -368,8 +365,8 @@ void Adventure::MoveCorpsesToGraveyard() glm::vec4 position; - float x = GetTemplate()->graveyard_x + emu_random.Real(-GetTemplate()->graveyard_radius, GetTemplate()->graveyard_radius); - float y = GetTemplate()->graveyard_y + emu_random.Real(-GetTemplate()->graveyard_radius, GetTemplate()->graveyard_radius); + float x = GetTemplate()->graveyard_x + EQ::Random::Instance()->Real(-GetTemplate()->graveyard_radius, GetTemplate()->graveyard_radius); + float y = GetTemplate()->graveyard_y + EQ::Random::Instance()->Real(-GetTemplate()->graveyard_radius, GetTemplate()->graveyard_radius); float z = GetTemplate()->graveyard_z; position.x = x; @@ -396,7 +393,7 @@ void Adventure::MoveCorpsesToGraveyard() d->InstanceID = 0; d->ZoneID = GetTemplate()->graveyard_zone_id; - zoneserver_list.SendPacket(0, GetInstanceID(), pack); + ZSList::Instance()->SendPacket(0, GetInstanceID(), pack); delete pack; @@ -407,7 +404,7 @@ void Adventure::MoveCorpsesToGraveyard() spc->player_corpse_id = e.id; spc->zone_id = GetTemplate()->graveyard_zone_id; - zoneserver_list.SendPacket(spc->zone_id, 0, pack); + ZSList::Instance()->SendPacket(spc->zone_id, 0, pack); delete pack; } diff --git a/world/adventure_manager.cpp b/world/adventure_manager.cpp index 3d8442e53..3ba718b53 100644 --- a/world/adventure_manager.cpp +++ b/world/adventure_manager.cpp @@ -14,10 +14,6 @@ #include #include -extern ZSList zoneserver_list; -extern ClientList client_list; -extern EQ::Random emu_random; - AdventureManager::AdventureManager() { process_timer = new Timer(500); @@ -68,7 +64,7 @@ void AdventureManager::Process() void AdventureManager::CalculateAdventureRequestReply(const char *data) { ServerAdventureRequest_Struct *sar = (ServerAdventureRequest_Struct*)data; - ClientListEntry *leader = client_list.FindCharacter(sar->leader); + ClientListEntry *leader = ClientList::Instance()->FindCharacter(sar->leader); if(!leader) { return; @@ -84,7 +80,7 @@ void AdventureManager::CalculateAdventureRequestReply(const char *data) ServerAdventureRequestDeny_Struct *deny = (ServerAdventureRequestDeny_Struct*)pack->pBuffer; strcpy(deny->leader, sar->leader); strcpy(deny->reason, "There are currently no adventures set for this theme."); - zoneserver_list.SendPacket(leader->zone(), leader->instance(), pack); + ZSList::Instance()->SendPacket(leader->zone(), leader->instance(), pack); delete pack; return; } @@ -113,7 +109,7 @@ void AdventureManager::CalculateAdventureRequestReply(const char *data) ss << (data + sizeof(ServerAdventureRequest_Struct) + (64 * i)) << " is already apart of an active adventure."; strcpy(deny->reason, ss.str().c_str()); - zoneserver_list.SendPacket(leader->zone(), leader->instance(), pack); + ZSList::Instance()->SendPacket(leader->zone(), leader->instance(), pack); delete pack; return; } @@ -185,7 +181,7 @@ void AdventureManager::CalculateAdventureRequestReply(const char *data) for(int i = 0; i < sar->member_count; ++i) { - ClientListEntry *current = client_list.FindCharacter((data + sizeof(ServerAdventureRequest_Struct) + (64 * i))); + ClientListEntry *current = ClientList::Instance()->FindCharacter((data + sizeof(ServerAdventureRequest_Struct) + (64 * i))); if(current) { int lvl = current->level(); @@ -244,7 +240,7 @@ void AdventureManager::CalculateAdventureRequestReply(const char *data) ServerAdventureRequestDeny_Struct *deny = (ServerAdventureRequestDeny_Struct*)pack->pBuffer; strcpy(deny->leader, sar->leader); strcpy(deny->reason, "The number of found players for this adventure was zero."); - zoneserver_list.SendPacket(leader->zone(), leader->instance(), pack); + ZSList::Instance()->SendPacket(leader->zone(), leader->instance(), pack); delete pack; return; } @@ -261,7 +257,7 @@ void AdventureManager::CalculateAdventureRequestReply(const char *data) ss << "The maximum level range for this adventure is " << RuleI(Adventure, MaxLevelRange); ss << " but the level range calculated was " << (max_level - min_level) << "."; strcpy(deny->reason, ss.str().c_str()); - zoneserver_list.SendPacket(leader->zone(), leader->instance(), pack); + ZSList::Instance()->SendPacket(leader->zone(), leader->instance(), pack); delete pack; return; } @@ -324,7 +320,7 @@ void AdventureManager::CalculateAdventureRequestReply(const char *data) if(eligible_adventures.size() > 0) { ea_iter = eligible_adventures.begin(); - int c_index = emu_random.Int(0, (eligible_adventures.size()-1)); + int c_index = EQ::Random::Instance()->Int(0, (eligible_adventures.size()-1)); for(int i = 0; i < c_index; ++i) { ++ea_iter; @@ -338,7 +334,7 @@ void AdventureManager::CalculateAdventureRequestReply(const char *data) sra->id = (*ea_iter)->id; sra->member_count = sar->member_count; memcpy((pack->pBuffer + sizeof(ServerAdventureRequestAccept_Struct)), (data + sizeof(ServerAdventureRequest_Struct)), (sar->member_count * 64)); - zoneserver_list.SendPacket(leader->zone(), leader->instance(), pack); + ZSList::Instance()->SendPacket(leader->zone(), leader->instance(), pack); delete pack; return; } @@ -348,7 +344,7 @@ void AdventureManager::CalculateAdventureRequestReply(const char *data) ServerAdventureRequestDeny_Struct *deny = (ServerAdventureRequestDeny_Struct*)pack->pBuffer; strcpy(deny->leader, sar->leader); strcpy(deny->reason, "The number of adventures returned was zero."); - zoneserver_list.SendPacket(leader->zone(), leader->instance(), pack); + ZSList::Instance()->SendPacket(leader->zone(), leader->instance(), pack); delete pack; return; } @@ -357,7 +353,7 @@ void AdventureManager::CalculateAdventureRequestReply(const char *data) void AdventureManager::TryAdventureCreate(const char *data) { ServerAdventureRequestCreate_Struct *src = (ServerAdventureRequestCreate_Struct*)data; - ClientListEntry *leader = client_list.FindCharacter(src->leader); + ClientListEntry *leader = ClientList::Instance()->FindCharacter(src->leader); if(!leader) { return; @@ -368,7 +364,7 @@ void AdventureManager::TryAdventureCreate(const char *data) { auto pack = new ServerPacket(ServerOP_AdventureCreateDeny, 64); strcpy((char*)pack->pBuffer, src->leader); - zoneserver_list.SendPacket(leader->zone(), leader->instance(), pack); + ZSList::Instance()->SendPacket(leader->zone(), leader->instance(), pack); delete pack; return; } @@ -378,7 +374,7 @@ void AdventureManager::TryAdventureCreate(const char *data) { auto pack = new ServerPacket(ServerOP_AdventureCreateDeny, 64); strcpy((char*)pack->pBuffer, src->leader); - zoneserver_list.SendPacket(leader->zone(), leader->instance(), pack); + ZSList::Instance()->SendPacket(leader->zone(), leader->instance(), pack); delete pack; delete adv; return; @@ -391,7 +387,7 @@ void AdventureManager::TryAdventureCreate(const char *data) { auto pack = new ServerPacket(ServerOP_AdventureCreateDeny, 64); strcpy((char*)pack->pBuffer, src->leader); - zoneserver_list.SendPacket(leader->zone(), leader->instance(), pack); + ZSList::Instance()->SendPacket(leader->zone(), leader->instance(), pack); delete pack; delete adv; return; @@ -404,7 +400,7 @@ void AdventureManager::TryAdventureCreate(const char *data) for(int i = 0; i < src->member_count; ++i) { - ClientListEntry *player = client_list.FindCharacter((data + sizeof(ServerAdventureRequestCreate_Struct) + (64 * i))); + ClientListEntry *player = ClientList::Instance()->FindCharacter((data + sizeof(ServerAdventureRequestCreate_Struct) + (64 * i))); if(player) { int f_count = 0; @@ -436,7 +432,7 @@ void AdventureManager::TryAdventureCreate(const char *data) sfa->zone_in_object = finished_adventures[f]->GetTemplate()->zone_in_object_id; } - zoneserver_list.SendPacket(player->zone(), player->instance(), pack); + ZSList::Instance()->SendPacket(player->zone(), player->instance(), pack); safe_delete_array(finished_adventures); delete pack; } @@ -458,7 +454,7 @@ void AdventureManager::GetAdventureData(Adventure *adv) void AdventureManager::GetAdventureData(const char *name) { - ClientListEntry *player = client_list.FindCharacter(name); + ClientListEntry *player = ClientList::Instance()->FindCharacter(name); if(player) { int f_count = 0; @@ -497,7 +493,7 @@ void AdventureManager::GetAdventureData(const char *name) delete pack; auto pack = new ServerPacket(ServerOP_AdventureDataClear, 64); strcpy((char*)pack->pBuffer, name); - zoneserver_list.SendPacket(player->zone(), player->instance(), pack); + ZSList::Instance()->SendPacket(player->zone(), player->instance(), pack); delete pack; delete[] finished_adventures; @@ -515,7 +511,7 @@ void AdventureManager::GetAdventureData(const char *name) sfa->zone_in_object = finished_adventures[i]->GetTemplate()->zone_in_object_id; } - zoneserver_list.SendPacket(player->zone(), player->instance(), pack); + ZSList::Instance()->SendPacket(player->zone(), player->instance(), pack); safe_delete_array(finished_adventures); delete pack; delete[] finished_adventures; @@ -764,7 +760,7 @@ void AdventureManager::PlayerClickedDoor(const char *player, int zone_id, int do { if((*iter)->PlayerExists(player)) { - ClientListEntry *pc = client_list.FindCharacter(player); + ClientListEntry *pc = ClientList::Instance()->FindCharacter(player); if(pc) { auto pack = @@ -783,7 +779,7 @@ void AdventureManager::PlayerClickedDoor(const char *player, int zone_id, int do (*iter)->SetStatus(AS_WaitingForPrimaryEndTime); } - zoneserver_list.SendPacket(pc->zone(), pc->instance(), pack); + ZSList::Instance()->SendPacket(pc->zone(), pc->instance(), pack); safe_delete(pack); } return; @@ -792,19 +788,19 @@ void AdventureManager::PlayerClickedDoor(const char *player, int zone_id, int do ++iter; } - ClientListEntry *pc = client_list.FindCharacter(player); + ClientListEntry *pc = ClientList::Instance()->FindCharacter(player); if(pc) { auto pack = new ServerPacket(ServerOP_AdventureClickDoorError, 64); strcpy((char*)pack->pBuffer, player); - zoneserver_list.SendPacket(pc->zone(), pc->instance(), pack); + ZSList::Instance()->SendPacket(pc->zone(), pc->instance(), pack); safe_delete(pack); } } void AdventureManager::LeaveAdventure(const char *name) { - ClientListEntry *pc = client_list.FindCharacter(name); + ClientListEntry *pc = ClientList::Instance()->FindCharacter(name); if(pc) { Adventure *current = GetActiveAdventure(name); @@ -814,7 +810,7 @@ void AdventureManager::LeaveAdventure(const char *name) { auto pack = new ServerPacket(ServerOP_AdventureLeaveDeny, 64); strcpy((char*)pack->pBuffer, name); - zoneserver_list.SendPacket(pc->zone(), pc->instance(), pack); + ZSList::Instance()->SendPacket(pc->zone(), pc->instance(), pack); safe_delete(pack); } else @@ -827,7 +823,7 @@ void AdventureManager::LeaveAdventure(const char *name) current->RemovePlayer(name); auto pack = new ServerPacket(ServerOP_AdventureLeaveReply, 64); strcpy((char*)pack->pBuffer, name); - zoneserver_list.SendPacket(pc->zone(), pc->instance(), pack); + ZSList::Instance()->SendPacket(pc->zone(), pc->instance(), pack); safe_delete(pack); } } @@ -835,7 +831,7 @@ void AdventureManager::LeaveAdventure(const char *name) { auto pack = new ServerPacket(ServerOP_AdventureLeaveReply, 64); strcpy((char*)pack->pBuffer, name); - zoneserver_list.SendPacket(pc->zone(), pc->instance(), pack); + ZSList::Instance()->SendPacket(pc->zone(), pc->instance(), pack); safe_delete(pack); } } @@ -867,12 +863,12 @@ void AdventureManager::IncrementCount(uint16 instance_id) while(siter != slist.end()) { - ClientListEntry *pc = client_list.FindCharacter((*siter).c_str()); + ClientListEntry *pc = ClientList::Instance()->FindCharacter((*siter).c_str()); if(pc) { memset(ac->player, 0, 64); strcpy(ac->player, (*siter).c_str()); - zoneserver_list.SendPacket(pc->zone(), pc->instance(), pack); + ZSList::Instance()->SendPacket(pc->zone(), pc->instance(), pack); } ++siter; } @@ -936,7 +932,7 @@ void AdventureManager::GetZoneData(uint16 instance_id) zd->dest_y = temp->dest_y; zd->dest_z = temp->dest_z; zd->dest_h = temp->dest_h; - zoneserver_list.SendPacket(0, instance_id, pack); + ZSList::Instance()->SendPacket(0, instance_id, pack); delete pack; } } @@ -1262,7 +1258,7 @@ void AdventureManager::DoLeaderboardRequest(const char* player, uint8 type) void AdventureManager::DoLeaderboardRequestWins(const char* player) { - ClientListEntry *pc = client_list.FindCharacter(player); + ClientListEntry *pc = ClientList::Instance()->FindCharacter(player); if(pc) { auto pack = new ServerPacket(ServerOP_AdventureLeaderboard, 64 + sizeof(AdventureLeaderboard_Struct)); @@ -1321,14 +1317,14 @@ void AdventureManager::DoLeaderboardRequestWins(const char* player) al->failure = our_failures; } - zoneserver_list.SendPacket(pc->zone(), pc->instance(), pack); + ZSList::Instance()->SendPacket(pc->zone(), pc->instance(), pack); delete pack; } } void AdventureManager::DoLeaderboardRequestPercentage(const char* player) { - ClientListEntry *pc = client_list.FindCharacter(player); + ClientListEntry *pc = ClientList::Instance()->FindCharacter(player); if(pc) { auto pack = new ServerPacket(ServerOP_AdventureLeaderboard, 64 + sizeof(AdventureLeaderboard_Struct)); @@ -1387,14 +1383,14 @@ void AdventureManager::DoLeaderboardRequestPercentage(const char* player) al->failure = our_failures; } - zoneserver_list.SendPacket(pc->zone(), pc->instance(), pack); + ZSList::Instance()->SendPacket(pc->zone(), pc->instance(), pack); delete pack; } } void AdventureManager::DoLeaderboardRequestWinsGuk(const char* player) { - ClientListEntry *pc = client_list.FindCharacter(player); + ClientListEntry *pc = ClientList::Instance()->FindCharacter(player); if(pc) { auto pack = new ServerPacket(ServerOP_AdventureLeaderboard, 64 + sizeof(AdventureLeaderboard_Struct)); @@ -1453,14 +1449,14 @@ void AdventureManager::DoLeaderboardRequestWinsGuk(const char* player) al->failure = our_failures; } - zoneserver_list.SendPacket(pc->zone(), pc->instance(), pack); + ZSList::Instance()->SendPacket(pc->zone(), pc->instance(), pack); delete pack; } } void AdventureManager::DoLeaderboardRequestPercentageGuk(const char* player) { - ClientListEntry *pc = client_list.FindCharacter(player); + ClientListEntry *pc = ClientList::Instance()->FindCharacter(player); if(pc) { auto pack = new ServerPacket(ServerOP_AdventureLeaderboard, 64 + sizeof(AdventureLeaderboard_Struct)); @@ -1519,14 +1515,14 @@ void AdventureManager::DoLeaderboardRequestPercentageGuk(const char* player) al->failure = our_failures; } - zoneserver_list.SendPacket(pc->zone(), pc->instance(), pack); + ZSList::Instance()->SendPacket(pc->zone(), pc->instance(), pack); delete pack; } } void AdventureManager::DoLeaderboardRequestWinsMir(const char* player) { - ClientListEntry *pc = client_list.FindCharacter(player); + ClientListEntry *pc = ClientList::Instance()->FindCharacter(player); if(pc) { auto pack = new ServerPacket(ServerOP_AdventureLeaderboard, 64 + sizeof(AdventureLeaderboard_Struct)); @@ -1585,14 +1581,14 @@ void AdventureManager::DoLeaderboardRequestWinsMir(const char* player) al->failure = our_failures; } - zoneserver_list.SendPacket(pc->zone(), pc->instance(), pack); + ZSList::Instance()->SendPacket(pc->zone(), pc->instance(), pack); delete pack; } } void AdventureManager::DoLeaderboardRequestPercentageMir(const char* player) { - ClientListEntry *pc = client_list.FindCharacter(player); + ClientListEntry *pc = ClientList::Instance()->FindCharacter(player); if(pc) { auto pack = new ServerPacket(ServerOP_AdventureLeaderboard, 64 + sizeof(AdventureLeaderboard_Struct)); @@ -1651,14 +1647,14 @@ void AdventureManager::DoLeaderboardRequestPercentageMir(const char* player) al->failure = our_failures; } - zoneserver_list.SendPacket(pc->zone(), pc->instance(), pack); + ZSList::Instance()->SendPacket(pc->zone(), pc->instance(), pack); delete pack; } } void AdventureManager::DoLeaderboardRequestWinsMmc(const char* player) { - ClientListEntry *pc = client_list.FindCharacter(player); + ClientListEntry *pc = ClientList::Instance()->FindCharacter(player); if(pc) { auto pack = new ServerPacket(ServerOP_AdventureLeaderboard, 64 + sizeof(AdventureLeaderboard_Struct)); @@ -1717,14 +1713,14 @@ void AdventureManager::DoLeaderboardRequestWinsMmc(const char* player) al->failure = our_failures; } - zoneserver_list.SendPacket(pc->zone(), pc->instance(), pack); + ZSList::Instance()->SendPacket(pc->zone(), pc->instance(), pack); delete pack; } } void AdventureManager::DoLeaderboardRequestPercentageMmc(const char* player) { - ClientListEntry *pc = client_list.FindCharacter(player); + ClientListEntry *pc = ClientList::Instance()->FindCharacter(player); if(pc) { auto pack = new ServerPacket(ServerOP_AdventureLeaderboard, 64 + sizeof(AdventureLeaderboard_Struct)); @@ -1783,14 +1779,14 @@ void AdventureManager::DoLeaderboardRequestPercentageMmc(const char* player) al->failure = our_failures; } - zoneserver_list.SendPacket(pc->zone(), pc->instance(), pack); + ZSList::Instance()->SendPacket(pc->zone(), pc->instance(), pack); delete pack; } } void AdventureManager::DoLeaderboardRequestWinsRuj(const char* player) { - ClientListEntry *pc = client_list.FindCharacter(player); + ClientListEntry *pc = ClientList::Instance()->FindCharacter(player); if(pc) { auto pack = new ServerPacket(ServerOP_AdventureLeaderboard, 64 + sizeof(AdventureLeaderboard_Struct)); @@ -1849,14 +1845,14 @@ void AdventureManager::DoLeaderboardRequestWinsRuj(const char* player) al->failure = our_failures; } - zoneserver_list.SendPacket(pc->zone(), pc->instance(), pack); + ZSList::Instance()->SendPacket(pc->zone(), pc->instance(), pack); delete pack; } } void AdventureManager::DoLeaderboardRequestPercentageRuj(const char* player) { - ClientListEntry *pc = client_list.FindCharacter(player); + ClientListEntry *pc = ClientList::Instance()->FindCharacter(player); if(pc) { auto pack = new ServerPacket(ServerOP_AdventureLeaderboard, 64 + sizeof(AdventureLeaderboard_Struct)); @@ -1915,14 +1911,14 @@ void AdventureManager::DoLeaderboardRequestPercentageRuj(const char* player) al->failure = our_failures; } - zoneserver_list.SendPacket(pc->zone(), pc->instance(), pack); + ZSList::Instance()->SendPacket(pc->zone(), pc->instance(), pack); delete pack; } } void AdventureManager::DoLeaderboardRequestWinsTak(const char* player) { - ClientListEntry *pc = client_list.FindCharacter(player); + ClientListEntry *pc = ClientList::Instance()->FindCharacter(player); if(pc) { auto pack = new ServerPacket(ServerOP_AdventureLeaderboard, 64 + sizeof(AdventureLeaderboard_Struct)); @@ -1981,14 +1977,14 @@ void AdventureManager::DoLeaderboardRequestWinsTak(const char* player) al->failure = our_failures; } - zoneserver_list.SendPacket(pc->zone(), pc->instance(), pack); + ZSList::Instance()->SendPacket(pc->zone(), pc->instance(), pack); delete pack; } } void AdventureManager::DoLeaderboardRequestPercentageTak(const char* player) { - ClientListEntry *pc = client_list.FindCharacter(player); + ClientListEntry *pc = ClientList::Instance()->FindCharacter(player); if(pc) { auto pack = new ServerPacket(ServerOP_AdventureLeaderboard, 64 + sizeof(AdventureLeaderboard_Struct)); @@ -2047,7 +2043,7 @@ void AdventureManager::DoLeaderboardRequestPercentageTak(const char* player) al->failure = our_failures; } - zoneserver_list.SendPacket(pc->zone(), pc->instance(), pack); + ZSList::Instance()->SendPacket(pc->zone(), pc->instance(), pack); delete pack; } } @@ -2074,7 +2070,7 @@ bool AdventureManager::PopFinishedEvent(const char *name, AdventureFinishEvent & void AdventureManager::SendAdventureFinish(AdventureFinishEvent fe) { - ClientListEntry *pc = client_list.FindCharacter(fe.name.c_str()); + ClientListEntry *pc = ClientList::Instance()->FindCharacter(fe.name.c_str()); if(pc) { auto pack = new ServerPacket(ServerOP_AdventureFinish, sizeof(ServerAdventureFinish_Struct)); @@ -2084,7 +2080,7 @@ void AdventureManager::SendAdventureFinish(AdventureFinishEvent fe) af->win = fe.win; af->points = fe.points; - zoneserver_list.SendPacket(pc->zone(), pc->instance(), pack); + ZSList::Instance()->SendPacket(pc->zone(), pc->instance(), pack); delete pack; } } diff --git a/world/adventure_manager.h b/world/adventure_manager.h index ae4bf950f..9f84ce838 100644 --- a/world/adventure_manager.h +++ b/world/adventure_manager.h @@ -40,6 +40,12 @@ public: AdventureTemplate *GetAdventureTemplate(int theme, int id); AdventureTemplate *GetAdventureTemplate(int id); void GetZoneData(uint16 instance_id); + + static AdventureManager* Instance() + { + static AdventureManager instance; + return &instance; + } protected: bool IsInExcludedZoneList(std::list excluded_zones, std::string zone_name, int version); bool IsInExcludedZoneInList(std::list excluded_zone_ins, int zone_id, int door_object); diff --git a/world/cli/copy_character.cpp b/world/cli/copy_character.cpp index 7d915ad15..ebe8e1831 100644 --- a/world/cli/copy_character.cpp +++ b/world/cli/copy_character.cpp @@ -11,7 +11,7 @@ void WorldserverCLI::CopyCharacter(int argc, char **argv, argh::parser &cmd, std "destination_account_name" }; std::vector options = {}; - + if (cmd[{"-h", "--help"}]) { return; } diff --git a/world/cli/database_dump.cpp b/world/cli/database_dump.cpp index 046539688..67a591fb0 100644 --- a/world/cli/database_dump.cpp +++ b/world/cli/database_dump.cpp @@ -14,7 +14,6 @@ void WorldserverCLI::DatabaseDump(int argc, char **argv, argh::parser &cmd, std: "--merc-tables", "--state-tables", "--system-tables", - "--query-serv-tables", "--static-instance-data", "--table-structure-only", "--table-lock", @@ -24,12 +23,12 @@ void WorldserverCLI::DatabaseDump(int argc, char **argv, argh::parser &cmd, std: "--compress" }; - EQEmuCommand::ValidateCmdInput(arguments, options, cmd, argc, argv); - if (cmd[{"-h", "--help"}]) { return; } + EQEmuCommand::ValidateCmdInput(arguments, options, cmd, argc, argv); + auto s = new DatabaseDumpService(); bool dump_all = cmd[{"-a", "--all"}]; @@ -45,7 +44,6 @@ void WorldserverCLI::DatabaseDump(int argc, char **argv, argh::parser &cmd, std: s->SetDumpMercTables(cmd[{"--merc-tables"}] || dump_all); s->SetDumpStateTables(cmd[{"--state-tables"}] || dump_all); s->SetDumpSystemTables(cmd[{"--system-tables"}] || dump_all); - s->SetDumpQueryServerTables(cmd[{"--query-serv-tables"}] || dump_all); s->SetDumpAllTables(dump_all); s->SetDumpWithNoData(cmd[{"--table-structure-only"}]); s->SetDumpTableLock(cmd[{"--table-lock"}]); diff --git a/world/cli/database_updates.cpp b/world/cli/database_updates.cpp index f85316de5..f967349fa 100644 --- a/world/cli/database_updates.cpp +++ b/world/cli/database_updates.cpp @@ -4,12 +4,17 @@ void WorldserverCLI::DatabaseUpdates(int argc, char **argv, argh::parser &cmd, s { description = "Runs database updates manually"; + std::vector options = { + "--skip-backup", + }; + if (cmd[{"-h", "--help"}]) { return; } DatabaseUpdate update; update.SetDatabase(&database) + ->SetSkipBackup(cmd[{"--skip-backup"}] ) ->SetContentDatabase(&content_db) ->CheckDbUpdates(); } diff --git a/world/cli/database_version.cpp b/world/cli/database_version.cpp index e3130b9dc..2426d9d72 100644 --- a/world/cli/database_version.cpp +++ b/world/cli/database_version.cpp @@ -12,8 +12,9 @@ void WorldserverCLI::DatabaseVersion(int argc, char **argv, argh::parser &cmd, s Json::Value v; - v["database_version"] = CURRENT_BINARY_DATABASE_VERSION; - v["bots_database_version"] = RuleB(Bots, Enabled) ? CURRENT_BINARY_BOTS_DATABASE_VERSION : 0; + v["database_version"] = CURRENT_BINARY_DATABASE_VERSION; + v["bots_database_version"] = RuleB(Bots, Enabled) ? CURRENT_BINARY_BOTS_DATABASE_VERSION : 0; + v["custom_database_version"] = CUSTOM_BINARY_DATABASE_VERSION; std::stringstream payload; payload << v; diff --git a/world/cli/etl_get_settings.cpp b/world/cli/etl_get_settings.cpp new file mode 100644 index 000000000..eb1d873ac --- /dev/null +++ b/world/cli/etl_get_settings.cpp @@ -0,0 +1,41 @@ +#include "../../common/events/player_event_logs.h" +#include "../../common/json/json.h" + +void WorldserverCLI::EtlGetSettings(int argc, char **argv, argh::parser &cmd, std::string &description) +{ + description = "Displays server player event logs that are etl enabled"; + + if (cmd[{"-h", "--help"}]) { + return; + } + + Json::Value etl_settings; + Json::Value player_events; + + PlayerEventLogs::Instance()->SetDatabase(&database)->Init(); + auto event_settings = PlayerEventLogs::Instance()->GetSettings(); + auto etl_details = PlayerEventLogs::Instance()->GetEtlSettings(); + + for (int i = PlayerEvent::GM_COMMAND; i < PlayerEvent::EventType::MAX; i++) { + player_events["event_id"] = event_settings[i].id; + player_events["enabled"] = event_settings[i].event_enabled ? true : false; + player_events["retention"] = event_settings[i].retention_days; + player_events["discord_id"] = event_settings[i].discord_webhook_id; + player_events["etl_enabled"] = event_settings[i].etl_enabled ? true : false; + player_events["table_name"] = ""; + + auto it = etl_details.find(static_cast(event_settings[i].id)); + + if (it != std::end(etl_details)) { + player_events["table_name"] = it->second.table_name; + player_events["etl_enabled"] = it->second.enabled; + } + + etl_settings["etl_settings"].append(player_events); + } + + std::stringstream payload; + payload << etl_settings; + + std::cout << payload.str() << std::endl; +} diff --git a/world/cli/test.cpp b/world/cli/test.cpp index 738741723..172d46d21 100644 --- a/world/cli/test.cpp +++ b/world/cli/test.cpp @@ -1,6 +1,8 @@ #include #include +#include #include "../../common/events/player_events.h" +#include "../../common/memory/ksm.hpp" void WorldserverCLI::TestCommand(int argc, char **argv, argh::parser &cmd, std::string &description) { @@ -10,5 +12,21 @@ void WorldserverCLI::TestCommand(int argc, char **argv, argh::parser &cmd, std:: return; } - + void* start_marker = KSM::MarkHeapStart(); + std::cout << "Start marker: " << start_marker << "\n"; + + std::vector vec = {}; + for (int i = 0; i < 100000; i++) { + vec.push_back("Some random string"); + } + + // Measure allocated memory size + size_t allocated_size = KSM::MeasureHeapUsage(start_marker); + // Convert to MB as a float and output with precision + double allocated_size_mb = static_cast(allocated_size) / (1024 * 1024); + std::cout << std::fixed << std::setprecision(3) + << "Allocated size: " << allocated_size_mb << " MB\n"; + + // Mark memory for KSM + KSM::MarkMemoryForKSM(start_marker, allocated_size); } diff --git a/world/cli/test_expansion.cpp b/world/cli/test_expansion.cpp index f8028b2da..856e053d8 100644 --- a/world/cli/test_expansion.cpp +++ b/world/cli/test_expansion.cpp @@ -16,7 +16,7 @@ void WorldserverCLI::ExpansionTestCommand(int argc, char **argv, argh::parser &c LogInfo("No rule set configured, using default rules"); } - content_service.SetCurrentExpansion(RuleI(Expansion, CurrentExpansion)); + WorldContentService::Instance()->SetCurrentExpansion(RuleI(Expansion, CurrentExpansion)); std::vector flags = {}; auto f = ContentFlagsRepository::NewEntity(); @@ -32,13 +32,13 @@ void WorldserverCLI::ExpansionTestCommand(int argc, char **argv, argh::parser &c flags.push_back(f); } - content_service.SetContentFlags(flags); + WorldContentService::Instance()->SetContentFlags(flags); LogInfo( "Current expansion is [{}] ({}) is Velious Enabled [{}] Criteria [{}]", - content_service.GetCurrentExpansion(), - content_service.GetCurrentExpansionName(), - content_service.IsTheScarsOfVeliousEnabled() ? "true" : "false", + WorldContentService::Instance()->GetCurrentExpansion(), + WorldContentService::Instance()->GetCurrentExpansionName(), + WorldContentService::Instance()->IsTheScarsOfVeliousEnabled() ? "true" : "false", ContentFilterCriteria::apply() ); } diff --git a/world/cli/version.cpp b/world/cli/version.cpp index d03fd5e26..383b69ac7 100644 --- a/world/cli/version.cpp +++ b/world/cli/version.cpp @@ -11,11 +11,12 @@ void WorldserverCLI::Version(int argc, char **argv, argh::parser &cmd, std::stri Json::Value j; - j["bots_database_version"] = CURRENT_BINARY_BOTS_DATABASE_VERSION; - j["compile_date"] = COMPILE_DATE; - j["compile_time"] = COMPILE_TIME; - j["database_version"] = CURRENT_BINARY_DATABASE_VERSION; - j["server_version"] = CURRENT_VERSION; + j["bots_database_version"] = CURRENT_BINARY_BOTS_DATABASE_VERSION; + j["compile_date"] = COMPILE_DATE; + j["compile_time"] = COMPILE_TIME; + j["custom_database_version"] = CUSTOM_BINARY_DATABASE_VERSION; + j["database_version"] = CURRENT_BINARY_DATABASE_VERSION; + j["server_version"] = CURRENT_VERSION; std::stringstream payload; payload << j; diff --git a/world/client.cpp b/world/client.cpp index a6f5cfb5f..a2d35a485 100644 --- a/world/client.cpp +++ b/world/client.cpp @@ -36,6 +36,7 @@ #include "../common/shareddb.h" #include "../common/opcodemgr.h" #include "../common/data_verification.h" +#include "../common/data_bucket.h" #include "client.h" #include "worlddb.h" @@ -88,10 +89,6 @@ std::vector character_create_allocations; std::vector character_create_race_class_combos; -extern ZSList zoneserver_list; -extern LoginServerList loginserverlist; -extern ClientList client_list; -extern EQ::Random emu_random; extern uint32 numclients; extern volatile bool RunLoops; extern volatile bool UCSServerAvailable_; @@ -135,6 +132,8 @@ Client::Client(EQStreamInterface* ieqs) } Client::~Client() { + ClearDataBucketsCache(); + if (RunLoops && cle && zone_id == 0) cle->SetOnline(CLE_Status::Offline); @@ -447,48 +446,50 @@ void Client::SendPostEnterWorld() { bool Client::HandleSendLoginInfoPacket(const EQApplicationPacket *app) { - if (app->size != sizeof(LoginInfo_Struct)) { + if (app->size != sizeof(LoginInfo)) { return false; } - auto *login_info = (LoginInfo_Struct *) app->pBuffer; + auto *r = (LoginInfo *) app->pBuffer; // Quagmire - max len for name is 18, pass 15 char name[19] = {0}; char password[16] = {0}; - strn0cpy(name, (char *) login_info->login_info, 18); - strn0cpy(password, (char *) &(login_info->login_info[strlen(name) + 1]), 15); + strn0cpy(name, (char *) r->login_info, 18); + strn0cpy(password, (char *) &(r->login_info[strlen(name) + 1]), 15); - LogDebug("Receiving Login Info Packet from Client | name [{0}] password [{1}]", name, password); + LogDebug("Receiving login info packet from client | name [{}] password [{}]", name, password); if (strlen(password) <= 1) { LogInfo("Login without a password"); return false; } - is_player_zoning = (login_info->zoning == 1); + is_player_zoning = (r->zoning == 1); uint32 id = Strings::ToInt(name); if (id == 0) { - LogWarning("Receiving Login Info Packet from Client | account_id is 0 - disconnecting"); + LogWarning("Receiving login info packet from client | account_id is 0 - disconnecting"); return false; } LogClientLogin("Checking authentication id [{}]", id); - if ((cle = client_list.CheckAuth(id, password))) { + if ((cle = ClientList::Instance()->CheckAuth(id, password))) { + LoadDataBucketsCache(); + LogClientLogin("Checking authentication id [{}] passed", id); if (!is_player_zoning) { // Track who is in and who is out of the game - char *inout= (char *) ""; + std::string in_out; - if (cle->GetOnline() == CLE_Status::Never){ + if (cle->GetOnline() == CLE_Status::Never) { // Desktop -> Char Select - inout = (char *) "In"; + in_out = "in"; } else { // Game -> Char Select - inout=(char *) "Out"; + in_out = "out"; } // Always at Char select at this point. @@ -497,7 +498,7 @@ bool Client::HandleSendLoginInfoPacket(const EQApplicationPacket *app) // Could use a Logging Out Completely message somewhere. cle->SetOnline(CLE_Status::CharSelect); - LogInfo("Account ({}) Logging({}) to character select :: LSID [{}] ", cle->AccountName(), inout, cle->LSID()); + LogInfo("Account ({}) Logging ({}) to character select :: LSID [{}] ", cle->AccountName(), in_out, cle->LSID()); } else { cle->SetOnline(); @@ -514,7 +515,7 @@ bool Client::HandleSendLoginInfoPacket(const EQApplicationPacket *app) ServerLSPlayerJoinWorld_Struct* join =(ServerLSPlayerJoinWorld_Struct*)pack->pBuffer; strcpy(join->key,GetLSKey()); join->lsaccount_id = GetLSID(); - loginserverlist.SendPacket(pack); + LoginServerList::Instance()->SendPacket(pack); safe_delete(pack); } @@ -541,6 +542,17 @@ bool Client::HandleSendLoginInfoPacket(const EQApplicationPacket *app) skip_char_info = true; } } + const auto& custom_files_key = RuleS(World, CustomFilesKey); + if (!skip_char_info && !custom_files_key.empty() && cle->Admin() < RuleI(World, CustomFilesAdminLevel)) { + // Modified clients can utilize this unused block in login_info to send custom payloads on login + // which indicates they are using custom client files with the correct version, based on key payload. + const auto client_key = std::string(reinterpret_cast(r->unknown064)); + if (custom_files_key != client_key) { + std::string message = fmt::format("Missing Files [{}]", RuleS(World, CustomFilesUrl) ); + SendUnsupportedClientPacket(message); + skip_char_info = true; + } + } if (!skip_char_info) { SendExpansionInfo(); @@ -786,7 +798,7 @@ bool Client::HandleEnterWorldPacket(const EQApplicationPacket *app) { RuleB(World, EnableIPExemptions) || RuleI(World, MaxClientsPerIP) > 0 ) { - client_list.GetCLEIP(GetIP()); //Check current CLE Entry IPs against incoming connection + ClientList::Instance()->GetCLEIP(GetIP()); //Check current CLE Entry IPs against incoming connection } auto ew = (EnterWorld_Struct *) app->pBuffer; @@ -806,7 +818,7 @@ bool Client::HandleEnterWorldPacket(const EQApplicationPacket *app) { return true; } - auto r = content_service.FindZone(zone_id, instance_id); + auto r = WorldContentService::Instance()->FindZone(zone_id, instance_id); if (r.zone_id && r.instance.id != instance_id) { LogInfo( "Zone [{}] has been remapped to instance_id [{}] from instance_id [{}] for client [{}]", @@ -973,7 +985,7 @@ bool Client::HandleEnterWorldPacket(const EQApplicationPacket *app) { safe_delete(outapp); // set mailkey - used for duration of character session - int mail_key = emu_random.Int(1, INT_MAX); + uint32 mail_key = EQ::Random::Instance()->Int(1, INT_MAX); database.SetMailKey(charid, GetIP(), mail_key); if (UCSServerAvailable_) { @@ -1086,7 +1098,7 @@ bool Client::HandlePacket(const EQApplicationPacket *app) { OpcodeManager::EmuToName(app->GetOpcode()), o->EmuToEQ(app->GetOpcode()) == 0 ? app->GetProtocolOpcode() : o->EmuToEQ(app->GetOpcode()), app->Size(), - (LogSys.IsLogEnabled(Logs::Detail, Logs::PacketClientServer) ? DumpPacketToString(app) : "") + (EQEmuLogSys::Instance()->IsLogEnabled(Logs::Detail, Logs::PacketClientServer) ? DumpPacketToString(app) : "") ); if (!eqs->CheckState(ESTABLISHED)) { @@ -1235,7 +1247,7 @@ bool Client::Process() { ServerLSPlayerLeftWorld_Struct* logout =(ServerLSPlayerLeftWorld_Struct*)pack->pBuffer; strcpy(logout->key,GetLSKey()); logout->lsaccount_id = GetLSID(); - loginserverlist.SendPacket(pack); + LoginServerList::Instance()->SendPacket(pack); safe_delete(pack); } LogInfo("Client disconnected (not active in process)"); @@ -1401,18 +1413,18 @@ void Client::EnterWorld(bool TryBootup) { return; } - zone_server = zoneserver_list.FindByInstanceID(instance_id); + zone_server = ZSList::Instance()->FindByInstanceID(instance_id); } else { - zone_server = zoneserver_list.FindByZoneID(zone_id); + zone_server = ZSList::Instance()->FindByZoneID(zone_id); } const char *zone_name = ZoneName(zone_id, true); if (zone_server) { if (false == enter_world_triggered) { //Drop any clients we own in other zones. - zoneserver_list.DropClient(GetLSID(), zone_server); + ZSList::Instance()->DropClient(GetLSID(), zone_server); // warn the zone we're coming zone_server->IncomingClient(this); @@ -1423,9 +1435,9 @@ void Client::EnterWorld(bool TryBootup) { } else { if (TryBootup) { - LogInfo("Attempting autobootup of [{}] ([{}]:[{}])", zone_name, zone_id, instance_id); + LogInfo("Attempting autobootup of [{}] [{}] [{}]", zone_name, zone_id, instance_id); autobootup_timeout.Start(); - zone_waiting_for_bootup = zoneserver_list.TriggerBootup(zone_id, instance_id); + zone_waiting_for_bootup = ZSList::Instance()->TriggerBootup(zone_id, instance_id); if (zone_waiting_for_bootup == 0) { LogInfo("No zoneserver available to boot up"); TellClientZoneUnavailable(); @@ -1441,7 +1453,7 @@ void Client::EnterWorld(bool TryBootup) { zone_waiting_for_bootup = 0; - if (GetAdmin() < 80 && zoneserver_list.IsZoneLocked(zone_id)) { + if (GetAdmin() < 80 && ZSList::Instance()->IsZoneLocked(zone_id)) { LogInfo("Enter world failed. Zone is locked"); TellClientZoneUnavailable(); return; @@ -1487,11 +1499,11 @@ void Client::Clearance(int8 response) ZoneServer* zs = nullptr; if(instance_id > 0) { - zs = zoneserver_list.FindByInstanceID(instance_id); + zs = ZSList::Instance()->FindByInstanceID(instance_id); } else { - zs = zoneserver_list.FindByZoneID(zone_id); + zs = ZSList::Instance()->FindByZoneID(zone_id); } if(zs == 0 || response == -1 || response == 0) @@ -2064,10 +2076,10 @@ bool CheckCharCreateInfoTitanium(CharCreate_Struct *cc) classtemp = cc->class_ - 1; racetemp = cc->race - 1; // these have non sequential race numbers so they need to be mapped - if (cc->race == FROGLOK) racetemp = 14; - if (cc->race == VAHSHIR) racetemp = 13; - if (cc->race == IKSAR) racetemp = 12; - if (cc->race == DRAKKIN) racetemp = 15; + if (cc->race == Race::Froglok2) racetemp = 14; + if (cc->race == Race::VahShir) racetemp = 13; + if (cc->race == Race::Iksar) racetemp = 12; + if (cc->race == Race::Drakkin) racetemp = 15; // if out of range looking it up in the table would crash stuff // so we return from these @@ -2160,7 +2172,7 @@ void Client::SetClassStartingSkills(PlayerProfile_Struct *pp) i == EQ::skills::SkillAlcoholTolerance || i == EQ::skills::SkillBindWound) continue; - pp->skills[i] = skill_caps.GetSkillCap(pp->class_, (EQ::skills::SkillType)i, 1).cap; + pp->skills[i] = SkillCaps::Instance()->GetSkillCap(pp->class_, (EQ::skills::SkillType)i, 1).cap; } } @@ -2174,43 +2186,43 @@ void Client::SetRaceStartingSkills( PlayerProfile_Struct *pp ) { switch( pp->race ) { - case BARBARIAN: - case DWARF: - case ERUDITE: - case HALF_ELF: - case HIGH_ELF: - case HUMAN: - case OGRE: - case TROLL: - case DRAKKIN: //Drakkin are supposed to get a starting AA Skill + case Race::Barbarian: + case Race::Dwarf: + case Race::Erudite: + case Race::HalfElf: + case Race::HighElf: + case Race::Human: + case Race::Ogre: + case Race::Troll: + case Race::Drakkin: //Drakkin are supposed to get a starting AA Skill { // No Race Specific Skills break; } - case DARK_ELF: + case Race::DarkElf: { pp->skills[EQ::skills::SkillHide] = 50; break; } - case FROGLOK: + case Race::Froglok2: { if (RuleI(Skills, SwimmingStartValue) < 125) { pp->skills[EQ::skills::SkillSwimming] = 125; } break; } - case GNOME: + case Race::Gnome: { pp->skills[EQ::skills::SkillTinkering] = 50; break; } - case HALFLING: + case Race::Halfling: { pp->skills[EQ::skills::SkillHide] = 50; pp->skills[EQ::skills::SkillSneak] = 50; break; } - case IKSAR: + case Race::Iksar: { pp->skills[EQ::skills::SkillForage] = 50; if (RuleI(Skills, SwimmingStartValue) < 100) { @@ -2218,13 +2230,13 @@ void Client::SetRaceStartingSkills( PlayerProfile_Struct *pp ) } break; } - case WOOD_ELF: + case Race::WoodElf: { pp->skills[EQ::skills::SkillForage] = 50; pp->skills[EQ::skills::SkillHide] = 50; break; } - case VAHSHIR: + case Race::VahShir: { pp->skills[EQ::skills::SkillSafeFall] = 50; pp->skills[EQ::skills::SkillSneak] = 50; @@ -2358,7 +2370,7 @@ bool Client::StoreCharacter( return false; } - const std::string& zone_name = zone_store.GetZoneName(p_player_profile_struct->zone_id, true); + const std::string& zone_name = ZoneStore::Instance()->GetZoneName(p_player_profile_struct->zone_id, true); if (Strings::EqualFold(zone_name, "UNKNOWN")) { p_player_profile_struct->zone_id = Zones::QEYNOS; } @@ -2369,21 +2381,26 @@ bool Client::StoreCharacter( auto e = InventoryRepository::NewEntity(); - e.charid = character_id; + e.character_id = character_id; for (int16 slot_id = EQ::invslot::EQUIPMENT_BEGIN; slot_id <= EQ::invbag::BANK_BAGS_END;) { const auto inst = p_inventory_profile->GetItem(slot_id); if (inst) { - e.slotid = slot_id; - e.itemid = inst->GetItem()->ID; - e.charges = inst->GetCharges(); - e.color = inst->GetColor(); - e.augslot1 = inst->GetAugmentItemID(EQ::invaug::SOCKET_BEGIN); - e.augslot2 = inst->GetAugmentItemID(EQ::invaug::SOCKET_BEGIN + 1); - e.augslot3 = inst->GetAugmentItemID(EQ::invaug::SOCKET_BEGIN + 2); - e.augslot4 = inst->GetAugmentItemID(EQ::invaug::SOCKET_BEGIN + 3); - e.augslot5 = inst->GetAugmentItemID(EQ::invaug::SOCKET_BEGIN + 4); - e.augslot6 = inst->GetAugmentItemID(EQ::invaug::SOCKET_END); + e.slot_id = slot_id; + e.item_id = inst->GetItem()->ID; + e.charges = inst->GetCharges(); + e.color = inst->GetColor(); + e.augment_one = inst->GetAugmentItemID(EQ::invaug::SOCKET_BEGIN); + e.augment_two = inst->GetAugmentItemID(EQ::invaug::SOCKET_BEGIN + 1); + e.augment_three = inst->GetAugmentItemID(EQ::invaug::SOCKET_BEGIN + 2); + e.augment_four = inst->GetAugmentItemID(EQ::invaug::SOCKET_BEGIN + 3); + e.augment_five = inst->GetAugmentItemID(EQ::invaug::SOCKET_BEGIN + 4); + e.augment_six = inst->GetAugmentItemID(EQ::invaug::SOCKET_END); + e.instnodrop = inst->IsAttuned() ? 1 : 0; + e.ornament_icon = inst->GetOrnamentationIcon(); + e.ornament_idfile = inst->GetOrnamentationIDFile(); + e.ornament_hero_model = inst->GetOrnamentHeroModel(); + e.guid = inst->GetSerialNumber(); v.emplace_back(e); } @@ -2411,7 +2428,7 @@ bool Client::StoreCharacter( void Client::RecordPossibleHack(const std::string& message) { - if (player_event_logs.IsEventEnabled(PlayerEvent::POSSIBLE_HACK)) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::POSSIBLE_HACK)) { auto event = PlayerEvent::PossibleHackEvent{.message = message}; std::stringstream ss; { @@ -2502,3 +2519,19 @@ void Client::SendUnsupportedClientPacket(const std::string& message) QueuePacket(&packet); } + +void Client::LoadDataBucketsCache() +{ + DataBucket::BulkLoadEntitiesToCache(DataBucketLoadType::Account, {GetAccountID()}); + const auto ids = CharacterDataRepository::GetCharacterIDsByAccountID(database, GetAccountID()); + DataBucket::BulkLoadEntitiesToCache(DataBucketLoadType::Client, ids); +} + +void Client::ClearDataBucketsCache() +{ + DataBucket::DeleteFromCache(GetAccountID(), DataBucketLoadType::Account); + auto ids = CharacterDataRepository::GetCharacterIDsByAccountID(database, GetAccountID()); + for (const auto& id : ids) { + DataBucket::DeleteFromCache(id, DataBucketLoadType::Client); + } +} diff --git a/world/client.h b/world/client.h index e88f8a94e..5890cfcf0 100644 --- a/world/client.h +++ b/world/client.h @@ -121,6 +121,9 @@ private: bool CanTradeFVNoDropItem(); void RecordPossibleHack(const std::string& message); void SendUnsupportedClientPacket(const std::string& message); + + void LoadDataBucketsCache(); + void ClearDataBucketsCache(); }; bool CheckCharCreateInfoSoF(CharCreate_Struct *cc); diff --git a/world/cliententry.cpp b/world/cliententry.cpp index 85dfe6ef9..d6feb68dd 100644 --- a/world/cliententry.cpp +++ b/world/cliententry.cpp @@ -1,20 +1,3 @@ -/* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2005 EQEMu Development Team (http://eqemulator.net) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; version 2 of the License. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY except by those people which sell it, which - are required to give you total support for your newly bought product; - without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ #include "../common/global_define.h" #include "cliententry.h" #include "clientlist.h" @@ -24,93 +7,83 @@ #include "worlddb.h" #include "zoneserver.h" #include "world_config.h" -#include "../common/guilds.h" -#include "../common/strings.h" -extern uint32 numplayers; -extern LoginServerList loginserverlist; -extern ClientList client_list; -extern volatile bool RunLoops; -extern SharedTaskManager shared_task_manager; +extern uint32 numplayers; +extern volatile bool RunLoops; -/** - * @param in_id - * @param in_loginserver_id - * @param in_loginserver_name - * @param in_login_name - * @param in_login_key - * @param in_is_world_admin - * @param ip - * @param local - */ ClientListEntry::ClientListEntry( - uint32 in_id, - uint32 in_loginserver_id, - const char *in_loginserver_name, - const char *in_login_name, - const char *in_login_key, - int16 in_is_world_admin, - uint32 ip, + uint32 id, + uint32 login_server_id, + const char *login_server_name, + const char *account_name, + const char *login_key, + int16 is_world_admin, + uint32 ip_address, uint8 local ) - : id(in_id) + : m_id(id) { ClearVars(true); LogDebug( - "in_id [{0}] in_loginserver_id [{1}] in_loginserver_name [{2}] in_login_name [{3}] in_login_key [{4}] " - " in_is_world_admin [{5}] ip [{6}] local [{7}]", - in_id, - in_loginserver_id, - in_loginserver_name, - in_login_name, - in_login_key, - in_is_world_admin, - ip, + "id [{}] loginserver_id [{}] loginserver_name [{}] login_name [{}] login_key [{}] is_world_admin [{}] ip [{}] local [{}]", + id, + login_server_id, + login_server_name, + account_name, + login_key, + is_world_admin, + ip_address, local ); - pIP = ip; - pLSID = in_loginserver_id; - if (in_loginserver_id > 0) { - paccountid = database.GetAccountIDFromLSID(in_loginserver_name, in_loginserver_id, paccountname, &padmin); + m_ip_address = ip_address; + m_login_server_id = login_server_id; + if (login_server_id > 0) { + m_account_id = database.GetAccountIDFromLSID( + login_server_name, + login_server_id, + m_account_name, + &m_admin + ); } - strn0cpy(loginserver_account_name, in_login_name, sizeof(loginserver_account_name)); - strn0cpy(plskey, in_login_key, sizeof(plskey)); - strn0cpy(source_loginserver, in_loginserver_name, sizeof(source_loginserver)); - pworldadmin = in_is_world_admin; - plocal = (local == 1); + strn0cpy(m_login_account_name, account_name, sizeof(m_login_account_name)); + strn0cpy(m_key, login_key, sizeof(m_key)); + strn0cpy(m_source_loginserver, login_server_name, sizeof(m_source_loginserver)); - memset(pLFGComments, 0, 64); + m_world_admin = is_world_admin; + m_is_local = (local == 1); + + memset(m_lfg_comments, 0, 64); } -ClientListEntry::ClientListEntry(uint32 in_id, ZoneServer *iZS, ServerClientList_Struct *scl, CLE_Status iOnline) - : id(in_id) +ClientListEntry::ClientListEntry(uint32 in_id, ZoneServer *z, ServerClientList_Struct *scl, CLE_Status online) + : m_id(in_id) { ClearVars(true); - pIP = 0; - pLSID = scl->LSAccountID; - strn0cpy(loginserver_account_name, scl->name, sizeof(loginserver_account_name)); - strn0cpy(plskey, scl->lskey, sizeof(plskey)); - pworldadmin = 0; + m_ip_address = 0; + m_login_server_id = scl->LSAccountID; + strn0cpy(m_login_account_name, scl->name, sizeof(m_login_account_name)); + strn0cpy(m_key, scl->lskey, sizeof(m_key)); + m_world_admin = 0; - paccountid = scl->AccountID; - strn0cpy(paccountname, scl->AccountName, sizeof(paccountname)); - padmin = scl->Admin; + m_account_id = scl->AccountID; + strn0cpy(m_account_name, scl->AccountName, sizeof(m_account_name)); + m_admin = scl->Admin; - pinstance = 0; - pLFGFromLevel = 0; - pLFGToLevel = 0; - pLFGMatchFilter = false; - memset(pLFGComments, 0, 64); + m_instance = 0; + m_lfg_from_level = 0; + m_lfg_to_level = 0; + m_lfg_match_filter = false; + memset(m_lfg_comments, 0, 64); - if (iOnline >= CLE_Status::Zoning) { - Update(iZS, scl, iOnline); + if (online >= CLE_Status::Zoning) { + Update(z, scl, online); } else { - SetOnline(iOnline); + SetOnline(online); } } @@ -118,18 +91,18 @@ ClientListEntry::~ClientListEntry() { if (RunLoops) { Camp(); // updates zoneserver's numplayers - client_list.RemoveCLEReferances(this); + ClientList::Instance()->RemoveCLEReferances(this); } - for (auto& elem : tell_queue) { + for (auto &elem: m_tell_queue) { safe_delete_array(elem); } - tell_queue.clear(); + m_tell_queue.clear(); } void ClientListEntry::SetChar(uint32 iCharID, const char *iCharName) { - pcharid = iCharID; - strn0cpy(pname, iCharName, sizeof(pname)); + m_char_id = iCharID; + strn0cpy(m_char_name, iCharName, sizeof(m_char_name)); } void ClientListEntry::SetOnline(CLE_Status iOnline) @@ -142,20 +115,20 @@ void ClientListEntry::SetOnline(CLE_Status iOnline) static_cast(iOnline) ); - if (iOnline >= CLE_Status::Online && pOnline < CLE_Status::Online) { + if (iOnline >= CLE_Status::Online && m_online < CLE_Status::Online) { numplayers++; } - else if (iOnline < CLE_Status::Online && pOnline >= CLE_Status::Online) { + else if (iOnline < CLE_Status::Online && m_online >= CLE_Status::Online) { numplayers--; } - if (iOnline != CLE_Status::Online || pOnline < CLE_Status::Online) { - pOnline = iOnline; + if (iOnline != CLE_Status::Online || m_online < CLE_Status::Online) { + m_online = iOnline; } if (iOnline < CLE_Status::Zoning) { Camp(); } - if (pOnline >= CLE_Status::Online) { - stale = 0; + if (m_online >= CLE_Status::Online) { + m_stale = 0; } } @@ -164,16 +137,17 @@ void ClientListEntry::LSUpdate(ZoneServer *iZS) if (WorldConfig::get()->UpdateStats) { auto pack = new ServerPacket; pack->opcode = ServerOP_LSZoneInfo; - pack->size = sizeof(ZoneInfo_Struct); + pack->size = sizeof(LoginserverZoneInfoUpdate); pack->pBuffer = new uchar[pack->size]; - ZoneInfo_Struct *zone = (ZoneInfo_Struct *) pack->pBuffer; + auto *zone = (LoginserverZoneInfoUpdate *) pack->pBuffer; zone->count = iZS->NumPlayers(); zone->zone = iZS->GetZoneID(); zone->zone_wid = iZS->GetID(); - loginserverlist.SendPacket(pack); + LoginServerList::Instance()->SendPacket(pack); safe_delete(pack); } } + void ClientListEntry::LSZoneChange(ZoneToZone_Struct *ztz) { if (WorldConfig::get()->UpdateStats) { @@ -181,60 +155,60 @@ void ClientListEntry::LSZoneChange(ZoneToZone_Struct *ztz) pack->opcode = ServerOP_LSPlayerZoneChange; pack->size = sizeof(ServerLSPlayerZoneChange_Struct); pack->pBuffer = new uchar[pack->size]; - ServerLSPlayerZoneChange_Struct *zonechange = (ServerLSPlayerZoneChange_Struct *) pack->pBuffer; + auto *zonechange = (ServerLSPlayerZoneChange_Struct *) pack->pBuffer; zonechange->lsaccount_id = LSID(); zonechange->from = ztz->current_zone_id; zonechange->to = ztz->requested_zone_id; - loginserverlist.SendPacket(pack); + LoginServerList::Instance()->SendPacket(pack); safe_delete(pack); } } void ClientListEntry::Update(ZoneServer *iZS, ServerClientList_Struct *scl, CLE_Status iOnline) { - if (pzoneserver != iZS) { - if (pzoneserver) { - pzoneserver->RemovePlayer(); - LSUpdate(pzoneserver); + if (m_zone_server != iZS) { + if (m_zone_server) { + m_zone_server->RemovePlayer(); + LSUpdate(m_zone_server); } if (iZS) { iZS->AddPlayer(); LSUpdate(iZS); } } - pzoneserver = iZS; - pzone = scl->zone; - pinstance = scl->instance_id; - pcharid = scl->charid; + m_zone_server = iZS; + m_zone = scl->zone; + m_instance = scl->instance_id; + m_char_id = scl->charid; - strcpy(pname, scl->name); - if (paccountid == 0) { - paccountid = scl->AccountID; - strcpy(paccountname, scl->AccountName); - strcpy(loginserver_account_name, scl->AccountName); - pIP = scl->IP; - pLSID = scl->LSAccountID; - strn0cpy(plskey, scl->lskey, sizeof(plskey)); + strcpy(m_char_name, scl->name); + if (m_account_id == 0) { + m_account_id = scl->AccountID; + strcpy(m_account_name, scl->AccountName); + strcpy(m_login_account_name, scl->AccountName); + m_ip_address = scl->IP; + m_login_server_id = scl->LSAccountID; + strn0cpy(m_key, scl->lskey, sizeof(m_key)); } - padmin = scl->Admin; - plevel = scl->level; - pclass_ = scl->class_; - prace = scl->race; - panon = scl->anon; - ptellsoff = scl->tellsoff; - pguild_id = scl->guild_id; - pguild_rank = scl->guild_rank; - pguild_tribute_opt_in = scl->guild_tribute_opt_in; - pLFG = scl->LFG; - gm = scl->gm; - pClientVersion = scl->ClientVersion; + m_admin = scl->Admin; + m_level = scl->level; + m_class_ = scl->class_; + m_race = scl->race; + m_anon = scl->anon; + m_tells_off = scl->tellsoff; + m_guild_id = scl->guild_id; + m_guild_rank = scl->guild_rank; + m_guild_tribute_opt_in = scl->guild_tribute_opt_in; + m_lfg = scl->LFG; + m_gm = scl->gm; + m_client_version = scl->ClientVersion; // Fields from the LFG Window if ((scl->LFGFromLevel != 0) && (scl->LFGToLevel != 0)) { - pLFGFromLevel = scl->LFGFromLevel; - pLFGToLevel = scl->LFGToLevel; - pLFGMatchFilter = scl->LFGMatchFilter; - memcpy(pLFGComments, scl->LFGComments, sizeof(pLFGComments)); + m_lfg_from_level = scl->LFGFromLevel; + m_lfg_to_level = scl->LFGToLevel; + m_lfg_match_filter = scl->LFGMatchFilter; + memcpy(m_lfg_comments, scl->LFGComments, sizeof(m_lfg_comments)); } SetOnline(iOnline); @@ -242,76 +216,76 @@ void ClientListEntry::Update(ZoneServer *iZS, ServerClientList_Struct *scl, CLE_ void ClientListEntry::LeavingZone(ZoneServer *iZS, CLE_Status iOnline) { - if (iZS != 0 && iZS != pzoneserver) { + if (iZS != 0 && iZS != m_zone_server) { return; } SetOnline(iOnline); - shared_task_manager.RemoveActiveInvitationByCharacterID(CharID()); + SharedTaskManager::Instance()->RemoveActiveInvitationByCharacterID(CharID()); - if (pzoneserver) { - pzoneserver->RemovePlayer(); - LSUpdate(pzoneserver); + if (m_zone_server) { + m_zone_server->RemovePlayer(); + LSUpdate(m_zone_server); } - pzoneserver = 0; - pzone = 0; + m_zone_server = 0; + m_zone = 0; } void ClientListEntry::ClearVars(bool iAll) { if (iAll) { - pOnline = CLE_Status::Never; - stale = 0; + m_online = CLE_Status::Never; + m_stale = 0; - pLSID = 0; - memset(loginserver_account_name, 0, sizeof(loginserver_account_name)); - memset(plskey, 0, sizeof(plskey)); - pworldadmin = 0; + m_login_server_id = 0; + memset(m_login_account_name, 0, sizeof(m_login_account_name)); + memset(m_key, 0, sizeof(m_key)); + m_world_admin = 0; - paccountid = 0; - memset(paccountname, 0, sizeof(paccountname)); - padmin = AccountStatus::Player; + m_account_id = 0; + memset(m_account_name, 0, sizeof(m_account_name)); + m_admin = AccountStatus::Player; } - pzoneserver = 0; - pzone = 0; - pcharid = 0; - memset(pname, 0, sizeof(pname)); - plevel = 0; - pclass_ = 0; - prace = 0; - panon = 0; - ptellsoff = 0; - pguild_id = GUILD_NONE; - pguild_rank = 0; - pLFG = 0; - gm = 0; - pClientVersion = 0; - for (auto& elem : tell_queue) { + m_zone_server = 0; + m_zone = 0; + m_char_id = 0; + memset(m_char_name, 0, sizeof(m_char_name)); + m_level = 0; + m_class_ = 0; + m_race = 0; + m_anon = 0; + m_tells_off = 0; + m_guild_id = GUILD_NONE; + m_guild_rank = 0; + m_lfg = 0; + m_gm = 0; + m_client_version = 0; + for (auto &elem: m_tell_queue) { safe_delete_array(elem); } - tell_queue.clear(); + m_tell_queue.clear(); } void ClientListEntry::Camp(ZoneServer *iZS) { - if (iZS != 0 && iZS != pzoneserver) { + if (iZS != 0 && iZS != m_zone_server) { return; } - if (pzoneserver) { - pzoneserver->RemovePlayer(); - LSUpdate(pzoneserver); + if (m_zone_server) { + m_zone_server->RemovePlayer(); + LSUpdate(m_zone_server); } ClearVars(); - stale = 0; + m_stale = 0; } bool ClientListEntry::CheckStale() { - stale++; - if (stale > 20) { - if (pOnline > CLE_Status::Offline) { + m_stale++; + if (m_stale > 20) { + if (m_online > CLE_Status::Offline) { SetOnline(CLE_Status::Offline); } @@ -323,48 +297,50 @@ bool ClientListEntry::CheckStale() bool ClientListEntry::CheckAuth(uint32 loginserver_account_id, const char *key_password) { LogDebug( - "ls_account_id [{0}] key_password [{1}] plskey [{2}]", + "ls_account_id [{}] key_password [{}] key [{}]", loginserver_account_id, key_password, - plskey + m_key ); - if (pLSID == loginserver_account_id && strncmp(plskey, key_password, 10) == 0) { + if (m_login_server_id == loginserver_account_id && strncmp(m_key, key_password, 10) == 0) { LogDebug( - "ls_account_id [{0}] key_password [{1}] plskey [{2}] lsid [{3}] paccountid [{4}]", + "ls_account_id [{}] key_password [{}] m_key [{}] lsid [{}] m_account_id [{}]", loginserver_account_id, key_password, - plskey, + m_key, LSID(), - paccountid + m_account_id ); - if (paccountid == 0 && LSID() > 0) { + // create account if it doesn't exist + if (m_account_id == 0 && LSID() > 0) { int16 default_account_status = WorldConfig::get()->DefaultStatus; - paccountid = database.CreateAccount( - loginserver_account_name, + m_account_id = database.CreateAccount( + m_login_account_name, std::string(), default_account_status, - source_loginserver, + m_source_loginserver, LSID() ); - if (!paccountid) { - LogInfo( - "Error adding local account for LS login: [{0}:{1}], duplicate name", - source_loginserver, - loginserver_account_name + if (!m_account_id) { + LogError( + "Error adding local account for LS login [{}] [{}], duplicate name", + m_source_loginserver, + m_login_account_name ); return false; } - strn0cpy(paccountname, loginserver_account_name, sizeof(paccountname)); - padmin = default_account_status; + strn0cpy(m_account_name, m_login_account_name, sizeof(m_account_name)); + m_admin = default_account_status; } std::string lsworldadmin; if (database.GetVariable("honorlsworldadmin", lsworldadmin)) { - if (Strings::ToInt(lsworldadmin) == 1 && pworldadmin != 0 && (padmin < pworldadmin || padmin == AccountStatus::Player)) { - padmin = pworldadmin; + if (Strings::ToInt(lsworldadmin) == 1 && m_world_admin != 0 && + (m_admin < m_world_admin || m_admin == AccountStatus::Player)) { + m_admin = m_world_admin; } } return true; @@ -379,8 +355,8 @@ void ClientListEntry::ProcessTellQueue() } ServerPacket *pack; - auto it = tell_queue.begin(); - while (it != tell_queue.end()) { + auto it = m_tell_queue.begin(); + while (it != m_tell_queue.end()) { pack = new ServerPacket( ServerOP_ChannelMessage, sizeof(ServerChannelMessage_Struct) + strlen((*it)->message) + 1 @@ -389,8 +365,7 @@ void ClientListEntry::ProcessTellQueue() Server()->SendPacket(pack); safe_delete(pack); safe_delete_array(*it); - it = tell_queue.erase(it); + it = m_tell_queue.erase(it); } - return; } diff --git a/world/cliententry.h b/world/cliententry.h index f11bdcc1a..bf9a2662c 100644 --- a/world/cliententry.h +++ b/world/cliententry.h @@ -8,8 +8,7 @@ #include "../common/rulesys.h" #include -typedef enum -{ +typedef enum { Never, Offline, Online, @@ -18,7 +17,7 @@ typedef enum InZone } CLE_Status; -static const char * CLEStatusString[] = { +static const char *CLEStatusString[] = { "Never", "Offline", "Online", @@ -33,150 +32,133 @@ struct ServerClientList_Struct; class ClientListEntry { public: - /** - * @param id - * @param in_loginserver_id - * @param in_loginserver_name - * @param in_login_name - * @param in_login_key - * @param in_is_world_admin - * @param ip - * @param local - */ ClientListEntry( uint32 id, - uint32 in_loginserver_id, - const char *in_loginserver_name, - const char *in_login_name, - const char *in_login_key, - int16 in_is_world_admin = 0, - uint32 ip = 0, + uint32 login_server_id, + const char *login_server_name, + const char *account_name, + const char *login_key, + int16 is_world_admin = 0, + uint32 ip_address = 0, uint8 local = 0 ); - /** - * @param id - * @param iZS - * @param scl - * @param iOnline - */ - ClientListEntry(uint32 id, uint32 iAccID, const char* iAccName, MD5& iMD5Pass, int16 iAdmin = AccountStatus::Player); - ClientListEntry(uint32 id, ZoneServer* iZS, ServerClientList_Struct* scl, CLE_Status iOnline); + ClientListEntry(uint32 id, ZoneServer *z, ServerClientList_Struct *scl, CLE_Status online); ~ClientListEntry(); - bool CheckStale(); - void Update(ZoneServer* zoneserver, ServerClientList_Struct* scl, CLE_Status iOnline = CLE_Status::InZone); - void LSUpdate(ZoneServer* zoneserver); - void LSZoneChange(ZoneToZone_Struct* ztz); - bool CheckAuth(uint32 loginserver_account_id, const char* key_password); - void SetOnline(CLE_Status iOnline = CLE_Status::Online); - void SetChar(uint32 iCharID, const char* iCharName); - inline CLE_Status Online() { return pOnline; } - inline const uint32 GetID() const { return id; } - inline const uint32 GetIP() const { return pIP; } - inline void SetIP(const uint32& iIP) { pIP = iIP; } - inline void KeepAlive() { stale = 0; } - inline uint8 GetStaleCounter() const { return stale; } - void LeavingZone(ZoneServer* iZS = 0, CLE_Status iOnline = CLE_Status::Offline); - void Camp(ZoneServer* iZS = 0); + bool CheckStale(); + void Update(ZoneServer *zoneserver, ServerClientList_Struct *scl, CLE_Status iOnline = CLE_Status::InZone); + void LSUpdate(ZoneServer *zoneserver); + void LSZoneChange(ZoneToZone_Struct *ztz); + bool CheckAuth(uint32 loginserver_account_id, const char *key_password); + void SetOnline(CLE_Status iOnline = CLE_Status::Online); + void SetChar(uint32 iCharID, const char *iCharName); + inline CLE_Status Online() { return m_online; } + inline const uint32 GetID() const { return m_id; } + inline const uint32 GetIP() const { return m_ip_address; } + inline void SetIP(const uint32 &iIP) { m_ip_address = iIP; } + inline void KeepAlive() { m_stale = 0; } + inline uint8 GetStaleCounter() const { return m_stale; } + void LeavingZone(ZoneServer *iZS = 0, CLE_Status iOnline = CLE_Status::Offline); + void Camp(ZoneServer *iZS = 0); // Login Server stuff - inline const char* LoginServer() const { return source_loginserver; } - inline uint32 LSID() const { return pLSID; } - inline uint32 LSAccountID() const { return pLSID; } - inline const char* LSName() const { return loginserver_account_name; } - inline int16 WorldAdmin() const { return pworldadmin; } - inline const char* GetLSKey() const { return plskey; } - inline const CLE_Status GetOnline() const { return pOnline; } + inline const char *LoginServer() const { return m_source_loginserver; } + inline uint32 LSID() const { return m_login_server_id; } + inline uint32 LSAccountID() const { return m_login_server_id; } + inline const char *LSName() const { return m_login_account_name; } + inline int16 WorldAdmin() const { return m_world_admin; } + inline const char *GetLSKey() const { return m_key; } + inline const CLE_Status GetOnline() const { return m_online; } // Account stuff - inline uint32 AccountID() const { return paccountid; } - inline const char* AccountName() const { return paccountname; } - inline int16 Admin() const { return padmin; } - inline void SetAdmin(uint16 iAdmin) { padmin = iAdmin; } + inline uint32 AccountID() const { return m_account_id; } + inline const char *AccountName() const { return m_account_name; } + inline int16 Admin() const { return m_admin; } + inline void SetAdmin(uint16 iAdmin) { m_admin = iAdmin; } // Character info - inline ZoneServer *Server() const { return pzoneserver; } - inline void ClearServer() { pzoneserver = 0; } - inline uint32 CharID() const { return pcharid; } - inline const char *name() const { return pname; } - inline uint32 zone() const { return pzone; } - inline uint16 instance() const { return pinstance; } - inline uint8 level() const { return plevel; } - inline uint8 class_() const { return pclass_; } - inline uint16 race() const { return prace; } - inline uint8 Anon() { return panon; } - inline uint8 TellsOff() const { return ptellsoff; } - inline uint32 GuildID() const { return pguild_id; } - inline uint32 GuildRank() const { return pguild_rank; } - inline bool GuildTributeOptIn() const { return pguild_tribute_opt_in; } - inline void SetGuild(uint32 guild_id) { pguild_id = guild_id; } - inline void SetGuildTributeOptIn(bool opt) { pguild_tribute_opt_in = opt; } - inline bool LFG() const { return pLFG; } - inline uint8 GetGM() const { return gm; } - inline void SetGM(uint8 igm) { gm = igm; } - inline void SetZone(uint32 zone) { pzone = zone; } - inline bool IsLocalClient() const { return plocal; } - inline uint8 GetLFGFromLevel() const { return pLFGFromLevel; } - inline uint8 GetLFGToLevel() const { return pLFGToLevel; } - inline bool GetLFGMatchFilter() const { return pLFGMatchFilter; } - inline const char *GetLFGComments() const { return pLFGComments; } - inline uint8 GetClientVersion() { return pClientVersion; } + inline ZoneServer *Server() const { return m_zone_server; } + inline void ClearServer() { m_zone_server = 0; } + inline uint32 CharID() const { return m_char_id; } + inline const char *name() const { return m_char_name; } + inline uint32 zone() const { return m_zone; } + inline uint16 instance() const { return m_instance; } + inline uint8 level() const { return m_level; } + inline uint8 class_() const { return m_class_; } + inline uint16 race() const { return m_race; } + inline uint8 Anon() { return m_anon; } + inline uint8 TellsOff() const { return m_tells_off; } + inline uint32 GuildID() const { return m_guild_id; } + inline uint32 GuildRank() const { return m_guild_rank; } + inline bool GuildTributeOptIn() const { return m_guild_tribute_opt_in; } + inline void SetGuild(uint32 guild_id) { m_guild_id = guild_id; } + inline void SetGuildTributeOptIn(bool opt) { m_guild_tribute_opt_in = opt; } + inline bool LFG() const { return m_lfg; } + inline uint8 GetGM() const { return m_gm; } + inline void SetGM(uint8 igm) { m_gm = igm; } + inline void SetZone(uint32 zone) { m_zone = zone; } + inline bool IsLocalClient() const { return m_is_local; } + inline uint8 GetLFGFromLevel() const { return m_lfg_from_level; } + inline uint8 GetLFGToLevel() const { return m_lfg_to_level; } + inline bool GetLFGMatchFilter() const { return m_lfg_match_filter; } + inline const char *GetLFGComments() const { return m_lfg_comments; } + inline uint8 GetClientVersion() { return m_client_version; } - inline bool TellQueueFull() const { return tell_queue.size() >= RuleI(World, TellQueueSize); } - inline bool TellQueueEmpty() const { return tell_queue.empty(); } - inline void PushToTellQueue(ServerChannelMessage_Struct *scm) { tell_queue.push_back(scm); } + inline bool TellQueueFull() const { return m_tell_queue.size() >= RuleI(World, TellQueueSize); } + inline bool TellQueueEmpty() const { return m_tell_queue.empty(); } + inline void PushToTellQueue(ServerChannelMessage_Struct *scm) { m_tell_queue.push_back(scm); } void ProcessTellQueue(); - void SetPendingExpeditionInvite(ServerPacket* pack) { p_pending_expedition_invite.reset(pack->Copy()); }; - std::unique_ptr GetPendingExpeditionInvite() { return std::move(p_pending_expedition_invite); } + void SetPendingDzInvite(ServerPacket *pack) { m_dz_invite.reset(pack->Copy()); }; + std::unique_ptr GetPendingDzInvite() { return std::move(m_dz_invite); } private: - void ClearVars(bool iAll = false); + void ClearVars(bool iAll = false); - const uint32 id; - uint32 pIP; - CLE_Status pOnline; - uint8 stale; + const uint32 m_id; + uint32 m_ip_address; + CLE_Status m_online; + uint8 m_stale; // Login Server stuff - char source_loginserver[64]{}; //Loginserver we came from. - uint32 pLSID; - char loginserver_account_name[32]{}; - char plskey[16]{}; - int16 pworldadmin; // Login server's suggested admin status setting - bool plocal; + char m_source_loginserver[64]{}; //Loginserver we came from. + uint32 m_login_server_id; + char m_login_account_name[32]{}; + char m_key[16]{}; + int16 m_world_admin; // Login server's suggested admin status setting + bool m_is_local; // Account stuff - uint32 paccountid; - char paccountname[32]{}; - int16 padmin{}; + uint32 m_account_id; + char m_account_name[32]{}; + int16 m_admin{}; // Character info - ZoneServer* pzoneserver{}; - uint32 pzone{}; - uint16 pinstance{}; - uint32 pcharid{}; - char pname[64]{}; - uint8 plevel{}; - uint8 pclass_{}; - uint16 prace{}; - uint8 panon{}; - uint8 ptellsoff{}; - uint32 pguild_id{}; - uint32 pguild_rank; - bool pguild_tribute_opt_in{}; - bool pLFG{}; - uint8 gm{}; - uint8 pClientVersion{}; - uint8 pLFGFromLevel{}; - uint8 pLFGToLevel{}; - bool pLFGMatchFilter{}; - char pLFGComments[64]{}; + ZoneServer *m_zone_server{}; + uint32 m_zone{}; + uint16 m_instance{}; + uint32 m_char_id{}; + char m_char_name[64]{}; + uint8 m_level{}; + uint8 m_class_{}; + uint16 m_race{}; + uint8 m_anon{}; + uint8 m_tells_off{}; + uint32 m_guild_id{}; + uint32 m_guild_rank; + bool m_guild_tribute_opt_in{}; + bool m_lfg{}; + uint8 m_gm{}; + uint8 m_client_version{}; + uint8 m_lfg_from_level{}; + uint8 m_lfg_to_level{}; + bool m_lfg_match_filter{}; + char m_lfg_comments[64]{}; // Tell Queue -- really a vector :D - std::vector tell_queue; + std::vector m_tell_queue; - std::unique_ptr p_pending_expedition_invite = nullptr; + std::unique_ptr m_dz_invite; }; #endif /*CLIENTENTRY_H_*/ diff --git a/world/clientlist.cpp b/world/clientlist.cpp index 5e02d805e..d8f1d9fbc 100644 --- a/world/clientlist.cpp +++ b/world/clientlist.cpp @@ -36,17 +36,19 @@ #include "../common/zone_store.h" #include -extern WebInterfaceList web_interface; - -extern ZSList zoneserver_list; uint32 numplayers = 0; //this really wants to be a member variable of ClientList... ClientList::ClientList() -: CLStale_timer(10000) + : CLStale_timer(10000), + m_poll_cache_timer(6000) { NextCLEID = 1; m_tick = std::make_unique(5000, true, std::bind(&ClientList::OnTick, this, std::placeholders::_1)); + + // pre-allocate / pin memory for the zone server caches + m_gm_zone_server_ids.reserve(512); + m_guild_zone_server_ids.reserve(1024); } ClientList::~ClientList() { @@ -57,6 +59,10 @@ void ClientList::Process() { if (CLStale_timer.Check()) CLCheckStale(); + if (m_poll_cache_timer.Check()) { + RebuildZoneServerCaches(); + } + LinkedListIterator iterator(list); iterator.Reset(); @@ -225,7 +231,7 @@ void ClientList::DisconnectByIP(uint32 in_ip) { strn0cpy(skp->adminname, "SessionLimit", sizeof(skp->adminname)); strn0cpy(skp->name, cle->name(), sizeof(skp->name)); skp->adminrank = 255; - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); safe_delete(pack); } cle->SetOnline(CLE_Status::Offline); @@ -275,64 +281,127 @@ ClientListEntry* ClientList::FindCLEByCharacterID(uint32 iCharID) { return nullptr; } -void ClientList::SendCLEList(const int16& admin, const char* to, WorldTCPConnection* connection, const char* iName) { +void ClientList::SendCLEList(const int16& admin, const char* to, WorldTCPConnection* connection, const char* search_criteria) +{ LinkedListIterator iterator(clientlist); - int x = 0, y = 0; - int namestrlen = iName == 0 ? 0 : strlen(iName); - bool addnewline = false; - char newline[3]; - if (connection->IsConsole()) - strcpy(newline, "\r\n"); - else - strcpy(newline, "^"); - auto out = fmt::memory_buffer(); + int found_count = 0; + int total_count = 0; + int name_length = search_criteria ? strlen(search_criteria) : 0; + + const char* new_line = connection->IsConsole() ? "\r\n" : "^"; + bool add_new_line = false; + + std::string message; + iterator.Reset(); - while(iterator.MoreElements()) { + + while (iterator.MoreElements()) { ClientListEntry* cle = iterator.GetData(); - if (admin >= cle->Admin() && (iName == 0 || namestrlen == 0 || strncasecmp(cle->name(), iName, namestrlen) == 0 || strncasecmp(cle->AccountName(), iName, namestrlen) == 0 || strncasecmp(cle->LSName(), iName, namestrlen) == 0)) { - struct in_addr in; - in.s_addr = cle->GetIP(); - if (addnewline) { - fmt::format_to(std::back_inserter(out), fmt::runtime(newline)); + + struct in_addr in; + in.s_addr = cle->GetIP(); + + if ( + admin >= cle->Admin() && + ( + !search_criteria || + Strings::Contains(std::string(inet_ntoa(in)), search_criteria) || + Strings::Contains(cle->name(), search_criteria) == 0 || + Strings::Contains(cle->AccountName(), search_criteria) == 0 || + Strings::Contains(cle->LSName(), search_criteria) == 0 + ) + ) { + if (add_new_line) { + message += new_line; } - fmt::format_to(std::back_inserter(out), "ID: {} Acc# {} AccName: {} IP: {}", cle->GetID(), cle->AccountID(), cle->AccountName(), inet_ntoa(in)); - fmt::format_to(std::back_inserter(out), "{} Stale: {} Online: {} Admin: {}", newline, cle->GetStaleCounter(), static_cast(cle->Online()), cle->Admin()); - if (cle->LSID()) - fmt::format_to(std::back_inserter(out), "{} LSID: {} LSName: {} WorldAdmin: {}", newline, cle->LSID(), cle->LSName(), cle->WorldAdmin()); - if (cle->CharID()) - fmt::format_to(std::back_inserter(out), "{} CharID: {} CharName: {} Zone: {} ({})", newline, cle->CharID(), cle->name(), ZoneName(cle->zone()), cle->zone()); - if (out.size() >= 3072) { + + message += fmt::format( + "Account: {} ({}) | IP: {} | Admin: {}", + cle->AccountName(), + cle->AccountID(), + inet_ntoa(in), + cle->Admin() + ); + + if (cle->CharID()) { + message += fmt::format( + "{}Character: {} ({}) | Zone: {} ({})", + new_line, + cle->name(), + cle->CharID(), + ZoneLongName(cle->zone()), + cle->zone() + ); + } + + if (message.size() >= 3072) { connection->SendEmoteMessageRaw( to, 0, AccountStatus::Player, Chat::NPCQuestSay, - out.data() + message.c_str() ); - addnewline = false; - out.clear(); + message.clear(); + add_new_line = false; } else { - addnewline = true; + add_new_line = true; } - y++; + + found_count++; } + iterator.Advance(); - x++; + total_count++; } - fmt::format_to(std::back_inserter(out), "{}{} CLEs in memory. {} CLEs listed. numplayers = {}.", newline, x, y, numplayers); + + message += fmt::format( + "{}{}Player Count: {}", + new_line, + ( + found_count != total_count ? + fmt::format( + "Total: {} CLE{} | Found: {} CLE{} | ", + total_count, + (total_count != 1 ? "s" : ""), + found_count, + (found_count != 1 ? "s" : "") + ) : + "" + ), + numplayers + ); + connection->SendEmoteMessageRaw( to, 0, AccountStatus::Player, Chat::NPCQuestSay, - out.data() + message.c_str() ); } - -void ClientList::CLEAdd(uint32 iLSID, const char *iLoginServerName, const char* iLoginName, const char* iLoginKey, int16 iWorldAdmin, uint32 ip, uint8 local) { - auto tmp = new ClientListEntry(GetNextCLEID(), iLSID, iLoginServerName, iLoginName, iLoginKey, iWorldAdmin, ip, local); +void ClientList::CLEAdd( + uint32 login_server_id, + const char *login_server_name, + const char *login_name, + const char *login_key, + int16 world_admin, + uint32 ip_address, + uint8 is_local +) +{ + auto tmp = new ClientListEntry( + GetNextCLEID(), + login_server_id, + login_server_name, + login_name, + login_key, + world_admin, + ip_address, + is_local + ); clientlist.Append(tmp); } @@ -366,6 +435,7 @@ void ClientList::ClientUpdate(ZoneServer *zoneserver, ServerClientList_Struct *s } else { cle->Update(zoneserver, scl); + AddToZoneServerCaches(cle); } return; } @@ -440,6 +510,7 @@ void ClientList::ClientUpdate(ZoneServer *zoneserver, ServerClientList_Struct *s ); clientlist.Insert(cle); + AddToZoneServerCaches(cle); zoneserver->ChangeWID(scl->charid, cle->GetID()); } @@ -457,19 +528,19 @@ void ClientList::CLEKeepAlive(uint32 numupdates, uint32* wid) { } } -ClientListEntry *ClientList::CheckAuth(uint32 iLSID, const char *iKey) +ClientListEntry *ClientList::CheckAuth(uint32 loginserver_account_id, const char *key) { LinkedListIterator iterator(clientlist); iterator.Reset(); while (iterator.MoreElements()) { - if (iterator.GetData()->CheckAuth(iLSID, iKey)) { + if (iterator.GetData()->CheckAuth(loginserver_account_id, key)) { return iterator.GetData(); } iterator.Advance(); } - return 0; + return nullptr; } void ClientList::SendOnlineGuildMembers(uint32 FromID, uint32 GuildID) @@ -524,7 +595,7 @@ void ClientList::SendOnlineGuildMembers(uint32 FromID, uint32 GuildID) Iterator.Advance(); } - zoneserver_list.SendPacket(from->zone(), from->instance(), pack); + ZSList::Instance()->SendPacket(from->zone(), from->instance(), pack); safe_delete(pack); } @@ -572,7 +643,7 @@ void ClientList::SendWhoAll(uint32 fromid,const char* to, int16 admin, Who_All_S whomlen = strlen(whom->whom); if (whom->wrace == 0x001A) { // 0x001A is the old Froglok race number and is sent by the client for /who all froglok - whom->wrace = FROGLOK; // This is what EQEmu uses for the Froglok Race number. + whom->wrace = Race::Froglok2; // This is what EQEmu uses for the Froglok Race number. } } @@ -1289,7 +1360,7 @@ void ClientList::RemoveCLEReferances(ClientListEntry* cle) { bool ClientList::SendPacket(const char* to, ServerPacket* pack) { if (to == 0 || to[0] == 0) { - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); return true; } else if (to[0] == '*') { @@ -1305,7 +1376,7 @@ bool ClientList::SendPacket(const char* to, ServerPacket* pack) { } return false; } else { - ZoneServer* zs = zoneserver_list.FindByName(to); + ZoneServer* zs = ZSList::Instance()->FindByName(to); if (zs != nullptr) { zs->SendPacket(pack); return true; @@ -1410,7 +1481,7 @@ void ClientList::SendClientVersionSummary(const char *Name) ); if (client_count[EQ::versions::ClientVersion::Titanium]) { - zoneserver_list.SendEmoteMessage( + ZSList::Instance()->SendEmoteMessage( Name, 0, AccountStatus::Player, @@ -1423,7 +1494,7 @@ void ClientList::SendClientVersionSummary(const char *Name) } if (client_count[EQ::versions::ClientVersion::SoF]) { - zoneserver_list.SendEmoteMessage( + ZSList::Instance()->SendEmoteMessage( Name, 0, AccountStatus::Player, @@ -1436,7 +1507,7 @@ void ClientList::SendClientVersionSummary(const char *Name) } if (client_count[EQ::versions::ClientVersion::SoD]) { - zoneserver_list.SendEmoteMessage( + ZSList::Instance()->SendEmoteMessage( Name, 0, AccountStatus::Player, @@ -1449,7 +1520,7 @@ void ClientList::SendClientVersionSummary(const char *Name) } if (client_count[EQ::versions::ClientVersion::UF]) { - zoneserver_list.SendEmoteMessage( + ZSList::Instance()->SendEmoteMessage( Name, 0, AccountStatus::Player, @@ -1462,7 +1533,7 @@ void ClientList::SendClientVersionSummary(const char *Name) } if (client_count[EQ::versions::ClientVersion::RoF]) { - zoneserver_list.SendEmoteMessage( + ZSList::Instance()->SendEmoteMessage( Name, 0, AccountStatus::Player, @@ -1475,7 +1546,7 @@ void ClientList::SendClientVersionSummary(const char *Name) } if (client_count[EQ::versions::ClientVersion::RoF2]) { - zoneserver_list.SendEmoteMessage( + ZSList::Instance()->SendEmoteMessage( Name, 0, AccountStatus::Player, @@ -1488,7 +1559,7 @@ void ClientList::SendClientVersionSummary(const char *Name) } - zoneserver_list.SendEmoteMessage( + ZSList::Instance()->SendEmoteMessage( Name, 0, AccountStatus::Player, @@ -1584,13 +1655,13 @@ void ClientList::OnTick(EQ::Timer *t) Iterator.Advance(); } - web_interface.SendEvent(out); + WebInterfaceList::Instance()->SendEvent(out); } /** * @param response */ -void ClientList::GetClientList(Json::Value &response) +void ClientList::GetClientList(Json::Value &response, bool full_list) { LinkedListIterator Iterator(clientlist); @@ -1601,62 +1672,68 @@ void ClientList::GetClientList(Json::Value &response) Json::Value row; - row["account_id"] = cle->AccountID(); - row["account_name"] = cle->AccountName(); - row["admin"] = cle->Admin(); - row["id"] = cle->GetID(); - row["ip"] = cle->GetIP(); - row["loginserver_account_id"] = cle->LSAccountID(); - row["loginserver_id"] = cle->LSID(); - row["loginserver_name"] = cle->LSName(); - row["online"] = cle->Online(); - row["world_admin"] = cle->WorldAdmin(); + row["id"] = cle->GetID(); + row["name"] = cle->name(); + row["level"] = cle->level(); + row["ip"] = cle->GetIP(); + row["gm"] = cle->GetGM(); + row["race"] = cle->race(); + row["class"] = cle->class_(); + row["client_version"] = cle->GetClientVersion(); + row["admin"] = cle->Admin(); + row["account_id"] = cle->AccountID(); + row["account_name"] = cle->AccountName(); + row["character_id"] = cle->CharID(); + row["anon"] = cle->Anon(); + row["guild_id"] = cle->GuildID(); + + if (full_list) { + row["loginserver_account_id"] = cle->LSAccountID(); + row["loginserver_id"] = cle->LSID(); + row["loginserver_name"] = cle->LSName(); + row["online"] = cle->Online(); + row["world_admin"] = cle->WorldAdmin(); + row["guild_rank"] = cle->GuildRank(); + row["guild_tribute_opt_in"] = cle->GuildTributeOptIn(); + row["instance"] = cle->instance(); + row["is_local_client"] = cle->IsLocalClient(); + row["lfg"] = cle->LFG(); + row["lfg_comments"] = cle->GetLFGComments(); + row["lfg_from_level"] = cle->GetLFGFromLevel(); + row["lfg_match_filter"] = cle->GetLFGMatchFilter(); + row["lfg_to_level"] = cle->GetLFGToLevel(); + row["tells_off"] = cle->TellsOff(); + row["zone"] = cle->zone(); + } auto server = cle->Server(); if (server) { - row["server"]["client_address"] = server->GetCAddress(); - row["server"]["client_local_address"] = server->GetCLocalAddress(); - row["server"]["client_port"] = server->GetCPort(); - row["server"]["compile_time"] = server->GetCompileTime(); - row["server"]["id"] = server->GetID(); - row["server"]["instance_id"] = server->GetInstanceID(); - row["server"]["ip"] = server->GetIP(); - row["server"]["is_booting"] = server->IsBootingUp(); - row["server"]["launch_name"] = server->GetLaunchName(); - row["server"]["launched_name"] = server->GetLaunchedName(); - row["server"]["number_players"] = server->NumPlayers(); - row["server"]["port"] = server->GetPort(); - row["server"]["previous_zone_id"] = server->GetPrevZoneID(); - row["server"]["static_zone"] = server->IsStaticZone(); - row["server"]["uui"] = server->GetUUID(); - row["server"]["zone_id"] = server->GetZoneID(); - row["server"]["zone_long_name"] = server->GetZoneLongName(); - row["server"]["zone_name"] = server->GetZoneName(); - row["server"]["zone_os_pid"] = server->GetZoneOSProcessID(); + row["server"]["zone_id"] = server->GetZoneID(); + row["server"]["zone_long_name"] = server->GetZoneLongName(); + row["server"]["zone_name"] = server->GetZoneName(); + row["server"]["zone_os_pid"] = server->GetZoneOSProcessID(); + row["server"]["id"] = server->GetID(); + + if (full_list) { + row["server"]["client_address"] = server->GetCAddress(); + row["server"]["client_local_address"] = server->GetCLocalAddress(); + row["server"]["client_port"] = server->GetCPort(); + row["server"]["compile_time"] = server->GetCompileTime(); + row["server"]["instance_id"] = server->GetInstanceID(); + row["server"]["ip"] = server->GetIP(); + row["server"]["is_booting"] = server->IsBootingUp(); + row["server"]["launch_name"] = server->GetLaunchName(); + row["server"]["launched_name"] = server->GetLaunchedName(); + row["server"]["number_players"] = server->NumPlayers(); + row["server"]["port"] = server->GetPort(); + row["server"]["previous_zone_id"] = server->GetPrevZoneID(); + row["server"]["static_zone"] = server->IsStaticZone(); + row["server"]["uui"] = server->GetUUID(); + } } else { row["server"] = Json::Value(); } - row["anon"] = cle->Anon(); - row["character_id"] = cle->CharID(); - row["class"] = cle->class_(); - row["client_version"] = cle->GetClientVersion(); - row["gm"] = cle->GetGM(); - row["guild_id"] = cle->GuildID(); - row["guild_rank"] = cle->GuildRank(); - row["guild_tribute_opt_in"] = cle->GuildTributeOptIn(); - row["instance"] = cle->instance(); - row["is_local_client"] = cle->IsLocalClient(); - row["level"] = cle->level(); - row["lfg"] = cle->LFG(); - row["lfg_comments"] = cle->GetLFGComments(); - row["lfg_from_level"] = cle->GetLFGFromLevel(); - row["lfg_match_filter"] = cle->GetLFGMatchFilter(); - row["lfg_to_level"] = cle->GetLFGToLevel(); - row["name"] = cle->name(); - row["race"] = cle->race(); - row["tells_off"] = cle->TellsOff(); - row["zone"] = cle->zone(); response.append(row); @@ -1717,13 +1794,13 @@ void ClientList::SendCharacterMessageID(ClientListEntry* character, return; } - SerializeBuffer serialized_args; + SerializeBuffer argbuf; for (const auto& arg : args) { - serialized_args.WriteString(arg); + argbuf.WriteString(arg); } - uint32_t args_size = static_cast(serialized_args.size()); + uint32_t args_size = static_cast(argbuf.size()); uint32_t pack_size = sizeof(CZClientMessageString_Struct) + args_size; auto pack = std::make_unique(ServerOP_CZClientMessageString, pack_size); auto buf = reinterpret_cast(pack->pBuffer); @@ -1731,7 +1808,10 @@ void ClientList::SendCharacterMessageID(ClientListEntry* character, buf->chat_type = chat_type; strn0cpy(buf->client_name, character->name(), sizeof(buf->client_name)); buf->args_size = args_size; - memcpy(buf->args, serialized_args.buffer(), serialized_args.size()); + if (argbuf.size() > 0) + { + memcpy(buf->args, argbuf.buffer(), argbuf.size()); + } character->Server()->SendPacket(pack.get()); } @@ -1829,3 +1909,98 @@ std::map ClientList::GetGuildClientsWithTributeOptIn( } return guild_members; } + +void ClientList::RebuildZoneServerCaches() +{ + // Clear without freeing memory (buckets stay allocated) + m_gm_zone_server_ids.clear(); + m_guild_zone_server_ids.clear(); + + LinkedListIterator iterator(clientlist); + iterator.Reset(); + + while (iterator.MoreElements()) { + ClientListEntry* cle = iterator.GetData(); + + if (cle->Online() != CLE_Status::InZone || !cle->Server()) { + iterator.Advance(); + continue; + } + + uint32_t server_id = cle->Server()->GetID(); + + // Track GM zone server + if (cle->GetGM()) { + m_gm_zone_server_ids.insert(server_id); + } + + // Track guild zone servers + if (cle->GuildID() > 0) { + auto& guild_set = m_guild_zone_server_ids[cle->GuildID()]; + guild_set.insert(server_id); + } + + iterator.Advance(); + } +} + +std::vector ClientList::GetGuildZoneServers(uint32 guild_id) +{ + if (RuleB(World, RealTimeCalculateGuilds)) { + std::vector zone_server_ids; + std::unordered_set seen_ids; + + LinkedListIterator iterator(clientlist); + + iterator.Reset(); + while (iterator.MoreElements()) { + ClientListEntry *cle = iterator.GetData(); + + if (cle->Online() != CLE_Status::InZone) { + iterator.Advance(); + continue; + } + + if (!cle->Server()) { + iterator.Advance(); + continue; + } + + if (cle->GuildID() == guild_id) { + uint32_t id = cle->Server()->GetID(); + if (seen_ids.insert(id).second) { + zone_server_ids.emplace_back(id); + } + } + + iterator.Advance(); + } + + return zone_server_ids; + } + + auto it = m_guild_zone_server_ids.find(guild_id); + if (it == m_guild_zone_server_ids.end()) { + return {}; + } + return {it->second.begin(), it->second.end()}; +} + +void ClientList::AddToZoneServerCaches(ClientListEntry* cle) +{ + if (!cle || cle->Online() != CLE_Status::InZone || !cle->Server()) { + return; + } + + uint32_t server_id = cle->Server()->GetID(); + + // Add GM zone server if applicable + if (cle->GetGM()) { + m_gm_zone_server_ids.insert(server_id); + } + + // Add guild zone server if applicable + if (cle->GuildID() > 0) { + m_guild_zone_server_ids[cle->GuildID()].insert(server_id); + } +} diff --git a/world/clientlist.h b/world/clientlist.h index f075691e5..b158ad28d 100644 --- a/world/clientlist.h +++ b/world/clientlist.h @@ -45,13 +45,13 @@ public: void SendClientVersionSummary(const char *Name); void SendLFGMatches(ServerLFGMatchesRequest_Struct *LFGMatchesRequest); void ConsoleSendWhoAll(const char* to, int16 admin, Who_All_Struct* whom, WorldTCPConnection* connection); - void SendCLEList(const int16& admin, const char* to, WorldTCPConnection* connection, const char* iName = 0); + void SendCLEList(const int16& admin, const char* to, WorldTCPConnection* connection, const char* search_criteria = 0); bool SendPacket(const char* to, ServerPacket* pack); void ClientUpdate(ZoneServer* zoneserver, ServerClientList_Struct* scl); void CLERemoveZSRef(ZoneServer* iZS); - ClientListEntry* CheckAuth(uint32 iLSID, const char* iKey); + ClientListEntry* CheckAuth(uint32 loginserver_account_id, const char* key); ClientListEntry* FindCharacter(const char* name); ClientListEntry* FindCLEByAccountID(uint32 iAccID); ClientListEntry* FindCLEByCharacterID(uint32 iCharID); @@ -59,14 +59,14 @@ public: void DisconnectByIP(uint32 in_ip); void CLCheckStale(); void CLEKeepAlive(uint32 numupdates, uint32* wid); - void CLEAdd(uint32 iLSID, const char* iLoginServerName, const char* iLoginName, const char* iLoginKey, int16 iWorldAdmin = AccountStatus::Player, uint32 ip = 0, uint8 local=0); + void CLEAdd(uint32 login_server_id, const char* login_server_name, const char* login_name, const char* login_key, int16 world_admin = AccountStatus::Player, uint32 ip_address = 0, uint8 is_local=0); void UpdateClientGuild(uint32 char_id, uint32 guild_id); bool IsAccountInGame(uint32 iLSID); int GetClientCount(); void GetClients(const char *zone_name, std::vector &into); - void GetClientList(Json::Value &response); + void GetClientList(Json::Value &response, bool full_list = false); void GetGuildClientList(Json::Value& response, uint32 guild_id); void SendCharacterMessage(uint32_t character_id, int chat_type, const std::string& message); @@ -76,6 +76,21 @@ public: void SendCharacterMessageID(const std::string& character_name, int chat_type, int eqstr_id, std::initializer_list args = {}); void SendCharacterMessageID(ClientListEntry* character, int chat_type, int eqstr_id, std::initializer_list args = {}); + void AddToZoneServerCaches(ClientListEntry* cle); + void RebuildZoneServerCaches(); + + std::vector GetGuildZoneServers(uint32 guild_id); + inline std::vector GetZoneServersWithGMs() + { + return {m_gm_zone_server_ids.begin(), m_gm_zone_server_ids.end()}; + } + + static ClientList* Instance() + { + static ClientList instance; + return &instance; + } + private: void OnTick(EQ::Timer *t); inline uint32 GetNextCLEID() { return NextCLEID++; } @@ -90,6 +105,11 @@ private: std::unique_ptr m_tick; + + // Zone server routing caches + Timer m_poll_cache_timer; + std::unordered_set m_gm_zone_server_ids; + std::unordered_map> m_guild_zone_server_ids; }; #endif /*CLIENTLIST_H_*/ diff --git a/world/console.cpp b/world/console.cpp index 25a7cd793..41906239b 100644 --- a/world/console.cpp +++ b/world/console.cpp @@ -33,10 +33,6 @@ #include "../common/zone_store.h" #include -extern ClientList client_list; -extern ZSList zoneserver_list; -extern LoginServerList loginserverlist; - /** * @param username * @param password @@ -95,9 +91,22 @@ void ConsoleApi( BenchTimer timer; timer.reset(); - EQEmuApiWorldDataService::get(response, args); + std::string method = args.empty() ? "" : args[0]; - std::string method = args[0]; + if (method.empty()) { + root["execution_time"] = std::to_string(timer.elapsed()); + root["method"] = method; + root["data"] = response; + root["error"] = "No method specified"; + + std::stringstream payload; + payload << root; + connection->SendLine(payload.str()); + return; + } + + // Safe to call now that args[0] is known to exist + EQEmuApiWorldDataService::get(response, args); root["execution_time"] = std::to_string(timer.elapsed()); root["method"] = method; @@ -105,7 +114,6 @@ void ConsoleApi( std::stringstream payload; payload << root; - connection->SendLine(payload.str()); } @@ -137,7 +145,7 @@ void ConsoleZoneStatus( ) { WorldConsoleTCPConnection console_connection(connection); - zoneserver_list.SendZoneStatus(0, connection->Admin(), &console_connection); + ZSList::Instance()->SendZoneStatus(0, connection->Admin(), &console_connection); } /** @@ -181,7 +189,7 @@ void ConsoleWho( } WorldConsoleTCPConnection console_connection(connection); - client_list.ConsoleSendWhoAll(0, connection->Admin(), &whom, &console_connection); + ClientList::Instance()->ConsoleSendWhoAll(0, connection->Admin(), &whom, &console_connection); } /** @@ -204,7 +212,7 @@ void ConsoleUptime( ServerUptime_Struct *sus = (ServerUptime_Struct *) pack->pBuffer; snprintf(sus->adminname, sizeof(sus->adminname), "*%s", connection->UserName().c_str()); sus->zoneserverid = Strings::ToInt(args[0]); - ZoneServer *zs = zoneserver_list.FindByID(sus->zoneserverid); + ZoneServer *zs = ZSList::Instance()->FindByID(sus->zoneserverid); if (zs) { zs->SendPacket(pack); } @@ -279,7 +287,7 @@ void ConsoleEmote( join_args.erase(join_args.begin(), join_args.begin() + 2); if (strcasecmp(args[0].c_str(), "world") == 0) { - zoneserver_list.SendEmoteMessageRaw( + ZSList::Instance()->SendEmoteMessageRaw( 0, 0, AccountStatus::Player, @@ -288,7 +296,7 @@ void ConsoleEmote( ); } else { - ZoneServer *zs = zoneserver_list.FindByName(args[0].c_str()); + ZoneServer *zs = ZSList::Instance()->FindByName(args[0].c_str()); if (zs != 0) { zs->SendEmoteMessageRaw( 0, @@ -299,7 +307,7 @@ void ConsoleEmote( ); } else { - zoneserver_list.SendEmoteMessageRaw( + ZSList::Instance()->SendEmoteMessageRaw( args[0].c_str(), 0, AccountStatus::Player, @@ -360,7 +368,7 @@ void ConsoleTell( auto join_args = args; join_args.erase(join_args.begin(), join_args.begin() + 1); - zoneserver_list.SendChannelMessage(tmpname, to.c_str(), ChatChannel_Tell, 0, Strings::Join(join_args, " ").c_str()); + ZSList::Instance()->SendChannelMessage(tmpname, to.c_str(), ChatChannel_Tell, 0, Strings::Join(join_args, " ").c_str()); } /** @@ -381,7 +389,7 @@ void ConsoleBroadcast( char tmpname[64]; tmpname[0] = '*'; strcpy(&tmpname[1], connection->UserName().c_str()); - zoneserver_list.SendChannelMessage(tmpname, 0, ChatChannel_Broadcast, 0, Strings::Join(args, " ").c_str()); + ZSList::Instance()->SendChannelMessage(tmpname, 0, ChatChannel_Broadcast, 0, Strings::Join(args, " ").c_str()); } /** @@ -402,7 +410,7 @@ void ConsoleGMSay( char tmpname[64]; tmpname[0] = '*'; strcpy(&tmpname[1], connection->UserName().c_str()); - zoneserver_list.SendChannelMessage(tmpname, 0, ChatChannel_GMSAY, 0, Strings::Join(args, " ").c_str()); + ZSList::Instance()->SendChannelMessage(tmpname, 0, ChatChannel_GMSAY, 0, Strings::Join(args, " ").c_str()); } /** @@ -435,7 +443,7 @@ void ConsoleGuildSay( Strings::Join(join_args, " ") ); - zoneserver_list.SendEmoteMessage(0, guild_id, AccountStatus::Player, Chat::Guild, message.c_str()); + ZSList::Instance()->SendEmoteMessage(0, guild_id, AccountStatus::Player, Chat::Guild, message.c_str()); } /** @@ -456,7 +464,7 @@ void ConsoleOOC( char tmpname[64]; tmpname[0] = '*'; strcpy(&tmpname[1], connection->UserName().c_str()); - zoneserver_list.SendChannelMessage(tmpname, 0, ChatChannel_OOC, 0, Strings::Join(args, " ").c_str()); + ZSList::Instance()->SendChannelMessage(tmpname, 0, ChatChannel_OOC, 0, Strings::Join(args, " ").c_str()); } /** @@ -477,7 +485,7 @@ void ConsoleAuction( char tmpname[64]; tmpname[0] = '*'; strcpy(&tmpname[1], connection->UserName().c_str()); - zoneserver_list.SendChannelMessage(tmpname, 0, ChatChannel_Auction, 0, Strings::Join(args, " ").c_str()); + ZSList::Instance()->SendChannelMessage(tmpname, 0, ChatChannel_Auction, 0, Strings::Join(args, " ").c_str()); } /** @@ -506,7 +514,7 @@ void ConsoleKick( strcpy(skp->adminname, tmpname); strcpy(skp->name, args[0].c_str()); skp->adminrank = connection->Admin(); - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); delete pack; } @@ -522,8 +530,8 @@ void ConsoleLock( ) { WorldConfig::LockWorld(); - if (loginserverlist.Connected()) { - loginserverlist.SendStatus(); + if (LoginServerList::Instance()->Connected()) { + LoginServerList::Instance()->SendStatus(); connection->SendLine("World locked."); } else { @@ -543,8 +551,8 @@ void ConsoleUnlock( ) { WorldConfig::UnlockWorld(); - if (loginserverlist.Connected()) { - loginserverlist.SendStatus(); + if (LoginServerList::Instance()->Connected()) { + LoginServerList::Instance()->SendStatus(); connection->SendLine("World unlocked."); } else { @@ -592,10 +600,10 @@ void ConsoleZoneShutdown( ZoneServer *zs = 0; if (s->zone_server_id != 0) { - zs = zoneserver_list.FindByID(s->zone_server_id); + zs = ZSList::Instance()->FindByID(s->zone_server_id); } else if (s->zone_id != 0) { - zs = zoneserver_list.FindByName(ZoneName(s->zone_id)); + zs = ZSList::Instance()->FindByName(ZoneName(s->zone_id)); } else { connection->SendLine("Error: ZoneShutdown: neither ID nor name specified"); @@ -636,14 +644,14 @@ void ConsoleZoneBootup( strcpy(&tmpname[1], connection->UserName().c_str()); if (args.size() > 2) { - zoneserver_list.SOPZoneBootup( + ZSList::Instance()->SOPZoneBootup( tmpname, Strings::ToInt(args[0]), args[1].c_str(), (bool) (strcasecmp(args[1].c_str(), "static") == 0)); } else { - zoneserver_list.SOPZoneBootup(tmpname, Strings::ToInt(args[0]), args[1].c_str(), false); + ZSList::Instance()->SOPZoneBootup(tmpname, Strings::ToInt(args[0]), args[1].c_str(), false); } } } @@ -665,7 +673,7 @@ void ConsoleZoneLock( if (strcasecmp(args[0].c_str(), "list") == 0) { WorldConsoleTCPConnection console_connection(connection); - zoneserver_list.ListLockedZones(0, &console_connection); + ZSList::Instance()->ListLockedZones(0, &console_connection); } else if (strcasecmp(args[0].c_str(), "lock") == 0 && connection->Admin() >= 101) { if (args.size() < 2) { @@ -674,8 +682,8 @@ void ConsoleZoneLock( uint16 tmp = ZoneID(args[1].c_str()); if (tmp) { - if (zoneserver_list.SetLockedZone(tmp, true)) { - zoneserver_list.SendEmoteMessage( + if (ZSList::Instance()->SetLockedZone(tmp, true)) { + ZSList::Instance()->SendEmoteMessage( 0, 0, AccountStatus::QuestTroupe, @@ -701,8 +709,8 @@ void ConsoleZoneLock( uint16 tmp = ZoneID(args[1].c_str()); if (tmp) { - if (zoneserver_list.SetLockedZone(tmp, false)) { - zoneserver_list.SendEmoteMessage( + if (ZSList::Instance()->SetLockedZone(tmp, false)) { + ZSList::Instance()->SendEmoteMessage( 0, 0, AccountStatus::QuestTroupe, @@ -822,7 +830,7 @@ void ConsoleWorldShutdown( int32 time, interval; if (Strings::IsNumber(args[0]) && Strings::IsNumber(args[1]) && ((time = Strings::ToInt(args[0])) > 0) && ((interval = Strings::ToInt(args[1])) > 0)) { - zoneserver_list.WorldShutDown(time, interval); + ZSList::Instance()->WorldShutDown(time, interval); } else { connection->SendLine("Usage: worldshutdown [now] [disable] ([time] [interval])"); @@ -830,19 +838,19 @@ void ConsoleWorldShutdown( } else if (args.size() == 1) { if (strcasecmp(args[0].c_str(), "now") == 0) { - zoneserver_list.WorldShutDown(0, 0); + ZSList::Instance()->WorldShutDown(0, 0); } else if (strcasecmp(args[0].c_str(), "disable") == 0) { connection->SendLine("[SYSTEM] World shutdown has been aborted."); - zoneserver_list.SendEmoteMessage( + ZSList::Instance()->SendEmoteMessage( 0, 0, AccountStatus::Player, Chat::Yellow, "[SYSTEM] World shutdown has been aborted." ); - zoneserver_list.shutdowntimer->Disable(); - zoneserver_list.reminder->Disable(); + ZSList::Instance()->shutdowntimer->Disable(); + ZSList::Instance()->reminder->Disable(); } else { connection->SendLine("Usage: worldshutdown [now] [disable] ([time] [interval])"); @@ -866,7 +874,7 @@ void ConsoleIpLookup( { if (!args.empty()) { WorldConsoleTCPConnection console_connection(connection); - client_list.SendCLEList(connection->Admin(), nullptr, &console_connection, args[0].c_str()); + ClientList::Instance()->SendCLEList(connection->Admin(), nullptr, &console_connection, args[0].c_str()); } } @@ -895,7 +903,7 @@ void ConsoleSignalCharByName( CZS->update_identifier = update_identifier; CZS->signal_id = Strings::ToInt(args[1]); strn0cpy(CZS->client_name, (char *) args[0].c_str(), 64); - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); safe_delete(pack); } @@ -911,11 +919,7 @@ void ConsoleReloadWorld( ) { connection->SendLine("Reloading World..."); - auto pack = new ServerPacket(ServerOP_ReloadWorld, sizeof(ReloadWorld_Struct)); - ReloadWorld_Struct *RW = (ReloadWorld_Struct *) pack->pBuffer; - RW->global_repop = ReloadWorld::Repop; - zoneserver_list.SendPacket(pack); - safe_delete(pack); + ZSList::Instance()->SendServerReload(ServerReload::Type::WorldRepop, nullptr); } auto debounce_reload = std::chrono::system_clock::now(); @@ -952,7 +956,7 @@ void ConsoleReloadZoneQuests( auto *hot_reload_quests = (HotReloadQuestsStruct *) pack->pBuffer; strn0cpy(hot_reload_quests->zone_short_name, (char *) zone_short_name.c_str(), 200); - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); safe_delete(pack); } @@ -1068,7 +1072,7 @@ void ConsoleCrossZoneCastSpell( strn0cpy(CZS->client_name, name.c_str(), sizeof(CZS->client_name)); - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); safe_delete(pack); connection->SendLine( @@ -1119,7 +1123,7 @@ void ConsoleWorldWideCastSpell( WWS->min_status = min_status; WWS->max_status = max_status; - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); safe_delete(pack); connection->SendLine( @@ -1211,7 +1215,7 @@ void ConsoleCrossZoneMove( const auto& zone_short_name = !Strings::IsNumber(args[2]) ? args[2] : ""; const uint16 instance_id = Strings::IsNumber(args[2]) ? static_cast(Strings::ToUnsignedInt(args[2])) : 0; - const auto& z = !zone_short_name.empty() ? zone_store.GetZone(zone_short_name) : nullptr; + const auto& z = !zone_short_name.empty() ? ZoneStore::Instance()->GetZone(zone_short_name) : nullptr; if (z && !z->id) { connection->SendLine(fmt::format("No zone with the short name '{}' exists.", zone_short_name)); @@ -1250,7 +1254,7 @@ void ConsoleCrossZoneMove( m->zone_short_name = zone_short_name; } - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); safe_delete(pack); connection->SendLine( @@ -1281,7 +1285,7 @@ void ConsoleWorldWideMove( const auto& zone_short_name = !Strings::IsNumber(args[2]) ? args[2] : ""; const uint16 instance_id = Strings::IsNumber(args[2]) ? static_cast(Strings::ToUnsignedInt(args[2])) : 0; - const auto& z = !zone_short_name.empty() ? zone_store.GetZone(zone_short_name) : nullptr; + const auto& z = !zone_short_name.empty() ? ZoneStore::Instance()->GetZone(zone_short_name) : nullptr; if (z && !z->id) { connection->SendLine(fmt::format("No zone with the short name '{}' exists.", zone_short_name)); @@ -1309,7 +1313,7 @@ void ConsoleWorldWideMove( strn0cpy(WWM->zone_short_name, zone_short_name.c_str(), sizeof(WWM->zone_short_name)); - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); safe_delete(pack); connection->SendLine( @@ -1322,6 +1326,42 @@ void ConsoleWorldWideMove( ); } +void ConsoleWWMarquee( + EQ::Net::ConsoleServerConnection* connection, + const std::string& command, + const std::vector& args +) +{ + if (args.size() < 2) { + connection->SendLine("Usage: wwmarquee "); + return; + } + + const uint32 type = Strings::IsNumber(args[0]) ? Strings::ToUnsignedInt(args[0]) : 0; + std::string message = Strings::Join(std::vector(args.begin() + 1, args.end()), " "); + if (message.empty()) { + connection->SendLine("Message cannot be empty."); + return; + } + + auto pack = new ServerPacket(ServerOP_WWMarquee, sizeof(WWMarquee_Struct)); + auto* wwm = (WWMarquee_Struct*)pack->pBuffer; + + wwm->type = type; + wwm->priority = 510; + wwm->fade_in = 0; + wwm->fade_out = 0; + wwm->duration = 5000; + wwm->min_status = AccountStatus::Player; + wwm->max_status = AccountStatus::Player; + + strn0cpy(wwm->message, message.c_str(), sizeof(wwm->message)); + + ZSList::Instance()->SendPacket(pack); + safe_delete(pack); + + connection->SendLine(fmt::format("Sent world marquee type {}: {}", type, message)); +} /** * @param console @@ -1359,6 +1399,12 @@ void RegisterConsoleFunctions(std::unique_ptr& console) console->RegisterCall("whoami", 50, "whoami", std::bind(ConsoleWhoami, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); console->RegisterCall("worldshutdown", 200, "worldshutdown", std::bind(ConsoleWorldShutdown, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); console->RegisterCall("wwcast", 50, "wwcast [spell_id] [min_status] [max_status] - min_status and max_status are optional", std::bind(ConsoleWorldWideCastSpell, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + console->RegisterCall( + "wwmarquee", + 50, + "wwmarquee ", + std::bind(ConsoleWWMarquee, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3) + ); console->RegisterCall("wwmove", 50, "wwmove [instance_id|zone_short_name] [min_status] [max_status] - min_status and max_status are optional, instance_id and zone_short_name are interchangeable", std::bind(ConsoleWorldWideMove, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); console->RegisterCall("zonebootup", 150, "zonebootup [zone_server_id] [zone_short_name]", std::bind(ConsoleZoneBootup, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); console->RegisterCall("zonelock", 150, "zonelock [list|lock|unlock] [zone_short_name]", std::bind(ConsoleZoneLock, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); diff --git a/world/dynamic_zone.cpp b/world/dynamic_zone.cpp index 3a23c186d..fd9406f5c 100644 --- a/world/dynamic_zone.cpp +++ b/world/dynamic_zone.cpp @@ -8,9 +8,6 @@ #include "../common/eqemu_logsys.h" #include "../common/repositories/instance_list_repository.h" -extern ClientList client_list; -extern ZSList zoneserver_list; - Database& DynamicZone::GetDatabase() { return database; @@ -18,7 +15,7 @@ Database& DynamicZone::GetDatabase() bool DynamicZone::SendServerPacket(ServerPacket* packet) { - return zoneserver_list.SendPacket(packet); + return ZSList::Instance()->SendPacket(packet); } DynamicZone* DynamicZone::FindDynamicZoneByID(uint32_t dz_id) @@ -41,7 +38,7 @@ void DynamicZone::ChooseNewLeader() auto it = std::find_if(m_members.begin(), m_members.end(), [&](const DynamicZoneMember& member) { if (member.id != GetLeaderID() && member.IsOnline()) { - auto member_cle = client_list.FindCLEByCharacterID(member.id); + auto member_cle = ClientList::Instance()->FindCLEByCharacterID(member.id); return (member_cle && member_cle->GetOnline() == CLE_Status::InZone); } return false; @@ -91,7 +88,7 @@ DynamicZoneStatus DynamicZone::Process() { status = DynamicZoneStatus::Expired; - auto dz_zoneserver = zoneserver_list.FindByInstanceID(GetInstanceID()); + auto dz_zoneserver = ZSList::Instance()->FindByInstanceID(GetInstanceID()); if (!dz_zoneserver || dz_zoneserver->NumPlayers() == 0) // no clients inside dz { status = DynamicZoneStatus::ExpiredEmpty; @@ -119,7 +116,7 @@ void DynamicZone::SendZonesDynamicZoneDeleted() auto pack = std::make_unique(ServerOP_DzDeleted, pack_size); auto buf = reinterpret_cast(pack->pBuffer); buf->dz_id = GetID(); - zoneserver_list.SendPacket(pack.get()); + ZSList::Instance()->SendPacket(pack.get()); } void DynamicZone::SetSecondsRemaining(uint32_t seconds_remaining) @@ -137,8 +134,11 @@ void DynamicZone::SetSecondsRemaining(uint32_t seconds_remaining) m_expire_time = now + new_remaining; m_duration = std::chrono::duration_cast(m_expire_time - m_start_time); - InstanceListRepository::UpdateDuration(database, - GetInstanceID(), static_cast(m_duration.count())); + InstanceListRepository::UpdateDuration( + database, + GetInstanceID(), + static_cast(m_duration.count()) + ); SendZonesDurationUpdate(); // update zone caches and actual instance's timer } @@ -151,7 +151,7 @@ void DynamicZone::SendZonesDurationUpdate() auto packbuf = reinterpret_cast(pack->pBuffer); packbuf->dz_id = GetID(); packbuf->seconds = static_cast(m_duration.count()); - zoneserver_list.SendPacket(pack.get()); + ZSList::Instance()->SendPacket(pack.get()); } void DynamicZone::SendZonesLeaderChanged() @@ -161,133 +161,7 @@ void DynamicZone::SendZonesLeaderChanged() auto buf = reinterpret_cast(pack->pBuffer); buf->dz_id = GetID(); buf->leader_id = GetLeaderID(); - zoneserver_list.SendPacket(pack.get()); -} - -void DynamicZone::HandleZoneMessage(ServerPacket* pack) -{ - switch (pack->opcode) - { - case ServerOP_DzCreated: - { - dynamic_zone_manager.CacheNewDynamicZone(pack); - break; - } - case ServerOP_DzSetCompass: - case ServerOP_DzSetSafeReturn: - case ServerOP_DzSetZoneIn: - { - auto buf = reinterpret_cast(pack->pBuffer); - auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id); - if (dz) - { - if (pack->opcode == ServerOP_DzSetCompass) - { - dz->SetCompass(buf->zone_id, buf->x, buf->y, buf->z, false); - } - else if (pack->opcode == ServerOP_DzSetSafeReturn) - { - dz->SetSafeReturn(buf->zone_id, buf->x, buf->y, buf->z, buf->heading, false); - } - else if (pack->opcode == ServerOP_DzSetZoneIn) - { - dz->SetZoneInLocation(buf->x, buf->y, buf->z, buf->heading, false); - } - } - zoneserver_list.SendPacket(pack); - break; - } - case ServerOP_DzSetSwitchID: - { - auto buf = reinterpret_cast(pack->pBuffer); - auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id); - if (dz) - { - dz->ProcessSetSwitchID(buf->dz_switch_id); - } - zoneserver_list.SendPacket(pack); - break; - } - case ServerOP_DzAddRemoveMember: - { - auto buf = reinterpret_cast(pack->pBuffer); - auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id); - if (dz) - { - auto status = static_cast(buf->character_status); - dz->ProcessMemberAddRemove({ buf->character_id, buf->character_name, status }, buf->removed); - } - zoneserver_list.SendPacket(pack); - break; - } - case ServerOP_DzSwapMembers: - { - auto buf = reinterpret_cast(pack->pBuffer); - auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id); - if (dz) - { - // we add first in world so new member can be chosen if leader is removed - auto status = static_cast(buf->add_character_status); - dz->ProcessMemberAddRemove({ buf->add_character_id, buf->add_character_name, status }, false); - dz->ProcessMemberAddRemove({ buf->remove_character_id, buf->remove_character_name }, true); - } - zoneserver_list.SendPacket(pack); - break; - } - case ServerOP_DzRemoveAllMembers: - { - auto buf = reinterpret_cast(pack->pBuffer); - auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id); - if (dz) - { - dz->ProcessRemoveAllMembers(); - } - zoneserver_list.SendPacket(pack); - break; - } - case ServerOP_DzSetSecondsRemaining: - { - auto buf = reinterpret_cast(pack->pBuffer); - auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id); - if (dz) - { - dz->SetSecondsRemaining(buf->seconds); - } - break; - } - case ServerOP_DzGetMemberStatuses: - { - auto buf = reinterpret_cast(pack->pBuffer); - auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id); - if (dz) - { - dz->SendZoneMemberStatuses(buf->sender_zone_id, buf->sender_instance_id); - } - break; - } - case ServerOP_DzUpdateMemberStatus: - { - auto buf = reinterpret_cast(pack->pBuffer); - auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id); - if (dz) - { - auto status = static_cast(buf->status); - dz->ProcessMemberStatusChange(buf->character_id, status); - } - zoneserver_list.SendPacket(pack); - break; - } - case ServerOP_DzMovePC: - { - auto buf = reinterpret_cast(pack->pBuffer); - auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id); - if (dz && dz->HasMember(buf->character_id)) - { - zoneserver_list.SendPacket(buf->sender_zone_id, buf->sender_instance_id, pack); - } - break; - } - }; + ZSList::Instance()->SendPacket(pack.get()); } void DynamicZone::ProcessMemberAddRemove(const DynamicZoneMember& member, bool removed) @@ -321,7 +195,7 @@ void DynamicZone::SendZonesExpireWarning(uint32_t minutes_remaining) auto buf = reinterpret_cast(pack->pBuffer); buf->dz_id = GetID(); buf->minutes_remaining = minutes_remaining; - zoneserver_list.SendPacket(pack.get()); + ZSList::Instance()->SendPacket(pack.get()); } void DynamicZone::SendZoneMemberStatuses(uint16_t zone_id, uint16_t instance_id) @@ -340,21 +214,21 @@ void DynamicZone::SendZoneMemberStatuses(uint16_t zone_id, uint16_t instance_id) buf->entries[i].online_status = static_cast(m_members[i].status); } - zoneserver_list.SendPacket(zone_id, instance_id, pack.get()); + ZSList::Instance()->SendPacket(zone_id, instance_id, pack.get()); } void DynamicZone::CacheMemberStatuses() { - if (m_has_member_statuses) + if (m_has_member_statuses || m_members.empty()) { return; } // called when a new dz is cached to fill member statuses - std::string zone_name{}; + std::string zone_name; std::vector all_clients; - all_clients.reserve(client_list.GetClientCount()); - client_list.GetClients(zone_name.c_str(), all_clients); + all_clients.reserve(ClientList::Instance()->GetClientCount()); + ClientList::Instance()->GetClients(zone_name.c_str(), all_clients); for (const auto& member : m_members) { diff --git a/world/dynamic_zone.h b/world/dynamic_zone.h index 6a3d5c953..48c702789 100644 --- a/world/dynamic_zone.h +++ b/world/dynamic_zone.h @@ -22,7 +22,6 @@ public: using DynamicZoneBase::DynamicZoneBase; // inherit base constructors static DynamicZone* FindDynamicZoneByID(uint32_t dz_id); - static void HandleZoneMessage(ServerPacket* pack); void SetSecondsRemaining(uint32_t seconds_remaining) override; @@ -33,7 +32,7 @@ public: protected: Database& GetDatabase() override; void ProcessMemberAddRemove(const DynamicZoneMember& member, bool removed) override; - bool ProcessMemberStatusChange(uint32_t member_id, DynamicZoneMemberStatus status) override; + bool ProcessMemberStatusChange(uint32_t character_id, DynamicZoneMemberStatus status) override; bool SendServerPacket(ServerPacket* packet) override; private: diff --git a/world/dynamic_zone_manager.cpp b/world/dynamic_zone_manager.cpp index 751246b2d..8cf87affe 100644 --- a/world/dynamic_zone_manager.cpp +++ b/world/dynamic_zone_manager.cpp @@ -1,13 +1,13 @@ #include "dynamic_zone_manager.h" #include "dynamic_zone.h" +#include "cliententry.h" +#include "clientlist.h" #include "worlddb.h" #include "zonelist.h" #include "zoneserver.h" #include "../common/rulesys.h" -#include "../common/repositories/expeditions_repository.h" -#include "../common/repositories/expedition_lockouts_repository.h" - -extern ZSList zoneserver_list; +#include "../common/repositories/dynamic_zone_lockouts_repository.h" +#include DynamicZoneManager dynamic_zone_manager; @@ -27,6 +27,8 @@ void DynamicZoneManager::PurgeExpiredDynamicZones() { LogDynamicZones("Purging [{}] dynamic zone(s)", dz_ids.size()); + DynamicZoneLockoutsRepository::DeleteWhere(database, + fmt::format("dynamic_zone_id IN ({})", fmt::join(dz_ids, ","))); DynamicZoneMembersRepository::DeleteWhere(database, fmt::format("dynamic_zone_id IN ({})", Strings::Join(dz_ids, ","))); DynamicZonesRepository::DeleteWhere(database, @@ -34,8 +36,7 @@ void DynamicZoneManager::PurgeExpiredDynamicZones() } } -DynamicZone* DynamicZoneManager::CreateNew( - DynamicZone& dz_request, const std::vector& members) +DynamicZone* DynamicZoneManager::TryCreate(DynamicZone& dz_request, const std::vector& members) { // this creates a new dz instance and saves it to both db and cache uint32_t dz_id = dz_request.Create(); @@ -46,16 +47,13 @@ DynamicZone* DynamicZoneManager::CreateNew( } auto dz = std::make_unique(dz_request); - if (!members.empty()) - { - dz->SaveMembers(members); - dz->CacheMemberStatuses(); - } + dz->SaveMembers(members); + dz->CacheMemberStatuses(); LogDynamicZones("Created new dz [{}] for zone [{}]", dz_id, dz_request.GetZoneID()); - auto pack = dz->CreateServerDzCreatePacket(0, 0); - zoneserver_list.SendPacket(pack.get()); + auto pack = dz->CreateServerPacket(0, 0); + ZSList::Instance()->SendPacket(pack.get()); auto inserted = dynamic_zone_cache.emplace(dz_id, std::move(dz)); return inserted.first->second.get(); @@ -63,38 +61,38 @@ DynamicZone* DynamicZoneManager::CreateNew( void DynamicZoneManager::CacheNewDynamicZone(ServerPacket* pack) { - auto buf = reinterpret_cast(pack->pBuffer); + auto buf = reinterpret_cast(pack->pBuffer); auto new_dz = std::make_unique(); - new_dz->LoadSerializedDzPacket(buf->cereal_data, buf->cereal_size); + new_dz->Unserialize({ buf->cereal_data, buf->cereal_size }); new_dz->CacheMemberStatuses(); // reserialize with member statuses cached before forwarding (restore origin zone) - auto repack = new_dz->CreateServerDzCreatePacket(buf->origin_zone_id, buf->origin_instance_id); + auto repack = new_dz->CreateServerPacket(buf->origin_zone_id, buf->origin_instance_id); - uint32_t dz_id = new_dz->GetID(); - dynamic_zone_cache.emplace(dz_id, std::move(new_dz)); - LogDynamicZones("Cached new dynamic zone [{}]", dz_id); + dynamic_zone_cache.emplace(buf->dz_id, std::move(new_dz)); + LogDynamicZones("Cached new dynamic zone [{}]", buf->dz_id); - zoneserver_list.SendPacket(repack.get()); + ZSList::Instance()->SendPacket(repack.get()); } void DynamicZoneManager::CacheAllFromDatabase() { BenchTimer bench; - auto dynamic_zones = DynamicZonesRepository::AllWithInstanceNotExpired(database); - auto dynamic_zone_members = DynamicZoneMembersRepository::GetAllWithNames(database); + auto dzs = DynamicZonesRepository::AllWithInstanceNotExpired(database); + auto members = DynamicZoneMembersRepository::AllWithNames(database); + auto lockouts = DynamicZoneLockoutsRepository::All(database); dynamic_zone_cache.clear(); - dynamic_zone_cache.reserve(dynamic_zones.size()); + dynamic_zone_cache.reserve(dzs.size()); - for (auto& entry : dynamic_zones) + for (auto& entry : dzs) { uint32_t dz_id = entry.id; auto dz = std::make_unique(std::move(entry)); - for (auto& member : dynamic_zone_members) + for (auto& member : members) { if (member.dynamic_zone_id == dz_id) { @@ -102,6 +100,14 @@ void DynamicZoneManager::CacheAllFromDatabase() } } + for (auto& lockout : lockouts) + { + if (lockout.dynamic_zone_id == dz->GetID()) + { + dz->m_lockouts.emplace_back(dz->GetName(), std::move(lockout)); + } + } + // note leader status won't be updated here until leader is set by owning system (expeditions) dz->CacheMemberStatuses(); @@ -142,24 +148,10 @@ void DynamicZoneManager::Process() dynamic_zone_cache.erase(dz_id); } - // need to look up expedition ids until lockouts are moved to dynamic zones - std::vector expedition_ids; - auto expeditions = ExpeditionsRepository::GetWhere(database, - fmt::format("dynamic_zone_id IN ({})", Strings::Join(dynamic_zone_ids, ","))); - - if (!expeditions.empty()) - { - for (const auto& expedition : expeditions) - { - expedition_ids.emplace_back(expedition.id); - } - ExpeditionLockoutsRepository::DeleteWhere(database, - fmt::format("expedition_id IN ({})", Strings::Join(expedition_ids, ","))); - } - - ExpeditionsRepository::DeleteWhere(database, - fmt::format("dynamic_zone_id IN ({})", Strings::Join(dynamic_zone_ids, ","))); - DynamicZoneMembersRepository::RemoveAllMembers(database, dynamic_zone_ids); + DynamicZoneLockoutsRepository::DeleteWhere(database, + fmt::format("dynamic_zone_id IN ({})", fmt::join(dynamic_zone_ids, ","))); + DynamicZoneMembersRepository::DeleteWhere(database, + fmt::format("dynamic_zone_id IN ({})", fmt::join(dynamic_zone_ids, ","))); DynamicZonesRepository::DeleteWhere(database, fmt::format("id IN ({})", Strings::Join(dynamic_zone_ids, ","))); } @@ -174,3 +166,286 @@ void DynamicZoneManager::LoadTemplates() m_dz_templates[dz_template.id] = dz_template; } } + +void DynamicZoneManager::SendBulkMemberStatuses(uint32_t zone_id, uint16_t inst_id) +{ + std::vector>> 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(size)); + auto buf = reinterpret_cast(pack.pBuffer); + buf->cereal_size = static_cast(sv.size()); + memcpy(buf->cereal_data, sv.data(), sv.size()); + + ZSList::Instance()->SendPacket(zone_id, inst_id, &pack); +} + +void DynamicZoneManager::HandleZoneMessage(ServerPacket* pack) +{ + switch (pack->opcode) + { + case ServerOP_DzCreated: + { + CacheNewDynamicZone(pack); + break; + } + case ServerOP_DzAddPlayer: + { + auto buf = reinterpret_cast(pack->pBuffer); + + ClientListEntry* cle = ClientList::Instance()->FindCharacter(buf->target_name); + if (cle && cle->Server()) + { + // continue in the add target's zone + buf->is_char_online = true; + cle->Server()->SendPacket(pack); + } + else + { + // add target not online, return to inviter + ClientListEntry* inviter_cle = ClientList::Instance()->FindCharacter(buf->requester_name); + if (inviter_cle && inviter_cle->Server()) + { + inviter_cle->Server()->SendPacket(pack); + } + } + break; + } + case ServerOP_DzSaveInvite: + { + auto buf = reinterpret_cast(pack->pBuffer); + if (ClientListEntry* cle = ClientList::Instance()->FindCharacter(buf->target_name)) + { + // store packet on cle and re-send it when client requests it + buf->is_char_online = true; + pack->opcode = ServerOP_DzAddPlayer; + cle->SetPendingDzInvite(pack); + } + break; + } + case ServerOP_DzRequestInvite: + { + auto buf = reinterpret_cast(pack->pBuffer); + if (ClientListEntry* cle = ClientList::Instance()->FindCLEByCharacterID(buf->char_id)) + { + auto invite_pack = cle->GetPendingDzInvite(); + if (invite_pack && cle->Server()) + { + cle->Server()->SendPacket(invite_pack.get()); + } + } + break; + } + case ServerOP_DzMakeLeader: + { + auto buf = reinterpret_cast(pack->pBuffer); + + // notify requester (old leader) and new leader of the result + ZoneServer* new_leader_zs = nullptr; + ClientListEntry* leader_cle = ClientList::Instance()->FindCharacter(buf->new_leader_name); + if (leader_cle && leader_cle->Server()) + { + auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id); + if (dz && dz->GetLeaderID() == buf->requester_id) + { + buf->is_success = dz->SetNewLeader(leader_cle->CharID()); + } + + buf->is_online = true; + new_leader_zs = leader_cle->Server(); + new_leader_zs->SendPacket(pack); + } + + // if old and new leader are in the same zone only send one message + ClientListEntry* requester_cle = ClientList::Instance()->FindCLEByCharacterID(buf->requester_id); + if (requester_cle && requester_cle->Server() && requester_cle->Server() != new_leader_zs) + { + requester_cle->Server()->SendPacket(pack); + } + break; + } + case ServerOP_DzSetCompass: + case ServerOP_DzSetSafeReturn: + case ServerOP_DzSetZoneIn: + { + auto buf = reinterpret_cast(pack->pBuffer); + if (auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id)) + { + if (pack->opcode == ServerOP_DzSetCompass) + { + dz->SetCompass(buf->zone_id, buf->x, buf->y, buf->z, false); + } + else if (pack->opcode == ServerOP_DzSetSafeReturn) + { + dz->SetSafeReturn(buf->zone_id, buf->x, buf->y, buf->z, buf->heading, false); + } + else if (pack->opcode == ServerOP_DzSetZoneIn) + { + dz->SetZoneInLocation(buf->x, buf->y, buf->z, buf->heading, false); + } + } + ZSList::Instance()->SendPacket(pack); + break; + } + case ServerOP_DzSetSwitchID: + { + auto buf = reinterpret_cast(pack->pBuffer); + if (auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id)) + { + dz->ProcessSetSwitchID(buf->dz_switch_id); + } + ZSList::Instance()->SendPacket(pack); + break; + } + case ServerOP_DzAddRemoveMember: + { + auto buf = reinterpret_cast(pack->pBuffer); + if (auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id)) + { + auto status = static_cast(buf->character_status); + dz->ProcessMemberAddRemove({ buf->character_id, buf->character_name, status }, buf->removed); + } + ZSList::Instance()->SendPacket(pack); + break; + } + case ServerOP_DzSwapMembers: + { + auto buf = reinterpret_cast(pack->pBuffer); + if (auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id)) + { + // we add first in world so new member can be chosen if leader is removed + auto status = static_cast(buf->add_character_status); + dz->ProcessMemberAddRemove({ buf->add_character_id, buf->add_character_name, status }, false); + dz->ProcessMemberAddRemove({ buf->remove_character_id, buf->remove_character_name }, true); + } + ZSList::Instance()->SendPacket(pack); + break; + } + case ServerOP_DzRemoveAllMembers: + { + auto buf = reinterpret_cast(pack->pBuffer); + if (auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id)) + { + dz->ProcessRemoveAllMembers(); + } + ZSList::Instance()->SendPacket(pack); + break; + } + case ServerOP_DzSetSecondsRemaining: + { + auto buf = reinterpret_cast(pack->pBuffer); + if (auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id)) + { + dz->SetSecondsRemaining(buf->seconds); + } + break; + } + case ServerOP_DzGetMemberStatuses: + { + auto buf = reinterpret_cast(pack->pBuffer); + if (auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id)) + { + dz->SendZoneMemberStatuses(buf->sender_zone_id, buf->sender_instance_id); + } + break; + } + case ServerOP_DzGetBulkMemberStatuses: + { + auto buf = reinterpret_cast(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(pack->pBuffer); + if (auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id)) + { + auto status = static_cast(buf->status); + dz->ProcessMemberStatusChange(buf->character_id, status); + } + ZSList::Instance()->SendPacket(pack); + break; + } + case ServerOP_DzMovePC: + { + auto buf = reinterpret_cast(pack->pBuffer); + auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id); + if (dz && dz->HasMember(buf->character_id)) + { + ZSList::Instance()->SendPacket(buf->sender_zone_id, buf->sender_instance_id, pack); + } + break; + } + case ServerOP_DzLock: + { + auto buf = reinterpret_cast(pack->pBuffer); + if (auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id)) + { + dz->SetLocked(buf->lock); + } + ZSList::Instance()->SendPacket(pack); + break; + } + case ServerOP_DzReplayOnJoin: + { + auto buf = reinterpret_cast(pack->pBuffer); + if (auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id)) + { + dz->SetReplayOnJoin(buf->enabled); + } + ZSList::Instance()->SendPacket(pack); + break; + } + case ServerOP_DzLockout: + { + auto buf = reinterpret_cast(pack->pBuffer); + if (auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id)) + { + DzLockout lockout{ dz->GetUUID(), dz->GetName(), buf->event_name, buf->expire_time, buf->duration }; + dz->HandleLockoutUpdate(lockout, buf->remove, buf->members_only); + } + ZSList::Instance()->SendPacket(pack); + break; + } + case ServerOP_DzLockoutDuration: + { + auto buf = reinterpret_cast(pack->pBuffer); + if (auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id)) + { + DzLockout lockout{ dz->GetUUID(), dz->GetName(), buf->event_name, buf->expire_time, buf->duration }; + dz->HandleLockoutDuration(lockout, buf->seconds, buf->members_only, false); + } + ZSList::Instance()->SendPacket(pack); + break; + } + case ServerOP_DzCharacterLockout: + { + auto buf = reinterpret_cast(pack->pBuffer); + auto cle = ClientList::Instance()->FindCLEByCharacterID(buf->char_id); + if (cle && cle->Server()) + { + cle->Server()->SendPacket(pack); + } + break; + } + default: + break; + }; +} diff --git a/world/dynamic_zone_manager.h b/world/dynamic_zone_manager.h index 8db6bfd06..0e7077639 100644 --- a/world/dynamic_zone_manager.h +++ b/world/dynamic_zone_manager.h @@ -20,7 +20,8 @@ public: void CacheAllFromDatabase(); void CacheNewDynamicZone(ServerPacket* pack); - DynamicZone* CreateNew(DynamicZone& dz_request, const std::vector& members); + DynamicZone* TryCreate(DynamicZone& dz_request, const std::vector& members); + void HandleZoneMessage(ServerPacket* pack); void LoadTemplates(); void Process(); void PurgeExpiredDynamicZones(); @@ -29,6 +30,8 @@ public: std::unordered_map> dynamic_zone_cache; private: + void SendBulkMemberStatuses(uint32_t zone_id, uint16_t inst_id); + Timer m_process_throttle_timer{}; std::unordered_map m_dz_templates; }; diff --git a/world/eqemu_api_world_data_service.cpp b/world/eqemu_api_world_data_service.cpp index bfa7f6a1b..eff7b7d94 100644 --- a/world/eqemu_api_world_data_service.cpp +++ b/world/eqemu_api_world_data_service.cpp @@ -1,23 +1,3 @@ -/** - * EQEmulator: Everquest Server Emulator - * Copyright (C) 2001-2019 EQEmulator Development Team (https://github.com/EQEmu/Server) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY except by those people which sell it, which - * are required to give you total support for your newly bought product; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR - * A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - #include #include "clientlist.h" #include "cliententry.h" @@ -25,20 +5,25 @@ #include "zoneserver.h" #include "zonelist.h" #include "../common/database_schema.h" +#include "../common/server_reload_types.h" #include "../common/zone_store.h" #include "worlddb.h" #include "wguild_mgr.h" #include "world_config.h" +#include "ucs.h" +#include "queryserv.h" -extern ZSList zoneserver_list; -extern ClientList client_list; extern WorldGuildManager guild_mgr; void callGetZoneList(Json::Value &response) { - for (auto &zone: zoneserver_list.getZoneServerList()) { + for (auto &zone: ZSList::Instance()->getZoneServerList()) { Json::Value row; + if (!zone) { + continue; + } + if (!zone->IsConnected()) { continue; } @@ -47,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(); @@ -124,139 +111,67 @@ void callGetDatabaseSchema(Json::Value &response) response.append(schema); } -void callGetClientList(Json::Value &response) +void callGetClientList(Json::Value &response, const std::vector &args) { - client_list.GetClientList(response); + // if args has "full" + bool full_list = false; + if (args.size() > 1) { + if (args[1] == "full") { + full_list = true; + } + } + + ClientList::Instance()->GetClientList(response, full_list); } - -struct Reload { - std::string command{}; - uint16 opcode; - std::string desc{}; -}; - -std::vector reload_types = { - Reload{.command = "aa", .opcode = ServerOP_ReloadAAData, .desc = "Alternate Advancement"}, - Reload{.command = "alternate_currencies", .opcode = ServerOP_ReloadAlternateCurrencies, .desc = "Alternate Currencies"}, - Reload{.command = "base_data", .opcode = ServerOP_ReloadBaseData, .desc = "Base Data"}, - Reload{.command = "blocked_spells", .opcode = ServerOP_ReloadBlockedSpells, .desc = "Blocked Spells"}, - Reload{.command = "commands", .opcode = ServerOP_ReloadCommands, .desc = "Commands"}, - Reload{.command = "content_flags", .opcode = ServerOP_ReloadContentFlags, .desc = "Content Flags"}, - Reload{.command = "data_buckets_cache", .opcode = ServerOP_ReloadDataBucketsCache, .desc = "Data Buckets Cache"}, - Reload{.command = "doors", .opcode = ServerOP_ReloadDoors, .desc = "Doors"}, - Reload{.command = "dztemplates", .opcode = ServerOP_ReloadDzTemplates, .desc = "Dynamic Zone Templates"}, - Reload{.command = "ground_spawns", .opcode = ServerOP_ReloadGroundSpawns, .desc = "Ground Spawns"}, - Reload{.command = "level_mods", .opcode = ServerOP_ReloadLevelEXPMods, .desc = "Level Mods"}, - Reload{.command = "logs", .opcode = ServerOP_ReloadLogs, .desc = "Log Settings"}, - Reload{.command = "loot", .opcode = ServerOP_ReloadLoot, .desc = "Loot"}, - Reload{.command = "merchants", .opcode = ServerOP_ReloadMerchants, .desc = "Merchants"}, - Reload{.command = "npc_emotes", .opcode = ServerOP_ReloadNPCEmotes, .desc = "NPC Emotes"}, - Reload{.command = "npc_spells", .opcode = ServerOP_ReloadNPCSpells, .desc = "NPC Spells"}, - Reload{.command = "objects", .opcode = ServerOP_ReloadObjects, .desc = "Objects"}, - Reload{.command = "opcodes", .opcode = ServerOP_ReloadOpcodes, .desc = "Opcodes"}, - Reload{.command = "perl_export", .opcode = ServerOP_ReloadPerlExportSettings, .desc = "Perl Event Export Settings"}, - Reload{.command = "rules", .opcode = ServerOP_ReloadRules, .desc = "Rules"}, - Reload{.command = "skill_caps", .opcode = ServerOP_ReloadSkillCaps, .desc = "Skill Caps"}, - Reload{.command = "static", .opcode = ServerOP_ReloadStaticZoneData, .desc = "Static Zone Data"}, - Reload{.command = "tasks", .opcode = ServerOP_ReloadTasks, .desc = "Tasks"}, - Reload{.command = "titles", .opcode = ServerOP_ReloadTitles, .desc = "Titles"}, - Reload{.command = "traps", .opcode = ServerOP_ReloadTraps, .desc = "Traps"}, - Reload{.command = "variables", .opcode = ServerOP_ReloadVariables, .desc = "Variables"}, - Reload{.command = "veteran_rewards", .opcode = ServerOP_ReloadVeteranRewards, .desc = "Veteran Rewards"}, - Reload{.command = "world", .opcode = ServerOP_ReloadWorld, .desc = "World"}, - Reload{.command = "zone_points", .opcode = ServerOP_ReloadZonePoints, .desc = "Zone Points"}, -}; - void getReloadTypes(Json::Value &response) { - for (auto &c: reload_types) { + for (auto &t: ServerReload::GetTypes()) { Json::Value v; - v["command"] = c.command; - v["opcode"] = c.opcode; - v["description"] = c.desc; + v["command"] = std::to_string(t); + v["description"] = ServerReload::GetName(t); response.append(v); } } +void getServerCounts(Json::Value &response, const std::vector &args) +{ + response["zone_count"] = ZSList::Instance()->GetServerListCount(); + response["client_count"] = ClientList::Instance()->GetClientCount(); +} void EQEmuApiWorldDataService::reload(Json::Value &r, const std::vector &args) { std::vector commands{}; - commands.reserve(reload_types.size()); - for (auto &c: reload_types) { - commands.emplace_back(c.command); + commands.reserve(ServerReload::GetTypes().size()); + for (auto &c: ServerReload::GetTypes()) { + commands.emplace_back(std::to_string(c)); } std::string command = !args[1].empty() ? args[1] : ""; if (command.empty()) { - message(r, fmt::format("Need to provide a type to reload. Example(s) [{}]", Strings::Implode("|", commands))); + message(r, fmt::format("Need to provide a type ID to reload. Example(s) [{}]", Strings::Implode("|", commands))); return; } ServerPacket *pack = nullptr; - bool found_command = false; - for (auto &c: reload_types) { - if (command == c.command) { - if (c.command == "world") { - uint8 global_repop = ReloadWorld::NoRepop; + bool found_command = false; - if (Strings::IsNumber(args[2])) { - global_repop = static_cast(Strings::ToUnsignedInt(args[2])); - - if (global_repop > ReloadWorld::ForceRepop) { - global_repop = ReloadWorld::ForceRepop; - } - } - - message( - r, - fmt::format( - "Attempting to reload Quests {}worldwide.", - ( - global_repop ? - ( - global_repop == ReloadWorld::Repop ? - "and repop NPCs " : - "and forcefully repop NPCs " - ) : - "" - ) - ) - ); - - pack = new ServerPacket(ServerOP_ReloadWorld, sizeof(ReloadWorld_Struct)); - auto RW = (ReloadWorld_Struct *) pack->pBuffer; - RW->global_repop = global_repop; - } - else { - pack = new ServerPacket(c.opcode, 0); - message(r, fmt::format("Reloading [{}] globally", c.desc)); - - if (c.opcode == ServerOP_ReloadLogs) { - LogSys.LoadLogDatabaseSettings(); - } - else if (c.opcode == ServerOP_ReloadRules) { - RuleManager::Instance()->LoadRules(&database, RuleManager::Instance()->GetActiveRuleset(), true); - } - } - - found_command = true; + for (auto &t: ServerReload::GetTypes()) { + if (std::to_string(t) == command || Strings::ToLower(ServerReload::GetName(t)) == command) { + message(r, fmt::format("Reloading [{}] globally", ServerReload::GetName(t))); + LogInfo("Queueing reload of type [{}] to zones", ServerReload::GetName(t)); + ZSList::Instance()->QueueServerReload(t); } + found_command = true; } if (!found_command) { message(r, fmt::format("Need to provide a type to reload. Example(s) [{}]", Strings::Implode("|", commands))); return; } - - if (pack) { - zoneserver_list.SendPacket(pack); - } - - safe_delete(pack); } void EQEmuApiWorldDataService::message(Json::Value &r, const std::string &message) @@ -274,7 +189,7 @@ void EQEmuApiWorldDataService::get(Json::Value &r, const std::vectorLocked; } @@ -292,7 +210,6 @@ void EQEmuApiWorldDataService::get(Json::Value &r, const std::vector &args) { - std::string command = !args[1].empty() ? args[1] : ""; if (command.empty()) { return; @@ -338,7 +255,7 @@ void EQEmuApiWorldDataService::callGetGuildDetails(Json::Value &response, const row["tribute"]["time_remaining"] = guild->tribute.time_remaining; row["tribute"]["enabled"] = guild->tribute.enabled; - client_list.GetGuildClientList(response, guild_id); + ClientList::Instance()->GetGuildClientList(response, guild_id); response.append(row); } diff --git a/world/eqw.cpp b/world/eqw.cpp index 1a7e6c147..8b0e17b43 100644 --- a/world/eqw.cpp +++ b/world/eqw.cpp @@ -45,10 +45,7 @@ #include -extern ZSList zoneserver_list; -extern ClientList client_list; extern uint32 numzones; -extern LoginServerList loginserverlist; extern LauncherList launcher_list; extern volatile bool RunLoops; @@ -86,15 +83,15 @@ const std::string &EQW::GetOutput() const { void EQW::LockWorld() { WorldConfig::LockWorld(); - if (loginserverlist.Connected()) { - loginserverlist.SendStatus(); + if (LoginServerList::Instance()->Connected()) { + LoginServerList::Instance()->SendStatus(); } } void EQW::UnlockWorld() { WorldConfig::UnlockWorld(); - if (loginserverlist.Connected()) { - loginserverlist.SendStatus(); + if (LoginServerList::Instance()->Connected()) { + LoginServerList::Instance()->SendStatus(); } } @@ -104,11 +101,11 @@ Const_char *EQW::GetConfig(Const_char *var_name) { } bool EQW::LSConnected() { - return(loginserverlist.Connected()); + return(LoginServerList::Instance()->Connected()); } int EQW::CountZones() { - return(zoneserver_list.GetZoneCount()); + return(ZSList::Instance()->GetZoneCount()); } //returns an array of zone_refs (opaque) @@ -116,7 +113,7 @@ std::vector EQW::ListBootedZones() { std::vector res; std::vector zones; - zoneserver_list.GetZoneIDList(zones); + ZSList::Instance()->GetZoneIDList(zones); std::vector::iterator cur, end; cur = zones.begin(); @@ -131,7 +128,7 @@ std::vector EQW::ListBootedZones() { std::map EQW::GetZoneDetails(Const_char *zone_ref) { std::map res; - ZoneServer *zs = zoneserver_list.FindByID(Strings::ToInt(zone_ref)); + ZoneServer *zs = ZSList::Instance()->FindByID(Strings::ToInt(zone_ref)); if(zs == nullptr) { res["error"] = "Invalid zone."; return(res); @@ -158,7 +155,7 @@ std::map EQW::GetZoneDetails(Const_char *zone_ref) { } int EQW::CountPlayers() { - return(client_list.GetClientCount()); + return(ClientList::Instance()->GetClientCount()); } //returns an array of character names in the zone (empty=all zones) @@ -166,7 +163,7 @@ std::vector EQW::ListPlayers(Const_char *zone_name) { std::vector res; std::vector list; - client_list.GetClients(zone_name, list); + ClientList::Instance()->GetClients(zone_name, list); std::vector::iterator cur, end; cur = list.begin(); @@ -180,7 +177,7 @@ std::vector EQW::ListPlayers(Const_char *zone_name) { std::map EQW::GetPlayerDetails(Const_char *char_name) { std::map res; - ClientListEntry *cle = client_list.FindCharacter(char_name); + ClientListEntry *cle = ClientList::Instance()->FindCharacter(char_name); if(cle == nullptr) { res["error"] = "1"; return(res); @@ -265,7 +262,7 @@ void EQW::CreateLauncher(Const_char *launcher_name, int dynamic_count) { uint32 EQW::CreateGuild(const char* name, uint32 leader_char_id) { uint32 id = guild_mgr.CreateGuild(name, leader_char_id); if(id != GUILD_NONE) - client_list.UpdateClientGuild(leader_char_id, id); + ClientList::Instance()->UpdateClientGuild(leader_char_id, id); return(id); } @@ -286,7 +283,7 @@ bool EQW::SetGuildLeader(uint32 guild_id, uint32 leader_char_id) { } bool EQW::SetGuild(uint32 charid, uint32 guild_id, uint8 rank) { - client_list.UpdateClientGuild(charid, guild_id); + ClientList::Instance()->UpdateClientGuild(charid, guild_id); return(guild_mgr.SetGuild(charid, guild_id, rank)); } @@ -361,7 +358,7 @@ void EQW::ResolveBug(const char *id) { } void EQW::SendMessage(uint32 type, const char *msg) { - zoneserver_list.SendEmoteMessage( + ZSList::Instance()->SendEmoteMessage( 0, 0, AccountStatus::Player, @@ -371,7 +368,7 @@ void EQW::SendMessage(uint32 type, const char *msg) { } void EQW::WorldShutDown(uint32 time, uint32 interval) { - zoneserver_list.WorldShutDown(time, interval); + ZSList::Instance()->WorldShutDown(time, interval); } #endif //EMBPERL diff --git a/world/expedition_database.cpp b/world/expedition_database.cpp deleted file mode 100644 index 995be8c6e..000000000 --- a/world/expedition_database.cpp +++ /dev/null @@ -1,78 +0,0 @@ -/** - * EQEmulator: Everquest Server Emulator - * Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY except by those people which sell it, which - * are required to give you total support for your newly bought product; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR - * A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#include "expedition_database.h" -#include "worlddb.h" -#include "../common/repositories/expeditions_repository.h" -#include "../common/repositories/expedition_lockouts_repository.h" -#include "../common/repositories/dynamic_zone_members_repository.h" - -void ExpeditionDatabase::PurgeExpiredExpeditions() -{ - std::string query = SQL( - SELECT - expeditions.id, - expeditions.dynamic_zone_id - FROM expeditions - LEFT JOIN dynamic_zones ON expeditions.dynamic_zone_id = dynamic_zones.id - LEFT JOIN instance_list ON dynamic_zones.instance_id = instance_list.id - LEFT JOIN - ( - SELECT dynamic_zone_id, COUNT(*) member_count - FROM dynamic_zone_members - GROUP BY dynamic_zone_id - ) dynamic_zone_members - ON dynamic_zone_members.dynamic_zone_id = expeditions.dynamic_zone_id - WHERE - instance_list.id IS NULL - OR dynamic_zone_members.member_count IS NULL - OR dynamic_zone_members.member_count = 0 - OR (instance_list.start_time + instance_list.duration) <= UNIX_TIMESTAMP(); - ); - - auto results = database.QueryDatabase(query); - if (results.Success()) - { - std::vector expedition_ids; - std::vector dynamic_zone_ids; - for (auto row = results.begin(); row != results.end(); ++row) - { - expedition_ids.emplace_back(row[0]); - dynamic_zone_ids.emplace_back(static_cast(strtoul(row[1], nullptr, 10))); - } - - if (!expedition_ids.empty()) - { - ExpeditionsRepository::DeleteWhere(database, fmt::format("id IN ({})", fmt::join(expedition_ids, ","))); - ExpeditionLockoutsRepository::DeleteWhere(database, fmt::format("expedition_id IN ({})", fmt::join(expedition_ids, ","))); - DynamicZoneMembersRepository::RemoveAllMembers(database, dynamic_zone_ids); - } - } -} - -void ExpeditionDatabase::PurgeExpiredCharacterLockouts() -{ - std::string query = SQL( - DELETE FROM character_expedition_lockouts - WHERE expire_time <= NOW(); - ); - - database.QueryDatabase(query); -} diff --git a/world/expedition_database.h b/world/expedition_database.h deleted file mode 100644 index a885dfc83..000000000 --- a/world/expedition_database.h +++ /dev/null @@ -1,35 +0,0 @@ -/** - * EQEmulator: Everquest Server Emulator - * Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY except by those people which sell it, which - * are required to give you total support for your newly bought product; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR - * A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#ifndef WORLD_EXPEDITION_DATABASE_H -#define WORLD_EXPEDITION_DATABASE_H - -#include -#include - -class Expedition; - -namespace ExpeditionDatabase -{ - void PurgeExpiredExpeditions(); - void PurgeExpiredCharacterLockouts(); -}; - -#endif diff --git a/world/expedition_message.cpp b/world/expedition_message.cpp deleted file mode 100644 index 93f6ccd6e..000000000 --- a/world/expedition_message.cpp +++ /dev/null @@ -1,151 +0,0 @@ -/** - * EQEmulator: Everquest Server Emulator - * Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY except by those people which sell it, which - * are required to give you total support for your newly bought product; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR - * A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#include "dynamic_zone.h" -#include "expedition_message.h" -#include "cliententry.h" -#include "clientlist.h" -#include "zonelist.h" -#include "zoneserver.h" -#include "../common/servertalk.h" -#include - -extern ClientList client_list; -extern ZSList zoneserver_list; - -void ExpeditionMessage::HandleZoneMessage(ServerPacket* pack) -{ - switch (pack->opcode) - { - case ServerOP_ExpeditionCreate: - { - zoneserver_list.SendPacket(pack); - break; - } - case ServerOP_ExpeditionDzAddPlayer: - { - ExpeditionMessage::AddPlayer(pack); - break; - } - case ServerOP_ExpeditionDzMakeLeader: - { - ExpeditionMessage::MakeLeader(pack); - break; - } - case ServerOP_ExpeditionCharacterLockout: - { - auto buf = reinterpret_cast(pack->pBuffer); - auto cle = client_list.FindCLEByCharacterID(buf->character_id); - if (cle && cle->Server()) - { - cle->Server()->SendPacket(pack); - } - break; - } - case ServerOP_ExpeditionSaveInvite: - { - ExpeditionMessage::SaveInvite(pack); - break; - } - case ServerOP_ExpeditionRequestInvite: - { - ExpeditionMessage::RequestInvite(pack); - break; - } - } -} - -void ExpeditionMessage::AddPlayer(ServerPacket* pack) -{ - auto buf = reinterpret_cast(pack->pBuffer); - - ClientListEntry* invited_cle = client_list.FindCharacter(buf->target_name); - if (invited_cle && invited_cle->Server()) - { - // continue in the add target's zone - buf->is_char_online = true; - invited_cle->Server()->SendPacket(pack); - } - else - { - // add target not online, return to inviter - ClientListEntry* inviter_cle = client_list.FindCharacter(buf->requester_name); - if (inviter_cle && inviter_cle->Server()) - { - inviter_cle->Server()->SendPacket(pack); - } - } -} - -void ExpeditionMessage::MakeLeader(ServerPacket* pack) -{ - auto buf = reinterpret_cast(pack->pBuffer); - - // notify requester (old leader) and new leader of the result - ZoneServer* new_leader_zs = nullptr; - ClientListEntry* new_leader_cle = client_list.FindCharacter(buf->new_leader_name); - if (new_leader_cle && new_leader_cle->Server()) - { - auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id); - if (dz && dz->GetLeaderID() == buf->requester_id) - { - buf->is_success = dz->SetNewLeader(new_leader_cle->CharID()); - } - - buf->is_online = true; - new_leader_zs = new_leader_cle->Server(); - new_leader_zs->SendPacket(pack); - } - - // if old and new leader are in the same zone only send one message - ClientListEntry* requester_cle = client_list.FindCLEByCharacterID(buf->requester_id); - if (requester_cle && requester_cle->Server() && requester_cle->Server() != new_leader_zs) - { - requester_cle->Server()->SendPacket(pack); - } -} - -void ExpeditionMessage::SaveInvite(ServerPacket* pack) -{ - auto buf = reinterpret_cast(pack->pBuffer); - - ClientListEntry* invited_cle = client_list.FindCharacter(buf->target_name); - if (invited_cle) - { - // store packet on cle and re-send it when client requests it - buf->is_char_online = true; - pack->opcode = ServerOP_ExpeditionDzAddPlayer; - invited_cle->SetPendingExpeditionInvite(pack); - } -} - -void ExpeditionMessage::RequestInvite(ServerPacket* pack) -{ - auto buf = reinterpret_cast(pack->pBuffer); - ClientListEntry* cle = client_list.FindCLEByCharacterID(buf->character_id); - if (cle) - { - auto invite_pack = cle->GetPendingExpeditionInvite(); - if (invite_pack && cle->Server()) - { - cle->Server()->SendPacket(invite_pack.get()); - } - } -} diff --git a/world/expedition_message.h b/world/expedition_message.h deleted file mode 100644 index b97651dbc..000000000 --- a/world/expedition_message.h +++ /dev/null @@ -1,35 +0,0 @@ -/** - * EQEmulator: Everquest Server Emulator - * Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY except by those people which sell it, which - * are required to give you total support for your newly bought product; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR - * A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#ifndef WORLD_EXPEDITION_MESSAGE_H -#define WORLD_EXPEDITION_MESSAGE_H - -class ServerPacket; - -namespace ExpeditionMessage -{ - void AddPlayer(ServerPacket* pack); - void HandleZoneMessage(ServerPacket* pack); - void MakeLeader(ServerPacket* pack); - void RequestInvite(ServerPacket* pack); - void SaveInvite(ServerPacket* pack); -}; - -#endif diff --git a/world/lfplist.cpp b/world/lfplist.cpp index 904d31261..fcdc9daf3 100644 --- a/world/lfplist.cpp +++ b/world/lfplist.cpp @@ -25,9 +25,6 @@ #include "../common/misc_functions.h" #include "../common/classes.h" -extern ClientList client_list; -extern ZSList zoneserver_list; - GroupLFP::GroupLFP(uint32 inLeaderID) { LeaderID = inLeaderID; @@ -69,7 +66,7 @@ void GroupLFP::SetDetails(ServerLFPUpdate_Struct *Update) { } // Otherwise try and find the information ourselves. else { - CLE = client_list.FindCharacter(Members[i].Name); + CLE = ClientList::Instance()->FindCharacter(Members[i].Name); if(CLE) { Members[i].Class = CLE->class_(); Members[i].Level = CLE->level(); @@ -125,7 +122,7 @@ void GroupLFPList::Process() { if(GroupMembers[i].Name[0] == '\0') continue; - ClientListEntry *CLE = client_list.FindCharacter(GroupMembers[i].Name); + ClientListEntry *CLE = ClientList::Instance()->FindCharacter(GroupMembers[i].Name); if(!CLE) { // The first member entry is always the person who posted the LFP group, either // a single ungrouped player, or the leader of the group. If (s)he is gone, remove @@ -264,13 +261,13 @@ void GroupLFPList::SendLFPMatches(ServerLFPMatchesRequest_Struct* smrs) { } } - ClientListEntry* CLE = client_list.FindCharacter(smrs->FromName); + ClientListEntry* CLE = ClientList::Instance()->FindCharacter(smrs->FromName); if (CLE != nullptr) { if (CLE->Server() != nullptr) CLE->Server()->SendPacket(Pack); } else { - ZoneServer* zs = zoneserver_list.FindByName(smrs->FromName); + ZoneServer* zs = ZSList::Instance()->FindByName(smrs->FromName); if (zs != nullptr) zs->SendPacket(Pack); } diff --git a/world/login_server.cpp b/world/login_server.cpp index b2017f094..9bdc0c470 100644 --- a/world/login_server.cpp +++ b/world/login_server.cpp @@ -1,20 +1,3 @@ -/* EQEMu: Everquest Server Emulator -Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org) - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; version 2 of the License. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY except by those people which sell it, which -are required to give you total support for your newly bought product; -without even the implied warranty of MERCHANTABILITY or FITNESS FOR -A PARTICULAR PURPOSE. See the GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ #include "../common/global_define.h" #include #include @@ -37,9 +20,6 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "cliententry.h" #include "world_config.h" - -extern ZSList zoneserver_list; -extern ClientList client_list; extern uint32 numzones; extern uint32 numplayers; extern volatile bool RunLoops; @@ -64,8 +44,8 @@ void LoginServer::ProcessUsertoWorldReqLeg(uint16_t opcode, EQ::Net::Packet &p) const WorldConfig *Config = WorldConfig::get(); LogNetcode("Received ServerPacket from LS OpCode {:#04x}", opcode); - UsertoWorldRequestLegacy_Struct *utwr = (UsertoWorldRequestLegacy_Struct *) p.Data(); - uint32 id = database.GetAccountIDFromLSID("eqemu", utwr->lsaccountid); + UsertoWorldRequestLegacy *utwr = (UsertoWorldRequestLegacy *) p.Data(); + uint32 id = database.GetAccountIDFromLSID("eqemu", utwr->lsaccountid); int16 status = database.GetAccountStatus(id); LogDebug( @@ -81,11 +61,11 @@ void LoginServer::ProcessUsertoWorldReqLeg(uint16_t opcode, EQ::Net::Packet &p) ServerPacket outpack; outpack.opcode = ServerOP_UsertoWorldRespLeg; - outpack.size = sizeof(UsertoWorldResponseLegacy_Struct); + outpack.size = sizeof(UsertoWorldResponseLegacy); outpack.pBuffer = new uchar[outpack.size]; memset(outpack.pBuffer, 0, outpack.size); - UsertoWorldResponseLegacy_Struct *utwrs = (UsertoWorldResponseLegacy_Struct *) outpack.pBuffer; + UsertoWorldResponseLegacy *utwrs = (UsertoWorldResponseLegacy *) outpack.pBuffer; utwrs->lsaccountid = utwr->lsaccountid; utwrs->ToID = utwr->FromID; utwrs->worldid = utwr->worldid; @@ -126,7 +106,7 @@ void LoginServer::ProcessUsertoWorldReqLeg(uint16_t opcode, EQ::Net::Packet &p) } if (RuleB(World, EnforceCharacterLimitAtLogin)) { - if (client_list.IsAccountInGame(utwr->lsaccountid)) { + if (ClientList::Instance()->IsAccountInGame(utwr->lsaccountid)) { LogDebug("User already online account_id [{0}]", utwr->lsaccountid); utwrs->response = UserToWorldStatusAlreadyOnline; SendPacket(&outpack); @@ -144,8 +124,8 @@ void LoginServer::ProcessUsertoWorldReq(uint16_t opcode, EQ::Net::Packet &p) const WorldConfig *Config = WorldConfig::get(); LogNetcode("Received ServerPacket from LS OpCode {:#04x}", opcode); - UsertoWorldRequest_Struct *utwr = (UsertoWorldRequest_Struct *) p.Data(); - uint32 id = database.GetAccountIDFromLSID(utwr->login, utwr->lsaccountid); + UsertoWorldRequest *utwr = (UsertoWorldRequest *) p.Data(); + uint32 id = database.GetAccountIDFromLSID(utwr->login, utwr->lsaccountid); int16 status = database.GetAccountStatus(id); LogDebug( @@ -161,11 +141,11 @@ void LoginServer::ProcessUsertoWorldReq(uint16_t opcode, EQ::Net::Packet &p) ServerPacket outpack; outpack.opcode = ServerOP_UsertoWorldResp; - outpack.size = sizeof(UsertoWorldResponse_Struct); + outpack.size = sizeof(UsertoWorldResponse); outpack.pBuffer = new uchar[outpack.size]; memset(outpack.pBuffer, 0, outpack.size); - UsertoWorldResponse_Struct *utwrs = (UsertoWorldResponse_Struct *) outpack.pBuffer; + UsertoWorldResponse *utwrs = (UsertoWorldResponse *) outpack.pBuffer; utwrs->lsaccountid = utwr->lsaccountid; utwrs->ToID = utwr->FromID; strn0cpy(utwrs->login, utwr->login, 64); @@ -207,7 +187,7 @@ void LoginServer::ProcessUsertoWorldReq(uint16_t opcode, EQ::Net::Packet &p) } if (RuleB(World, EnforceCharacterLimitAtLogin)) { - if (client_list.IsAccountInGame(utwr->lsaccountid)) { + if (ClientList::Instance()->IsAccountInGame(utwr->lsaccountid)) { LogDebug("User already online account_id [{0}]", utwr->lsaccountid); utwrs->response = UserToWorldStatusAlreadyOnline; SendPacket(&outpack); @@ -226,27 +206,27 @@ void LoginServer::ProcessLSClientAuthLegacy(uint16_t opcode, EQ::Net::Packet &p) LogNetcode("Received ServerPacket from LS OpCode {:#04x}", opcode); try { - auto client_authentication_request = p.GetSerialize(0); + auto r = p.GetSerialize(0); LogDebug( - "Processing Loginserver Auth Legacy | account_id [{0}] account_name [{1}] key [{2}] admin [{3}] ip [{4}] " - "local_network [{5}]", - client_authentication_request.loginserver_account_id, - client_authentication_request.loginserver_account_name, - client_authentication_request.key, - client_authentication_request.is_world_admin, - client_authentication_request.ip, - client_authentication_request.is_client_from_local_network + "Processing Loginserver Auth Legacy | account_id [{}] account_name [{}] key [{}] admin [{}] ip [{}] " + "local_network [{}]", + r.loginserver_account_id, + r.loginserver_account_name, + r.key, + r.is_world_admin, + r.ip_address, + r.is_client_from_local_network ); - client_list.CLEAdd( - client_authentication_request.loginserver_account_id, + ClientList::Instance()->CLEAdd( + r.loginserver_account_id, "eqemu", - client_authentication_request.loginserver_account_name, - client_authentication_request.key, - client_authentication_request.is_world_admin, - client_authentication_request.ip, - client_authentication_request.is_client_from_local_network + r.loginserver_account_name, + r.key, + r.is_world_admin, + r.ip_address, + r.is_client_from_local_network ); } catch (std::exception &ex) { @@ -260,28 +240,28 @@ void LoginServer::ProcessLSClientAuth(uint16_t opcode, EQ::Net::Packet &p) LogNetcode("Received ServerPacket from LS OpCode {:#04x}", opcode); try { - auto client_authentication_request = p.GetSerialize(0); + auto r = p.GetSerialize(0); LogDebug( - "Processing Loginserver Auth | account_id [{0}] account_name [{1}] loginserver_name [{2}] key [{3}] " - "admin [{4}] ip [{5}] local_network [{6}]", - client_authentication_request.loginserver_account_id, - client_authentication_request.account_name, - client_authentication_request.loginserver_name, - client_authentication_request.key, - client_authentication_request.is_world_admin, - client_authentication_request.ip, - client_authentication_request.is_client_from_local_network + "Processing Loginserver Auth | account_id [{}] account_name [{}] loginserver_name [{}] key [{}] " + "admin [{}] ip [{}] local_network [{}]", + r.loginserver_account_id, + r.account_name, + r.loginserver_name, + r.key, + r.is_world_admin, + r.ip_address, + r.is_client_from_local_network ); - client_list.CLEAdd( - client_authentication_request.loginserver_account_id, - client_authentication_request.loginserver_name, - client_authentication_request.account_name, - client_authentication_request.key, - client_authentication_request.is_world_admin, - client_authentication_request.ip, - client_authentication_request.is_client_from_local_network + ClientList::Instance()->CLEAdd( + r.loginserver_account_id, + r.loginserver_name, + r.account_name, + r.key, + r.is_world_admin, + r.ip_address, + r.is_client_from_local_network ); } catch (std::exception &ex) { @@ -304,7 +284,7 @@ void LoginServer::ProcessLSFatalError(uint16_t opcode, EQ::Net::Packet &p) if (error.find("Worldserver Account / Password INVALID") != std::string::npos) { reason = "Usually this indicates you do not have a valid [account] and [password] (worldserver) account associated with your loginserver configuration. "; if (fmt::format("{}", m_loginserver_address).find("login.eqemulator.net") != std::string::npos) { - reason += "For Legacy EQEmulator connections, you need to register your server @ http://www.eqemulator.org/account/?LS"; + reason += "For Legacy EQEmulator connections, you need to register your server @ https://www.eqemulator.org/index.php?pageid=ws_mgmt"; } } @@ -330,7 +310,7 @@ void LoginServer::ProcessSystemwideMessage(uint16_t opcode, EQ::Net::Packet &p) LogNetcode("Received ServerPacket from LS OpCode {:#04x}", opcode); ServerSystemwideMessage *swm = (ServerSystemwideMessage *) p.Data(); - zoneserver_list.SendEmoteMessageRaw( + ZSList::Instance()->SendEmoteMessageRaw( 0, 0, AccountStatus::Player, @@ -394,7 +374,7 @@ bool LoginServer::Connect() SendInfo(); SendStatus(); - zoneserver_list.SendLSZones(); + ZSList::Instance()->SendLSZones(); m_statusupdate_timer = std::make_unique( LoginServer_StatusUpdateInterval, true, [this](EQ::Timer *t) { @@ -503,7 +483,7 @@ bool LoginServer::Connect() ); SendInfo(); SendStatus(); - zoneserver_list.SendLSZones(); + ZSList::Instance()->SendLSZones(); m_statusupdate_timer = std::make_unique( LoginServer_StatusUpdateInterval, true, [this](EQ::Timer *t) { @@ -595,11 +575,6 @@ bool LoginServer::Connect() ); } - m_keepalive = std::make_unique( - 1000, - true, - std::bind(&LoginServer::OnKeepAlive, this, std::placeholders::_1)); - return true; } @@ -614,11 +589,11 @@ void LoginServer::SendInfo() auto pack = new ServerPacket; pack->opcode = ServerOP_NewLSInfo; - pack->size = sizeof(ServerNewLSInfo_Struct); + pack->size = sizeof(LoginserverNewWorldRequest); pack->pBuffer = new uchar[pack->size]; memset(pack->pBuffer, 0, pack->size); - auto *l = (ServerNewLSInfo_Struct *) pack->pBuffer; + auto *l = (LoginserverNewWorldRequest *) pack->pBuffer; strcpy(l->protocol_version, EQEMU_PROTOCOL_VERSION); strcpy(l->server_version, LOGIN_VERSION); strcpy(l->server_long_name, Config->LongName.c_str()); @@ -662,10 +637,10 @@ void LoginServer::SendStatus() auto pack = new ServerPacket; pack->opcode = ServerOP_LSStatus; - pack->size = sizeof(ServerLSStatus_Struct); + pack->size = sizeof(LoginserverWorldStatusUpdate); pack->pBuffer = new uchar[pack->size]; memset(pack->pBuffer, 0, pack->size); - auto loginserver_status = (ServerLSStatus_Struct *) pack->pBuffer; + auto loginserver_status = (LoginserverWorldStatusUpdate *) pack->pBuffer; if (WorldConfig::get()->Locked) { loginserver_status->status = -2; @@ -683,9 +658,6 @@ void LoginServer::SendStatus() delete pack; } -/** - * @param pack - */ void LoginServer::SendPacket(ServerPacket *pack) { if (m_legacy_client) { @@ -703,21 +675,16 @@ void LoginServer::SendAccountUpdate(ServerPacket *pack) return; } - auto *ls_account_update = (ServerLSAccountUpdate_Struct *) pack->pBuffer; + auto *req = (LoginserverAccountUpdate *) pack->pBuffer; if (CanUpdate()) { LogInfo( "Sending ServerOP_LSAccountUpdate packet to loginserver: [{0}]:[{1}]", m_loginserver_address, m_loginserver_port ); - strn0cpy(ls_account_update->worldaccount, m_login_account.c_str(), 30); - strn0cpy(ls_account_update->worldpassword, m_login_password.c_str(), 30); + strn0cpy(req->world_account, m_login_account.c_str(), 30); + strn0cpy(req->world_password, m_login_password.c_str(), 30); SendPacket(pack); } } -void LoginServer::OnKeepAlive(EQ::Timer *t) -{ - ServerPacket pack(ServerOP_KeepAlive, 0); - SendPacket(&pack); -} diff --git a/world/login_server.h b/world/login_server.h index 21e25870f..0a5e6a107 100644 --- a/world/login_server.h +++ b/world/login_server.h @@ -50,7 +50,6 @@ private: void ProcessLSRemoteAddr(uint16_t opcode, EQ::Net::Packet &p); void ProcessLSAccountUpdate(uint16_t opcode, EQ::Net::Packet &p); - void OnKeepAlive(EQ::Timer *t); std::unique_ptr m_keepalive; std::unique_ptr m_client; diff --git a/world/login_server_list.cpp b/world/login_server_list.cpp index 30285c72a..5a2c8f375 100644 --- a/world/login_server_list.cpp +++ b/world/login_server_list.cpp @@ -36,9 +36,6 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "clientlist.h" #include "world_config.h" -extern ZSList zoneserver_list; -extern LoginServerList loginserverlist; -extern ClientList client_list; extern uint32 numzones; extern uint32 numplayers; extern volatile bool RunLoops; diff --git a/world/login_server_list.h b/world/login_server_list.h index d9a270e55..54de20bef 100644 --- a/world/login_server_list.h +++ b/world/login_server_list.h @@ -21,6 +21,13 @@ public: bool SendAccountUpdate(ServerPacket *pack); bool Connected(); size_t GetServerCount() const { return m_list.size(); } + + static LoginServerList* Instance() + { + static LoginServerList instance; + return &instance; + } + protected: std::list> m_list; }; diff --git a/world/main.cpp b/world/main.cpp index aff459fb9..c9386128e 100644 --- a/world/main.cpp +++ b/world/main.cpp @@ -46,6 +46,7 @@ #include "client.h" #include "worlddb.h" #include "wguild_mgr.h" +#include "../common/evolving_items.h" #ifdef _WINDOWS #include @@ -75,10 +76,10 @@ #include "web_interface.h" #include "console.h" #include "dynamic_zone_manager.h" -#include "expedition_database.h" #include "world_server_cli.h" #include "../common/content/world_content_service.h" +#include "../common/repositories/character_expedition_lockouts_repository.h" #include "../common/repositories/character_task_timers_repository.h" #include "../common/zone_store.h" #include "world_event_scheduler.h" @@ -88,29 +89,14 @@ #include "../common/events/player_event_logs.h" #include "../common/skill_caps.h" #include "../common/repositories/character_parcels_repository.h" +#include "../common/ip_util.h" -SkillCaps skill_caps; -ZoneStore zone_store; -ClientList client_list; GroupLFPList LFPGroupList; -ZSList zoneserver_list; -LoginServerList loginserverlist; -UCSConnection UCSLink; -QueryServConnection QSLink; LauncherList launcher_list; -AdventureManager adventure_manager; -WorldEventScheduler event_scheduler; -SharedTaskManager shared_task_manager; -EQ::Random emu_random; volatile bool RunLoops = true; uint32 numclients = 0; uint32 numzones = 0; const WorldConfig *Config; -EQEmuLogSys LogSys; -WorldContentService content_service; -WebInterfaceList web_interface; -PathManager path; -PlayerEventLogs player_event_logs; void CatchSignal(int sig_num); @@ -132,14 +118,14 @@ inline void UpdateWindowTitle(std::string new_title) int main(int argc, char **argv) { RegisterExecutablePlatform(ExePlatformWorld); - LogSys.LoadLogSettingsDefaults(); + EQEmuLogSys::Instance()->LoadLogSettingsDefaults(); set_exception_handler(); if (WorldBoot::HandleCommandInput(argc, argv)) { return 0; } - path.LoadPaths(); + PathManager::Instance()->Init(); if (!WorldBoot::LoadServerConfig()) { return 0; @@ -179,12 +165,18 @@ int main(int argc, char **argv) EQTimeTimer.Start(600000); Timer parcel_prune_timer(86400000); parcel_prune_timer.Start(86400000); - + Timer player_event_log_process(1000); + player_event_log_process.Start(1000); // global loads LogInfo("Loading launcher list"); launcher_list.LoadList(); - zoneserver_list.Init(); + ZSList::Instance()->Init(); + + if (IpUtil::IsPortInUse(Config->WorldIP, Config->WorldTCPPort)) { + LogError("World port [{}] already in use", Config->WorldTCPPort); + return 1; + } std::unique_ptr console; if (Config->TelnetEnabled) { @@ -193,12 +185,12 @@ int main(int argc, char **argv) RegisterConsoleFunctions(console); } - content_service.SetDatabase(&database) + WorldContentService::Instance()->SetDatabase(&database) ->SetContentDatabase(&content_db) ->SetExpansionContext() ->ReloadContentFlags(); - skill_caps.SetContentDatabase(&content_db)->LoadSkillCaps(); + SkillCaps::Instance()->SetContentDatabase(&content_db)->LoadSkillCaps(); std::unique_ptr server_connection; server_connection = std::make_unique(); @@ -213,7 +205,7 @@ int main(int argc, char **argv) server_connection->OnConnectionIdentified( "Zone", [&console](std::shared_ptr connection) { numzones++; - zoneserver_list.Add(new ZoneServer(connection, console.get())); + ZSList::Instance()->Add(new ZoneServer(connection, console.get())); LogInfo( "New Zone Server connection from [{}] at [{}:{}] zone_count [{}]", @@ -228,7 +220,7 @@ int main(int argc, char **argv) server_connection->OnConnectionRemoved( "Zone", [](std::shared_ptr connection) { numzones--; - zoneserver_list.Remove(connection->GetUUID()); + ZSList::Instance()->Remove(connection->GetUUID()); LogInfo( "Removed Zone Server connection from [{}] total zone_count [{}]", @@ -270,7 +262,7 @@ int main(int argc, char **argv) connection->Handle()->RemotePort(), connection->GetUUID()); - QSLink.AddConnection(connection); + QueryServConnection::Instance()->AddConnection(connection); } ); @@ -281,7 +273,7 @@ int main(int argc, char **argv) connection->GetUUID() ); - QSLink.RemoveConnection(connection); + QueryServConnection::Instance()->RemoveConnection(connection); } ); @@ -294,9 +286,9 @@ int main(int argc, char **argv) connection->GetUUID() ); - UCSLink.SetConnection(connection); + UCSConnection::Instance()->SetConnection(connection); - zoneserver_list.UpdateUCSServerAvailable(); + ZSList::Instance()->UpdateUCSServerAvailable(); } ); @@ -304,12 +296,12 @@ int main(int argc, char **argv) "UCS", [](std::shared_ptr connection) { LogInfo("Connection lost from UCS Server [{}]", connection->GetUUID()); - auto ucs_connection = UCSLink.GetConnection(); + auto ucs_connection = UCSConnection::Instance()->GetConnection(); if (ucs_connection->GetUUID() == connection->GetUUID()) { LogInfo("Removing currently active UCS connection"); - UCSLink.SetConnection(nullptr); - zoneserver_list.UpdateUCSServerAvailable(false); + UCSConnection::Instance()->SetConnection(nullptr); + ZSList::Instance()->UpdateUCSServerAvailable(false); } } ); @@ -323,7 +315,7 @@ int main(int argc, char **argv) connection->GetUUID() ); - web_interface.AddConnection(connection); + WebInterfaceList::Instance()->AddConnection(connection); } ); @@ -334,28 +326,28 @@ int main(int argc, char **argv) connection->GetUUID() ); - web_interface.RemoveConnection(connection); + WebInterfaceList::Instance()->RemoveConnection(connection); } ); WorldBoot::CheckForPossibleConfigurationIssues(); EQStreamManagerInterfaceOptions opts(9000, false, false); - opts.daybreak_options.resend_delay_ms = RuleI(Network, ResendDelayBaseMS); - opts.daybreak_options.resend_delay_factor = RuleR(Network, ResendDelayFactor); - opts.daybreak_options.resend_delay_min = RuleI(Network, ResendDelayMinMS); - opts.daybreak_options.resend_delay_max = RuleI(Network, ResendDelayMaxMS); - opts.daybreak_options.outgoing_data_rate = RuleR(Network, ClientDataRate); + opts.reliable_stream_options.resend_delay_ms = RuleI(Network, ResendDelayBaseMS); + opts.reliable_stream_options.resend_delay_factor = RuleR(Network, ResendDelayFactor); + opts.reliable_stream_options.resend_delay_min = RuleI(Network, ResendDelayMinMS); + opts.reliable_stream_options.resend_delay_max = RuleI(Network, ResendDelayMaxMS); + opts.reliable_stream_options.outgoing_data_rate = RuleR(Network, ClientDataRate); EQ::Net::EQStreamManager eqsm(opts); //register all the patches we have avaliable with the stream identifier. EQStreamIdentifier stream_identifier; RegisterAllPatches(stream_identifier); - zoneserver_list.shutdowntimer = new Timer(60000); - zoneserver_list.shutdowntimer->Disable(); - zoneserver_list.reminder = new Timer(20000); - zoneserver_list.reminder->Disable(); + ZSList::Instance()->shutdowntimer = new Timer(60000); + ZSList::Instance()->shutdowntimer->Disable(); + ZSList::Instance()->reminder = new Timer(20000); + ZSList::Instance()->reminder->Disable(); Timer InterserverTimer(INTERSERVER_TIMER); // does MySQL pings and auto-reconnect InterserverTimer.Trigger(); uint8 ReconnectCounter = 100; @@ -373,8 +365,9 @@ int main(int argc, char **argv) } ); - Timer player_event_process_timer(1000); - player_event_logs.SetDatabase(&database)->Init(); + if (PlayerEventLogs::Instance()->LoadDatabaseConnection()) { + PlayerEventLogs::Instance()->Init(); + } auto loop_fn = [&](EQ::Timer* t) { Timer::SetCurrentTime(); @@ -400,7 +393,7 @@ int main(int argc, char **argv) LogInfo("Connection [{}] PASSED banned IPs check. Processing connection", inet_ntoa(in)); auto client = new Client(eqsi); // @merth: client->zoneattempt=0; - client_list.Add(client); + ClientList::Instance()->Add(client); } else { LogInfo("Connection from [{}] failed banned IPs check. Closing connection", inet_ntoa(in)); @@ -415,13 +408,13 @@ int main(int argc, char **argv) ); auto client = new Client(eqsi); // @merth: client->zoneattempt=0; - client_list.Add(client); + ClientList::Instance()->Add(client); } } - event_scheduler.Process(&zoneserver_list); + WorldEventScheduler::Instance()->Process(ZSList::Instance()); - client_list.Process(); + ClientList::Instance()->Process(); guild_mgr.Process(); if (parcel_prune_timer.Check()) { @@ -432,26 +425,22 @@ int main(int argc, char **argv) ); auto out = std::make_unique(ServerOP_ParcelPrune); - zoneserver_list.SendPacketToBootedZones(out.get()); + ZSList::Instance()->SendPacketToBootedZones(out.get()); database.PurgeCharacterParcels(); } } - if (player_event_process_timer.Check()) { - player_event_logs.Process(); - } - if (PurgeInstanceTimer.Check()) { database.PurgeExpiredInstances(); database.PurgeAllDeletedDataBuckets(); - ExpeditionDatabase::PurgeExpiredCharacterLockouts(); + CharacterExpeditionLockoutsRepository::DeleteWhere(database, "expire_time <= NOW()"); CharacterTaskTimersRepository::DeleteWhere(database, "expire_time <= NOW()"); } if (EQTimeTimer.Check()) { TimeOfDay_Struct tod{}; - zoneserver_list.worldclock.GetCurrentEQTimeOfDay(time(nullptr), &tod); + ZSList::Instance()->worldclock.GetCurrentEQTimeOfDay(time(nullptr), &tod); if (!database.SaveTime(tod.minute, tod.hour, tod.day, tod.month, tod.year)) { LogEqTime("Failed to save eqtime"); } @@ -466,13 +455,19 @@ int main(int argc, char **argv) } } - zoneserver_list.Process(); + ZSList::Instance()->Process(); launcher_list.Process(); LFPGroupList.Process(); - adventure_manager.Process(); - shared_task_manager.Process(); + AdventureManager::Instance()->Process(); + SharedTaskManager::Instance()->Process(); dynamic_zone_manager.Process(); + if (!RuleB(Logging, PlayerEventsQSProcess)) { + if (player_event_log_process.Check()) { + PlayerEventLogs::Instance()->Process(); + } + } + if (InterserverTimer.Check()) { InterserverTimer.Start(); database.ping(); @@ -481,7 +476,7 @@ int main(int argc, char **argv) std::string window_title = fmt::format( "World [{}] Clients [{}]", Config->LongName, - client_list.GetClientCount() + ClientList::Instance()->GetClientCount() ); UpdateWindowTitle(window_title); } @@ -494,10 +489,10 @@ int main(int argc, char **argv) LogInfo("World main loop completed"); LogInfo("Shutting down zone connections (if any)"); - zoneserver_list.KillAll(); + ZSList::Instance()->KillAll(); LogInfo("Zone (TCP) listener stopped"); LogInfo("Signaling HTTP service to stop"); - LogSys.CloseFileLogs(); + EQEmuLogSys::Instance()->CloseFileLogs(); WorldBoot::Shutdown(); diff --git a/world/queryserv.cpp b/world/queryserv.cpp index 0611e7d50..69185607c 100644 --- a/world/queryserv.cpp +++ b/world/queryserv.cpp @@ -9,9 +9,6 @@ #include "../common/md5.h" #include "../common/packet_dump.h" -extern ClientList client_list; -extern ZSList zoneserver_list; - QueryServConnection::QueryServConnection() { } @@ -22,7 +19,6 @@ void QueryServConnection::AddConnection(std::shared_ptrOnMessage(ServerOP_QueryServGeneric, std::bind(&QueryServConnection::HandleGenericMessage, this, std::placeholders::_1, std::placeholders::_2)); connection->OnMessage(ServerOP_LFGuildUpdate, std::bind(&QueryServConnection::HandleLFGuildUpdateMessage, this, std::placeholders::_1, std::placeholders::_2)); m_streams.emplace(std::make_pair(connection->GetUUID(), connection)); - m_keepalive = std::make_unique(1000, true, std::bind(&QueryServConnection::OnKeepAlive, this, std::placeholders::_1)); } void QueryServConnection::RemoveConnection(std::shared_ptr connection) @@ -37,12 +33,12 @@ void QueryServConnection::HandleGenericMessage(uint16_t opcode, EQ::Net::Packet uint32 ZoneID = p.GetUInt32(0); uint16 InstanceID = p.GetUInt32(4); ServerPacket pack(opcode, p); - zoneserver_list.SendPacket(ZoneID, InstanceID, &pack); + ZSList::Instance()->SendPacket(ZoneID, InstanceID, &pack); } void QueryServConnection::HandleLFGuildUpdateMessage(uint16_t opcode, EQ::Net::Packet &p) { ServerPacket pack(opcode, p); - zoneserver_list.SendPacket(&pack); + ZSList::Instance()->SendPacket(&pack); } bool QueryServConnection::SendPacket(ServerPacket* pack) @@ -54,8 +50,3 @@ bool QueryServConnection::SendPacket(ServerPacket* pack) return true; } -void QueryServConnection::OnKeepAlive(EQ::Timer *t) -{ - ServerPacket pack(ServerOP_KeepAlive, 0); - SendPacket(&pack); -} diff --git a/world/queryserv.h b/world/queryserv.h index a02aab647..5366fbeb5 100644 --- a/world/queryserv.h +++ b/world/queryserv.h @@ -15,7 +15,13 @@ public: void HandleGenericMessage(uint16_t opcode, EQ::Net::Packet &p); void HandleLFGuildUpdateMessage(uint16_t opcode, EQ::Net::Packet &p); bool SendPacket(ServerPacket* pack); - void OnKeepAlive(EQ::Timer *t); + + static QueryServConnection* Instance() + { + static QueryServConnection instance; + return &instance; + } + private: std::map> m_streams; std::unique_ptr m_keepalive; diff --git a/world/shared_task_manager.cpp b/world/shared_task_manager.cpp index 15925daed..914f40958 100644 --- a/world/shared_task_manager.cpp +++ b/world/shared_task_manager.cpp @@ -17,9 +17,6 @@ #include "../common/repositories/shared_task_dynamic_zones_repository.h" #include -extern ClientList client_list; -extern ZSList zoneserver_list; - SharedTaskManager::SharedTaskManager() : m_process_timer{ static_cast(RuleI(TaskSystem, SharedTasksWorldProcessRate)) } { @@ -199,7 +196,7 @@ void SharedTaskManager::RemoveMember(SharedTask* s, const SharedTaskMember& memb SendSharedTaskMemberRemovedToAllMembers(s, member.character_name); - client_list.SendCharacterMessageID(member.character_id, Chat::Yellow, + ClientList::Instance()->SendCharacterMessageID(member.character_id, Chat::Yellow, TaskStr::PLAYER_REMOVED, {member.character_name, s->GetTaskData().title}); if (member.is_leader) { @@ -229,10 +226,10 @@ void SharedTaskManager::RemoveEveryoneFromSharedTask(SharedTask *t, uint32 reque SendRemovePlayerFromSharedTaskPacket(m.character_id, t->GetTaskData().id, true); - client_list.SendCharacterMessageID(m.character_id, Chat::Yellow, TaskStr::YOU_REMOVED, {t->GetTaskData().title}); + ClientList::Instance()->SendCharacterMessageID(m.character_id, Chat::Yellow, TaskStr::YOU_REMOVED, {t->GetTaskData().title}); } - client_list.SendCharacterMessageID(requested_character_id, Chat::Red, TaskStr::PLAYER_REMOVED, {"Everyone", t->GetTaskData().title}); + ClientList::Instance()->SendCharacterMessageID(requested_character_id, Chat::Red, TaskStr::PLAYER_REMOVED, {"Everyone", t->GetTaskData().title}); RemoveAllMembersFromDynamicZones(t); @@ -252,7 +249,7 @@ void SharedTaskManager::Terminate(SharedTask& s, bool send_fail, bool erase) SendSharedTaskFailed(member.character_id, s.GetTaskData().id); } SendRemovePlayerFromSharedTaskPacket(member.character_id, s.GetTaskData().id, true); - client_list.SendCharacterMessageID(member.character_id, Chat::Yellow, TaskStr::HAS_ENDED, {s.GetTaskData().title}); + ClientList::Instance()->SendCharacterMessageID(member.character_id, Chat::Yellow, TaskStr::HAS_ENDED, {s.GetTaskData().title}); } RemoveAllMembersFromDynamicZones(&s); @@ -581,7 +578,7 @@ void SharedTaskManager::SharedTaskActivityUpdate( d->ignore_quest_update = ignore_quest_update; // get requested character zone server - ClientListEntry *c = client_list.FindCLEByCharacterID(m.character_id); + ClientListEntry *c = ClientList::Instance()->FindCLEByCharacterID(m.character_id); if (c && c->Server()) { c->Server()->SendPacket(p.get()); } @@ -677,13 +674,13 @@ void SharedTaskManager::SendAcceptNewSharedTaskPacket( ); auto d = reinterpret_cast(p->pBuffer); - d->requested_character_id = character_id; - d->requested_task_id = task_id; - d->requested_npc_type_id = npc_context_id; - d->accept_time = accept_time; + d->requested_character_id = character_id; + d->requested_task_id = task_id; + d->requested_npc_entity_id = npc_context_id; + d->accept_time = accept_time; // get requested character zone server - ClientListEntry *cle = client_list.FindCLEByCharacterID(character_id); + ClientListEntry *cle = ClientList::Instance()->FindCLEByCharacterID(character_id); if (cle && cle->Server()) { cle->Server()->SendPacket(p.get()); } @@ -704,7 +701,7 @@ void SharedTaskManager::SendRemovePlayerFromSharedTaskPacket( d->remove_from_db = remove_from_db; // get requested character zone server - ClientListEntry *cle = client_list.FindCLEByCharacterID(character_id); + ClientListEntry *cle = ClientList::Instance()->FindCLEByCharacterID(character_id); if (cle && cle->Server()) { cle->Server()->SendPacket(p.get()); } @@ -717,7 +714,7 @@ void SharedTaskManager::SendSharedTaskFailed(uint32_t character_id, uint32_t tas buf->character_id = character_id; buf->task_id = task_id; - ClientListEntry* cle = client_list.FindCLEByCharacterID(character_id); + ClientListEntry* cle = ClientList::Instance()->FindCLEByCharacterID(character_id); if (cle && cle->Server()) { cle->Server()->SendPacket(&pack); @@ -746,7 +743,7 @@ void SharedTaskManager::SendSharedTaskMemberList(uint32 character_id, const EQ:: memcpy(d->cereal_serialized_members, serialized_members.Data(), serialized_members.Length()); // send memberlist - ClientListEntry *cle = client_list.FindCLEByCharacterID(character_id); + ClientListEntry *cle = ClientList::Instance()->FindCLEByCharacterID(character_id); if (cle && cle->Server()) { cle->Server()->SendPacket(p.get()); } @@ -768,7 +765,7 @@ void SharedTaskManager::SendSharedTaskMemberChange( d->removed = removed; strn0cpy(d->player_name, player_name.c_str(), sizeof(d->player_name)); - ClientListEntry *cle = client_list.FindCLEByCharacterID(character_id); + ClientListEntry *cle = ClientList::Instance()->FindCLEByCharacterID(character_id); if (cle && cle->Server()) { cle->Server()->SendPacket(p.get()); } @@ -990,7 +987,7 @@ void SharedTaskManager::SendSharedTaskInvitePacket(SharedTask *s, int64 invited_ strn0cpy(d->task_name, s->GetTaskData().title.c_str(), sizeof(d->task_name)); // get requested character zone server - ClientListEntry *cle = client_list.FindCLEByCharacterID(invited_character_id); + ClientListEntry *cle = ClientList::Instance()->FindCLEByCharacterID(invited_character_id); if (cle && cle->Server()) { SendLeaderMessageID(s, Chat::Yellow, TaskStr::SEND_INVITE_TO, {cle->name()}); cle->Server()->SendPacket(p.get()); @@ -1172,7 +1169,7 @@ void SharedTaskManager::CreateDynamicZone(SharedTask *shared_task, DynamicZone & } } - auto new_dz = dynamic_zone_manager.CreateNew(dz_request, dz_members); + auto new_dz = dynamic_zone_manager.TryCreate(dz_request, dz_members); if (new_dz) { auto shared_task_dz = SharedTaskDynamicZonesRepository::NewEntity(); shared_task_dz.shared_task_id = shared_task->GetDbSharedTask().id; @@ -1213,7 +1210,7 @@ void SharedTaskManager::SendLeaderMessage(SharedTask *shared_task, int chat_type for (const auto &member : shared_task->GetMembers()) { if (member.is_leader) { - client_list.SendCharacterMessage(member.character_id, chat_type, message); + ClientList::Instance()->SendCharacterMessage(member.character_id, chat_type, message); break; } } @@ -1230,7 +1227,7 @@ void SharedTaskManager::SendLeaderMessageID( for (const auto &member : shared_task->GetMembers()) { if (member.is_leader) { - client_list.SendCharacterMessageID(member.character_id, chat_type, eqstr_id, args); + ClientList::Instance()->SendCharacterMessageID(member.character_id, chat_type, eqstr_id, args); break; } } @@ -1243,7 +1240,7 @@ void SharedTaskManager::SendMembersMessage(SharedTask *shared_task, int chat_typ } for (const auto &member : shared_task->GetMembers()) { - client_list.SendCharacterMessage(member.character_id, chat_type, message); + ClientList::Instance()->SendCharacterMessage(member.character_id, chat_type, message); } } @@ -1274,7 +1271,7 @@ void SharedTaskManager::SendMembersMessageID( memcpy(buf->args, serialized_args.buffer(), serialized_args.size()); for (const auto &member : shared_task->GetMembers()) { - auto character = client_list.FindCLEByCharacterID(member.character_id); + auto character = ClientList::Instance()->FindCLEByCharacterID(member.character_id); if (character && character->Server()) { strn0cpy(buf->client_name, character->name(), sizeof(buf->client_name)); character->Server()->SendPacket(pack.get()); @@ -1320,13 +1317,13 @@ bool SharedTaskManager::CanRequestSharedTask(uint32_t task_id, const SharedTaskR // messages for every character already in a shared task for (const auto& it : shared_task_members) { if (it.character_id == request.leader_id) { - client_list.SendCharacterMessageID(request.leader_id, Chat::Red, TaskStr::REQUEST_HAVE); + ClientList::Instance()->SendCharacterMessageID(request.leader_id, Chat::Red, TaskStr::REQUEST_HAVE); } else if (request.group_type == SharedTaskRequestGroupType::Group) { - client_list.SendCharacterMessageID(request.leader_id, Chat::Red, TaskStr::REQUEST_GROUP_HAS, {it.character_name}); + ClientList::Instance()->SendCharacterMessageID(request.leader_id, Chat::Red, TaskStr::REQUEST_GROUP_HAS, {it.character_name}); } else { - client_list.SendCharacterMessageID(request.leader_id, Chat::Red, TaskStr::REQUEST_RAID_HAS, {it.character_name}); + ClientList::Instance()->SendCharacterMessageID(request.leader_id, Chat::Red, TaskStr::REQUEST_RAID_HAS, {it.character_name}); } } @@ -1335,40 +1332,40 @@ bool SharedTaskManager::CanRequestSharedTask(uint32_t task_id, const SharedTaskR // check if any party member's minimum level is too low (pre-2014 this was average level) if (task.min_level > 0 && request.lowest_level < task.min_level) { - client_list.SendCharacterMessage(request.leader_id, Chat::Red, TaskStr::Get(TaskStr::LVL_TOO_LOW)); + ClientList::Instance()->SendCharacterMessage(request.leader_id, Chat::Red, TaskStr::Get(TaskStr::LVL_TOO_LOW)); return false; } // check if any party member's maximum level is too high (pre-2014 this was average level) if (task.max_level > 0 && request.highest_level > task.max_level) { - client_list.SendCharacterMessage(request.leader_id, Chat::Red, TaskStr::Get(TaskStr::LVL_TOO_HIGH)); + ClientList::Instance()->SendCharacterMessage(request.leader_id, Chat::Red, TaskStr::Get(TaskStr::LVL_TOO_HIGH)); return false; } // allow gm/dev bypass for minimum player count requirements - auto requester = client_list.FindCLEByCharacterID(request.leader_id); + auto requester = ClientList::Instance()->FindCLEByCharacterID(request.leader_id); bool is_gm = (requester && requester->GetGM()); // check if party member count is below the minimum if (!is_gm && task.min_players > 0 && request.members.size() < task.min_players) { - client_list.SendCharacterMessageID(request.leader_id, Chat::Red, TaskStr::MIN_PLAYERS); + ClientList::Instance()->SendCharacterMessageID(request.leader_id, Chat::Red, TaskStr::MIN_PLAYERS); return false; } if (is_gm) { - client_list.SendCharacterMessage(requester->CharID(), Chat::White, "Your GM flag allows you to bypass shared task minimum player requirements."); + ClientList::Instance()->SendCharacterMessage(requester->CharID(), Chat::White, "Your GM flag allows you to bypass shared task minimum player requirements."); } // check if party member count is above the maximum // todo: live creates the shared task but truncates members if it exceeds max (sorted by leader and raid group numbers) if (task.max_players > 0 && request.members.size() > task.max_players) { - client_list.SendCharacterMessageID(request.leader_id, Chat::Red, TaskStr::MAX_PLAYERS); + ClientList::Instance()->SendCharacterMessageID(request.leader_id, Chat::Red, TaskStr::MAX_PLAYERS); return false; } // check if party level spread exceeds task's maximum if (task.level_spread > 0 && (request.highest_level - request.lowest_level) > task.level_spread) { - client_list.SendCharacterMessageID(request.leader_id, Chat::Red, TaskStr::LVL_SPREAD_HIGH); + ClientList::Instance()->SendCharacterMessageID(request.leader_id, Chat::Red, TaskStr::LVL_SPREAD_HIGH); return false; } @@ -1384,10 +1381,10 @@ bool SharedTaskManager::CanRequestSharedTask(uint32_t task_id, const SharedTaskR if (character_task_timers.front().character_id == request.leader_id) { if (timer_type == TaskTimerType::Replay) { - client_list.SendCharacterMessageID(request.leader_id, Chat::Red, TaskStr::YOU_REPLAY_TIMER, {days, hours, mins}); + ClientList::Instance()->SendCharacterMessageID(request.leader_id, Chat::Red, TaskStr::YOU_REPLAY_TIMER, {days, hours, mins}); } else if (timer_type == TaskTimerType::Request) { - client_list.SendCharacterMessage(request.leader_id, Chat::Red, fmt::format( + ClientList::Instance()->SendCharacterMessage(request.leader_id, Chat::Red, fmt::format( TaskStr::Get(TaskStr::YOU_REQUEST_TIMER), days, hours, mins)); } } @@ -1398,11 +1395,11 @@ bool SharedTaskManager::CanRequestSharedTask(uint32_t task_id, const SharedTaskR }); if (it != request.members.end() && timer_type == TaskTimerType::Replay) { - client_list.SendCharacterMessageID(request.leader_id, Chat::Red, + ClientList::Instance()->SendCharacterMessageID(request.leader_id, Chat::Red, TaskStr::PLAYER_REPLAY_TIMER, {it->character_name, days, hours, mins}); } else if (it != request.members.end() && timer_type == TaskTimerType::Request) { - client_list.SendCharacterMessage(request.leader_id, Chat::Red, fmt::format( + ClientList::Instance()->SendCharacterMessage(request.leader_id, Chat::Red, fmt::format( TaskStr::Get(TaskStr::PLAYER_REQUEST_TIMER), it->character_name, days, hours, mins)); } } @@ -1426,7 +1423,7 @@ bool SharedTaskManager::CanAddPlayer(SharedTask *s, uint32_t character_id, std:: } // check if player is online and in cle (other checks require online) - auto cle = client_list.FindCLEByCharacterID(character_id); + auto cle = ClientList::Instance()->FindCLEByCharacterID(character_id); if (!cle || !cle->Server()) { SendLeaderMessageID(s, Chat::Red, TaskStr::PLAYER_NOT_ONLINE, {player_name}); SendLeaderMessageID(s, Chat::Red, TaskStr::COULD_NOT_BE_INVITED, {player_name}); @@ -1614,7 +1611,7 @@ void SharedTaskManager::AddReplayTimers(SharedTask *s) task_timers.emplace_back(timer); - client_list.SendCharacterMessage( + ClientList::Instance()->SendCharacterMessage( member_id, Chat::Yellow, fmt::format( diff --git a/world/shared_task_manager.h b/world/shared_task_manager.h index 3f41ef7be..197a8bce2 100644 --- a/world/shared_task_manager.h +++ b/world/shared_task_manager.h @@ -102,6 +102,12 @@ public: void SetSharedTasks(const std::vector &shared_tasks); SharedTaskManager * PurgeExpiredSharedTasks(); + + static SharedTaskManager* Instance() + { + static SharedTaskManager instance; + return &instance; + } protected: // reference to database Database *m_database; diff --git a/world/shared_task_world_messaging.cpp b/world/shared_task_world_messaging.cpp index 01584c495..39f4af804 100644 --- a/world/shared_task_world_messaging.cpp +++ b/world/shared_task_world_messaging.cpp @@ -14,26 +14,22 @@ #include "../common/repositories/task_activities_repository.h" #include "dynamic_zone.h" -extern ClientList client_list; -extern ZSList zoneserver_list; -extern SharedTaskManager shared_task_manager; - void SharedTaskWorldMessaging::HandleZoneMessage(ServerPacket *pack) { switch (pack->opcode) { case ServerOP_SharedTaskRequest: { auto *r = (ServerSharedTaskRequest_Struct *) pack->pBuffer; LogTasksDetail( - "[ServerOP_SharedTaskRequest] Received request from character [{}] task_id [{}] npc_type_id [{}]", + "[ServerOP_SharedTaskRequest] Received request from character [{}] task_id [{}] npc_entity_id [{}]", r->requested_character_id, r->requested_task_id, - r->requested_npc_type_id + r->requested_npc_entity_id ); - shared_task_manager.AttemptSharedTaskCreation( + SharedTaskManager::Instance()->AttemptSharedTaskCreation( r->requested_task_id, r->requested_character_id, - r->requested_npc_type_id + r->requested_npc_entity_id ); break; @@ -47,11 +43,11 @@ void SharedTaskWorldMessaging::HandleZoneMessage(ServerPacket *pack) r->remove_from_db ); - auto s = shared_task_manager.FindSharedTaskByTaskIdAndCharacterId(r->task_id, r->requested_character_id); + auto s = SharedTaskManager::Instance()->FindSharedTaskByTaskIdAndCharacterId(r->task_id, r->requested_character_id); if (s) { auto member = s->FindMemberFromCharacterID(r->requested_character_id); if (member.character_id != 0) { - shared_task_manager.RemoveMember(s, member, r->remove_from_db); + SharedTaskManager::Instance()->RemoveMember(s, member, r->remove_from_db); } } break; @@ -64,14 +60,14 @@ void SharedTaskWorldMessaging::HandleZoneMessage(ServerPacket *pack) r->task_id ); - auto t = shared_task_manager.FindSharedTaskByTaskIdAndCharacterId(r->task_id, r->source_character_id); + auto t = SharedTaskManager::Instance()->FindSharedTaskByTaskIdAndCharacterId(r->task_id, r->source_character_id); if (t) { auto leader = t->GetLeader(); if (leader.character_id != r->source_character_id) { - client_list.SendCharacterMessageID(r->source_character_id, Chat::Red, TaskStr::NOT_LEADER, {leader.character_name}); + ClientList::Instance()->SendCharacterMessageID(r->source_character_id, Chat::Red, TaskStr::NOT_LEADER, {leader.character_name}); } else { - shared_task_manager.RemoveEveryoneFromSharedTask(t, r->source_character_id); + SharedTaskManager::Instance()->RemoveEveryoneFromSharedTask(t, r->source_character_id); } } @@ -89,7 +85,7 @@ void SharedTaskWorldMessaging::HandleZoneMessage(ServerPacket *pack) (r->ignore_quest_update ? "true" : "false") ); - shared_task_manager.SharedTaskActivityUpdate( + SharedTaskManager::Instance()->SharedTaskActivityUpdate( r->source_character_id, r->task_id, r->activity_id, @@ -108,7 +104,7 @@ void SharedTaskWorldMessaging::HandleZoneMessage(ServerPacket *pack) r->task_id ); - auto t = shared_task_manager.FindSharedTaskByTaskIdAndCharacterId(r->task_id, r->source_character_id); + auto t = SharedTaskManager::Instance()->FindSharedTaskByTaskIdAndCharacterId(r->task_id, r->source_character_id); if (t) { LogTasksDetail( "[ServerOP_SharedTaskRequestMemberlist] Found shared task character [{}] shared_task_id [{}]", @@ -116,7 +112,7 @@ void SharedTaskWorldMessaging::HandleZoneMessage(ServerPacket *pack) t->GetDbSharedTask().id ); - shared_task_manager.SendSharedTaskMemberList( + SharedTaskManager::Instance()->SendSharedTaskMemberList( r->source_character_id, t->GetMembers() ); @@ -134,7 +130,7 @@ void SharedTaskWorldMessaging::HandleZoneMessage(ServerPacket *pack) r->player_name ); - auto t = shared_task_manager.FindSharedTaskByTaskIdAndCharacterId(r->task_id, r->source_character_id); + auto t = SharedTaskManager::Instance()->FindSharedTaskByTaskIdAndCharacterId(r->task_id, r->source_character_id); if (t) { LogTasksDetail( "[ServerOP_SharedTaskRemovePlayer] Found shared task character [{}] shared_task_id [{}]", @@ -144,7 +140,7 @@ void SharedTaskWorldMessaging::HandleZoneMessage(ServerPacket *pack) auto leader = t->GetLeader(); if (leader.character_id != r->source_character_id) { - client_list.SendCharacterMessageID(r->source_character_id, Chat::Red, TaskStr::NOT_LEADER, {leader.character_name}); + ClientList::Instance()->SendCharacterMessageID(r->source_character_id, Chat::Red, TaskStr::NOT_LEADER, {leader.character_name}); } else { LogTasksDetail( @@ -155,11 +151,11 @@ void SharedTaskWorldMessaging::HandleZoneMessage(ServerPacket *pack) auto member = t->FindMemberFromCharacterName(r->player_name); if (member.character_id == 0) { - shared_task_manager.SendLeaderMessageID(t, Chat::Red, TaskStr::IS_NOT_MEMBER, {r->player_name}); + SharedTaskManager::Instance()->SendLeaderMessageID(t, Chat::Red, TaskStr::IS_NOT_MEMBER, {r->player_name}); } else { - shared_task_manager.RemoveMember(t, member, true); - shared_task_manager.SendLeaderMessageID(t, Chat::Yellow, TaskStr::PLAYER_REMOVED, {member.character_name, t->GetTaskData().title}); + SharedTaskManager::Instance()->RemoveMember(t, member, true); + SharedTaskManager::Instance()->SendLeaderMessageID(t, Chat::Yellow, TaskStr::PLAYER_REMOVED, {member.character_name, t->GetTaskData().title}); } } } @@ -176,7 +172,7 @@ void SharedTaskWorldMessaging::HandleZoneMessage(ServerPacket *pack) r->player_name ); - auto t = shared_task_manager.FindSharedTaskByTaskIdAndCharacterId(r->task_id, r->source_character_id); + auto t = SharedTaskManager::Instance()->FindSharedTaskByTaskIdAndCharacterId(r->task_id, r->source_character_id); if (t) { LogTasksDetail( "[ServerOP_SharedTaskMakeLeader] Found shared task character [{}] shared_task_id [{}]", @@ -186,10 +182,10 @@ void SharedTaskWorldMessaging::HandleZoneMessage(ServerPacket *pack) auto leader = t->GetLeader(); if (leader.character_id != r->source_character_id) { - client_list.SendCharacterMessageID(r->source_character_id, Chat::Red, TaskStr::NOT_LEADER, {leader.character_name}); + ClientList::Instance()->SendCharacterMessageID(r->source_character_id, Chat::Red, TaskStr::NOT_LEADER, {leader.character_name}); } else if (strcasecmp(leader.character_name.c_str(), r->player_name) == 0) { - client_list.SendCharacterMessageID(r->source_character_id, Chat::Red, TaskStr::YOU_ALREADY_LEADER); + ClientList::Instance()->SendCharacterMessageID(r->source_character_id, Chat::Red, TaskStr::YOU_ALREADY_LEADER); } else { LogTasksDetail( @@ -199,7 +195,7 @@ void SharedTaskWorldMessaging::HandleZoneMessage(ServerPacket *pack) ); std::string character_name = r->player_name; - shared_task_manager.MakeLeaderByPlayerName(t, character_name); + SharedTaskManager::Instance()->MakeLeaderByPlayerName(t, character_name); } } @@ -215,7 +211,7 @@ void SharedTaskWorldMessaging::HandleZoneMessage(ServerPacket *pack) r->player_name ); - auto t = shared_task_manager.FindSharedTaskByTaskIdAndCharacterId(r->task_id, r->source_character_id); + auto t = SharedTaskManager::Instance()->FindSharedTaskByTaskIdAndCharacterId(r->task_id, r->source_character_id); if (t) { LogTasksDetail( "[ServerOP_SharedTaskAddPlayer] Found shared task character [{}] shared_task_id [{}]", @@ -226,7 +222,7 @@ void SharedTaskWorldMessaging::HandleZoneMessage(ServerPacket *pack) auto leader = t->GetLeader(); if (leader.character_id != r->source_character_id) { // taskadd is client sided with System color in newer clients, server side might still be red - client_list.SendCharacterMessageID(r->source_character_id, Chat::Red, TaskStr::NOT_LEADER, {leader.character_name}); + ClientList::Instance()->SendCharacterMessageID(r->source_character_id, Chat::Red, TaskStr::NOT_LEADER, {leader.character_name}); } else { LogTasksDetail( @@ -236,7 +232,7 @@ void SharedTaskWorldMessaging::HandleZoneMessage(ServerPacket *pack) ); std::string character_name = r->player_name; - shared_task_manager.InvitePlayerByPlayerName(t, character_name); + SharedTaskManager::Instance()->InvitePlayerByPlayerName(t, character_name); } } @@ -252,21 +248,21 @@ void SharedTaskWorldMessaging::HandleZoneMessage(ServerPacket *pack) r->accepted ); - auto t = shared_task_manager.FindSharedTaskById(r->shared_task_id); - if (t && shared_task_manager.IsInvitationActive(r->shared_task_id, r->source_character_id)) { + auto t = SharedTaskManager::Instance()->FindSharedTaskById(r->shared_task_id); + if (t && SharedTaskManager::Instance()->IsInvitationActive(r->shared_task_id, r->source_character_id)) { LogTasksDetail( "[ServerOP_SharedTaskInviteAcceptedPlayer] Found shared task character [{}] shared_task_id [{}]", r->source_character_id, t->GetDbSharedTask().id ); - shared_task_manager.RemoveActiveInvitation(r->shared_task_id, r->source_character_id); + SharedTaskManager::Instance()->RemoveActiveInvitation(r->shared_task_id, r->source_character_id); if (r->accepted) { - shared_task_manager.AddPlayerByCharacterIdAndName(t, r->source_character_id, r->player_name); + SharedTaskManager::Instance()->AddPlayerByCharacterIdAndName(t, r->source_character_id, r->player_name); } else { - shared_task_manager.SendLeaderMessageID(t, Chat::Red, TaskStr::PLAYER_DECLINED_OFFER, {r->player_name}); + SharedTaskManager::Instance()->SendLeaderMessageID(t, Chat::Red, TaskStr::PLAYER_DECLINED_OFFER, {r->player_name}); } } break; @@ -280,25 +276,25 @@ void SharedTaskWorldMessaging::HandleZoneMessage(ServerPacket *pack) buf->task_id ); - auto t = shared_task_manager.FindSharedTaskByTaskIdAndCharacterId(buf->task_id, buf->source_character_id); + auto t = SharedTaskManager::Instance()->FindSharedTaskByTaskIdAndCharacterId(buf->task_id, buf->source_character_id); if (t) { DynamicZone dz; - dz.LoadSerializedDzPacket(buf->cereal_data, buf->cereal_size); + dz.Unserialize({ buf->cereal_data, buf->cereal_size }); - shared_task_manager.CreateDynamicZone(t, dz); + SharedTaskManager::Instance()->CreateDynamicZone(t, dz); } break; } case ServerOP_SharedTaskPurgeAllCommand: { LogTasksDetail("[ServerOP_SharedTaskPurgeAllCommand] Received request to purge all shared tasks"); - shared_task_manager.PurgeAllSharedTasks(); + SharedTaskManager::Instance()->PurgeAllSharedTasks(); auto p = std::make_unique( ServerOP_SharedTaskPurgeAllCommand, 0 ); - zoneserver_list.SendPacket(p.get()); + ZSList::Instance()->SendPacket(p.get()); break; } @@ -311,7 +307,7 @@ void SharedTaskWorldMessaging::HandleZoneMessage(ServerPacket *pack) buf->task_id ); - auto s = shared_task_manager.FindSharedTaskByTaskIdAndCharacterId(buf->task_id, buf->source_character_id); + auto s = SharedTaskManager::Instance()->FindSharedTaskByTaskIdAndCharacterId(buf->task_id, buf->source_character_id); if (s) { std::vector player_names; @@ -319,40 +315,40 @@ void SharedTaskWorldMessaging::HandleZoneMessage(ServerPacket *pack) player_names.emplace_back(member.character_name); if (member.is_leader) { - client_list.SendCharacterMessageID(buf->source_character_id, Chat::Yellow, TaskStr::LEADER_PRINT, {member.character_name}); + ClientList::Instance()->SendCharacterMessageID(buf->source_character_id, Chat::Yellow, TaskStr::LEADER_PRINT, {member.character_name}); } } std::string player_list = fmt::format("{}", Strings::Join(player_names, ", ")); - client_list.SendCharacterMessageID(buf->source_character_id, Chat::Yellow, TaskStr::MEMBERS_PRINT, {player_list}); + ClientList::Instance()->SendCharacterMessageID(buf->source_character_id, Chat::Yellow, TaskStr::MEMBERS_PRINT, {player_list}); } break; } case ServerOP_SharedTaskLock: { auto buf = reinterpret_cast(pack->pBuffer); - auto shared_task = shared_task_manager.FindSharedTaskByTaskIdAndCharacterId(buf->task_id, buf->source_character_id); + auto shared_task = SharedTaskManager::Instance()->FindSharedTaskByTaskIdAndCharacterId(buf->task_id, buf->source_character_id); if (shared_task) { - shared_task_manager.LockTask(shared_task, buf->lock); + SharedTaskManager::Instance()->LockTask(shared_task, buf->lock); } break; } case ServerOP_SharedTaskEndByDz: { auto buf = reinterpret_cast(pack->pBuffer); - auto shared_task = shared_task_manager.FindSharedTaskByDzId(buf->dz_id); + auto shared_task = SharedTaskManager::Instance()->FindSharedTaskByDzId(buf->dz_id); if (shared_task) { - shared_task_manager.Terminate(*shared_task, buf->send_fail, true); + SharedTaskManager::Instance()->Terminate(*shared_task, buf->send_fail, true); } break; } case ServerOP_SharedTaskEnd: { auto buf = reinterpret_cast(pack->pBuffer); - auto shared_task = shared_task_manager.FindSharedTaskByTaskIdAndCharacterId(buf->task_id, buf->character_id); + auto shared_task = SharedTaskManager::Instance()->FindSharedTaskByTaskIdAndCharacterId(buf->task_id, buf->character_id); if (shared_task) { - shared_task_manager.Terminate(*shared_task, buf->send_fail, true); + SharedTaskManager::Instance()->Terminate(*shared_task, buf->send_fail, true); } break; } diff --git a/world/ucs.cpp b/world/ucs.cpp index 0af363051..0cc70a0ea 100644 --- a/world/ucs.cpp +++ b/world/ucs.cpp @@ -31,8 +31,6 @@ void UCSConnection::SetConnection(std::shared_ptr(1000, true, std::bind(&UCSConnection::OnKeepAlive, this, std::placeholders::_1)); } const std::shared_ptr &UCSConnection::GetConnection() const @@ -92,13 +90,3 @@ void UCSConnection::SendMessage(const char *From, const char *Message) SendPacket(pack); safe_delete(pack); } - -void UCSConnection::OnKeepAlive(EQ::Timer *t) -{ - if (!connection) { - return; - } - - ServerPacket pack(ServerOP_KeepAlive, 0); - connection->SendPacket(&pack); -} diff --git a/world/ucs.h b/world/ucs.h index d226e4dc9..a324d91fc 100644 --- a/world/ucs.h +++ b/world/ucs.h @@ -19,15 +19,16 @@ public: const std::shared_ptr &GetConnection() const; inline bool IsConnected() const { return connection->Handle() ? connection->Handle()->IsConnected() : false; } + static UCSConnection* Instance() + { + static UCSConnection instance; + return &instance; + } + private: inline std::string GetIP() const { return (connection && connection->Handle()) ? connection->Handle()->RemoteIP() : 0; } std::shared_ptr connection; - /** - * Keepalive - */ - std::unique_ptr m_keepalive; - void OnKeepAlive(EQ::Timer *t); }; #endif /*UCS_H_*/ diff --git a/world/web_interface.h b/world/web_interface.h index fc90265d9..0725c190a 100644 --- a/world/web_interface.h +++ b/world/web_interface.h @@ -42,6 +42,11 @@ public: void SendError(const std::string &uuid, const std::string &message); void SendError(const std::string &uuid, const std::string &message, const std::string &id); + static WebInterfaceList* Instance() + { + static WebInterfaceList instance; + return &instance; + } private: std::map> m_interfaces; }; diff --git a/world/web_interface_eqw.cpp b/world/web_interface_eqw.cpp index ad0cf6953..2601eccc8 100644 --- a/world/web_interface_eqw.cpp +++ b/world/web_interface_eqw.cpp @@ -6,9 +6,6 @@ #include "zonelist.h" #include "launcher_list.h" -extern LoginServerList loginserverlist; -extern ClientList client_list; -extern ZSList zoneserver_list; extern LauncherList launcher_list; void EQW__GetConfig(WebInterface *i, const std::string& method, const std::string& id, const Json::Value& params) { @@ -33,8 +30,8 @@ void EQW__IsLocked(WebInterface *i, const std::string& method, const std::string void EQW__Lock(WebInterface *i, const std::string& method, const std::string& id, const Json::Value& params) { WorldConfig::LockWorld(); - if (loginserverlist.Connected()) { - loginserverlist.SendStatus(); + if (LoginServerList::Instance()->Connected()) { + LoginServerList::Instance()->SendStatus(); } Json::Value ret; @@ -44,8 +41,8 @@ void EQW__Lock(WebInterface *i, const std::string& method, const std::string& id void EQW__Unlock(WebInterface *i, const std::string& method, const std::string& id, const Json::Value& params) { WorldConfig::UnlockWorld(); - if (loginserverlist.Connected()) { - loginserverlist.SendStatus(); + if (LoginServerList::Instance()->Connected()) { + LoginServerList::Instance()->SendStatus(); } Json::Value ret; @@ -54,12 +51,12 @@ void EQW__Unlock(WebInterface *i, const std::string& method, const std::string& } void EQW__GetPlayerCount(WebInterface *i, const std::string& method, const std::string& id, const Json::Value& params) { - Json::Value ret = client_list.GetClientCount(); + Json::Value ret = ClientList::Instance()->GetClientCount(); i->SendResponse(id, ret); } void EQW__GetZoneCount(WebInterface *i, const std::string& method, const std::string& id, const Json::Value& params) { - Json::Value ret = zoneserver_list.GetZoneCount(); + Json::Value ret = ZSList::Instance()->GetZoneCount(); i->SendResponse(id, ret); } @@ -69,7 +66,7 @@ void EQW__GetLauncherCount(WebInterface *i, const std::string& method, const std } void EQW__GetLoginServerCount(WebInterface *i, const std::string& method, const std::string& id, const Json::Value& params) { - Json::Value ret = loginserverlist.GetServerCount(); + Json::Value ret = LoginServerList::Instance()->GetServerCount(); i->SendResponse(id, ret); } diff --git a/world/wguild_mgr.cpp b/world/wguild_mgr.cpp index 03b7204fb..ace4d9e0f 100644 --- a/world/wguild_mgr.cpp +++ b/world/wguild_mgr.cpp @@ -34,9 +34,6 @@ #include "../common/repositories/tributes_repository.h" #include "../common/repositories/tribute_levels_repository.h" - -extern ClientList client_list; -extern ZSList zoneserver_list; std::map tribute_list; WorldGuildManager guild_mgr; @@ -50,7 +47,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); + ZSList::Instance()->SendPacketToZonesWithGuild(guild_id, pack); safe_delete(pack); } @@ -61,7 +58,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); + ZSList::Instance()->SendPacketToZonesWithGuild(guild_id, pack); safe_delete(pack); } @@ -70,7 +67,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); + ZSList::Instance()->SendPacketToZonesWithGuild(guild_id, pack); safe_delete(pack); } @@ -85,15 +82,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. + ZSList::Instance()->SendPacketToBootedZones(pack); + break; } @@ -104,11 +100,11 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) { RefreshGuild(s->guild_id); //preform the local update - client_list.UpdateClientGuild(s->char_id, s->guild_id); + ClientList::Instance()->UpdateClientGuild(s->char_id, s->guild_id); //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); + ZSList::Instance()->SendPacketToZonesWithGuild(s->guild_id, pack); break; } @@ -131,7 +127,7 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) { LogGuilds("Received and broadcasting guild delete for guild [{}]", s->guild_id); //broadcast this packet to all zones. - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); break; } @@ -147,7 +143,7 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) { auto s = (ServerGuildID_Struct *)pack->pBuffer; RefreshGuild(s->guild_id); - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacketToZonesWithGuild(s->guild_id, pack); break; } case ServerOP_GuildPermissionUpdate: @@ -179,7 +175,7 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) { sg->function_value ); - zoneserver_list.SendPacketToBootedZones(pack); + ZSList::Instance()->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 +209,7 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) { rnc->rank, rnc->rank_name ); - zoneserver_list.SendPacketToBootedZones(pack); + ZSList::Instance()->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 +226,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; + ZSList::Instance()->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 +240,15 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) { BaseGuildManager::RefreshGuild(in->guild_id); } - zoneserver_list.SendPacketToBootedZones(pack); + ZSList::Instance()->SendPacketToZonesWithGuild(in->guild_id, pack); break; } + case ServerOP_GuildSendGuildList: { + auto in = (ServerOP_GuildMessage_Struct *) pack->pBuffer; + ZSList::Instance()->SendPacketToBootedZones(pack); + break; + } + default: LogGuilds("Unknown packet {:#04x} received from zone??", pack->opcode); break; @@ -302,7 +304,7 @@ void WorldGuildManager::Process() uint32 WorldGuildManager::GetGuildTributeCost(uint32 guild_id) { - auto guild_members = client_list.GetGuildClientsWithTributeOptIn(guild_id); + auto guild_members = ClientList::Instance()->GetGuildClientsWithTributeOptIn(guild_id); auto total = guild_members.size(); auto total_cost = 0; @@ -451,6 +453,6 @@ void WorldGuildManager::SendGuildTributeFavorAndTimer(uint32 guild_id, uint32 fa data->tribute_timer = time; data->trophy_timer = 0; - zoneserver_list.SendPacketToBootedZones(sp); + ZSList::Instance()->SendPacketToZonesWithGuild(guild_id, sp); safe_delete(sp) } diff --git a/world/world_boot.cpp b/world/world_boot.cpp index 831babdb7..19667927c 100644 --- a/world/world_boot.cpp +++ b/world/world_boot.cpp @@ -5,12 +5,12 @@ #include "../common/http/uri.h" #include "../common/net/console_server.h" #include "../common/net/servertalk_server.h" +#include "../common/repositories/character_expedition_lockouts_repository.h" #include "../common/repositories/character_task_timers_repository.h" #include "../common/rulesys.h" #include "../common/strings.h" #include "adventure_manager.h" #include "dynamic_zone_manager.h" -#include "expedition_database.h" #include "login_server_list.h" #include "shared_task_manager.h" #include "ucs.h" @@ -27,8 +27,8 @@ #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; auto mutex = new Mutex; @@ -52,11 +52,11 @@ void WorldBoot::GMSayHookCallBackProcessWorld(uint16 log_category, const char *f auto message_split = Strings::Split(message, '\n'); for (size_t iter = 0; iter < message_split.size(); ++iter) { - zoneserver_list.SendEmoteMessage( + ZSList::Instance()->SendEmoteMessage( 0, 0, AccountStatus::QuestTroupe, - LogSys.GetGMSayColorFromCategory(log_category), + EQEmuLogSys::Instance()->GetGMSayColorFromCategory(log_category), fmt::format( " {}{}", (iter == 0 ? " ---" : ""), @@ -68,11 +68,11 @@ void WorldBoot::GMSayHookCallBackProcessWorld(uint16 log_category, const char *f return; } - zoneserver_list.SendEmoteMessage( + ZSList::Instance()->SendEmoteMessage( 0, 0, AccountStatus::QuestTroupe, - LogSys.GetGMSayColorFromCategory(log_category), + EQEmuLogSys::Instance()->GetGMSayColorFromCategory(log_category), "%s", fmt::format("[{}] [{}] {}", Logs::LogCategoryName[log_category], func, message).c_str() ); @@ -82,12 +82,12 @@ bool WorldBoot::HandleCommandInput(int argc, char **argv) { // command handler if (argc > 1) { - LogSys.SilenceConsoleLogging(); - path.LoadPaths(); + EQEmuLogSys::Instance()->SilenceConsoleLogging(); + PathManager::Instance()->Init(); WorldConfig::LoadConfig(); LoadDatabaseConnections(); RuleManager::Instance()->LoadRules(&database, "default", false); - LogSys.EnableConsoleLogging(); + EQEmuLogSys::Instance()->EnableConsoleLogging(); WorldserverCLI::CommandHandler(argc, argv); } @@ -101,6 +101,13 @@ bool WorldBoot::HandleCommandInput(int argc, char **argv) } } + // check if we ran a valid command, this whole CLI handler needs to be improved at a later time + std::string arg1 = argc >= 2 ? argv[1] : ""; + if (argc >= 2 && !Strings::Contains(arg1, ":")) { + std::cout << "Invalid command, use --help to see available commands" << std::endl; + return true; + } + return false; } @@ -175,15 +182,13 @@ int get_file_size(const std::string &filename) // path to file return size; } -extern LoginServerList loginserverlist; - void WorldBoot::RegisterLoginservers() { const auto c = EQEmuConfig::get(); if (c->LoginCount == 0) { if (c->LoginHost.length()) { - loginserverlist.Add( + LoginServerList::Instance()->Add( c->LoginHost.c_str(), c->LoginPort, c->LoginAccount.c_str(), @@ -199,7 +204,7 @@ void WorldBoot::RegisterLoginservers() iterator.Reset(); while (iterator.MoreElements()) { if (iterator.GetData()->LoginHost.length()) { - loginserverlist.Add( + LoginServerList::Instance()->Add( iterator.GetData()->LoginHost.c_str(), iterator.GetData()->LoginPort, iterator.GetData()->LoginAccount.c_str(), @@ -218,18 +223,14 @@ void WorldBoot::RegisterLoginservers() } } -extern SharedTaskManager shared_task_manager; -extern AdventureManager adventure_manager; -extern WorldEventScheduler event_scheduler; - bool WorldBoot::DatabaseLoadRoutines(int argc, char **argv) { // logging system init - auto logging = LogSys.SetDatabase(&database) - ->SetLogPath(path.GetLogPath()) + auto logging = EQEmuLogSys::Instance()->SetDatabase(&database) + ->SetLogPath(PathManager::Instance()->GetLogPath()) ->LoadLogDatabaseSettings(); - LogSys.SetDiscordHandler(&WorldBoot::DiscordWebhookMessageHandler); + EQEmuLogSys::Instance()->SetDiscordHandler(&WorldBoot::DiscordWebhookMessageHandler); const auto c = EQEmuConfig::get(); if (c->auto_database_updates) { @@ -271,9 +272,9 @@ bool WorldBoot::DatabaseLoadRoutines(int argc, char **argv) LogInfo("Loading zones"); - zone_store.LoadZones(content_db); + ZoneStore::Instance()->LoadZones(content_db); - if (zone_store.GetZones().empty()) { + if (ZoneStore::Instance()->GetZones().empty()) { LogError("Failed to load zones data, check your schema for possible errors"); return 1; } @@ -349,27 +350,26 @@ bool WorldBoot::DatabaseLoadRoutines(int argc, char **argv) TimeOfDay_Struct eqTime{}; time_t realtime; eqTime = database.LoadTime(realtime); - zoneserver_list.worldclock.SetCurrentEQTimeOfDay(eqTime, realtime); + ZSList::Instance()->worldclock.SetCurrentEQTimeOfDay(eqTime, realtime); LogInfo("Deleted [{}] stale player corpses from database", database.DeleteStalePlayerCorpses()); LogInfo("Loading adventures"); - if (!adventure_manager.LoadAdventureTemplates()) { + if (!AdventureManager::Instance()->LoadAdventureTemplates()) { LogInfo("Unable to load adventure templates"); } - if (!adventure_manager.LoadAdventureEntries()) { + if (!AdventureManager::Instance()->LoadAdventureEntries()) { LogInfo("Unable to load adventure templates"); } - adventure_manager.LoadLeaderboardInfo(); + AdventureManager::Instance()->LoadLeaderboardInfo(); LogInfo("Purging expired dynamic zones and members"); dynamic_zone_manager.PurgeExpiredDynamicZones(); - LogInfo("Purging expired expeditions"); - ExpeditionDatabase::PurgeExpiredExpeditions(); - ExpeditionDatabase::PurgeExpiredCharacterLockouts(); + LogInfo("Purging expired character expedition lockouts"); + CharacterExpeditionLockoutsRepository::DeleteWhere(database, "expire_time <= NOW()"); LogInfo("Purging expired character task timers"); CharacterTaskTimersRepository::DeleteWhere(database, "expire_time <= NOW()"); @@ -386,26 +386,31 @@ bool WorldBoot::DatabaseLoadRoutines(int argc, char **argv) content_db.LoadCharacterCreateCombos(); LogInfo("Initializing [EventScheduler]"); - event_scheduler.SetDatabase(&database)->LoadScheduledEvents(); + WorldEventScheduler::Instance()->SetDatabase(&database)->LoadScheduledEvents(); LogInfo("Initializing [WorldContentService]"); - content_service.SetDatabase(&database) + WorldContentService::Instance()->SetDatabase(&database) ->SetContentDatabase(&content_db) ->SetExpansionContext() ->ReloadContentFlags(); LogInfo("Initializing [SharedTaskManager]"); - shared_task_manager.SetDatabase(&database) + SharedTaskManager::Instance()->SetDatabase(&database) ->SetContentDatabase(&content_db) ->LoadTaskData() ->LoadSharedTaskState(); LogInfo("Purging expired shared tasks"); - shared_task_manager.PurgeExpiredSharedTasks(); + SharedTaskManager::Instance()->PurgeExpiredSharedTasks(); LogInfo("Cleaning up instance corpses"); database.CleanupInstanceCorpses(); + if (RuleB(Zone, StateSavingOnShutdown)) { + ZoneStateSpawnsRepository::PurgeInvalidZoneStates(database); + ZoneStateSpawnsRepository::PurgeOldZoneStates(database); + } + return true; } @@ -616,14 +621,14 @@ void WorldBoot::Shutdown() void WorldBoot::SendDiscordMessage(int webhook_id, const std::string &message) { - if (UCSLink.IsConnected()) { + if (UCSConnection::Instance()->IsConnected()) { auto pack = new ServerPacket(ServerOP_DiscordWebhookMessage, sizeof(DiscordWebhookMessage_Struct) + 1); auto *q = (DiscordWebhookMessage_Struct *) pack->pBuffer; strn0cpy(q->message, message.c_str(), 2000); q->webhook_id = webhook_id; - UCSLink.SendPacket(pack); + UCSConnection::Instance()->SendPacket(pack); safe_delete(pack); } diff --git a/world/world_boot.h b/world/world_boot.h index 5ac9552d6..e58b900c5 100644 --- a/world/world_boot.h +++ b/world/world_boot.h @@ -4,8 +4,7 @@ #include #include "../common/types.h" #include "../common/discord/discord.h" - -extern UCSConnection UCSLink; +#include "ucs.h" class WorldBoot { public: diff --git a/world/world_event_scheduler.cpp b/world/world_event_scheduler.cpp index 86342b882..c1ab226ab 100644 --- a/world/world_event_scheduler.cpp +++ b/world/world_event_scheduler.cpp @@ -2,6 +2,7 @@ #include "../common/servertalk.h" #include #include "../common/rulesys.h" +#include "../common/server_reload_types.h" void WorldEventScheduler::Process(ZSList *zs_list) { @@ -55,11 +56,7 @@ void WorldEventScheduler::Process(ZSList *zs_list) if (e.event_type == ServerEvents::EVENT_TYPE_RELOAD_WORLD) { LogScheduler("Sending reload world event [{}]", e.event_data.c_str()); - auto pack = new ServerPacket(ServerOP_ReloadWorld, sizeof(ReloadWorld_Struct)); - auto *reload_world = (ReloadWorld_Struct *) pack->pBuffer; - reload_world->global_repop = ReloadWorld::Repop; - zs_list->SendPacket(pack); - safe_delete(pack); + zs_list->SendServerReload(ServerReload::Type::WorldRepop, nullptr); } } } diff --git a/world/world_event_scheduler.h b/world/world_event_scheduler.h index b2a6725df..4819b1400 100644 --- a/world/world_event_scheduler.h +++ b/world/world_event_scheduler.h @@ -7,6 +7,12 @@ class WorldEventScheduler : public ServerEventScheduler { public: void Process(ZSList *zs_list); + + static WorldEventScheduler* Instance() + { + static WorldEventScheduler instance; + return &instance; + } }; #endif //EQEMU_EVENT_SCHEDULER_H diff --git a/world/world_server_cli.cpp b/world/world_server_cli.cpp index 973374c01..3af88f953 100644 --- a/world/world_server_cli.cpp +++ b/world/world_server_cli.cpp @@ -34,6 +34,7 @@ void WorldserverCLI::CommandHandler(int argc, char **argv) function_map["test:repository2"] = &WorldserverCLI::TestRepository2; function_map["test:db-concurrency"] = &WorldserverCLI::TestDatabaseConcurrency; function_map["test:string-benchmark"] = &WorldserverCLI::TestStringBenchmarkCommand; + function_map["etl:settings"] = &WorldserverCLI::EtlGetSettings; EQEmuCommand::HandleMenu(function_map, cmd, argc, argv); } @@ -56,3 +57,4 @@ void WorldserverCLI::CommandHandler(int argc, char **argv) #include "cli/test_repository_2.cpp" #include "cli/test_string_benchmark.cpp" #include "cli/version.cpp" +#include "cli/etl_get_settings.cpp" diff --git a/world/world_server_cli.h b/world/world_server_cli.h index 0edebd261..1066158b9 100644 --- a/world/world_server_cli.h +++ b/world/world_server_cli.h @@ -25,6 +25,7 @@ public: static void TestRepository2(int argc, char **argv, argh::parser &cmd, std::string &description); static void TestDatabaseConcurrency(int argc, char **argv, argh::parser &cmd, std::string &description); static void TestStringBenchmarkCommand(int argc, char **argv, argh::parser &cmd, std::string &description); + static void EtlGetSettings(int argc, char **argv, argh::parser &cmd, std::string &description); }; diff --git a/world/worlddb.cpp b/world/worlddb.cpp index 986843da5..f47cdb608 100644 --- a/world/worlddb.cpp +++ b/world/worlddb.cpp @@ -26,20 +26,19 @@ #include #include "sof_char_create_data.h" #include "../common/repositories/character_instance_safereturns_repository.h" +#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; extern std::vector character_create_allocations; extern std::vector character_create_race_class_combos; - -/** - * @param account_id - * @param out_app - * @param client_version_bit - */ void WorldDatabase::GetCharSelectInfo(uint32 account_id, EQApplicationPacket **out_app, uint32 client_version_bit) { EQ::versions::ClientVersion @@ -55,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 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; @@ -243,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 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); } @@ -760,6 +732,12 @@ void WorldDatabase::SetTitaniumDefaultStartZone(PlayerProfile_Struct* in_pp, Cha in_pp->binds[0].zone_id = Zones::SHARVAHL; // sharvahl break; } + case StartZoneIndex::RatheMtn: + { + in_pp->zone_id = Zones::RATHEMTN; // rathemtn + in_pp->binds[0].zone_id = Zones::RATHEMTN; // rathemtn + break; + } } } } @@ -854,118 +832,84 @@ 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 &inventories, + const CharacterDataRepository::CharacterData &character, + EQ::InventoryProfile *inv +) { - if (!account_id || !name || !inv) + if (inventories.empty() || !character.id || !inv) { return false; + } - std::string query = StringFormat( - "SELECT" - " slotid," - " itemid," - " charges," - " color," - " augslot1," - " augslot2," - " augslot3," - " augslot4," - " augslot5," - " augslot6," - " instnodrop," - " custom_data," - " ornamenticon," - " ornamentidfile," - " ornament_hero_model " - "FROM" - " inventory " - "INNER JOIN" - " character_data ch " - "ON" - " ch.id = charid " - "WHERE" - " ch.name = '%s' " - "AND" - " ch.account_id = %i " - "AND" - " slotid >= %i " - "AND" - " slotid <= %i", - name, - account_id, - EQ::invslot::slotHead, - EQ::invslot::slotFeet - ); - auto results = QueryDatabase(query); - if (!results.Success()) - return false; - - for (auto row = results.begin(); row != results.end(); ++row) { - int16 slot_id = Strings::ToInt(row[0]); - - switch (slot_id) { - case EQ::invslot::slotFace: - case EQ::invslot::slotEar2: - case EQ::invslot::slotNeck: - case EQ::invslot::slotShoulders: - case EQ::invslot::slotBack: - case EQ::invslot::slotFinger1: - case EQ::invslot::slotFinger2: + for (const auto& e : inventories) { + if (e.character_id != character.id) { continue; - default: - break; } - uint32 item_id = Strings::ToInt(row[1]); - int8 charges = Strings::ToInt(row[2]); - uint32 color = Strings::ToUnsignedInt(row[3]); - - uint32 aug[EQ::invaug::SOCKET_COUNT]; - aug[0] = (uint32)Strings::ToInt(row[4]); - aug[1] = (uint32)Strings::ToInt(row[5]); - aug[2] = (uint32)Strings::ToInt(row[6]); - aug[3] = (uint32)Strings::ToInt(row[7]); - aug[4] = (uint32)Strings::ToInt(row[8]); - aug[5] = (uint32)Strings::ToInt(row[9]); - - bool instnodrop = ((row[10] && (uint16)Strings::ToInt(row[10])) ? true : false); - uint32 ornament_icon = (uint32)Strings::ToUnsignedInt(row[12]); - uint32 ornament_idfile = (uint32)Strings::ToUnsignedInt(row[13]); - uint32 ornament_hero_model = (uint32)Strings::ToUnsignedInt(row[14]); - - const EQ::ItemData *item = content_db.GetItem(item_id); - if (!item) - continue; - - EQ::ItemInstance *inst = content_db.CreateBaseItem(item, charges); - - if (inst == nullptr) - continue; - - inst->SetAttuned(instnodrop); - - if (row[11]) { - std::string data_str(row[11]); - inst->SetCustomDataString(data_str); + switch (e.slot_id) { + case EQ::invslot::slotFace: + case EQ::invslot::slotEar2: + case EQ::invslot::slotNeck: + case EQ::invslot::slotShoulders: + case EQ::invslot::slotBack: + case EQ::invslot::slotFinger1: + case EQ::invslot::slotFinger2: + continue; + default: + break; } - inst->SetOrnamentIcon(ornament_icon); - inst->SetOrnamentationIDFile(ornament_idfile); - inst->SetOrnamentHeroModel(item->HerosForgeModel); - if (color > 0) - inst->SetColor(color); + uint32 augment_ids[EQ::invaug::SOCKET_COUNT] = { + e.augment_one, + e.augment_two, + e.augment_three, + e.augment_four, + e.augment_five, + e.augment_six + }; - inst->SetCharges(charges); + const EQ::ItemData* item = content_db.GetItem(e.item_id); + if (!item) { + continue; + } + + EQ::ItemInstance *inst = content_db.CreateBaseItem(item, e.charges); + + if (!inst) { + continue; + } + + inst->SetCharges(e.charges); + + if (e.color > 0) { + inst->SetColor(e.color); + } if (item->IsClassCommon()) { for (int i = EQ::invaug::SOCKET_BEGIN; i <= EQ::invaug::SOCKET_END; i++) { - if (aug[i]) - inst->PutAugment(this, i, aug[i]); + if (augment_ids[i]) { + inst->PutAugment(this, i, augment_ids[i]); + } } } - inv->PutItem(slot_id, *inst); + inst->SetAttuned(e.instnodrop); + + if (!e.custom_data.empty()) { + inst->SetCustomDataString(e.custom_data); + } + + if (e.ornament_icon != 0 || e.ornament_idfile != 0 || e.ornament_hero_model != 0) { + inst->SetOrnamentIcon(e.ornament_icon); + inst->SetOrnamentationIDFile(e.ornament_idfile); + inst->SetOrnamentHeroModel(e.ornament_hero_model); + } else if (item->HerosForgeModel > 0) { + inst->SetOrnamentHeroModel(item->HerosForgeModel); + } + + inv->PutItem(e.slot_id, *inst); safe_delete(inst); } diff --git a/world/worlddb.h b/world/worlddb.h index 9054c77ea..5d9dab2e8 100644 --- a/world/worlddb.h +++ b/world/worlddb.h @@ -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 &inventories, + const CharacterDataRepository::CharacterData &character, + EQ::InventoryProfile *inv + ); }; extern WorldDatabase database; diff --git a/world/zonelist.cpp b/world/zonelist.cpp index 31427552a..f108031dc 100644 --- a/world/zonelist.cpp +++ b/world/zonelist.cpp @@ -28,10 +28,20 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "../common/event_sub.h" #include "web_interface.h" #include "../common/zone_store.h" +#include "../common/events/player_event_logs.h" +#include "../common/patches/patches.h" +#include "../common/skill_caps.h" +#include "../common/content/world_content_service.h" +#include "world_boot.h" +#include "shared_task_manager.h" +#include "dynamic_zone_manager.h" +#include "ucs.h" +#include "clientlist.h" +#include "queryserv.h" +#include "../common/repositories/trader_repository.h" +#include "../common/repositories/buyer_repository.h" extern uint32 numzones; -extern EQ::Random emu_random; -extern WebInterfaceList web_interface; volatile bool UCSServerAvailable_ = false; void CatchSignal(int sig_num); @@ -42,7 +52,6 @@ ZSList::ZSList() memset(pLockedZones, 0, sizeof(pLockedZones)); m_tick = std::make_unique(5000, true, std::bind(&ZSList::OnTick, this, std::placeholders::_1)); - m_keepalive = std::make_unique(1000, true, std::bind(&ZSList::OnKeepAlive, this, std::placeholders::_1)); } ZSList::~ZSList() { @@ -74,6 +83,8 @@ void ZSList::Remove(const std::string &uuid) while (iter != zone_server_list.end()) { if ((*iter)->GetUUID().compare(uuid) == 0) { auto port = (*iter)->GetCPort(); + (*iter)->CheckToClearTraderAndBuyerTables(); + zone_server_list.erase(iter); if (port != 0) { @@ -118,6 +129,16 @@ void ZSList::Process() { ).c_str() ); } + + if (!m_queued_reloads.empty()) { + m_queued_reloads_mutex.lock(); + for (auto &type : m_queued_reloads) { + LogInfo("Sending reload of type [{}] to zones", ServerReload::GetName(type)); + SendServerReload(type, nullptr); + } + m_queued_reloads.clear(); + m_queued_reloads_mutex.unlock(); + } } bool ZSList::SendPacket(ServerPacket* pack) { @@ -507,19 +528,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; @@ -527,22 +556,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; } @@ -647,7 +691,7 @@ void ZSList::RebootZone(const char* ip1, uint16 port, const char* ip2, uint32 sk safe_delete_array(tmp); return; } - uint32 z = emu_random.Int(0, y - 1); + uint32 z = EQ::Random::Instance()->Int(0, y - 1); auto pack = new ServerPacket(ServerOP_ZoneReboot, sizeof(ServerZoneReboot_Struct)); ServerZoneReboot_Struct* s = (ServerZoneReboot_Struct*)pack->pBuffer; @@ -843,14 +887,7 @@ void ZSList::OnTick(EQ::Timer *t) out["data"].append(outzone); } - web_interface.SendEvent(out); -} - -void ZSList::OnKeepAlive(EQ::Timer *t) -{ - for (auto &zone : zone_server_list) { - zone->SendKeepAlive(); - } + WebInterfaceList::Instance()->SendEvent(out); } const std::list> &ZSList::getZoneServerList() const @@ -869,3 +906,115 @@ bool ZSList::SendPacketToBootedZones(ServerPacket* pack) return true; } + +bool ZSList::SendPacketToZonesWithGuild(uint32 guild_id, ServerPacket* pack) +{ + auto servers = ClientList::Instance()->GetGuildZoneServers(guild_id); + for (auto const& z : zone_server_list) { + for (auto const& server_id : servers) { + if (z->GetID() == server_id && z->GetZoneID() > 0) { + z->SendPacket(pack); + } + } + } + + return true; +} + +bool ZSList::SendPacketToZonesWithGMs(ServerPacket* pack) +{ + auto servers = ClientList::Instance()->GetZoneServersWithGMs(); + for (auto const &z: zone_server_list) { + for (auto const &server_id: servers) { + if (z->GetID() == server_id && z->GetZoneID() > 0) { + z->SendPacket(pack); + } + } + } + + return true; +} + +void ZSList::SendServerReload(ServerReload::Type type, uchar *packet) +{ + static auto pack = ServerPacket(ServerOP_ServerReloadRequest, sizeof(ServerReload::Request)); + auto r = (ServerReload::Request *) pack.pBuffer; + + // Copy the packet data if it exists + if (packet) { + memcpy(pack.pBuffer, packet, sizeof(ServerReload::Request)); + } + + r->type = type; + r->requires_zone_booted = true; + + LogInfo("Sending reload to all zones for type [{}]", ServerReload::GetName(type)); + + static const std::unordered_set no_zone_boot_required = { + ServerReload::Type::Opcodes, + ServerReload::Type::Rules, + ServerReload::Type::ContentFlags, + ServerReload::Type::Logs, + ServerReload::Type::Commands, + ServerReload::Type::PerlExportSettings, + ServerReload::Type::DataBucketsCache, + ServerReload::Type::Quests, + ServerReload::Type::QuestsTimerReset, + ServerReload::Type::WorldRepop, + ServerReload::Type::WorldWithRespawn + }; + + // Set requires_zone_booted flag before executing reload logic + if (no_zone_boot_required.contains(type)) { + r->requires_zone_booted = false; + } + + // reload at the world level + if (type == ServerReload::Type::Opcodes) { + ReloadAllPatches(); + } else if (type == ServerReload::Type::Rules) { + RuleManager::Instance()->LoadRules(&database, RuleManager::Instance()->GetActiveRuleset(), true); + } else if (type == ServerReload::Type::SkillCaps) { + SkillCaps::Instance()->ReloadSkillCaps(); + } else if (type == ServerReload::Type::ContentFlags) { + WorldContentService::Instance()->SetExpansionContext()->ReloadContentFlags(); + } else if (type == ServerReload::Type::Logs) { + EQEmuLogSys::Instance()->LoadLogDatabaseSettings(); + PlayerEventLogs::Instance()->ReloadSettings(); + UCSConnection::Instance()->SendPacket(&pack); + QueryServConnection::Instance()->SendPacket(&pack); + } else if (type == ServerReload::Type::Tasks) { + SharedTaskManager::Instance()->LoadTaskData(); + } else if (type == ServerReload::Type::DzTemplates) { + dynamic_zone_manager.LoadTemplates(); + } + + // Send the packet to all zones with staggered delays + // to prevent all zones from reloading at the same time + // and causing a massive spike in CPU usage + // This is especially important for large servers + // with many zones + // we reload 10 zones every second + int counter = 0; + + for (auto &z: zone_server_list) { + bool is_local = r->zone_server_id != 0; + + // if the zone reload is local to a specific zone + if (r->zone_server_id != 0 && r->zone_server_id != z->GetID()) { + continue; + } + + // if the reload is local, we don't need to stagger the reloads + r->reload_at_unix = is_local ? 0 : (std::time(nullptr) + 1) + (counter / 10); + z->SendPacket(&pack); + ++counter; + } +} + +void ZSList::QueueServerReload(ServerReload::Type &type) +{ + m_queued_reloads_mutex.lock(); + m_queued_reloads.emplace_back(type); + m_queued_reloads_mutex.unlock(); +} diff --git a/world/zonelist.h b/world/zonelist.h index 71d364c9b..43dd52b44 100644 --- a/world/zonelist.h +++ b/world/zonelist.h @@ -5,9 +5,11 @@ #include "../common/eqtime.h" #include "../common/timer.h" #include "../common/event/timer.h" +#include "../common/server_reload_types.h" #include #include #include +#include class WorldTCPConnection; class ServerPacket; @@ -28,6 +30,8 @@ public: bool SendPacket(ServerPacket *pack); bool SendPacket(uint32 zoneid, ServerPacket *pack); bool SendPacket(uint32 zoneid, uint16 instanceid, ServerPacket *pack); + bool SendPacketToZonesWithGuild(uint32 guild_id, ServerPacket *pack); + bool SendPacketToZonesWithGMs(ServerPacket *pack); bool SendPacketToBootedZones(ServerPacket* pack); bool SetLockedZone(uint16 iZoneID, bool iLock); @@ -69,10 +73,21 @@ public: ZoneServer* FindByZoneID(uint32 ZoneID); const std::list> &getZoneServerList() const; + inline uint32_t GetServerListCount() { return zone_server_list.size(); } + void SendServerReload(ServerReload::Type type, uchar *packet = nullptr); + std::mutex m_queued_reloads_mutex; + std::vector m_queued_reloads = {}; + + void QueueServerReload(ServerReload::Type &type); + + static ZSList* Instance() + { + static ZSList instance; + return &instance; + } private: void OnTick(EQ::Timer *t); - void OnKeepAlive(EQ::Timer *t); uint32 NextID; uint16 pLockedZones[MaxLockedZones]; uint32 CurGroupID; diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index b00afb4b7..30e90cb09 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -39,7 +39,6 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "../common/zone_store.h" #include "dynamic_zone.h" #include "dynamic_zone_manager.h" -#include "expedition_message.h" #include "shared_task_world_messaging.h" #include "../common/shared_tasks.h" #include "shared_task_manager.h" @@ -47,20 +46,15 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "../common/repositories/player_event_logs_repository.h" #include "../common/events/player_event_logs.h" #include "../common/patches/patches.h" -#include "../zone/data_bucket.h" #include "../common/repositories/guild_tributes_repository.h" #include "../common/skill_caps.h" +#include "../common/server_reload_types.h" +#include "../common/repositories/trader_repository.h" +#include "../common/repositories/buyer_repository.h" -extern ClientList client_list; extern GroupLFPList LFPGroupList; -extern ZSList zoneserver_list; -extern LoginServerList loginserverlist; extern volatile bool RunLoops; extern volatile bool UCSServerAvailable_; -extern AdventureManager adventure_manager; -extern UCSConnection UCSLink; -extern QueryServConnection QSLink; -extern SharedTaskManager shared_task_manager; void CatchSignal(int sig_num); @@ -73,7 +67,7 @@ ZoneServer::ZoneServer(std::shared_ptr in_c memset(client_address, 0, sizeof(client_address)); memset(client_local_address, 0, sizeof(client_local_address)); - zone_server_id = zoneserver_list.GetNextID(); + zone_server_id = ZSList::Instance()->GetNextID(); zone_server_zone_id = 0; instance_id = 0; zone_os_process_id = 0; @@ -96,7 +90,7 @@ ZoneServer::ZoneServer(std::shared_ptr in_c ZoneServer::~ZoneServer() { if (RunLoops) { - client_list.CLERemoveZSRef(this); + ClientList::Instance()->CLERemoveZSRef(this); } } @@ -131,7 +125,7 @@ bool ZoneServer::SetZone(uint32 in_zone_id, uint32 in_instance_id, bool in_is_st } if (!zone_server_zone_id) { - client_list.CLERemoveZSRef(this); + ClientList::Instance()->CLERemoveZSRef(this); zone_player_count = 0; LSSleepUpdate(GetPrevZoneID()); } @@ -141,7 +135,7 @@ bool ZoneServer::SetZone(uint32 in_zone_id, uint32 in_instance_id, bool in_is_st strn0cpy(zone_name, zone_short_name.c_str(), sizeof(zone_name)); strn0cpy(long_name, zone_long_name.c_str(), sizeof(long_name)); - client_list.ZoneBootup(this); + ClientList::Instance()->ZoneBootup(this); zone_boot_timer.Start(); return true; @@ -163,7 +157,7 @@ void ZoneServer::LSBootUpdate(uint32 zone_id, uint32 instanceid, bool startup) { bootup->zone = zone_id; bootup->zone_wid = GetID(); bootup->instance = instanceid; - loginserverlist.SendPacket(pack); + LoginServerList::Instance()->SendPacket(pack); safe_delete(pack); } } @@ -178,7 +172,7 @@ void ZoneServer::LSSleepUpdate(uint32 zone_id) { auto sleep = (ServerLSZoneSleep_Struct*) pack->pBuffer; sleep->zone = zone_id; sleep->zone_wid = GetID(); - loginserverlist.SendPacket(pack); + LoginServerList::Instance()->SendPacket(pack); safe_delete(pack); } } @@ -207,7 +201,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { } auto gis = (GroupInvite_Struct*) pack->pBuffer; - client_list.SendPacket(gis->invitee_name, pack); + ClientList::Instance()->SendPacket(gis->invitee_name, pack); break; } case ServerOP_GroupFollow: { @@ -216,7 +210,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { } auto sgfs = (ServerGroupFollow_Struct*) pack->pBuffer; - client_list.SendPacket(sgfs->gf.name1, pack); + ClientList::Instance()->SendPacket(sgfs->gf.name1, pack); break; } case ServerOP_GroupFollowAck: { @@ -225,7 +219,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { } auto sgfas = (ServerGroupFollowAck_Struct*) pack->pBuffer; - client_list.SendPacket(sgfas->Name, pack); + ClientList::Instance()->SendPacket(sgfas->Name, pack); break; } case ServerOP_GroupCancelInvite: { @@ -234,7 +228,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { } auto gcs = (GroupCancel_Struct*) pack->pBuffer; - client_list.SendPacket(gcs->name1, pack); + ClientList::Instance()->SendPacket(gcs->name1, pack); break; } case ServerOP_GroupIDReq: { @@ -246,7 +240,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { break; } - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); break; } case ServerOP_GroupJoin: { @@ -254,7 +248,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { break; } - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); break; } case ServerOP_ForceGroupUpdate: { @@ -262,7 +256,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { break; } - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); break; } case ServerOP_DisbandGroup: { @@ -270,7 +264,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { break; } - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); break; } case ServerOP_RaidAdd: { @@ -278,7 +272,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { break; } - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); break; } case ServerOP_RaidRemove: { @@ -286,7 +280,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { break; } - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); break; } case ServerOP_RaidDisband: { @@ -294,7 +288,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { break; } - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); break; } case ServerOP_RaidLockFlag: { @@ -302,7 +296,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { break; } - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); break; } case ServerOP_RaidChangeGroup: { @@ -310,7 +304,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { break; } - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); break; } case ServerOP_UpdateGroup: { @@ -318,7 +312,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { break; } - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); break; } case ServerOP_RaidGroupDisband: { @@ -326,7 +320,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { break; } - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); break; } case ServerOP_RaidGroupAdd: { @@ -334,7 +328,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { break; } - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); break; } case ServerOP_RaidGroupRemove: { @@ -342,7 +336,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { break; } - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); break; } case ServerOP_RaidGroupLeader: { @@ -350,7 +344,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { break; } - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); break; } case ServerOP_RaidLeader: { @@ -358,7 +352,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { break; } - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); break; } case ServerOP_PlayerEvent: { @@ -372,15 +366,15 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { // if set, process events in queryserver // if you want to offload event recording to a dedicated QS instance if (!RuleB(Logging, PlayerEventsQSProcess)) { - player_event_logs.AddToQueue(n.player_event_log); + PlayerEventLogs::Instance()->AddToQueue(n.player_event_log); } else { - QSLink.SendPacket(pack); + QueryServConnection::Instance()->SendPacket(pack); } // if discord enabled for event, ship to UCS to process - if (player_event_logs.IsEventDiscordEnabled(n.player_event_log.event_type_id)) { - UCSLink.SendPacket(pack); + if (PlayerEventLogs::Instance()->IsEventDiscordEnabled(n.player_event_log.event_type_id)) { + UCSConnection::Instance()->SendPacket(pack); } break; @@ -390,7 +384,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { break; } - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); break; } case ServerOP_RaidMOTD: { @@ -398,7 +392,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { break; } - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); break; } case ServerOP_RaidNote: { @@ -406,7 +400,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { break; } - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); break; } case ServerOP_SpawnCondition: { @@ -415,7 +409,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { } auto ssc = (ServerSpawnCondition_Struct*) pack->pBuffer; - zoneserver_list.SendPacket(ssc->zoneID, ssc->instanceID, pack); + ZSList::Instance()->SendPacket(ssc->zoneID, ssc->instanceID, pack); break; } case ServerOP_SpawnEvent: { @@ -424,7 +418,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { } auto sse = (ServerSpawnEvent_Struct*) pack->pBuffer; - zoneserver_list.SendPacket(sse->zoneID, 0, pack); + ZSList::Instance()->SendPacket(sse->zoneID, 0, pack); break; } case ServerOP_ChannelMessage: { @@ -434,7 +428,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { auto scm = (ServerChannelMessage_Struct*) pack->pBuffer; if (scm->chan_num == ChatChannel_UCSRelay) { - UCSLink.SendMessage(scm->from, scm->message); + UCSConnection::Instance()->SendMessage(scm->from, scm->message); break; } @@ -453,14 +447,14 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { auto scm2 = (ServerChannelMessage_Struct*) pack->pBuffer; strcpy(scm2->deliverto, scm2->from); scm2->noreply = true; - client_list.SendPacket(scm->from, pack); + ClientList::Instance()->SendPacket(scm->from, pack); safe_delete(pack); } ) ) && !scm->noreply ) { - zoneserver_list.SendEmoteMessage( + ZSList::Instance()->SendEmoteMessage( scm->from, 0, AccountStatus::Player, @@ -475,7 +469,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { break; } - auto cle = client_list.FindCharacter(scm->deliverto); + auto cle = ClientList::Instance()->FindCharacter(scm->deliverto); if ( !cle || cle->Online() < CLE_Status::Zoning || @@ -488,7 +482,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { ) ) { if (!scm->noreply) { - auto sender = client_list.FindCharacter(scm->from); + auto sender = ClientList::Instance()->FindCharacter(scm->from); if (!sender || !sender->Server()) { break; } @@ -502,7 +496,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { } else if (cle->Online() == CLE_Status::Zoning) { if (!scm->noreply) { - auto sender = client_list.FindCharacter(scm->from); + auto sender = ClientList::Instance()->FindCharacter(scm->from); if (cle->TellQueueFull()) { if (!sender || !sender->Server()) { break; @@ -534,7 +528,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { } } else if (!cle->Server()) { if (!scm->noreply) - zoneserver_list.SendEmoteMessage( + ZSList::Instance()->SendEmoteMessage( scm->from, 0, AccountStatus::Player, @@ -565,20 +559,26 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { auto scm2 = (ServerChannelMessage_Struct*) pack->pBuffer; strcpy(scm2->deliverto, scm2->from); scm2->noreply = true; - client_list.SendPacket(scm->from, pack); + ClientList::Instance()->SendPacket(scm->from, pack); safe_delete(pack); } ); } } - zoneserver_list.SendPacket(pack); + if (scm->guilddbid > 0) { + ZSList::Instance()->SendPacketToZonesWithGuild(scm->guilddbid, pack); + } else if (scm->chan_num == ChatChannel_GMSAY) { + ZSList::Instance()->SendPacketToZonesWithGMs(pack); + } else { + ZSList::Instance()->SendPacket(pack); + } } break; } case ServerOP_EmoteMessage: { auto sem = (ServerEmoteMessage_Struct*) pack->pBuffer; - zoneserver_list.SendEmoteMessageRaw( + ZSList::Instance()->SendEmoteMessageRaw( sem->to, sem->guilddbid, sem->minstatus, @@ -590,13 +590,13 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { case ServerOP_VoiceMacro: { auto svm = (ServerVoiceMacro_Struct*) pack->pBuffer; if (svm->Type == VoiceMacroTell) { - auto cle = client_list.FindCharacter(svm->To); + auto cle = ClientList::Instance()->FindCharacter(svm->To); if ( !cle || cle->Online() < CLE_Status::Zoning || !cle->Server() ) { - zoneserver_list.SendEmoteMessage( + ZSList::Instance()->SendEmoteMessage( svm->From, 0, AccountStatus::Player, @@ -610,14 +610,14 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { } cle->Server()->SendPacket(pack); } else { - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); } break; } case ServerOP_RezzPlayer: { auto rps = (RezzPlayer_Struct*) pack->pBuffer; - if (zoneserver_list.SendPacket(pack)) { + if (ZSList::Instance()->SendPacket(pack)) { LogInfo("Sent Rez packet for [{}]", rps->rez.your_name); } else { LogInfo("Could not send Rez packet for [{}]", rps->rez.your_name); @@ -627,12 +627,12 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { } case ServerOP_RezzPlayerReject: { auto recipient = (char*) pack->pBuffer; - client_list.SendPacket(recipient, pack); + ClientList::Instance()->SendPacket(recipient, pack); break; } case ServerOP_MultiLineMsg: { auto mlm = (ServerMultiLineMsg_Struct*) pack->pBuffer; - client_list.SendPacket(mlm->to, pack); + ClientList::Instance()->SendPacket(mlm->to, pack); break; } case ServerOP_SetZone: { @@ -661,7 +661,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { auto sci = (ServerConnectInfo*) pack->pBuffer; if (!sci->port) { - client_port = zoneserver_list.GetAvailableZonePort(); + client_port = ZSList::Instance()->GetAvailableZonePort(); ServerPacket p(ServerOP_SetConnectInfo, sizeof(ServerConnectInfo)); memset(p.pBuffer, 0, sizeof(ServerConnectInfo)); @@ -703,20 +703,20 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { } case ServerOP_ShutdownAll: { if (!pack->size) { - zoneserver_list.SendPacket(pack); - zoneserver_list.Process(); + ZSList::Instance()->SendPacket(pack); + ZSList::Instance()->Process(); CatchSignal(2); } else { auto wsd = (WorldShutDown_Struct*) pack->pBuffer; - if (!wsd->time && !wsd->interval && zoneserver_list.shutdowntimer->Enabled()) { - zoneserver_list.shutdowntimer->Disable(); - zoneserver_list.reminder->Disable(); + if (!wsd->time && !wsd->interval && ZSList::Instance()->shutdowntimer->Enabled()) { + ZSList::Instance()->shutdowntimer->Disable(); + ZSList::Instance()->reminder->Disable(); } else { - zoneserver_list.shutdowntimer->SetTimer(wsd->time); - zoneserver_list.reminder->SetTimer(wsd->interval - 1000); - zoneserver_list.reminder->SetAtTrigger(wsd->interval); - zoneserver_list.shutdowntimer->Start(); - zoneserver_list.reminder->Start(); + ZSList::Instance()->shutdowntimer->SetTimer(wsd->time); + ZSList::Instance()->reminder->SetTimer(wsd->interval - 1000); + ZSList::Instance()->reminder->SetAtTrigger(wsd->interval); + ZSList::Instance()->shutdowntimer->Start(); + ZSList::Instance()->reminder->Start(); } } @@ -726,21 +726,23 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { auto *s = (ServerZoneStateChange_Struct*) pack->pBuffer; ZoneServer* zs = 0; if (s->zone_server_id) { - zs = zoneserver_list.FindByID(s->zone_server_id); + zs = ZSList::Instance()->FindByID(s->zone_server_id); } else if (s->zone_id) { - zs = zoneserver_list.FindByName(ZoneName(s->zone_id)); + zs = ZSList::Instance()->FindByName(ZoneName(s->zone_id)); + } else if (s->instance_id) { + zs = ZSList::Instance()->FindByInstanceID(s->instance_id); } else { - zoneserver_list.SendEmoteMessage( + ZSList::Instance()->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" ); } if (!zs) { - zoneserver_list.SendEmoteMessage( + ZSList::Instance()->SendEmoteMessage( s->admin_name, 0, AccountStatus::Player, @@ -755,13 +757,13 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { } case ServerOP_ZoneBootup: { auto *s = (ServerZoneStateChange_Struct*) pack->pBuffer; - zoneserver_list.SOPZoneBootup(s->admin_name, s->zone_server_id, ZoneName(s->zone_id), s->is_static); + ZSList::Instance()->SOPZoneBootup(s->admin_name, s->zone_server_id, ZoneName(s->zone_id), s->is_static); break; } case ServerOP_ZoneStatus: { if (pack->size >= 1) { auto z = (ServerZoneStatus_Struct*) pack->pBuffer; - zoneserver_list.SendZoneStatus(z->name, z->admin, this); + ZSList::Instance()->SendZoneStatus(z->name, z->admin, this); } break; @@ -772,7 +774,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { } auto wtz = (WorldToZone_Struct*) pack->pBuffer; - auto client = client_list.FindByAccountID(wtz->account_id); + auto client = ClientList::Instance()->FindByAccountID(wtz->account_id); if (client) { client->Clearance(wtz->response); } @@ -795,7 +797,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { auto ztz = (ZoneToZone_Struct*) pack->pBuffer; ClientListEntry* client = nullptr; if (WorldConfig::get()->UpdateStats) { - client = client_list.FindCharacter(ztz->name); + client = ClientList::Instance()->FindCharacter(ztz->name); } LogZoning( @@ -825,7 +827,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { if ( ztz->admin < AccountStatus::QuestTroupe && ztz->ignorerestrictions < 2 && - zoneserver_list.IsZoneLocked(ztz->requested_zone_id) + ZSList::Instance()->IsZoneLocked(ztz->requested_zone_id) ) { ztz->response = 0; @@ -842,8 +844,8 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { auto ingress_server = ( ztz->requested_instance_id ? - zoneserver_list.FindByInstanceID(ztz->requested_instance_id) : - zoneserver_list.FindByZoneID(ztz->requested_zone_id) + ZSList::Instance()->FindByInstanceID(ztz->requested_instance_id) : + ZSList::Instance()->FindByZoneID(ztz->requested_zone_id) ); if (ingress_server) { @@ -857,7 +859,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { ztz->response = 1; } else { int server_id; - if ((server_id = zoneserver_list.TriggerBootup(ztz->requested_zone_id, ztz->requested_instance_id))) { + if ((server_id = ZSList::Instance()->TriggerBootup(ztz->requested_zone_id, ztz->requested_instance_id))) { LogZoning( "ZoneToZone successfully booted a zone for character [{}] zone [{}] ({}) instance [{}] ({})", ztz->name, @@ -867,7 +869,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { server_id ); ztz->response = 1; - ingress_server = zoneserver_list.FindByID(server_id); + ingress_server = ZSList::Instance()->FindByID(server_id); } else { LogError("failed to boot a zone for [{}]", ztz->name); ztz->response = 0; @@ -891,8 +893,8 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { ); auto egress_server = ( ztz->current_instance_id ? - zoneserver_list.FindByInstanceID(ztz->current_instance_id) : - zoneserver_list.FindByZoneID(ztz->current_zone_id) + ZSList::Instance()->FindByInstanceID(ztz->current_instance_id) : + ZSList::Instance()->FindByZoneID(ztz->current_zone_id) ); if (egress_server) { @@ -916,7 +918,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { } auto scl = (ServerClientList_Struct*) pack->pBuffer; - client_list.ClientUpdate(this, scl); + ClientList::Instance()->ClientUpdate(this, scl); break; } case ServerOP_ClientListKA: { @@ -926,7 +928,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { break; } - client_list.CLEKeepAlive(sclka->numupdates, sclka->wid); + ClientList::Instance()->CLEKeepAlive(sclka->numupdates, sclka->wid); break; } case ServerOP_Who: { @@ -939,28 +941,28 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { whom->wclass = whoall->wclass; whom->wrace = whoall->wrace; strn0cpy(whom->whom, whoall->whom, sizeof(whom->whom)); - client_list.SendWhoAll(whoall->fromid, whoall->from, whoall->admin, whom, this); + ClientList::Instance()->SendWhoAll(whoall->fromid, whoall->from, whoall->admin, whom, this); safe_delete(whom); break; } case ServerOP_RequestOnlineGuildMembers: { auto srogms = (ServerRequestOnlineGuildMembers_Struct*) pack->pBuffer; - client_list.SendOnlineGuildMembers(srogms->FromID, srogms->GuildID); + ClientList::Instance()->SendOnlineGuildMembers(srogms->FromID, srogms->GuildID); break; } case ServerOP_ClientVersionSummary: { auto srcvss = (ServerRequestClientVersionSummary_Struct*) pack->pBuffer; - client_list.SendClientVersionSummary(srcvss->Name); + ClientList::Instance()->SendClientVersionSummary(srcvss->Name); break; } case ServerOP_FriendsWho: { auto sfw = (ServerFriendsWho_Struct*) pack->pBuffer; - client_list.SendFriendsWho(sfw, this); + ClientList::Instance()->SendFriendsWho(sfw, this); break; } case ServerOP_LFGMatches: { auto smrs = (ServerLFGMatchesRequest_Struct*) pack->pBuffer; - client_list.SendLFGMatches(smrs); + ClientList::Instance()->SendLFGMatches(smrs); break; } case ServerOP_LFPMatches: { @@ -997,12 +999,12 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { break; } case ServerOP_FlagUpdate: { - auto cle = client_list.FindCLEByAccountID(*((uint32*) pack->pBuffer)); + auto cle = ClientList::Instance()->FindCLEByAccountID(*((uint32*) pack->pBuffer)); if (cle) { cle->SetAdmin(*((int16*)&pack->pBuffer[4])); } - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); break; } case ServerOP_GMGoto: { @@ -1012,7 +1014,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { } auto gmg = (ServerGMGoto_Struct*) pack->pBuffer; - auto cle = client_list.FindCharacter(gmg->gotoname); + auto cle = ClientList::Instance()->FindCharacter(gmg->gotoname); if (cle) { if (!cle->Server()) { SendEmoteMessage( @@ -1067,8 +1069,8 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { WorldConfig::UnlockWorld(); } - if (loginserverlist.Connected()) { - loginserverlist.SendStatus(); + if (LoginServerList::Instance()->Connected()) { + LoginServerList::Instance()->SendStatus(); SendEmoteMessage( l->character_name, 0, @@ -1102,7 +1104,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { auto smotd = (ServerMotd_Struct*) pack->pBuffer; RuleManager::Instance()->SetRule("MOTD", smotd->motd, &database, true, true); - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); break; } case ServerOP_Uptime: { @@ -1115,7 +1117,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { if (!sus->zoneserverid) { ZSList::ShowUpTime(this, sus->adminname); } else { - auto zs = zoneserver_list.FindByID(sus->zoneserverid); + auto zs = ZSList::Instance()->FindByID(sus->zoneserverid); if (zs) { zs->SendPacket(pack); } @@ -1132,8 +1134,8 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { pack->pBuffer = new uchar[pack->size]; memset(pack->pBuffer, 0, pack->size); auto tod = (eqTimeOfDay*) pack->pBuffer; - tod->start_eqtime = zoneserver_list.worldclock.getStartEQTime(); - tod->start_realtime = zoneserver_list.worldclock.getStartRealTime(); + tod->start_eqtime = ZSList::Instance()->worldclock.getStartEQTime(); + tod->start_realtime = ZSList::Instance()->worldclock.getStartRealTime(); SendPacket(pack); safe_delete(pack); break; @@ -1141,10 +1143,10 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { case ServerOP_SetWorldTime: { LogNetcode("Received SetWorldTime"); auto newtime = (eqTimeOfDay*) pack->pBuffer; - zoneserver_list.worldclock.SetCurrentEQTimeOfDay(newtime->start_eqtime, newtime->start_realtime); + ZSList::Instance()->worldclock.SetCurrentEQTimeOfDay(newtime->start_eqtime, newtime->start_realtime); LogInfo("New time = [{}]-[{}]-[{}] [{}]:[{}] ([{}])\n", newtime->start_eqtime.year, newtime->start_eqtime.month, (int)newtime->start_eqtime.day, (int)newtime->start_eqtime.hour, (int)newtime->start_eqtime.minute, (int)newtime->start_realtime); database.SaveTime((int)newtime->start_eqtime.minute, (int)newtime->start_eqtime.hour, (int)newtime->start_eqtime.day, newtime->start_eqtime.month, newtime->start_eqtime.year); - zoneserver_list.SendTimeSync(); + ZSList::Instance()->SendTimeSync(); break; } case ServerOP_IPLookup: { @@ -1155,9 +1157,9 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { auto sgwq = (ServerGenericWorldQuery_Struct*) pack->pBuffer; if (pack->size == sizeof(ServerGenericWorldQuery_Struct)) { - client_list.SendCLEList(sgwq->admin, sgwq->from, this); + ClientList::Instance()->SendCLEList(sgwq->admin, sgwq->from, this); } else { - client_list.SendCLEList(sgwq->admin, sgwq->from, this, sgwq->query); + ClientList::Instance()->SendCLEList(sgwq->admin, sgwq->from, this, sgwq->query); } break; @@ -1170,14 +1172,14 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { auto lock_zone = (ServerLockZone_Struct*) pack->pBuffer; if (lock_zone->op == ServerLockType::List) { - zoneserver_list.ListLockedZones(lock_zone->adminname, this); + ZSList::Instance()->ListLockedZones(lock_zone->adminname, this); break; } else if ( lock_zone->op == ServerLockType::Lock || lock_zone->op == ServerLockType::Unlock ) { - if (zoneserver_list.SetLockedZone(lock_zone->zoneID, lock_zone->op == ServerLockType::Lock)) { - zoneserver_list.SendEmoteMessage( + if (ZSList::Instance()->SetLockedZone(lock_zone->zoneID, lock_zone->op == ServerLockType::Lock)) { + ZSList::Instance()->SendEmoteMessage( 0, 0, AccountStatus::QuestTroupe, @@ -1212,7 +1214,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { } case ServerOP_Revoke: { auto rev = (RevokeStruct*) pack->pBuffer; - auto cle = client_list.FindCharacter(rev->name); + auto cle = ClientList::Instance()->FindCharacter(rev->name); if (cle && cle->Server()) { cle->Server()->SendPacket(pack); } @@ -1221,7 +1223,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { } case ServerOP_SpawnPlayerCorpse: { auto s = (SpawnPlayerCorpse_Struct*) pack->pBuffer; - auto zs = zoneserver_list.FindByZoneID(s->zone_id); + auto zs = ZSList::Instance()->FindByZoneID(s->zone_id); if (zs) { zs->SendPacket(pack); } @@ -1233,8 +1235,8 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { auto owner_zs = ( s->instance_id ? - zoneserver_list.FindByInstanceID(s->instance_id) : - zoneserver_list.FindByZoneID(s->zone_id) + ZSList::Instance()->FindByInstanceID(s->instance_id) : + ZSList::Instance()->FindByZoneID(s->zone_id) ); if (owner_zs) { @@ -1244,12 +1246,12 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { } if (s->consent_type == EQ::consent::Normal) { - auto cle = client_list.FindCharacter(s->grantname); + auto cle = ClientList::Instance()->FindCharacter(s->grantname); if (cle) { auto granted_zs = ( cle->instance() ? - zoneserver_list.FindByInstanceID(cle->instance()) : - zoneserver_list.FindByZoneID(cle->zone()) + ZSList::Instance()->FindByInstanceID(cle->instance()) : + ZSList::Instance()->FindByZoneID(cle->zone()) ); if (granted_zs && granted_zs != owner_zs) { @@ -1262,7 +1264,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { } case ServerOP_InstanceUpdateTime: { auto iut = (ServerInstanceUpdateTime_Struct*) pack->pBuffer; - auto zm = zoneserver_list.FindByInstanceID(iut->instance_id); + auto zm = ZSList::Instance()->FindByInstanceID(iut->instance_id); if (zm) { zm->SendPacket(pack); } @@ -1274,7 +1276,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { break; } - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); break; } case ServerOP_QGlobalDelete: { @@ -1282,65 +1284,65 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { break; } - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); break; } case ServerOP_AdventureRequest: { - adventure_manager.CalculateAdventureRequestReply((const char*) pack->pBuffer); + AdventureManager::Instance()->CalculateAdventureRequestReply((const char*) pack->pBuffer); break; } case ServerOP_AdventureRequestCreate: { - adventure_manager.TryAdventureCreate((const char*) pack->pBuffer); + AdventureManager::Instance()->TryAdventureCreate((const char*) pack->pBuffer); break; } case ServerOP_AdventureDataRequest: { AdventureFinishEvent fe; - while (adventure_manager.PopFinishedEvent((const char*) pack->pBuffer, fe)) { - adventure_manager.SendAdventureFinish(fe); + while (AdventureManager::Instance()->PopFinishedEvent((const char*) pack->pBuffer, fe)) { + AdventureManager::Instance()->SendAdventureFinish(fe); } - adventure_manager.GetAdventureData((const char*) pack->pBuffer); + AdventureManager::Instance()->GetAdventureData((const char*) pack->pBuffer); break; } case ServerOP_AdventureClickDoor: { auto pcad = (ServerPlayerClickedAdventureDoor_Struct*) pack->pBuffer; - adventure_manager.PlayerClickedDoor(pcad->player, pcad->zone_id, pcad->id); + AdventureManager::Instance()->PlayerClickedDoor(pcad->player, pcad->zone_id, pcad->id); break; } case ServerOP_AdventureLeave: { - adventure_manager.LeaveAdventure((const char*) pack->pBuffer); + AdventureManager::Instance()->LeaveAdventure((const char*) pack->pBuffer); break; } case ServerOP_AdventureCountUpdate: { auto sc = (ServerAdventureCount_Struct*) pack->pBuffer; - adventure_manager.IncrementCount(sc->instance_id); + AdventureManager::Instance()->IncrementCount(sc->instance_id); break; } case ServerOP_AdventureAssaCountUpdate: { - adventure_manager.IncrementAssassinationCount(*((uint16*) pack->pBuffer)); + AdventureManager::Instance()->IncrementAssassinationCount(*((uint16*) pack->pBuffer)); break; } case ServerOP_AdventureZoneData: { - adventure_manager.GetZoneData(*((uint16*) pack->pBuffer)); + AdventureManager::Instance()->GetZoneData(*((uint16*) pack->pBuffer)); break; } case ServerOP_AdventureLeaderboard: { auto lr = (ServerLeaderboardRequest_Struct*) pack->pBuffer; - adventure_manager.DoLeaderboardRequest(lr->player, lr->type); + AdventureManager::Instance()->DoLeaderboardRequest(lr->player, lr->type); break; } case ServerOP_LSAccountUpdate: { LogNetcode("Received ServerOP_LSAccountUpdate packet from zone"); - loginserverlist.SendAccountUpdate(pack); + LoginServerList::Instance()->SendAccountUpdate(pack); break; } case ServerOP_DiscordWebhookMessage: case ServerOP_UCSMailMessage: { - UCSLink.SendPacket(pack); + UCSConnection::Instance()->SendPacket(pack); break; } case ServerOP_UCSServerStatusRequest: { auto ucsss = (UCSServerStatus_Struct*) pack->pBuffer; - auto zs = zoneserver_list.FindByPort(ucsss->port); + auto zs = ZSList::Instance()->FindByPort(ucsss->port); if (!zs) { break; } @@ -1353,22 +1355,8 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { safe_delete(outapp); break; } - case ServerOP_Speech: - case ServerOP_QSSendQuery: - case ServerOP_QSPlayerLogDeletes: - case ServerOP_QSPlayerDropItem: - case ServerOP_QSPlayerLogHandins: - case ServerOP_QSPlayerLogMerchantTransactions: - case ServerOP_QSPlayerLogMoves: - case ServerOP_QSPlayerLogNPCKills: - case ServerOP_QSPlayerLogTrades: case ServerOP_QueryServGeneric: { - QSLink.SendPacket(pack); - break; - } - case ServerOP_ReloadOpcodes: { - ReloadAllPatches(); - zoneserver_list.SendPacket(pack); + QueryServConnection::Instance()->SendPacket(pack); break; } case ServerOP_CZDialogueWindow: @@ -1384,10 +1372,6 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { case ServerOP_Consent: case ServerOP_DepopAllPlayersCorpses: case ServerOP_DepopPlayerCorpse: - case ServerOP_ExpeditionLockState: - case ServerOP_ExpeditionLockout: - case ServerOP_ExpeditionLockoutDuration: - case ServerOP_ExpeditionReplayOnJoin: case ServerOP_GuildRankUpdate: case ServerOP_ItemStatus: case ServerOP_KickPlayer: @@ -1398,30 +1382,6 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { case ServerOP_RaidGroupSay: case ServerOP_RaidSay: case ServerOP_RefreshCensorship: - case ServerOP_ReloadAAData: - case ServerOP_ReloadAlternateCurrencies: - case ServerOP_ReloadBaseData: - case ServerOP_ReloadBlockedSpells: - case ServerOP_ReloadCommands: - case ServerOP_ReloadDoors: - case ServerOP_ReloadDataBucketsCache: - case ServerOP_ReloadFactions: - case ServerOP_ReloadGroundSpawns: - case ServerOP_ReloadLevelEXPMods: - case ServerOP_ReloadMerchants: - case ServerOP_ReloadNPCEmotes: - case ServerOP_ReloadNPCSpells: - case ServerOP_ReloadObjects: - case ServerOP_ReloadPerlExportSettings: - case ServerOP_ReloadStaticZoneData: - case ServerOP_ReloadTitles: - case ServerOP_ReloadTraps: - case ServerOP_ReloadVariables: - case ServerOP_ReloadVeteranRewards: - case ServerOP_ReloadWorld: - case ServerOP_ReloadZonePoints: - case ServerOP_ReloadZoneData: - case ServerOP_ReloadLoot: case ServerOP_RezzPlayerAccept: case ServerOP_SpawnStatusChange: case ServerOP_TraderMessaging: @@ -1436,17 +1396,12 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { case ServerOP_WWSpell: case ServerOP_WWTaskUpdate: case ServerOP_ZonePlayer: { - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); break; } - case ServerOP_ReloadSkillCaps: { - zoneserver_list.SendPacket(pack); - skill_caps.ReloadSkillCaps(); - break; - } - case ServerOP_ReloadRules: { - zoneserver_list.SendPacket(pack); - RuleManager::Instance()->LoadRules(&database, "default", true); + case ServerOP_ServerReloadRequest: { + auto o = (ServerReload::Request*) pack->pBuffer; + ZSList::Instance()->SendServerReload((ServerReload::Type) o->type, pack->pBuffer); break; } case ServerOP_IsOwnerOnline: { @@ -1455,7 +1410,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { } auto o = (ServerIsOwnerOnline_Struct*) pack->pBuffer; - auto cle = client_list.FindCLEByAccountID(o->account_id); + auto cle = ClientList::Instance()->FindCLEByAccountID(o->account_id); o->online = cle ? 1 : 0; @@ -1468,34 +1423,12 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { ); } - auto zs = zoneserver_list.FindByZoneID(o->zone_id); + auto zs = ZSList::Instance()->FindByZoneID(o->zone_id); if (zs) { zs->SendPacket(pack); } break; } - case ServerOP_ReloadContentFlags: { - zoneserver_list.SendPacket(pack); - content_service.SetExpansionContext()->ReloadContentFlags(); - break; - } - case ServerOP_ReloadLogs: { - zoneserver_list.SendPacket(pack); - UCSLink.SendPacket(pack); - LogSys.LoadLogDatabaseSettings(); - player_event_logs.ReloadSettings(); - break; - } - case ServerOP_ReloadTasks: { - shared_task_manager.LoadTaskData(); - zoneserver_list.SendPacket(pack); - break; - } - case ServerOP_ReloadDzTemplates: { - dynamic_zone_manager.LoadTemplates(); - zoneserver_list.SendPacket(pack); - break; - } case ServerOP_ChangeSharedMem: { auto hotfix_name = std::string((char*) pack->pBuffer); @@ -1504,12 +1437,12 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { LogInfo("Error: Could not load item data. But ignoring"); } - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); break; } case ServerOP_RequestTellQueue: { auto rtq = (ServerRequestTellQueue_Struct*) pack->pBuffer; - auto cle = client_list.FindCharacter(rtq->name); + auto cle = ClientList::Instance()->FindCharacter(rtq->name); if (!cle || cle->TellQueueEmpty()) { break; } @@ -1519,7 +1452,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { } case ServerOP_CZClientMessageString: { auto buf = reinterpret_cast(pack->pBuffer); - client_list.SendPacket(buf->client_name, pack); + ClientList::Instance()->SendPacket(buf->client_name, pack); break; } case ServerOP_SharedTaskRequest: @@ -1540,33 +1473,29 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { SharedTaskWorldMessaging::HandleZoneMessage(pack); break; } - case ServerOP_ExpeditionCreate: - case ServerOP_ExpeditionDzAddPlayer: - case ServerOP_ExpeditionDzMakeLeader: - case ServerOP_ExpeditionCharacterLockout: - case ServerOP_ExpeditionSaveInvite: - case ServerOP_ExpeditionRequestInvite: { - ExpeditionMessage::HandleZoneMessage(pack); - break; - } case ServerOP_DzCreated: + case ServerOP_DzAddPlayer: + case ServerOP_DzSaveInvite: + case ServerOP_DzRequestInvite: + case ServerOP_DzMakeLeader: case ServerOP_DzAddRemoveMember: case ServerOP_DzSwapMembers: case ServerOP_DzRemoveAllMembers: case ServerOP_DzGetMemberStatuses: + case ServerOP_DzGetBulkMemberStatuses: case ServerOP_DzSetSecondsRemaining: case ServerOP_DzSetCompass: case ServerOP_DzSetSafeReturn: case ServerOP_DzSetZoneIn: case ServerOP_DzSetSwitchID: case ServerOP_DzMovePC: + case ServerOP_DzLock: + case ServerOP_DzReplayOnJoin: + case ServerOP_DzLockout: + case ServerOP_DzLockoutDuration: + case ServerOP_DzCharacterLockout: case ServerOP_DzUpdateMemberStatus: { - DynamicZone::HandleZoneMessage(pack); - break; - } - case ServerOP_DataBucketCacheUpdate: { - zoneserver_list.SendPacket(pack); - + dynamic_zone_manager.HandleZoneMessage(pack); break; } case ServerOP_GuildTributeUpdate: { @@ -1583,7 +1512,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { guild->tribute.timer.Disable(); - zoneserver_list.SendPacketToBootedZones(pack); + ZSList::Instance()->SendPacketToZonesWithGuild(data->guild_id, pack); } break; } @@ -1626,7 +1555,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); + ZSList::Instance()->SendPacketToZonesWithGuild(data->guild_id, pack); } break; } @@ -1634,12 +1563,12 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { { auto in = (GuildTributeMemberToggle *)pack->pBuffer; auto guild = guild_mgr.GetGuildByGuildID(in->guild_id); - auto c = client_list.FindCharacter(in->player_name); + auto c = ClientList::Instance()->FindCharacter(in->player_name); if (c) { c->SetGuildTributeOptIn(in->tribute_toggle ? true : false); } - auto cle = client_list.FindCLEByCharacterID(in->char_id); + auto cle = ClientList::Instance()->FindCLEByCharacterID(in->char_id); if (cle) { cle->SetGuildTributeOptIn(in->tribute_toggle ? true : false); } @@ -1660,7 +1589,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); + ZSList::Instance()->SendPacketToZonesWithGuild(in->guild_id, out); safe_delete(out); } break; @@ -1683,7 +1612,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); + ZSList::Instance()->SendPacketToZonesWithGuild(in->guild_id, sp); safe_delete(sp); } @@ -1703,7 +1632,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); + ZSList::Instance()->SendPacketToZonesWithGuild(in->guild_id, sp); safe_delete(sp); } @@ -1727,7 +1656,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); + ZSList::Instance()->SendPacketToZonesWithGuild(out->guild_id, sp); safe_delete(sp) } break; @@ -1741,7 +1670,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { return; } - zoneserver_list.SendPacketToBootedZones(pack); + ZSList::Instance()->SendPacketToBootedZones(pack); break; } case ServerOP_BazaarPurchase: { @@ -1755,9 +1684,9 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { return; } - auto trader = client_list.FindCLEByCharacterID(in->trader_buy_struct.trader_id); + auto trader = ClientList::Instance()->FindCLEByCharacterID(in->trader_buy_struct.trader_id); if (trader) { - zoneserver_list.SendPacket(trader->zone(), trader->instance(), pack); + ZSList::Instance()->SendPacket(trader->zone(), trader->instance(), pack); } break; @@ -1775,22 +1704,22 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { return; } - zoneserver_list.SendPacketToBootedZones(pack); + ZSList::Instance()->SendPacketToBootedZones(pack); break; } case Barter_SellItem: { - auto buyer = client_list.FindCharacter(in->buyer_name); + auto buyer = ClientList::Instance()->FindCharacter(in->buyer_name); if (buyer) { - zoneserver_list.SendPacket(buyer->zone(), buyer->instance(), pack); + ZSList::Instance()->SendPacket(buyer->zone(), buyer->instance(), pack); } break; } case Barter_FailedTransaction: case Barter_BuyerTransactionComplete: { - auto seller = client_list.FindCharacter(in->seller_name); + auto seller = ClientList::Instance()->FindCharacter(in->seller_name); if (seller) { - zoneserver_list.SendPacket(seller->zone(), seller->instance(), pack); + ZSList::Instance()->SendPacket(seller->zone(), seller->instance(), pack); } break; @@ -1858,7 +1787,7 @@ void ZoneServer::SendEmoteMessageRaw(const char* to, uint32 to_guilddbid, int16 void ZoneServer::SendGroupIDs() { auto pack = new ServerPacket(ServerOP_GroupIDReply, sizeof(ServerGroupIDReply_Struct)); auto sgi = (ServerGroupIDReply_Struct*) pack->pBuffer; - zoneserver_list.NextGroupIDs(sgi->start, sgi->end); + ZSList::Instance()->NextGroupIDs(sgi->start, sgi->end); SendPacket(pack); delete pack; } @@ -1875,7 +1804,7 @@ void ZoneServer::ChangeWID(uint32 iCharID, uint32 iWID) { auto scw = (ServerChangeWID_Struct*) pack->pBuffer; scw->charid = iCharID; scw->newwid = iWID; - zoneserver_list.SendPacket(pack); + ZSList::Instance()->SendPacket(pack); delete pack; } @@ -1925,3 +1854,19 @@ void ZoneServer::IncomingClient(Client* client) { SendPacket(pack); delete pack; } + +void ZoneServer::CheckToClearTraderAndBuyerTables() +{ + if (GetZoneID() == Zones::BAZAAR) { + TraderRepository::DeleteWhere( + database, + fmt::format("`char_zone_id` = {} AND `char_zone_instance_id` = {}", GetZoneID(), GetInstanceID() + ) + ); + BuyerRepository::DeleteBuyers(database, GetZoneID(), GetInstanceID()); + + LogTradingDetail( + "Removed trader and buyer entries for Zone ID [{}] and Instance ID [{}]", GetZoneID(), GetInstanceID() + ); + } +} diff --git a/world/zoneserver.h b/world/zoneserver.h index cc490bdd1..4e93c9b7e 100644 --- a/world/zoneserver.h +++ b/world/zoneserver.h @@ -53,6 +53,9 @@ public: inline const char* GetZoneName() const { return zone_name; } inline const char* GetZoneLongName() const { return long_name; } + inline std::string GetCurrentVersion() const { return CURRENT_VERSION; } + void CheckToClearTraderAndBuyerTables(); + inline std::string GetCompileDate() const { return COMPILE_DATE; } const char* GetCompileTime() const{ return compiled; } void SetCompile(char* in_compile){ strcpy(compiled,in_compile); } inline uint32 GetZoneID() const { return zone_server_zone_id; } diff --git a/zone/CMakeLists.txt b/zone/CMakeLists.txt index 1ead62e1b..ffea99f78 100644 --- a/zone/CMakeLists.txt +++ b/zone/CMakeLists.txt @@ -12,19 +12,18 @@ SET(zone_sources bonuses.cpp bot.cpp bot_raid.cpp - bot_command.cpp bot_database.cpp botspellsai.cpp cheat_manager.cpp client.cpp + client_evolving_items.cpp client_bot.cpp client_mods.cpp client_packet.cpp client_process.cpp combat_record.cpp - command.cpp corpse.cpp - data_bucket.cpp + ../common/data_bucket.cpp doors.cpp dialogue_window.cpp dynamic_zone.cpp @@ -35,8 +34,6 @@ SET(zone_sources encounter.cpp entity.cpp exp.cpp - expedition.cpp - expedition_database.cpp expedition_request.cpp fastmath.cpp fearpath.cpp @@ -49,35 +46,6 @@ SET(zone_sources horse.cpp inventory.cpp loot.cpp - lua_bot.cpp - lua_bit.cpp - lua_buff.cpp - lua_corpse.cpp - lua_client.cpp - lua_database.cpp - lua_door.cpp - lua_encounter.cpp - lua_entity.cpp - lua_entity_list.cpp - lua_expedition.cpp - lua_general.cpp - lua_group.cpp - lua_hate_list.cpp - lua_inventory.cpp - lua_item.cpp - lua_iteminst.cpp - lua_merc.cpp - lua_mob.cpp - lua_mod.cpp - lua_npc.cpp - lua_object.cpp - lua_packet.cpp - lua_parser.cpp - lua_parser_events.cpp - lua_raid.cpp - lua_spawn.cpp - lua_spell.cpp - lua_stat_bonuses.cpp embperl.cpp entity.cpp exp.cpp @@ -108,29 +76,6 @@ SET(zone_sources pathfinder_nav_mesh.cpp pathfinder_null.cpp pathing.cpp - perl_bot.cpp - perl_buff.cpp - perl_client.cpp - perl_database.cpp - perl_doors.cpp - perl_entity.cpp - perl_expedition.cpp - perl_groups.cpp - perl_hateentry.cpp - perl_inventory.cpp - perl_merc.cpp - perl_mob.cpp - perl_npc.cpp - perl_object.cpp - perl_perlpacket.cpp - perl_player_corpse.cpp - perl_questitem.cpp - perl_questitem_data.cpp - perl_raids.cpp - perl_spawn.cpp - perl_spell.cpp - perl_stat_bonuses.cpp - perlpacket.cpp petitions.cpp pets.cpp position.cpp @@ -172,6 +117,7 @@ SET(zone_sources zone_event_scheduler.cpp zone_npc_factions.cpp zone_reload.cpp + zone_save_state.cpp zoning.cpp ) @@ -193,7 +139,7 @@ SET(zone_headers command.h common.h corpse.h - data_bucket.h + ../common/data_bucket.h doors.h dialogue_window.h dynamic_zone.h @@ -202,8 +148,6 @@ SET(zone_headers encounter.h entity.h event_codes.h - expedition.h - expedition_database.h expedition_request.h fastmath.h forage.h @@ -243,6 +187,7 @@ SET(zone_headers lua_spawn.h lua_spell.h lua_stat_bonuses.h + lua_zone.h map.h masterentity.h merc.h @@ -292,20 +237,272 @@ SET(zone_headers zonedump.h zone_cli.h zone_reload.h + zone_save_state.h zone_cli.cpp) +# lua unity build +set(lua_sources + lua_bot.cpp + lua_bit.cpp + lua_buff.cpp + lua_corpse.cpp + lua_client.cpp + lua_database.cpp + lua_door.cpp + lua_encounter.cpp + lua_entity.cpp + lua_entity_list.cpp + lua_expedition.cpp + lua_general.cpp + lua_group.cpp + lua_hate_list.cpp + lua_inventory.cpp + lua_item.cpp + lua_iteminst.cpp + lua_merc.cpp + lua_mob.cpp + lua_mod.cpp + lua_npc.cpp + lua_object.cpp + lua_packet.cpp + lua_parser.cpp + lua_parser_events.cpp + lua_raid.cpp + lua_spawn.cpp + lua_spell.cpp + lua_stat_bonuses.cpp + lua_zone.cpp +) + +add_library(lua_zone STATIC ${lua_sources}) +set_target_properties(lua_zone PROPERTIES UNITY_BUILD ON UNITY_BUILD_BATCH_SIZE 8) + +# perl unity build +set(perl_sources + perl_bot.cpp + perl_buff.cpp + perl_client.cpp + perl_database.cpp + perl_doors.cpp + perl_entity.cpp + perl_expedition.cpp + perl_groups.cpp + perl_hateentry.cpp + perl_inventory.cpp + perl_merc.cpp + perl_mob.cpp + perl_npc.cpp + perl_object.cpp + perl_perlpacket.cpp + perl_player_corpse.cpp + perl_questitem.cpp + perl_questitem_data.cpp + perl_raids.cpp + perl_spawn.cpp + perl_spell.cpp + perl_stat_bonuses.cpp + perl_zone.cpp + perlpacket.cpp +) + +add_library(perl_zone STATIC ${perl_sources}) +set_target_properties(perl_zone PROPERTIES UNITY_BUILD ON UNITY_BUILD_BATCH_SIZE 8) + +# gm commands +set(gm_command_sources + command.cpp + bot_command.cpp + gm_commands/acceptrules.cpp + gm_commands/advnpcspawn.cpp + gm_commands/aggrozone.cpp + gm_commands/ai.cpp + gm_commands/appearance.cpp + gm_commands/appearanceeffects.cpp + gm_commands/attack.cpp + gm_commands/augmentitem.cpp + gm_commands/ban.cpp + gm_commands/bugs.cpp + gm_commands/camerashake.cpp + gm_commands/castspell.cpp + gm_commands/chat.cpp + gm_commands/clearxtargets.cpp + gm_commands/copycharacter.cpp + gm_commands/corpse.cpp + gm_commands/corpsefix.cpp + gm_commands/countitem.cpp + gm_commands/damage.cpp + gm_commands/databuckets.cpp + gm_commands/dbspawn2.cpp + gm_commands/delacct.cpp + gm_commands/delpetition.cpp + gm_commands/depop.cpp + gm_commands/depopzone.cpp + gm_commands/devtools.cpp + gm_commands/disablerecipe.cpp + gm_commands/disarmtrap.cpp + gm_commands/doanim.cpp + gm_commands/door.cpp + gm_commands/door_manipulation.cpp + gm_commands/dye.cpp + gm_commands/dz.cpp + gm_commands/dzkickplayers.cpp + gm_commands/editmassrespawn.cpp + gm_commands/emote.cpp + gm_commands/emptyinventory.cpp + gm_commands/enablerecipe.cpp + gm_commands/entityvariable.cpp + gm_commands/exptoggle.cpp + gm_commands/faction.cpp + gm_commands/evolving_items.cpp + gm_commands/feature.cpp + gm_commands/find.cpp + gm_commands/fish.cpp + gm_commands/fixmob.cpp + gm_commands/flagedit.cpp + gm_commands/fleeinfo.cpp + gm_commands/forage.cpp + gm_commands/gearup.cpp + gm_commands/giveitem.cpp + gm_commands/givemoney.cpp + gm_commands/gmzone.cpp + gm_commands/goto.cpp + gm_commands/grantaa.cpp + gm_commands/grid.cpp + gm_commands/guild.cpp + gm_commands/hp.cpp + gm_commands/illusion_block.cpp + gm_commands/instance.cpp + gm_commands/interrogateinv.cpp + gm_commands/interrupt.cpp + gm_commands/invsnapshot.cpp + gm_commands/ipban.cpp + gm_commands/kick.cpp + gm_commands/kill.cpp + gm_commands/killallnpcs.cpp + gm_commands/list.cpp + gm_commands/lootsim.cpp + gm_commands/loc.cpp + gm_commands/logs.cpp + gm_commands/makepet.cpp + gm_commands/memspell.cpp + gm_commands/merchantshop.cpp + gm_commands/modifynpcstat.cpp + gm_commands/movechar.cpp + gm_commands/movement.cpp + gm_commands/myskills.cpp + gm_commands/mysql.cpp + gm_commands/mystats.cpp + gm_commands/npccast.cpp + gm_commands/npcedit.cpp + gm_commands/npceditmass.cpp + gm_commands/npcemote.cpp + gm_commands/npcloot.cpp + gm_commands/npcsay.cpp + gm_commands/npcshout.cpp + gm_commands/npcspawn.cpp + gm_commands/npctypespawn.cpp + gm_commands/nudge.cpp + gm_commands/nukebuffs.cpp + gm_commands/nukeitem.cpp + gm_commands/object.cpp + gm_commands/object_manipulation.cpp + gm_commands/parcels.cpp + gm_commands/path.cpp + gm_commands/peqzone.cpp + gm_commands/petitems.cpp + gm_commands/petname.cpp + gm_commands/picklock.cpp + gm_commands/profanity.cpp + gm_commands/push.cpp + gm_commands/raidloot.cpp + gm_commands/randomfeatures.cpp + gm_commands/refreshgroup.cpp + gm_commands/reload.cpp + gm_commands/removeitem.cpp + gm_commands/repop.cpp + gm_commands/resetaa.cpp + gm_commands/resetaa_timer.cpp + gm_commands/resetdisc_timer.cpp + gm_commands/revoke.cpp + gm_commands/roambox.cpp + gm_commands/rules.cpp + gm_commands/save.cpp + gm_commands/scale.cpp + gm_commands/scribespell.cpp + gm_commands/scribespells.cpp + gm_commands/sendzonespawns.cpp + gm_commands/sensetrap.cpp + gm_commands/serverrules.cpp + gm_commands/set.cpp + gm_commands/show.cpp + gm_commands/shutdown.cpp + gm_commands/spawn.cpp + gm_commands/spawneditmass.cpp + gm_commands/spawnfix.cpp + gm_commands/faction_association.cpp + gm_commands/stun.cpp + gm_commands/summon.cpp + gm_commands/summonburiedplayercorpse.cpp + gm_commands/summonitem.cpp + gm_commands/suspend.cpp + gm_commands/suspendmulti.cpp + gm_commands/takeplatinum.cpp + gm_commands/task.cpp + gm_commands/traindisc.cpp + gm_commands/tune.cpp + gm_commands/undye.cpp + gm_commands/unmemspell.cpp + gm_commands/unmemspells.cpp + gm_commands/unscribespell.cpp + gm_commands/unscribespells.cpp + gm_commands/untraindisc.cpp + gm_commands/untraindiscs.cpp + gm_commands/wc.cpp + gm_commands/worldshutdown.cpp + gm_commands/worldwide.cpp + gm_commands/wp.cpp + gm_commands/wpadd.cpp + gm_commands/zone.cpp + gm_commands/zonebootup.cpp + gm_commands/zoneshutdown.cpp + gm_commands/zonevariable.cpp + gm_commands/zone_instance.cpp + gm_commands/zone_shard.cpp + gm_commands/zsave.cpp +) + +add_library(gm_commands_zone STATIC ${gm_command_sources}) +set_target_properties(gm_commands_zone PROPERTIES UNITY_BUILD ON UNITY_BUILD_BATCH_SIZE 32) + +# zone combine sources and headers ADD_EXECUTABLE(zone ${zone_sources} ${zone_headers}) +# binary output directory INSTALL(TARGETS zone RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) -IF (WIN32 AND EQEMU_BUILD_PCH) - TARGET_PRECOMPILE_HEADERS(zone PRIVATE ../common/pch/pch.h) - TARGET_PRECOMPILE_HEADERS(zone PRIVATE ../common/types.h ../common/eqemu_logsys.h ../common/eqemu_logsys_log_aliases.h ../common/features.h ../common/global_define.h) - TARGET_PRECOMPILE_HEADERS(zone PRIVATE mob.h npc.h corpse.h doors.h bot.h entity.h client.h zone.h) +# precompiled headers +IF (EQEMU_BUILD_PCH) + TARGET_PRECOMPILE_HEADERS(zone PRIVATE ../common/pch/app-pch.h) + TARGET_PRECOMPILE_HEADERS(zone PRIVATE ../common/pch/std-pch.h) + TARGET_PRECOMPILE_HEADERS(zone PRIVATE ./pch/pch.h) ENDIF() ADD_DEFINITIONS(-DZONE) -TARGET_LINK_LIBRARIES(zone ${ZONE_LIBS}) +# link lua_zone unity build against luabind +target_link_libraries(lua_zone PRIVATE luabind) +if (EQEMU_BUILD_STATIC AND LUA_LIBRARY) + target_link_libraries(zone PRIVATE ${LUA_LIBRARY}) +endif() + +# perl unity build links against perl_zone +target_link_libraries(perl_zone PRIVATE perlbind) +if (EQEMU_BUILD_STATIC AND PERL_LIBRARY) + target_link_libraries(zone PRIVATE ${PERL_LIBRARY}) +endif() + +# link zone against common libraries +target_link_libraries(zone PRIVATE lua_zone perl_zone gm_commands_zone ${ZONE_LIBS}) SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) + diff --git a/zone/aa.cpp b/zone/aa.cpp index d75001d18..edefa361d 100644 --- a/zone/aa.cpp +++ b/zone/aa.cpp @@ -82,7 +82,7 @@ void Mob::TemporaryPets(uint16 spell_id, Mob *targ, const char *name_override, u for (int x = 0; x < MAX_SWARM_PETS; x++) { - if (spells[spell_id].effect_id[x] == SE_TemporaryPets) + if (spells[spell_id].effect_id[x] == SpellEffect::TemporaryPets) { pet.count = spells[spell_id].base_value[x]; pet.duration = spells[spell_id].max_value[x]; @@ -1180,7 +1180,7 @@ void Client::FinishAlternateAdvancementPurchase(AA::Rank *rank, bool ignore_cost SendAlternateAdvancementStats(); } - if (player_event_logs.IsEventEnabled(PlayerEvent::AA_PURCHASE)) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::AA_PURCHASE)) { auto e = PlayerEvent::AAPurchasedEvent{ .aa_id = rank->id, .aa_cost = cost, @@ -1202,19 +1202,6 @@ void Client::FinishAlternateAdvancementPurchase(AA::Rank *rank, bool ignore_cost cost == 1 ? std::to_string(AA_POINT).c_str() : std::to_string(AA_POINTS).c_str() ); } - - /* QS: Player_Log_AA_Purchases */ - if (RuleB(QueryServ, PlayerLogAAPurchases)) { - const auto event_desc = fmt::format( - "Ranked AA Purchase :: aa_id:{} at cost:{} in zoneid:{} instid:{}", - rank->id, - cost, - GetZoneID(), - GetInstanceID() - ); - - QServ->PlayerLogEvent(Player_Log_AA_Purchases, CharacterID(), event_desc); - } } else { if (send_message_and_save) { MessageString( @@ -1225,19 +1212,6 @@ void Client::FinishAlternateAdvancementPurchase(AA::Rank *rank, bool ignore_cost cost == 1 ? std::to_string(AA_POINT).c_str() : std::to_string(AA_POINTS).c_str() ); } - - /* QS: Player_Log_AA_Purchases */ - if (RuleB(QueryServ, PlayerLogAAPurchases)) { - const auto event_desc = fmt::format( - "Initial AA Purchase :: aa_id:{} at cost:{} in zoneid:{} instid:{}", - rank->id, - cost, - GetZoneID(), - GetInstanceID() - ); - - QServ->PlayerLogEvent(Player_Log_AA_Purchases, CharacterID(), event_desc); - } } if (parse->PlayerHasQuestSub(EVENT_AA_BUY)) { @@ -1402,7 +1376,7 @@ int Mob::GetAlternateAdvancementCooldownReduction(AA::Rank *rank_in) { } for(auto &effect : rank->effects) { - if(effect.effect_id == SE_HastenedAASkill && effect.limit_value == ability_in->id) { + if(effect.effect_id == SpellEffect::HastenedAASkill && effect.limit_value == ability_in->id) { total_reduction += effect.base_value; } } @@ -1645,7 +1619,7 @@ bool Mob::CanUseAlternateAdvancementRank(AA::Rank *rank) auto race = GetPlayerRaceValue(GetBaseRace()); - race = race > PLAYER_RACE_COUNT ? Race::Human : race; + race = race > RaceIndex::Drakkin ? Race::Human : race; if (!(a->races & (1 << (race - 1)))) { return false; @@ -1975,7 +1949,7 @@ void Client::TogglePassiveAlternativeAdvancement(const AA::Rank &rank, uint32 ab Instructions for how to make the AA - assuming a basic level of knowledge of how AA's work. - aa_abilities table : Create new ability with a hotkey, type 3, zero charges - - aa_ranks table : [Disabled rank] First rank, should have a cost > 0 (this is what you buy), Set hotkeys, MUST SET A SPELL CONTAINING EFFECT SE_Buy_AA_Rank(SPA 472), set a short recast timer. + - aa_ranks table : [Disabled rank] First rank, should have a cost > 0 (this is what you buy), Set hotkeys, MUST SET A SPELL CONTAINING EFFECT SpellEffect::Buy_AA_Rank(SPA 472), set a short recast timer. [Enabled rank] Second rank, should have a cost = 0, Set hotkeys, Set any valid spell ID you want (it has to exist but does nothing), set a short recast timer. *Recommend if doing custom, just make the hotkey titled 'Toggle ' and use for both. @@ -1995,7 +1969,7 @@ void Client::TogglePassiveAlternativeAdvancement(const AA::Rank &rank, uint32 ab */ - bool enable_next_rank = IsEffectInSpell(rank.spell, SE_Buy_AA_Rank); + bool enable_next_rank = IsEffectInSpell(rank.spell, SpellEffect::Buy_AA_Rank); if (enable_next_rank) { @@ -2006,7 +1980,7 @@ void Client::TogglePassiveAlternativeAdvancement(const AA::Rank &rank, uint32 ab AA::Rank *rank_next = zone->GetAlternateAdvancementRank(rank.next_id); //Add checks for any special cases for toggle. - if (rank_next && IsEffectinAlternateAdvancementRankEffects(*rank_next, SE_Weapon_Stance)) { + if (rank_next && IsEffectinAlternateAdvancementRankEffects(*rank_next, SpellEffect::Weapon_Stance)) { weaponstance.aabonus_enabled = true; ApplyWeaponsStance(); } @@ -2020,7 +1994,7 @@ void Client::TogglePassiveAlternativeAdvancement(const AA::Rank &rank, uint32 ab Message(Chat::Spells, "You disable an ability."); //Message live gives you. Should come from spell. //Add checks for any special cases for toggle. - if (IsEffectinAlternateAdvancementRankEffects(rank, SE_Weapon_Stance)) { + if (IsEffectinAlternateAdvancementRankEffects(rank, SpellEffect::Weapon_Stance)) { weaponstance.aabonus_enabled = false; BuffFadeBySpellID(weaponstance.aabonus_buff_spell_id); } @@ -2031,8 +2005,8 @@ void Client::TogglePassiveAlternativeAdvancement(const AA::Rank &rank, uint32 ab bool Client::UseTogglePassiveHotkey(const AA::Rank &rank) { /* - Disabled rank needs a rank spell containing the SE_Buy_AA_Rank effect to return true. - Enabled rank checks to see if the prior rank contains a rank spell with SE_Buy_AA_Rank, if so true. + Disabled rank needs a rank spell containing the SpellEffect::Buy_AA_Rank effect to return true. + Enabled rank checks to see if the prior rank contains a rank spell with SpellEffect::Buy_AA_Rank, if so true. Note: On live the enabled rank is Expendable with Charge 1. @@ -2040,13 +2014,13 @@ bool Client::UseTogglePassiveHotkey(const AA::Rank &rank) { */ - if (IsEffectInSpell(rank.spell, SE_Buy_AA_Rank)) {//Checked when is Disabled. + if (IsEffectInSpell(rank.spell, SpellEffect::Buy_AA_Rank)) {//Checked when is Disabled. return true; } else if (rank.prev_id != -1) {//Check when effect is Enabled. AA::Rank *rank_prev = zone->GetAlternateAdvancementRank(rank.prev_id); - if (rank_prev && IsEffectInSpell(rank_prev->spell, SE_Buy_AA_Rank)) { + if (rank_prev && IsEffectInSpell(rank_prev->spell, SpellEffect::Buy_AA_Rank)) { return true; } } diff --git a/zone/aggro.cpp b/zone/aggro.cpp index 1bc7efa76..136a09ded 100644 --- a/zone/aggro.cpp +++ b/zone/aggro.cpp @@ -405,17 +405,12 @@ bool Mob::CheckWillAggro(Mob *mob) { return false; } - Mob *pet_owner = mob->GetOwner(); - if ( - pet_owner && - pet_owner->IsClient() && - ( - !RuleB(Aggro, AggroPlayerPets) || - pet_owner->CastToClient()->GetGM() || - mob->GetSpecialAbility(SpecialAbility::AggroImmunity) - ) - ) { - return false; + Mob* pet_owner = mob->GetOwner(); + + if (pet_owner && pet_owner->IsOfClientBot()) { + if (mob->IsPetAggroExempt(pet_owner)) { + return false; + } } if (IsNPC() && mob->IsNPC() && mob->GetSpecialAbility(SpecialAbility::NPCAggroImmunity)) { @@ -582,6 +577,34 @@ bool Mob::CheckWillAggro(Mob *mob) { return false; } +bool Mob::IsPetAggroExempt(Mob* pet_owner) { + if (!pet_owner) { + return false; + } + + bool exempt_client_pet = pet_owner->IsClient() && !RuleB(Aggro, AggroPlayerPets); + bool exempt_bot_pet = pet_owner->IsBot() && !RuleB(Aggro, AggroBotPets); + + if (exempt_client_pet || exempt_bot_pet) { + return true; + } + + Mob* ultimate_owner = GetUltimateOwner(); + Client* client_owner = (ultimate_owner && ultimate_owner->IsClient()) + ? ultimate_owner->CastToClient() + : nullptr; + + if (client_owner && client_owner->GetGM()) { + return true; + } + + if (GetSpecialAbility(SpecialAbility::AggroImmunity)) { + return true; + } + + return false; +} + int EntityList::FleeAllyCount(Mob* attacker, Mob* skipped) { // Return a list of how many NPCs of the same faction or race are within aggro range of the given exclude Mob. @@ -743,12 +766,30 @@ bool Mob::IsAttackAllowed(Mob *target, bool isSpellAttack) } // can't damage own pet (applies to everthing) - Mob *target_owner = target->GetOwner(); - Mob *our_owner = GetOwner(); - if(target_owner && target_owner == this) - return false; - else if(our_owner && our_owner == target) + Mob* target_owner = target->GetOwner(); + Mob* our_owner = GetOwner(); + + // Self-owner check + if (target_owner == this || our_owner == target) { return false; + } + + // Bot-specific logic + if (IsBot()) { + Mob* target_ultimate_owner = target->IsBot() ? target->CastToBot()->GetBotOwner() : target->GetUltimateOwner(); + Mob* our_ultimate_owner = CastToBot()->GetBotOwner(); + + if (target_ultimate_owner) { + if (target_ultimate_owner == our_ultimate_owner || target_ultimate_owner->IsOfClientBot()) { + return false; + } + } + + // Bots should not attack their ultimate owner + if (our_ultimate_owner == target) { + return false; + } + } // invalidate for swarm pets for later on if their owner is a corpse if (IsNPC() && CastToNPC()->GetSwarmInfo() && our_owner && @@ -1278,6 +1319,39 @@ bool Mob::CheckLosFN(glm::vec3 posWatcher, float sizeWatcher, glm::vec3 posTarge return zone->zonemap->CheckLoS(posWatcher, posTarget); } +bool Mob::CheckPositioningLosFN(Mob* other, float x, float y, float z) { + if (!zone->zonemap) { + //not sure what the best return is on error + //should make this a database variable, but im lazy today +#ifdef LOS_DEFAULT_CAN_SEE + return(true); +#else + return(false); +#endif + } + + if (!other) { + return(true); + } + glm::vec3 myloc; + glm::vec3 oloc; + +#define LOS_DEFAULT_HEIGHT 6.0f + + oloc.x = other->GetX(); + oloc.y = other->GetY(); + oloc.z = other->GetZ() + (other->GetSize() == 0.0 ? LOS_DEFAULT_HEIGHT : other->GetSize()) / 2 * SEE_POSITION; + + myloc.x = x; + myloc.y = y; + myloc.z = z + (GetSize() == 0.0 ? LOS_DEFAULT_HEIGHT : GetSize()) / 2 * HEAD_POSITION; + +#if LOSDEBUG>=5 + LogDebug("LOS from ([{}], [{}], [{}]) to ([{}], [{}], [{}]) sizes: ([{}], [{}])", myloc.x, myloc.y, myloc.z, oloc.x, oloc.y, oloc.z, GetSize(), mobSize); +#endif + return zone->zonemap->CheckLoS(myloc, oloc); +} + //offensive spell aggro int32 Mob::CheckAggroAmount(uint16 spell_id, Mob *target, bool is_proc) { @@ -1303,132 +1377,132 @@ int32 Mob::CheckAggroAmount(uint16 spell_id, Mob *target, bool is_proc) for (int o = 0; o < EFFECT_COUNT; o++) { switch (spells[spell_id].effect_id[o]) { - case SE_CurrentHPOnce: - case SE_CurrentHP: { + case SpellEffect::CurrentHPOnce: + case SpellEffect::CurrentHP: { int64 val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base_value[o], spells[spell_id].max_value[o], mob_level, spell_id); if(val < 0) { aggro_amount -= val; } break; } - case SE_MovementSpeed: { + case SpellEffect::MovementSpeed: { int64 val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base_value[o], spells[spell_id].max_value[o], mob_level, spell_id); if (val < 0) { aggro_amount += default_aggro; } break; } - case SE_AttackSpeed: - case SE_AttackSpeed2: - case SE_AttackSpeed3: { + case SpellEffect::AttackSpeed: + case SpellEffect::AttackSpeed2: + case SpellEffect::AttackSpeed3: { int64 val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base_value[o], spells[spell_id].max_value[o], mob_level, spell_id); if (val < 100) { aggro_amount += default_aggro; } break; } - case SE_Stun: - case SE_Blind: - case SE_Mez: - case SE_Charm: - case SE_Fear: - case SE_Fearstun: + case SpellEffect::Stun: + case SpellEffect::Blind: + case SpellEffect::Mez: + case SpellEffect::Charm: + case SpellEffect::Fear: + case SpellEffect::Fearstun: aggro_amount += default_aggro; break; - case SE_Root: + case SpellEffect::Root: aggro_amount += 10; break; - case SE_ACv2: - case SE_ArmorClass: { + case SpellEffect::ACv2: + case SpellEffect::ArmorClass: { int64 val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base_value[o], spells[spell_id].max_value[o], mob_level, spell_id); if (val < 0) { aggro_amount += default_aggro; } break; } - case SE_ATK: - case SE_ResistMagic: - case SE_ResistFire: - case SE_ResistCold: - case SE_ResistPoison: - case SE_ResistDisease: - case SE_STR: - case SE_STA: - case SE_DEX: - case SE_AGI: - case SE_INT: - case SE_WIS: - case SE_CHA: { + case SpellEffect::ATK: + case SpellEffect::ResistMagic: + case SpellEffect::ResistFire: + case SpellEffect::ResistCold: + case SpellEffect::ResistPoison: + case SpellEffect::ResistDisease: + case SpellEffect::STR: + case SpellEffect::STA: + case SpellEffect::DEX: + case SpellEffect::AGI: + case SpellEffect::INT: + case SpellEffect::WIS: + case SpellEffect::CHA: { int64 val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base_value[o], spells[spell_id].max_value[o], mob_level, spell_id); if (val < 0) { aggro_amount += 10; } break; } - case SE_ResistAll: { + case SpellEffect::ResistAll: { int64 val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base_value[o], spells[spell_id].max_value[o], mob_level, spell_id); if (val < 0) { aggro_amount += 50; } break; } - case SE_AllStats: { + case SpellEffect::AllStats: { int64 val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base_value[o], spells[spell_id].max_value[o], mob_level, spell_id); if (val < 0) { aggro_amount += 70; } break; } - case SE_BardAEDot: + case SpellEffect::BardAEDot: aggro_amount += 10; break; - case SE_SpinTarget: - case SE_Amnesia: - case SE_Silence: - case SE_Destroy: + case SpellEffect::SpinTarget: + case SpellEffect::Amnesia: + case SpellEffect::Silence: + case SpellEffect::Destroy: aggro_amount += default_aggro; break; // unsure -- leave them this for now - case SE_Harmony: - case SE_CastingLevel: - case SE_MeleeMitigation: - case SE_CriticalHitChance: - case SE_AvoidMeleeChance: - case SE_RiposteChance: - case SE_DodgeChance: - case SE_ParryChance: - case SE_DualWieldChance: - case SE_DoubleAttackChance: - case SE_MeleeSkillCheck: - case SE_HitChance: - case SE_DamageModifier: - case SE_MinDamageModifier: - case SE_IncreaseBlockChance: - case SE_Accuracy: - case SE_DamageShield: - case SE_SpellDamageShield: - case SE_ReverseDS: { + case SpellEffect::Harmony: + case SpellEffect::CastingLevel: + case SpellEffect::MeleeMitigation: + case SpellEffect::CriticalHitChance: + case SpellEffect::AvoidMeleeChance: + case SpellEffect::RiposteChance: + case SpellEffect::DodgeChance: + case SpellEffect::ParryChance: + case SpellEffect::DualWieldChance: + case SpellEffect::DoubleAttackChance: + case SpellEffect::MeleeSkillCheck: + case SpellEffect::HitChance: + case SpellEffect::DamageModifier: + case SpellEffect::MinDamageModifier: + case SpellEffect::IncreaseBlockChance: + case SpellEffect::Accuracy: + case SpellEffect::DamageShield: + case SpellEffect::SpellDamageShield: + case SpellEffect::ReverseDS: { aggro_amount += mob_level * 2; break; } // unsure -- leave them this for now - case SE_CurrentMana: - case SE_ManaRegen_v2: - case SE_ManaPool: - case SE_CurrentEndurance: { + case SpellEffect::CurrentMana: + case SpellEffect::ManaRegen_v2: + case SpellEffect::ManaPool: + case SpellEffect::CurrentEndurance: { int64 val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base_value[o], spells[spell_id].max_value[o], mob_level, spell_id); if (val < 0) { aggro_amount -= val * 2; } break; } - case SE_CancelMagic: - case SE_DispelDetrimental: - case SE_DispelBeneficial: + case SpellEffect::CancelMagic: + case SpellEffect::DispelDetrimental: + case SpellEffect::DispelBeneficial: dispel = true; break; - case SE_ReduceHate: - case SE_InstantHate: + case SpellEffect::ReduceHate: + case SpellEffect::InstantHate: non_modified_aggro = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base_value[o], spells[spell_id].max_value[o], mob_level, spell_id); break; } @@ -1475,8 +1549,8 @@ int32 Mob::CheckHealAggroAmount(uint16 spell_id, Mob *target, uint32 heal_possib for (int o = 0; o < EFFECT_COUNT; o++) { switch (spells[spell_id].effect_id[o]) { - case SE_CurrentHP: - case SE_PercentalHeal: + case SpellEffect::CurrentHP: + case SpellEffect::PercentalHeal: { if (heal_possible == 0) { AggroAmount += 1; @@ -1498,12 +1572,12 @@ int32 Mob::CheckHealAggroAmount(uint16 spell_id, Mob *target, uint32 heal_possib AggroAmount += std::max(val, (int64)1); break; } - case SE_Rune: + case SpellEffect::Rune: AggroAmount += CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base_value[o], spells[spell_id].max_value[o], GetLevel(), spell_id) * 2; ignore_default_buff = true; break; - case SE_HealOverTime: + case SpellEffect::HealOverTime: AggroAmount += 10; ignore_default_buff = true; break; @@ -1658,4 +1732,3 @@ void Mob::RogueEvade(Mob *other) return; } - diff --git a/zone/api_service.cpp b/zone/api_service.cpp index a0d02b1c9..607ab64c6 100644 --- a/zone/api_service.cpp +++ b/zone/api_service.cpp @@ -82,7 +82,7 @@ Json::Value ApiGetPacketStatistics(EQ::Net::WebsocketServerConnection *connectio auto connection = client->Connection(); auto opts = connection->GetManager()->GetOptions(); auto eqs_stats = connection->GetStats(); - auto &stats = eqs_stats.DaybreakStats; + auto &stats = eqs_stats.ReliableStreamStats; auto now = EQ::Net::Clock::now(); auto sec_since_stats_reset = std::chrono::duration_cast>( now - stats.created @@ -835,9 +835,9 @@ Json::Value ApiGetLogsysCategories(EQ::Net::WebsocketServerConnection *connectio row["log_category_id"] = i; row["log_category_description"] = Logs::LogCategoryName[i]; - row["log_to_console"] = LogSys.log_settings[i].log_to_console; - row["log_to_file"] = LogSys.log_settings[i].log_to_file; - row["log_to_gmsay"] = LogSys.log_settings[i].log_to_gmsay; + row["log_to_console"] = EQEmuLogSys::Instance()->log_settings[i].log_to_console; + row["log_to_file"] = EQEmuLogSys::Instance()->log_settings[i].log_to_file; + row["log_to_gmsay"] = EQEmuLogSys::Instance()->log_settings[i].log_to_gmsay; response.append(row); } @@ -866,15 +866,15 @@ Json::Value ApiSetLoggingLevel(EQ::Net::WebsocketServerConnection *connection, J if (logging_category < Logs::LogCategory::MaxCategoryID && logging_category > Logs::LogCategory::None ) { - LogSys.log_settings[logging_category].log_to_console = logging_level; + EQEmuLogSys::Instance()->log_settings[logging_category].log_to_console = logging_level; response["status"] = "Category log level updated"; } if (logging_level > 0) { - LogSys.log_settings[logging_category].is_category_enabled = 1; + EQEmuLogSys::Instance()->log_settings[logging_category].is_category_enabled = 1; } else { - LogSys.log_settings[logging_category].is_category_enabled = 0; + EQEmuLogSys::Instance()->log_settings[logging_category].is_category_enabled = 0; } return response; @@ -882,7 +882,7 @@ Json::Value ApiSetLoggingLevel(EQ::Net::WebsocketServerConnection *connection, J void RegisterApiLogEvent(std::unique_ptr &server) { - LogSys.SetConsoleHandler( + EQEmuLogSys::Instance()->SetConsoleHandler( [&](uint16 log_category, const std::string &msg) { Json::Value data; data["log_category"] = log_category; diff --git a/zone/attack.cpp b/zone/attack.cpp index 6ffe44099..f5552c47c 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -179,7 +179,7 @@ int Mob::compute_tohit(EQ::skills::SkillType skillinuse) //SYNC WITH: tune.cpp, mob.h TuneGetTotalToHit int Mob::GetTotalToHit(EQ::skills::SkillType skill, int chance_mod) { - if (chance_mod >= 10000) // override for stuff like SE_SkillAttack + if (chance_mod >= 10000) // override for stuff like SpellEffect::SkillAttack return -1; // calculate attacker's accuracy @@ -209,7 +209,7 @@ int Mob::GetTotalToHit(EQ::skills::SkillType skill, int chance_mod) if (atkhit_bonus) accuracy += round(static_cast(accuracy) * static_cast(atkhit_bonus) * 0.0001); - // 216 Melee Accuracy Amt aka SE_Accuracy -- flat bonus + // 216 Melee Accuracy Amt aka SpellEffect::Accuracy -- flat bonus accuracy += itembonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1] + aabonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1] + spellbonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1] + @@ -224,7 +224,7 @@ int Mob::GetTotalToHit(EQ::skills::SkillType skill, int chance_mod) if (spellbonuses.HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1] >= 10000) return -1; - // 184 Accuracy % aka SE_HitChance -- percentage increase + // 184 Accuracy % aka SpellEffect::HitChance -- percentage increase auto hit_bonus = itembonuses.HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1] + aabonuses.HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1] + spellbonuses.HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1] + @@ -285,7 +285,7 @@ int Mob::compute_defense() } - //516 SE_AC_Mitigation_Max_Percent + //516 SpellEffect::AC_Mitigation_Max_Percent auto ac_bonus = itembonuses.AC_Mitigation_Max_Percent + aabonuses.AC_Mitigation_Max_Percent + spellbonuses.AC_Mitigation_Max_Percent; if (ac_bonus) { defense += round(static_cast(defense) * static_cast(ac_bonus) * 0.0001); @@ -315,15 +315,15 @@ int Mob::GetTotalDefense() if (evasion_bonus >= 10000) return -1; - // 515 SE_AC_Avoidance_Max_Percent + // 515 SpellEffect::AC_Avoidance_Max_Percent auto ac_aviodance_bonus = itembonuses.AC_Avoidance_Max_Percent + aabonuses.AC_Avoidance_Max_Percent + spellbonuses.AC_Avoidance_Max_Percent; if (ac_aviodance_bonus) avoidance += round(static_cast(avoidance) * static_cast(ac_aviodance_bonus) * 0.0001); - // 172 Evasion aka SE_AvoidMeleeChance + // 172 Evasion aka SpellEffect::AvoidMeleeChance evasion_bonus += itembonuses.AvoidMeleeChanceEffect + aabonuses.AvoidMeleeChanceEffect; // item bonus here isn't mod2 avoidance - // 215 Pet Avoidance % aka SE_PetAvoidance + // 215 Pet Avoidance % aka SpellEffect::PetAvoidance evasion_bonus += GetPetAvoidanceBonusFromOwner(); // Evasion is a percentage bonus according to AA descriptions @@ -401,8 +401,8 @@ bool Mob::AvoidDamage(Mob *other, DamageHitInfo &hit) */ /* Order according to current (SoF+?) dev quotes: - * https://forums.daybreakgames.com/eq/index.php?threads/test-update-06-10-15.223510/page-2#post-3261772 - * https://forums.daybreakgames.com/eq/index.php?threads/test-update-06-10-15.223510/page-2#post-3268227 + * https://web.archive.org/web/20250816014133/https://forums.everquest.com/index.php?threads/test-update-06-10-15.223510/page-2#post-3261772 + * https://web.archive.org/web/20250816014133/https://forums.everquest.com/index.php?threads/test-update-06-10-15.223510/page-2#post-3268227 * Riposte 50, hDEX, must have weapon/fists, doesn't work on archery/throwing * Block 25, hDEX, works on archery/throwing, behind block done here if back to attacker base1 is chance * Parry 45, hDEX, doesn't work on throwing/archery, must be facing target @@ -461,7 +461,7 @@ bool Mob::AvoidDamage(Mob *other, DamageHitInfo &hit) } /* Heroic Strikethrough Implementation per Dev Quotes (2018): - * https://forums.daybreakgames.com/eq/index.php?threads/illusions-benefit-neza-10-dodge.246757/#post-3622670 + * https://web.archive.org/web/20250816014810/https://forums.everquest.com/index.php?threads/illusions-benefit-neza-10-dodge.246757/#post-3622670 * Step1 = HeroicStrikethrough(NPC) * Step2 = HeroicAgility / 25 * Step3 = MIN( Step1, Step2 ) @@ -896,7 +896,7 @@ int Mob::GetClassRaceACBonus() ac_bonus = 16; } - if (GetRace() == IKSAR) + if (GetRace() == Race::Iksar) ac_bonus += EQ::Clamp(static_cast(level), 10, 35); return ac_bonus; @@ -1246,7 +1246,12 @@ int64 Mob::GetWeaponDamage(Mob *against, const EQ::ItemInstance *weapon_item, in return 0; } - if (!weapon_item->IsClassEquipable(GetClass())) { + if (!weapon_item->IsClassEquipable(GetClass()) && + ( + !IsBot() || + (IsBot() && !RuleB(Bots, AllowBotEquipAnyClassGear)) + ) + ) { return 0; } @@ -1550,10 +1555,7 @@ void Mob::DoAttack(Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts, boo hit.damage_done = 0; } - parse->EventBotMerc( - EVENT_USE_SKILL, - this, - nullptr, + parse->EventBotMerc(EVENT_USE_SKILL, this, nullptr, [&]() { return fmt::format( "{} {}", @@ -1915,7 +1917,7 @@ bool Client::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::Skil dead = true; if (m_pet && m_pet->IsCharmed()) { - m_pet->BuffFadeByEffect(SE_Charm); + m_pet->BuffFadeByEffect(SpellEffect::Charm); } if (GetMerc()) { @@ -2161,15 +2163,7 @@ bool Client::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::Skil GoToDeath(); } - /* QS: PlayerLogDeaths */ - if (RuleB(QueryServ, PlayerLogDeaths)) { - const char * killer_name = ""; - if (killer_mob && killer_mob->GetCleanName()) { killer_name = killer_mob->GetCleanName(); } - std::string event_desc = StringFormat("Died in zoneid:%i instid:%i by '%s', spellid:%i, damage:%i", GetZoneID(), GetInstanceID(), killer_name, spell, damage); - QServ->PlayerLogEvent(Player_Log_Deaths, CharacterID(), event_desc); - } - - if (player_event_logs.IsEventEnabled(PlayerEvent::DEATH)) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::DEATH)) { auto e = PlayerEvent::DeathEvent{ .killer_id = killer_mob ? static_cast(killer_mob->GetID()) : static_cast(0), .killer_name = killer_mob ? killer_mob->GetCleanName() : "No Killer", @@ -2396,7 +2390,7 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool LogCombat("Final damage against [{}]: [{}]", other->GetName(), my_hit.damage_done); - if (other->IsClient() && IsPet() && GetOwner()->IsClient()) { + if (other->IsClient() && IsPet() && GetOwner()->IsOfClientBot()) { //pets do half damage to clients in pvp my_hit.damage_done /= 2; if (my_hit.damage_done < 1) { @@ -2513,6 +2507,23 @@ 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) { + auto lde = LootdropEntriesRepository::NewNpcEntity(); + lde.equip_item = 0; + lde.item_charges = i.item->GetCharges(); + AddLootDrop(i.item->GetItem(), lde, true); + } + } + } + if (killer_mob && killer_mob->IsOfClientBot() && IsValidSpell(spell) && damage > 0) { char val1[20] = { 0 }; @@ -2562,16 +2573,12 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy const uint8 killed_level = GetLevel(); if (GetClass() == Class::LDoNTreasure) { // open chest - auto outapp = new EQApplicationPacket(OP_Animation, sizeof(Animation_Struct)); - - auto a = (Animation_Struct*) outapp->pBuffer; - + static EQApplicationPacket p(OP_Animation, sizeof(Animation_Struct)); + auto a = (Animation_Struct*) p.pBuffer; a->spawnid = GetID(); a->action = 0x0F; a->speed = 10; - - entity_list.QueueCloseClients(this, outapp); - safe_delete(outapp); + entity_list.QueueCloseClients(this, &p); } auto app = new EQApplicationPacket(OP_Death, sizeof(Death_Struct)); @@ -2619,35 +2626,28 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy } if (give_exp && give_exp->HasOwner()) { - bool owner_in_group = false; + auto owner = give_exp->GetOwner(); - if ( - ( - give_exp->HasGroup() && - give_exp->GetGroup()->IsGroupMember(give_exp->GetUltimateOwner()) - ) || - ( - give_exp->IsPet() && - ( - give_exp->GetOwner()->IsClient() || - ( - give_exp->GetOwner()->HasGroup() && - give_exp->GetOwner()->GetGroup()->IsGroupMember(give_exp->GetOwner()->GetUltimateOwner()) - ) - ) - ) - ) { - owner_in_group = true; + if (owner) { + Mob* ulimate_owner = give_exp->GetUltimateOwner(); + 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)) || + (owner_is_client && give_exp->IsInGroupOrRaid(ulimate_owner)) + ); + + give_exp = (is_in_same_group_or_raid ? give_exp->GetUltimateOwner() : nullptr); } - - give_exp = give_exp->GetUltimateOwner(); - - if (!RuleB(Bots, BotGroupXP) && !owner_in_group) { + else { give_exp = nullptr; } } - if (give_exp && give_exp->IsTempPet() && give_exp->IsPetOwnerClient()) { + if (give_exp && give_exp->IsTempPet() && give_exp->IsPetOwnerOfClientBot()) { if (give_exp->IsNPC() && give_exp->CastToNPC()->GetSwarmOwner()) { Mob* temp_owner = entity_list.GetMobID(give_exp->CastToNPC()->GetSwarmOwner()); if (temp_owner) { @@ -2683,7 +2683,7 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy give_exp_client->GetCleanName(), GetNPCTypeID() ); - task_manager->HandleUpdateTasksOnKill(give_exp_client, this); + TaskManager::Instance()->HandleUpdateTasksOnKill(give_exp_client, this); } if (killer_raid) { @@ -2721,37 +2721,6 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy player_count++; } } - - // QueryServ Logging - Raid Kills - if (RuleB(QueryServ, PlayerLogNPCKills)) { - auto pack = new ServerPacket( - ServerOP_QSPlayerLogNPCKills, - sizeof(QSPlayerLogNPCKill_Struct) + - (sizeof(QSPlayerLogNPCKillsPlayers_Struct) * player_count) - ); - - player_count = 0; - - auto QS = (QSPlayerLogNPCKill_Struct*)pack->pBuffer; - - QS->s1.NPCID = GetNPCTypeID(); - QS->s1.ZoneID = GetZoneID(); - QS->s1.Type = 2; // Raid Fight - - for (const auto& m : killer_raid->members) { - if (m.is_bot) { - continue; - } - - if (m.member && m.member->IsClient()) { - QS->Chars[player_count].char_id = m.member->CastToClient()->CharacterID(); - player_count++; - } - } - - worldserver.SendPacket(pack); - safe_delete(pack); - } } else if (give_exp_client->IsGrouped() && killer_group) { if (!is_ldon_treasure && MerchantType == 0) { killer_group->SplitExp(ExpSource::Kill, final_exp, this); @@ -2785,40 +2754,13 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy player_count++; } } - - // QueryServ Logging - Group Kills - if (RuleB(QueryServ, PlayerLogNPCKills)) { - auto pack = new ServerPacket( - ServerOP_QSPlayerLogNPCKills, - sizeof(QSPlayerLogNPCKill_Struct) + - (sizeof(QSPlayerLogNPCKillsPlayers_Struct) * player_count) - ); - - player_count = 0; - - auto QS = (QSPlayerLogNPCKill_Struct*) pack->pBuffer; - - QS->s1.NPCID = GetNPCTypeID(); - QS->s1.ZoneID = GetZoneID(); - QS->s1.Type = 1; // Group Fight - - for (const auto& m : killer_group->members) { - if (m && m->IsClient()) { - QS->Chars[player_count].char_id = m->CastToClient()->CharacterID(); - player_count++; - } - } - - worldserver.SendPacket(pack); - safe_delete(pack); - } } else { if (!is_ldon_treasure && !MerchantType) { const uint32 con_level = give_exp->GetLevelCon(GetLevel()); if (con_level != ConsiderColor::Gray) { - if (!GetOwner() || (GetOwner() && !GetOwner()->IsClient())) { - give_exp_client->AddEXP(ExpSource::Kill, final_exp, con_level); + if (!GetOwner() || (GetOwner() && !GetOwner()->IsOfClientBot())) { + give_exp_client->AddEXP(ExpSource::Kill, final_exp, con_level, false, this); if ( killer_mob && @@ -2842,28 +2784,6 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy give_exp_client->GetDeity() ); } - - // QueryServ Logging - Solo - if (RuleB(QueryServ, PlayerLogNPCKills)) { - auto pack = new ServerPacket( - ServerOP_QSPlayerLogNPCKills, - sizeof(QSPlayerLogNPCKill_Struct) + - (sizeof(QSPlayerLogNPCKillsPlayers_Struct) * 1) - ); - - auto QS = (QSPlayerLogNPCKill_Struct*)pack->pBuffer; - - QS->s1.NPCID = GetNPCTypeID(); - QS->s1.ZoneID = GetZoneID(); - QS->s1.Type = 0; // Solo Fight - QS->Chars[0].char_id = give_exp_client->CharacterID(); - - player_count++; - - worldserver.SendPacket(pack); // Send Packet to World - safe_delete(pack); - } - // End QueryServ Logging } } @@ -2875,32 +2795,35 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy const uint16 entity_id = GetID(); if ( - !HasOwner() && - !IsMerc() && - !GetSwarmInfo() && - (!is_merchant || allow_merchant_corpse) && ( + !HasOwner() && + !IsMerc() && + !GetSwarmInfo() && + (!is_merchant || allow_merchant_corpse) && ( - killer && ( - killer->IsClient() || + killer && ( - killer->HasOwner() && - killer->GetUltimateOwner()->IsClient() - ) || - ( - killer->IsNPC() && - killer->CastToNPC()->GetSwarmInfo() && - killer->CastToNPC()->GetSwarmInfo()->GetOwner() && - killer->CastToNPC()->GetSwarmInfo()->GetOwner()->IsClient() + killer->IsClient() || + ( + killer->HasOwner() && + killer->GetUltimateOwner()->IsClient() + ) || + ( + killer->IsNPC() && + killer->CastToNPC()->GetSwarmInfo() && + killer->CastToNPC()->GetSwarmInfo()->GetOwner() && + killer->CastToNPC()->GetSwarmInfo()->GetOwner()->IsClient() + ) ) + ) || + ( + killer_mob && is_ldon_treasure ) - ) || - ( - killer_mob && is_ldon_treasure ) ) - ) { + || IsQueuedForCorpse() + ) { if (killer) { if (killer->GetOwner() != 0 && killer->GetOwner()->IsClient()) { killer = killer->GetOwner(); @@ -3078,10 +3001,7 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy std::vector args = { corpse }; - parse->EventMercNPC( - EVENT_DEATH_COMPLETE, - this, - owner_or_self, + parse->EventMercNPC(EVENT_DEATH_COMPLETE, this, owner_or_self, [&]() { return fmt::format( "{} {} {} {} {} {} {} {} {}", @@ -3096,8 +3016,7 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy m_combat_record.GetHealingReceived() ); }, - 0, - &args + 0, &args ); // Zone controller process EVENT_DEATH_ZONE (Death events) @@ -3120,6 +3039,25 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy DispatchZoneControllerEvent(EVENT_DEATH_ZONE, owner_or_self, export_string, 0, &args); } + if (parse->ZoneHasQuestSub(EVENT_DEATH_ZONE)) { + const auto& export_string = fmt::format( + "{} {} {} {} {} {} {} {} {}", + killer_mob ? killer_mob->GetID() : 0, + damage, + spell, + static_cast(attack_skill), + entity_id, + m_combat_record.GetStartTime(), + m_combat_record.GetEndTime(), + m_combat_record.GetDamageReceived(), + m_combat_record.GetHealingReceived() + ); + + std::vector args = { corpse, this, owner_or_self }; + + parse->EventZone(EVENT_DEATH_ZONE, zone, export_string, 0, &args); + } + return true; } @@ -3128,9 +3066,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; @@ -3143,11 +3087,11 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo else SetAssistAggro(true); - bool wasengaged = IsEngaged(); + bool was_engaged = IsEngaged(); Mob* owner = other->GetOwner(); - Mob* mypet = GetPet(); - Mob* myowner = GetOwner(); - Mob* targetmob = GetTarget(); + Mob* my_pet = GetPet(); + Mob* my_owner = GetOwner(); + Mob* target_mob = GetTarget(); bool on_hatelist = CheckAggro(other); AddRampage(other); @@ -3176,7 +3120,7 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo if (IsPet()) { if ((IsGHeld() || (IsHeld() && IsFocused())) && !on_hatelist) // we want them to be able to climb the hate list return; - if ((IsHeld() || IsPetStop() || IsPetRegroup()) && !wasengaged) // not 100% sure on stop/regroup kind of hard to test, but regroup is like "classic hold" + if ((IsHeld() || IsPetStop() || IsPetRegroup()) && !was_engaged) // not 100% sure on stop/regroup kind of hard to test, but regroup is like "classic hold" return; } } @@ -3209,7 +3153,7 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo return; } - if (other == myowner) { + if (other == my_owner) { return; } @@ -3311,26 +3255,39 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo } } - if (mypet && !mypet->IsHeld() && !mypet->IsPetStop()) { // I have a pet, add other to it - if ( - !mypet->IsFamiliar() && - !mypet->GetSpecialAbility(SpecialAbility::AggroImmunity) && - !(IsBot() && mypet->GetSpecialAbility(SpecialAbility::BotAggroImmunity)) && - !(IsClient() && mypet->GetSpecialAbility(SpecialAbility::ClientAggroImmunity)) && - !(IsNPC() && mypet->GetSpecialAbility(SpecialAbility::NPCAggroImmunity)) - ) { - mypet->hate_list.AddEntToHateList(other, 0, 0, bFrenzy); + if (my_pet) { + bool aggro_immunity = my_pet->GetSpecialAbility(SpecialAbility::AggroImmunity); + bool bot_aggro_immunity = IsBot() && my_pet->GetSpecialAbility(SpecialAbility::BotAggroImmunity); + bool client_aggro_immunity = IsClient() && my_pet->GetSpecialAbility(SpecialAbility::ClientAggroImmunity); + bool npc_aggro_immunity = IsNPC() && my_pet->GetSpecialAbility(SpecialAbility::NPCAggroImmunity); + bool can_add_to_hatelist = !my_pet->IsFamiliar() && + !aggro_immunity && + !bot_aggro_immunity && + !client_aggro_immunity && + !npc_aggro_immunity; + + if (can_add_to_hatelist) { + bool bot_with_controllable_pet = IsBot() && CastToBot()->HasControllablePet(BotAnimEmpathy::Attack); + + if (!IsBot() || bot_with_controllable_pet) { + my_pet->hate_list.AddEntToHateList(other, 0, 0, bFrenzy); + } } } - else if (myowner) { // I am a pet, add other to owner if it's NPC/LD - if ( - myowner->IsAIControlled() && - !myowner->GetSpecialAbility(SpecialAbility::AggroImmunity) && - !(myowner->IsBot() && GetSpecialAbility(SpecialAbility::BotAggroImmunity)) && - !(myowner->IsClient() && GetSpecialAbility(SpecialAbility::ClientAggroImmunity)) && - !(myowner->IsNPC() && GetSpecialAbility(SpecialAbility::NPCAggroImmunity)) - ) { - myowner->hate_list.AddEntToHateList(other, 0, 0, bFrenzy); + else if (my_owner) { // I am a pet, add other to owner if it's NPC/LD + if (my_owner->IsAIControlled()) { + bool aggro_immunity = my_owner->GetSpecialAbility(SpecialAbility::AggroImmunity); + bool bot_aggro_immunity = my_owner->IsBot() && GetSpecialAbility(SpecialAbility::BotAggroImmunity); + bool client_aggro_immunity = my_owner->IsClient() && GetSpecialAbility(SpecialAbility::ClientAggroImmunity); + bool npc_aggro_immunity = my_owner->IsNPC() && GetSpecialAbility(SpecialAbility::NPCAggroImmunity); + bool can_add_to_hatelist = !aggro_immunity && + !bot_aggro_immunity && + !client_aggro_immunity && + !npc_aggro_immunity; + + if (can_add_to_hatelist) { + my_owner->hate_list.AddEntToHateList(other, 0, 0, bFrenzy); + } } } @@ -3339,7 +3296,7 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo entity_list.AddTempPetsToHateList(this, other, bFrenzy); } - if (!wasengaged) { + if (!was_engaged) { if (IsNPC() && other->IsClient() && other->CastToClient()) { if (parse->HasQuestSub(GetNPCTypeID(), EVENT_AGGRO)) { parse->EventNPC(EVENT_AGGRO, CastToNPC(), other, "", 0); @@ -3422,15 +3379,15 @@ void Mob::DamageShield(Mob* attacker, bool spell_ds) { attacker->Damage(this, -DS, spellid, EQ::skills::SkillAbjuration/*hackish*/, false); //we can assume there is a spell now - auto outapp = new EQApplicationPacket(OP_Damage, sizeof(CombatDamage_Struct)); - CombatDamage_Struct* cds = (CombatDamage_Struct*)outapp->pBuffer; - cds->target = attacker->GetID(); - cds->source = GetID(); - cds->type = spellbonuses.DamageShieldType; - cds->spellid = 0x0; - cds->damage = DS; - entity_list.QueueCloseClients(this, outapp); - safe_delete(outapp); + + static EQApplicationPacket p(OP_Damage, sizeof(CombatDamage_Struct)); + auto b = (CombatDamage_Struct *) p.pBuffer; + b->target = attacker->GetID(); + b->source = GetID(); + b->type = spellbonuses.DamageShieldType; + b->spellid = 0x0; + b->damage = DS; + entity_list.QueueCloseClients(this, &p); } else if (DS > 0 && !spell_ds) { //we are healing the attacker... @@ -3457,7 +3414,7 @@ void Mob::DamageShield(Mob* attacker, bool spell_ds) { uint8 Mob::GetWeaponDamageBonus(const EQ::ItemData *weapon, bool offhand) { // dev quote with old and new formulas - // https://forums.daybreakgames.com/eq/index.php?threads/test-update-09-17-15.226618/page-5#post-3326194 + // https://web.archive.org/web/20250816013618/https://forums.everquest.com/index.php?threads/test-update-09-17-15.226618/page-5#post-3326194 // // We assume that the level check is done before calling this function and sinister strikes is checked before // calling for offhand DB @@ -3588,7 +3545,7 @@ int Mob::GetHandToHandDelay(void) int iksar = 0; if (IsClient() && CastToClient()->GetItemIDAt(12) == 10652 && GetLevel() > 46) epic = 280; - else if (GetRace() == IKSAR) + else if (GetRace() == Race::Iksar) iksar = 1; // the delay bonus from the monk epic scales up to a skill of 280 if (epic >= skill) @@ -3629,8 +3586,8 @@ int Mob::GetHandToHandDelay(void) return 16; int level = GetLevel(); if (level > 62) - return GetRace() == IKSAR ? 21 : 20; - return GetRace() == IKSAR ? mnk_iks_delay[level] : mnk_hum_delay[level]; + return GetRace() == Race::Iksar ? 21 : 20; + return GetRace() == Race::Iksar ? mnk_iks_delay[level] : mnk_hum_delay[level]; } else if (GetClass() == Class::Beastlord) { int level = GetLevel(); @@ -3721,7 +3678,7 @@ int64 Mob::ReduceDamage(int64 damage) return DMG_RUNE; if (spellbonuses.MeleeRune[SBIndex::RUNE_AMOUNT] && spellbonuses.MeleeRune[SBIndex::RUNE_BUFFSLOT] >= 0) - damage = RuneAbsorb(damage, SE_Rune); + damage = RuneAbsorb(damage, SpellEffect::Rune); if (damage < 1) return DMG_RUNE; @@ -3848,10 +3805,10 @@ int64 Mob::AffectMagicalDamage(int64 damage, uint16 spell_id, const bool iBuffTi //Regular runes absorb spell damage (except dots) - Confirmed on live. if (spellbonuses.MeleeRune[SBIndex::RUNE_AMOUNT] && spellbonuses.MeleeRune[SBIndex::RUNE_BUFFSLOT] >= 0) - damage = RuneAbsorb(damage, SE_Rune); + damage = RuneAbsorb(damage, SpellEffect::Rune); if (spellbonuses.AbsorbMagicAtt[SBIndex::RUNE_AMOUNT] && spellbonuses.AbsorbMagicAtt[SBIndex::RUNE_BUFFSLOT] >= 0) - damage = RuneAbsorb(damage, SE_AbsorbMagicAtt); + damage = RuneAbsorb(damage, SpellEffect::AbsorbMagicAtt); if (damage < 1) return 0; @@ -4156,11 +4113,11 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons if (IsPet()) { Mob *owner = GetOwner(); if (owner && owner->IsClient()) { - if (GetPetOrder() == SPO_Sit) { + if (GetPetOrder() == PetOrder::Sit) { SetPetOrder(GetPreviousPetOrder()); } // fix GUI sit button to be unpressed and stop sitting regen - owner->CastToClient()->SetPetCommandState(PET_BUTTON_SIT, 0); + owner->CastToClient()->SetPetCommandState(PetButton::Sit, PetButtonState::Off); SetAppearance(eaStanding); } } @@ -4190,12 +4147,12 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons if (IsClient() && !pet->IsPetStop()) { // if pet was sitting his new mode is previous setting of // follow or guard after the battle (live verified) - if (pet->GetPetOrder() == SPO_Sit) { + if (pet->GetPetOrder() == PetOrder::Sit) { pet->SetPetOrder(pet->GetPreviousPetOrder()); } // fix GUI sit button to be unpressed and stop sitting regen - CastToClient()->SetPetCommandState(PET_BUTTON_SIT, 0); + CastToClient()->SetPetCommandState(PetButton::Sit, PetButtonState::Off); pet->SetAppearance(eaStanding); } @@ -4217,7 +4174,7 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons damage = ReduceDamage(damage); LogCombat("Melee Damage reduced to [{}]", damage); damage = ReduceAllDamage(damage); - TryTriggerThreshHold(damage, SE_TriggerMeleeThreshold, attacker); + TryTriggerThreshHold(damage, SpellEffect::TriggerMeleeThreshold, attacker); CheckNumHitsRemaining(NumHit::IncomingHitSuccess); } @@ -4242,7 +4199,7 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons Message(263, "%s tries to cast on YOU, but YOUR magical skin absorbs the spell.", attacker->GetCleanName()); } damage = ReduceAllDamage(damage); - TryTriggerThreshHold(damage, SE_TriggerSpellThreshold, attacker); + TryTriggerThreshHold(damage, SpellEffect::TriggerSpellThreshold, attacker); } if (IsClient() && CastToClient()->sneaking) { @@ -4266,10 +4223,7 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons if (attacker) { args = { this }; - parse->EventMob( - EVENT_DAMAGE_GIVEN, - attacker, - this, + parse->EventMob(EVENT_DAMAGE_GIVEN, attacker, this, [&]() { return fmt::format( "{} {} {} {} {} {} {} {} {}", @@ -4284,17 +4238,13 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons static_cast(special) ); }, - 0, - &args + 0, &args ); } args = { attacker }; - damage_override = parse->EventMob( - EVENT_DAMAGE_TAKEN, - this, - attacker, + damage_override = parse->EventMob(EVENT_DAMAGE_TAKEN, this, attacker, [&]() { return fmt::format( "{} {} {} {} {} {} {} {} {}", @@ -4309,8 +4259,7 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons static_cast(special) ); }, - 0, - &args + 0, &args ); if (damage_override > 0) { @@ -4365,7 +4314,7 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons GetCleanName(), /* Message1 */ attacker->GetCleanName() /* Message2 */ ); - BuffFadeByEffect(SE_Mez); + BuffFadeByEffect(SpellEffect::Mez); } // broken up for readability @@ -4450,12 +4399,12 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons int bashsave_roll = zone->random.Int(0, 100); if (bashsave_roll > 98 || bashsave_roll > (55 - stunbash_chance)) { // did stun -- roll other resists - // SE_FrontalStunResist description says any angle now a days + // SpellEffect::FrontalStunResist description says any angle now a days int stun_resist2 = spellbonuses.FrontalStunResist + itembonuses.FrontalStunResist + aabonuses.FrontalStunResist; if (zone->random.Int(1, 100) > stun_resist2) { // stun resist 2 failed - // time to check SE_StunResist and mod2 stun resist + // time to check SpellEffect::StunResist and mod2 stun resist int stun_resist = spellbonuses.StunResist + itembonuses.StunResist + aabonuses.StunResist; if (zone->random.Int(0, 100) >= stun_resist) { @@ -4555,8 +4504,8 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons //send damage packet... if (!iBuffTic) { //buff ticks do not send damage, instead they just call SendHPUpdate(), which is done above - auto outapp = new EQApplicationPacket(OP_Damage, sizeof(CombatDamage_Struct)); - CombatDamage_Struct* a = (CombatDamage_Struct*)outapp->pBuffer; + static EQApplicationPacket p(OP_Damage, sizeof(CombatDamage_Struct)); + auto a = (CombatDamage_Struct *) p.pBuffer; a->target = GetID(); if (!attacker) { @@ -4637,7 +4586,7 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons if (!FromDamageShield) { entity_list.QueueCloseClients( attacker, /* Sender */ - outapp, /* packet */ + &p, /* packet */ false, /* Skip Sender */ ((IsValidSpell(spell_id)) ? RuleI(Range, SpellMessages) : RuleI(Range, DamageMessages)), 0, /* don't skip anyone on spell */ @@ -4711,11 +4660,11 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons filter = FilterMyMisses; if (attacker->IsClient()) { - attacker->CastToClient()->QueuePacket(outapp, true, CLIENT_CONNECTED, filter); + attacker->CastToClient()->QueuePacket(&p, true, CLIENT_CONNECTED, filter); } else { entity_list.QueueCloseClients( attacker, /* Sender */ - outapp, /* packet */ + &p, /* packet */ false, /* Skip Sender */ ( IsValidSpell(spell_id) ? @@ -4770,7 +4719,7 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons a->type = DamageTypeSpell; entity_list.QueueCloseClients( this, /* Sender */ - outapp, /* packet */ + &p, /* packet */ false, /* Skip Sender */ range, /* distance packet travels at the speed of sound */ 0, /* don't skip anyone on spell */ @@ -4781,7 +4730,7 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons else { //I dont think any filters apply to damage affecting us if (IsClient()) { - CastToClient()->QueuePacket(outapp); + CastToClient()->QueuePacket(&p); } // Send normal message to observers @@ -4791,7 +4740,7 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons if (!owner || (owner && !owner->IsClient())) { entity_list.QueueCloseClients( this, /* Sender */ - outapp, /* packet */ + &p, /* packet */ true, /* Skip Sender */ range, /* distance packet travels at the speed of sound */ (IsValidSpell(spell_id) && skill_used != EQ::skills::SkillTigerClaw) ? 0 : skip, @@ -4801,8 +4750,6 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons } } } - - safe_delete(outapp); } else { //else, it is a buff tic... @@ -5721,7 +5668,7 @@ void Mob::DoRiposte(Mob *defender) defender->itembonuses.DoubleRiposte; if (DoubleRipChance && zone->random.Roll(DoubleRipChance)) { - LogCombat("Preforming a double riposted from SE_DoubleRiposte ([{}] percent chance)", DoubleRipChance); + LogCombat("Preforming a double riposted from SpellEffect::DoubleRiposte ([{}] percent chance)", DoubleRipChance); defender->Attack(this, EQ::invslot::slotPrimary, true); if (HasDied()) return; @@ -5732,7 +5679,7 @@ void Mob::DoRiposte(Mob *defender) // Live AA - Double Riposte if (DoubleRipChance && zone->random.Roll(DoubleRipChance)) { - LogCombat("Preforming a double riposted from SE_GiveDoubleRiposte base1 == 0 ([{}] percent chance)", DoubleRipChance); + LogCombat("Preforming a double riposted from SpellEffect::GiveDoubleRiposte base1 == 0 ([{}] percent chance)", DoubleRipChance); defender->Attack(this, EQ::invslot::slotPrimary, true); if (HasDied()) return; @@ -6080,12 +6027,12 @@ void Mob::TrySkillProc(Mob *on, EQ::skills::SkillType skill, uint16 ReuseTime, b for (int i = 0; i < EFFECT_COUNT; i++) { - if (spells[base_spell_id].effect_id[i] == SE_SkillProcAttempt || spells[base_spell_id].effect_id[i] == SE_SkillProcSuccess) { + if (spells[base_spell_id].effect_id[i] == SpellEffect::SkillProcAttempt || spells[base_spell_id].effect_id[i] == SpellEffect::SkillProcSuccess) { proc_spell_id = spells[base_spell_id].base_value[i]; ProcMod = static_cast(spells[base_spell_id].limit_value[i]); } - else if (spells[base_spell_id].effect_id[i] == SE_LimitToSkill && spells[base_spell_id].base_value[i] <= EQ::skills::HIGHEST_SKILL) { + else if (spells[base_spell_id].effect_id[i] == SpellEffect::LimitToSkill && spells[base_spell_id].base_value[i] <= EQ::skills::HIGHEST_SKILL) { if (CanProc && spells[base_spell_id].base_value[i] == skill && IsValidSpell(proc_spell_id)) { float final_chance = chance * (ProcMod / 100.0f); if (zone->random.Roll(final_chance)) { @@ -6124,12 +6071,12 @@ void Mob::TrySkillProc(Mob *on, EQ::skills::SkillType skill, uint16 ReuseTime, b ProcMod = 0; for (int i = 0; i < EFFECT_COUNT; i++) { - if (spells[base_spell_id].effect_id[i] == SE_SkillProcAttempt || spells[base_spell_id].effect_id[i] == SE_SkillProcSuccess) { + if (spells[base_spell_id].effect_id[i] == SpellEffect::SkillProcAttempt || spells[base_spell_id].effect_id[i] == SpellEffect::SkillProcSuccess) { proc_spell_id = spells[base_spell_id].base_value[i]; ProcMod = static_cast(spells[base_spell_id].limit_value[i]); } - else if (spells[base_spell_id].effect_id[i] == SE_LimitToSkill && spells[base_spell_id].base_value[i] <= EQ::skills::HIGHEST_SKILL) { + else if (spells[base_spell_id].effect_id[i] == SpellEffect::LimitToSkill && spells[base_spell_id].base_value[i] <= EQ::skills::HIGHEST_SKILL) { if (CanProc && spells[base_spell_id].base_value[i] == skill && IsValidSpell(proc_spell_id)) { float final_chance = chance * (ProcMod / 100.0f); @@ -6186,11 +6133,11 @@ void Mob::TrySkillProc(Mob *on, EQ::skills::SkillType skill, uint16 ReuseTime, b limit_value = effect.limit_value; slot = effect.slot; - if (effect_id == SE_SkillProcAttempt || effect_id == SE_SkillProcSuccess) { + if (effect_id == SpellEffect::SkillProcAttempt || effect_id == SpellEffect::SkillProcSuccess) { proc_spell_id = base_value; ProcMod = static_cast(limit_value); } - else if (effect_id == SE_LimitToSkill && base_value <= EQ::skills::HIGHEST_SKILL) { + else if (effect_id == SpellEffect::LimitToSkill && base_value <= EQ::skills::HIGHEST_SKILL) { if (CanProc && base_value == skill && IsValidSpell(proc_spell_id)) { float final_chance = chance * (ProcMod / 100.0f); @@ -6353,7 +6300,7 @@ bool Mob::TryRootFadeByDamage(int buffslot, Mob* attacker) { int32 Mob::RuneAbsorb(int64 damage, uint16 type) { uint32 buff_max = GetMaxTotalSlots(); - if (type == SE_Rune) { + if (type == SpellEffect::Rune) { for (uint32 slot = 0; slot < buff_max; slot++) { if (slot == spellbonuses.MeleeRune[SBIndex::RUNE_BUFFSLOT] && spellbonuses.MeleeRune[SBIndex::RUNE_AMOUNT] && buffs[slot].melee_rune && IsValidSpell(buffs[slot].spellid)) { int melee_rune_left = buffs[slot].melee_rune; @@ -6456,8 +6403,10 @@ void Mob::CommonOutgoingHitSuccess(Mob* defender, DamageHitInfo &hit, ExtraAttac } else { int ass = TryAssassinate(defender, hit.skill); - if (ass > 0) + + if (ass > 0) { hit.damage_done = ass; + } } } else if (hit.skill == EQ::skills::SkillFrenzy && GetClass() == Class::Berserker && GetLevel() > 50) { @@ -6502,7 +6451,7 @@ void Mob::CommonOutgoingHitSuccess(Mob* defender, DamageHitInfo &hit, ExtraAttac int mod = GetSpecialAbilityParam(SpecialAbility::Rampage, 2); if (mod > 0) spec_mod = mod; - if ((IsPet() || IsTempPet()) && IsPetOwnerClient()) { + if ((IsPet() || IsTempPet()) && IsPetOwnerOfClientBot()) { //SE_PC_Pet_Rampage SPA 464 on pet, damage modifier int spell_mod = spellbonuses.PC_Pet_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] + itembonuses.PC_Pet_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] + aabonuses.PC_Pet_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD]; if (spell_mod > spec_mod) @@ -6513,7 +6462,7 @@ void Mob::CommonOutgoingHitSuccess(Mob* defender, DamageHitInfo &hit, ExtraAttac int mod = GetSpecialAbilityParam(SpecialAbility::AreaRampage, 2); if (mod > 0) spec_mod = mod; - if ((IsPet() || IsTempPet()) && IsPetOwnerClient()) { + if ((IsPet() || IsTempPet()) && IsPetOwnerOfClientBot()) { //SE_PC_Pet_AE_Rampage SPA 465 on pet, damage modifier int spell_mod = spellbonuses.PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] + itembonuses.PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] + aabonuses.PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD]; if (spell_mod > spec_mod) @@ -6595,7 +6544,7 @@ void Mob::DoShieldDamageOnShielderSpellEffect(Mob* shield_target, int64 hit_dama return; } /* - SPA 463 SE_SHIELD_TARGET + SPA 463 SpellEffect::SHIELD_TARGET Live description: "Shields your target, taking a percentage of their damage". Only example spell on live is an NPC who uses it during a raid event "Laurion's Song" expansion. SPA 54492 'Guardian Stance' Described as 100% Melee Shielding @@ -6647,7 +6596,7 @@ void Mob::CommonBreakInvisibleFromCombat() CancelSneakHide(); if (spellbonuses.NegateIfCombat) { - BuffFadeByEffect(SE_NegateIfCombat); + BuffFadeByEffect(SpellEffect::NegateIfCombat); } hidden = false; @@ -6760,7 +6709,9 @@ void Client::SetAttackTimer() else speed = static_cast(speed + ((hhe / 100.0f) * delay)); } - TimerToUse->SetAtTrigger(std::max(RuleI(Combat, MinHastedDelay), speed), true, true); + + bool reinit = !TimerToUse->Enabled(); + TimerToUse->SetAtTrigger(std::max(RuleI(Combat, MinHastedDelay), speed), reinit, reinit); if (i == EQ::invslot::slotPrimary) { primary_speed = speed; @@ -6945,7 +6896,7 @@ void Mob::DoMainHandAttackRounds(Mob *target, ExtraAttackOptions *opts, bool ram Attack(target, EQ::invslot::slotPrimary, false, false, false, opts); if (CanThisClassDoubleAttack() && CheckDoubleAttack()) { Attack(target, EQ::invslot::slotPrimary, false, false, false, opts); - if ((IsPet() || IsTempPet()) && IsPetOwnerClient()) { + if ((IsPet() || IsTempPet()) && IsPetOwnerOfClientBot()) { int chance = spellbonuses.PC_Pet_Flurry + itembonuses.PC_Pet_Flurry + aabonuses.PC_Pet_Flurry; if (chance && zone->random.Roll(chance)) { Flurry(nullptr); @@ -7006,7 +6957,7 @@ void Mob::DoOffHandAttackRounds(Mob *target, ExtraAttackOptions *opts, bool ramp if (CanThisClassDoubleAttack() && GetLevel() > 35 && CheckDoubleAttack() && !rampage) { Attack(target, EQ::invslot::slotSecondary, false, false, false, opts); - if ((IsPet() || IsTempPet()) && IsPetOwnerClient()) { + if ((IsPet() || IsTempPet()) && IsPetOwnerOfClientBot()) { int chance = spellbonuses.PC_Pet_Flurry + itembonuses.PC_Pet_Flurry + aabonuses.PC_Pet_Flurry; if (chance && zone->random.Roll(chance)) { Flurry(nullptr); diff --git a/zone/aura.cpp b/zone/aura.cpp index f1285dc5e..a511b6082 100644 --- a/zone/aura.cpp +++ b/zone/aura.cpp @@ -9,7 +9,7 @@ Aura::Aura(NPCType *type_data, Mob *owner, AuraRecord &record) : NPC(type_data, 0, owner->GetPosition(), GravityBehavior::Flying), spell_id(record.spell_id), distance(record.distance), - remove_timer(record.duration), movement_timer(100), process_timer(1000), aura_id(-1) + remove_timer(record.duration), movement_timer(1000), process_timer(1000), aura_id(-1) { GiveNPCTypeData(type_data); // we will delete this later on m_owner = owner->GetID(); @@ -81,7 +81,7 @@ void Aura::ProcessOnAllFriendlies(Mob *owner) if (!mob) { continue; } - if (mob->IsClient() || mob->IsPetOwnerClient() || mob->IsMerc() || mob->IsBot()) { + if (mob->IsOfClientBotMerc() || mob->IsPetOwnerOfClientBot()) { auto it = casted_on.find(mob->GetID()); if (it != casted_on.end()) { // we are already on the list, let's check for removal @@ -131,7 +131,7 @@ void Aura::ProcessOnAllGroupMembers(Mob *owner) std::set delayed_remove; bool is_buff = IsBuffSpell(spell_id); // non-buff spells don't cast on enter - if (owner->IsRaidGrouped() && owner->IsClient()) { // currently raids are just client, but safety check + if (owner->IsRaidGrouped() && owner->IsOfClientBot()) { // currently raids are just client, but safety check auto raid = owner->GetRaid(); if (raid == nullptr) { // well shit owner->RemoveAura(GetID(), false, true); @@ -198,17 +198,17 @@ void Aura::ProcessOnAllGroupMembers(Mob *owner) auto it = casted_on.find(mob->GetID()); if (it != casted_on.end()) { // verify still good! - if (mob->IsClient()) { + if (mob->IsOfClientBot()) { if (!verify_raid_client(mob->CastToClient())) { delayed_remove.insert(mob->GetID()); } } - else if (mob->IsPet() && mob->IsPetOwnerClient() && mob->GetOwner()) { + else if (mob->IsPet() && mob->IsPetOwnerOfClientBot() && mob->GetOwner()) { if (!verify_raid_client_pet(mob)) { delayed_remove.insert(mob->GetID()); } } - else if (mob->IsNPC() && mob->IsPetOwnerClient()) { + else if (mob->IsNPC() && mob->IsPetOwnerOfClientBot()) { auto npc = mob->CastToNPC(); if (!verify_raid_client_swarm(npc)) { delayed_remove.insert(mob->GetID()); @@ -216,19 +216,19 @@ void Aura::ProcessOnAllGroupMembers(Mob *owner) } } else { // we're not on it! - if (mob->IsClient() && verify_raid_client(mob->CastToClient())) { + if (mob->IsOfClientBot() && verify_raid_client(mob->CastToClient())) { casted_on.insert(mob->GetID()); if (is_buff) { SpellFinished(spell_id, mob); } } - else if (mob->IsPet() && mob->IsPetOwnerClient() && mob->GetOwner() && verify_raid_client_pet(mob)) { + else if (mob->IsPet() && mob->IsPetOwnerOfClientBot() && mob->GetOwner() && verify_raid_client_pet(mob)) { casted_on.insert(mob->GetID()); if (is_buff) { SpellFinished(spell_id, mob); } } - else if (mob->IsNPC() && mob->IsPetOwnerClient()) { + else if (mob->IsNPC() && mob->IsPetOwnerOfClientBot()) { auto npc = mob->CastToNPC(); if (verify_raid_client_swarm(npc)) { casted_on.insert(mob->GetID()); @@ -376,7 +376,7 @@ void Aura::ProcessOnGroupMembersPets(Mob *owner) auto group_member = owner->GetOwnerOrSelf(); if (group_member->IsRaidGrouped() && - group_member->IsClient()) { // currently raids are just client, but safety check + group_member->IsOfClientBot()) { // currently raids are just client, but safety check auto raid = group_member->GetRaid(); if (raid == nullptr) { // well shit owner->RemoveAura(GetID(), false, true); @@ -428,12 +428,12 @@ void Aura::ProcessOnGroupMembersPets(Mob *owner) auto it = casted_on.find(mob->GetID()); if (it != casted_on.end()) { // verify still good! - if (mob->IsPet() && mob->IsPetOwnerClient() && mob->GetOwner()) { + if (mob->IsPet() && mob->IsPetOwnerOfClientBot() && mob->GetOwner()) { if (!verify_raid_client_pet(mob)) { delayed_remove.insert(mob->GetID()); } } - else if (mob->IsNPC() && mob->IsPetOwnerClient()) { + else if (mob->IsNPC() && mob->IsPetOwnerOfClientBot()) { auto npc = mob->CastToNPC(); if (!verify_raid_client_swarm(npc)) { delayed_remove.insert(mob->GetID()); @@ -441,16 +441,16 @@ void Aura::ProcessOnGroupMembersPets(Mob *owner) } } else { // we're not on it! - if (mob->IsClient()) { + if (mob->IsOfClientBot()) { continue; // never hit client } - else if (mob->IsPet() && mob->IsPetOwnerClient() && mob->GetOwner() && verify_raid_client_pet(mob)) { + else if (mob->IsPet() && mob->IsPetOwnerOfClientBot() && mob->GetOwner() && verify_raid_client_pet(mob)) { casted_on.insert(mob->GetID()); if (is_buff) { SpellFinished(spell_id, mob); } } - else if (mob->IsNPC() && mob->IsPetOwnerClient()) { + else if (mob->IsNPC() && mob->IsPetOwnerOfClientBot()) { auto npc = mob->CastToNPC(); if (verify_raid_client_swarm(npc)) { casted_on.insert(mob->GetID()); @@ -499,7 +499,7 @@ void Aura::ProcessOnGroupMembersPets(Mob *owner) } } else { // not on, check if we should be! - if (mob->IsClient()) { + if (mob->IsOfClientBot()) { continue; } else if (mob->IsPet() && verify_group_pet(mob)) { @@ -696,6 +696,10 @@ void Aura::ProcessSpawns() auto c = e.second->CastToClient(); + if (!c) { + continue; + } + bool spawned = spawned_for.find(c->GetID()) != spawned_for.end(); if (ShouldISpawnFor(c)) { if (!spawned) { @@ -735,21 +739,22 @@ bool Aura::Process() if (movement_type == AuraMovement::Follow && GetPosition() != owner->GetPosition() && movement_timer.Check()) { m_Position = owner->GetPosition(); - auto app = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); - auto spu = (PlayerPositionUpdateServer_Struct *) app->pBuffer; + + static EQApplicationPacket packet(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); + auto spu = (PlayerPositionUpdateServer_Struct *) packet.pBuffer; + MakeSpawnUpdate(spu); auto it = spawned_for.begin(); while (it != spawned_for.end()) { auto client = entity_list.GetClientByID(*it); if (client) { - client->QueuePacket(app); + client->QueuePacket(&packet); ++it; } else { it = spawned_for.erase(it); } } - safe_delete(app); } // TODO: waypoints? @@ -800,7 +805,7 @@ bool Aura::ShouldISpawnFor(Client *c) return true; } - if (owner->IsRaidGrouped() && owner->IsClient()) { + if (owner->IsRaidGrouped() && owner->IsOfClientBot()) { auto raid = owner->GetRaid(); if (raid == nullptr) { return false; diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index 6aa16bbe6..0cc44cac3 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -52,7 +52,7 @@ void Mob::CalcBonuses() We set this here because NPC's can cast spells to change walkspeed/runspeed */ float get_walk_speed = static_cast(0.025f * GetWalkspeed()); - rooted = FindType(SE_Root); + rooted = FindType(SpellEffect::Root); } void NPC::CalcBonuses() @@ -108,7 +108,7 @@ void Client::CalcBonuses() SetAttackTimer(); - rooted = FindType(SE_Root); + rooted = FindType(SpellEffect::Root); XPRate = 100 + spellbonuses.XPRateMod; @@ -281,443 +281,233 @@ void Mob::AddItemBonuses(const EQ::ItemInstance* inst, StatBonuses* b, bool is_a return; } - if (!is_ammo_item) { - const auto recommended_level = is_augment ? recommended_level_override : inst->GetItemRecommendedLevel(true); + if (is_ammo_item) { + return; + } - if (IsNPC() || current_level >= recommended_level) { - b->HP += item->HP; - b->Mana += item->Mana; - b->Endurance += item->Endur; + const auto recommended_level = is_augment ? recommended_level_override : inst->GetItemRecommendedLevel(true); + const bool meets_recommended = (IsNPC() && !RuleB(Items, NPCUseRecommendedLevels)) || current_level >= recommended_level; - b->AC += item->AC; - b->ATK += item->Attack; + auto CalcItemBonus = [&](int statValue) -> int { + return meets_recommended ? statValue : CalcRecommendedLevelBonus(current_level, recommended_level, statValue); + }; - b->STR += (item->AStr + item->HeroicStr); - b->STA += (item->ASta + item->HeroicSta); - b->DEX += (item->ADex + item->HeroicDex); - b->AGI += (item->AAgi + item->HeroicAgi); - b->INT += (item->AInt + item->HeroicInt); - b->WIS += (item->AWis + item->HeroicWis); - b->CHA += (item->ACha + item->HeroicCha); + auto CalcCappedItemBonus = [&](int currentStat, int bonus, int cap) -> int { + int calc_stat = currentStat + CalcItemBonus(bonus); + return IsOfClientBotMerc() ? std::min(cap, calc_stat) : calc_stat; + }; - b->MR += (item->MR + item->HeroicMR); - b->FR += (item->FR + item->HeroicFR); - b->CR += (item->CR + item->HeroicCR); - b->PR += (item->PR + item->HeroicPR); - b->DR += (item->DR + item->HeroicDR); - b->Corrup += (item->SVCorruption + item->HeroicSVCorrup); + b->HP += CalcItemBonus(item->HP); + b->Mana += CalcItemBonus(item->Mana); + b->Endurance += CalcItemBonus(item->Endur); + b->AC += CalcItemBonus(item->AC); - 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->STR += CalcItemBonus(item->AStr + item->HeroicStr); + b->STA += CalcItemBonus(item->ASta + item->HeroicSta); + b->DEX += CalcItemBonus(item->ADex + item->HeroicDex); + b->AGI += CalcItemBonus(item->AAgi + item->HeroicAgi); + b->INT += CalcItemBonus(item->AInt + item->HeroicInt); + b->WIS += CalcItemBonus(item->AWis + item->HeroicWis); + b->CHA += CalcItemBonus(item->ACha + item->HeroicCha); - b->MRCapMod += item->HeroicMR; - b->CRCapMod += item->HeroicFR; - b->FRCapMod += item->HeroicCR; - b->PRCapMod += item->HeroicPR; - b->DRCapMod += item->HeroicDR; - b->CorrupCapMod += item->HeroicSVCorrup; + b->HeroicSTR += CalcItemBonus(item->HeroicStr); + b->HeroicSTA += CalcItemBonus(item->HeroicSta); + b->HeroicDEX += CalcItemBonus(item->HeroicDex); + b->HeroicAGI += CalcItemBonus(item->HeroicAgi); + b->HeroicINT += CalcItemBonus(item->HeroicInt); + b->HeroicWIS += CalcItemBonus(item->HeroicWis); + b->HeroicCHA += CalcItemBonus(item->HeroicCha); - b->HeroicSTR += item->HeroicStr; - b->HeroicSTA += item->HeroicSta; - b->HeroicDEX += item->HeroicDex; - b->HeroicAGI += item->HeroicAgi; - b->HeroicINT += item->HeroicInt; - b->HeroicWIS += item->HeroicWis; - b->HeroicCHA += item->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->HeroicMR += item->HeroicMR; - b->HeroicFR += item->HeroicFR; - b->HeroicCR += item->HeroicCR; - b->HeroicPR += item->HeroicPR; - b->HeroicDR += item->HeroicDR; - b->HeroicCorrup += item->HeroicSVCorrup; + b->MR += CalcItemBonus(item->MR + item->HeroicMR); + b->FR += CalcItemBonus(item->FR + item->HeroicFR); + b->CR += CalcItemBonus(item->CR + item->HeroicCR); + b->PR += CalcItemBonus(item->PR + item->HeroicPR); + b->DR += CalcItemBonus(item->DR + item->HeroicDR); + b->Corrup += CalcItemBonus(item->SVCorruption + item->HeroicSVCorrup); + + b->HeroicMR += CalcItemBonus(item->HeroicMR); + b->HeroicFR += CalcItemBonus(item->HeroicFR); + b->HeroicCR += CalcItemBonus(item->HeroicCR); + b->HeroicPR += CalcItemBonus(item->HeroicPR); + b->HeroicDR += CalcItemBonus(item->HeroicDR); + b->HeroicCorrup += CalcItemBonus(item->HeroicSVCorrup); + + 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); + b->EnduranceRegen += CalcItemBonus(item->EnduranceRegen); + + // These have rule-configured caps. + b->ATK = CalcCappedItemBonus(b->ATK, item->Attack, RuleI(Character, ItemATKCap) + itembonuses.ItemATKCap + spellbonuses.ItemATKCap + aabonuses.ItemATKCap); + b->DamageShield = CalcCappedItemBonus(b->DamageShield, item->DamageShield, RuleI(Character, ItemDamageShieldCap)); + b->SpellShield = CalcCappedItemBonus(b->SpellShield, item->SpellShield, RuleI(Character, ItemSpellShieldingCap)); + b->MeleeMitigation = CalcCappedItemBonus(b->MeleeMitigation, item->Shielding, RuleI(Character, ItemShieldingCap)); + b->StunResist = CalcCappedItemBonus(b->StunResist, item->StunResist, RuleI(Character, ItemStunResistCap)); + b->StrikeThrough = CalcCappedItemBonus(b->StrikeThrough, item->StrikeThrough, RuleI(Character, ItemStrikethroughCap)); + b->AvoidMeleeChance = CalcCappedItemBonus(b->AvoidMeleeChance, item->Avoidance, RuleI(Character, ItemAvoidanceCap)); + b->HitChance = CalcCappedItemBonus(b->HitChance, item->Accuracy, RuleI(Character, ItemAccuracyCap)); + b->ProcChance = CalcCappedItemBonus(b->ProcChance, item->CombatEffects, RuleI(Character, ItemCombatEffectsCap)); + b->DoTShielding = CalcCappedItemBonus(b->DoTShielding, item->DotShielding, RuleI(Character, ItemDoTShieldingCap)); + b->HealAmt = CalcCappedItemBonus(b->HealAmt, item->HealAmt, RuleI(Character, ItemHealAmtCap)); + b->SpellDmg = CalcCappedItemBonus(b->SpellDmg, item->SpellDmg, RuleI(Character, ItemSpellDmgCap)); + b->Clairvoyance = CalcCappedItemBonus(b->Clairvoyance, item->Clairvoyance, RuleI(Character, ItemClairvoyanceCap)); + b->DSMitigation = CalcCappedItemBonus(b->DSMitigation, item->DSMitigation, RuleI(Character, ItemDSMitigationCap)); + + if (b->haste < item->Haste) { + b->haste = item->Haste; + } + + if (item->ExtraDmgAmt != 0 && item->ExtraDmgSkill <= EQ::skills::HIGHEST_SKILL) { + if (item->ExtraDmgSkill == ALL_SKILLS) { + for (const auto &skill_id: EQ::skills::GetExtraDamageSkills()) { + b->SkillDamageAmount[skill_id] = CalcCappedItemBonus(b->SkillDamageAmount[skill_id], item->ExtraDmgAmt, RuleI(Character, ItemExtraDmgCap)); + } } else { - b->HP += CalcRecommendedLevelBonus(current_level, recommended_level, item->HP); - b->Mana += CalcRecommendedLevelBonus(current_level, recommended_level, item->Mana); - b->Endurance += CalcRecommendedLevelBonus(current_level, recommended_level, item->Endur); - - b->AC += CalcRecommendedLevelBonus(current_level, recommended_level, item->AC); - b->ATK += CalcRecommendedLevelBonus(current_level, recommended_level, item->Attack); - - b->STR += CalcRecommendedLevelBonus(current_level, recommended_level, (item->AStr + item->HeroicStr)); - b->STA += CalcRecommendedLevelBonus(current_level, recommended_level, (item->ASta + item->HeroicSta)); - b->DEX += CalcRecommendedLevelBonus(current_level, recommended_level, (item->ADex + item->HeroicDex)); - b->AGI += CalcRecommendedLevelBonus(current_level, recommended_level, (item->AAgi + item->HeroicAgi)); - b->INT += CalcRecommendedLevelBonus(current_level, recommended_level, (item->AInt + item->HeroicInt)); - b->WIS += CalcRecommendedLevelBonus(current_level, recommended_level, (item->AWis + item->HeroicWis)); - b->CHA += CalcRecommendedLevelBonus(current_level, recommended_level, (item->ACha + item->HeroicCha)); - - b->MR += CalcRecommendedLevelBonus(current_level, recommended_level, (item->MR + item->HeroicMR)); - b->FR += CalcRecommendedLevelBonus(current_level, recommended_level, (item->FR + item->HeroicFR)); - b->CR += CalcRecommendedLevelBonus(current_level, recommended_level, (item->CR + item->HeroicCR)); - b->PR += CalcRecommendedLevelBonus(current_level, recommended_level, (item->PR + item->HeroicPR)); - b->DR += CalcRecommendedLevelBonus(current_level, recommended_level, (item->DR + item->HeroicDR)); - b->Corrup += CalcRecommendedLevelBonus(current_level, recommended_level, (item->SVCorruption + item->HeroicSVCorrup)); - - b->STRCapMod += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicStr); - b->STACapMod += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicSta); - b->DEXCapMod += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicDex); - b->AGICapMod += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicAgi); - b->INTCapMod += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicInt); - b->WISCapMod += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicWis); - b->CHACapMod += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicCha); - - b->MRCapMod += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicMR); - b->CRCapMod += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicFR); - b->FRCapMod += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicCR); - b->PRCapMod += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicPR); - b->DRCapMod += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicDR); - b->CorrupCapMod += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicSVCorrup); - - b->HeroicSTR += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicStr); - b->HeroicSTA += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicSta); - b->HeroicDEX += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicDex); - b->HeroicAGI += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicAgi); - b->HeroicINT += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicInt); - b->HeroicWIS += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicWis); - b->HeroicCHA += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicCha); - - b->HeroicMR += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicMR); - b->HeroicFR += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicFR); - b->HeroicCR += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicCR); - b->HeroicPR += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicPR); - b->HeroicDR += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicDR); - b->HeroicCorrup += CalcRecommendedLevelBonus(current_level, recommended_level, item->HeroicSVCorrup); + b->SkillDamageAmount[item->ExtraDmgSkill] = CalcCappedItemBonus(b->SkillDamageAmount[item->ExtraDmgSkill], item->ExtraDmgAmt, RuleI(Character, ItemExtraDmgCap)); } + } - if (b->haste < item->Haste) { - b->haste = item->Haste; + if (item->Worn.Effect > 0 && item->Worn.Type == EQ::item::ItemEffectWorn) { + ApplySpellsBonuses(item->Worn.Effect, item->Worn.Level, b, 0, item->Worn.Type); + } + + if (item->Focus.Effect > 0 && item->Focus.Type == EQ::item::ItemEffectFocus) { + if ( + IsOfClientBotMerc() || + (IsNPC() && RuleB(Spells, NPC_UseFocusFromItems)) + ) { + ApplySpellsBonuses(item->Focus.Effect, item->Focus.Level, b, 0); } + } - if (item->Regen != 0) { - b->HPRegen += item->Regen; + switch (item->BardType) { + case EQ::item::ItemTypeAllInstrumentTypes: { // (e.g. Singing Short Sword) + if (item->BardValue > b->singingMod) { + b->singingMod = item->BardValue; + } + + if (item->BardValue > b->brassMod) { + b->brassMod = item->BardValue; + } + + if (item->BardValue > b->stringedMod) { + b->stringedMod = item->BardValue; + } + + if (item->BardValue > b->percussionMod) { + b->percussionMod = item->BardValue; + } + + if (item->BardValue > b->windMod) { + b->windMod = item->BardValue; + } + + break; } + case EQ::item::ItemTypeSinging: { + if (item->BardValue > b->singingMod) { + b->singingMod = item->BardValue; + } - if (item->ManaRegen != 0) { - b->ManaRegen += item->ManaRegen; + break; } + case EQ::item::ItemTypeWindInstrument: { + if (item->BardValue > b->windMod) { + b->windMod = item->BardValue; + } - if (item->EnduranceRegen != 0) { - b->EnduranceRegen += item->EnduranceRegen; + break; } + case EQ::item::ItemTypeStringedInstrument: { + if (item->BardValue > b->stringedMod) { + b->stringedMod = item->BardValue; + } - if (item->Attack != 0) { - unsigned int cap = RuleI(Character, ItemATKCap); - cap += itembonuses.ItemATKCap + spellbonuses.ItemATKCap + aabonuses.ItemATKCap; + break; + } + case EQ::item::ItemTypeBrassInstrument: { + if (item->BardValue > b->brassMod) { + b->brassMod = item->BardValue; + } - if ( - IsOfClientBotMerc() && - (b->ATK + item->Attack) > cap + break; + } + case EQ::item::ItemTypePercussionInstrument: { + if (item->BardValue > b->percussionMod) { + b->percussionMod = item->BardValue; + } + + break; + } + } + + if (item->SkillModValue != 0 && item->SkillModType <= EQ::skills::HIGHEST_SKILL) { + if ( + (item->SkillModValue > 0 && b->skillmod[item->SkillModType] < item->SkillModValue) || + (item->SkillModValue < 0 && b->skillmod[item->SkillModType] > item->SkillModValue) ) { - b->ATK = RuleI(Character, ItemATKCap); - } else { - b->ATK += item->Attack; - } + b->skillmod[item->SkillModType] = item->SkillModValue; } + } - if (item->DamageShield != 0) { - if ( - IsOfClientBotMerc() && - (b->DamageShield + item->DamageShield) > RuleI(Character, ItemDamageShieldCap) - ) { - b->DamageShield = RuleI(Character, ItemDamageShieldCap); - } else { - b->DamageShield += item->DamageShield; - } + if (item->FactionMod1) { + if (item->FactionAmt1 > 0 && item->FactionAmt1 > GetItemFactionBonus(item->FactionMod1)) { + AddItemFactionBonus(item->FactionMod1, item->FactionAmt1); + } else if (item->FactionAmt1 < 0 && item->FactionAmt1 < GetItemFactionBonus(item->FactionMod1)) { + AddItemFactionBonus(item->FactionMod1, item->FactionAmt1); } + } - if (item->SpellShield != 0) { - if ( - IsOfClientBotMerc() && - (b->SpellShield + item->SpellShield) > RuleI(Character, ItemSpellShieldingCap) - ) { - b->SpellShield = RuleI(Character, ItemSpellShieldingCap); - } else { - b->SpellShield += item->SpellShield; - } + if (item->FactionMod2) { + if (item->FactionAmt2 > 0 && item->FactionAmt2 > GetItemFactionBonus(item->FactionMod2)) { + AddItemFactionBonus(item->FactionMod2, item->FactionAmt2); + } else if (item->FactionAmt2 < 0 && item->FactionAmt2 < GetItemFactionBonus(item->FactionMod2)) { + AddItemFactionBonus(item->FactionMod2, item->FactionAmt2); } + } - if (item->Shielding != 0) { - if ( - IsOfClientBotMerc() && - (b->MeleeMitigation + item->Shielding) > RuleI(Character, ItemShieldingCap) - ) { - b->MeleeMitigation = RuleI(Character, ItemShieldingCap); - } else { - b->MeleeMitigation += item->Shielding; - } + if (item->FactionMod3) { + if (item->FactionAmt3 > 0 && item->FactionAmt3 > GetItemFactionBonus(item->FactionMod3)) { + AddItemFactionBonus(item->FactionMod3, item->FactionAmt3); + } else if (item->FactionAmt3 < 0 && item->FactionAmt3 < GetItemFactionBonus(item->FactionMod3)) { + AddItemFactionBonus(item->FactionMod3, item->FactionAmt3); } + } - if (item->StunResist != 0) { - if ( - IsOfClientBotMerc() && - (b->StunResist + item->StunResist) > RuleI(Character, ItemStunResistCap) - ) { - b->StunResist = RuleI(Character, ItemStunResistCap); - } else { - b->StunResist += item->StunResist; - } + if (item->FactionMod4) { + if (item->FactionAmt4 > 0 && item->FactionAmt4 > GetItemFactionBonus(item->FactionMod4)) { + AddItemFactionBonus(item->FactionMod4, item->FactionAmt4); + } else if (item->FactionAmt4 < 0 && item->FactionAmt4 < GetItemFactionBonus(item->FactionMod4)) { + AddItemFactionBonus(item->FactionMod4, item->FactionAmt4); } + } - if (item->StrikeThrough != 0) { - if ( - IsOfClientBotMerc() && - (b->StrikeThrough + item->StrikeThrough) > RuleI(Character, ItemStrikethroughCap) - ) { - b->StrikeThrough = RuleI(Character, ItemStrikethroughCap); - } else { - b->StrikeThrough += item->StrikeThrough; + if (!is_augment) { + for (int i = EQ::invaug::SOCKET_BEGIN; i <= EQ::invaug::SOCKET_END; i++) { + const auto* augment = inst->GetAugment(i); + if (!augment) { + continue; } - } - if (item->Avoidance != 0) { - if ( - IsOfClientBotMerc() && - (b->AvoidMeleeChance + item->Avoidance) > RuleI(Character, ItemAvoidanceCap) - ) { - b->AvoidMeleeChance = RuleI(Character, ItemAvoidanceCap); - } else { - b->AvoidMeleeChance += item->Avoidance; - } - } - - if (item->Accuracy != 0) { - if ( - IsOfClientBotMerc() && - (b->HitChance + item->Accuracy) > RuleI(Character, ItemAccuracyCap) - ) { - b->HitChance = RuleI(Character, ItemAccuracyCap); - } else { - b->HitChance += item->Accuracy; - } - } - - if (item->CombatEffects != 0) { - if ( - IsOfClientBotMerc() && - (b->ProcChance + item->CombatEffects) > RuleI(Character, ItemCombatEffectsCap) - ) { - b->ProcChance = RuleI(Character, ItemCombatEffectsCap); - } else { - b->ProcChance += item->CombatEffects; - } - } - - if (item->DotShielding != 0) { - if ( - IsOfClientBotMerc() && - (b->DoTShielding + item->DotShielding) > RuleI(Character, ItemDoTShieldingCap) - ) { - b->DoTShielding = RuleI(Character, ItemDoTShieldingCap); - } else { - b->DoTShielding += item->DotShielding; - } - } - - if (item->HealAmt != 0) { - if ( - IsOfClientBotMerc() && - (b->HealAmt + item->HealAmt) > RuleI(Character, ItemHealAmtCap) - ) { - b->HealAmt = RuleI(Character, ItemHealAmtCap); - } else { - b->HealAmt += item->HealAmt; - } - } - - if (item->SpellDmg != 0) { - if ( - IsOfClientBotMerc() && - (b->SpellDmg + item->SpellDmg) > RuleI(Character, ItemSpellDmgCap) - ) { - b->SpellDmg = RuleI(Character, ItemSpellDmgCap); - } else { - b->SpellDmg += item->SpellDmg; - } - } - - if (item->Clairvoyance != 0) { - if ( - IsOfClientBotMerc() && - (b->Clairvoyance + item->Clairvoyance) > RuleI(Character, ItemClairvoyanceCap) - ) { - b->Clairvoyance = RuleI(Character, ItemClairvoyanceCap); - } else { - b->Clairvoyance += item->Clairvoyance; - } - } - - if (item->DSMitigation != 0) { - if ( - IsOfClientBotMerc() && - (b->DSMitigation + item->DSMitigation) > RuleI(Character, ItemDSMitigationCap) - ) { - b->DSMitigation = RuleI(Character, ItemDSMitigationCap); - } else { - b->DSMitigation += item->DSMitigation; - } - } - - if (item->Worn.Effect > 0 && item->Worn.Type == EQ::item::ItemEffectWorn) { - ApplySpellsBonuses(item->Worn.Effect, item->Worn.Level, b, 0, item->Worn.Type); - } - - if (item->Focus.Effect > 0 && item->Focus.Type == EQ::item::ItemEffectFocus) { - if ( - IsOfClientBotMerc() || - (IsNPC() && RuleB(Spells, NPC_UseFocusFromItems)) - ) { - ApplySpellsBonuses(item->Focus.Effect, item->Focus.Level, b, 0); - } - } - - switch (item->BardType) { - case EQ::item::ItemTypeAllInstrumentTypes: { // (e.g. Singing Short Sword) - if (item->BardValue > b->singingMod) { - b->singingMod = item->BardValue; - } - - if (item->BardValue > b->brassMod) { - b->brassMod = item->BardValue; - } - - if (item->BardValue > b->stringedMod) { - b->stringedMod = item->BardValue; - } - - if (item->BardValue > b->percussionMod) { - b->percussionMod = item->BardValue; - } - - if (item->BardValue > b->windMod) { - b->windMod = item->BardValue; - } - - break; - } - case EQ::item::ItemTypeSinging: { - if (item->BardValue > b->singingMod) { - b->singingMod = item->BardValue; - } - - break; - } - case EQ::item::ItemTypeWindInstrument: { - if (item->BardValue > b->windMod) { - b->windMod = item->BardValue; - } - - break; - } - case EQ::item::ItemTypeStringedInstrument: { - if (item->BardValue > b->stringedMod) { - b->stringedMod = item->BardValue; - } - - break; - } - case EQ::item::ItemTypeBrassInstrument: { - if (item->BardValue > b->brassMod) { - b->brassMod = item->BardValue; - } - - break; - } - case EQ::item::ItemTypePercussionInstrument: { - if (item->BardValue > b->percussionMod) { - b->percussionMod = item->BardValue; - } - - break; - } - } - - if (item->SkillModValue != 0 && item->SkillModType <= EQ::skills::HIGHEST_SKILL) { - if ( - (item->SkillModValue > 0 && b->skillmod[item->SkillModType] < item->SkillModValue) || - (item->SkillModValue < 0 && b->skillmod[item->SkillModType] > item->SkillModValue) - ) { - b->skillmod[item->SkillModType] = item->SkillModValue; - } - } - - if (item->FactionMod1) { - if (item->FactionAmt1 > 0 && item->FactionAmt1 > GetItemFactionBonus(item->FactionMod1)) { - AddItemFactionBonus(item->FactionMod1, item->FactionAmt1); - } else if (item->FactionAmt1 < 0 && item->FactionAmt1 < GetItemFactionBonus(item->FactionMod1)) { - AddItemFactionBonus(item->FactionMod1, item->FactionAmt1); - } - } - - if (item->FactionMod2) { - if (item->FactionAmt2 > 0 && item->FactionAmt2 > GetItemFactionBonus(item->FactionMod2)) { - AddItemFactionBonus(item->FactionMod2, item->FactionAmt2); - } else if (item->FactionAmt2 < 0 && item->FactionAmt2 < GetItemFactionBonus(item->FactionMod2)) { - AddItemFactionBonus(item->FactionMod2, item->FactionAmt2); - } - } - - if (item->FactionMod3) { - if (item->FactionAmt3 > 0 && item->FactionAmt3 > GetItemFactionBonus(item->FactionMod3)) { - AddItemFactionBonus(item->FactionMod3, item->FactionAmt3); - } else if (item->FactionAmt3 < 0 && item->FactionAmt3 < GetItemFactionBonus(item->FactionMod3)) { - AddItemFactionBonus(item->FactionMod3, item->FactionAmt3); - } - } - - if (item->FactionMod4) { - if (item->FactionAmt4 > 0 && item->FactionAmt4 > GetItemFactionBonus(item->FactionMod4)) { - AddItemFactionBonus(item->FactionMod4, item->FactionAmt4); - } else if (item->FactionAmt4 < 0 && item->FactionAmt4 < GetItemFactionBonus(item->FactionMod4)) { - AddItemFactionBonus(item->FactionMod4, item->FactionAmt4); - } - } - - if (item->ExtraDmgAmt != 0 && item->ExtraDmgSkill <= EQ::skills::HIGHEST_SKILL) { - if (item->ExtraDmgSkill == ALL_SKILLS) { - for (const auto& skill_id : EQ::skills::GetExtraDamageSkills()) { - if ( - !IsNPC() && - RuleI(Character, ItemExtraDmgCap) >= 0 && - (b->SkillDamageAmount[skill_id] + item->ExtraDmgAmt) > RuleI(Character, ItemExtraDmgCap) - ) { - b->SkillDamageAmount[skill_id] = RuleI(Character, ItemExtraDmgCap); - } else { - b->SkillDamageAmount[skill_id] += item->ExtraDmgAmt; - } - } - } else { - if ( - !IsNPC() && - RuleI(Character, ItemExtraDmgCap) >= 0 && - (b->SkillDamageAmount[item->ExtraDmgSkill] + item->ExtraDmgAmt) > RuleI(Character, ItemExtraDmgCap) - ) { - b->SkillDamageAmount[item->ExtraDmgSkill] = RuleI(Character, ItemExtraDmgCap); - } else { - b->SkillDamageAmount[item->ExtraDmgSkill] += item->ExtraDmgAmt; - } - } - } - - if (!is_augment) { - for (int i = EQ::invaug::SOCKET_BEGIN; i <= EQ::invaug::SOCKET_END; i++) { - const auto* augment = inst->GetAugment(i); - if (!augment) { - continue; - } - - AddItemBonuses(augment, b, true, false, recommended_level); - } + AddItemBonuses(augment, b, true, false, recommended_level); } } } - void Mob::AdditiveWornBonuses(const EQ::ItemInstance* inst, StatBonuses* b, bool is_augment) { /* Powerful Non-live like option allows developers to add worn effects on items that @@ -835,7 +625,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) uint32 effect = 0; int32 base_value = 0; - int32 limit_value = 0; // only really used for SE_RaiseStatCap & SE_ReduceSkillTimer in aa_effects table + int32 limit_value = 0; // only really used for SpellEffect::RaiseStatCap & SpellEffect::ReduceSkillTimer in aa_effects table uint32 slot = 0; for (const auto &e : rank.effects) { @@ -844,13 +634,13 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) limit_value = e.limit_value; slot = e.slot; - // we default to 0 (SE_CurrentHP) for the effect, so if there aren't any base1/2 values, we'll just skip it + // we default to 0 (SpellEffect::CurrentHP) for the effect, so if there aren't any base1/2 values, we'll just skip it if (effect == 0 && base_value == 0 && limit_value == 0) continue; // IsBlankSpellEffect() - if (effect == SE_Blank || (effect == SE_CHA && base_value == 0) || effect == SE_StackingCommand_Block || - effect == SE_StackingCommand_Overwrite) + if (effect == SpellEffect::Blank || (effect == SpellEffect::CHA && base_value == 0) || effect == SpellEffect::StackingCommand_Block || + effect == SpellEffect::StackingCommand_Overwrite) continue; LogAA("Applying Effect [{}] from AA [{}] in slot [{}] (base1: [{}], base2: [{}]) on [{}]", @@ -863,12 +653,12 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) } switch (effect) { - case SE_ACv2: - case SE_ArmorClass: + case SpellEffect::ACv2: + case SpellEffect::ArmorClass: newbon->AC += base_value; break; // Note: AA effects that use accuracy are skill limited, while spell effect is not. - case SE_Accuracy: + case SpellEffect::Accuracy: // Bad data or unsupported new skill if (limit_value > EQ::skills::HIGHEST_SKILL) break; @@ -877,83 +667,83 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) else if (newbon->Accuracy[limit_value] < base_value) newbon->Accuracy[limit_value] += base_value; break; - case SE_CurrentHP: // regens + case SpellEffect::CurrentHP: // regens newbon->HPRegen += base_value; break; - case SE_CurrentEndurance: + case SpellEffect::CurrentEndurance: newbon->EnduranceRegen += base_value; break; - case SE_MovementSpeed: + case SpellEffect::MovementSpeed: newbon->movementspeed += base_value; // should we let these stack? /*if (base1 > newbon->movementspeed) //or should we use a total value? newbon->movementspeed = base1;*/ break; - case SE_STR: + case SpellEffect::STR: newbon->STR += base_value; break; - case SE_DEX: + case SpellEffect::DEX: newbon->DEX += base_value; break; - case SE_AGI: + case SpellEffect::AGI: newbon->AGI += base_value; break; - case SE_STA: + case SpellEffect::STA: newbon->STA += base_value; break; - case SE_INT: + case SpellEffect::INT: newbon->INT += base_value; break; - case SE_WIS: + case SpellEffect::WIS: newbon->WIS += base_value; break; - case SE_CHA: + case SpellEffect::CHA: newbon->CHA += base_value; break; - case SE_WaterBreathing: + case SpellEffect::WaterBreathing: // handled by client break; - case SE_CurrentMana: + case SpellEffect::CurrentMana: newbon->ManaRegen += base_value; break; - case SE_ManaPool: + case SpellEffect::ManaPool: newbon->Mana += base_value; break; - case SE_ItemManaRegenCapIncrease: + case SpellEffect::ItemManaRegenCapIncrease: newbon->ItemManaRegenCap += base_value; break; - case SE_ResistFire: + case SpellEffect::ResistFire: newbon->FR += base_value; break; - case SE_ResistCold: + case SpellEffect::ResistCold: newbon->CR += base_value; break; - case SE_ResistPoison: + case SpellEffect::ResistPoison: newbon->PR += base_value; break; - case SE_ResistDisease: + case SpellEffect::ResistDisease: newbon->DR += base_value; break; - case SE_ResistMagic: + case SpellEffect::ResistMagic: newbon->MR += base_value; break; - case SE_ResistCorruption: + case SpellEffect::ResistCorruption: newbon->Corrup += base_value; break; - case SE_IncreaseSpellHaste: + case SpellEffect::IncreaseSpellHaste: break; - case SE_IncreaseRange: + case SpellEffect::IncreaseRange: break; - case SE_MaxHPChange: + case SpellEffect::MaxHPChange: newbon->PercentMaxHPChange += base_value; break; - case SE_Packrat: + case SpellEffect::Packrat: newbon->Packrat += base_value; break; - case SE_TwoHandBash: + case SpellEffect::TwoHandBash: break; - case SE_SetBreathLevel: + case SpellEffect::SetBreathLevel: break; - case SE_RaiseStatCap: + case SpellEffect::RaiseStatCap: switch (limit_value) { // are these #define'd somewhere? case 0: // str @@ -997,117 +787,117 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } break; - case SE_SpellSlotIncrease: + case SpellEffect::SpellSlotIncrease: break; - case SE_MysticalAttune: + case SpellEffect::MysticalAttune: newbon->BuffSlotIncrease += base_value; break; - case SE_TotalHP: + case SpellEffect::TotalHP: newbon->FlatMaxHPChange += base_value; break; - case SE_StunResist: + case SpellEffect::StunResist: newbon->StunResist += base_value; break; - case SE_SpellCritChance: + case SpellEffect::SpellCritChance: newbon->CriticalSpellChance += base_value; break; - case SE_SpellCritDmgIncrease: + case SpellEffect::SpellCritDmgIncrease: newbon->SpellCritDmgIncrease += base_value; break; - case SE_DotCritDmgIncrease: + case SpellEffect::DotCritDmgIncrease: newbon->DotCritDmgIncrease += base_value; break; - case SE_ResistSpellChance: + case SpellEffect::ResistSpellChance: newbon->ResistSpellChance += base_value; break; - case SE_CriticalHealChance: + case SpellEffect::CriticalHealChance: newbon->CriticalHealChance += base_value; break; - case SE_CriticalHealOverTime: + case SpellEffect::CriticalHealOverTime: newbon->CriticalHealOverTime += base_value; break; - case SE_CriticalDoTChance: + case SpellEffect::CriticalDoTChance: newbon->CriticalDoTChance += base_value; break; - case SE_ReduceSkillTimer: + case SpellEffect::ReduceSkillTimer: newbon->SkillReuseTime[limit_value] += base_value; break; - case SE_Fearless: + case SpellEffect::Fearless: newbon->Fearless = true; break; - case SE_PersistantCasting: + case SpellEffect::PersistantCasting: newbon->PersistantCasting += base_value; break; - case SE_DelayDeath: + case SpellEffect::DelayDeath: newbon->DelayDeath += base_value; break; - case SE_FrontalStunResist: + case SpellEffect::FrontalStunResist: newbon->FrontalStunResist += base_value; break; - case SE_ImprovedBindWound: + case SpellEffect::ImprovedBindWound: newbon->BindWound += base_value; break; - case SE_MaxBindWound: + case SpellEffect::MaxBindWound: newbon->MaxBindWound += base_value; break; - case SE_SeeInvis: + case SpellEffect::SeeInvis: base_value = std::min({ base_value, MAX_INVISIBILTY_LEVEL }); if (newbon->SeeInvis < base_value) { newbon->SeeInvis = base_value; } break; - case SE_BaseMovementSpeed: + case SpellEffect::BaseMovementSpeed: newbon->BaseMovementSpeed += base_value; break; - case SE_IncreaseRunSpeedCap: + case SpellEffect::IncreaseRunSpeedCap: newbon->IncreaseRunSpeedCap += base_value; break; - case SE_ConsumeProjectile: + case SpellEffect::ConsumeProjectile: newbon->ConsumeProjectile += base_value; break; - case SE_ForageAdditionalItems: + case SpellEffect::ForageAdditionalItems: newbon->ForageAdditionalItems += base_value; break; - case SE_Salvage: + case SpellEffect::Salvage: newbon->SalvageChance += base_value; break; - case SE_ArcheryDamageModifier: + case SpellEffect::ArcheryDamageModifier: newbon->ArcheryDamageModifier += base_value; break; - case SE_DoubleRangedAttack: + case SpellEffect::DoubleRangedAttack: newbon->DoubleRangedAttack += base_value; break; - case SE_DamageShield: + case SpellEffect::DamageShield: newbon->DamageShield += base_value; break; - case SE_CharmBreakChance: + case SpellEffect::CharmBreakChance: newbon->CharmBreakChance += base_value; break; - case SE_OffhandRiposteFail: + case SpellEffect::OffhandRiposteFail: newbon->OffhandRiposteFail += base_value; break; - case SE_ItemAttackCapIncrease: + case SpellEffect::ItemAttackCapIncrease: newbon->ItemATKCap += base_value; break; - case SE_GivePetGroupTarget: + case SpellEffect::GivePetGroupTarget: newbon->GivePetGroupTarget = true; break; - case SE_ItemHPRegenCapIncrease: + case SpellEffect::ItemHPRegenCapIncrease: newbon->ItemHPRegenCap += base_value; break; - case SE_Ambidexterity: + case SpellEffect::Ambidexterity: newbon->Ambidexterity += base_value; break; - case SE_PetMaxHP: + case SpellEffect::PetMaxHP: newbon->PetMaxHP += base_value; break; - case SE_AvoidMeleeChance: + case SpellEffect::AvoidMeleeChance: newbon->AvoidMeleeChanceEffect += base_value; break; - case SE_CombatStability: + case SpellEffect::CombatStability: newbon->CombatStability += base_value; break; - case SE_AddSingingMod: + case SpellEffect::AddSingingMod: switch (limit_value) { case EQ::item::ItemTypeWindInstrument: newbon->windMod += base_value; @@ -1126,99 +916,99 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } break; - case SE_SongModCap: + case SpellEffect::SongModCap: newbon->songModCap += base_value; break; - case SE_PetCriticalHit: + case SpellEffect::PetCriticalHit: newbon->PetCriticalHit += base_value; break; - case SE_PetAvoidance: + case SpellEffect::PetAvoidance: newbon->PetAvoidance += base_value; break; - case SE_ShieldBlock: + case SpellEffect::ShieldBlock: newbon->ShieldBlock += base_value; break; - case SE_ShieldEquipDmgMod: + case SpellEffect::ShieldEquipDmgMod: newbon->ShieldEquipDmgMod += base_value; break; - case SE_SecondaryDmgInc: + case SpellEffect::SecondaryDmgInc: newbon->SecondaryDmgInc = true; break; - case SE_ChangeAggro: + case SpellEffect::ChangeAggro: newbon->hatemod += base_value; break; - case SE_EndurancePool: + case SpellEffect::EndurancePool: newbon->Endurance += base_value; break; - case SE_ChannelChanceItems: + case SpellEffect::ChannelChanceItems: newbon->ChannelChanceItems += base_value; break; - case SE_ChannelChanceSpells: + case SpellEffect::ChannelChanceSpells: newbon->ChannelChanceSpells += base_value; break; - case SE_DoubleSpecialAttack: + case SpellEffect::DoubleSpecialAttack: newbon->DoubleSpecialAttack += base_value; break; - case SE_TripleBackstab: + case SpellEffect::TripleBackstab: newbon->TripleBackstab += base_value; break; - case SE_FrontalBackstabMinDmg: + case SpellEffect::FrontalBackstabMinDmg: newbon->FrontalBackstabMinDmg = true; break; - case SE_FrontalBackstabChance: + case SpellEffect::FrontalBackstabChance: newbon->FrontalBackstabChance += base_value; break; - case SE_Double_Backstab_Front: + case SpellEffect::Double_Backstab_Front: newbon->Double_Backstab_Front += base_value; break; - case SE_BlockBehind: + case SpellEffect::BlockBehind: newbon->BlockBehind += base_value; break; - case SE_StrikeThrough: - case SE_StrikeThrough2: + case SpellEffect::StrikeThrough: + case SpellEffect::StrikeThrough2: newbon->StrikeThrough += base_value; break; - case SE_DoubleAttackChance: + case SpellEffect::DoubleAttackChance: newbon->DoubleAttackChance += base_value; break; - case SE_GiveDoubleAttack: + case SpellEffect::GiveDoubleAttack: newbon->GiveDoubleAttack += base_value; break; - case SE_ProcChance: + case SpellEffect::ProcChance: newbon->ProcChanceSPA += base_value; break; - case SE_RiposteChance: + case SpellEffect::RiposteChance: newbon->RiposteChance += base_value; break; - case SE_DodgeChance: + case SpellEffect::DodgeChance: newbon->DodgeChance += base_value; break; - case SE_ParryChance: + case SpellEffect::ParryChance: newbon->ParryChance += base_value; break; - case SE_IncreaseBlockChance: + case SpellEffect::IncreaseBlockChance: newbon->IncreaseBlockChance += base_value; break; - case SE_Flurry: + case SpellEffect::Flurry: newbon->FlurryChance += base_value; break; - case SE_PetFlurry: + case SpellEffect::PetFlurry: newbon->PetFlurry += base_value; break; - case SE_BardSongRange: + case SpellEffect::BardSongRange: newbon->SongRange += base_value; break; - case SE_RootBreakChance: + case SpellEffect::RootBreakChance: newbon->RootBreakChance += base_value; break; - case SE_UnfailingDivinity: + case SpellEffect::UnfailingDivinity: newbon->UnfailingDivinity += base_value; break; - case SE_CrippBlowChance: + case SpellEffect::CrippBlowChance: newbon->CrippBlowChance += base_value; break; - case SE_HitChance: { + case SpellEffect::HitChance: { // Bad data or unsupported new skill if (limit_value > EQ::skills::HIGHEST_SKILL) break; @@ -1229,7 +1019,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_ProcOnKillShot: + case SpellEffect::ProcOnKillShot: for (int i = 0; i < MAX_SPELL_TRIGGER * 3; i += 3) { if (!newbon->SpellOnKill[i] || ((newbon->SpellOnKill[i] == limit_value) && (newbon->SpellOnKill[i + 1] < base_value))) { @@ -1248,7 +1038,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) } break; - case SE_SpellOnDeath: + case SpellEffect::SpellOnDeath: for (int i = 0; i < MAX_SPELL_TRIGGER * 2; i += 2) { if (!newbon->SpellOnDeath[i]) { // base1 = SpellID to be triggered, base2 = chance to fire @@ -1259,8 +1049,8 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) } break; - case SE_WeaponProc: - case SE_AddMeleeProc: + case SpellEffect::WeaponProc: + case SpellEffect::AddMeleeProc: for (int i = 0; i < MAX_AA_PROCS; i += 4) { if (!newbon->SpellProc[i + SBIndex::COMBAT_PROC_ORIGIN_ID]) { newbon->SpellProc[i + SBIndex::COMBAT_PROC_ORIGIN_ID] = rank.id; //aa rank id @@ -1272,7 +1062,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) } break; - case SE_RangedProc: + case SpellEffect::RangedProc: for (int i = 0; i < MAX_AA_PROCS; i += 4) { if (!newbon->RangedProc[i + SBIndex::COMBAT_PROC_ORIGIN_ID]) { newbon->RangedProc[i + SBIndex::COMBAT_PROC_ORIGIN_ID] = rank.id; //aa rank id @@ -1284,7 +1074,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) } break; - case SE_DefensiveProc: + case SpellEffect::DefensiveProc: for (int i = 0; i < MAX_AA_PROCS; i += 4) { if (!newbon->DefensiveProc[i + SBIndex::COMBAT_PROC_ORIGIN_ID]) { newbon->DefensiveProc[i + SBIndex::COMBAT_PROC_ORIGIN_ID] = rank.id; //aa rank id @@ -1296,7 +1086,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) } break; - case SE_Proc_Timer_Modifier: { + case SpellEffect::Proc_Timer_Modifier: { /* AA can multiples of this in a single effect, proc should use the timer that comes after the respective proc spell effect, thus rank.id will be already set @@ -1330,7 +1120,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_CriticalHitChance: { + case SpellEffect::CriticalHitChance: { // Bad data or unsupported new skill if (limit_value > EQ::skills::HIGHEST_SKILL) break; @@ -1340,7 +1130,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) newbon->CriticalHitChance[limit_value] += base_value; } break; - case SE_CriticalDamageMob: { + case SpellEffect::CriticalDamageMob: { // Bad data or unsupported new skill if (limit_value > EQ::skills::HIGHEST_SKILL) break; @@ -1352,7 +1142,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_Critical_Melee_Damage_Mod_Max: + case SpellEffect::Critical_Melee_Damage_Mod_Max: { // Bad data or unsupported new skill if (limit_value > EQ::skills::HIGHEST_SKILL) @@ -1365,7 +1155,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_CriticalSpellChance: { + case SpellEffect::CriticalSpellChance: { newbon->CriticalSpellChance += base_value; if (limit_value > newbon->SpellCritDmgIncNoStack) @@ -1374,16 +1164,12 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_ResistFearChance: { - if (base_value == 100) // If we reach 100% in a single spell/item then we should be immune to - // negative fear resist effects until our immunity is over - newbon->Fearless = true; - + case SpellEffect::ResistFearChance: { newbon->ResistFearChance += base_value; // these should stack break; } - case SE_SkillDamageAmount: { + case SpellEffect::SkillDamageAmount: { // Bad data or unsupported new skill if (limit_value > EQ::skills::HIGHEST_SKILL) break; @@ -1394,7 +1180,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_SkillAttackProc: { + case SpellEffect::SkillAttackProc: { for (int i = 0; i < MAX_CAST_ON_SKILL_USE; i += 3) { if (!newbon->SkillAttackProc[i + SBIndex::SKILLATK_PROC_SPELL_ID]) { // spell id newbon->SkillAttackProc[i + SBIndex::SKILLATK_PROC_SPELL_ID] = rank.spell; // spell to proc @@ -1410,7 +1196,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_DamageModifier: { + case SpellEffect::DamageModifier: { // Bad data or unsupported new skill if (limit_value > EQ::skills::HIGHEST_SKILL) break; @@ -1421,7 +1207,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_DamageModifier2: { + case SpellEffect::DamageModifier2: { // Bad data or unsupported new skill if (limit_value > EQ::skills::HIGHEST_SKILL) break; @@ -1432,7 +1218,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_Skill_Base_Damage_Mod: { + case SpellEffect::Skill_Base_Damage_Mod: { // Bad data or unsupported new skill if (limit_value > EQ::skills::HIGHEST_SKILL) break; @@ -1443,7 +1229,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_SlayUndead: { + case SpellEffect::SlayUndead: { if (newbon->SlayUndead[SBIndex::SLAYUNDEAD_DMG_MOD] < base_value) { newbon->SlayUndead[SBIndex::SLAYUNDEAD_DMG_MOD] = base_value; // Rate newbon->SlayUndead[SBIndex::SLAYUNDEAD_RATE_MOD] = limit_value; // Damage Modifier @@ -1451,12 +1237,12 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_DoubleRiposte: { + case SpellEffect::DoubleRiposte: { newbon->DoubleRiposte += base_value; break; } - case SE_GiveDoubleRiposte: { + case SpellEffect::GiveDoubleRiposte: { // 0=Regular Riposte 1=Skill Attack Riposte 2=Skill if (limit_value == 0) { if (newbon->GiveDoubleRiposte[SBIndex::DOUBLE_RIPOSTE_CHANCE] < base_value) @@ -1472,7 +1258,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) } // Physically raises skill cap ie if 55/55 it will raise to 55/60 - case SE_RaiseSkillCap: { + case SpellEffect::RaiseSkillCap: { if (limit_value > EQ::skills::HIGHEST_SKILL) break; @@ -1482,23 +1268,23 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_MasteryofPast: { + case SpellEffect::MasteryofPast: { if (newbon->MasteryofPast < base_value) newbon->MasteryofPast = base_value; break; } - case SE_CastingLevel: { + case SpellEffect::CastingLevel: { newbon->adjusted_casting_skill += base_value; break; } - case SE_CastingLevel2: { + case SpellEffect::CastingLevel2: { newbon->effective_casting_level += base_value; break; } - case SE_DivineSave: { + case SpellEffect::DivineSave: { if (newbon->DivineSaveChance[SBIndex::DIVINE_SAVE_CHANCE] < base_value) { newbon->DivineSaveChance[SBIndex::DIVINE_SAVE_CHANCE] = base_value; newbon->DivineSaveChance[SBIndex::DIVINE_SAVE_SPELL_TRIGGER_ID] = limit_value; @@ -1506,7 +1292,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_SpellEffectResistChance: { + case SpellEffect::SpellEffectResistChance: { for (int e = 0; e < MAX_RESISTABLE_EFFECTS * 2; e += 2) { if ( !newbon->SEResist[e + 1] || @@ -1524,7 +1310,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_MitigateDamageShield: { + case SpellEffect::MitigateDamageShield: { //AA that increase mitigation are set to negative. if (base_value < 0) { @@ -1535,7 +1321,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_FinishingBlow: { + case SpellEffect::FinishingBlow: { // base1 = chance, base2 = damage if (newbon->FinishingBlow[SBIndex::FINISHING_EFFECT_DMG] < limit_value) { newbon->FinishingBlow[SBIndex::FINISHING_EFFECT_PROC_CHANCE] = base_value; @@ -1544,7 +1330,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_FinishingBlowLvl: { + case SpellEffect::FinishingBlowLvl: { // base1 = level, base2 = ??? (Set to 200 in AA data, possible proc rate mod?) if (newbon->FinishingBlowLvl[SBIndex::FINISHING_EFFECT_LEVEL_MAX] < base_value) { newbon->FinishingBlowLvl[SBIndex::FINISHING_EFFECT_LEVEL_MAX] = base_value; @@ -1553,23 +1339,23 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_StunBashChance: + case SpellEffect::StunBashChance: newbon->StunBashChance += base_value; break; - case SE_IncreaseChanceMemwipe: + case SpellEffect::IncreaseChanceMemwipe: newbon->IncreaseChanceMemwipe += base_value; break; - case SE_CriticalMend: + case SpellEffect::CriticalMend: newbon->CriticalMend += base_value; break; - case SE_HealRate: + case SpellEffect::HealRate: newbon->HealRate += base_value; break; - case SE_MeleeLifetap: { + case SpellEffect::MeleeLifetap: { if ((base_value < 0) && (newbon->MeleeLifetap > base_value)) newbon->MeleeLifetap = base_value; @@ -1579,27 +1365,27 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_Vampirism: + case SpellEffect::Vampirism: newbon->Vampirism += base_value; break; - case SE_FrenziedDevastation: + case SpellEffect::FrenziedDevastation: newbon->FrenziedDevastation += limit_value; break; - case SE_SpellProcChance: + case SpellEffect::SpellProcChance: newbon->SpellProcChance += base_value; break; - case SE_Berserk: + case SpellEffect::Berserk: newbon->BerserkSPA = true; break; - case SE_Metabolism: + case SpellEffect::Metabolism: newbon->Metabolism += base_value; break; - case SE_ImprovedReclaimEnergy: { + case SpellEffect::ImprovedReclaimEnergy: { if ((base_value < 0) && (newbon->ImprovedReclaimEnergy > base_value)) newbon->ImprovedReclaimEnergy = base_value; @@ -1608,7 +1394,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_HeadShot: { + case SpellEffect::HeadShot: { if (newbon->HeadShot[SBIndex::FINISHING_EFFECT_DMG] < limit_value) { newbon->HeadShot[SBIndex::FINISHING_EFFECT_PROC_CHANCE] = base_value; newbon->HeadShot[SBIndex::FINISHING_EFFECT_DMG] = limit_value; @@ -1616,7 +1402,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_HeadShotLevel: { + case SpellEffect::HeadShotLevel: { if (newbon->HSLevel[SBIndex::FINISHING_EFFECT_LEVEL_MAX] < base_value) { newbon->HSLevel[SBIndex::FINISHING_EFFECT_LEVEL_MAX] = base_value; newbon->HSLevel[SBIndex::FINISHING_EFFECT_LEVEL_CHANCE_BONUS] = limit_value; @@ -1624,7 +1410,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_Assassinate: { + case SpellEffect::Assassinate: { if (newbon->Assassinate[SBIndex::FINISHING_EFFECT_DMG] < limit_value) { newbon->Assassinate[SBIndex::FINISHING_EFFECT_PROC_CHANCE] = base_value; newbon->Assassinate[SBIndex::FINISHING_EFFECT_DMG] = limit_value; @@ -1632,7 +1418,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_AssassinateLevel: { + case SpellEffect::AssassinateLevel: { if (newbon->AssassinateLevel[SBIndex::FINISHING_EFFECT_LEVEL_MAX] < base_value) { newbon->AssassinateLevel[SBIndex::FINISHING_EFFECT_LEVEL_MAX] = base_value; newbon->AssassinateLevel[SBIndex::FINISHING_EFFECT_LEVEL_CHANCE_BONUS] = limit_value; @@ -1640,11 +1426,11 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_PetMeleeMitigation: + case SpellEffect::PetMeleeMitigation: newbon->PetMeleeMitigation += base_value; break; - case SE_FactionModPct: { + case SpellEffect::FactionModPct: { if ((base_value < 0) && (newbon->FactionModPct > base_value)) newbon->FactionModPct = base_value; @@ -1653,15 +1439,15 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_Illusion: + case SpellEffect::Illusion: newbon->Illusion = rank.spell; break; - case SE_IllusionPersistence: + case SpellEffect::IllusionPersistence: newbon->IllusionPersistence = base_value; break; - case SE_LimitToSkill: { + case SpellEffect::LimitToSkill: { // Bad data or unsupported new skill if (base_value > EQ::skills::HIGHEST_SKILL) { @@ -1674,7 +1460,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_SkillProcAttempt: { + case SpellEffect::SkillProcAttempt: { for (int e = 0; e < MAX_SKILL_PROCS; e++) { if (newbon->SkillProc[e] && newbon->SkillProc[e] == rank.id) break; // Do not use the same aa id more than once. @@ -1687,7 +1473,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_SkillProcSuccess: { + case SpellEffect::SkillProcSuccess: { for (int e = 0; e < MAX_SKILL_PROCS; e++) { if (newbon->SkillProcSuccess[e] && newbon->SkillProcSuccess[e] == rank.id) @@ -1701,44 +1487,44 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_MeleeMitigation: + case SpellEffect::MeleeMitigation: newbon->MeleeMitigationEffect += base_value; break; - case SE_ATK: + case SpellEffect::ATK: newbon->ATK += base_value; break; - case SE_IncreaseExtTargetWindow: + case SpellEffect::IncreaseExtTargetWindow: newbon->extra_xtargets += base_value; break; - case SE_PC_Pet_Rampage: { + case SpellEffect::PC_Pet_Rampage: { newbon->PC_Pet_Rampage[SBIndex::PET_RAMPAGE_CHANCE] += base_value; //Chance to rampage if (newbon->PC_Pet_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] < limit_value) newbon->PC_Pet_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] = limit_value; //Damage modifer - take highest break; } - case SE_PC_Pet_AE_Rampage: { + case SpellEffect::PC_Pet_AE_Rampage: { newbon->PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_CHANCE] += base_value; //Chance to rampage if (newbon->PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] < limit_value) newbon->PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] = limit_value; //Damage modifer - take highest break; } - case SE_PC_Pet_Flurry_Chance: + case SpellEffect::PC_Pet_Flurry_Chance: newbon->PC_Pet_Flurry += base_value; //Chance to Flurry break; - case SE_ShroudofStealth: + case SpellEffect::ShroudofStealth: newbon->ShroudofStealth = true; break; - case SE_ReduceFallDamage: + case SpellEffect::ReduceFallDamage: newbon->ReduceFallDamage += base_value; break; - case SE_ReduceTradeskillFail:{ + case SpellEffect::ReduceTradeskillFail:{ if (limit_value > EQ::skills::HIGHEST_SKILL) break; @@ -1747,42 +1533,42 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_TradeSkillMastery: + case SpellEffect::TradeSkillMastery: if (newbon->TradeSkillMastery < base_value) newbon->TradeSkillMastery = base_value; break; - case SE_NoBreakAESneak: + case SpellEffect::NoBreakAESneak: if (newbon->NoBreakAESneak < base_value) newbon->NoBreakAESneak = base_value; break; - case SE_FeignedCastOnChance: + case SpellEffect::FeignedCastOnChance: if (newbon->FeignedCastOnChance < base_value) newbon->FeignedCastOnChance = base_value; break; - case SE_AddPetCommand: - if (base_value && limit_value < PET_MAXCOMMANDS) + case SpellEffect::AddPetCommand: + if (base_value && limit_value < PetCommand::Max) newbon->PetCommands[limit_value] = true; break; - case SE_FeignedMinion: + case SpellEffect::FeignedMinion: if (newbon->FeignedMinionChance < base_value) { newbon->FeignedMinionChance = base_value; } - newbon->PetCommands[PET_FEIGN] = true; + newbon->PetCommands[PetCommand::Feign] = true; break; - case SE_AdditionalAura: + case SpellEffect::AdditionalAura: newbon->aura_slots += base_value; break; - case SE_IncreaseTrapCount: + case SpellEffect::IncreaseTrapCount: newbon->trap_slots += base_value; break; - case SE_ForageSkill: + case SpellEffect::ForageSkill: newbon->GrantForage += base_value; // we need to grant a skill point here // I'd rather not do this here, but whatever, probably fine @@ -1793,19 +1579,19 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) } break; - case SE_Attack_Accuracy_Max_Percent: + case SpellEffect::Attack_Accuracy_Max_Percent: newbon->Attack_Accuracy_Max_Percent += base_value; break; - case SE_AC_Mitigation_Max_Percent: + case SpellEffect::AC_Mitigation_Max_Percent: newbon->AC_Mitigation_Max_Percent += base_value; break; - case SE_AC_Avoidance_Max_Percent: + case SpellEffect::AC_Avoidance_Max_Percent: newbon->AC_Avoidance_Max_Percent += base_value; break; - case SE_Damage_Taken_Position_Mod: + case SpellEffect::Damage_Taken_Position_Mod: { //Mitigate if damage taken from behind base2 = 0, from front base2 = 1 if (limit_value < 0 || limit_value >= 2) { @@ -1818,7 +1604,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_Melee_Damage_Position_Mod: + case SpellEffect::Melee_Damage_Position_Mod: { if (limit_value < 0 || limit_value >= 2) { break; @@ -1830,7 +1616,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_Damage_Taken_Position_Amt: + case SpellEffect::Damage_Taken_Position_Amt: { //Mitigate if damage taken from behind base2 = 0, from front base2 = 1 if (limit_value < 0 || limit_value >= 2) { @@ -1840,7 +1626,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_Melee_Damage_Position_Amt: + case SpellEffect::Melee_Damage_Position_Amt: { //Mitigate if damage taken from behind base2 = 0, from front base2 = 1 if (limit_value < 0 || limit_value >= 2) { @@ -1851,23 +1637,23 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_DS_Mitigation_Amount: + case SpellEffect::DS_Mitigation_Amount: newbon->DS_Mitigation_Amount += base_value; break; - case SE_DS_Mitigation_Percentage: + case SpellEffect::DS_Mitigation_Percentage: newbon->DS_Mitigation_Percentage += base_value; break; - case SE_Pet_Crit_Melee_Damage_Pct_Owner: + case SpellEffect::Pet_Crit_Melee_Damage_Pct_Owner: newbon->Pet_Crit_Melee_Damage_Pct_Owner += base_value; break; - case SE_Pet_Add_Atk: + case SpellEffect::Pet_Add_Atk: newbon->Pet_Add_Atk += base_value; break; - case SE_Weapon_Stance: + case SpellEffect::Weapon_Stance: { if (IsValidSpell(base_value)) { //base1 is the spell_id of buff if (limit_value <= WEAPON_STANCE_TYPE_MAX) { //0=2H, 1=Shield, 2=DW @@ -1886,7 +1672,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_ExtraAttackChance: + case SpellEffect::ExtraAttackChance: { if (newbon->ExtraAttackChance[SBIndex::EXTRA_ATTACK_CHANCE] < base_value) { newbon->ExtraAttackChance[SBIndex::EXTRA_ATTACK_CHANCE] = base_value; @@ -1895,7 +1681,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_AddExtraAttackPct_1h_Primary: + case SpellEffect::AddExtraAttackPct_1h_Primary: { if (newbon->ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_CHANCE] < base_value) { newbon->ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_CHANCE] = base_value; @@ -1904,7 +1690,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_AddExtraAttackPct_1h_Secondary: + case SpellEffect::AddExtraAttackPct_1h_Secondary: { if (newbon->ExtraAttackChanceSecondary[SBIndex::EXTRA_ATTACK_CHANCE] < base_value) { @@ -1914,7 +1700,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_Double_Melee_Round: + case SpellEffect::Double_Melee_Round: { if (newbon->DoubleMeleeRound[SBIndex::DOUBLE_MELEE_ROUND_CHANCE] < base_value) { newbon->DoubleMeleeRound[SBIndex::DOUBLE_MELEE_ROUND_CHANCE] = base_value; @@ -1924,7 +1710,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_ExtendedShielding: + case SpellEffect::ExtendedShielding: { if (newbon->ExtendedShielding < base_value) { newbon->ExtendedShielding = base_value; @@ -1932,7 +1718,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_ShieldDuration: + case SpellEffect::ShieldDuration: { if (newbon->ShieldDuration < base_value) { newbon->ShieldDuration = base_value; @@ -1940,23 +1726,23 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - case SE_Worn_Endurance_Regen_Cap: + case SpellEffect::Worn_Endurance_Regen_Cap: newbon->ItemEnduranceRegenCap += base_value; break; - case SE_SecondaryForte: + case SpellEffect::SecondaryForte: if (newbon->SecondaryForte < base_value) { newbon->SecondaryForte = base_value; } break; - case SE_ZoneSuspendMinion: + case SpellEffect::ZoneSuspendMinion: newbon->ZoneSuspendMinion = base_value; break; - case SE_Reflect: + case SpellEffect::Reflect: if (newbon->reflect[SBIndex::REFLECT_CHANCE] < base_value) { newbon->reflect[SBIndex::REFLECT_CHANCE] = base_value; @@ -1966,69 +1752,69 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) } break; - case SE_SpellDamageShield: + case SpellEffect::SpellDamageShield: newbon->SpellDamageShield += base_value; break; - case SE_Amplification: + case SpellEffect::Amplification: newbon->Amplification += base_value; break; - case SE_MitigateSpellDamage: + case SpellEffect::MitigateSpellDamage: { newbon->MitigateSpellRune[SBIndex::MITIGATION_RUNE_PERCENT] += base_value; break; } - case SE_MitigateDotDamage: + case SpellEffect::MitigateDotDamage: { newbon->MitigateDotRune[SBIndex::MITIGATION_RUNE_PERCENT] += base_value; break; } - case SE_TrapCircumvention: + case SpellEffect::TrapCircumvention: newbon->TrapCircumvention += base_value; break; // to do - case SE_PetDiscipline: + case SpellEffect::PetDiscipline: break; - case SE_PotionBeltSlots: + case SpellEffect::PotionBeltSlots: break; - case SE_BandolierSlots: + case SpellEffect::BandolierSlots: break; - case SE_ReduceApplyPoisonTime: + case SpellEffect::ReduceApplyPoisonTime: break; - case SE_NimbleEvasion: + case SpellEffect::NimbleEvasion: break; // not handled here - case SE_HastenedAASkill: + case SpellEffect::HastenedAASkill: // not handled here but don't want to clutter debug log -- these may need to be verified to ignore - case SE_LimitMaxLevel: - case SE_LimitResist: - case SE_LimitTarget: - case SE_LimitEffect: - case SE_LimitSpellType: - case SE_LimitMinDur: - case SE_LimitInstant: - case SE_LimitMinLevel: - case SE_LimitCastTimeMin: - case SE_LimitCastTimeMax: - case SE_LimitSpell: - case SE_LimitCombatSkills: - case SE_LimitManaMin: - case SE_LimitSpellGroup: - case SE_LimitSpellClass: - case SE_LimitSpellSubclass: - case SE_LimitHPPercent: - case SE_LimitManaPercent: - case SE_LimitEndPercent: - case SE_LimitClass: - case SE_LimitRace: - case SE_LimitCastingSkill: - case SE_LimitUseMin: - case SE_LimitUseType: + case SpellEffect::LimitMaxLevel: + case SpellEffect::LimitResist: + case SpellEffect::LimitTarget: + case SpellEffect::LimitEffect: + case SpellEffect::LimitSpellType: + case SpellEffect::LimitMinDur: + case SpellEffect::LimitInstant: + case SpellEffect::LimitMinLevel: + case SpellEffect::LimitCastTimeMin: + case SpellEffect::LimitCastTimeMax: + case SpellEffect::LimitSpell: + case SpellEffect::LimitCombatSkills: + case SpellEffect::LimitManaMin: + case SpellEffect::LimitSpellGroup: + case SpellEffect::LimitSpellClass: + case SpellEffect::LimitSpellSubclass: + case SpellEffect::LimitHPPercent: + case SpellEffect::LimitManaPercent: + case SpellEffect::LimitEndPercent: + case SpellEffect::LimitClass: + case SpellEffect::LimitRace: + case SpellEffect::LimitCastingSkill: + case SpellEffect::LimitUseMin: + case SpellEffect::LimitUseType: break; default: @@ -2065,7 +1851,7 @@ void Mob::CalcSpellBonuses(StatBonuses* newbon) //Disables a specific spell effect bonus completely, can also be limited to negate only item, AA or spell bonuses. if (spellbonuses.NegateEffects){ for(i = 0; i < buff_count; i++) { - if(IsValidSpell(buffs[i].spellid) && (IsEffectInSpell(buffs[i].spellid, SE_NegateSpellEffect)) ) + if(IsValidSpell(buffs[i].spellid) && (IsEffectInSpell(buffs[i].spellid, SpellEffect::NegateSpellEffect)) ) NegateSpellEffectBonuses(buffs[i].spellid); } } @@ -2127,7 +1913,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne //Special custom cases for loading effects on to NPC from 'npc_spels_effects' table //Non-Focused Effect to modify incoming spell damage by resist type. - if (spell_effect_id == SE_FcSpellVulnerability) { + if (spell_effect_id == SpellEffect::FcSpellVulnerability) { ModVulnerability(limit_value, effect_value); } @@ -2136,17 +1922,17 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne switch (spell_effect_id) { - case SE_CurrentHP: //regens + case SpellEffect::CurrentHP: //regens if(effect_value > 0) { new_bonus->HPRegen += effect_value; } break; - case SE_CurrentEndurance: + case SpellEffect::CurrentEndurance: new_bonus->EnduranceRegen += effect_value; break; - case SE_ChangeFrenzyRad: + case SpellEffect::ChangeFrenzyRad: { if (max_value != 0 && GetLevel() > max_value) break; @@ -2158,7 +1944,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_Harmony: + case SpellEffect::Harmony: { if (max_value != 0 && GetLevel() > max_value) break; @@ -2172,7 +1958,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_AttackSpeed: + case SpellEffect::AttackSpeed: { if ((effect_value - 100) > 0) { // Haste if (new_bonus->haste < 0) break; // Slowed - Don't apply haste @@ -2189,7 +1975,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_AttackSpeed2: + case SpellEffect::AttackSpeed2: { if ((effect_value - 100) > 0) { // Haste V2 - Stacks with V1 but does not Overcap if (new_bonus->hastetype2 < 0) break; //Slowed - Don't apply haste2 @@ -2206,7 +1992,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_AttackSpeed3: + case SpellEffect::AttackSpeed3: { if (effect_value < 0){ //Slow effect_value -= (effect_value * GetSlowMitigation()/100); @@ -2222,7 +2008,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_AttackSpeed4: + case SpellEffect::AttackSpeed4: { // These don't generate the IMMUNE_ATKSPEED message and the icon shows up // but have no effect on the mobs attack speed @@ -2241,87 +2027,87 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_IncreaseArchery: + case SpellEffect::IncreaseArchery: { new_bonus->increase_archery += effect_value; break; } - case SE_TotalHP: + case SpellEffect::TotalHP: { new_bonus->FlatMaxHPChange += effect_value; break; } - case SE_ManaRegen_v2: - case SE_CurrentMana: + case SpellEffect::ManaRegen_v2: + case SpellEffect::CurrentMana: { new_bonus->ManaRegen += effect_value; break; } - case SE_ManaPool: + case SpellEffect::ManaPool: { new_bonus->Mana += effect_value; break; } - case SE_Stamina: + case SpellEffect::Stamina: { new_bonus->EnduranceReduction += effect_value; break; } - case SE_ACv2: - case SE_ArmorClass: + case SpellEffect::ACv2: + case SpellEffect::ArmorClass: { new_bonus->AC += effect_value; break; } - case SE_ATK: + case SpellEffect::ATK: { new_bonus->ATK += effect_value; break; } - case SE_STR: + case SpellEffect::STR: { new_bonus->STR += effect_value; break; } - case SE_DEX: + case SpellEffect::DEX: { new_bonus->DEX += effect_value; break; } - case SE_AGI: + case SpellEffect::AGI: { new_bonus->AGI += effect_value; break; } - case SE_STA: + case SpellEffect::STA: { new_bonus->STA += effect_value; break; } - case SE_INT: + case SpellEffect::INT: { new_bonus->INT += effect_value; break; } - case SE_WIS: + case SpellEffect::WIS: { new_bonus->WIS += effect_value; break; } - case SE_CHA: + case SpellEffect::CHA: { if (spells[spell_id].base_value[i] != 0) { new_bonus->CHA += effect_value; @@ -2329,7 +2115,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_AllStats: + case SpellEffect::AllStats: { new_bonus->STR += effect_value; new_bonus->DEX += effect_value; @@ -2341,37 +2127,37 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_ResistFire: + case SpellEffect::ResistFire: { new_bonus->FR += effect_value; break; } - case SE_ResistCold: + case SpellEffect::ResistCold: { new_bonus->CR += effect_value; break; } - case SE_ResistPoison: + case SpellEffect::ResistPoison: { new_bonus->PR += effect_value; break; } - case SE_ResistDisease: + case SpellEffect::ResistDisease: { new_bonus->DR += effect_value; break; } - case SE_ResistMagic: + case SpellEffect::ResistMagic: { new_bonus->MR += effect_value; break; } - case SE_ResistAll: + case SpellEffect::ResistAll: { new_bonus->MR += effect_value; new_bonus->DR += effect_value; @@ -2381,13 +2167,13 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_ResistCorruption: + case SpellEffect::ResistCorruption: { new_bonus->Corrup += effect_value; break; } - case SE_RaiseStatCap: + case SpellEffect::RaiseStatCap: { switch(spells[spell_id].limit_value[i]) { @@ -2435,13 +2221,13 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_CastingLevel: // Brilliance of Ro + case SpellEffect::CastingLevel: // Brilliance of Ro { new_bonus->adjusted_casting_skill += effect_value; break; } - case SE_CastingLevel2: + case SpellEffect::CastingLevel2: { new_bonus->effective_casting_level += effect_value; @@ -2451,15 +2237,15 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_MovementSpeed: + case SpellEffect::MovementSpeed: new_bonus->movementspeed += effect_value; break; - case SE_SpellDamageShield: + case SpellEffect::SpellDamageShield: new_bonus->SpellDamageShield += effect_value; break; - case SE_DamageShield: + case SpellEffect::DamageShield: { new_bonus->DamageShield += effect_value; new_bonus->DamageShieldSpellID = spell_id; @@ -2472,7 +2258,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_ReverseDS: + case SpellEffect::ReverseDS: { new_bonus->ReverseDamageShield += effect_value; new_bonus->ReverseDamageShieldSpellID = spell_id; @@ -2484,7 +2270,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_Reflect: + case SpellEffect::Reflect: if (AdditiveWornBonus) { new_bonus->reflect[SBIndex::REFLECT_CHANCE] += effect_value; @@ -2497,20 +2283,20 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne } break; - case SE_Amplification: + case SpellEffect::Amplification: new_bonus->Amplification += effect_value; break; - case SE_ChangeAggro: + case SpellEffect::ChangeAggro: new_bonus->hatemod += effect_value; break; - case SE_MeleeMitigation: + case SpellEffect::MeleeMitigation: // This value is negative because it counteracts another SPA :P new_bonus->MeleeMitigationEffect += effect_value; break; - case SE_CriticalHitChance: + case SpellEffect::CriticalHitChance: { // Bad data or unsupported new skill if (limit_value > EQ::skills::HIGHEST_SKILL) @@ -2539,7 +2325,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_CrippBlowChance: + case SpellEffect::CrippBlowChance: { if (AdditiveWornBonus) new_bonus->CrippBlowChance += effect_value; @@ -2553,7 +2339,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_AvoidMeleeChance: + case SpellEffect::AvoidMeleeChance: { if (AdditiveWornBonus) new_bonus->AvoidMeleeChanceEffect += effect_value; @@ -2566,7 +2352,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_RiposteChance: + case SpellEffect::RiposteChance: { if (AdditiveWornBonus) new_bonus->RiposteChance += effect_value; @@ -2579,7 +2365,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_DodgeChance: + case SpellEffect::DodgeChance: { if (AdditiveWornBonus) new_bonus->DodgeChance += effect_value; @@ -2592,7 +2378,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_ParryChance: + case SpellEffect::ParryChance: { if (AdditiveWornBonus) new_bonus->ParryChance += effect_value; @@ -2605,7 +2391,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_DualWieldChance: + case SpellEffect::DualWieldChance: { if (AdditiveWornBonus) new_bonus->DualWieldChance += effect_value; @@ -2618,7 +2404,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_DoubleAttackChance: + case SpellEffect::DoubleAttackChance: { if (AdditiveWornBonus) @@ -2632,7 +2418,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_TripleAttackChance: + case SpellEffect::TripleAttackChance: { if (AdditiveWornBonus) @@ -2646,7 +2432,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_MeleeLifetap: + case SpellEffect::MeleeLifetap: { if (AdditiveWornBonus) new_bonus->MeleeLifetap += spells[spell_id].base_value[i]; @@ -2659,11 +2445,11 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_Vampirism: + case SpellEffect::Vampirism: new_bonus->Vampirism += effect_value; break; - case SE_AllInstrumentMod: + case SpellEffect::AllInstrumentMod: { if(effect_value > new_bonus->singingMod) new_bonus->singingMod = effect_value; @@ -2678,24 +2464,21 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_ResistSpellChance: + case SpellEffect::ResistSpellChance: new_bonus->ResistSpellChance += effect_value; break; - case SE_ResistFearChance: + case SpellEffect::ResistFearChance: { - if(effect_value == 100) // If we reach 100% in a single spell/item then we should be immune to negative fear resist effects until our immunity is over - new_bonus->Fearless = true; - new_bonus->ResistFearChance += effect_value; // these should stack break; } - case SE_Fearless: + case SpellEffect::Fearless: new_bonus->Fearless = true; break; - case SE_HundredHands: + case SpellEffect::HundredHands: { if (AdditiveWornBonus) new_bonus->HundredHands += effect_value; @@ -2707,7 +2490,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_MeleeSkillCheck: + case SpellEffect::MeleeSkillCheck: { if(new_bonus->MeleeSkillCheck < effect_value) { new_bonus->MeleeSkillCheck = effect_value; @@ -2716,7 +2499,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_HitChance: + case SpellEffect::HitChance: { // Bad data or unsupported new skill if (limit_value > EQ::skills::HIGHEST_SKILL) @@ -2753,7 +2536,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne } - case SE_DamageModifier: + case SpellEffect::DamageModifier: { // Bad data or unsupported new skill if (limit_value > EQ::skills::HIGHEST_SKILL) @@ -2766,7 +2549,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_DamageModifier2: + case SpellEffect::DamageModifier2: { // Bad data or unsupported new skill if (limit_value > EQ::skills::HIGHEST_SKILL) @@ -2779,7 +2562,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_Skill_Base_Damage_Mod: + case SpellEffect::Skill_Base_Damage_Mod: { // Bad data or unsupported new skill if (limit_value > EQ::skills::HIGHEST_SKILL) @@ -2792,7 +2575,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_MinDamageModifier: + case SpellEffect::MinDamageModifier: { // Bad data or unsupported new skill if (limit_value > EQ::skills::HIGHEST_SKILL) @@ -2805,7 +2588,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_ReduceSkill: { + case SpellEffect::ReduceSkill: { // Bad data or unsupported new skill if (spells[spell_id].base_value[i] > EQ::skills::HIGHEST_SKILL) { break; @@ -2824,14 +2607,14 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_StunResist: + case SpellEffect::StunResist: { if(new_bonus->StunResist < effect_value) new_bonus->StunResist = effect_value; break; } - case SE_ProcChance: + case SpellEffect::ProcChance: { if (AdditiveWornBonus) new_bonus->ProcChanceSPA += effect_value; @@ -2845,7 +2628,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_ExtraAttackChance: + case SpellEffect::ExtraAttackChance: { if (AdditiveWornBonus) { new_bonus->ExtraAttackChance[SBIndex::EXTRA_ATTACK_CHANCE] += effect_value; @@ -2858,7 +2641,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_AddExtraAttackPct_1h_Primary: + case SpellEffect::AddExtraAttackPct_1h_Primary: { if (AdditiveWornBonus) { new_bonus->ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_CHANCE] += effect_value; @@ -2872,7 +2655,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_AddExtraAttackPct_1h_Secondary: + case SpellEffect::AddExtraAttackPct_1h_Secondary: { if (AdditiveWornBonus) { new_bonus->ExtraAttackChanceSecondary[SBIndex::EXTRA_ATTACK_CHANCE] += effect_value; @@ -2886,7 +2669,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_Double_Melee_Round: + case SpellEffect::Double_Melee_Round: { if (AdditiveWornBonus) { new_bonus->DoubleMeleeRound[SBIndex::DOUBLE_MELEE_ROUND_CHANCE] += effect_value; @@ -2900,14 +2683,14 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_PercentXPIncrease: + case SpellEffect::PercentXPIncrease: { if(new_bonus->XPRateMod < effect_value) new_bonus->XPRateMod = effect_value; break; } - case SE_DeathSave: + case SpellEffect::DeathSave: { if(new_bonus->DeathSave[SBIndex::DEATH_SAVE_TYPE] < effect_value) { @@ -2920,7 +2703,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_DivineSave: + case SpellEffect::DivineSave: { if (AdditiveWornBonus) { new_bonus->DivineSaveChance[SBIndex::DIVINE_SAVE_CHANCE] += effect_value; @@ -2935,11 +2718,11 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_Flurry: + case SpellEffect::Flurry: new_bonus->FlurryChance += effect_value; break; - case SE_Accuracy: + case SpellEffect::Accuracy: { // Bad data or unsupported new skill if (limit_value > EQ::skills::HIGHEST_SKILL) @@ -2953,19 +2736,19 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_MaxHPChange: + case SpellEffect::MaxHPChange: new_bonus->PercentMaxHPChange += effect_value; break; - case SE_EndurancePool: + case SpellEffect::EndurancePool: new_bonus->Endurance += effect_value; break; - case SE_HealRate: + case SpellEffect::HealRate: new_bonus->HealRate += effect_value; break; - case SE_SkillDamageTaken: + case SpellEffect::SkillDamageTaken: { // Bad data or unsupported new skill if (limit_value > EQ::skills::HIGHEST_SKILL) @@ -2988,11 +2771,11 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_SpellCritChance: + case SpellEffect::SpellCritChance: new_bonus->CriticalSpellChance += effect_value; break; - case SE_CriticalSpellChance: + case SpellEffect::CriticalSpellChance: { new_bonus->CriticalSpellChance += effect_value; @@ -3001,35 +2784,35 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_SpellCritDmgIncrease: + case SpellEffect::SpellCritDmgIncrease: new_bonus->SpellCritDmgIncrease += effect_value; break; - case SE_DotCritDmgIncrease: + case SpellEffect::DotCritDmgIncrease: new_bonus->DotCritDmgIncrease += effect_value; break; - case SE_CriticalHealChance: + case SpellEffect::CriticalHealChance: new_bonus->CriticalHealChance += effect_value; break; - case SE_CriticalHealOverTime: + case SpellEffect::CriticalHealOverTime: new_bonus->CriticalHealOverTime += effect_value; break; - case SE_CriticalHealDecay: + case SpellEffect::CriticalHealDecay: new_bonus->CriticalHealDecay = true; break; - case SE_CriticalRegenDecay: + case SpellEffect::CriticalRegenDecay: new_bonus->CriticalRegenDecay = true; break; - case SE_CriticalDotDecay: + case SpellEffect::CriticalDotDecay: new_bonus->CriticalDotDecay = true; break; - case SE_MitigateDamageShield: + case SpellEffect::MitigateDamageShield: { /* Bard songs have identical negative base value and positive max @@ -3045,11 +2828,11 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_CriticalDoTChance: + case SpellEffect::CriticalDoTChance: new_bonus->CriticalDoTChance += effect_value; break; - case SE_ProcOnKillShot: + case SpellEffect::ProcOnKillShot: { for(int e = 0; e < MAX_SPELL_TRIGGER*3; e+=3) { @@ -3065,7 +2848,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_SpellOnDeath: + case SpellEffect::SpellOnDeath: { for(int e = 0; e < MAX_SPELL_TRIGGER; e+=2) { @@ -3080,7 +2863,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_CriticalDamageMob: + case SpellEffect::CriticalDamageMob: { // Bad data or unsupported new skill if (limit_value > EQ::skills::HIGHEST_SKILL) @@ -3092,7 +2875,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_Critical_Melee_Damage_Mod_Max: + case SpellEffect::Critical_Melee_Damage_Mod_Max: { // Bad data or unsupported new skill if (limit_value > EQ::skills::HIGHEST_SKILL) @@ -3106,14 +2889,14 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_ReduceSkillTimer: + case SpellEffect::ReduceSkillTimer: { if(new_bonus->SkillReuseTime[limit_value] < effect_value) new_bonus->SkillReuseTime[limit_value] = effect_value; break; } - case SE_SkillDamageAmount: + case SpellEffect::SkillDamageAmount: { // Bad data or unsupported new skill if (limit_value > EQ::skills::HIGHEST_SKILL) @@ -3125,23 +2908,23 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_GravityEffect: + case SpellEffect::GravityEffect: new_bonus->GravityEffect = 1; break; - case SE_AntiGate: + case SpellEffect::AntiGate: new_bonus->AntiGate = true; break; - case SE_MagicWeapon: + case SpellEffect::MagicWeapon: new_bonus->MagicWeapon = true; break; - case SE_Hunger: + case SpellEffect::Hunger: new_bonus->hunger = true; break; - case SE_IncreaseBlockChance: + case SpellEffect::IncreaseBlockChance: if (AdditiveWornBonus) new_bonus->IncreaseBlockChance += effect_value; else if (effect_value < 0 && new_bonus->IncreaseBlockChance > effect_value) @@ -3150,11 +2933,11 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne new_bonus->IncreaseBlockChance = effect_value; break; - case SE_PersistantCasting: + case SpellEffect::PersistantCasting: new_bonus->PersistantCasting += effect_value; break; - case SE_LimitHPPercent: + case SpellEffect::LimitHPPercent: { if(new_bonus->HPPercCap[SBIndex::RESOURCE_PERCENT_CAP] != 0 && new_bonus->HPPercCap[SBIndex::RESOURCE_PERCENT_CAP] > effect_value){ new_bonus->HPPercCap[SBIndex::RESOURCE_PERCENT_CAP] = effect_value; @@ -3166,7 +2949,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne } break; } - case SE_LimitManaPercent: + case SpellEffect::LimitManaPercent: { if(new_bonus->ManaPercCap[SBIndex::RESOURCE_PERCENT_CAP] != 0 && new_bonus->ManaPercCap[SBIndex::RESOURCE_PERCENT_CAP] > effect_value){ new_bonus->ManaPercCap[SBIndex::RESOURCE_PERCENT_CAP] = effect_value; @@ -3179,7 +2962,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_LimitEndPercent: + case SpellEffect::LimitEndPercent: { if(new_bonus->EndPercCap[SBIndex::RESOURCE_PERCENT_CAP] != 0 && new_bonus->EndPercCap[SBIndex::RESOURCE_PERCENT_CAP] > effect_value) { new_bonus->EndPercCap[SBIndex::RESOURCE_PERCENT_CAP] = effect_value; @@ -3194,33 +2977,33 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_NegateSpellEffect: + case SpellEffect::NegateSpellEffect: new_bonus->NegateEffects = true; break; - case SE_ImmuneFleeing: + case SpellEffect::ImmuneFleeing: new_bonus->ImmuneToFlee = true; if (currently_fleeing) // lets update shit now instead of next tick ProcessFlee(); break; - case SE_DelayDeath: + case SpellEffect::DelayDeath: new_bonus->DelayDeath += effect_value; break; - case SE_SpellProcChance: + case SpellEffect::SpellProcChance: new_bonus->SpellProcChance += effect_value; break; - case SE_CharmBreakChance: + case SpellEffect::CharmBreakChance: new_bonus->CharmBreakChance += effect_value; break; - case SE_BardSongRange: + case SpellEffect::BardSongRange: new_bonus->SongRange += effect_value; break; - case SE_HPToMana: + case SpellEffect::HPToMana: { //Lower the ratio the more favorable if ((!new_bonus->HPToManaConvert) || (new_bonus->HPToManaConvert >= effect_value)) { @@ -3229,7 +3012,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_SkillDamageAmount2: + case SpellEffect::SkillDamageAmount2: { // Bad data or unsupported new skill if (limit_value > EQ::skills::HIGHEST_SKILL) @@ -3241,7 +3024,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_NegateAttacks: + case SpellEffect::NegateAttacks: { if ( !new_bonus->NegateAttacks[SBIndex::NEGATE_ATK_EXISTS] || @@ -3257,7 +3040,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_MitigateMeleeDamage: + case SpellEffect::MitigateMeleeDamage: { if (new_bonus->MitigateMeleeRune[SBIndex::MITIGATION_RUNE_PERCENT] < effect_value){ new_bonus->MitigateMeleeRune[SBIndex::MITIGATION_RUNE_PERCENT] = effect_value; @@ -3269,7 +3052,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne } - case SE_MeleeThresholdGuard: + case SpellEffect::MeleeThresholdGuard: { if (new_bonus->MeleeThresholdGuard[SBIndex::THRESHOLDGUARD_MITIGATION_PERCENT] < effect_value){ new_bonus->MeleeThresholdGuard[SBIndex::THRESHOLDGUARD_MITIGATION_PERCENT] = effect_value; @@ -3279,7 +3062,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_SpellThresholdGuard: + case SpellEffect::SpellThresholdGuard: { if (new_bonus->SpellThresholdGuard[SBIndex::THRESHOLDGUARD_MITIGATION_PERCENT] < effect_value){ new_bonus->SpellThresholdGuard[SBIndex::THRESHOLDGUARD_MITIGATION_PERCENT] = effect_value; @@ -3289,7 +3072,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_MitigateSpellDamage: + case SpellEffect::MitigateSpellDamage: { if (WornType) { new_bonus->MitigateSpellRune[SBIndex::MITIGATION_RUNE_PERCENT] += effect_value; @@ -3304,7 +3087,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_MitigateDotDamage: + case SpellEffect::MitigateDotDamage: { if (WornType) { new_bonus->MitigateDotRune[SBIndex::MITIGATION_RUNE_PERCENT] += effect_value; @@ -3319,7 +3102,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_ManaAbsorbPercentDamage: + case SpellEffect::ManaAbsorbPercentDamage: { if (new_bonus->ManaAbsorbPercentDamage < effect_value){ new_bonus->ManaAbsorbPercentDamage = effect_value; @@ -3327,7 +3110,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_Endurance_Absorb_Pct_Damage: + case SpellEffect::Endurance_Absorb_Pct_Damage: { if (new_bonus->EnduranceAbsorbPercentDamage[SBIndex::ENDURANCE_ABSORD_MITIGIATION] < effect_value) { new_bonus->EnduranceAbsorbPercentDamage[SBIndex::ENDURANCE_ABSORD_MITIGIATION] = effect_value; @@ -3336,7 +3119,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_Shield_Target: + case SpellEffect::Shield_Target: { if (new_bonus->ShieldTargetSpa[SBIndex::SHIELD_TARGET_MITIGATION_PERCENT] < effect_value) { new_bonus->ShieldTargetSpa[SBIndex::SHIELD_TARGET_MITIGATION_PERCENT] = effect_value; @@ -3345,27 +3128,27 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_TriggerMeleeThreshold: + case SpellEffect::TriggerMeleeThreshold: new_bonus->TriggerMeleeThreshold = true; break; - case SE_TriggerSpellThreshold: + case SpellEffect::TriggerSpellThreshold: new_bonus->TriggerSpellThreshold = true; break; - case SE_ShieldBlock: + case SpellEffect::ShieldBlock: new_bonus->ShieldBlock += effect_value; break; - case SE_ShieldEquipDmgMod: + case SpellEffect::ShieldEquipDmgMod: new_bonus->ShieldEquipDmgMod += effect_value; break; - case SE_BlockBehind: + case SpellEffect::BlockBehind: new_bonus->BlockBehind += effect_value; break; - case SE_Blind: + case SpellEffect::Blind: if (!RuleB(Combat, AllowRaidTargetBlind) && IsRaidTarget()) { // do not blind raid targets break; } @@ -3373,93 +3156,93 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne new_bonus->IsBlind = true; break; - case SE_Fear: + case SpellEffect::Fear: new_bonus->IsFeared = true; break; //AA bonuses - implemented broadly into spell/item effects - case SE_FrontalStunResist: + case SpellEffect::FrontalStunResist: new_bonus->FrontalStunResist += effect_value; break; - case SE_ImprovedBindWound: + case SpellEffect::ImprovedBindWound: new_bonus->BindWound += effect_value; break; - case SE_MaxBindWound: + case SpellEffect::MaxBindWound: new_bonus->MaxBindWound += effect_value; break; - case SE_BaseMovementSpeed: + case SpellEffect::BaseMovementSpeed: new_bonus->BaseMovementSpeed += effect_value; break; - case SE_IncreaseRunSpeedCap: + case SpellEffect::IncreaseRunSpeedCap: new_bonus->IncreaseRunSpeedCap += effect_value; break; - case SE_DoubleSpecialAttack: + case SpellEffect::DoubleSpecialAttack: new_bonus->DoubleSpecialAttack += effect_value; break; - case SE_TripleBackstab: + case SpellEffect::TripleBackstab: new_bonus->TripleBackstab += effect_value; break; - case SE_FrontalBackstabMinDmg: + case SpellEffect::FrontalBackstabMinDmg: new_bonus->FrontalBackstabMinDmg = true; break; - case SE_FrontalBackstabChance: + case SpellEffect::FrontalBackstabChance: new_bonus->FrontalBackstabChance += effect_value; break; - case SE_Double_Backstab_Front: + case SpellEffect::Double_Backstab_Front: new_bonus->Double_Backstab_Front += effect_value; break; - case SE_ConsumeProjectile: + case SpellEffect::ConsumeProjectile: new_bonus->ConsumeProjectile += effect_value; break; - case SE_ForageAdditionalItems: + case SpellEffect::ForageAdditionalItems: new_bonus->ForageAdditionalItems += effect_value; break; - case SE_Salvage: + case SpellEffect::Salvage: new_bonus->SalvageChance += effect_value; break; - case SE_ArcheryDamageModifier: + case SpellEffect::ArcheryDamageModifier: new_bonus->ArcheryDamageModifier += effect_value; break; - case SE_DoubleRangedAttack: + case SpellEffect::DoubleRangedAttack: new_bonus->DoubleRangedAttack += effect_value; break; - case SE_SecondaryDmgInc: + case SpellEffect::SecondaryDmgInc: new_bonus->SecondaryDmgInc = true; break; - case SE_StrikeThrough: - case SE_StrikeThrough2: + case SpellEffect::StrikeThrough: + case SpellEffect::StrikeThrough2: new_bonus->StrikeThrough += effect_value; break; - case SE_GiveDoubleAttack: + case SpellEffect::GiveDoubleAttack: new_bonus->GiveDoubleAttack += effect_value; break; - case SE_PetCriticalHit: + case SpellEffect::PetCriticalHit: new_bonus->PetCriticalHit += effect_value; break; - case SE_CombatStability: + case SpellEffect::CombatStability: new_bonus->CombatStability += effect_value; break; - case SE_AddSingingMod: + case SpellEffect::AddSingingMod: switch (limit_value) { case EQ::item::ItemTypeWindInstrument: new_bonus->windMod += effect_value; @@ -3481,76 +3264,76 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne } break; - case SE_SongModCap: + case SpellEffect::SongModCap: new_bonus->songModCap += effect_value; break; - case SE_PetAvoidance: + case SpellEffect::PetAvoidance: new_bonus->PetAvoidance += effect_value; break; - case SE_Ambidexterity: + case SpellEffect::Ambidexterity: new_bonus->Ambidexterity += effect_value; break; - case SE_PetMaxHP: + case SpellEffect::PetMaxHP: new_bonus->PetMaxHP += effect_value; break; - case SE_PetFlurry: + case SpellEffect::PetFlurry: new_bonus->PetFlurry += effect_value; break; - case SE_GivePetGroupTarget: + case SpellEffect::GivePetGroupTarget: new_bonus->GivePetGroupTarget = true; break; - case SE_RootBreakChance: + case SpellEffect::RootBreakChance: new_bonus->RootBreakChance += effect_value; break; - case SE_ChannelChanceItems: + case SpellEffect::ChannelChanceItems: new_bonus->ChannelChanceItems += effect_value; break; - case SE_ChannelChanceSpells: + case SpellEffect::ChannelChanceSpells: new_bonus->ChannelChanceSpells += effect_value; break; - case SE_UnfailingDivinity: + case SpellEffect::UnfailingDivinity: new_bonus->UnfailingDivinity += effect_value; break; - case SE_ItemHPRegenCapIncrease: + case SpellEffect::ItemHPRegenCapIncrease: new_bonus->ItemHPRegenCap += effect_value; break; - case SE_OffhandRiposteFail: + case SpellEffect::OffhandRiposteFail: new_bonus->OffhandRiposteFail += effect_value; break; - case SE_ItemAttackCapIncrease: + case SpellEffect::ItemAttackCapIncrease: new_bonus->ItemATKCap += effect_value; break; - case SE_TwoHandBluntBlock: + case SpellEffect::TwoHandBluntBlock: new_bonus->TwoHandBluntBlock += effect_value; break; - case SE_StunBashChance: + case SpellEffect::StunBashChance: new_bonus->StunBashChance += effect_value; break; - case SE_IncreaseChanceMemwipe: + case SpellEffect::IncreaseChanceMemwipe: new_bonus->IncreaseChanceMemwipe += effect_value; break; - case SE_CriticalMend: + case SpellEffect::CriticalMend: new_bonus->CriticalMend += effect_value; break; - case SE_SpellEffectResistChance: + case SpellEffect::SpellEffectResistChance: { for(int e = 0; e < MAX_RESISTABLE_EFFECTS*2; e+=2) { @@ -3570,20 +3353,20 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_MasteryofPast: + case SpellEffect::MasteryofPast: { if(new_bonus->MasteryofPast < effect_value) new_bonus->MasteryofPast = effect_value; break; } - case SE_DoubleRiposte: + case SpellEffect::DoubleRiposte: { new_bonus->DoubleRiposte += effect_value; break; } - case SE_GiveDoubleRiposte: + case SpellEffect::GiveDoubleRiposte: { //Only allow for regular double riposte chance. if(new_bonus->GiveDoubleRiposte[limit_value] == 0){ @@ -3593,7 +3376,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_SlayUndead: { + case SpellEffect::SlayUndead: { if (new_bonus->SlayUndead[SBIndex::SLAYUNDEAD_DMG_MOD] < effect_value) { new_bonus->SlayUndead[SBIndex::SLAYUNDEAD_RATE_MOD] = limit_value; // Rate new_bonus->SlayUndead[SBIndex::SLAYUNDEAD_DMG_MOD] = effect_value; // Damage Modifier @@ -3601,16 +3384,16 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_TriggerOnReqTarget: - case SE_TriggerOnReqCaster: + case SpellEffect::TriggerOnReqTarget: + case SpellEffect::TriggerOnReqCaster: new_bonus->TriggerOnCastRequirement = true; break; - case SE_DivineAura: + case SpellEffect::DivineAura: new_bonus->DivineAura = true; break; - case SE_ImprovedTaunt: + case SpellEffect::ImprovedTaunt: if (new_bonus->ImprovedTaunt[SBIndex::IMPROVED_TAUNT_MAX_LVL] < effect_value) { new_bonus->ImprovedTaunt[SBIndex::IMPROVED_TAUNT_MAX_LVL] = effect_value; new_bonus->ImprovedTaunt[SBIndex::IMPROVED_TAUNT_AGGRO_MOD] = limit_value; @@ -3619,15 +3402,15 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; - case SE_DistanceRemoval: + case SpellEffect::DistanceRemoval: new_bonus->DistanceRemoval = true; break; - case SE_FrenziedDevastation: + case SpellEffect::FrenziedDevastation: new_bonus->FrenziedDevastation += limit_value; break; - case SE_Root: + case SpellEffect::Root: if (new_bonus->Root[SBIndex::ROOT_EXISTS] && (new_bonus->Root[SBIndex::ROOT_BUFFSLOT] > buffslot)){ new_bonus->Root[SBIndex::ROOT_EXISTS] = 1; new_bonus->Root[SBIndex::ROOT_BUFFSLOT] = buffslot; @@ -3638,7 +3421,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne } break; - case SE_Rune: + case SpellEffect::Rune: if (new_bonus->MeleeRune[SBIndex::RUNE_AMOUNT] && (new_bonus->MeleeRune[SBIndex::RUNE_BUFFSLOT] > buffslot)){ @@ -3652,7 +3435,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; - case SE_AbsorbMagicAtt: + case SpellEffect::AbsorbMagicAtt: if (new_bonus->AbsorbMagicAtt[SBIndex::RUNE_AMOUNT] && (new_bonus->AbsorbMagicAtt[SBIndex::RUNE_BUFFSLOT] > buffslot)){ new_bonus->AbsorbMagicAtt[SBIndex::RUNE_AMOUNT] = effect_value; new_bonus->AbsorbMagicAtt[SBIndex::RUNE_BUFFSLOT] = buffslot; @@ -3663,15 +3446,15 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne } break; - case SE_NegateIfCombat: + case SpellEffect::NegateIfCombat: new_bonus->NegateIfCombat = true; break; - case SE_Screech: + case SpellEffect::Screech: new_bonus->Screech = effect_value; break; - case SE_AlterNPCLevel: + case SpellEffect::AlterNPCLevel: if (IsNPC()){ if (!new_bonus->AlterNPCLevel @@ -3691,36 +3474,36 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne } break; - case SE_AStacker: + case SpellEffect::AStacker: new_bonus->AStacker[SBIndex::BUFFSTACKER_EXISTS] = 1; new_bonus->AStacker[SBIndex::BUFFSTACKER_VALUE] = effect_value; break; - case SE_BStacker: + case SpellEffect::BStacker: new_bonus->BStacker[SBIndex::BUFFSTACKER_EXISTS] = 1; new_bonus->BStacker[SBIndex::BUFFSTACKER_VALUE] = effect_value; break; - case SE_CStacker: + case SpellEffect::CStacker: new_bonus->CStacker[SBIndex::BUFFSTACKER_EXISTS] = 1; new_bonus->CStacker[SBIndex::BUFFSTACKER_VALUE] = effect_value; break; - case SE_DStacker: + case SpellEffect::DStacker: new_bonus->DStacker[SBIndex::BUFFSTACKER_EXISTS] = 1; new_bonus->DStacker[SBIndex::BUFFSTACKER_VALUE] = effect_value; break; - case SE_Berserk: + case SpellEffect::Berserk: new_bonus->BerserkSPA = true; break; - case SE_Metabolism: + case SpellEffect::Metabolism: new_bonus->Metabolism += effect_value; break; - case SE_ImprovedReclaimEnergy: + case SpellEffect::ImprovedReclaimEnergy: { if((effect_value < 0) && (new_bonus->ImprovedReclaimEnergy > effect_value)) new_bonus->ImprovedReclaimEnergy = effect_value; @@ -3730,7 +3513,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_HeadShot: + case SpellEffect::HeadShot: { if(new_bonus->HeadShot[SBIndex::FINISHING_EFFECT_DMG] < limit_value){ new_bonus->HeadShot[SBIndex::FINISHING_EFFECT_PROC_CHANCE] = effect_value; @@ -3739,7 +3522,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_HeadShotLevel: { + case SpellEffect::HeadShotLevel: { if (new_bonus->HSLevel[SBIndex::FINISHING_EFFECT_LEVEL_MAX] < effect_value) { new_bonus->HSLevel[SBIndex::FINISHING_EFFECT_LEVEL_MAX] = effect_value; new_bonus->HSLevel[SBIndex::FINISHING_EFFECT_LEVEL_CHANCE_BONUS] = limit_value; @@ -3747,7 +3530,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_Assassinate: + case SpellEffect::Assassinate: { if(new_bonus->Assassinate[SBIndex::FINISHING_EFFECT_DMG] < limit_value){ new_bonus->Assassinate[SBIndex::FINISHING_EFFECT_PROC_CHANCE] = effect_value; @@ -3756,7 +3539,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_AssassinateLevel: + case SpellEffect::AssassinateLevel: { if(new_bonus->AssassinateLevel[SBIndex::FINISHING_EFFECT_LEVEL_MAX] < effect_value) { new_bonus->AssassinateLevel[SBIndex::FINISHING_EFFECT_LEVEL_MAX] = effect_value; @@ -3765,7 +3548,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_FinishingBlow: + case SpellEffect::FinishingBlow: { //base1 = chance, base2 = damage if (new_bonus->FinishingBlow[SBIndex::FINISHING_EFFECT_DMG] < limit_value){ @@ -3775,7 +3558,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_FinishingBlowLvl: + case SpellEffect::FinishingBlowLvl: { if (new_bonus->FinishingBlowLvl[SBIndex::FINISHING_EFFECT_LEVEL_MAX] < effect_value){ new_bonus->FinishingBlowLvl[SBIndex::FINISHING_EFFECT_LEVEL_MAX] = effect_value; @@ -3784,15 +3567,15 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_PetMeleeMitigation: + case SpellEffect::PetMeleeMitigation: new_bonus->PetMeleeMitigation += effect_value; break; - case SE_Sanctuary: + case SpellEffect::Sanctuary: new_bonus->Sanctuary = true; break; - case SE_FactionModPct: + case SpellEffect::FactionModPct: { if((effect_value < 0) && (new_bonus->FactionModPct > effect_value)) new_bonus->FactionModPct = effect_value; @@ -3802,15 +3585,15 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_Illusion: + case SpellEffect::Illusion: new_bonus->Illusion = spell_id; break; - case SE_IllusionPersistence: + case SpellEffect::IllusionPersistence: new_bonus->IllusionPersistence = effect_value; break; - case SE_LimitToSkill: { + case SpellEffect::LimitToSkill: { // Bad data or unsupported new skill if (effect_value > EQ::skills::HIGHEST_SKILL) { break; @@ -3822,7 +3605,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_SkillProcAttempt:{ + case SpellEffect::SkillProcAttempt:{ for(int e = 0; e < MAX_SKILL_PROCS; e++) { @@ -3838,7 +3621,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_SkillProcSuccess:{ + case SpellEffect::SkillProcSuccess:{ for(int e = 0; e < MAX_SKILL_PROCS; e++) { @@ -3853,7 +3636,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_SkillAttackProc: { + case SpellEffect::SkillAttackProc: { for (int i = 0; i < MAX_CAST_ON_SKILL_USE; i += 3) { if (!new_bonus->SkillAttackProc[i + SBIndex::SKILLATK_PROC_SPELL_ID]) { // spell id new_bonus->SkillAttackProc[i + SBIndex::SKILLATK_PROC_SPELL_ID] = max_value; // spell to proc @@ -3869,33 +3652,33 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_PC_Pet_Rampage: { + case SpellEffect::PC_Pet_Rampage: { new_bonus->PC_Pet_Rampage[SBIndex::PET_RAMPAGE_CHANCE] += effect_value; //Chance to rampage if (new_bonus->PC_Pet_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] < limit_value) new_bonus->PC_Pet_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] = limit_value; //Damage modifer - take highest break; } - case SE_PC_Pet_AE_Rampage: { + case SpellEffect::PC_Pet_AE_Rampage: { new_bonus->PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_CHANCE] += effect_value; //Chance to rampage if (new_bonus->PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] < limit_value) new_bonus->PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] = limit_value; //Damage modifer - take highest break; } - case SE_PC_Pet_Flurry_Chance: + case SpellEffect::PC_Pet_Flurry_Chance: new_bonus->PC_Pet_Flurry += effect_value; //Chance to Flurry break; - case SE_ShroudofStealth: + case SpellEffect::ShroudofStealth: new_bonus->ShroudofStealth = true; break; - case SE_ReduceFallDamage: + case SpellEffect::ReduceFallDamage: new_bonus->ReduceFallDamage += effect_value; break; - case SE_ReduceTradeskillFail:{ + case SpellEffect::ReduceTradeskillFail:{ if (limit_value > EQ::skills::HIGHEST_SKILL) break; @@ -3904,12 +3687,12 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_TradeSkillMastery: + case SpellEffect::TradeSkillMastery: if (new_bonus->TradeSkillMastery < effect_value) new_bonus->TradeSkillMastery = effect_value; break; - case SE_RaiseSkillCap: { + case SpellEffect::RaiseSkillCap: { if (limit_value > EQ::skills::HIGHEST_SKILL) break; @@ -3918,40 +3701,40 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_NoBreakAESneak: + case SpellEffect::NoBreakAESneak: if (new_bonus->NoBreakAESneak < effect_value) new_bonus->NoBreakAESneak = effect_value; break; - case SE_FeignedCastOnChance: + case SpellEffect::FeignedCastOnChance: if (new_bonus->FeignedCastOnChance < effect_value) new_bonus->FeignedCastOnChance = effect_value; break; - case SE_AdditionalAura: + case SpellEffect::AdditionalAura: if (new_bonus->aura_slots < effect_value) new_bonus->aura_slots = effect_value; break; - case SE_IncreaseTrapCount: + case SpellEffect::IncreaseTrapCount: if (new_bonus->trap_slots < effect_value) new_bonus->trap_slots = effect_value; break; - case SE_Attack_Accuracy_Max_Percent: + case SpellEffect::Attack_Accuracy_Max_Percent: new_bonus->Attack_Accuracy_Max_Percent += effect_value; break; - case SE_AC_Mitigation_Max_Percent: + case SpellEffect::AC_Mitigation_Max_Percent: new_bonus->AC_Mitigation_Max_Percent += effect_value; break; - case SE_AC_Avoidance_Max_Percent: + case SpellEffect::AC_Avoidance_Max_Percent: new_bonus->AC_Avoidance_Max_Percent += effect_value; break; - case SE_Damage_Taken_Position_Mod: + case SpellEffect::Damage_Taken_Position_Mod: { //Mitigate if damage taken from behind base2 = 0, from front base2 = 1 if (limit_value < 0 || limit_value >= 2) { @@ -3966,7 +3749,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_Melee_Damage_Position_Mod: + case SpellEffect::Melee_Damage_Position_Mod: { //Increase damage by percent from behind base2 = 0, from front base2 = 1 if (limit_value < 0 || limit_value >= 2) { @@ -3981,7 +3764,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_Damage_Taken_Position_Amt: + case SpellEffect::Damage_Taken_Position_Amt: { //Mitigate if damage taken from behind base2 = 0, from front base2 = 1 if (limit_value < 0 || limit_value >= 2) { @@ -3992,7 +3775,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_Melee_Damage_Position_Amt: + case SpellEffect::Melee_Damage_Position_Amt: { //Mitigate if damage taken from behind base2 = 0, from front base2 = 1 if (limit_value < 0 || limit_value >= 2) { @@ -4003,23 +3786,23 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_DS_Mitigation_Amount: + case SpellEffect::DS_Mitigation_Amount: new_bonus->DS_Mitigation_Amount += effect_value; break; - case SE_DS_Mitigation_Percentage: + case SpellEffect::DS_Mitigation_Percentage: new_bonus->DS_Mitigation_Percentage += effect_value; break; - case SE_Pet_Crit_Melee_Damage_Pct_Owner: + case SpellEffect::Pet_Crit_Melee_Damage_Pct_Owner: new_bonus->Pet_Crit_Melee_Damage_Pct_Owner += effect_value; break; - case SE_Pet_Add_Atk: + case SpellEffect::Pet_Add_Atk: new_bonus->Pet_Add_Atk += effect_value; break; - case SE_ExtendedShielding: + case SpellEffect::ExtendedShielding: { if (AdditiveWornBonus) { new_bonus->ExtendedShielding += effect_value; @@ -4033,7 +3816,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_ShieldDuration: + case SpellEffect::ShieldDuration: { if (AdditiveWornBonus) { new_bonus->ShieldDuration += effect_value; @@ -4047,15 +3830,15 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_Worn_Endurance_Regen_Cap: + case SpellEffect::Worn_Endurance_Regen_Cap: new_bonus->ItemEnduranceRegenCap += effect_value; break; - case SE_ItemManaRegenCapIncrease: + case SpellEffect::ItemManaRegenCapIncrease: new_bonus->ItemManaRegenCap += effect_value; break; - case SE_Weapon_Stance: { + case SpellEffect::Weapon_Stance: { if (IsValidSpell(effect_value)) { //base1 is the spell_id of buff if (limit_value <= WEAPON_STANCE_TYPE_MAX) { //0=2H, 1=Shield, 2=DW if (IsValidSpell(new_bonus->WeaponStance[limit_value])) { //Check if we already a spell_id saved for this effect @@ -4087,42 +3870,42 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_Invisibility: - case SE_Invisibility2: + case SpellEffect::Invisibility: + case SpellEffect::Invisibility2: effect_value = std::min({ effect_value, MAX_INVISIBILTY_LEVEL }); if (new_bonus->invisibility < effect_value) new_bonus->invisibility = effect_value; break; - case SE_InvisVsUndead: - case SE_InvisVsUndead2: + case SpellEffect::InvisVsUndead: + case SpellEffect::InvisVsUndead2: if (new_bonus->invisibility_verse_undead < effect_value) new_bonus->invisibility_verse_undead = effect_value; break; - case SE_InvisVsAnimals: - case SE_ImprovedInvisAnimals: + case SpellEffect::InvisVsAnimals: + case SpellEffect::ImprovedInvisAnimals: effect_value = std::min({ effect_value, MAX_INVISIBILTY_LEVEL }); if (new_bonus->invisibility_verse_animal < effect_value) new_bonus->invisibility_verse_animal = effect_value; break; - case SE_SeeInvis: + case SpellEffect::SeeInvis: effect_value = std::min({ effect_value, MAX_INVISIBILTY_LEVEL }); if (new_bonus->SeeInvis < effect_value) { new_bonus->SeeInvis = effect_value; } break; - case SE_ZoneSuspendMinion: + case SpellEffect::ZoneSuspendMinion: new_bonus->ZoneSuspendMinion = effect_value; break; - case SE_CompleteHeal: + case SpellEffect::CompleteHeal: new_bonus->CompleteHealBuffBlocker = true; break; - case SE_TrapCircumvention: + case SpellEffect::TrapCircumvention: new_bonus->TrapCircumvention += effect_value; break; } @@ -4345,93 +4128,93 @@ uint8 Mob::IsFocusEffect(uint16 spell_id,int effect_index, bool AA,uint32 aa_eff switch (effect) { - case SE_ImprovedDamage: + case SpellEffect::ImprovedDamage: return focusImprovedDamage; - case SE_ImprovedDamage2: + case SpellEffect::ImprovedDamage2: return focusImprovedDamage2; - case SE_ImprovedHeal: + case SpellEffect::ImprovedHeal: return focusImprovedHeal; - case SE_ReduceManaCost: + case SpellEffect::ReduceManaCost: return focusManaCost; - case SE_IncreaseSpellHaste: + case SpellEffect::IncreaseSpellHaste: return focusSpellHaste; - case SE_IncreaseSpellDuration: + case SpellEffect::IncreaseSpellDuration: return focusSpellDuration; - case SE_SpellDurationIncByTic: + case SpellEffect::SpellDurationIncByTic: return focusSpellDurByTic; - case SE_SwarmPetDuration: + case SpellEffect::SwarmPetDuration: return focusSwarmPetDuration; - case SE_IncreaseRange: + case SpellEffect::IncreaseRange: return focusRange; - case SE_ReduceReagentCost: + case SpellEffect::ReduceReagentCost: return focusReagentCost; - case SE_PetPowerIncrease: + case SpellEffect::PetPowerIncrease: return focusPetPower; - case SE_SpellResistReduction: + case SpellEffect::SpellResistReduction: return focusResistRate; - case SE_Fc_ResistIncoming: + case SpellEffect::Fc_ResistIncoming: return focusFcResistIncoming; - case SE_Fc_Amplify_Mod: + case SpellEffect::Fc_Amplify_Mod: return focusFcAmplifyMod; - case SE_Fc_Amplify_Amt: + case SpellEffect::Fc_Amplify_Amt: return focusFcAmplifyAmt; - case SE_SpellHateMod: + case SpellEffect::SpellHateMod: return focusSpellHateMod; - case SE_ReduceReuseTimer: + case SpellEffect::ReduceReuseTimer: return focusReduceRecastTime; - case SE_TriggerOnCast: + case SpellEffect::TriggerOnCast: return focusTriggerOnCast; - case SE_FcSpellVulnerability: + case SpellEffect::FcSpellVulnerability: return focusSpellVulnerability; - case SE_Fc_Spell_Damage_Pct_IncomingPC: + case SpellEffect::Fc_Spell_Damage_Pct_IncomingPC: return focusFcSpellDamagePctIncomingPC; - case SE_BlockNextSpellFocus: + case SpellEffect::BlockNextSpellFocus: return focusBlockNextSpell; - case SE_FcTwincast: + case SpellEffect::FcTwincast: return focusTwincast; - case SE_SympatheticProc: + case SpellEffect::SympatheticProc: return focusSympatheticProc; - case SE_FcDamageAmt: + case SpellEffect::FcDamageAmt: return focusFcDamageAmt; - case SE_FcDamageAmt2: + case SpellEffect::FcDamageAmt2: return focusFcDamageAmt2; - case SE_FcDamageAmtCrit: + case SpellEffect::FcDamageAmtCrit: return focusFcDamageAmtCrit; - case SE_FcDamagePctCrit: + case SpellEffect::FcDamagePctCrit: return focusFcDamagePctCrit; - case SE_FcDamageAmtIncoming: + case SpellEffect::FcDamageAmtIncoming: return focusFcDamageAmtIncoming; - case SE_Fc_Spell_Damage_Amt_IncomingPC: + case SpellEffect::Fc_Spell_Damage_Amt_IncomingPC: return focusFcSpellDamageAmtIncomingPC; - case SE_FcHealAmtIncoming: + case SpellEffect::FcHealAmtIncoming: return focusFcHealAmtIncoming; - case SE_FcHealPctIncoming: + case SpellEffect::FcHealPctIncoming: return focusFcHealPctIncoming; - case SE_FcBaseEffects: + case SpellEffect::FcBaseEffects: return focusFcBaseEffects; - case SE_FcIncreaseNumHits: + case SpellEffect::FcIncreaseNumHits: return focusIncreaseNumHits; - case SE_FcLimitUse: + case SpellEffect::FcLimitUse: return focusFcLimitUse; - case SE_FcMute: + case SpellEffect::FcMute: return focusFcMute; - case SE_FcTimerRefresh: + case SpellEffect::FcTimerRefresh: return focusFcTimerRefresh; - case SE_FcTimerLockout: + case SpellEffect::FcTimerLockout: return focusFcTimerLockout; - case SE_Fc_Cast_Spell_On_Land: + case SpellEffect::Fc_Cast_Spell_On_Land: return focusFcCastSpellOnLand; - case SE_FcStunTimeMod: + case SpellEffect::FcStunTimeMod: return focusFcStunTimeMod; - case SE_Fc_CastTimeMod2: + case SpellEffect::Fc_CastTimeMod2: return focusFcCastTimeMod2; - case SE_Fc_CastTimeAmt: + case SpellEffect::Fc_CastTimeAmt: return focusFcCastTimeAmt; - case SE_FcHealPctCritIncoming: + case SpellEffect::FcHealPctCritIncoming: return focusFcHealPctCritIncoming; - case SE_FcHealAmt: + case SpellEffect::FcHealAmt: return focusFcHealAmt; - case SE_FcHealAmtCrit: + case SpellEffect::FcHealAmtCrit: return focusFcHealAmtCrit; } @@ -4451,7 +4234,7 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) bool negate_aabonus = false; bool negate_itembonus = false; - if (spells[spell_id].effect_id[i] == SE_NegateSpellEffect) { + if (spells[spell_id].effect_id[i] == SpellEffect::NegateSpellEffect) { //Set negate types switch (spells[spell_id].base_value[i]) @@ -4503,43 +4286,43 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) //Negate bonuses switch (spells[spell_id].limit_value[i]) { - case SE_CurrentHP: + case SpellEffect::CurrentHP: if (negate_spellbonus) { spellbonuses.HPRegen = effect_value; } if (negate_aabonus) { aabonuses.HPRegen = effect_value; } if (negate_itembonus) { itembonuses.HPRegen = effect_value; } break; - case SE_CurrentEndurance: + case SpellEffect::CurrentEndurance: if (negate_spellbonus) { spellbonuses.EnduranceRegen = effect_value; } if (negate_aabonus) { aabonuses.EnduranceRegen = effect_value; } if (negate_itembonus) { itembonuses.EnduranceRegen = effect_value; } break; - case SE_ChangeFrenzyRad: + case SpellEffect::ChangeFrenzyRad: if (negate_spellbonus) { spellbonuses.AggroRange = static_cast(effect_value); } if (negate_aabonus) { aabonuses.AggroRange = static_cast(effect_value); } if (negate_itembonus) { itembonuses.AggroRange = static_cast(effect_value); } break; - case SE_Harmony: + case SpellEffect::Harmony: if (negate_spellbonus) { spellbonuses.AssistRange = static_cast(effect_value); } if (negate_aabonus) { aabonuses.AssistRange = static_cast(effect_value); } if (negate_itembonus) { itembonuses.AssistRange = static_cast(effect_value); } break; - case SE_AttackSpeed: + case SpellEffect::AttackSpeed: if (negate_spellbonus) { spellbonuses.haste = effect_value; } if (negate_aabonus) { aabonuses.haste = effect_value; } if (negate_itembonus) { itembonuses.haste = effect_value; } break; - case SE_AttackSpeed2: + case SpellEffect::AttackSpeed2: if (negate_spellbonus) { spellbonuses.hastetype2 = effect_value; } if (negate_aabonus) { aabonuses.hastetype2 = effect_value; } if (negate_itembonus) { itembonuses.hastetype2 = effect_value; } break; - case SE_AttackSpeed3: + case SpellEffect::AttackSpeed3: { if (negate_spellbonus) { spellbonuses.hastetype3 = effect_value; } if (negate_aabonus) { aabonuses.hastetype3 = effect_value; } @@ -4547,99 +4330,99 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; } - case SE_AttackSpeed4: + case SpellEffect::AttackSpeed4: if (negate_spellbonus) { spellbonuses.inhibitmelee = effect_value; } if (negate_aabonus) { aabonuses.inhibitmelee = effect_value; } if (negate_itembonus) { itembonuses.inhibitmelee = effect_value; } break; - case SE_IncreaseArchery: + case SpellEffect::IncreaseArchery: if (negate_spellbonus) { spellbonuses.increase_archery = effect_value; } if (negate_aabonus) { aabonuses.increase_archery = effect_value; } if (negate_itembonus) { itembonuses.increase_archery = effect_value; } break; - case SE_TotalHP: + case SpellEffect::TotalHP: if (negate_spellbonus) { spellbonuses.FlatMaxHPChange = effect_value; } if (negate_aabonus) { aabonuses.FlatMaxHPChange = effect_value; } if (negate_itembonus) { itembonuses.FlatMaxHPChange = effect_value; } break; - case SE_ManaRegen_v2: - case SE_CurrentMana: + case SpellEffect::ManaRegen_v2: + case SpellEffect::CurrentMana: if (negate_spellbonus) { spellbonuses.ManaRegen = effect_value; } if (negate_aabonus) { aabonuses.ManaRegen = effect_value; } if (negate_itembonus) { itembonuses.ManaRegen = effect_value; } break; - case SE_ManaPool: + case SpellEffect::ManaPool: if (negate_spellbonus) { spellbonuses.Mana = effect_value; } if (negate_itembonus) { itembonuses.Mana = effect_value; } if (negate_aabonus) { aabonuses.Mana = effect_value; } break; - case SE_Stamina: + case SpellEffect::Stamina: if (negate_spellbonus) { spellbonuses.EnduranceReduction = effect_value; } if (negate_itembonus) { itembonuses.EnduranceReduction = effect_value; } if (negate_aabonus) { aabonuses.EnduranceReduction = effect_value; } break; - case SE_ACv2: - case SE_ArmorClass: + case SpellEffect::ACv2: + case SpellEffect::ArmorClass: if (negate_spellbonus) { spellbonuses.AC = effect_value; } if (negate_aabonus) { aabonuses.AC = effect_value; } if (negate_itembonus) { itembonuses.AC = effect_value; } break; - case SE_ATK: + case SpellEffect::ATK: if (negate_spellbonus) { spellbonuses.ATK = effect_value; } if (negate_aabonus) { aabonuses.ATK = effect_value; } if (negate_itembonus) { itembonuses.ATK = effect_value; } break; - case SE_STR: + case SpellEffect::STR: if (negate_spellbonus) { spellbonuses.STR = effect_value; } if (negate_itembonus) { itembonuses.STR = effect_value; } if (negate_aabonus) { aabonuses.STR = effect_value; } break; - case SE_DEX: + case SpellEffect::DEX: if (negate_spellbonus) { spellbonuses.DEX = effect_value; } if (negate_aabonus) { aabonuses.DEX = effect_value; } if (negate_itembonus) { itembonuses.DEX = effect_value; } break; - case SE_AGI: + case SpellEffect::AGI: if (negate_itembonus) { itembonuses.AGI = effect_value; } if (negate_aabonus) { aabonuses.AGI = effect_value; } if (negate_spellbonus) { spellbonuses.AGI = effect_value; } break; - case SE_STA: + case SpellEffect::STA: if (negate_spellbonus) { spellbonuses.STA = effect_value; } if (negate_itembonus) { itembonuses.STA = effect_value; } if (negate_aabonus) { aabonuses.STA = effect_value; } break; - case SE_INT: + case SpellEffect::INT: if (negate_spellbonus) { spellbonuses.INT = effect_value; } if (negate_aabonus) { aabonuses.INT = effect_value; } if (negate_itembonus) { itembonuses.INT = effect_value; } break; - case SE_WIS: + case SpellEffect::WIS: if (negate_spellbonus) { spellbonuses.WIS = effect_value; } if (negate_aabonus) { aabonuses.WIS = effect_value; } if (negate_itembonus) { itembonuses.WIS = effect_value; } break; - case SE_CHA: + case SpellEffect::CHA: if (negate_itembonus) { itembonuses.CHA = effect_value; } if (negate_spellbonus) { spellbonuses.CHA = effect_value; } if (negate_aabonus) { aabonuses.CHA = effect_value; } break; - case SE_AllStats: + case SpellEffect::AllStats: { if (negate_spellbonus) { spellbonuses.STR = effect_value; @@ -4674,37 +4457,37 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; } - case SE_ResistFire: + case SpellEffect::ResistFire: if (negate_spellbonus) { spellbonuses.FR = effect_value; } if (negate_itembonus) { itembonuses.FR = effect_value; } if (negate_aabonus) { aabonuses.FR = effect_value; } break; - case SE_ResistCold: + case SpellEffect::ResistCold: if (negate_spellbonus) { spellbonuses.CR = effect_value; } if (negate_aabonus) { aabonuses.CR = effect_value; } if (negate_itembonus) { itembonuses.CR = effect_value; } break; - case SE_ResistPoison: + case SpellEffect::ResistPoison: if (negate_spellbonus) { spellbonuses.PR = effect_value; } if (negate_aabonus) { aabonuses.PR = effect_value; } if (negate_itembonus) { itembonuses.PR = effect_value; } break; - case SE_ResistDisease: + case SpellEffect::ResistDisease: if (negate_spellbonus) { spellbonuses.DR = effect_value; } if (negate_itembonus) { itembonuses.DR = effect_value; } if (negate_aabonus) { aabonuses.DR = effect_value; } break; - case SE_ResistMagic: + case SpellEffect::ResistMagic: if (negate_spellbonus) { spellbonuses.MR = effect_value; } if (negate_aabonus) { aabonuses.MR = effect_value; } if (negate_itembonus) { itembonuses.MR = effect_value; } break; - case SE_ResistAll: + case SpellEffect::ResistAll: { if (negate_spellbonus) { spellbonuses.MR = effect_value; @@ -4733,74 +4516,74 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; } - case SE_ResistCorruption: + case SpellEffect::ResistCorruption: if (negate_spellbonus) { spellbonuses.Corrup = effect_value; } if (negate_itembonus) { itembonuses.Corrup = effect_value; } if (negate_aabonus) { aabonuses.Corrup = effect_value; } break; - case SE_CastingLevel: // Brilliance of Ro + case SpellEffect::CastingLevel: // Brilliance of Ro if (negate_spellbonus) { spellbonuses.adjusted_casting_skill = effect_value; } if (negate_aabonus) { aabonuses.adjusted_casting_skill = effect_value; } if (negate_itembonus) { itembonuses.adjusted_casting_skill = effect_value; } break; - case SE_CastingLevel2: + case SpellEffect::CastingLevel2: if (negate_spellbonus) { spellbonuses.effective_casting_level = effect_value; } if (negate_aabonus) { aabonuses.effective_casting_level = effect_value; } if (negate_itembonus) { itembonuses.effective_casting_level = effect_value; } break; - case SE_MovementSpeed: + case SpellEffect::MovementSpeed: if (negate_spellbonus) { spellbonuses.movementspeed = effect_value; } if (negate_aabonus) { aabonuses.movementspeed = effect_value; } if (negate_itembonus) { itembonuses.movementspeed = effect_value; } break; - case SE_SpellDamageShield: + case SpellEffect::SpellDamageShield: if (negate_spellbonus) { spellbonuses.SpellDamageShield = effect_value; } if (negate_aabonus) { aabonuses.SpellDamageShield = effect_value; } if (negate_itembonus) { itembonuses.SpellDamageShield = effect_value; } break; - case SE_DamageShield: + case SpellEffect::DamageShield: if (negate_spellbonus) { spellbonuses.DamageShield = effect_value; } if (negate_aabonus) { aabonuses.DamageShield = effect_value; } if (negate_itembonus) { itembonuses.DamageShield = effect_value; } break; - case SE_ReverseDS: + case SpellEffect::ReverseDS: if (negate_spellbonus) { spellbonuses.ReverseDamageShield = effect_value; } if (negate_aabonus) { aabonuses.ReverseDamageShield = effect_value; } if (negate_itembonus) { itembonuses.ReverseDamageShield = effect_value; } break; - case SE_Reflect: + case SpellEffect::Reflect: if (negate_spellbonus) { spellbonuses.reflect[SBIndex::REFLECT_CHANCE] = effect_value; } if (negate_aabonus) { aabonuses.reflect[SBIndex::REFLECT_CHANCE] = effect_value; } if (negate_itembonus) { itembonuses.reflect[SBIndex::REFLECT_CHANCE] = effect_value; } break; - case SE_Amplification: + case SpellEffect::Amplification: if (negate_spellbonus) { spellbonuses.Amplification = effect_value; } if (negate_itembonus) { itembonuses.Amplification = effect_value; } if (negate_aabonus) { aabonuses.Amplification = effect_value; } break; - case SE_ChangeAggro: + case SpellEffect::ChangeAggro: if (negate_spellbonus) { spellbonuses.hatemod = effect_value; } if (negate_itembonus) { itembonuses.hatemod = effect_value; } if (negate_aabonus) { aabonuses.hatemod = effect_value; } break; - case SE_MeleeMitigation: + case SpellEffect::MeleeMitigation: if (negate_spellbonus) { spellbonuses.MeleeMitigationEffect = effect_value; } if (negate_itembonus) { itembonuses.MeleeMitigationEffect = effect_value; } if (negate_aabonus) { aabonuses.MeleeMitigationEffect = effect_value; } break; - case SE_CriticalHitChance: + case SpellEffect::CriticalHitChance: { for (int e = 0; e < EQ::skills::HIGHEST_SKILL + 1; e++) { @@ -4810,61 +4593,61 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) } } - case SE_CrippBlowChance: + case SpellEffect::CrippBlowChance: if (negate_spellbonus) { spellbonuses.CrippBlowChance = effect_value; } if (negate_aabonus) { aabonuses.CrippBlowChance = effect_value; } if (negate_itembonus) { itembonuses.CrippBlowChance = effect_value; } break; - case SE_AvoidMeleeChance: + case SpellEffect::AvoidMeleeChance: if (negate_spellbonus) { spellbonuses.AvoidMeleeChanceEffect = effect_value; } if (negate_aabonus) { aabonuses.AvoidMeleeChanceEffect = effect_value; } if (negate_itembonus) { itembonuses.AvoidMeleeChanceEffect = effect_value; } break; - case SE_RiposteChance: + case SpellEffect::RiposteChance: if (negate_spellbonus) { spellbonuses.RiposteChance = effect_value; } if (negate_aabonus) { aabonuses.RiposteChance = effect_value; } if (negate_itembonus) { itembonuses.RiposteChance = effect_value; } break; - case SE_DodgeChance: + case SpellEffect::DodgeChance: if (negate_spellbonus) { spellbonuses.DodgeChance = effect_value; } if (negate_aabonus) { aabonuses.DodgeChance = effect_value; } if (negate_itembonus) { itembonuses.DodgeChance = effect_value; } break; - case SE_ParryChance: + case SpellEffect::ParryChance: if (negate_spellbonus) { spellbonuses.ParryChance = effect_value; } if (negate_aabonus) { aabonuses.ParryChance = effect_value; } if (negate_itembonus) { itembonuses.ParryChance = effect_value; } break; - case SE_DualWieldChance: + case SpellEffect::DualWieldChance: if (negate_spellbonus) { spellbonuses.DualWieldChance = effect_value; } if (negate_aabonus) { aabonuses.DualWieldChance = effect_value; } if (negate_itembonus) { itembonuses.DualWieldChance = effect_value; } break; - case SE_DoubleAttackChance: + case SpellEffect::DoubleAttackChance: if (negate_spellbonus) { spellbonuses.DoubleAttackChance = effect_value; } if (negate_aabonus) { aabonuses.DoubleAttackChance = effect_value; } if (negate_itembonus) { itembonuses.DoubleAttackChance = effect_value; } break; - case SE_TripleAttackChance: + case SpellEffect::TripleAttackChance: if (negate_spellbonus) { spellbonuses.TripleAttackChance = effect_value; } if (negate_aabonus) { aabonuses.TripleAttackChance = effect_value; } if (negate_itembonus) { itembonuses.TripleAttackChance = effect_value; } break; - case SE_MeleeLifetap: + case SpellEffect::MeleeLifetap: if (negate_spellbonus) { spellbonuses.MeleeLifetap = effect_value; } if (negate_aabonus) { aabonuses.MeleeLifetap = effect_value; } if (negate_itembonus) { itembonuses.MeleeLifetap = effect_value; } break; - case SE_AllInstrumentMod: + case SpellEffect::AllInstrumentMod: { if (negate_spellbonus) { spellbonuses.singingMod = effect_value; @@ -4892,42 +4675,38 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; } - case SE_ResistSpellChance: + case SpellEffect::ResistSpellChance: if (negate_spellbonus) { spellbonuses.ResistSpellChance = effect_value; } if (negate_aabonus) { aabonuses.ResistSpellChance = effect_value; } if (negate_itembonus) { itembonuses.ResistSpellChance = effect_value; } break; - case SE_ResistFearChance: - if (negate_spellbonus) { - spellbonuses.Fearless = false; - spellbonuses.ResistFearChance = effect_value; - } - + case SpellEffect::ResistFearChance: + if (negate_spellbonus) {spellbonuses.ResistFearChance = effect_value; } if (negate_aabonus) { aabonuses.ResistFearChance = effect_value; } if (negate_itembonus) { itembonuses.ResistFearChance = effect_value; } break; - case SE_Fearless: + case SpellEffect::Fearless: if (negate_spellbonus) { spellbonuses.Fearless = false; } if (negate_aabonus) { aabonuses.Fearless = false; } if (negate_itembonus) { itembonuses.Fearless = false; } break; - case SE_HundredHands: + case SpellEffect::HundredHands: if (negate_spellbonus) { spellbonuses.HundredHands = effect_value; } if (negate_aabonus) { aabonuses.HundredHands = effect_value; } if (negate_itembonus) { itembonuses.HundredHands = effect_value; } break; - case SE_MeleeSkillCheck: + case SpellEffect::MeleeSkillCheck: { if (negate_spellbonus) { spellbonuses.MeleeSkillCheck = effect_value; } if (negate_spellbonus) { spellbonuses.MeleeSkillCheckSkill = effect_value; } break; } - case SE_HitChance: + case SpellEffect::HitChance: { for (int e = 0; e < EQ::skills::HIGHEST_SKILL + 1; e++) { @@ -4938,7 +4717,7 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; } - case SE_DamageModifier: + case SpellEffect::DamageModifier: { for (int e = 0; e < EQ::skills::HIGHEST_SKILL + 1; e++) { @@ -4949,7 +4728,7 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; } - case SE_DamageModifier2: + case SpellEffect::DamageModifier2: { for (int e = 0; e < EQ::skills::HIGHEST_SKILL + 1; e++) { @@ -4960,7 +4739,7 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; } - case SE_Skill_Base_Damage_Mod: + case SpellEffect::Skill_Base_Damage_Mod: { for (int e = 0; e < EQ::skills::HIGHEST_SKILL + 1; e++) { @@ -4972,7 +4751,7 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) } - case SE_MinDamageModifier: + case SpellEffect::MinDamageModifier: { for (int e = 0; e < EQ::skills::HIGHEST_SKILL + 1; e++) { @@ -4983,55 +4762,55 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; } - case SE_StunResist: + case SpellEffect::StunResist: if (negate_spellbonus) { spellbonuses.StunResist = effect_value; } if (negate_aabonus) { aabonuses.StunResist = effect_value; } if (negate_itembonus) { itembonuses.StunResist = effect_value; } break; - case SE_ProcChance: + case SpellEffect::ProcChance: if (negate_spellbonus) { spellbonuses.ProcChanceSPA = effect_value; } if (negate_aabonus) { aabonuses.ProcChanceSPA = effect_value; } if (negate_itembonus) { itembonuses.ProcChanceSPA = effect_value; } break; - case SE_ExtraAttackChance: + case SpellEffect::ExtraAttackChance: if (negate_spellbonus) { spellbonuses.ExtraAttackChance[SBIndex::EXTRA_ATTACK_CHANCE] = effect_value; } if (negate_aabonus) { aabonuses.ExtraAttackChance[SBIndex::EXTRA_ATTACK_CHANCE] = effect_value; } if (negate_itembonus) { itembonuses.ExtraAttackChance[SBIndex::EXTRA_ATTACK_CHANCE] = effect_value; } break; - case SE_AddExtraAttackPct_1h_Primary: + case SpellEffect::AddExtraAttackPct_1h_Primary: if (negate_spellbonus) { spellbonuses.ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_CHANCE] = effect_value; } if (negate_aabonus) { aabonuses.ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_CHANCE] = effect_value; } if (negate_itembonus) { itembonuses.ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_CHANCE] = effect_value; } break; - case SE_AddExtraAttackPct_1h_Secondary: + case SpellEffect::AddExtraAttackPct_1h_Secondary: if (negate_spellbonus) { spellbonuses.ExtraAttackChanceSecondary[SBIndex::EXTRA_ATTACK_CHANCE] = effect_value; } if (negate_aabonus) { aabonuses.ExtraAttackChanceSecondary[SBIndex::EXTRA_ATTACK_CHANCE] = effect_value; } if (negate_itembonus) { itembonuses.ExtraAttackChanceSecondary[SBIndex::EXTRA_ATTACK_CHANCE] = effect_value; } break; - case SE_Double_Melee_Round: + case SpellEffect::Double_Melee_Round: if (negate_spellbonus) { spellbonuses.DoubleMeleeRound[SBIndex::DOUBLE_MELEE_ROUND_CHANCE] = effect_value; } if (negate_aabonus) { aabonuses.DoubleMeleeRound[SBIndex::DOUBLE_MELEE_ROUND_CHANCE] = effect_value; } if (negate_itembonus) { itembonuses.DoubleMeleeRound[SBIndex::DOUBLE_MELEE_ROUND_CHANCE] = effect_value; } break; - case SE_PercentXPIncrease: + case SpellEffect::PercentXPIncrease: if (negate_spellbonus) { spellbonuses.XPRateMod = effect_value; } if (negate_aabonus) { aabonuses.XPRateMod = effect_value; } if (negate_itembonus) { itembonuses.XPRateMod = effect_value; } break; - case SE_Flurry: + case SpellEffect::Flurry: if (negate_spellbonus) { spellbonuses.FlurryChance = effect_value; } if (negate_aabonus) { aabonuses.FlurryChance = effect_value; } if (negate_itembonus) { itembonuses.FlurryChance = effect_value; } break; - case SE_Accuracy: + case SpellEffect::Accuracy: { if (negate_spellbonus) { spellbonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1] = effect_value; } if (negate_itembonus) { itembonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1] = effect_value; } @@ -5043,25 +4822,25 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; } - case SE_MaxHPChange: + case SpellEffect::MaxHPChange: if (negate_spellbonus) { spellbonuses.PercentMaxHPChange = effect_value; } if (negate_aabonus) { aabonuses.PercentMaxHPChange = effect_value; } if (negate_itembonus) { itembonuses.PercentMaxHPChange = effect_value; } break; - case SE_EndurancePool: + case SpellEffect::EndurancePool: if (negate_spellbonus) { spellbonuses.Endurance = effect_value; } if (negate_aabonus) { aabonuses.Endurance = effect_value; } if (negate_itembonus) { itembonuses.Endurance = effect_value; } break; - case SE_HealRate: + case SpellEffect::HealRate: if (negate_spellbonus) { spellbonuses.HealRate = effect_value; } if (negate_aabonus) { aabonuses.HealRate = effect_value; } if (negate_itembonus) { itembonuses.HealRate = effect_value; } break; - case SE_SkillDamageTaken: + case SpellEffect::SkillDamageTaken: { for (int e = 0; e < EQ::skills::HIGHEST_SKILL + 1; e++) { @@ -5073,13 +4852,13 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; } - case SE_SpellCritChance: + case SpellEffect::SpellCritChance: if (negate_spellbonus) { spellbonuses.CriticalSpellChance = effect_value; } if (negate_aabonus) { aabonuses.CriticalSpellChance = effect_value; } if (negate_itembonus) { itembonuses.CriticalSpellChance = effect_value; } break; - case SE_CriticalSpellChance: + case SpellEffect::CriticalSpellChance: if (negate_spellbonus) { spellbonuses.CriticalSpellChance = effect_value; spellbonuses.SpellCritDmgIncrease = effect_value; @@ -5097,43 +4876,43 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; - case SE_SpellCritDmgIncrease: + case SpellEffect::SpellCritDmgIncrease: if (negate_spellbonus) { spellbonuses.SpellCritDmgIncrease = effect_value; } if (negate_aabonus) { aabonuses.SpellCritDmgIncrease = effect_value; } if (negate_itembonus) { itembonuses.SpellCritDmgIncrease = effect_value; } break; - case SE_DotCritDmgIncrease: + case SpellEffect::DotCritDmgIncrease: if (negate_spellbonus) { spellbonuses.DotCritDmgIncrease = effect_value; } if (negate_aabonus) { aabonuses.DotCritDmgIncrease = effect_value; } if (negate_itembonus) { itembonuses.DotCritDmgIncrease = effect_value; } break; - case SE_CriticalHealChance: + case SpellEffect::CriticalHealChance: if (negate_spellbonus) { spellbonuses.CriticalHealChance = effect_value; } if (negate_aabonus) { aabonuses.CriticalHealChance = effect_value; } if (negate_itembonus) { itembonuses.CriticalHealChance = effect_value; } break; - case SE_CriticalHealOverTime: + case SpellEffect::CriticalHealOverTime: if (negate_spellbonus) { spellbonuses.CriticalHealOverTime = effect_value; } if (negate_aabonus) { aabonuses.CriticalHealOverTime = effect_value; } if (negate_itembonus) { itembonuses.CriticalHealOverTime = effect_value; } break; - case SE_MitigateDamageShield: + case SpellEffect::MitigateDamageShield: if (negate_spellbonus) { spellbonuses.DSMitigationOffHand = effect_value; } if (negate_itembonus) { itembonuses.DSMitigationOffHand = effect_value; } if (negate_aabonus) { aabonuses.DSMitigationOffHand = effect_value; } break; - case SE_CriticalDoTChance: + case SpellEffect::CriticalDoTChance: if (negate_spellbonus) { spellbonuses.CriticalDoTChance = effect_value; } if (negate_aabonus) { aabonuses.CriticalDoTChance = effect_value; } if (negate_itembonus) { itembonuses.CriticalDoTChance = effect_value; } break; - case SE_ProcOnKillShot: + case SpellEffect::ProcOnKillShot: { for (int e = 0; e < MAX_SPELL_TRIGGER * 3; e += 3) { @@ -5160,7 +4939,7 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) } /* - case SE_SpellOnDeath: + case SpellEffect::SpellOnDeath: { for(int e = 0; e < MAX_SPELL_TRIGGER; e=2) { @@ -5171,7 +4950,7 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) } */ - case SE_CriticalDamageMob: + case SpellEffect::CriticalDamageMob: { for (int e = 0; e < EQ::skills::HIGHEST_SKILL + 1; e++) { @@ -5182,7 +4961,7 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; } - case SE_Critical_Melee_Damage_Mod_Max: + case SpellEffect::Critical_Melee_Damage_Mod_Max: { for (int e = 0; e < EQ::skills::HIGHEST_SKILL + 1; e++) { @@ -5193,7 +4972,7 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; } - case SE_SkillDamageAmount: + case SpellEffect::SkillDamageAmount: { for (int e = 0; e < EQ::skills::HIGHEST_SKILL + 1; e++) { @@ -5204,47 +4983,47 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; } - case SE_IncreaseBlockChance: + case SpellEffect::IncreaseBlockChance: if (negate_spellbonus) { spellbonuses.IncreaseBlockChance = effect_value; } if (negate_aabonus) { aabonuses.IncreaseBlockChance = effect_value; } if (negate_itembonus) { itembonuses.IncreaseBlockChance = effect_value; } break; - case SE_PersistantCasting: + case SpellEffect::PersistantCasting: if (negate_spellbonus) { spellbonuses.PersistantCasting = effect_value; } if (negate_itembonus) { itembonuses.PersistantCasting = effect_value; } if (negate_aabonus) { aabonuses.PersistantCasting = effect_value; } break; - case SE_ImmuneFleeing: + case SpellEffect::ImmuneFleeing: if (negate_spellbonus) { spellbonuses.ImmuneToFlee = false; } break; - case SE_DelayDeath: + case SpellEffect::DelayDeath: if (negate_spellbonus) { spellbonuses.DelayDeath = effect_value; } if (negate_aabonus) { aabonuses.DelayDeath = effect_value; } if (negate_itembonus) { itembonuses.DelayDeath = effect_value; } break; - case SE_SpellProcChance: + case SpellEffect::SpellProcChance: if (negate_spellbonus) { spellbonuses.SpellProcChance = effect_value; } if (negate_itembonus) { itembonuses.SpellProcChance = effect_value; } if (negate_aabonus) { aabonuses.SpellProcChance = effect_value; } break; - case SE_CharmBreakChance: + case SpellEffect::CharmBreakChance: if (negate_spellbonus) { spellbonuses.CharmBreakChance = effect_value; } if (negate_aabonus) { aabonuses.CharmBreakChance = effect_value; } if (negate_itembonus) { itembonuses.CharmBreakChance = effect_value; } break; - case SE_BardSongRange: + case SpellEffect::BardSongRange: if (negate_spellbonus) { spellbonuses.SongRange = effect_value; } if (negate_aabonus) { aabonuses.SongRange = effect_value; } if (negate_itembonus) { itembonuses.SongRange = effect_value; } break; - case SE_SkillDamageAmount2: + case SpellEffect::SkillDamageAmount2: { for (int e = 0; e < EQ::skills::HIGHEST_SKILL + 1; e++) { @@ -5255,7 +5034,7 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; } - case SE_NegateAttacks: + case SpellEffect::NegateAttacks: if (negate_spellbonus) { spellbonuses.NegateAttacks[SBIndex::NEGATE_ATK_EXISTS] = effect_value; spellbonuses.NegateAttacks[SBIndex::NEGATE_ATK_BUFFSLOT] = effect_value; @@ -5263,7 +5042,7 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; - case SE_MitigateMeleeDamage: + case SpellEffect::MitigateMeleeDamage: if (negate_spellbonus) { spellbonuses.MitigateMeleeRune[SBIndex::MITIGATION_RUNE_PERCENT] = effect_value; spellbonuses.MitigateMeleeRune[SBIndex::MITIGATION_RUNE_BUFFSLOT] = -1; @@ -5271,7 +5050,7 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; - case SE_MeleeThresholdGuard: + case SpellEffect::MeleeThresholdGuard: if (negate_spellbonus) { spellbonuses.MeleeThresholdGuard[SBIndex::THRESHOLDGUARD_MITIGATION_PERCENT] = effect_value; spellbonuses.MeleeThresholdGuard[SBIndex::THRESHOLDGUARD_BUFFSLOT] = -1; @@ -5280,7 +5059,7 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; - case SE_SpellThresholdGuard: + case SpellEffect::SpellThresholdGuard: if (negate_spellbonus) { spellbonuses.SpellThresholdGuard[SBIndex::THRESHOLDGUARD_MITIGATION_PERCENT] = effect_value; spellbonuses.SpellThresholdGuard[SBIndex::THRESHOLDGUARD_BUFFSLOT] = -1; @@ -5289,7 +5068,7 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; - case SE_MitigateSpellDamage: + case SpellEffect::MitigateSpellDamage: if (negate_spellbonus) { spellbonuses.MitigateSpellRune[SBIndex::MITIGATION_RUNE_PERCENT] = effect_value; spellbonuses.MitigateSpellRune[SBIndex::MITIGATION_RUNE_BUFFSLOT] = -1; @@ -5297,7 +5076,7 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; - case SE_MitigateDotDamage: + case SpellEffect::MitigateDotDamage: if (negate_spellbonus) { spellbonuses.MitigateDotRune[SBIndex::MITIGATION_RUNE_PERCENT] = effect_value; spellbonuses.MitigateDotRune[SBIndex::MITIGATION_RUNE_BUFFSLOT] = -1; @@ -5305,11 +5084,11 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; - case SE_ManaAbsorbPercentDamage: + case SpellEffect::ManaAbsorbPercentDamage: if (negate_spellbonus) { spellbonuses.ManaAbsorbPercentDamage = effect_value; } break; - case SE_Endurance_Absorb_Pct_Damage: + case SpellEffect::Endurance_Absorb_Pct_Damage: if (negate_spellbonus) { spellbonuses.EnduranceAbsorbPercentDamage[SBIndex::ENDURANCE_ABSORD_MITIGIATION] = effect_value; spellbonuses.EnduranceAbsorbPercentDamage[SBIndex::ENDURANCE_ABSORD_DRAIN_PER_HP] = effect_value; @@ -5317,223 +5096,223 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; - case SE_ShieldBlock: + case SpellEffect::ShieldBlock: if (negate_spellbonus) { spellbonuses.ShieldBlock = effect_value; } if (negate_aabonus) { aabonuses.ShieldBlock = effect_value; } if (negate_itembonus) { itembonuses.ShieldBlock = effect_value; } - case SE_BlockBehind: + case SpellEffect::BlockBehind: if (negate_spellbonus) { spellbonuses.BlockBehind = effect_value; } if (negate_aabonus) { aabonuses.BlockBehind = effect_value; } if (negate_itembonus) { itembonuses.BlockBehind = effect_value; } break; - case SE_Blind: + case SpellEffect::Blind: if (negate_spellbonus) { spellbonuses.IsBlind = false; } break; - case SE_Fear: + case SpellEffect::Fear: if (negate_spellbonus) { spellbonuses.IsFeared = false; } break; - case SE_FrontalStunResist: + case SpellEffect::FrontalStunResist: if (negate_spellbonus) { spellbonuses.FrontalStunResist = effect_value; } if (negate_aabonus) { aabonuses.FrontalStunResist = effect_value; } if (negate_itembonus) { itembonuses.FrontalStunResist = effect_value; } break; - case SE_ImprovedBindWound: + case SpellEffect::ImprovedBindWound: if (negate_aabonus) { aabonuses.BindWound = effect_value; } if (negate_itembonus) { itembonuses.BindWound = effect_value; } if (negate_spellbonus) { spellbonuses.BindWound = effect_value; } break; - case SE_MaxBindWound: + case SpellEffect::MaxBindWound: if (negate_spellbonus) { spellbonuses.MaxBindWound = effect_value; } if (negate_aabonus) { aabonuses.MaxBindWound = effect_value; } if (negate_itembonus) { itembonuses.MaxBindWound = effect_value; } break; - case SE_BaseMovementSpeed: + case SpellEffect::BaseMovementSpeed: if (negate_spellbonus) { spellbonuses.BaseMovementSpeed = effect_value; } if (negate_aabonus) { aabonuses.BaseMovementSpeed = effect_value; } if (negate_itembonus) { itembonuses.BaseMovementSpeed = effect_value; } break; - case SE_IncreaseRunSpeedCap: + case SpellEffect::IncreaseRunSpeedCap: if (negate_itembonus) { itembonuses.IncreaseRunSpeedCap = effect_value; } if (negate_aabonus) { aabonuses.IncreaseRunSpeedCap = effect_value; } if (negate_spellbonus) { spellbonuses.IncreaseRunSpeedCap = effect_value; } break; - case SE_DoubleSpecialAttack: + case SpellEffect::DoubleSpecialAttack: if (negate_spellbonus) { spellbonuses.DoubleSpecialAttack = effect_value; } if (negate_aabonus) { aabonuses.DoubleSpecialAttack = effect_value; } if (negate_itembonus) { itembonuses.DoubleSpecialAttack = effect_value; } break; - case SE_TripleBackstab: + case SpellEffect::TripleBackstab: if (negate_spellbonus) { spellbonuses.TripleBackstab = effect_value; } if (negate_aabonus) { aabonuses.TripleBackstab = effect_value; } if (negate_itembonus) { itembonuses.TripleBackstab = effect_value; } break; - case SE_FrontalBackstabMinDmg: + case SpellEffect::FrontalBackstabMinDmg: if (negate_spellbonus) { spellbonuses.FrontalBackstabMinDmg = false; } break; - case SE_FrontalBackstabChance: + case SpellEffect::FrontalBackstabChance: if (negate_spellbonus) { spellbonuses.FrontalBackstabChance = effect_value; } if (negate_aabonus) { aabonuses.FrontalBackstabChance = effect_value; } if (negate_itembonus) { itembonuses.FrontalBackstabChance = effect_value; } break; - case SE_Double_Backstab_Front: + case SpellEffect::Double_Backstab_Front: if (negate_spellbonus) { spellbonuses.Double_Backstab_Front = effect_value; } if (negate_aabonus) { aabonuses.Double_Backstab_Front = effect_value; } if (negate_itembonus) { itembonuses.Double_Backstab_Front = effect_value; } break; - case SE_ConsumeProjectile: + case SpellEffect::ConsumeProjectile: if (negate_spellbonus) { spellbonuses.ConsumeProjectile = effect_value; } if (negate_aabonus) { aabonuses.ConsumeProjectile = effect_value; } if (negate_itembonus) { itembonuses.ConsumeProjectile = effect_value; } break; - case SE_ForageAdditionalItems: + case SpellEffect::ForageAdditionalItems: if (negate_spellbonus) { spellbonuses.ForageAdditionalItems = effect_value; } if (negate_aabonus) { aabonuses.ForageAdditionalItems = effect_value; } if (negate_itembonus) { itembonuses.ForageAdditionalItems = effect_value; } break; - case SE_Salvage: + case SpellEffect::Salvage: if (negate_spellbonus) { spellbonuses.SalvageChance = effect_value; } if (negate_aabonus) { aabonuses.SalvageChance = effect_value; } if (negate_itembonus) { itembonuses.SalvageChance = effect_value; } break; - case SE_ArcheryDamageModifier: + case SpellEffect::ArcheryDamageModifier: if (negate_spellbonus) { spellbonuses.ArcheryDamageModifier = effect_value; } if (negate_aabonus) { aabonuses.ArcheryDamageModifier = effect_value; } if (negate_itembonus) { itembonuses.ArcheryDamageModifier = effect_value; } break; - case SE_SecondaryDmgInc: + case SpellEffect::SecondaryDmgInc: if (negate_spellbonus) { spellbonuses.SecondaryDmgInc = false; } if (negate_aabonus) { aabonuses.SecondaryDmgInc = false; } if (negate_itembonus) { itembonuses.SecondaryDmgInc = false; } break; - case SE_StrikeThrough: - case SE_StrikeThrough2: + case SpellEffect::StrikeThrough: + case SpellEffect::StrikeThrough2: if (negate_spellbonus) { spellbonuses.StrikeThrough = effect_value; } if (negate_aabonus) { aabonuses.StrikeThrough = effect_value; } if (negate_itembonus) { itembonuses.StrikeThrough = effect_value; } break; - case SE_GiveDoubleAttack: + case SpellEffect::GiveDoubleAttack: if (negate_spellbonus) { spellbonuses.GiveDoubleAttack = effect_value; } if (negate_aabonus) { aabonuses.GiveDoubleAttack = effect_value; } if (negate_itembonus) { itembonuses.GiveDoubleAttack = effect_value; } break; - case SE_PetCriticalHit: + case SpellEffect::PetCriticalHit: if (negate_spellbonus) { spellbonuses.PetCriticalHit = effect_value; } if (negate_aabonus) { aabonuses.PetCriticalHit = effect_value; } if (negate_itembonus) { itembonuses.PetCriticalHit = effect_value; } break; - case SE_CombatStability: + case SpellEffect::CombatStability: if (negate_spellbonus) { spellbonuses.CombatStability = effect_value; } if (negate_aabonus) { aabonuses.CombatStability = effect_value; } if (negate_itembonus) { itembonuses.CombatStability = effect_value; } break; - case SE_PetAvoidance: + case SpellEffect::PetAvoidance: if (negate_spellbonus) { spellbonuses.PetAvoidance = effect_value; } if (negate_aabonus) { aabonuses.PetAvoidance = effect_value; } if (negate_itembonus) { itembonuses.PetAvoidance = effect_value; } break; - case SE_Ambidexterity: + case SpellEffect::Ambidexterity: if (negate_spellbonus) { spellbonuses.Ambidexterity = effect_value; } if (negate_aabonus) { aabonuses.Ambidexterity = effect_value; } if (negate_itembonus) { itembonuses.Ambidexterity = effect_value; } break; - case SE_PetMaxHP: + case SpellEffect::PetMaxHP: if (negate_spellbonus) { spellbonuses.PetMaxHP = effect_value; } if (negate_aabonus) { aabonuses.PetMaxHP = effect_value; } if (negate_itembonus) { itembonuses.PetMaxHP = effect_value; } break; - case SE_PetFlurry: + case SpellEffect::PetFlurry: if (negate_spellbonus) { spellbonuses.PetFlurry = effect_value; } if (negate_aabonus) { aabonuses.PetFlurry = effect_value; } if (negate_itembonus) { itembonuses.PetFlurry = effect_value; } break; - case SE_GivePetGroupTarget: + case SpellEffect::GivePetGroupTarget: if (negate_spellbonus) { spellbonuses.GivePetGroupTarget = false; } if (negate_aabonus) { aabonuses.GivePetGroupTarget = false; } if (negate_itembonus) { itembonuses.GivePetGroupTarget = false; } break; - case SE_PetMeleeMitigation: + case SpellEffect::PetMeleeMitigation: if (negate_spellbonus) { spellbonuses.PetMeleeMitigation = effect_value; } if (negate_itembonus) { itembonuses.PetMeleeMitigation = effect_value; } if (negate_aabonus) { aabonuses.PetMeleeMitigation = effect_value; } break; - case SE_RootBreakChance: + case SpellEffect::RootBreakChance: if (negate_spellbonus) { spellbonuses.RootBreakChance = effect_value; } if (negate_aabonus) { aabonuses.RootBreakChance = effect_value; } if (negate_itembonus) { itembonuses.RootBreakChance = effect_value; } break; - case SE_ChannelChanceItems: + case SpellEffect::ChannelChanceItems: if (negate_spellbonus) { spellbonuses.ChannelChanceItems = effect_value; } if (negate_aabonus) { aabonuses.ChannelChanceItems = effect_value; } if (negate_itembonus) { itembonuses.ChannelChanceItems = effect_value; } break; - case SE_ChannelChanceSpells: + case SpellEffect::ChannelChanceSpells: if (negate_spellbonus) { spellbonuses.ChannelChanceSpells = effect_value; } if (negate_aabonus) { aabonuses.ChannelChanceSpells = effect_value; } if (negate_itembonus) { itembonuses.ChannelChanceSpells = effect_value; } break; - case SE_UnfailingDivinity: + case SpellEffect::UnfailingDivinity: if (negate_spellbonus) { spellbonuses.UnfailingDivinity = effect_value; } if (negate_aabonus) { aabonuses.UnfailingDivinity = effect_value; } if (negate_itembonus) { itembonuses.UnfailingDivinity = effect_value; } break; - case SE_ItemHPRegenCapIncrease: + case SpellEffect::ItemHPRegenCapIncrease: if (negate_spellbonus) { spellbonuses.ItemHPRegenCap = effect_value; } if (negate_aabonus) { aabonuses.ItemHPRegenCap = effect_value; } if (negate_itembonus) { itembonuses.ItemHPRegenCap = effect_value; } break; - case SE_Worn_Endurance_Regen_Cap: + case SpellEffect::Worn_Endurance_Regen_Cap: if (negate_spellbonus) { spellbonuses.ItemEnduranceRegenCap = effect_value; } if (negate_aabonus) { aabonuses.ItemEnduranceRegenCap = effect_value; } if (negate_itembonus) { itembonuses.ItemEnduranceRegenCap = effect_value; } break; - case SE_OffhandRiposteFail: + case SpellEffect::OffhandRiposteFail: if (negate_spellbonus) { spellbonuses.OffhandRiposteFail = effect_value; } if (negate_aabonus) { aabonuses.OffhandRiposteFail = effect_value; } if (negate_itembonus) { itembonuses.OffhandRiposteFail = effect_value; } break; - case SE_ItemAttackCapIncrease: + case SpellEffect::ItemAttackCapIncrease: if (negate_aabonus) { aabonuses.ItemATKCap = effect_value; } if (negate_itembonus) { itembonuses.ItemATKCap = effect_value; } if (negate_spellbonus) { spellbonuses.ItemATKCap = effect_value; } break; - case SE_SpellEffectResistChance: + case SpellEffect::SpellEffectResistChance: { for (int e = 0; e < MAX_RESISTABLE_EFFECTS * 2; e += 2) { @@ -5541,29 +5320,37 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) spellbonuses.SEResist[e] = effect_value; spellbonuses.SEResist[e + 1] = effect_value; } + if (negate_itembonus) { + itembonuses.SEResist[e] = effect_value; + itembonuses.SEResist[e + 1] = effect_value; + } + if (negate_aabonus) { + aabonuses.SEResist[e] = effect_value; + aabonuses.SEResist[e + 1] = effect_value; + } } break; } - case SE_MasteryofPast: + case SpellEffect::MasteryofPast: if (negate_spellbonus) { spellbonuses.MasteryofPast = effect_value; } if (negate_aabonus) { aabonuses.MasteryofPast = effect_value; } if (negate_itembonus) { itembonuses.MasteryofPast = effect_value; } break; - case SE_DoubleRiposte: + case SpellEffect::DoubleRiposte: if (negate_spellbonus) { spellbonuses.DoubleRiposte = effect_value; } if (negate_itembonus) { itembonuses.DoubleRiposte = effect_value; } if (negate_aabonus) { aabonuses.DoubleRiposte = effect_value; } break; - case SE_GiveDoubleRiposte: + case SpellEffect::GiveDoubleRiposte: if (negate_spellbonus) { spellbonuses.GiveDoubleRiposte[SBIndex::DOUBLE_RIPOSTE_CHANCE] = effect_value; } if (negate_itembonus) { itembonuses.GiveDoubleRiposte[SBIndex::DOUBLE_RIPOSTE_CHANCE] = effect_value; } if (negate_aabonus) { aabonuses.GiveDoubleRiposte[SBIndex::DOUBLE_RIPOSTE_CHANCE] = effect_value; } break; - case SE_SlayUndead: + case SpellEffect::SlayUndead: if (negate_spellbonus) { spellbonuses.SlayUndead[SBIndex::SLAYUNDEAD_RATE_MOD] = effect_value; spellbonuses.SlayUndead[SBIndex::SLAYUNDEAD_DMG_MOD] = effect_value; @@ -5581,53 +5368,53 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; - case SE_DoubleRangedAttack: + case SpellEffect::DoubleRangedAttack: if (negate_spellbonus) { spellbonuses.DoubleRangedAttack = effect_value; } if (negate_aabonus) { aabonuses.DoubleRangedAttack = effect_value; } if (negate_itembonus) { itembonuses.DoubleRangedAttack = effect_value; } break; - case SE_ShieldEquipDmgMod: + case SpellEffect::ShieldEquipDmgMod: if (negate_spellbonus) { spellbonuses.ShieldEquipDmgMod = effect_value; } if (negate_aabonus) { aabonuses.ShieldEquipDmgMod = effect_value; } if (negate_itembonus) { itembonuses.ShieldEquipDmgMod = effect_value; } break; - case SE_TriggerMeleeThreshold: + case SpellEffect::TriggerMeleeThreshold: if (negate_spellbonus) { spellbonuses.TriggerMeleeThreshold = false; } break; - case SE_TriggerSpellThreshold: + case SpellEffect::TriggerSpellThreshold: if (negate_spellbonus) { spellbonuses.TriggerSpellThreshold = false; } break; - case SE_DivineAura: + case SpellEffect::DivineAura: if (negate_spellbonus) { spellbonuses.DivineAura = false; } break; - case SE_StunBashChance: + case SpellEffect::StunBashChance: if (negate_spellbonus) { spellbonuses.StunBashChance = effect_value; } if (negate_itembonus) { itembonuses.StunBashChance = effect_value; } if (negate_aabonus) { aabonuses.StunBashChance = effect_value; } break; - case SE_IncreaseChanceMemwipe: + case SpellEffect::IncreaseChanceMemwipe: if (negate_spellbonus) { spellbonuses.IncreaseChanceMemwipe = effect_value; } if (negate_itembonus) { itembonuses.IncreaseChanceMemwipe = effect_value; } if (negate_aabonus) { aabonuses.IncreaseChanceMemwipe = effect_value; } break; - case SE_CriticalMend: + case SpellEffect::CriticalMend: if (negate_spellbonus) { spellbonuses.CriticalMend = effect_value; } if (negate_itembonus) { itembonuses.CriticalMend = effect_value; } if (negate_aabonus) { aabonuses.CriticalMend = effect_value; } break; - case SE_DistanceRemoval: + case SpellEffect::DistanceRemoval: if (negate_spellbonus) { spellbonuses.DistanceRemoval = false; } break; - case SE_ImprovedTaunt: + case SpellEffect::ImprovedTaunt: if (negate_spellbonus) { spellbonuses.ImprovedTaunt[SBIndex::IMPROVED_TAUNT_MAX_LVL] = effect_value; spellbonuses.ImprovedTaunt[SBIndex::IMPROVED_TAUNT_AGGRO_MOD] = effect_value; @@ -5636,13 +5423,13 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; - case SE_FrenziedDevastation: + case SpellEffect::FrenziedDevastation: if (negate_spellbonus) { spellbonuses.FrenziedDevastation = effect_value; } if (negate_aabonus) { aabonuses.FrenziedDevastation = effect_value; } if (negate_itembonus) { itembonuses.FrenziedDevastation = effect_value; } break; - case SE_Root: + case SpellEffect::Root: if (negate_spellbonus) { spellbonuses.Root[SBIndex::ROOT_EXISTS] = effect_value; spellbonuses.Root[SBIndex::ROOT_BUFFSLOT] = -1; @@ -5650,7 +5437,7 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; - case SE_Rune: + case SpellEffect::Rune: if (negate_spellbonus) { spellbonuses.MeleeRune[SBIndex::RUNE_AMOUNT] = effect_value; spellbonuses.MeleeRune[SBIndex::RUNE_BUFFSLOT] = -1; @@ -5658,7 +5445,7 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; - case SE_AbsorbMagicAtt: + case SpellEffect::AbsorbMagicAtt: if (negate_spellbonus) { spellbonuses.AbsorbMagicAtt[SBIndex::RUNE_AMOUNT] = effect_value; spellbonuses.AbsorbMagicAtt[SBIndex::RUNE_BUFFSLOT] = -1; @@ -5666,31 +5453,31 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; - case SE_Berserk: + case SpellEffect::Berserk: if (negate_spellbonus) { spellbonuses.BerserkSPA = false; } if (negate_aabonus) { aabonuses.BerserkSPA = false; } if (negate_itembonus) { itembonuses.BerserkSPA = false; } break; - case SE_Vampirism: + case SpellEffect::Vampirism: if (negate_spellbonus) { spellbonuses.Vampirism = effect_value; } if (negate_aabonus) { aabonuses.Vampirism = effect_value; } if (negate_itembonus) { itembonuses.Vampirism = effect_value; } break; - case SE_Metabolism: + case SpellEffect::Metabolism: if (negate_spellbonus) { spellbonuses.Metabolism = effect_value; } if (negate_aabonus) { aabonuses.Metabolism = effect_value; } if (negate_itembonus) { itembonuses.Metabolism = effect_value; } break; - case SE_ImprovedReclaimEnergy: + case SpellEffect::ImprovedReclaimEnergy: if (negate_spellbonus) { spellbonuses.ImprovedReclaimEnergy = effect_value; } if (negate_aabonus) { aabonuses.ImprovedReclaimEnergy = effect_value; } if (negate_itembonus) { itembonuses.ImprovedReclaimEnergy = effect_value; } break; - case SE_HeadShot: + case SpellEffect::HeadShot: if (negate_spellbonus) { spellbonuses.HeadShot[SBIndex::FINISHING_EFFECT_PROC_CHANCE] = effect_value; spellbonuses.HeadShot[SBIndex::FINISHING_EFFECT_DMG] = effect_value; @@ -5708,7 +5495,7 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; - case SE_HeadShotLevel: + case SpellEffect::HeadShotLevel: if (negate_spellbonus) { spellbonuses.HSLevel[SBIndex::FINISHING_EFFECT_LEVEL_MAX] = effect_value; spellbonuses.HSLevel[SBIndex::FINISHING_EFFECT_LEVEL_CHANCE_BONUS] = effect_value; @@ -5726,7 +5513,7 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; - case SE_Assassinate: + case SpellEffect::Assassinate: if (negate_spellbonus) { spellbonuses.Assassinate[SBIndex::FINISHING_EFFECT_PROC_CHANCE] = effect_value; spellbonuses.Assassinate[SBIndex::FINISHING_EFFECT_DMG] = effect_value; @@ -5744,7 +5531,7 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; - case SE_AssassinateLevel: + case SpellEffect::AssassinateLevel: if (negate_spellbonus) { spellbonuses.AssassinateLevel[SBIndex::FINISHING_EFFECT_LEVEL_MAX] = effect_value; spellbonuses.AssassinateLevel[SBIndex::FINISHING_EFFECT_LEVEL_CHANCE_BONUS] = effect_value; @@ -5762,7 +5549,7 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; - case SE_FinishingBlow: + case SpellEffect::FinishingBlow: if (negate_spellbonus) { spellbonuses.FinishingBlow[SBIndex::FINISHING_EFFECT_PROC_CHANCE] = effect_value; spellbonuses.FinishingBlow[SBIndex::FINISHING_EFFECT_DMG] = effect_value; @@ -5780,7 +5567,7 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; - case SE_FinishingBlowLvl: + case SpellEffect::FinishingBlowLvl: if (negate_spellbonus) { spellbonuses.FinishingBlowLvl[SBIndex::FINISHING_EFFECT_LEVEL_MAX] = effect_value; spellbonuses.FinishingBlowLvl[SBIndex::FINISHING_BLOW_LEVEL_HP_RATIO] = effect_value; @@ -5798,42 +5585,42 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; - case SE_Sanctuary: + case SpellEffect::Sanctuary: if (negate_spellbonus) { spellbonuses.Sanctuary = false; } break; - case SE_FactionModPct: + case SpellEffect::FactionModPct: if (negate_spellbonus) { spellbonuses.FactionModPct = effect_value; } if (negate_itembonus) { itembonuses.FactionModPct = effect_value; } if (negate_aabonus) { aabonuses.FactionModPct = effect_value; } break; - case SE_IllusionPersistence: + case SpellEffect::IllusionPersistence: if (negate_spellbonus) { spellbonuses.IllusionPersistence = effect_value; } if (negate_itembonus) { itembonuses.IllusionPersistence = effect_value; } if (negate_aabonus) { aabonuses.IllusionPersistence = effect_value; } break; - case SE_Attack_Accuracy_Max_Percent: + case SpellEffect::Attack_Accuracy_Max_Percent: if (negate_spellbonus) { spellbonuses.Attack_Accuracy_Max_Percent = effect_value; } if (negate_itembonus) { itembonuses.Attack_Accuracy_Max_Percent = effect_value; } if (negate_aabonus) { aabonuses.Attack_Accuracy_Max_Percent = effect_value; } break; - case SE_AC_Mitigation_Max_Percent: + case SpellEffect::AC_Mitigation_Max_Percent: if (negate_spellbonus) { spellbonuses.AC_Mitigation_Max_Percent = effect_value; } if (negate_itembonus) { itembonuses.AC_Mitigation_Max_Percent = effect_value; } if (negate_aabonus) { aabonuses.AC_Mitigation_Max_Percent = effect_value; } break; - case SE_AC_Avoidance_Max_Percent: + case SpellEffect::AC_Avoidance_Max_Percent: if (negate_spellbonus) { spellbonuses.AC_Avoidance_Max_Percent = effect_value; } if (negate_itembonus) { itembonuses.AC_Avoidance_Max_Percent = effect_value; } if (negate_aabonus) { aabonuses.AC_Avoidance_Max_Percent = effect_value; } break; - case SE_Melee_Damage_Position_Mod: + case SpellEffect::Melee_Damage_Position_Mod: if (negate_spellbonus) { spellbonuses.Melee_Damage_Position_Mod[SBIndex::POSITION_BACK] = effect_value; spellbonuses.Melee_Damage_Position_Mod[SBIndex::POSITION_FRONT] = effect_value; @@ -5851,7 +5638,7 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; - case SE_Damage_Taken_Position_Mod: + case SpellEffect::Damage_Taken_Position_Mod: if (negate_spellbonus) { spellbonuses.Damage_Taken_Position_Mod[SBIndex::POSITION_BACK] = effect_value; spellbonuses.Damage_Taken_Position_Mod[SBIndex::POSITION_FRONT] = effect_value; @@ -5869,7 +5656,7 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; - case SE_Melee_Damage_Position_Amt: + case SpellEffect::Melee_Damage_Position_Amt: if (negate_spellbonus) { spellbonuses.Melee_Damage_Position_Amt[SBIndex::POSITION_BACK] = effect_value; spellbonuses.Melee_Damage_Position_Amt[SBIndex::POSITION_FRONT] = effect_value; @@ -5887,7 +5674,7 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; - case SE_Damage_Taken_Position_Amt: + case SpellEffect::Damage_Taken_Position_Amt: if (negate_spellbonus) { spellbonuses.Damage_Taken_Position_Amt[SBIndex::POSITION_BACK] = effect_value; spellbonuses.Damage_Taken_Position_Amt[SBIndex::POSITION_FRONT] = effect_value; @@ -5905,31 +5692,31 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; - case SE_DS_Mitigation_Amount: + case SpellEffect::DS_Mitigation_Amount: if (negate_spellbonus) { spellbonuses.DS_Mitigation_Amount = effect_value; } if (negate_itembonus) { itembonuses.DS_Mitigation_Amount = effect_value; } if (negate_aabonus) { aabonuses.DS_Mitigation_Amount = effect_value; } break; - case SE_DS_Mitigation_Percentage: + case SpellEffect::DS_Mitigation_Percentage: if (negate_spellbonus) { spellbonuses.DS_Mitigation_Percentage = effect_value; } if (negate_itembonus) { itembonuses.DS_Mitigation_Percentage = effect_value; } if (negate_aabonus) { aabonuses.DS_Mitigation_Percentage = effect_value; } break; - case SE_Pet_Crit_Melee_Damage_Pct_Owner: + case SpellEffect::Pet_Crit_Melee_Damage_Pct_Owner: if (negate_spellbonus) { spellbonuses.Pet_Crit_Melee_Damage_Pct_Owner = effect_value; } if (negate_itembonus) { itembonuses.Pet_Crit_Melee_Damage_Pct_Owner = effect_value; } if (negate_aabonus) { aabonuses.Pet_Crit_Melee_Damage_Pct_Owner = effect_value; } break; - case SE_Pet_Add_Atk: + case SpellEffect::Pet_Add_Atk: if (negate_spellbonus) { spellbonuses.Pet_Add_Atk = effect_value; } if (negate_itembonus) { itembonuses.Pet_Add_Atk = effect_value; } if (negate_aabonus) { aabonuses.Pet_Add_Atk = effect_value; } break; - case SE_PC_Pet_Rampage: + case SpellEffect::PC_Pet_Rampage: if (negate_spellbonus) { spellbonuses.PC_Pet_Rampage[SBIndex::PET_RAMPAGE_CHANCE] = effect_value; spellbonuses.PC_Pet_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] = effect_value; @@ -5947,7 +5734,7 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; - case SE_PC_Pet_AE_Rampage: + case SpellEffect::PC_Pet_AE_Rampage: if (negate_spellbonus) { spellbonuses.PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_CHANCE] = effect_value; spellbonuses.PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] = effect_value; @@ -5966,7 +5753,7 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; - case SE_SkillProcSuccess: { + case SpellEffect::SkillProcSuccess: { for (int e = 0; e < MAX_SKILL_PROCS; e++) { if (negate_spellbonus) { spellbonuses.SkillProcSuccess[e] = effect_value; } @@ -5976,7 +5763,7 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; } - case SE_SkillProcAttempt: { + case SpellEffect::SkillProcAttempt: { for (int e = 0; e < MAX_SKILL_PROCS; e++) { if (negate_spellbonus) { spellbonuses.SkillProc[e] = effect_value; } @@ -5986,7 +5773,7 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) break; } - case SE_Shield_Target: { + case SpellEffect::Shield_Target: { if (negate_spellbonus) { spellbonuses.ShieldTargetSpa[SBIndex::SHIELD_TARGET_MITIGATION_PERCENT] = effect_value; spellbonuses.ShieldTargetSpa[SBIndex::SHIELD_TARGET_BUFFSLOT] = effect_value; diff --git a/zone/bot.cpp b/zone/bot.cpp index 7871f9ac9..1aeb0c09a 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -29,8 +29,14 @@ #include "../common/repositories/criteria/content_filter_criteria.h" #include "../common/skill_caps.h" +/* +TODO bot rewrite: +--command cleanup remaining commands (move to new help window, make more descriptive) +--Add quest methods for functions +*/ + // This constructor is used during the bot create command -Bot::Bot(NPCType *npcTypeData, Client* botOwner) : NPC(npcTypeData, nullptr, glm::vec4(), Ground, false), rest_timer(1), ping_timer(1) { +Bot::Bot(NPCType *npcTypeData, Client* botOwner) : NPC(npcTypeData, nullptr, glm::vec4(), Ground, false), rest_timer(1), m_ping_timer(1) { GiveNPCTypeData(npcTypeData); if (botOwner) { @@ -69,46 +75,51 @@ Bot::Bot(NPCType *npcTypeData, Client* botOwner) : NPC(npcTypeData, nullptr, glm RestRegenHP = 0; RestRegenMana = 0; RestRegenEndurance = 0; - m_enforce_spell_settings = false; - m_bot_archery_setting = false; - m_expansion_bitmask = -1; - m_bot_caster_range = 0; + SetBotID(0); SetBotSpellID(0); SetSpawnStatus(false); SetBotCharmer(false); - SetPetChooser(false); - SetRangerAutoWeaponSelect(false); - SetTaunting(GetClass() == Class::Warrior || GetClass() == Class::Paladin || GetClass() == Class::ShadowKnight); SetDefaultBotStance(); + SetTaunting((GetClass() == Class::Warrior || GetClass() == Class::Paladin || GetClass() == Class::ShadowKnight) && (GetBotStance() == Stance::Aggressive)); - SetAltOutOfCombatBehavior(GetClass() == Class::Bard); // will need to be updated if more classes make use of this flag - SetShowHelm(true); SetPauseAI(false); + m_combat_jitter_timer.Disable(); + m_auto_save_timer.Disable(); + m_rogue_evade_timer.Disable(); + m_monk_evade_timer.Disable(); m_auto_defend_timer.Disable(); SetGuardFlag(false); SetHoldFlag(false); SetAttackFlag(false); + SetCombatRoundForAlerts(false); SetAttackingFlag(false); SetPullFlag(false); SetPullingFlag(false); SetReturningFlag(false); SetIsUsingItemClick(false); - m_previous_pet_order = SPO_Guard; + m_previous_pet_order = PetOrder::Guard; rest_timer.Disable(); - ping_timer.Disable(); - SetFollowDistance(BOT_FOLLOW_DISTANCE_DEFAULT); - if (IsCasterClass(GetClass())) - SetStopMeleeLevel((uint8)RuleI(Bots, CasterStopMeleeLevel)); - else - SetStopMeleeLevel(255); + m_ping_timer.Disable(); + + LoadDefaultBotSettings(); + SetCastedSpellType(UINT16_MAX); + SetTempSpellType(UINT16_MAX); + SetCommandedSpell(false); + SetPullingSpell(false); // Do this once and only in this constructor GenerateAppearance(); GenerateBaseStats(); bot_timers.clear(); + bot_blocked_buffs.clear(); + _spell_target_list.clear(); + _group_spell_target_list.clear(); + SetStoredRaid(nullptr); + SetVerifiedRaid(false); + p_raid_instance = nullptr; // Calculate HitPoints Last As It Uses Base Stats current_hp = GenerateBaseHitPoints(); @@ -131,10 +142,9 @@ Bot::Bot( uint32 botSpellsID, double totalPlayTime, uint32 lastZoneId, - NPCType *npcTypeData, - int32 expansion_bitmask + NPCType *npcTypeData ) - : NPC(npcTypeData, nullptr, glm::vec4(), Ground, false), rest_timer(1), ping_timer(1) + : NPC(npcTypeData, nullptr, glm::vec4(), Ground, false), rest_timer(1), m_ping_timer(1) { GiveNPCTypeData(npcTypeData); @@ -175,13 +185,14 @@ Bot::Bot( RestRegenHP = 0; RestRegenMana = 0; RestRegenEndurance = 0; - m_expansion_bitmask = expansion_bitmask; SetBotID(botID); SetBotSpellID(botSpellsID); SetSpawnStatus(false); SetBotCharmer(false); - SetPetChooser(false); - SetRangerAutoWeaponSelect(false); + SetCastedSpellType(UINT16_MAX); + SetTempSpellType(UINT16_MAX); + SetCommandedSpell(false); + SetPullingSpell(false); bool stance_flag = false; if (!database.botdb.LoadStance(this, stance_flag) && bot_owner) { @@ -207,24 +218,24 @@ Bot::Bot( SetTaunting((GetClass() == Class::Warrior || GetClass() == Class::Paladin || GetClass() == Class::ShadowKnight) && (GetBotStance() == Stance::Aggressive)); SetPauseAI(false); + m_combat_jitter_timer.Disable(); + m_auto_save_timer.Disable(); + m_rogue_evade_timer.Disable(); + m_monk_evade_timer.Disable(); m_auto_defend_timer.Disable(); SetGuardFlag(false); SetHoldFlag(false); SetAttackFlag(false); + SetCombatRoundForAlerts(false); SetAttackingFlag(false); SetPullFlag(false); SetPullingFlag(false); SetReturningFlag(false); SetIsUsingItemClick(false); - m_previous_pet_order = SPO_Guard; + m_previous_pet_order = PetOrder::Guard; rest_timer.Disable(); - ping_timer.Disable(); - SetFollowDistance(BOT_FOLLOW_DISTANCE_DEFAULT); - if (IsCasterClass(GetClass())) - SetStopMeleeLevel((uint8)RuleI(Bots, CasterStopMeleeLevel)); - else - SetStopMeleeLevel(255); + m_ping_timer.Disable(); strcpy(name, GetCleanName()); @@ -234,8 +245,14 @@ Bot::Bot( EquipBot(); + m_combat_jitter_timer.Start(); + if (GetClass() == Class::Rogue) { - m_evade_timer.Start(); + m_rogue_evade_timer.Start(); + } + + if (GetClass() == Class::Monk) { + m_monk_evade_timer.Start(); } m_CastingRoles.GroupHealer = false; @@ -246,9 +263,21 @@ Bot::Bot( GenerateBaseStats(); bot_timers.clear(); - database.botdb.LoadTimers(this); + LoadDefaultBotSettings(); + database.botdb.LoadBotSettings(this); + + if (RuleB(Bots, AllowBotBlockedBuffs)) { + bot_blocked_buffs.clear(); + database.botdb.LoadBotBlockedBuffs(this); + } + + _spell_target_list.clear(); + _group_spell_target_list.clear(); + SetStoredRaid(nullptr); + SetVerifiedRaid(false); + p_raid_instance = nullptr; LoadAAs(); if (database.botdb.LoadBuffs(this)) { @@ -267,142 +296,146 @@ Bot::Bot( for (int x1 = 0; x1 < EFFECT_COUNT; x1++) { switch (spell.effect_id[x1]) { - case SE_IllusionCopy: - case SE_Illusion: { - if (spell.base_value[x1] == -1) { - if (gender == Gender::Female) { - gender = Gender::Male; - } else if (gender == Gender::Male) { - gender = Gender::Female; + case SpellEffect::IllusionCopy: + case SpellEffect::Illusion: { + if (GetIllusionBlock()) { + break; } - SendIllusionPacket( - AppearanceStruct{ - .gender_id = gender, - .race_id = GetRace(), + if (spell.base_value[x1] == -1) { + if (gender == Gender::Female) { + gender = Gender::Male; + } else if (gender == Gender::Male) { + gender = Gender::Female; } - ); - } else if (spell.base_value[x1] == -2) // WTF IS THIS - { - if (GetRace() == IKSAR || GetRace() == VAHSHIR || GetRace() <= GNOME) { + SendIllusionPacket( AppearanceStruct{ - .gender_id = GetGender(), - .helmet_texture = static_cast(spell.max_value[x1]), + .gender_id = gender, .race_id = GetRace(), + } + ); + } else if (spell.base_value[x1] == -2) // WTF IS THIS + { + if (GetRace() == Race::Iksar || GetRace() == Race::VahShir || GetRace() <= Race::Gnome) { + SendIllusionPacket( + AppearanceStruct{ + .gender_id = GetGender(), + .helmet_texture = static_cast(spell.max_value[x1]), + .race_id = GetRace(), + .texture = static_cast(spell.limit_value[x1]), + } + ); + } + } else if (spell.max_value[x1] > 0) { + SendIllusionPacket( + AppearanceStruct{ + .helmet_texture = static_cast(spell.max_value[x1]), + .race_id = static_cast(spell.base_value[x1]), + .texture = static_cast(spell.limit_value[x1]), + } + ); + } else { + SendIllusionPacket( + AppearanceStruct{ + .helmet_texture = static_cast(spell.max_value[x1]), + .race_id = static_cast(spell.base_value[x1]), .texture = static_cast(spell.limit_value[x1]), } ); } - } else if (spell.max_value[x1] > 0) { - SendIllusionPacket( - AppearanceStruct{ - .helmet_texture = static_cast(spell.max_value[x1]), - .race_id = static_cast(spell.base_value[x1]), - .texture = static_cast(spell.limit_value[x1]), - } - ); - } else { - SendIllusionPacket( - AppearanceStruct{ - .helmet_texture = static_cast(spell.max_value[x1]), - .race_id = static_cast(spell.base_value[x1]), - .texture = static_cast(spell.limit_value[x1]), - } - ); - } - switch (spell.base_value[x1]) { - case OGRE: - SendAppearancePacket(AppearanceType::Size, 9); - break; - case TROLL: - SendAppearancePacket(AppearanceType::Size, 8); - break; - case VAHSHIR: - case BARBARIAN: - SendAppearancePacket(AppearanceType::Size, 7); - break; - case HALF_ELF: - case WOOD_ELF: - case DARK_ELF: - case FROGLOK: - SendAppearancePacket(AppearanceType::Size, 5); - break; - case DWARF: - SendAppearancePacket(AppearanceType::Size, 4); - break; - case HALFLING: - case GNOME: - SendAppearancePacket(AppearanceType::Size, 3); - break; - default: - SendAppearancePacket(AppearanceType::Size, 6); + switch (spell.base_value[x1]) { + case Race::Ogre: + SendAppearancePacket(AppearanceType::Size, 9); + break; + case Race::Troll: + SendAppearancePacket(AppearanceType::Size, 8); + break; + case Race::VahShir: + case Race::Barbarian: + SendAppearancePacket(AppearanceType::Size, 7); + break; + case Race::HalfElf: + case Race::WoodElf: + case Race::DarkElf: + case Race::Froglok2: + SendAppearancePacket(AppearanceType::Size, 5); + break; + case Race::Dwarf: + SendAppearancePacket(AppearanceType::Size, 4); + break; + case Race::Halfling: + case Race::Gnome: + SendAppearancePacket(AppearanceType::Size, 3); + break; + default: + SendAppearancePacket(AppearanceType::Size, 6); + break; + } break; } - break; - } - case SE_Silence: - { - Silence(true); - break; - } - case SE_Amnesia: - { - Amnesia(true); - break; - } - case SE_DivineAura: - { - invulnerable = true; - break; - } - case SE_Invisibility2: - case SE_Invisibility: - { - invisible = true; - SendAppearancePacket(AppearanceType::Invisibility, 1); - break; - } - case SE_Levitate: - { - if (!zone->CanLevitate()) + case SpellEffect::Silence: { - SendAppearancePacket(AppearanceType::FlyMode, 0); - BuffFadeByEffect(SE_Levitate); + Silence(true); + break; } - else { - SendAppearancePacket(AppearanceType::FlyMode, 2); + case SpellEffect::Amnesia: + { + Amnesia(true); + break; + } + case SpellEffect::DivineAura: + { + invulnerable = true; + break; + } + case SpellEffect::Invisibility2: + case SpellEffect::Invisibility: + { + invisible = true; + SendAppearancePacket(AppearanceType::Invisibility, 1); + break; + } + case SpellEffect::Levitate: + { + if (!zone->CanLevitate()) + { + SendAppearancePacket(AppearanceType::FlyMode, 0); + BuffFadeByEffect(SpellEffect::Levitate); + } + else { + SendAppearancePacket(AppearanceType::FlyMode, 2); + } + break; + } + case SpellEffect::InvisVsUndead2: + case SpellEffect::InvisVsUndead: + { + invisible_undead = true; + break; + } + case SpellEffect::InvisVsAnimals: + { + invisible_animals = true; + break; + } + case SpellEffect::AddMeleeProc: + case SpellEffect::WeaponProc: + { + AddProcToWeapon(GetProcID(buffs[j1].spellid, x1), false, 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid, buffs[j1].casterlevel); + break; + } + case SpellEffect::DefensiveProc: + { + AddDefensiveProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid); + break; + } + case SpellEffect::RangedProc: + { + AddRangedProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid); + break; } - break; - } - case SE_InvisVsUndead2: - case SE_InvisVsUndead: - { - invisible_undead = true; - break; - } - case SE_InvisVsAnimals: - { - invisible_animals = true; - break; - } - case SE_AddMeleeProc: - case SE_WeaponProc: - { - AddProcToWeapon(GetProcID(buffs[j1].spellid, x1), false, 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid, buffs[j1].casterlevel); - break; - } - case SE_DefensiveProc: - { - AddDefensiveProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid); - break; - } - case SE_RangedProc: - { - AddRangedProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid); - break; - } } } } @@ -424,10 +457,10 @@ Bot::Bot( int resurrection_sickness_spell_id = ( RuleB(Bots, OldRaceRezEffects) && ( - GetRace() == BARBARIAN || - GetRace() == DWARF || - GetRace() == TROLL || - GetRace() == OGRE + GetRace() == Race::Barbarian || + GetRace() == Race::Dwarf || + GetRace() == Race::Troll || + GetRace() == Race::Ogre ) ? RuleI(Bots, OldResurrectionSicknessSpell) : RuleI(Bots, ResurrectionSicknessSpell) @@ -538,39 +571,72 @@ void Bot::SetSuffix(std::string_view bot_suffix) { } } -uint32 Bot::GetBotArcheryRange() { +uint32 Bot::GetBotRangedValue() { const EQ::ItemInstance *range_inst = GetBotItem(EQ::invslot::slotRange); const EQ::ItemInstance *ammo_inst = GetBotItem(EQ::invslot::slotAmmo); - if (!range_inst || !ammo_inst) + + if (!range_inst) { return 0; + } const EQ::ItemData *range_item = range_inst->GetItem(); - const EQ::ItemData *ammo_item = ammo_inst->GetItem(); - if (!range_item || !ammo_item || range_item->ItemType != EQ::item::ItemTypeBow || ammo_item->ItemType != EQ::item::ItemTypeArrow) - return 0; + const EQ::ItemData *ammo_item = nullptr; - // everything is good! - return (range_item->Range + ammo_item->Range); + if (ammo_inst) { + ammo_item = ammo_inst->GetItem(); + } + + bool has_ammo = ammo_item; + + if (!range_item || !has_ammo) { + return 0; + } + + // Bow requires arrows + if ( + range_item->ItemType == EQ::item::ItemTypeBow && + ammo_item->ItemType != EQ::item::ItemTypeArrow + ) { + return 0; + } + + // Throwing items + if ( + range_item->ItemType == EQ::item::ItemTypeSmallThrowing || + range_item->ItemType == EQ::item::ItemTypeLargeThrowing + ) { + if (range_item->ID == ammo_item->ID) { + return range_item->Range; + } + + return 0; // mismatched throwing + } + + // Bows and arrows + if ( + range_item->ItemType == EQ::item::ItemTypeBow && + ammo_item->ItemType == EQ::item::ItemTypeArrow + ) { + return (range_item->Range + ammo_item->Range); + } + + return 0; } -void Bot::ChangeBotArcherWeapons(bool isArcher) { - if ((GetClass()==Class::Warrior) || (GetClass()==Class::Paladin) || (GetClass()==Class::Ranger) || (GetClass()==Class::ShadowKnight) || (GetClass()==Class::Rogue)) { - if (!isArcher) { - BotAddEquipItem(EQ::invslot::slotPrimary, GetBotItemBySlot(EQ::invslot::slotPrimary)); - BotAddEquipItem(EQ::invslot::slotSecondary, GetBotItemBySlot(EQ::invslot::slotSecondary)); - SetAttackTimer(); - BotGroupSay(this, "My blade is ready"); - } else { - BotRemoveEquipItem(EQ::invslot::slotPrimary); - BotRemoveEquipItem(EQ::invslot::slotSecondary); - BotAddEquipItem(EQ::invslot::slotAmmo, GetBotItemBySlot(EQ::invslot::slotAmmo)); - BotAddEquipItem(EQ::invslot::slotSecondary, GetBotItemBySlot(EQ::invslot::slotRange)); - SetAttackTimer(); - BotGroupSay(this, "My bow is true and ready"); - } +void Bot::ChangeBotRangedWeapons(bool isRanged) { + if (!isRanged) { + BotAddEquipItem(EQ::invslot::slotPrimary, GetBotItemBySlot(EQ::invslot::slotPrimary)); + BotAddEquipItem(EQ::invslot::slotSecondary, GetBotItemBySlot(EQ::invslot::slotSecondary)); + SetAttackTimer(); + RaidGroupSay("My blade is ready"); + } else { + BotRemoveEquipItem(EQ::invslot::slotPrimary); + BotRemoveEquipItem(EQ::invslot::slotSecondary); + BotAddEquipItem(EQ::invslot::slotAmmo, GetBotItemBySlot(EQ::invslot::slotAmmo)); + BotAddEquipItem(EQ::invslot::slotSecondary, GetBotItemBySlot(EQ::invslot::slotRange)); + SetAttackTimer(); + RaidGroupSay("My blades are sheathed"); } - else - BotGroupSay(this, "I don't know how to use a bow"); } void Bot::Sit() { @@ -889,9 +955,9 @@ void Bot::GenerateBaseStats() float BotSize = GetSize(); switch(GetRace()) { - case HUMAN: // Humans have no race bonus + case Race::Human: // Humans have no race bonus break; - case BARBARIAN: + case Race::Barbarian: Strength += 28; Stamina += 20; Agility += 7; @@ -902,7 +968,7 @@ void Bot::GenerateBaseStats() BotSize = 7.0; ColdResist += 10; break; - case ERUDITE: + case Race::Erudite: Strength -= 15; Stamina -= 5; Agility -= 5; @@ -913,7 +979,7 @@ void Bot::GenerateBaseStats() MagicResist += 5; DiseaseResist -= 5; break; - case WOOD_ELF: + case Race::WoodElf: Strength -= 10; Stamina -= 10; Agility += 20; @@ -921,7 +987,7 @@ void Bot::GenerateBaseStats() Wisdom += 5; BotSize = 5.0; break; - case HIGH_ELF: + case Race::HighElf: Strength -= 20; Stamina -= 10; Agility += 10; @@ -930,7 +996,7 @@ void Bot::GenerateBaseStats() Intelligence += 12; Charisma += 5; break; - case DARK_ELF: + case Race::DarkElf: Strength -= 15; Stamina -= 10; Agility += 15; @@ -939,7 +1005,7 @@ void Bot::GenerateBaseStats() Charisma -= 15; BotSize = 5.0; break; - case HALF_ELF: + case Race::HalfElf: Strength -= 5; Stamina -= 5; Agility += 15; @@ -947,7 +1013,7 @@ void Bot::GenerateBaseStats() Wisdom -= 15; BotSize = 5.5; break; - case DWARF: + case Race::Dwarf: Strength += 15; Stamina += 15; Agility -= 5; @@ -959,7 +1025,7 @@ void Bot::GenerateBaseStats() MagicResist -= 5; PoisonResist += 5; break; - case TROLL: + case Race::Troll: Strength += 33; Stamina += 34; Agility += 8; @@ -969,7 +1035,7 @@ void Bot::GenerateBaseStats() BotSize = 8.0; FireResist -= 20; break; - case OGRE: + case Race::Ogre: Strength += 55; Stamina += 77; Agility -= 5; @@ -979,7 +1045,7 @@ void Bot::GenerateBaseStats() Charisma -= 38; BotSize = 9.0; break; - case HALFLING: + case Race::Halfling: Strength -= 5; Agility += 20; Dexterity += 15; @@ -990,7 +1056,7 @@ void Bot::GenerateBaseStats() PoisonResist += 5; DiseaseResist += 5; break; - case GNOME: + case Race::Gnome: Strength -= 15; Stamina -= 5; Agility += 10; @@ -1000,7 +1066,7 @@ void Bot::GenerateBaseStats() Charisma -= 15; BotSize = 3.0; break; - case IKSAR: + case Race::Iksar: Strength -= 5; Stamina -= 5; Agility += 15; @@ -1010,7 +1076,7 @@ void Bot::GenerateBaseStats() MagicResist -= 5; FireResist -= 5; break; - case VAHSHIR: + case Race::VahShir: Strength += 15; Agility += 15; Dexterity -= 5; @@ -1021,7 +1087,7 @@ void Bot::GenerateBaseStats() MagicResist -= 5; FireResist -= 5; break; - case FROGLOK: + case Race::Froglok2: Strength -= 5; Stamina += 5; Agility += 25; @@ -1031,7 +1097,7 @@ void Bot::GenerateBaseStats() MagicResist -= 5; FireResist -= 5; break; - case DRAKKIN: + case Race::Drakkin: Strength -= 5; Stamina += 5; Agility += 10; @@ -1074,7 +1140,7 @@ void Bot::GenerateBaseStats() void Bot::GenerateAppearance() { // Randomize facial appearance int iFace = 0; - if (GetRace() == BARBARIAN) // Barbarian w/Tatoo + if (GetRace() == Race::Barbarian) // Barbarian w/Tatoo { iFace = zone->random.Int(0, 79); } @@ -1086,13 +1152,13 @@ void Bot::GenerateAppearance() { int iHair = 0; int iBeard = 0; int iBeardColor = 1; - if (GetRace() == DRAKKIN) { + if (GetRace() == Race::Drakkin) { iHair = zone->random.Int(0, 8); iBeard = zone->random.Int(0, 11); iBeardColor = zone->random.Int(0, 3); } else if (GetGender()) { iHair = zone->random.Int(0, 2); - if (GetRace() == DWARF && zone->random.Int(1, 100) < 50) { + if (GetRace() == Race::Dwarf && zone->random.Int(1, 100) < 50) { iFace += 10; } } else { @@ -1102,7 +1168,7 @@ void Bot::GenerateAppearance() { } int iHairColor = 0; - if (GetRace() == DRAKKIN) { + if (GetRace() == Race::Drakkin) { iHairColor = zone->random.Int(0, 3); } else { iHairColor = zone->random.Int(0, 19); @@ -1110,7 +1176,7 @@ void Bot::GenerateAppearance() { auto iEyeColor1 = (uint8)zone->random.Int(0, 9); uint8 iEyeColor2 = 0; - if (GetRace() == DRAKKIN) { + if (GetRace() == Race::Drakkin) { iEyeColor1 = iEyeColor2 = (uint8)zone->random.Int(0, 11); } else if (zone->random.Int(1, 100) > 96) { iEyeColor2 = zone->random.Int(0, 9); @@ -1121,7 +1187,7 @@ void Bot::GenerateAppearance() { int iHeritage = 0; int iTattoo = 0; int iDetails = 0; - if (GetRace() == DRAKKIN) { + if (GetRace() == Race::Drakkin) { iHeritage = zone->random.Int(0, 6); iTattoo = zone->random.Int(0, 7); iDetails = zone->random.Int(0, 7); @@ -1176,7 +1242,7 @@ uint16 Bot::GetPrimarySkillValue() { } uint16 Bot::MaxSkill(EQ::skills::SkillType skillid, uint16 class_, uint16 level) const { - return skill_caps.GetSkillCap(class_, skillid, level).cap; + return SkillCaps::Instance()->GetSkillCap(class_, skillid, level).cap; } uint32 Bot::GetTotalATK() { @@ -1239,7 +1305,6 @@ int32 Bot::GenerateBaseHitPoints() { } void Bot::LoadAAs() { - aa_ranks.clear(); int id = 0; @@ -1265,7 +1330,7 @@ void Bot::LoadAAs() { } while(current) { - if (!CanUseAlternateAdvancementRank(current)) { + if (current->level_req > GetLevel() || !CanUseAlternateAdvancementRank(current)) { current = nullptr; } else { current = current->next; @@ -1304,17 +1369,27 @@ bool Bot::IsValidName() bool Bot::IsValidName(std::string& name) { - if (name.length() < 4) - return false; - if (!isupper(name[0])) + if (name.empty()) { // can't be empty return false; + } - for (char c : name.substr(1)) { - if (!RuleB(Bots, AllowCamelCaseNames) && !islower(c)) { - return false; - } - if (isdigit(c) || ispunct(c)) { - return false; + if (islower(name[0])) { // capitalize first letter if not + name[0] = toupper(name[0]); + } + + if (!EQ::ValueWithin(name.size(), 4, 15)) { // must be between 4 and 15 characters + return false; + } + + if (std::any_of(name.begin(), name.end(), [](char c) { return c == ' ' || c == '_'; })) { // cannot contain spaces or _ + return false; + } + + if (!RuleB(Bots, AllowCamelCaseNames)) { + for (int i = 1; i < name.size(); ++i) { + if (isupper(name[i])) { + return false; + } } } @@ -1344,6 +1419,11 @@ bool Bot::Save() database.botdb.SaveBuffs(this); database.botdb.SaveTimers(this); database.botdb.SaveStance(this); + database.botdb.SaveBotSettings(this); + + if (RuleB(Bots, AllowBotBlockedBuffs)) { + database.botdb.SaveBotBlockedBuffs(this); + } if (!SavePet()) bot_owner->Message(Chat::White, "Failed to save pet for '%s'", GetCleanName()); @@ -1389,20 +1469,30 @@ bool Bot::DeleteBot() RemoveBotFromRaid(this); } - if (!database.botdb.DeleteItems(GetBotID())) { - return false; - } + if (!RuleB(Bots, BotSoftDeletes)) { + if (!database.botdb.DeleteItems(GetBotID())) { + return false; + } - if (!database.botdb.DeleteTimers(GetBotID())) { - return false; - } + if (!database.botdb.DeleteTimers(GetBotID())) { + return false; + } - if (!database.botdb.DeleteBuffs(GetBotID())) { - return false; - } + if (!database.botdb.DeleteBuffs(GetBotID())) { + return false; + } - if (!database.botdb.DeleteStance(GetBotID())) { - return false; + if (!database.botdb.DeleteStance(GetBotID())) { + return false; + } + + if (!database.botdb.DeleteBotSettings(GetBotID())) { + return false; + } + + if (!database.botdb.DeleteBotBlockedBuffs(GetBotID())) { + return false; + } } if (!database.botdb.DeleteBot(GetBotID())) { @@ -1437,7 +1527,7 @@ bool Bot::LoadPet() auto my_buffs = GetBuffs(); if (buffs_max && my_buffs) { for (int index = 0; index < buffs_max; ++index) { - if (IsEffectInSpell(my_buffs[index].spellid, SE_Familiar)) { + if (IsEffectInSpell(my_buffs[index].spellid, SpellEffect::Familiar)) { MakePet(my_buffs[index].spellid, spells[my_buffs[index].spellid].teleport_zone); return true; } @@ -1564,6 +1654,7 @@ bool Bot::Process() { if (IsStunned() && stunned_timer.Check()) { Mob::UnStun(); + spun_timer.Disable(); } if (!GetBotOwner()) { @@ -1571,7 +1662,6 @@ bool Bot::Process() } if (GetDepop()) { - _botOwner = nullptr; _botOwnerCharacterID = 0; @@ -1594,7 +1684,7 @@ bool Bot::Process() BuffProcess(); CalcRestState(); - if (currently_fleeing) { + if (currently_fleeing || IsFeared()) { ProcessFlee(); } @@ -1629,25 +1719,34 @@ bool Bot::Process() } } + if (viral_timer.Check()) { + VirusEffectProcess(); + } + + if (spellbonuses.GravityEffect == 1) { + if (gravity_timer.Check()) { + DoGravityEffect(); + } + } + if (GetAppearance() == eaDead && GetHP() > 0) { SetAppearance(eaStanding); } if (IsMoving()) { - ping_timer.Disable(); + m_ping_timer.Disable(); } else { - - if (!ping_timer.Enabled()) { - ping_timer.Start(BOT_KEEP_ALIVE_INTERVAL); + if (!m_ping_timer.Enabled()) { + m_ping_timer.Start(BOT_KEEP_ALIVE_INTERVAL); } - if (ping_timer.Check()) { + if (m_ping_timer.Check()) { SentPositionPacket(0.0f, 0.0f, 0.0f, 0.0f, 0); } } - if (auto_save_timer.Check()) { + if (m_auto_save_timer.Check()) { clock_t t = std::clock(); /* Function timer start */ Save(); LogDebug( @@ -1655,10 +1754,22 @@ bool Bot::Process() GetBotID(), ((float)(std::clock() - t)) / CLOCKS_PER_SEC ); - auto_save_timer.Start(RuleI(Bots, AutosaveIntervalSeconds) * 1000); + m_auto_save_timer.Start(RuleI(Bots, AutosaveIntervalSeconds) * 1000); } - if (IsStunned() || IsMezzed()) { + if (ForcedMovement) { + ProcessForcedMovement(); + } + + if (IsMezzed()) { + return true; + } + + if (IsStunned()) { + if (spun_timer.Check()) { + Spin(); + } + return true; } @@ -1708,128 +1819,275 @@ void Bot::AI_Bot_Init() } void Bot::SpellProcess() { - if (spellend_timer.Check(false)) { + if (spellend_timer.Check(false)) { NPC::SpellProcess(); if (GetClass() == Class::Bard && casting_spell_id != 0) casting_spell_id = 0; } } -void Bot::BotMeditate(bool isSitting) { - if (isSitting) { - if (GetManaRatio() < 99.0f || GetHPRatio() < 99.0f) { - if (!IsEngaged() && !IsSitting()) { - Sit(); - } - } else { - if (IsSitting()) { - Stand(); - } - } - } else { - if (IsSitting()) { - Stand(); - } +void Bot::BotMeditate(bool is_sitting) { + bool needs_to_med = + (!IsEngaged() && (GetManaRatio() < 100 || GetHPRatio() < 100)) || + (GetMedInCombat() && !HasTargetReflection() && + ( + GetManaRatio() < GetSitManaPct() || + (GetHPRatio() < GetSitHPPct() && GetLevel() >= GetStopMeleeLevel()) + ) + ); + + if (needs_to_med && !is_sitting) { + Sit(); + } + else if (!needs_to_med && is_sitting) { + Stand(); } } -void Bot::BotRangedAttack(Mob* other) { - //make sure the attack and ranged timers are up - //if the ranged timer is disabled, then they have no ranged weapon and shouldent be attacking anyhow - if ((attack_timer.Enabled() && !attack_timer.Check(false)) || (ranged_timer.Enabled() && !ranged_timer.Check())) { - LogCombatDetail("Bot Archery attack canceled. Timer not up. Attack [{}] ranged [{}]", attack_timer.GetRemainingTime(), ranged_timer.GetRemainingTime()); - Message(0, "Error: Timer not up. Attack %d, ranged %d", attack_timer.GetRemainingTime(), ranged_timer.GetRemainingTime()); - return; - } - - const auto rangedItem = GetBotItem(EQ::invslot::slotRange); - const EQ::ItemData* RangeWeapon = nullptr; - if (rangedItem) - RangeWeapon = rangedItem->GetItem(); - - const auto ammoItem = GetBotItem(EQ::invslot::slotAmmo); - const EQ::ItemData* Ammo = nullptr; - if (ammoItem) - Ammo = ammoItem->GetItem(); - - if (!RangeWeapon || !Ammo) - return; - - LogCombatDetail("Shooting [{}] with bow [{}] ([{}]) and arrow [{}] ([{}])", other->GetCleanName(), RangeWeapon->Name, RangeWeapon->ID, Ammo->Name, Ammo->ID); - if (!IsAttackAllowed(other) || IsCasting() || DivineAura() || IsStunned() || IsMezzed() || (GetAppearance() == eaDead)) - return; - - SendItemAnimation(other, Ammo, EQ::skills::SkillArchery); - DoArcheryAttackDmg(other, rangedItem, ammoItem); // watch - - //break invis when you attack - if (invisible) { - LogCombatDetail("Removing invisibility due to melee attack"); - BuffFadeByEffect(SE_Invisibility); - BuffFadeByEffect(SE_Invisibility2); - invisible = false; - } - - if (invisible_undead) { - LogCombatDetail("Removing invisibility vs. undead due to melee attack"); - BuffFadeByEffect(SE_InvisVsUndead); - BuffFadeByEffect(SE_InvisVsUndead2); - invisible_undead = false; - } - - if (invisible_animals) { - LogCombatDetail("Removing invisibility vs. animals due to melee attack"); - BuffFadeByEffect(SE_InvisVsAnimals); - invisible_animals = false; - } - - if (spellbonuses.NegateIfCombat) - BuffFadeByEffect(SE_NegateIfCombat); - - if (hidden || improved_hidden) { - hidden = false; - improved_hidden = false; - auto outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct)); - auto sa_out = (SpawnAppearance_Struct*)outapp->pBuffer; - sa_out->spawn_id = GetID(); - sa_out->type = 0x03; - sa_out->parameter = 0; - entity_list.QueueClients(this, outapp, true); - safe_delete(outapp); - } -} - -bool Bot::CheckBotDoubleAttack(bool tripleAttack) { - //Check for bonuses that give you a double attack chance regardless of skill (ie Bestial Frenzy/Harmonious Attack AA) - uint32 bonusGiveDA = (aabonuses.GiveDoubleAttack + spellbonuses.GiveDoubleAttack + itembonuses.GiveDoubleAttack); - // If you don't have the double attack skill, return - if (!GetSkill(EQ::skills::SkillDoubleAttack) && !(GetClass() == Class::Bard || GetClass() == Class::Beastlord)) +bool Bot::BotRangedAttack(Mob* other, bool can_double_attack) { + if ( + !other || + !IsAttackAllowed(other) || + IsCasting() || + DivineAura() || + IsStunned() || + IsMezzed() || + (GetAppearance() == eaDead) + ) { return false; + } + + if ( + !GetPullingFlag() && + !GetReturningFlag() && + ( + ( + GetBotStance() != Stance::Aggressive && + GetBotStance() != Stance::Burn && + GetBotStance() != Stance::AEBurn + ) && + other->GetHPRatio() > 99.0f + ) + ) { + return false; + } + + if ( + !can_double_attack && + ( + ( + attack_timer.Enabled() && + !attack_timer.Check(false) + ) || + ( + ranged_timer.Enabled() && + !ranged_timer.Check() + ) + ) + ) { + LogCombatDetail("Bot ranged attack canceled. Timer not up. Attack [{}] ranged [{}]", attack_timer.GetRemainingTime(), ranged_timer.GetRemainingTime()); + return false; + } + + const auto ranged_item = GetBotItem(EQ::invslot::slotRange); + const EQ::ItemData* ranged_weapon = nullptr; + + if (ranged_item) { + ranged_weapon = ranged_item->GetItem(); + } + + if (!ranged_weapon) { + return false; + } + + const auto ammo_item = GetBotItem(EQ::invslot::slotAmmo); + const EQ::ItemData* ammo = nullptr; + + if (ammo_item) { + ammo = ammo_item->GetItem(); + } + + // Bow requires arrows + // Check if ammo is invalid + bool is_invalid_ammo = !ammo; + + // Check if ranged weapon type is invalid + bool is_invalid_ranged_weapon_type = ranged_weapon && + (ranged_weapon->ItemType != EQ::item::ItemTypeBow && + ranged_weapon->ItemType != EQ::item::ItemTypeSmallThrowing && + ranged_weapon->ItemType != EQ::item::ItemTypeLargeThrowing); + + // Check if bow has the wrong ammo + bool is_bow_with_invalid_ammo = ranged_weapon && + (ranged_weapon->ItemType == EQ::item::ItemTypeBow && + (!ammo || ammo->ItemType != EQ::item::ItemTypeArrow)); + + // Check if throwing weapon has insufficient charges + bool is_throwing_weapon_with_insufficient_ammo = ranged_weapon && + (ranged_weapon->ItemType == EQ::item::ItemTypeSmallThrowing || ranged_weapon->ItemType == EQ::item::ItemTypeLargeThrowing) && + ( + !ammo_item || ammo_item->GetCharges() < 1 || // Not enough ammo + ( + (RuleI(Bots, StackSizeMin) != -1 && ranged_item->GetCharges() != ranged_weapon->StackSize) || // Invalid stack size + ranged_item->GetCharges() < RuleI(Bots, StackSizeMin) // Charges below minimum + ) + ); + + // Final ranged weapon validity check + bool is_invalid_ranged_weapon = is_invalid_ranged_weapon_type || is_bow_with_invalid_ammo || is_throwing_weapon_with_insufficient_ammo; + + // Final condition + if (is_invalid_ammo || is_invalid_ranged_weapon) { + if (!ammo || ammo_item->GetCharges() < 1) { + if (!GetCombatRoundForAlerts()) { + SetCombatRoundForAlerts(); + RaidGroupSay("I do not have any ammo!"); + } + } + return false; + } + + LogCombatDetail("Ranged attacking [{}] with {} [{}] ([{}]){}{}{}", + other->GetCleanName(), + (ranged_weapon->ItemType == EQ::item::ItemTypeBow ? "bow" : "throwing"), + ranged_weapon->Name, + ranged_weapon->ID, + (ammo && ammo->ItemType == EQ::item::ItemTypeArrow ? " and arrow " : ""), + (ammo && ammo->ItemType == EQ::item::ItemTypeArrow ? ammo->Name : ""), + (ammo && ammo->ItemType == EQ::item::ItemTypeArrow ? std::to_string(ammo->ID) : "") + ); + + SendItemAnimation(other, ammo, (ranged_weapon->ItemType == EQ::item::ItemTypeBow ? EQ::skills::SkillArchery : EQ::skills::SkillThrowing)); + if (ranged_weapon->ItemType == EQ::item::ItemTypeBow) { + DoArcheryAttackDmg(other, ranged_item, ammo_item); + + int chance_avoid_consume = aabonuses.ConsumeProjectile + itembonuses.ConsumeProjectile + spellbonuses.ConsumeProjectile; + bool consumes_ammo = RuleB(Bots, BotArcheryConsumesAmmo); + bool is_expendable_arrow = ranged_weapon->ExpendableArrow; + bool no_chance_to_avoid = chance_avoid_consume == 0; + bool failed_avoid_check = chance_avoid_consume < 100 && zone->random.Int(0, 99) > chance_avoid_consume; + bool should_consume_ammo = consumes_ammo && (is_expendable_arrow || no_chance_to_avoid || failed_avoid_check); + + if (should_consume_ammo) { + ammo_item->SetCharges(ammo_item->GetCharges() - 1); + LogCombatDetail("Consumed Archery Ammo from slot {}.", EQ::invslot::slotAmmo); + + if (ammo_item->GetCharges() < 1) { + RemoveBotItemBySlot(EQ::invslot::slotAmmo); + BotRemoveEquipItem(EQ::invslot::slotAmmo); + } + } + else if (!consumes_ammo) { + LogCombatDetail("Archery Ammo Consumption is disabled."); + } + else { + LogCombatDetail("Endless Quiver prevented Ammo Consumption."); + } + } + else { + DoThrowingAttackDmg(other, ranged_item); // watch + // Consume Ammo, unless Ammo Consumption is disabled + if (RuleB(Bots, BotThrowingConsumesAmmo)) { + ammo_item->SetCharges((ammo_item->GetCharges() - 1)); + LogCombatDetail("Consumed Throwing Ammo from slot {}.", EQ::invslot::slotAmmo); + + if (ammo_item->GetCharges() < 1) { + RemoveBotItemBySlot(EQ::invslot::slotAmmo); + BotRemoveEquipItem(EQ::invslot::slotAmmo); + } + } + else { + LogCombatDetail("Throwing Ammo Consumption is disabled."); + } + } + + CommonBreakInvisibleFromCombat(); + + return true; +} + +bool Bot::CheckBotDoubleAttack(bool triple_attack) { + //Check for bonuses that give you a double attack chance regardless of skill (ie Bestial Frenzy/Harmonious Attack AA) + uint32 bonus_give_double_attack = aabonuses.GiveDoubleAttack + spellbonuses.GiveDoubleAttack + itembonuses.GiveDoubleAttack; + + if (!GetSkill(EQ::skills::SkillDoubleAttack) && !bonus_give_double_attack) { + return false; + } - // You start with no chance of double attacking float chance = 0.0f; uint16 skill = GetSkill(EQ::skills::SkillDoubleAttack); - int32 bonusDA = (aabonuses.DoubleAttackChance + spellbonuses.DoubleAttackChance + itembonuses.DoubleAttackChance); - //Use skill calculations otherwise, if you only have AA applied GiveDoubleAttack chance then use that value as the base. - if (skill) - chance = ((float(skill + GetLevel()) * (float(100.0f + bonusDA + bonusGiveDA) / 100.0f)) / 500.0f); - else - chance = ((float(bonusGiveDA) * (float(100.0f + bonusDA) / 100.0f)) / 100.0f); - //Live now uses a static Triple Attack skill (lv 46 = 2% lv 60 = 20%) - We do not have this skill on EMU ATM. - //A reasonable forumla would then be TA = 20% * chance - //AA's can also give triple attack skill over cap. (ie Burst of Power) NOTE: Skill ID in spell data is 76 (Triple Attack) - //Kayen: Need to decide if we can implement triple attack skill before working in over the cap effect. - if (tripleAttack) { - // Only some Double Attack classes get Triple Attack [This is already checked in client_processes.cpp] - int32 triple_bonus = (spellbonuses.TripleAttackChance + itembonuses.TripleAttackChance); - chance *= 0.2f; //Baseline chance is 20% of your double attack chance. - chance *= (float(100.0f + triple_bonus) / 100.0f); //Apply modifiers. + int32 bonus_double_attack = 0; + if ((GetClass() == Class::Paladin || GetClass() == Class::ShadowKnight) && (!HasTwoHanderEquipped())) { + LogCombatDetail("Knight class without a 2 hand weapon equipped = No DA Bonus!"); + } + else { + bonus_double_attack = aabonuses.DoubleAttackChance + spellbonuses.DoubleAttackChance + itembonuses.DoubleAttackChance; } - if (zone->random.Real(0, 1) < chance) - return true; + //Use skill calculations otherwise, if you only have AA applied GiveDoubleAttack chance then use that value as the base. + if (skill) { + chance = (float(skill + GetLevel()) * (float(100.0f + bonus_double_attack + bonus_give_double_attack) / 100.0f)) / 500.0f; + } + else { + chance = (float(bonus_give_double_attack + bonus_double_attack) / 100.0f); + } - return false; + LogCombatDetail( + "skill [{}] bonus_give_double_attack [{}] bonus_double_attack [{}] chance [{}]", + skill, + bonus_give_double_attack, + bonus_double_attack, + chance + ); + + return zone->random.Roll(chance); +} + +bool Bot::CheckTripleAttack() +{ + int chance; + + if (RuleB(Combat, ClassicTripleAttack)) { + if ( + GetLevel() >= 60 && + ( + GetClass() == Class::Warrior || + GetClass() == Class::Ranger || + GetClass() == Class::Monk || + GetClass() == Class::Berserker + ) + ) { + switch (GetClass()) { + case Class::Warrior: + chance = RuleI(Combat, ClassicTripleAttackChanceWarrior); + break; + case Class::Ranger: + chance = RuleI(Combat, ClassicTripleAttackChanceRanger); + break; + case Class::Monk: + chance = RuleI(Combat, ClassicTripleAttackChanceMonk); + break; + case Class::Berserker: + chance = RuleI(Combat, ClassicTripleAttackChanceBerserker); + break; + default: + break; + } + } + } + else { + chance = GetSkill(EQ::skills::SkillTripleAttack); + } + + if (chance < 1) { + return false; + } + + int inc = aabonuses.TripleAttackChance + spellbonuses.TripleAttackChance + itembonuses.TripleAttackChance; + chance = static_cast(chance * (1 + inc / 100.0f)); + chance = (chance * 100) / (chance + 800); + + return zone->random.Int(1, 100) <= chance; } void Bot::SetTarget(Mob *mob) @@ -1839,22 +2097,11 @@ void Bot::SetTarget(Mob *mob) } } -void Bot::SetStopMeleeLevel(uint8 level) { - if (IsCasterClass(GetClass()) || IsHybridClass(GetClass())) - _stopMeleeLevel = level; - else - _stopMeleeLevel = 255; -} - void Bot::SetGuardMode() { StopMoving(); m_GuardPoint = GetPosition(); SetGuardFlag(); - - if (HasPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) { - GetPet()->StopMoving(); - } } void Bot::SetHoldMode() { @@ -1864,30 +2111,13 @@ void Bot::SetHoldMode() { // AI Processing for the Bot object -constexpr float MAX_CASTER_DISTANCE[Class::PLAYER_CLASS_COUNT] = { - 0, - (34 * 34), - (24 * 24), - (28 * 28), - (26 * 26), - (42 * 42), - 0, - (30 * 30), - 0, - (38 * 38), - (54 * 54), - (48 * 48), - (52 * 52), - (50 * 50), - (32 * 32), - 0 -}; - void Bot::AI_Process() { -#define PULLING_BOT (GetPullingFlag() || GetReturningFlag()) -#define NOT_PULLING_BOT (!GetPullingFlag() && !GetReturningFlag()) +#define PULLING_BOT (GetPullingFlag()) +#define NOT_PULLING_BOT (!GetPullingFlag()) +#define RETURNING_BOT (GetReturningFlag()) +#define NOT_RETURNING_BOT (!GetReturningFlag()) #define GUARDING (GetGuardFlag()) #define NOT_GUARDING (!GetGuardFlag()) #define HOLDING (GetHoldFlag()) @@ -1901,19 +2131,21 @@ void Bot::AI_Process() return; } - auto raid = entity_list.GetRaidByBotName(GetName()); + Raid* raid = entity_list.GetRaidByBot(this); + SetStoredRaid(raid); uint32 r_group = RAID_GROUPLESS; + if (raid) { - raid->VerifyRaid(); + if (!GetVerifiedRaid()) { + raid->VerifyRaid(); + SetVerifiedRaid(true); + } + + if (mana_timer.Check() || send_hp_update_timer.Check()) { + raid->SendHPManaEndPacketsFrom(this); + } + r_group = raid->GetGroup(GetName()); - - if (mana_timer.Check(false)) { - raid->SendHPManaEndPacketsFrom(this); - } - - if (send_hp_update_timer.Check(false)) { - raid->SendHPManaEndPacketsFrom(this); - } } auto bot_group = GetGroup(); @@ -1923,15 +2155,26 @@ void Bot::AI_Process() return; } - auto leash_owner = SetLeashOwner(bot_owner, bot_group, raid, r_group); + Client* leash_owner = bot_owner; if (!leash_owner) { return; } - SetFollowID(leash_owner->GetID()); + Mob* follow_mob = nullptr; - auto follow_mob = SetFollowMob(leash_owner); + if (!GetFollowID()) { + follow_mob = leash_owner; + } + else { + follow_mob = entity_list.GetMob(GetFollowID()); + + if (!follow_mob || !IsInGroupOrRaid(follow_mob)) { + follow_mob = leash_owner; + } + } + + SetFollowID(follow_mob->GetID()); SetBerserkState(); @@ -1940,6 +2183,12 @@ void Bot::AI_Process() return; } + if (HOLDING || (raid && r_group == RAID_GROUPLESS)) { + TryNonCombatMovementChecks(bot_owner, follow_mob); + + return; + } + float fm_distance = DistanceSquared(m_Position, follow_mob->GetPosition()); float lo_distance = DistanceSquared(m_Position, leash_owner->GetPosition()); float leash_distance = RuleR(Bots, LeashDistance); @@ -1950,8 +2199,11 @@ void Bot::AI_Process() return; } -// HEAL ROTATION CASTING CHECKS + _spell_target_list.clear(); + _group_spell_target_list.clear(); + SetTempSpellType(UINT16_MAX); +// HEAL ROTATION CASTING CHECKS HealRotationChecks(); if (GetAttackFlag()) { // Push owner's target onto our hate list @@ -1962,19 +2214,15 @@ void Bot::AI_Process() } //ALT COMBAT (ACQUIRE HATE) - - glm::vec3 Goal(0, 0, 0); - // We have aggro to choose from if (IsEngaged()) { - if (rest_timer.Enabled()) { rest_timer.Disable(); } // PULLING FLAG (TARGET VALIDATION) - if (GetPullingFlag()) { + if (PULLING_BOT) { if (!PullingFlagChecks(bot_owner)) { return; } @@ -1982,8 +2230,8 @@ void Bot::AI_Process() // RETURNING FLAG - else if (GetReturningFlag()) { - if (!ReturningFlagChecks(bot_owner, fm_distance)) { + if (RETURNING_BOT) { + if (ReturningFlagChecks(bot_owner, leash_owner, fm_distance)) { return; } } @@ -1991,8 +2239,8 @@ void Bot::AI_Process() // DEFAULT (ACQUIRE TARGET) // VERIFY TARGET AND STANCE - auto tar = GetBotTarget(bot_owner); + if (!tar) { return; } @@ -2003,17 +2251,22 @@ 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)) { + if (HasPet()) { + if (tar && GetPet()->GetTarget() && GetPet()->GetTarget() == tar) { + GetPet()->WipeHateList(); + GetPet()->SetTarget(nullptr); + } + } + return; } // This causes conflicts with default pet handler (bounces between targets) - if (NOT_PULLING_BOT && HasPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) { - + if (NOT_PULLING_BOT && NOT_RETURNING_BOT && HasControllablePet(BotAnimEmpathy::Attack)) { // We don't add to hate list here because it's assumed to already be on the list GetPet()->SetTarget(tar); } @@ -2026,135 +2279,219 @@ void Bot::AI_Process() SendAddPlayerState(PlayerState::Aggressive); } +// COMBAT RANGE CALCS + bool front_mob = InFrontMob(tar, GetX(), GetY()); + bool behind_mob = BehindMob(tar, GetX(), GetY()); + bool stop_melee_level = GetLevel() >= GetStopMeleeLevel(); + + tar_distance = sqrt(tar_distance); // sqrt this for future calculations + const EQ::ItemInstance* p_item = GetBotItem(EQ::invslot::slotPrimary); + const EQ::ItemInstance* s_item = GetBotItem(EQ::invslot::slotSecondary); + + CombatRangeInput i = { + .target = tar, + .target_distance = tar_distance, + .stop_melee_level = stop_melee_level, + .p_item = p_item, + .s_item = s_item + }; + + CombatRangeOutput o = EvaluateCombatRange(i); + + // Combat range variables + bool at_combat_range = o.at_combat_range; + float melee_distance_min = o.melee_distance_min; + float melee_distance = o.melee_distance; + float melee_distance_max = o.melee_distance_max; + // PULLING FLAG (ACTIONABLE RANGE) - if (GetPullingFlag()) { + if (PULLING_BOT) { + if (!TargetValidation(tar)) { + SetPullFlag(false); + SetPullingFlag(false); + bot_owner->SetBotPulling(false); - constexpr size_t PULL_AGGRO = 5225; // spells[5225]: 'Throw Stone' - 0 cast time + if (GetPet()) { + GetPet()->SetPetOrder(PetOrder::Follow); + GetPet()->CastToNPC()->SaveGuardSpot(true); + } - if (tar_distance <= (spells[PULL_AGGRO].range * spells[PULL_AGGRO].range)) { - - StopMoving(); - CastSpell(PULL_AGGRO, tar->GetID()); return; } + + if (!at_combat_range && RuleB(Bots, UseSpellPulling)) { + uint16 pull_spell_id = RuleI(Bots, PullSpellID); + + if (IsValidSpell(pull_spell_id) && tar_distance <= spells[pull_spell_id].range) { + at_combat_range = true; + } + } + + if (at_combat_range && DoLosChecks(tar)) { + bool ai_cast_successful = false; + bool can_range_attack = !tar->GetSpecialAbility(SpecialAbility::RangedAttackImmunity) && + RuleB(Bots, AllowRangedPulling) && + IsBotRanged() && + ranged_timer.Check(false); + + if (can_range_attack) { + StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY())); + + if (BotRangedAttack(tar) && CheckDoubleRangedAttack()) { + BotRangedAttack(tar, true); + } + + ranged_timer.Start(); + + return; + } + + bool can_ai_spell_pull = RuleB(Bots, AllowAISpellPulling) && + !IsBotNonSpellFighter() && + AI_HasSpells(); + + if (can_ai_spell_pull) { + StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY())); + SetPullingSpell(true); + ai_cast_successful = AI_EngagedCastCheck(); + SetPullingSpell(false); + + if (ai_cast_successful) { + return; + } + } + + if (RuleB(Bots, UseSpellPulling)) { + uint16 pull_spell_id = RuleI(Bots, PullSpellID); + + if (IsValidSpell(pull_spell_id) && tar_distance <= spells[pull_spell_id].range) { + StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY())); + SetPullingSpell(true); + CastSpell(pull_spell_id, tar->GetID()); + SetPullingSpell(false); + } + } + + return; + } + + TryPursueTarget(leash_distance); + + return; } -// COMBAT RANGE CALCS - - bool atCombatRange; - const EQ::ItemInstance* p_item; - const EQ::ItemInstance* s_item; - CheckCombatRange(tar, tar_distance, atCombatRange, p_item, s_item); + if ( + HasTargetReflection() && + !IsTaunting() && + !tar->IsFleeing() && + !tar->IsFeared() && + TryEvade(tar) + ) { + return; + } // ENGAGED AT COMBAT RANGE // We can fight - if (atCombatRange) { + bool other_bot_pulling = + (bot_owner->GetBotPulling() && NOT_PULLING_BOT) && + (bot_owner->GetBotPulling() && NOT_RETURNING_BOT); - if (IsMoving()) { - StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY())); + if (!other_bot_pulling && at_combat_range) { + CombatPositioningInput cpi { + .tar = tar, + .stop_melee_level = stop_melee_level, + .tar_distance = tar_distance, + .melee_distance_min = melee_distance_min, + .melee_distance = melee_distance, + .melee_distance_max = melee_distance_max, + .behind_mob = behind_mob, + .front_mob = front_mob + }; + + if (DoCombatPositioning(cpi) && IsMoving()) { return; } - if (AI_movement_timer->Check() && (!spellend_timer.Enabled() || GetClass() == Class::Bard)) { - - if (TryEvade(tar)) { - return; - } - - if (TryFacingTarget(tar)) { - return; - } - } - - if (!IsBotNonSpellFighter() && AI_EngagedCastCheck()) { + if (!IsSitting() && !IsFacingMob(tar)) { + FaceTarget(tar); return; } - // Up to this point, GetTarget() has been safe to dereference since the initial - // if (!GetTarget() || GetAppearance() == eaDead) { return false; } call. Due to the chance of the target dying and our pointer - // being nullified, we need to test it before dereferencing to avoid crashes - - if (IsBotArcher() && TryRangedAttack(tar)) { + if (!IsBotNonSpellFighter() && AI_HasSpells() && AI_EngagedCastCheck()) { return; } - if (!IsBotArcher() && GetLevel() < GetStopMeleeLevel()) { - if (!TryClassAttacks(tar)) { - return; + if ( + !tar->GetSpecialAbility(SpecialAbility::RangedAttackImmunity) && + IsBotRanged() && + ranged_timer.Check(false) + ) { + if (BotRangedAttack(tar) && CheckDoubleRangedAttack()) { + BotRangedAttack(tar, true); } - if (!TryPrimaryWeaponAttacks(tar, p_item)) { - return; - } - - if (!TrySecondaryWeaponAttacks(tar, s_item)) { - return; - } + ranged_timer.Start(); } + else if (!IsBotRanged() && !stop_melee_level) { + if ( + IsTaunting() || + !GetMaxMeleeRange() || + !RuleB(Bots, DisableSpecialAbilitiesAtMaxMelee) + ) { + DoClassAttacks(tar); + } + + if (!TargetValidation(tar)) { return; } + + if (attack_timer.Check()) { + TryCombatProcs(p_item, tar, EQ::invslot::slotPrimary); + TriggerDefensiveProcs(tar, EQ::invslot::slotPrimary, false); + DoAttackRounds(tar, EQ::invslot::slotPrimary); + + if (TryDoubleMeleeRoundEffect()) { + DoAttackRounds(tar, EQ::invslot::slotPrimary); + } + } + + if (!TargetValidation(tar)) { return; } + + if (attack_dw_timer.Check()) { + if (CanThisClassDualWield() && CastToClient()->CheckDualWield()) { + TryCombatProcs(s_item, tar, EQ::invslot::slotSecondary); + DoAttackRounds(tar, EQ::invslot::slotSecondary); + } + } + + if (!TargetValidation(tar)) { return; } - if (GetAppearance() == eaDead) { - return; } } // ENGAGED NOT AT COMBAT RANGE - else if (!TryPursueTarget(leash_distance, Goal)) { + else if (!other_bot_pulling && !TryPursueTarget(leash_distance)) { return; } // End not in combat range - - if (TryMeditate()) { - return; - } - } - else { // Out-of-combat behavior - - SetAttackFlag(false); - SetAttackingFlag(false); - if (!bot_owner->GetBotPulling()) { - - SetPullingFlag(false); - SetReturningFlag(false); - } - -// AUTO DEFEND - - if (TryAutoDefend(bot_owner, leash_distance) ) { - return; - } - - SetTarget(nullptr); - - if (HasPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 1)) { - + if (bot_owner->GetBotPulling() && HasPet()) { GetPet()->WipeHateList(); GetPet()->SetTarget(nullptr); } - if (m_PlayerState & static_cast(PlayerState::Aggressive)) { - SendRemovePlayerState(PlayerState::Aggressive); - } - -// OK TO IDLE - - // Ok to idle - if (TryNonCombatMovementChecks(bot_owner, follow_mob, Goal)) { - return; - } - if (TryIdleChecks(fm_distance)) { - return; - } - if (TryBardMovementCasts()) { - return; - } + TryMeditate(); + } + else { // Out-of-combat behavior + DoOutOfCombatChecks(bot_owner, follow_mob, leash_distance, fm_distance); } } bool Bot::TryBardMovementCasts() {// Basically, bard bots get a chance to cast idle spells while moving + if (HOLDING) { + return false; + } if (GetClass() == Class::Bard && IsMoving() && NOT_PASSIVE && !spellend_timer.Enabled() && AI_think_timer->Check()) { @@ -2164,19 +2501,20 @@ bool Bot::TryBardMovementCasts() {// Basically, bard bots get a chance to cast i return false; } -bool Bot::TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob, glm::vec3& Goal) {// Non-engaged movement checks - +bool Bot::TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob) {// Non-engaged movement checks if (AI_movement_timer->Check() && (!IsCasting() || GetClass() == Class::Bard)) { + glm::vec3 Goal(0, 0, 0); + if (GUARDING) { Goal = GetGuardPoint(); } else { Goal = follow_mob->GetPosition(); } + float destination_distance = DistanceSquared(GetPosition(), Goal); - if ((!bot_owner->GetBotPulling() || PULLING_BOT) && (destination_distance > GetFollowDistance())) { - + if (destination_distance > GetFollowDistance()) { if (!IsRooted()) { if (rest_timer.Enabled()) { rest_timer.Disable(); @@ -2190,12 +2528,12 @@ bool Bot::TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob, g else { if (IsMoving()) { - StopMoving(); return true; } } } + return false; } @@ -2210,23 +2548,61 @@ bool Bot::TryIdleChecks(float fm_distance) { !spellend_timer.Enabled() ) { - if (NOT_PASSIVE) { - - if (!AI_IdleCastCheck() && !IsCasting() && GetClass() != Class::Bard) { - BotMeditate(true); - } - - } else { - if (GetClass() != Class::Bard) { - BotMeditate(true); - } - + if (!AI_IdleCastCheck() && !IsCasting()) { + BotMeditate(IsSitting()); } + return true; } + return false; } +void Bot::DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, float leash_distance, float fm_distance) { + SetAttackFlag(false); + SetCombatRoundForAlerts(false); + SetAttackingFlag(false); + + if (PULLING_BOT || RETURNING_BOT || !bot_owner->GetBotPulling()) { + SetPullingFlag(false); + SetReturningFlag(false); + bot_owner->SetBotPulling(false); + + if (GetPet()) { + GetPet()->SetPetOrder(PetOrder::Follow); + GetPet()->CastToNPC()->SaveGuardSpot(true); + } + } + + if (TryAutoDefend(bot_owner, leash_distance) ) { + return; + } + + SetTarget(nullptr); + + if (HasControllablePet(BotAnimEmpathy::BackOff)) { + GetPet()->WipeHateList(); + GetPet()->SetTarget(nullptr); + } + + if (m_PlayerState & static_cast(PlayerState::Aggressive)) { + SendRemovePlayerState(PlayerState::Aggressive); + } + + // Ok to idle + if (TryNonCombatMovementChecks(bot_owner, follow_mob)) { + return; + } + + if (!IsBotNonSpellFighter() && AI_HasSpells() && TryIdleChecks(fm_distance)) { + return; + } + + if (GetClass() == Class::Bard && AI_HasSpells() && TryBardMovementCasts()) { + return; + } +} + // This is as close as I could get without modifying the aggro mechanics and making it an expensive process... // 'class Client' doesn't make use of hate_list bool Bot::TryAutoDefend(Client* bot_owner, float leash_distance) { @@ -2241,83 +2617,209 @@ bool Bot::TryAutoDefend(Client* bot_owner, float leash_distance) { if ( m_auto_defend_timer.Check() && - bot_owner->GetAggroCount() && NOT_HOLDING && NOT_PASSIVE ) { - auto xhaters = bot_owner->GetXTargetAutoMgr(); + XTargetAutoHaters* temp_haters; + std::vector assistee_haters; + std::vector assistee_members; + bool found = false; - if (xhaters && !xhaters->empty()) { - for (auto hater_iter : xhaters->get_list()) { - if (!hater_iter.spawn_id) { - continue; - } + if (bot_owner->GetAggroCount()) { + temp_haters = bot_owner->GetXTargetAutoMgr(); - if (bot_owner->GetBotPulling() && bot_owner->GetTarget() && hater_iter.spawn_id == bot_owner->GetTarget()->GetID()) { - continue; - } + if (temp_haters && !temp_haters->empty()) { + assistee_haters.emplace_back(temp_haters); + assistee_members.emplace_back(bot_owner); + } + } - auto hater = entity_list.GetMob(hater_iter.spawn_id); - if (hater && hater->CastToNPC()->IsOnHatelist(bot_owner) && !hater->IsMezzed() && DistanceSquared(hater->GetPosition(), bot_owner->GetPosition()) <= leash_distance) { - // This is roughly equivilent to npc attacking a client pet owner - AddToHateList(hater, 1); - SetTarget(hater); - SetAttackingFlag(); + if ( + (!bot_owner->GetAssistee() || !entity_list.GetClientByCharID(bot_owner->GetAssistee())) && + RuleB(Bots, AllowCrossGroupRaidAssist) + ) { + XTargetAutoHaters* temp_xhaters = bot_owner->GetXTargetAutoMgr(); + bool assistee_found = false; - if (HasPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) { - GetPet()->AddToHateList(hater, 1); - GetPet()->SetTarget(hater); + if (IsRaidGrouped()) { + Raid* raid = GetStoredRaid(); + if (raid) { + for (const auto& m : raid->members) { + if ( + m.member && + m.member->IsClient() && + m.member->GetAggroCount() && + raid->IsAssister(m.member_name) + ) { + temp_xhaters = m.member->GetXTargetAutoMgr(); + + if (!temp_xhaters || temp_xhaters->empty()) { + continue; + } + + assistee_haters.emplace_back(temp_xhaters); + assistee_members.emplace_back(m.member); + } } + } + } + else if (HasGroup()) { + Group* g = GetGroup(); + if (g) { + for (auto& m : g->members) { + if ( + m && + m->IsClient() && + m->CastToClient()->GetAggroCount() && + g->AmIMainAssist(m->GetName()) + ) { + temp_xhaters = m->CastToClient()->GetXTargetAutoMgr(); - m_auto_defend_timer.Disable(); + if (!temp_xhaters || temp_xhaters->empty()) { + continue; + } - return true; + assistee_haters.emplace_back(temp_xhaters); + assistee_members.emplace_back(m->CastToClient()); + } + } + } + } + else { + return false; + } + } + else { + if (bot_owner->GetAssistee()) { + Client* c = entity_list.GetClientByCharID(bot_owner->GetAssistee()); + + if (bot_owner->IsInGroupOrRaid(c) && c->GetAggroCount()) { + temp_haters = bot_owner->GetXTargetAutoMgr(); + + if (temp_haters && !temp_haters->empty()) { + assistee_haters.emplace_back(temp_haters); + assistee_members.emplace_back(c); + } } } } + + if (!assistee_haters.empty()) { + for (XTargetAutoHaters* x_haters : assistee_haters) { + if (!x_haters->empty()) { + for (auto hater_iter : x_haters->get_list()) { + if (!hater_iter.spawn_id) { + continue; + } + + Mob* hater = nullptr; + + for (Client* x_member : assistee_members) { + if ( + x_member && + x_member->GetBotPulling() && + x_member->GetTarget() && + (hater_iter.spawn_id == x_member->GetTarget()->GetID()) + ) { + continue; + } + + hater = entity_list.GetMob(hater_iter.spawn_id); + + if ( + hater && + !hater->IsMezzed() && + (DistanceSquared(hater->GetPosition(), bot_owner->GetPosition()) <= leash_distance) && + hater->CastToNPC()->IsOnHatelist(x_member) + ) { + break; + } + + hater = nullptr; + } + + if (hater) { + AddToHateList(hater, 1); + SetTarget(hater); + SetAttackingFlag(); + + if (HasControllablePet(BotAnimEmpathy::Attack)) { + GetPet()->AddToHateList(hater, 1); + GetPet()->SetTarget(hater); + } + + m_auto_defend_timer.Disable(); + + return true; + } + } + } + } + } + + return false; } } + return false; } bool Bot::TryMeditate() { - if (!IsMoving() && !spellend_timer.Enabled()) { - if (GetTarget() && AI_EngagedCastCheck()) { - BotMeditate(false); - } else if (GetArchetype() == Archetype::Caster) { - BotMeditate(true); + if (IsEngaged()) { + if (HasOrMayGetAggro(IsSitting())) { + if (IsSitting()) { + Stand(); + } + + return false; + } + + for (auto mob : hate_list.GetHateList()) { + auto tar = mob->entity_on_hatelist; + + if (tar) { + Mob* tar_target = tar->GetTarget(); + + if (tar_target && tar_target == this) { + return false; + } + } + } } + BotMeditate(IsSitting()); + if (!(GetPlayerState() & static_cast(PlayerState::Aggressive))) { SendAddPlayerState(PlayerState::Aggressive); } + return true; } + return false; } // This code actually gets processed when we are too far away from target and have not engaged yet -bool Bot::TryPursueTarget(float leash_distance, glm::vec3& Goal) { - +bool Bot::TryPursueTarget(float leash_distance) { if (AI_movement_timer->Check() && (!spellend_timer.Enabled() || GetClass() == Class::Bard)) { if (GetTarget() && !IsRooted()) { LogAIDetail("Pursuing [{}] while engaged", GetTarget()->GetCleanName()); - Goal = GetTarget()->GetPosition(); + glm::vec3 Goal = GetTarget()->GetPosition(); + if (DistanceSquared(m_Position, Goal) <= leash_distance) { RunTo(Goal.x, Goal.y, Goal.z); - } else { WipeHateList(); SetTarget(nullptr); - if (HasPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) { + if (HasControllablePet(BotAnimEmpathy::BackOff)) { GetPet()->WipeHateList(); GetPet()->SetTarget(nullptr); } } - return true; + return true; } else { if (IsMoving()) { StopMoving(); @@ -2333,156 +2835,116 @@ bool Bot::TryPursueTarget(float leash_distance, glm::vec3& Goal) { } // This is a mob that is fleeing either because it has been feared or is low on hitpoints - AI_PursueCastCheck(); + if (!HOLDING && !IsBotNonSpellFighter() && AI_HasSpells()) { + AI_PursueCastCheck(); + } return true; } + return false; } -bool Bot::TrySecondaryWeaponAttacks(Mob* tar, const EQ::ItemInstance* s_item) { +void Bot::DoAttackRounds(Mob* target, int hand) { + if (!target || (target && target->IsCorpse())) { + return; + } - if (!GetTarget() || GetAppearance() == eaDead) { return false; } - if (attack_dw_timer.Check() && CanThisClassDualWield()) { - const EQ::ItemData* s_itemdata = nullptr; + Attack(target, hand, false, false); - // Can only dual wield without a weapon if you're a monk - if (s_item || (GetClass() == Class::Monk)) { + bool candouble = CanThisClassDoubleAttack(); + // extra off hand non-sense, can only double with skill of 150 or above + // or you have any amount of GiveDoubleAttack + if (candouble && hand == EQ::invslot::slotSecondary) + candouble = + GetSkill(EQ::skills::SkillDoubleAttack) > 149 || + (aabonuses.GiveDoubleAttack + spellbonuses.GiveDoubleAttack + itembonuses.GiveDoubleAttack) > 0; - if (s_item) { - s_itemdata = s_item->GetItem(); + if (candouble) { + if (CheckBotDoubleAttack()) { + Attack(target, hand, false, false); + + if (hand == EQ::invslot::slotPrimary) { + bool is_two_hander = HasTwoHanderEquipped(); + + auto extra_attack_chance = is_two_hander + ? aabonuses.ExtraAttackChance[SBIndex::EXTRA_ATTACK_CHANCE] + + spellbonuses.ExtraAttackChance[SBIndex::EXTRA_ATTACK_CHANCE] + + itembonuses.ExtraAttackChance[SBIndex::EXTRA_ATTACK_CHANCE] + : aabonuses.ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_CHANCE] + + spellbonuses.ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_CHANCE] + + itembonuses.ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_CHANCE]; + + int extra_attack_amt_aas = is_two_hander + ? aabonuses.ExtraAttackChance[SBIndex::EXTRA_ATTACK_NUM_ATKS] + : aabonuses.ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_NUM_ATKS]; + + int extra_attack_amt_spells = is_two_hander + ? spellbonuses.ExtraAttackChance[SBIndex::EXTRA_ATTACK_NUM_ATKS] + : spellbonuses.ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_NUM_ATKS]; + + int extra_attack_amt_items = is_two_hander + ? itembonuses.ExtraAttackChance[SBIndex::EXTRA_ATTACK_NUM_ATKS] + : itembonuses.ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_NUM_ATKS]; + + int extra_attack_amt = std::max({ extra_attack_amt_aas, extra_attack_amt_spells, extra_attack_amt_items }); + + if (extra_attack_chance && zone->random.Roll(extra_attack_chance)) { + for (int i = 0; i < extra_attack_amt; i++) { + Attack(target, hand, false, false); + } + } } - if (!s_itemdata) { - return false; + if (hand == EQ::invslot::slotSecondary) { + auto extra_attack_chance_secondary = + aabonuses.ExtraAttackChanceSecondary[SBIndex::EXTRA_ATTACK_CHANCE] + + spellbonuses.ExtraAttackChanceSecondary[SBIndex::EXTRA_ATTACK_CHANCE] + + itembonuses.ExtraAttackChanceSecondary[SBIndex::EXTRA_ATTACK_CHANCE]; + + if (extra_attack_chance_secondary && zone->random.Roll(extra_attack_chance_secondary)) { + auto extra_attack_amt_secondary = std::max({ + aabonuses.ExtraAttackChanceSecondary[SBIndex::EXTRA_ATTACK_NUM_ATKS], + spellbonuses.ExtraAttackChanceSecondary[SBIndex::EXTRA_ATTACK_NUM_ATKS], + itembonuses.ExtraAttackChanceSecondary[SBIndex::EXTRA_ATTACK_NUM_ATKS] + }); + + for (int i = 0; i < extra_attack_amt_secondary; i++) { + Attack(target, hand, false, false); + } + } } - bool use_fist = true; - if (s_itemdata) { - use_fist = false; - } + // you can only triple from the main hand + if (hand == EQ::invslot::slotPrimary && CanThisClassTripleAttack()) { + if (CheckTripleAttack()) { + Attack(target, hand, false, false); - if (use_fist || !s_itemdata->IsType2HWeapon()) { + int flurry_chance = aabonuses.FlurryChance + spellbonuses.FlurryChance + itembonuses.FlurryChance; - float DualWieldProbability = 0.0f; + if (flurry_chance && zone->random.Roll(flurry_chance)) { + Attack(target, hand, false, false); - int32 Ambidexterity = (aabonuses.Ambidexterity + spellbonuses.Ambidexterity + itembonuses.Ambidexterity); - DualWieldProbability = ((GetSkill(EQ::skills::SkillDualWield) + GetLevel() + Ambidexterity) / 400.0f); // 78.0 max chance + if (zone->random.Roll(flurry_chance)) { + Attack(target, hand, false, false); + } - int32 DWBonus = (spellbonuses.DualWieldChance + itembonuses.DualWieldChance); - DualWieldProbability += (DualWieldProbability * float(DWBonus) / 100.0f); - - float random = zone->random.Real(0, 1); - if (random < DualWieldProbability) { // Max 78% for DW chance - Attack(tar, EQ::invslot::slotSecondary); // Single attack with offhand - - if (GetAppearance() == eaDead) { return false; } - TryCombatProcs(s_item, tar, EQ::invslot::slotSecondary); - - if (GetAppearance() == eaDead) { return false; } - if (CanThisClassDoubleAttack() && CheckBotDoubleAttack() && tar->GetHP() > -10) { - Attack(tar, EQ::invslot::slotSecondary); // Single attack with offhand + if (GetOwner()) { + GetOwner()->MessageString( + Chat::NPCFlurry, + NPC_FLURRY, + GetCleanName(), + target->GetCleanName() + ); + } } } } } } - return true; -} - -bool Bot::TryPrimaryWeaponAttacks(Mob* tar, const EQ::ItemInstance* p_item) { - - if (!GetTarget() || GetAppearance() == eaDead) { return false; } - if (attack_timer.Check()) { // Process primary weapon attacks - - Attack(tar, EQ::invslot::slotPrimary); - - if (GetAppearance() == eaDead) { return false; } - TriggerDefensiveProcs(tar, EQ::invslot::slotPrimary, false); - - if (GetAppearance() == eaDead) { return false; } - TryCombatProcs(p_item, tar, EQ::invslot::slotPrimary); - - if (GetAppearance() == eaDead) { return false; } - if (CanThisClassDoubleAttack()) { - - if (CheckBotDoubleAttack()) { - Attack(tar, EQ::invslot::slotPrimary, true); - } - - if (GetAppearance() == eaDead) { return false; } - if (GetSpecialAbility(SpecialAbility::TripleAttack) && CheckBotDoubleAttack(true)) { - - Attack(tar, EQ::invslot::slotPrimary, true); - } - - if (GetAppearance() == eaDead) { return false; } - // quad attack, does this belong here?? - if (GetSpecialAbility(SpecialAbility::QuadrupleAttack) && CheckBotDoubleAttack(true)) { - Attack(tar, EQ::invslot::slotPrimary, true); - } - } - - if (GetAppearance() == eaDead) { return false; } - // Live AA - Flurry, Rapid Strikes ect (Flurry does not require Triple Attack). - if (int32 flurrychance = (aabonuses.FlurryChance + spellbonuses.FlurryChance + itembonuses.FlurryChance)) { - - if (zone->random.Int(0, 100) < flurrychance) { - - MessageString(Chat::NPCFlurry, YOU_FLURRY); - Attack(tar, EQ::invslot::slotPrimary, false); - - if (GetAppearance() == eaDead) { return false; } - Attack(tar, EQ::invslot::slotPrimary, false); - } - } - - if (GetAppearance() == eaDead) { return false; } - auto ExtraAttackChanceBonus = - (spellbonuses.ExtraAttackChance[0] + itembonuses.ExtraAttackChance[0] + - aabonuses.ExtraAttackChance[0]); - - if ( - ExtraAttackChanceBonus && - p_item && - p_item->GetItem()->IsType2HWeapon() && - zone->random.Int(0, 100) < ExtraAttackChanceBonus - ) { - Attack(tar, EQ::invslot::slotPrimary, false); - } - } - return true; -} - -// We can't fight if we don't have a target, are stun/mezzed or dead.. -bool Bot::TryClassAttacks(Mob* tar) { - -// Stop attacking if the target is enraged - if (!GetTarget() || GetAppearance() == eaDead) { return false; } - if (tar->IsEnraged() && !BehindMob(tar, GetX(), GetY())) { - return false; - } - - // First, special attack per class (kick, backstab etc..) - DoClassAttacks(tar); - return true; -} - -bool Bot::TryRangedAttack(Mob* tar) { - - if (IsBotArcher() && ranged_timer.Check(false)) { - - if (!GetTarget() || GetAppearance() == eaDead) { return false; } - if (GetTarget()->GetHPRatio() <= 99.0f) { - BotRangedAttack(tar); - } - return true; - } - return false; } bool Bot::TryFacingTarget(Mob* tar) { - if (!IsSitting() && !IsFacingMob(tar)) { FaceTarget(tar); return true; @@ -2492,108 +2954,125 @@ bool Bot::TryFacingTarget(Mob* tar) { bool Bot::TryEvade(Mob* tar) { + if (GetSpellTypeHold(BotSpellTypes::Escape)) { + return false; + } - if ( - !IsRooted() && - HasTargetReflection() && - !tar->IsFeared() && - !tar->IsStunned() && - GetClass() == Class::Rogue && - m_evade_timer.Check(false) - ) { - int timer_duration = (HideReuseTime - GetSkillReuseTime(EQ::skills::SkillHide)) * 1000; + switch (GetClass()) { + case Class::Rogue: { + if (GetSkill(EQ::skills::SkillHide) == 0) { + return false; + } - if (timer_duration < 0) { - timer_duration = 0; + if (m_rogue_evade_timer.Check(false)) { + int timer_duration = (HideReuseTime - GetSkillReuseTime(EQ::skills::SkillHide)) * 1000; + + if (timer_duration < 0) { + timer_duration = 0; + } + + m_rogue_evade_timer.Start(timer_duration); + + if (zone->random.Int(0, 260) < (int)GetSkill(EQ::skills::SkillHide)) { + + } + + float hidechance = ((GetSkill(EQ::skills::SkillHide) / 250.0f) + .25) * 100; + float random = zone->random.Real(0, 100); + + if (random < hidechance) { + //SendAppearancePacket(AT_Invis, Invisibility::Invisible); + + if (spellbonuses.ShroudofStealth || aabonuses.ShroudofStealth || itembonuses.ShroudofStealth) { + improved_hidden = true; + hidden = true; + } + else { + hidden = true; + } + } + + if (zone->random.Int(0, 260) < (int)GetSkill(EQ::skills::SkillHide)) { + RaidGroupSay("I have momentarily ducked away from the main combat."); + RogueEvade(tar); + } + else { + RaidGroupSay("My attempts at ducking clear of combat fail."); + } + + //SendAppearancePacket(AT_Invis, Invisibility::Visible); + hidden = false; + + return true; + } + + break; } + case Class::Monk: { + if (GetSkill(EQ::skills::SkillFeignDeath) == 0) { + return false; + } - m_evade_timer.Start(timer_duration); - if (zone->random.Int(0, 260) < (int) GetSkill(EQ::skills::SkillHide)) { - RogueEvade(tar); + if (m_monk_evade_timer.Check(false)) { + int timer_duration = (FeignDeathReuseTime - GetSkillReuseTime(EQ::skills::SkillFeignDeath)) * 1000; + + if (timer_duration < 0) { + timer_duration = 0; + } + + m_monk_evade_timer.Start(timer_duration); + + uint16 primfeign = GetSkill(EQ::skills::SkillFeignDeath); + uint16 secfeign = GetSkill(EQ::skills::SkillFeignDeath); + if (primfeign > 100) { + primfeign = 100; + secfeign = secfeign - 100; + secfeign = secfeign / 2; + } + else + secfeign = 0; + + uint16 totalfeign = primfeign + secfeign; + + if (zone->random.Real(0, 160) > totalfeign) { + //SendAppearancePacket(AT_Anim, ANIM_DEATH); + RaidGroupSay("I have fallen to the ground."); + SetFeigned(false); + } + else { + RaidGroupSay("I have successfully feigned my death."); + SetFeigned(true); + //SendAppearancePacket(AT_Anim, ANIM_DEATH); + } + + //SendAppearancePacket(AT_Anim, ANIM_STAND); + SetFeigned(false); + return true; + } } - return true; } return false; } -void Bot::CheckCombatRange(Mob* tar, float tar_distance, bool& atCombatRange, const EQ::ItemInstance*& p_item, const EQ::ItemInstance*& s_item) { - - atCombatRange= false; - p_item= GetBotItem(EQ::invslot::slotPrimary); - s_item= GetBotItem(EQ::invslot::slotSecondary); - bool behind_mob = false; - bool backstab_weapon = false; - if (GetClass() == Class::Rogue) { - - behind_mob = BehindMob(tar, GetX(), GetY()); // Can be separated for other future use - backstab_weapon = p_item && p_item->GetItemBackstabDamage(); - } - - // Calculate melee distances - float melee_distance_max = 0.0f; - float melee_distance = 0.0f; - - CalcMeleeDistances(tar, p_item, s_item, behind_mob, backstab_weapon, melee_distance_max, melee_distance); - - float caster_distance_max = GetBotCasterMaxRange(melee_distance_max); - - bool atArcheryRange = IsArcheryRange(tar); - - SetRangerCombatWeapon(atArcheryRange); - - bool stop_melee_level = GetLevel() >= GetStopMeleeLevel(); - if (IsBotArcher() && atArcheryRange) { - atCombatRange = true; - } - else if (caster_distance_max && tar_distance <= caster_distance_max && stop_melee_level) { - atCombatRange = true; - } - else if (tar_distance <= melee_distance) { - atCombatRange = true; - } -} - -void Bot::SetRangerCombatWeapon(bool atArcheryRange) { - - if (GetRangerAutoWeaponSelect()) { - bool changeWeapons = false; - - if (atArcheryRange && !IsBotArcher()) { - SetBotArcherySetting(true); - changeWeapons = true; - } - - else if (!atArcheryRange && IsBotArcher()) { - SetBotArcherySetting(false); - changeWeapons = true; - } - - if (changeWeapons) { - ChangeBotArcherWeapons(IsBotArcher()); - } - } -} - -void Bot::CalcMeleeDistances(const Mob* tar, const EQ::ItemInstance* const& p_item, const EQ::ItemInstance* const& s_item, bool behind_mob, bool backstab_weapon, float& melee_distance_max, float& melee_distance) const { +CombatRangeOutput Bot::EvaluateCombatRange(const CombatRangeInput& input) { + CombatRangeOutput o; float size_mod = GetSize(); - float other_size_mod = tar->GetSize(); + float other_size_mod = input.target->GetSize(); // 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; } // For races with a fixed size - if (tar->GetRace() == Race::LavaDragon || tar->GetRace() == Race::Wurm || tar->GetRace() == Race::GhostDragon) { + if (input.target->GetRace() == Race::LavaDragon || input.target->GetRace() == Race::Wurm || input.target->GetRace() == Race::GhostDragon) { other_size_mod = 60.0f; } - else if (other_size_mod < 6.0f) { other_size_mod = 8.0f; } @@ -2605,68 +3084,67 @@ void Bot::CalcMeleeDistances(const Mob* tar, const EQ::ItemInstance* const& p_it if (size_mod > 29.0f) { size_mod *= size_mod; } - else if (size_mod > 19.0f) { size_mod *= (size_mod * 2.0f); } - else { size_mod *= (size_mod * 4.0f); } + if (input.target->GetRace() == Race::VeliousDragon) // Lord Vyemm and other velious dragons + { + size_mod *= 1.75; + } + + if (input.target->GetRace() == Race::DragonSkeleton) // Dracoliche in Fear. Skeletal Dragon + { + size_mod *= 2.25; + } + + size_mod *= RuleR(Combat, HitBoxMod); // used for testing sizemods on different races. + // Prevention of ridiculously sized hit boxes if (size_mod > 10000.0f) { size_mod = (size_mod / 7.0f); } - melee_distance_max = size_mod; + o.melee_distance_max = sqrt(size_mod); - switch (GetClass()) { - case Class::Warrior: - case Class::Paladin: - case Class::ShadowKnight: - if (p_item && p_item->GetItem()->IsType2HWeapon()) { - melee_distance = melee_distance_max * 0.45f; - } - else if ((s_item && s_item->GetItem()->IsTypeShield()) || (!p_item && !s_item)) { - melee_distance = melee_distance_max * 0.35f; - } - else { - melee_distance = melee_distance_max * 0.40f; - } - break; - case Class::Necromancer: - case Class::Wizard: - case Class::Magician: - case Class::Enchanter: - if (p_item && p_item->GetItem()->IsType2HWeapon()) { - melee_distance = melee_distance_max * 0.95f; - } - else { - melee_distance = melee_distance_max * 0.75f; - } - break; - case Class::Rogue: - if (behind_mob && backstab_weapon) { - if (p_item->GetItem()->IsType2HWeapon()) { - melee_distance = melee_distance_max * 0.30f; - } - else { - melee_distance = melee_distance_max * 0.25f; - } - break; - } - // Fall-through - default: - if (p_item && p_item->GetItem()->IsType2HWeapon()) { - melee_distance = melee_distance_max * 0.70f; - } - else { - melee_distance = melee_distance_max * 0.50f; - } + 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(); - break; + if (IsTaunting()) { // Taunting bots + o.melee_distance_min = o.melee_distance_max * 0.25f; + o.melee_distance = o.melee_distance_max * 0.45f; } + 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 * 0.75f)); + o.melee_distance = std::min(max_distance, desired_range); + } + else if (input.stop_melee_level) { // Casters + float desired_range = GetBotDistanceRanged(); + + o.melee_distance_min = std::max(o.melee_distance_max, (desired_range * 0.75f)); + o.melee_distance = std::max((o.melee_distance_max * 1.25f), desired_range); + } + else if (GetMaxMeleeRange()) { // Melee bots set to max melee range + o.melee_distance_min = o.melee_distance_max * 0.80f; + o.melee_distance = o.melee_distance_max * 0.95f; + } + else { // Regular melee + o.melee_distance_min = o.melee_distance_max * 0.30f; + o.melee_distance = o.melee_distance_max * 0.65f; + } + + o.at_combat_range = (input.target_distance <= o.melee_distance); + + return o; } bool Bot::IsValidTarget( @@ -2682,13 +3160,17 @@ bool Bot::IsValidTarget( return false; } + SetHasLoS(DoLosChecks(tar)); + bool invalid_target_state = false; + if (HOLDING || !tar->IsNPC() || - tar->IsMezzed() || + (tar->IsMezzed() && !HasBotAttackFlag(tar)) || + (!Charmed() && tar->GetUltimateOwner()->IsOfClientBotMerc()) || lo_distance > leash_distance || tar_distance > leash_distance || - (!GetAttackingFlag() && !CheckLosFN(tar) && !leash_owner->CheckLosFN(tar)) || + (!GetAttackingFlag() && !HasLoS()) || !IsAttackAllowed(tar) ) { invalid_target_state = true; @@ -2707,15 +3189,17 @@ bool Bot::IsValidTarget( SetTarget(nullptr); SetAttackFlag(false); + SetCombatRoundForAlerts(false); SetAttackingFlag(false); - if (PULLING_BOT) { + if (PULLING_BOT || RETURNING_BOT) { SetPullingFlag(false); SetReturningFlag(false); bot_owner->SetBotPulling(false); if (GetPet()) { - GetPet()->SetPetOrder(m_previous_pet_order); + GetPet()->SetPetOrder(PetOrder::Follow); + GetPet()->CastToNPC()->SaveGuardSpot(true); } } @@ -2742,52 +3226,42 @@ Mob* Bot::GetBotTarget(Client* bot_owner) SetAttackFlag(false); SetAttackingFlag(false); - if (PULLING_BOT) { + if (PULLING_BOT || RETURNING_BOT) { // 'Flags' should only be set on the bot that is pulling SetPullingFlag(false); SetReturningFlag(false); bot_owner->SetBotPulling(false); if (GetPet()) { - GetPet()->SetPetOrder(m_previous_pet_order); + GetPet()->SetPetOrder(PetOrder::Follow); + GetPet()->CastToNPC()->SaveGuardSpot(true); } } - if (GetArchetype() == Archetype::Caster) { - BotMeditate(true); - } + BotMeditate(IsSitting()); } return t; } -bool Bot::ReturningFlagChecks(Client* bot_owner, float fm_distance) {// Need to make it back to group before clearing return flag - - if (fm_distance <= GetFollowDistance()) { - - // Once we're back, clear blocking flags so everyone else can join in - SetReturningFlag(false); - bot_owner->SetBotPulling(false); - - if (GetPet()) { - GetPet()->SetPetOrder(m_previous_pet_order); - } - } - - // Need to keep puller out of combat until they reach their 'return to' destination - if (HasTargetReflection()) { - - SetTarget(nullptr); - WipeHateList(); +bool Bot::TargetValidation(Mob* other) { + if (GetAppearance() == eaDead || GetHP() < 0) { return false; } + + if (!other || other->GetAppearance() == eaDead || other->GetHP() < 0) { + return false; + } + return true; } -bool Bot::PullingFlagChecks(Client* bot_owner) { - - if (!GetTarget()) { +bool Bot::ReturningFlagChecks(Client* bot_owner, Mob* leash_owner, float fm_distance) { + bool target_check = !GetTarget() || Distance(GetPosition(), GetTarget()->GetPosition()) <= 75.0f; + bool returned_check = (NOT_GUARDING && fm_distance <= GetFollowDistance()) || + (GUARDING && DistanceSquared(GetPosition(), GetGuardPoint()) <= GetFollowDistance()); + if (target_check && returned_check) { // Once we're back, clear blocking flags so everyone else can join in WipeHateList(); SetTarget(nullptr); SetPullingFlag(false); @@ -2795,35 +3269,83 @@ bool Bot::PullingFlagChecks(Client* bot_owner) { bot_owner->SetBotPulling(false); if (GetPet()) { - GetPet()->SetPetOrder(m_previous_pet_order); + GetPet()->SetPetOrder(PetOrder::Follow); + GetPet()->CastToNPC()->SaveGuardSpot(true); + + if (HasControllablePet(BotAnimEmpathy::BackOff)) { + GetPet()->WipeHateList(); + GetPet()->SetTarget(nullptr); + } + } + + return false; + } + + if (!IsMoving()) { + glm::vec3 Goal(0, 0, 0); + + if (GUARDING) { + Goal = GetGuardPoint(); + } + else { + Mob* follow_mob = entity_list.GetMob(GetFollowID()); + + if (!follow_mob) { + follow_mob = leash_owner; + SetFollowID(leash_owner->GetID()); + } + + Goal = follow_mob->GetPosition(); + } + + RunTo(Goal.x, Goal.y, Goal.z); + } + + return true; +} + +bool Bot::PullingFlagChecks(Client* bot_owner) { + if (!GetTarget()) { + WipeHateList(); + SetTarget(nullptr); + SetPullingFlag(false); + SetReturningFlag(false); + bot_owner->SetBotPulling(false); + + if (GetPet()) { + GetPet()->SetPetOrder(PetOrder::Follow); + GetPet()->CastToNPC()->SaveGuardSpot(true); } return false; } else if (GetTarget()->GetHateList().size()) { - - WipeHateList(); - SetTarget(nullptr); SetPullingFlag(false); SetReturningFlag(); - if (HasPet() && - (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 1)) { + Mob* my_pet = GetPet(); - GetPet()->WipeHateList(); - GetPet()->SetTarget(nullptr); + if (my_pet) { + if (HasControllablePet(BotAnimEmpathy::BackOff)) { + my_pet->WipeHateList(); + my_pet->SetTarget(nullptr); + } else { + my_pet->AddToHateList(GetTarget(), 1); + my_pet->SetTarget(GetTarget()); + } } if (GetPlayerState() & static_cast(PlayerState::Aggressive)) { SendRemovePlayerState(PlayerState::Aggressive); } + + return false; } return true; } void Bot::HealRotationChecks() { - if (IsMyHealRotationSet()) { if (AIHealRotation(HealRotationTarget(), UseHealRotationFastHeals())) { m_member_of_heal_rotation->SetMemberIsCasting(this); @@ -2838,7 +3360,6 @@ void Bot::HealRotationChecks() { } bool Bot::IsAIProcessValid(const Client* bot_owner, const Group* bot_group, const Raid* raid) { - if (!bot_owner || (!bot_group && !raid) || !IsAIControlled()) { return false; } @@ -2848,11 +3369,11 @@ bool Bot::IsAIProcessValid(const Client* bot_owner, const Group* bot_group, cons SetBotOwner(nullptr); return false; } + return true; } bool Bot::CheckIfCasting(float fm_distance) { - if (IsCasting()) { if (IsHealRotationMember() && m_member_of_heal_rotation->CastingOverride() && @@ -2864,12 +3385,10 @@ bool Bot::CheckIfCasting(float fm_distance) { InterruptSpell(); } else if (AmICastingForHealRotation() && m_member_of_heal_rotation->CastingMember() == this) { - AdvanceHealRotation(false); return true; } else if (GetClass() != Class::Bard) { - if (IsEngaged()) { return true; } @@ -2887,13 +3406,12 @@ bool Bot::CheckIfCasting(float fm_distance) { else if (IsHealRotationMember()) { m_member_of_heal_rotation->SetMemberIsCasting(this, false); } + return false; } bool Bot::CheckIfIncapacitated() { - if (GetPauseAI() || IsStunned() || IsMezzed() || (GetAppearance() == eaDead)) { - if (IsCasting()) { InterruptSpell(); } @@ -2902,6 +3420,7 @@ bool Bot::CheckIfIncapacitated() { AdvanceHealRotation(false); m_member_of_heal_rotation->SetMemberIsCasting(this, false); } + return true; } @@ -2910,18 +3429,35 @@ bool Bot::CheckIfIncapacitated() { return true; } + if (currently_fleeing) { + if (RuleB(Combat, EnableFearPathing) && AI_movement_timer->Check()) { + // Check if we have reached the last fear point + if (DistanceNoZ(glm::vec3(GetX(), GetY(), GetZ()), m_FearWalkTarget) <= 5.0f) { + // Calculate a new point to run to + StopNavigation(); + CalculateNewFearpoint(); + } + + RunTo( + m_FearWalkTarget.x, + m_FearWalkTarget.y, + m_FearWalkTarget.z + ); + } + + return true; + } + return false; } void Bot::SetBerserkState() {// Berserk updates should occur if primary AI criteria are met if (GetClass() == Class::Warrior || GetClass() == Class::Berserker) { - - if (!berserk && GetHP() > 0 && GetHPRatio() < 30.0f) { + if (!berserk && GetHPRatio() < RuleI(Combat, BerserkerFrenzyStart)) { entity_list.MessageCloseString(this, false, 200, 0, BERSERK_START, GetName()); berserk = true; } - - if (berserk && GetHPRatio() >= 30.0f) { + if (berserk && GetHPRatio() > RuleI(Combat, BerserkerFrenzyEnd)) { entity_list.MessageCloseString(this, false, 200, 0, BERSERK_END, GetName()); berserk = false; } @@ -2930,35 +3466,35 @@ void Bot::SetBerserkState() {// Berserk updates should occur if primary AI crite Mob* Bot::SetFollowMob(Client* leash_owner) { Mob* follow_mob = entity_list.GetMob(GetFollowID()); - if (!follow_mob) { + if (!follow_mob) { follow_mob = leash_owner; SetFollowID(leash_owner->GetID()); } + return follow_mob; } Client* Bot::SetLeashOwner(Client* bot_owner, Group* bot_group, Raid* raid, uint32 r_group) const { - Client* leash_owner = nullptr; + if (raid && r_group < MAX_RAID_GROUPS && raid->GetGroupLeader(r_group)) { leash_owner = raid->GetGroupLeader(r_group) && raid->GetGroupLeader(r_group)->IsClient() ? raid->GetGroupLeader(r_group)->CastToClient() : bot_owner; - } else if (bot_group) { leash_owner = (bot_group->GetLeader() && bot_group->GetLeader()->IsClient() ? bot_group->GetLeader()->CastToClient() : bot_owner); - } else { leash_owner = bot_owner; } + return leash_owner; } void Bot::SetOwnerTarget(Client* bot_owner) { - if (GetPet() && PULLING_BOT) { - GetPet()->SetPetOrder(m_previous_pet_order); + if (GetPet() && (PULLING_BOT || RETURNING_BOT)) { + GetPet()->SetPetOrder(PetOrder::Follow); } SetAttackFlag(false); @@ -2969,16 +3505,16 @@ void Bot::SetOwnerTarget(Client* bot_owner) { bot_owner->SetBotPulling(false); if (NOT_HOLDING && NOT_PASSIVE) { - auto attack_target = bot_owner->GetTarget(); - if (attack_target && HasBotAttackFlag(attack_target)) { + if (attack_target && HasBotAttackFlag(attack_target)) { InterruptSpell(); WipeHateList(); AddToHateList(attack_target, 1); SetTarget(attack_target); SetAttackingFlag(); - if (GetPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) { + + if (HasControllablePet(BotAnimEmpathy::Attack)) { GetPet()->WipeHateList(); GetPet()->AddToHateList(attack_target, 1); GetPet()->SetTarget(attack_target); @@ -2989,27 +3525,28 @@ void Bot::SetOwnerTarget(Client* bot_owner) { void Bot::BotPullerProcess(Client* bot_owner, Raid* raid) { SetAttackFlag(false); + SetCombatRoundForAlerts(false); SetAttackingFlag(false); SetPullFlag(false); SetPullingFlag(false); SetReturningFlag(false); bot_owner->SetBotPulling(false); + if (GetPet()) { + GetPet()->SetPetOrder(PetOrder::Follow); + GetPet()->CastToNPC()->SaveGuardSpot(true); + } + if (NOT_HOLDING && NOT_PASSIVE) { auto pull_target = bot_owner->GetTarget(); + if (pull_target) { - if (raid) { - const auto msg = fmt::format("Pulling {} to the group..", pull_target->GetCleanName()); - raid->RaidSay(msg.c_str(), GetCleanName(), 0, 100); - } else { - BotGroupSay( - this, - fmt::format( - "Pulling {}.", - pull_target->GetCleanName() - ).c_str() - ); - } + RaidGroupSay( + fmt::format( + "Pulling {}.", + pull_target->GetCleanName() + ).c_str() + ); InterruptSpell(); WipeHateList(); @@ -3017,12 +3554,16 @@ void Bot::BotPullerProcess(Client* bot_owner, Raid* raid) { SetTarget(pull_target); SetPullingFlag(); bot_owner->SetBotPulling(); - if (HasPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 1)) { + if (GetPet()) { GetPet()->WipeHateList(); GetPet()->SetTarget(nullptr); - m_previous_pet_order = GetPet()->GetPetOrder(); - GetPet()->SetPetOrder(SPO_Guard); + + if (HasControllablePet(BotAnimEmpathy::Guard)) { + m_previous_pet_order = GetPet()->GetPetOrder(); + GetPet()->CastToNPC()->SaveGuardSpot(GetPosition()); + GetPet()->SetPetOrder(PetOrder::Guard); + } } } } @@ -3031,9 +3572,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(SpellEffect::Charm); + } + else { + bot_pet->Depop(); + } + } _botOwner = nullptr; _botOwnerCharacterID = 0; @@ -3089,7 +3639,8 @@ bool Bot::Spawn(Client* botCharacterOwner) { m_targetable = true; entity_list.AddBot(this, true, true); - DataBucket::GetDataBuckets(this); + ClearDataBucketCache(); + LoadDataBucketsCache(); LoadBotSpellSettings(); if (!AI_AddBotSpells(GetBotSpellID())) { GetBotOwner()->CastToClient()->Message( @@ -3105,8 +3656,17 @@ bool Bot::Spawn(Client* botCharacterOwner) { // Load pet LoadPet(); SentPositionPacket(0.0f, 0.0f, 0.0f, 0.0f, 0); - ping_timer.Start(8000); - auto_save_timer.Start(RuleI(Bots, AutosaveIntervalSeconds) * 1000); + m_ping_timer.Start(8000); + m_auto_save_timer.Start(RuleI(Bots, AutosaveIntervalSeconds) * 1000); + + m_dont_heal_me_before = 0; + m_dont_buff_me_before = Timer::GetCurrentTime() + 400; + m_dont_dot_me_before = 0; + m_dont_root_me_before = 0; + m_dont_snare_me_before = 0; + m_dont_cure_me_before = 0; + + // there is something askew with spawn struct appearance fields... // I re-enabled this until I can sort it out const auto& m = GetBotItemSlots(); @@ -3129,6 +3689,9 @@ bool Bot::Spawn(Client* botCharacterOwner) { SetRaidGrouped(true); raid->LearnMembers(); raid->VerifyRaid(); + SetStoredRaid(raid); + p_raid_instance = raid; + SetVerifiedRaid(true); } } else if (auto group = entity_list.GetGroupByMobName(GetName())) { @@ -3143,6 +3706,10 @@ bool Bot::Spawn(Client* botCharacterOwner) { } } + if (IsBotRanged()) { + ChangeBotRangedWeapons(true); + } + return true; } @@ -3426,7 +3993,6 @@ void Bot::LevelBotWithClient(Client* c, uint8 new_level, bool send_appearance) { parse->EventBot(EVENT_LEVEL_UP, e, nullptr, std::to_string(levels_change), 0); } - e->SetPetChooser(false); // not sure what this does, but was in bot 'update' code e->CalcBotStats(c->GetBotOption(Client::booStatsUpdate)); if (send_appearance) { @@ -3659,7 +4225,6 @@ bool Bot::AddBotToGroup(Bot* bot, Group* group) { if (bot && group->AddMember(bot)) { if (group->GetLeader()) { - bot->SetFollowID(group->GetLeader()->GetID()); // Need to send this only once when a group is formed with a bot so the client knows it is also the group leader if (group->GroupCount() == 2 && group->GetLeader()->IsClient()) { group->UpdateGroupAAs(); @@ -3675,7 +4240,7 @@ bool Bot::AddBotToGroup(Bot* bot, Group* group) { } // Completes a trade with a client bot owner -void Bot::FinishTrade(Client* client, BotTradeType trade_type) +void Bot::FinishTrade(Client* client, BotTradeType trade_type, int16 chosen_slot) { if ( !client || @@ -3699,7 +4264,7 @@ void Bot::FinishTrade(Client* client, BotTradeType trade_type) else if (trade_type == BotTradeClientNoDropNoTrade) { // Items being traded are found on the Client's cursor slot, slot id 30. This item can be either a single item or it can be a bag. // If it is a bag, then we have to search for items in slots 331 thru 340 - PerformTradeWithClient(EQ::invslot::slotCursor, EQ::invslot::slotCursor, client); + PerformTradeWithClient(EQ::invslot::slotCursor, EQ::invslot::slotCursor, client, chosen_slot); // TODO: Add logic here to test if the item in SLOT_CURSOR is a container type, if it is then we need to call the following: // PerformTradeWithClient(331, 340, client); @@ -3707,7 +4272,7 @@ void Bot::FinishTrade(Client* client, BotTradeType trade_type) } // Perfoms the actual trade action with a client bot owner -void Bot::PerformTradeWithClient(int16 begin_slot_id, int16 end_slot_id, Client* client) +void Bot::PerformTradeWithClient(int16 begin_slot_id, int16 end_slot_id, Client* client, int16 chosen_slot) { using namespace EQ; @@ -3727,14 +4292,16 @@ void Bot::PerformTradeWithClient(int16 begin_slot_id, int16 end_slot_id, Client* ClientReturn(ItemInstance* item, int16 from) : return_item_instance(item), from_bot_slot(from) { } }; - static const int16 bot_equip_order[invslot::EQUIPMENT_COUNT] = { - invslot::slotCharm, invslot::slotEar1, invslot::slotHead, invslot::slotFace, - invslot::slotEar2, invslot::slotNeck, invslot::slotShoulders, invslot::slotArms, - invslot::slotBack, invslot::slotWrist1, invslot::slotWrist2, invslot::slotRange, - invslot::slotHands, invslot::slotPrimary, invslot::slotSecondary, invslot::slotFinger1, - invslot::slotFinger2, invslot::slotChest, invslot::slotLegs, invslot::slotFeet, - invslot::slotWaist, invslot::slotPowerSource, invslot::slotAmmo - }; + std::vector bot_equip_order; + + if (chosen_slot != INVALID_INDEX) { + bot_equip_order.emplace_back(chosen_slot); + } + else { + for (int16 i = 0; i < invslot::EQUIPMENT_COUNT; ++i) { + bot_equip_order.push_back(i); + } + } enum { stageStackable = 0, stageEmpty, stageReplaceable }; @@ -3823,23 +4390,44 @@ void Bot::PerformTradeWithClient(int16 begin_slot_id, int16 end_slot_id, Client* return; } - if (trade_instance->IsStackable() && trade_instance->GetCharges() < trade_instance->GetItem()->StackSize) { // temp until partial stacks are implemented - if (trade_event_exists) { - event_trade.push_back(ClientTrade(trade_instance, trade_index)); - continue; - } - else { + if (RuleI(Bots, StackSizeMin) != -1) { + if ( + trade_instance->IsStackable() && + trade_instance->GetCharges() < RuleI(Bots, StackSizeMin) + ) { // temp until partial stacks are implemented + if (trade_event_exists) { + event_trade.push_back(ClientTrade(trade_instance, trade_index)); + continue; + } + client->Message( Chat::Yellow, fmt::format( - "{} is only a partially stacked item, the trade has been cancelled!", - item_link + "{} is too small of a stack, you need atleast {}, the trade has been cancelled!", + item_link, + RuleI(Bots, StackSizeMin) ).c_str() ); client->ResetTrade(); + return; + } } + else if ( + trade_instance->IsStackable() && + trade_instance->GetCharges() < trade_instance->GetItem()->StackSize + ) { + client->Message( + Chat::Yellow, + fmt::format( + "{} is only a partially stacked item, the trade has been cancelled!", + item_link + ).c_str() + ); + client->ResetTrade(); + return; + } for (int m = EQ::invaug::SOCKET_BEGIN; m <= EQ::invaug::SOCKET_END; ++m) { const auto augment = trade_instance->GetAugment(m); @@ -3898,9 +4486,15 @@ void Bot::PerformTradeWithClient(int16 begin_slot_id, int16 end_slot_id, Client* } if ( - !trade_instance->IsClassEquipable(GetClass()) || + ( + !trade_instance->IsClassEquipable(GetClass()) && + !RuleB(Bots, AllowBotEquipAnyClassGear) + ) || GetLevel() < trade_instance->GetItem()->ReqLevel || - (!trade_instance->IsRaceEquipable(GetBaseRace()) && !RuleB(Bots, AllowBotEquipAnyRaceGear)) + ( + !trade_instance->IsRaceEquipable(GetBaseRace()) && + !RuleB(Bots, AllowBotEquipAnyRaceGear) + ) ) { if (trade_event_exists) { event_trade.push_back(ClientTrade(trade_instance, trade_index)); @@ -4192,14 +4786,7 @@ void Bot::PerformTradeWithClient(int16 begin_slot_id, int16 end_slot_id, Client* std::vector args = { return_iterator.return_item_instance }; - parse->EventBot( - EVENT_UNEQUIP_ITEM_BOT, - this, - nullptr, - export_string, - return_iterator.return_item_instance->GetID(), - &args - ); + parse->EventBot(EVENT_UNEQUIP_ITEM_BOT, this, nullptr, export_string, return_iterator.return_item_instance->GetID(), &args); } if (return_instance) { @@ -4264,14 +4851,7 @@ void Bot::PerformTradeWithClient(int16 begin_slot_id, int16 end_slot_id, Client* std::vector args = { trade_iterator.trade_item_instance }; - parse->EventBot( - EVENT_EQUIP_ITEM_BOT, - this, - nullptr, - export_string, - trade_iterator.trade_item_instance->GetID(), - &args - ); + parse->EventBot(EVENT_EQUIP_ITEM_BOT, this, nullptr, export_string, trade_iterator.trade_item_instance->GetID(), &args); } trade_iterator.trade_item_instance = nullptr; // actual deletion occurs in client delete below @@ -4362,7 +4942,7 @@ bool Bot::Death(Mob *killer_mob, int64 damage, uint16 spell_id, EQ::skills::Skil LeaveHealRotationMemberPool(); - if ((GetPullingFlag() || GetReturningFlag()) && my_owner && my_owner->IsClient()) { + if ((PULLING_BOT || RETURNING_BOT) && my_owner && my_owner->IsClient()) { my_owner->CastToClient()->SetBotPulling(false); } @@ -4442,15 +5022,15 @@ float Bot::GetProcChances(float ProcBonus, uint16 hand) { float ProcChance = 0.0f; uint32 weapon_speed = 0; switch (hand) { - case EQ::invslot::slotPrimary: - weapon_speed = attack_timer.GetDuration(); - break; - case EQ::invslot::slotSecondary: - weapon_speed = attack_dw_timer.GetDuration(); - break; - case EQ::invslot::slotRange: - weapon_speed = ranged_timer.GetDuration(); - break; + case EQ::invslot::slotPrimary: + weapon_speed = attack_timer.GetDuration(); + break; + case EQ::invslot::slotSecondary: + weapon_speed = attack_dw_timer.GetDuration(); + break; + case EQ::invslot::slotRange: + weapon_speed = ranged_timer.GetDuration(); + break; } if (weapon_speed < RuleI(Combat, MinHastedDelay)) @@ -4559,84 +5139,93 @@ int Bot::GetBaseSkillDamage(EQ::skills::SkillType skill, Mob *target) int base = EQ::skills::GetBaseDamage(skill); auto skill_level = GetSkill(skill); switch (skill) { - case EQ::skills::SkillDragonPunch: - case EQ::skills::SkillEagleStrike: - case EQ::skills::SkillTigerClaw: - if (skill_level >= 25) - base++; - if (skill_level >= 75) - base++; - if (skill_level >= 125) - base++; - if (skill_level >= 175) - base++; - return base; - case EQ::skills::SkillFrenzy: - if (GetBotItem(EQ::invslot::slotPrimary)) { - if (GetLevel() > 15) - base += GetLevel() - 15; - if (base > 23) - base = 23; - if (GetLevel() > 50) - base += 2; - if (GetLevel() > 54) + case EQ::skills::SkillDragonPunch: + case EQ::skills::SkillEagleStrike: + case EQ::skills::SkillTigerClaw: + if (skill_level >= 25) base++; - if (GetLevel() > 59) + if (skill_level >= 75) base++; - } - return base; - case EQ::skills::SkillFlyingKick: { - float skill_bonus = skill_level / 9.0f; - float ac_bonus = 0.0f; - auto inst = GetBotItem(EQ::invslot::slotFeet); - if (inst) - ac_bonus = inst->GetItemArmorClass(true) / 25.0f; - if (ac_bonus > skill_bonus) - ac_bonus = skill_bonus; - return static_cast(ac_bonus + skill_bonus); - } - case EQ::skills::SkillKick: { - float skill_bonus = skill_level / 10.0f; - float ac_bonus = 0.0f; - auto inst = GetBotItem(EQ::invslot::slotFeet); - if (inst) - ac_bonus = inst->GetItemArmorClass(true) / 25.0f; - if (ac_bonus > skill_bonus) - ac_bonus = skill_bonus; - return static_cast(ac_bonus + skill_bonus); - } - case EQ::skills::SkillBash: { - float skill_bonus = skill_level / 10.0f; - float ac_bonus = 0.0f; - const EQ::ItemInstance *inst = nullptr; - if (HasShieldEquipped()) - inst = GetBotItem(EQ::invslot::slotSecondary); - else if (HasTwoHanderEquipped()) - inst = GetBotItem(EQ::invslot::slotPrimary); - if (inst) - ac_bonus = inst->GetItemArmorClass(true) / 25.0f; - if (ac_bonus > skill_bonus) - ac_bonus = skill_bonus; - return static_cast(ac_bonus + skill_bonus); - } - case EQ::skills::SkillBackstab: { - float skill_bonus = static_cast(skill_level) * 0.02f; - auto inst = GetBotItem(EQ::invslot::slotPrimary); - if (inst && inst->GetItem() && inst->GetItem()->ItemType == EQ::item::ItemType1HPiercing) { - base = inst->GetItemBackstabDamage(true); - if (!inst->GetItemBackstabDamage()) - base += inst->GetItemWeaponDamage(true); - if (target) { - if (inst->GetItemElementalFlag(true) && inst->GetItemElementalDamage(true)) - base += target->ResistElementalWeaponDmg(inst); - if (inst->GetItemBaneDamageBody(true) || inst->GetItemBaneDamageRace(true)) - base += target->CheckBaneDamage(inst); + if (skill_level >= 125) + base++; + if (skill_level >= 175) + base++; + return base; + case EQ::skills::SkillFrenzy: + if (GetBotItem(EQ::invslot::slotPrimary)) { + if (GetLevel() > 15) + base += GetLevel() - 15; + if (base > 23) + base = 23; + if (GetLevel() > 50) + base += 2; + if (GetLevel() > 54) + base++; + if (GetLevel() > 59) + base++; } + return base; + case EQ::skills::SkillFlyingKick: { + float skill_bonus = skill_level / 9.0f; + float ac_bonus = 0.0f; + auto inst = GetBotItem(EQ::invslot::slotFeet); + if (inst) + ac_bonus = inst->GetItemArmorClass(true) / 25.0f; + if (ac_bonus > skill_bonus) + ac_bonus = skill_bonus; + return static_cast(ac_bonus + skill_bonus); } - return static_cast(static_cast(base) * (skill_bonus + 2.0f)); - } - default: - return 0; + case EQ::skills::SkillKick: { + float skill_bonus = skill_level / 10.0f; + float ac_bonus = 0.0f; + auto inst = GetBotItem(EQ::invslot::slotFeet); + if (inst) + ac_bonus = inst->GetItemArmorClass(true) / 25.0f; + if (ac_bonus > skill_bonus) + ac_bonus = skill_bonus; + return static_cast(ac_bonus + skill_bonus); + } + case EQ::skills::SkillBash: { + float skill_bonus = skill_level / 10.0f; + float ac_bonus = 0.0f; + const EQ::ItemInstance *inst = nullptr; + if (HasShieldEquipped()) + inst = GetBotItem(EQ::invslot::slotSecondary); + else if (HasTwoHanderEquipped()) + inst = GetBotItem(EQ::invslot::slotPrimary); + if (inst) + ac_bonus = inst->GetItemArmorClass(true) / 25.0f; + if (ac_bonus > skill_bonus) + ac_bonus = skill_bonus; + return static_cast(ac_bonus + skill_bonus); + } + case EQ::skills::SkillBackstab: { + float skill_bonus = static_cast(skill_level) * 0.02f; + auto inst = GetBotItem(EQ::invslot::slotPrimary); + if (inst && inst->GetItem() && inst->GetItem()->ItemType == EQ::item::ItemType1HPiercing) { + base = inst->GetItemBackstabDamage(true); + if (!inst->GetItemBackstabDamage()) + base += inst->GetItemWeaponDamage(true); + if (target) { + if ( + inst->GetItemElementalFlag(true) && + inst->GetItemElementalDamage(true) + ) { + base += target->ResistElementalWeaponDmg(inst); + } + + if ( + inst->GetItemBaneDamageBody(true) || + inst->GetItemBaneDamageRace(true) + ) { + base += target->CheckBaneDamage(inst); + } + } + } + return static_cast(static_cast(base) * (skill_bonus + 2.0f)); + } + default: + return 0; } } @@ -4691,107 +5280,30 @@ void Bot::DoSpecialAttackDamage(Mob *who, EQ::skills::SkillType skill, int32 max TrySkillProc(who, skill, (ReuseTime * 1000), true); } -void Bot::TryBackstab(Mob *other, int ReuseTime) { - if (!other) - return; - - bool bIsBehind = false; - bool bCanFrontalBS = false; - const EQ::ItemInstance* inst = GetBotItem(EQ::invslot::slotPrimary); - const EQ::ItemData* botpiercer = nullptr; - if (inst) - botpiercer = inst->GetItem(); - - if (!botpiercer || (botpiercer->ItemType != EQ::item::ItemType1HPiercing)) { - BotGroupSay(this, "I can't backstab with this weapon!"); - return; - } - - int tripleChance = (itembonuses.TripleBackstab + spellbonuses.TripleBackstab + aabonuses.TripleBackstab); - if (BehindMob(other, GetX(), GetY())) - bIsBehind = true; - else { - int FrontalBSChance = (itembonuses.FrontalBackstabChance + spellbonuses.FrontalBackstabChance + aabonuses.FrontalBackstabChance); - if (FrontalBSChance && (FrontalBSChance > zone->random.Int(0, 100))) - bCanFrontalBS = true; - } - - if (bIsBehind || bCanFrontalBS) { - int chance = (10 + (GetDEX() / 10) + (itembonuses.HeroicDEX / 10)); - if (level >= 60 && other->GetLevel() <= 45 && !other->CastToNPC()->IsEngaged() && other->GetHP()<= 32000 && other->IsNPC() && zone->random.Real(0, 99) < chance) { - entity_list.MessageCloseString(this, false, 200, Chat::MeleeCrit, ASSASSINATES, GetName()); - RogueAssassinate(other); - } else { - RogueBackstab(other); - if (level > 54) { - float DoubleAttackProbability = ((GetSkill(EQ::skills::SkillDoubleAttack) + GetLevel()) / 500.0f); - if (zone->random.Real(0, 1) < DoubleAttackProbability) { - if (other->GetHP() > 0) - RogueBackstab(other,false,ReuseTime); - - if (tripleChance && other->GetHP() > 0 && tripleChance > zone->random.Int(0, 100)) - RogueBackstab(other,false,ReuseTime); - } - } - } - } else if (aabonuses.FrontalBackstabMinDmg || itembonuses.FrontalBackstabMinDmg || spellbonuses.FrontalBackstabMinDmg) { - m_specialattacks = eSpecialAttacks::ChaoticStab; - RogueBackstab(other, true); - m_specialattacks = eSpecialAttacks::None; - } - else - Attack(other, EQ::invslot::slotPrimary); -} - -void Bot::RogueBackstab(Mob *other, bool min_damage, int ReuseTime) -{ - if (!other) - return; - - EQ::ItemInstance *botweaponInst = GetBotItem(EQ::invslot::slotPrimary); - if (botweaponInst) { - if (!GetWeaponDamage(other, botweaponInst)) - return; - } else if (!GetWeaponDamage(other, (const EQ::ItemData *)nullptr)) { - return; - } - - int64 hate = 0; - - int base_damage = GetBaseSkillDamage(EQ::skills::SkillBackstab, other); - hate = base_damage; - - DoSpecialAttackDamage(other, EQ::skills::SkillBackstab, base_damage, 0, hate, ReuseTime); - DoAnim(anim1HPiercing); -} - -void Bot::RogueAssassinate(Mob* other) { - EQ::ItemInstance* botweaponInst = GetBotItem(EQ::invslot::slotPrimary); - if (botweaponInst) { - if (GetWeaponDamage(other, botweaponInst)) - other->Damage(this, 32000, SPELL_UNKNOWN, EQ::skills::SkillBackstab); - else - other->Damage(this, -5, SPELL_UNKNOWN, EQ::skills::SkillBackstab); - } - - DoAnim(anim1HPiercing); -} - void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { - if (!target || spellend_timer.Enabled() || IsFeared() || IsStunned() || IsMezzed() || DivineAura() || GetHP() < 0 || !IsAttackAllowed(target)) + if ( + !target || + GetAppearance() == eaDead || + spellend_timer.Enabled() || + IsFeared() || + IsStunned() || + IsMezzed() || + DivineAura() || + GetHP() < 0 || + !IsAttackAllowed(target) + ) { return; + } bool taunt_time = taunt_timer.Check(); bool ca_time = classattack_timer.Check(false); bool ma_time = monkattack_timer.Check(false); - bool ka_time = knightattack_timer.Check(false); if (taunt_time) { // Bots without this skill shouldn't be 'checking' on this timer..let's just disable it and avoid the extra IsAttackAllowed() checks // Note: this is done here instead of NPC::ctor() because taunt skill can be acquired during level ups (the timer is re-enabled in CalcBotStats()) if (!GetSkill(EQ::skills::SkillTaunt)) { - taunt_timer.Disable(); return; } @@ -4801,40 +5313,13 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { } } - if ((ca_time || ma_time || ka_time) && !IsAttackAllowed(target)) { + if ((ca_time || ma_time) && !IsAttackAllowed(target)) { return; } - if (ka_time) { - - switch (GetClass()) { - case Class::ShadowKnight: { - CastSpell(SPELL_NPC_HARM_TOUCH, target->GetID()); - knightattack_timer.Start(HarmTouchReuseTime * 1000); - - break; - } - case Class::Paladin: { - if (GetHPRatio() < 20) { - CastSpell(SPELL_LAY_ON_HANDS, GetID()); - knightattack_timer.Start(LayOnHandsReuseTime * 1000); - } - else { - knightattack_timer.Start(2000); - } - - break; - } - default: { - break; - } - } - } - - if (taunting && target->IsNPC() && taunt_time) { + if (IsTaunting() && target->IsNPC() && taunt_time) { if (GetTarget() && GetTarget()->GetHateTop() && GetTarget()->GetHateTop() != this) { - BotGroupSay( - this, + RaidGroupSay( fmt::format( "Taunting {}.", target->GetCleanName() @@ -4847,58 +5332,58 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { if (ma_time) { switch (GetClass()) { - case Class::Monk: { - int reuse = (MonkSpecialAttack(target, EQ::skills::SkillTigerClaw) - 1); + case Class::Monk: { + int reuse = (MonkSpecialAttack(target, EQ::skills::SkillTigerClaw) - 1); - // Live AA - Technique of Master Wu - int wuchance = itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack; + // Live AA - Technique of Master Wu + int wuchance = itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack; - if (wuchance) { - const int MonkSPA[5] = { - EQ::skills::SkillFlyingKick, - EQ::skills::SkillDragonPunch, - EQ::skills::SkillEagleStrike, - EQ::skills::SkillTigerClaw, - EQ::skills::SkillRoundKick - }; - int extra = 0; - // always 1/4 of the double attack chance, 25% at rank 5 (100/4) - while (wuchance > 0) { - if (zone->random.Roll(wuchance)) { - ++extra; + if (wuchance) { + const int MonkSPA[5] = { + EQ::skills::SkillFlyingKick, + EQ::skills::SkillDragonPunch, + EQ::skills::SkillEagleStrike, + EQ::skills::SkillTigerClaw, + EQ::skills::SkillRoundKick + }; + int extra = 0; + // always 1/4 of the double attack chance, 25% at rank 5 (100/4) + while (wuchance > 0) { + if (zone->random.Roll(wuchance)) { + ++extra; + } + else { + break; + } + wuchance /= 4; } - else { - break; + + Mob* bo = GetBotOwner(); + if (bo && bo->IsClient() && bo->CastToClient()->GetBotOption(Client::booMonkWuMessage)) { + + bo->Message( + GENERIC_EMOTE, + "The spirit of Master Wu fills %s! %s gains %d additional attack(s).", + GetCleanName(), + GetCleanName(), + extra + ); + } + + auto classic = RuleB(Combat, ClassicMasterWu); + while (extra && TargetValidation(GetTarget())) { + MonkSpecialAttack(GetTarget(), (classic ? MonkSPA[zone->random.Int(0, 4)] : EQ::skills::SkillTigerClaw)); + --extra; } - wuchance /= 4; } - Mob* bo = GetBotOwner(); - if (bo && bo->IsClient() && bo->CastToClient()->GetBotOption(Client::booMonkWuMessage)) { + float HasteModifier = (GetHaste() * 0.01f); + monkattack_timer.Start((reuse * 1000) / HasteModifier); - bo->Message( - GENERIC_EMOTE, - "The spirit of Master Wu fills %s! %s gains %d additional attack(s).", - GetCleanName(), - GetCleanName(), - extra - ); - } - - auto classic = RuleB(Combat, ClassicMasterWu); - while (extra) { - MonkSpecialAttack(GetTarget(), (classic ? MonkSPA[zone->random.Int(0, 4)] : EQ::skills::SkillTigerClaw)); - --extra; - } + break; } - - float HasteModifier = (GetHaste() * 0.01f); - monkattack_timer.Start((reuse * 1000) / HasteModifier); - - break; - } - default: - break; + default: + break; } } @@ -4911,53 +5396,77 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { int bot_level = GetLevel(); int reuse = (TauntReuseTime * 1000); // Same as Bash and Kick bool did_attack = false; + switch (GetClass()) { case Class::Warrior: - if (bot_level >= RuleI(Combat, NPCBashKickLevel)) { - bool canBash = false; - if ((GetRace() == OGRE || GetRace() == TROLL || GetRace() == BARBARIAN) || (m_inv.GetItem(EQ::invslot::slotSecondary) && m_inv.GetItem(EQ::invslot::slotSecondary)->GetItem()->ItemType == EQ::item::ItemTypeShield) || (m_inv.GetItem(EQ::invslot::slotPrimary) && m_inv.GetItem(EQ::invslot::slotPrimary)->GetItem()->IsType2HWeapon() && GetAA(aa2HandBash) >= 1)) - canBash = true; - - if (!canBash || zone->random.Int(0, 100) > 25) - skill_to_use = EQ::skills::SkillKick; - else - skill_to_use = EQ::skills::SkillBash; - } - case Class::Ranger: - case Class::Beastlord: - skill_to_use = EQ::skills::SkillKick; - break; - case Class::Berserker: - skill_to_use = EQ::skills::SkillFrenzy; - break; case Class::Cleric: case Class::ShadowKnight: case Class::Paladin: - if (bot_level >= RuleI(Combat, NPCBashKickLevel)) { - if ((GetRace() == OGRE || GetRace() == TROLL || GetRace() == BARBARIAN) || (m_inv.GetItem(EQ::invslot::slotSecondary) && m_inv.GetItem(EQ::invslot::slotSecondary)->GetItem()->ItemType == EQ::item::ItemTypeShield) || (m_inv.GetItem(EQ::invslot::slotPrimary) && m_inv.GetItem(EQ::invslot::slotPrimary)->GetItem()->IsType2HWeapon() && GetAA(aa2HandBash) >= 1)) + { + bool is_large_race = ( + GetBaseRace() == Race::Ogre || + GetBaseRace() == Race::Troll || + GetBaseRace() == Race::Barbarian + ); + bool has_bash_skill = GetSkill(EQ::skills::SkillBash) > 0; + bool has_shield_in_secondary = + m_inv.GetItem(EQ::invslot::slotSecondary) && + m_inv.GetItem(EQ::invslot::slotSecondary)->GetItem()->ItemType == EQ::item::ItemTypeShield; + bool has_two_hander_with_aa = + m_inv.GetItem(EQ::invslot::slotPrimary) && + m_inv.GetItem(EQ::invslot::slotPrimary)->GetItem()->IsType2HWeapon() && + GetAA(aa2HandBash) >= 1; + bool can_bash = + is_large_race || + ( + has_bash_skill && + ( + has_shield_in_secondary || has_two_hander_with_aa + ) + ); + + if (can_bash) { skill_to_use = EQ::skills::SkillBash; + } + + break; } + case Class::Ranger: + case Class::Beastlord: + if (GetSkill(EQ::skills::SkillKick)) { + skill_to_use = EQ::skills::SkillKick; + } + + break; + case Class::Berserker: + if (GetSkill(EQ::skills::SkillFrenzy)) { + skill_to_use = EQ::skills::SkillFrenzy; + } + break; case Class::Monk: - if (GetLevel() >= 30) { + if (GetSkill(EQ::skills::SkillFlyingKick)) { skill_to_use = EQ::skills::SkillFlyingKick; } - else if (GetLevel() >= 25) { + else if (GetSkill(EQ::skills::SkillDragonPunch)) { skill_to_use = EQ::skills::SkillDragonPunch; } - else if (GetLevel() >= 20) { + else if (GetSkill(EQ::skills::SkillEagleStrike)) { skill_to_use = EQ::skills::SkillEagleStrike; } - else if (GetLevel() >= 5) { + else if (GetSkill(EQ::skills::SkillRoundKick)) { skill_to_use = EQ::skills::SkillRoundKick; } - else { + else if (GetSkill(EQ::skills::SkillKick)) { skill_to_use = EQ::skills::SkillKick; } break; case Class::Rogue: - skill_to_use = EQ::skills::SkillBackstab; + if (GetSkill(EQ::skills::SkillBackstab)) { + skill_to_use = EQ::skills::SkillBackstab; + } + break; } @@ -4975,18 +5484,37 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { } if (skill_to_use == EQ::skills::SkillFrenzy) { - int AtkRounds = 3; + int AtkRounds = 1; + float HasteMod = (FrenzyReuseTime - 1) / (GetHaste() * 0.01f); + reuse = (FrenzyReuseTime * 1000); DoAnim(anim2HSlashing); - reuse = (FrenzyReuseTime * 1000); - did_attack = true; - while(AtkRounds > 0) { - if (GetTarget() && (AtkRounds == 1 || zone->random.Int(0, 100) < 75)) { - DoSpecialAttackDamage(GetTarget(), EQ::skills::SkillFrenzy, dmg, 0, dmg, reuse, true); + // bards can do riposte frenzy for some reason + if (!IsRiposte && GetClass() == Class::Berserker) { + int chance = GetLevel() * 2 + GetSkill(EQ::skills::SkillFrenzy); + + if (zone->random.Roll0(450) < chance) { + AtkRounds++; + } + + if (zone->random.Roll0(450) < chance) { + AtkRounds++; + } + } + + while (AtkRounds > 0) { + if (GetTarget() != this && TargetValidation(GetTarget())) { + DoSpecialAttackDamage(GetTarget(), EQ::skills::SkillFrenzy, dmg, 0, dmg, HasteMod); } AtkRounds--; } + + if (reuse > 0 && IsRiposte) { + reuse = 0; + } + + did_attack = true; } if (skill_to_use == EQ::skills::SkillKick) { @@ -5044,7 +5572,7 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { } auto classic = RuleB(Combat, ClassicMasterWu); - while (extra) { + while (extra && TargetValidation(GetTarget())) { MonkSpecialAttack(GetTarget(), (classic ? MonkSPA[zone->random.Int(0, 4)] : skill_to_use)); --extra; } @@ -5057,11 +5585,14 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { if (skill_to_use == EQ::skills::SkillBackstab) { reuse = (BackstabReuseTime * 1000); did_attack = true; - if (IsRiposte) - reuse = 0; - TryBackstab(target,reuse); + if (IsRiposte) { + reuse = 0; + } + + Mob::TryBackstab(target, reuse); } + classattack_timer.Start(reuse / HasteModifier); } @@ -5173,9 +5704,9 @@ void Bot::BotOrderCampAll(Client* c, uint8 class_id) { void Bot::ProcessBotOwnerRefDelete(Mob* botOwner) { if (botOwner && botOwner->IsClient()) { - std::list BotList = entity_list.GetBotsByBotOwnerCharacterID(botOwner->CastToClient()->CharacterID()); + std::vector BotList = entity_list.GetBotsByBotOwnerCharacterID(botOwner->CastToClient()->CharacterID()); if (!BotList.empty()) { - for (std::list::iterator botListItr = BotList.begin(); botListItr != BotList.end(); ++botListItr) { + for (std::vector::iterator botListItr = BotList.begin(); botListItr != BotList.end(); ++botListItr) { Bot* tempBot = *botListItr; if (tempBot) { tempBot->SetTarget(nullptr); @@ -5350,7 +5881,7 @@ bool Bot::CastSpell( ) { bool Result = false; if (zone && !zone->IsSpellBlocked(spell_id, glm::vec3(GetPosition()))) { - // LogSpells("CastSpell called for spell [{}] ([{}]) on entity [{}], slot [{}], time [{}], mana [{}], from item slot [{}]", spells[spell_id].name, spell_id, target_id, slot, cast_time, mana_cost, (item_slot==0xFFFFFFFF)?999:item_slot); + // LogSpells("CastSpell called for spell [{}] ([{}]) on entity [{}], slot [{}], time [{}], mana [{}], from item slot [{}]", GetSpellName(spell_id), spell_id, target_id, slot, cast_time, mana_cost, (item_slot==0xFFFFFFFF)?999:item_slot); if (casting_spell_id == spell_id) { ZeroCastingVars(); @@ -5362,22 +5893,13 @@ bool Bot::CastSpell( casting_spell_id || delaytimer || spellend_timer.Enabled() || - IsStunned() || - IsFeared() || - IsMezzed() || + ((IsStunned() || IsMezzed() || DivineAura()) && !IsCastNotStandingSpell(spell_id)) || (IsSilenced() && !IsDiscipline(spell_id)) || (IsAmnesiad() && IsDiscipline(spell_id)) ) { LogSpellsDetail("Spell casting canceled: not able to cast now. Valid? [{}] casting [{}] waiting? [{}] spellend? [{}] stunned? [{}] feared? [{}] mezed? [{}] silenced? [{}]", IsValidSpell(spell_id), casting_spell_id, delaytimer, spellend_timer.Enabled(), IsStunned(), IsFeared(), IsMezzed(), IsSilenced() ); - if (IsSilenced() && !IsDiscipline(spell_id)) { - MessageString(Chat::White, SILENCED_STRING); - } - - if (IsAmnesiad() && IsDiscipline(spell_id)) { - MessageString(Chat::White, MELEE_SILENCE); - } if (casting_spell_id) { AI_Bot_Event_SpellCastFinished(false, static_cast(casting_spell_slot)); @@ -5388,7 +5910,7 @@ bool Bot::CastSpell( } if (IsDetrimentalSpell(spell_id) && !zone->CanDoCombat()) { - MessageString(Chat::White, SPELL_WOULDNT_HOLD); + //MessageString(Chat::White, SPELL_WOULDNT_HOLD); if (casting_spell_id) { AI_Bot_Event_SpellCastFinished(false, static_cast(casting_spell_slot)); } @@ -5398,7 +5920,7 @@ bool Bot::CastSpell( if (DivineAura()) { LogSpellsDetail("Spell casting canceled: cannot cast while Divine Aura is in effect"); - InterruptSpell(173, 0x121, false); + InterruptSpell(SPELL_FIZZLE, 0x121, false); return false; } @@ -5452,7 +5974,7 @@ bool Bot::SpellOnTarget( if (spelltar->IsPet()) { for (int i = 0; i < EFFECT_COUNT; ++i) { - if (spells[spell_id].effect_id[i] == SE_Illusion) { + if (spells[spell_id].effect_id[i] == SpellEffect::Illusion) { return false; } } @@ -5496,14 +6018,17 @@ bool Bot::IsImmuneToSpell(uint16 spell_id, Mob *caster) { bool Bot::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_center, CastAction_type &CastAction, EQ::spells::CastingSlot slot) { bool Result = false; - SpellTargetType targetType = spells[spell_id].target_type; - if (targetType == ST_GroupClientAndPet) { + SpellTargetType target_type = spells[spell_id].target_type; + + if (target_type == ST_GroupClientAndPet) { if ((spell_id == 1768 && zone->GetZoneID() == 202) || (!IsDetrimentalSpell(spell_id))) { CastAction = SingleTarget; return true; } } + Result = Mob::DetermineSpellTargets(spell_id, spell_target, ae_center, CastAction, slot); + return Result; } @@ -5635,27 +6160,47 @@ bool Bot::DoFinishedSpellSingleTarget(uint16 spell_id, Mob* spellTarget, EQ::spe if ( spellTarget && - IsGrouped() && - ( - spellTarget->IsBot() || - spellTarget->IsClient() - ) && - RuleB(Bots, GroupBuffing) + GetClass() != Class::Bard && + (IsGrouped() || (IsRaidGrouped() && GetStoredRaid()->GetGroup(GetCleanName()) != RAID_GROUPLESS)) && + (spellTarget->IsBot() || spellTarget->IsClient()) && + (RuleB(Bots, GroupBuffing) || RuleB(Bots, RaidBuffing)) ) { bool noGroupSpell = false; uint16 thespell = spell_id; - for (int i = 0; i < AIBot_spells.size(); i++) { + for (int i = 0; i < AIBot_spells.size(); i++) { // TODO bot rewrite - fix this to reduce loop with AIBot_spells_by_type? int j = BotGetSpells(i); int spelltype = BotGetSpellType(i); bool spellequal = (j == thespell); - bool spelltypeequal = ((spelltype == 2) || (spelltype == 16) || (spelltype == 32)); - bool spelltypetargetequal = ((spelltype == 8) && (spells[thespell].target_type == ST_Self)); - bool spelltypeclassequal = ((spelltype == 1024) && (GetClass() == Class::Shaman)); + bool spelltypeequal = ( + (spelltype == BotSpellTypes::RegularHeal) || + (spelltype == BotSpellTypes::Escape) || + (spelltype == BotSpellTypes::Pet) + ); + bool spelltypetargetequal = ( + (spelltype == BotSpellTypes::Buff) && + (spells[thespell].target_type == ST_Self) + ); + bool spelltypeclassequal = ( + (spelltype == BotSpellTypes::InCombatBuff) && + (GetClass() == Class::Shaman) + ); bool slotequal = (slot == EQ::spells::CastingSlot::Item); + if (spellequal || slotequal) { - if ((spelltypeequal || spelltypetargetequal) || spelltypeclassequal || slotequal) { - if (((spells[thespell].effect_id[0] == 0) && (spells[thespell].base_value[0] < 0)) && - (spellTarget->GetHP() < ((spells[thespell].base_value[0] * (-1)) + 100))) { + if ( + ( + spelltypeequal || spelltypetargetequal + ) || + spelltypeclassequal || + slotequal + ) { + if ( + ( + (spells[thespell].effect_id[0] == 0) && + (spells[thespell].base_value[0] < 0) + ) && + (spellTarget->GetHP() < ((spells[thespell].base_value[0] * (-1)) + 100)) + ) { LogSpells("GroupBuffing failure"); return false; } @@ -5668,34 +6213,48 @@ bool Bot::DoFinishedSpellSingleTarget(uint16 spell_id, Mob* spellTarget, EQ::spe } if (!noGroupSpell) { - Group *g = GetGroup(); - if (g) { - for (int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if (g->members[i]) { - if ((g->members[i]->GetClass() == Class::Necromancer) && (IsEffectInSpell(thespell, SE_AbsorbMagicAtt) || IsEffectInSpell(thespell, SE_Rune))) { - } - else - SpellOnTarget(thespell, g->members[i]); + for (Mob* m : GetBuffTargets(spellTarget)) { + if (IsEffectInSpell(thespell, SpellEffect::AbsorbMagicAtt) || IsEffectInSpell(thespell, SpellEffect::Rune)) { + for (int i = 0; i < m->GetMaxTotalSlots(); i++) { + uint32 buff_count = m->GetMaxTotalSlots(); - if (g->members[i] && g->members[i]->GetPetID()) - SpellOnTarget(thespell, g->members[i]->GetPet()); + for (unsigned int j = 0; j < buff_count; j++) { + if (IsValidSpell(m->GetBuffs()[j].spellid)) { + if (IsLichSpell(m->GetBuffs()[j].spellid)) { + continue; + } + } + } } } - SetMana(GetMana() - (GetActSpellCost(thespell, spells[thespell].mana) * (g->GroupCount() - 1))); + + SpellOnTarget(thespell, m); + + if ( + m->GetPetID() && + ( + !RuleB(Bots, RequirePetAffinity) || + m->HasPetAffinity() + ) + ) { + SpellOnTarget(thespell, m->GetPet()); + } + + SetMana(GetMana() - (GetActSpellCost(thespell, spells[thespell].mana) * (GetBuffTargets(spellTarget).size() - 1))); } } + stopLogic = true; } + return true; } bool Bot::DoFinishedSpellGroupTarget(uint16 spell_id, Mob* spellTarget, EQ::spells::CastingSlot slot, bool& stopLogic) { bool isMainGroupMGB = false; - Raid* raid = entity_list.GetRaidByBotName(GetName()); if (isMainGroupMGB && (GetClass() != Class::Bard)) { - BotGroupSay( - this, + RaidGroupSay( fmt::format( "Casting {} as a Mass Group Buff.", spells[spell_id].name @@ -5704,30 +6263,29 @@ bool Bot::DoFinishedSpellGroupTarget(uint16 spell_id, Mob* spellTarget, EQ::spel SpellOnTarget(spell_id, this); entity_list.AESpell(this, this, spell_id, true); } - else if (raid) - { - std::vector raid_group_members = raid->GetRaidGroupMembers(raid->GetGroup(GetName())); - for (auto iter = raid_group_members.begin(); iter != raid_group_members.end(); ++iter) { - if (iter->member) { - SpellOnTarget(spell_id, iter->member); - if (iter->member && iter->member->GetPetID()) - SpellOnTarget(spell_id, iter->member ->GetPet()); - } + else { + if (spellTarget != this) { + SpellOnTarget(spell_id, this); } - } - else - { - Group *g = GetGroup(); - if (g) { - for (int i = 0; i < MAX_GROUP_MEMBERS; ++i) { - if (g->members[i]) { - SpellOnTarget(spell_id, g->members[i]); - if (g->members[i] && g->members[i]->GetPetID()) - SpellOnTarget(spell_id, g->members[i]->GetPet()); + + if (spellTarget->IsOfClientBotMerc()) { + for (Mob* m : GetBuffTargets(spellTarget)) { + if (m == this && spellTarget != this) { + continue; + } + + SpellOnTarget(spell_id, m); + + if (m->GetPetID() && (!RuleB(Bots, RequirePetAffinity) || m->HasPetAffinity())) { + SpellOnTarget(spell_id, m->GetPet()); } } } + else if (spellTarget->IsPet() && !spellTarget->IsFamiliar() && spellTarget->GetOwner() && (!RuleB(Bots, RequirePetAffinity) || spellTarget->GetOwner()->HasPetAffinity())) { + SpellOnTarget(spell_id, spellTarget); + } } + stopLogic = true; return true; } @@ -5796,9 +6354,9 @@ int32 Bot::GetMaxStat() { int32 Bot::GetMaxResist() { int level = GetLevel(); int32 base = 500; - if (level > 60) - base += ((level - 60) * 5); - + if (level > 65) { + base += ((level - 65) * 5); + } return base; } @@ -6172,28 +6730,45 @@ int64 Bot::CalcHPRegen() { } int64 Bot::CalcManaRegen() { - uint8 level = GetLevel(); - uint8 botclass = GetClass(); - int32 regen = 0; - if (IsSitting()) { - BuffFadeBySitModifier(); - if (botclass != Class::Warrior && botclass != Class::Monk && botclass != Class::Rogue && botclass != Class::Berserker) { - regen = ((((GetSkill(EQ::skills::SkillMeditate) / 10) + (level - (level / 4))) / 4) + 4); - regen += (spellbonuses.ManaRegen + itembonuses.ManaRegen); - } else - regen = (2 + spellbonuses.ManaRegen + itembonuses.ManaRegen); - } else - regen = (2 + spellbonuses.ManaRegen + itembonuses.ManaRegen); + uint8 level = GetLevel(); + uint8 bot_class = GetClass(); - regen += aabonuses.ManaRegen + itembonuses.heroic_mana_regen; + // Default values + int32 regen = 2; // Default regen for non-sitting state + float mana_regen_rate = std::max(0.0f, RuleR(Bots, ManaRegen)); - regen = ((regen * RuleI(Character, ManaRegenMultiplier)) / 100); - float mana_regen_rate = RuleR(Bots, ManaRegen); - if (mana_regen_rate < 0.0f) - mana_regen_rate = 0.0f; + if (bot_class == Class::Bard) { + regen = IsSitting() ? 2 : 1; - regen = (regen * mana_regen_rate); - return regen; + if (IsSitting()) { + BuffFadeBySitModifier(); + } + + regen += itembonuses.ManaRegen + aabonuses.ManaRegen; + + return regen; + } + + if (IsSitting()) { + BuffFadeBySitModifier(); + + if (GetArchetype() != Archetype::Melee) { + regen = (((GetSkill(EQ::skills::SkillMeditate) / 10) + (level - (level / 4))) / 4) + 4; + } + } + + regen += spellbonuses.ManaRegen + itembonuses.ManaRegen; + + if (IsHeroicINTCasterClass(bot_class)) { + regen += itembonuses.HeroicINT * RuleR(Character, HeroicIntelligenceMultiplier) / 25; + } else if (IsHeroicWISCasterClass(bot_class)) { + regen += itembonuses.HeroicWIS * RuleR(Character, HeroicWisdomMultiplier) / 25; + } + + regen += aabonuses.ManaRegen; + regen = (regen * RuleI(Character, ManaRegenMultiplier)) / 100; + + return static_cast(regen * mana_regen_rate); } uint64 Bot::GetClassHPFactor() { @@ -6375,17 +6950,6 @@ void Bot::DoEnduranceUpkeep() { } void Bot::Camp(bool save_to_database) { - - if (IsEngaged() || GetBotOwner()->IsEngaged()) { - GetBotOwner()->Message( - Chat::White, - fmt::format( - "You cannot camp your bots while in combat" - ).c_str() - ); - return; - } - Sit(); LeaveHealRotationMemberPool(); @@ -6402,7 +6966,7 @@ void Bot::Camp(bool save_to_database) { } void Bot::Zone() { - if (auto raid = entity_list.GetRaidByBotName(GetName())) { + if (auto raid = entity_list.GetRaidByBot(this)) { raid->MemberZoned(CastToClient()); } else if (HasGroup()) { @@ -6413,17 +6977,21 @@ void Bot::Zone() { Depop(); } -bool Bot::IsArcheryRange(Mob *target) { +bool Bot::IsAtRange(Mob *target) { bool result = false; + if (target) { - float range = (GetBotArcheryRange() + 5.0); + float range = (GetBotRangedValue() + 5.0); range *= range; float targetDistance = DistanceSquaredNoZ(m_Position, target->GetPosition()); float minRuleDistance = (RuleI(Combat, MinRangedAttackDist) * RuleI(Combat, MinRangedAttackDist)); - if ((targetDistance > range) || (targetDistance < minRuleDistance)) + + if ((targetDistance > range) || (targetDistance < minRuleDistance)) { result = false; - else + } + else { result = true; + } } return result; } @@ -6457,107 +7025,107 @@ void Bot::UpdateGroupCastingRoles(const Group* group, bool disband) // GroupHealer switch (iter->GetClass()) { - case Class::Cleric: - if (!healer) - healer = iter; - else - switch (healer->GetClass()) { - case Class::Cleric: - break; - default: + case Class::Cleric: + if (!healer) healer = iter; - } + else + switch (healer->GetClass()) { + case Class::Cleric: + break; + default: + healer = iter; + } - break; - case Class::Druid: - if (!healer) - healer = iter; - else - switch (healer->GetClass()) { - case Class::Cleric: - case Class::Druid: - break; - default: + break; + case Class::Druid: + if (!healer) healer = iter; - } - break; - case Class::Shaman: - if (!healer) - healer = iter; - else - switch (healer->GetClass()) { - case Class::Cleric: - case Class::Druid: - case Class::Shaman: - break; - default: + else + switch (healer->GetClass()) { + case Class::Cleric: + case Class::Druid: + break; + default: + healer = iter; + } + break; + case Class::Shaman: + if (!healer) healer = iter; - } - break; - case Class::Paladin: - case Class::Ranger: - case Class::Beastlord: - if (!healer) - healer = iter; - break; - default: - break; - } + else + switch (healer->GetClass()) { + case Class::Cleric: + case Class::Druid: + case Class::Shaman: + break; + default: + healer = iter; + } + break; + case Class::Paladin: + case Class::Ranger: + case Class::Beastlord: + if (!healer) + healer = iter; + break; + default: + break; + } - // GroupSlower - switch (iter->GetClass()) { - case Class::Shaman: - if (!slower) - slower = iter; - else - switch (slower->GetClass()) { + // GroupSlower + switch (iter->GetClass()) { case Class::Shaman: + if (!slower) + slower = iter; + else + switch (slower->GetClass()) { + case Class::Shaman: + break; + default: + slower = iter; + } break; - default: - slower = iter; - } - break; - case Class::Enchanter: - if (!slower) - slower = iter; - else - switch (slower->GetClass()) { - case Class::Shaman: case Class::Enchanter: + if (!slower) + slower = iter; + else + switch (slower->GetClass()) { + case Class::Shaman: + case Class::Enchanter: + break; + default: + slower = iter; + } + break; + case Class::Beastlord: + if (!slower) + slower = iter; break; default: - slower = iter; - } - break; - case Class::Beastlord: - if (!slower) - slower = iter; - break; - default: - break; - } + break; + } - // GroupNuker - switch (iter->GetClass()) { - // wizard - // magician - // necromancer - // enchanter - // druid - // cleric - // shaman - // shadowknight - // paladin - // ranger - // beastlord - default: - break; - } + // GroupNuker + switch (iter->GetClass()) { + // wizard + // magician + // necromancer + // enchanter + // druid + // cleric + // shaman + // shadowknight + // paladin + // ranger + // beastlord + default: + break; + } - // GroupDoter - switch (iter->GetClass()) { - default: - break; + // GroupDoter + switch (iter->GetClass()) { + default: + break; } } @@ -6570,7 +7138,7 @@ void Bot::UpdateGroupCastingRoles(const Group* group, bool disband) Bot* Bot::GetBotByBotClientOwnerAndBotName(Client* c, const std::string& botName) { Bot* Result = nullptr; if (c) { - std::list BotList = entity_list.GetBotsByBotOwnerCharacterID(c->CharacterID()); + std::vector BotList = entity_list.GetBotsByBotOwnerCharacterID(c->CharacterID()); if (!BotList.empty()) { for (auto botListItr = BotList.begin(); botListItr != BotList.end(); ++botListItr) { if (std::string((*botListItr)->GetCleanName()) == botName) { @@ -6585,11 +7153,26 @@ Bot* Bot::GetBotByBotClientOwnerAndBotName(Client* c, const std::string& botName void Bot::ProcessBotGroupInvite(Client* c, std::string const& botName) { if (c && !c->HasRaid()) { - Bot* invitedBot = GetBotByBotClientOwnerAndBotName(c, botName); + Bot* invitedBot = entity_list.GetBotByBotName(botName); + if (!invitedBot) { return; } + if ( + invitedBot->GetBotOwnerCharacterID() != c->CharacterID() && + !( + c->GetGroup() && + !invitedBot->HasGroup() && + invitedBot->GetOwner()->HasGroup() && + c->GetGroup() == invitedBot->GetOwner()->GetGroup() + ) + ) { + c->Message(Chat::Red, "%s's owner needs to be in your group to be able to invite them.", invitedBot->GetCleanName()); + + return; + } + if (!invitedBot->HasGroup() && !invitedBot->HasRaid()) { if (!c->IsGrouped()) { auto g = new Group(c); @@ -6630,7 +7213,7 @@ void Bot::ProcessBotGroupDisband(Client* c, const std::string& botName) { // Processes a raid disband request from a Client for a Bot. void Bot::RemoveBotFromRaid(Bot* bot) { - Raid* bot_raid = entity_list.GetRaidByBotName(bot->GetName()); + Raid* bot_raid = entity_list.GetRaidByBot(bot); if (bot_raid) { uint32 gid = bot_raid->GetGroup(bot->GetName()); bot_raid->SendRaidGroupRemove(bot->GetName(), gid); @@ -6640,18 +7223,22 @@ void Bot::RemoveBotFromRaid(Bot* bot) { bot_raid->DisbandRaid(); } } + + bot->SetStoredRaid(nullptr); + bot->p_raid_instance = nullptr; + bot->SetVerifiedRaid(false); } // Handles all client zone change event void Bot::ProcessClientZoneChange(Client* botOwner) { if (botOwner) { - std::list BotList = entity_list.GetBotsByBotOwnerCharacterID(botOwner->CharacterID()); + std::vector BotList = entity_list.GetBotsByBotOwnerCharacterID(botOwner->CharacterID()); - for (std::list::iterator itr = BotList.begin(); itr != BotList.end(); ++itr) { + for (std::vector::iterator itr = BotList.begin(); itr != BotList.end(); ++itr) { Bot* tempBot = *itr; if (tempBot) { - Raid* raid = entity_list.GetRaidByBotName(tempBot->GetName()); + Raid* raid = entity_list.GetRaidByBot(tempBot); if (raid) { tempBot->Zone(); } @@ -6755,7 +7342,7 @@ void Bot::CalcBotStats(bool showtext) { SetLevel(GetBotOwner()->GetLevel()); for (int sindex = 0; sindex <= EQ::skills::HIGHEST_SKILL; ++sindex) { - skills[sindex] = skill_caps.GetSkillCap(GetClass(), (EQ::skills::SkillType)sindex, GetLevel()).cap; + skills[sindex] = SkillCaps::Instance()->GetSkillCap(GetClass(), (EQ::skills::SkillType)sindex, GetLevel()).cap; } taunt_timer.Start(1000); @@ -6802,8 +7389,9 @@ void Bot::CalcBotStats(bool showtext) { } bool Bot::CheckLoreConflict(const EQ::ItemData* item) { - if (!item || !(item->LoreFlag)) + if (!item || !(item->LoreFlag) || (item->LoreGroup == 0)) { return false; + } if (item->LoreGroup == -1) // Standard lore items; look everywhere except the shared bank, return the result return (m_inv.HasItem(item->ID, 0, invWhereWorn) != INVALID_INDEX); @@ -6812,354 +7400,59 @@ bool Bot::CheckLoreConflict(const EQ::ItemData* item) { return (m_inv.HasItemByLoreGroup(item->LoreGroup, invWhereWorn) != INVALID_INDEX); } -bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, float iRange, uint32 iSpellTypes) { +bool Bot::AttemptCloseBeneficialSpells(uint16 spell_type) { + bool result = false; + Mob* tar = nullptr; - if ((iSpellTypes & SPELL_TYPES_DETRIMENTAL) != 0) { - LogError("[EntityList::Bot_AICheckCloseBeneficialSpells] detrimental spells requested"); - return false; - } + for (Mob* m : GetSpellTargetList(RuleB(Bots, CrossRaidBuffingAndHealing))) { + tar = m; - if (!caster || !caster->AI_HasSpells()) { - return false; - } + if (!tar) { + continue; + } - if (iChance < 100) { - uint8 tmp = zone->random.Int(1, 100); - if (tmp > iChance) - return false; - } + if (RuleB(Bots, CrossRaidBuffingAndHealing) && IsGroupTargetOnlyBotSpellType(spell_type)) { + Raid* raid = GetStoredRaid(); - uint8 botCasterClass = caster->GetClass(); - - if (iSpellTypes == SpellType_Heal) { - if (botCasterClass == Class::Cleric || botCasterClass == Class::Druid || botCasterClass == Class::Shaman) { - if (caster->IsRaidGrouped()) { - Raid* raid = entity_list.GetRaidByBotName(caster->GetName()); - uint32 gid = raid->GetGroup(caster->GetName()); - if (gid < MAX_RAID_GROUPS) { - std::vector raid_group_members = raid->GetRaidGroupMembers(gid); - for (std::vector::iterator iter = raid_group_members.begin(); iter != raid_group_members.end(); ++iter) { - if (iter->member && !iter->member->qglobal) { - if (iter->member->IsClient() && iter->member->GetHPRatio() < 90) { - if (caster->AICastSpell(iter->member, 100, SpellType_Heal)) - return true; - } - else if ((iter->member->GetClass() == Class::Warrior || iter->member->GetClass() == Class::Paladin || iter->member->GetClass() == Class::ShadowKnight) && iter->member->GetHPRatio() < 95) { - if (caster->AICastSpell(iter->member, 100, SpellType_Heal)) - return true; - } - else if (iter->member->GetClass() == Class::Enchanter && iter->member->GetHPRatio() < 80) { - if (caster->AICastSpell(iter->member, 100, SpellType_Heal)) - return true; - } - else if (iter->member->GetHPRatio() < 70) { - if (caster->AICastSpell(iter->member, 100, SpellType_Heal)) - return true; - } - - } - - if (iter->member && !iter->member->qglobal && iter->member->HasPet() && iter->member->GetPet()->GetHPRatio() < 50) { - if (iter->member->GetPet()->GetOwner() != caster && caster->IsEngaged() && iter->member->IsCasting() && iter->member->GetClass() != Class::Enchanter) - continue; - - if (caster->AICastSpell(iter->member->GetPet(), 100, SpellType_Heal)) - return true; - } - } - } - } - - else if (caster->HasGroup()) { - Group *g = caster->GetGroup(); - if (g) { - for (int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if (g->members[i] && !g->members[i]->qglobal) { - if (g->members[i]->IsClient() && g->members[i]->GetHPRatio() < 90) { - if (caster->AICastSpell(g->members[i], 100, SpellType_Heal)) - return true; - } else if ((g->members[i]->GetClass() == Class::Warrior || g->members[i]->GetClass() == Class::Paladin || g->members[i]->GetClass() == Class::ShadowKnight) && g->members[i]->GetHPRatio() < 95) { - if (caster->AICastSpell(g->members[i], 100, SpellType_Heal)) - return true; - } else if (g->members[i]->GetClass() == Class::Enchanter && g->members[i]->GetHPRatio() < 80) { - if (caster->AICastSpell(g->members[i], 100, SpellType_Heal)) - return true; - } else if (g->members[i]->GetHPRatio() < 70) { - if (caster->AICastSpell(g->members[i], 100, SpellType_Heal)) - return true; - } - } - - if (g->members[i] && !g->members[i]->qglobal && g->members[i]->HasPet() && g->members[i]->GetPet()->GetHPRatio() < 50) { - if (g->members[i]->GetPet()->GetOwner() != caster && caster->IsEngaged() && g->members[i]->IsCasting() && g->members[i]->GetClass() != Class::Enchanter ) - continue; - - if (caster->AICastSpell(g->members[i]->GetPet(), 100, SpellType_Heal)) - return true; - } - } - } + if (raid && + (raid->GetGroup(GetName()) == raid->GetGroup(tar->GetName())) + ) { + continue; } } - if ((botCasterClass == Class::Paladin || botCasterClass == Class::Beastlord || botCasterClass == Class::Ranger) && (caster->HasGroup() || caster->IsRaidGrouped())) { - float hpRatioToHeal = 25.0f; - switch(caster->GetBotStance()) { - case Stance::Reactive: - case Stance::Balanced: - hpRatioToHeal = 50.0f; - break; - case Stance::Burn: - case Stance::AEBurn: - hpRatioToHeal = 20.0f; - break; - case Stance::Aggressive: - case Stance::Efficient: - default: - hpRatioToHeal = 25.0f; - break; - } - if (caster->IsRaidGrouped()) { - if (auto raid = entity_list.GetRaidByBotName(caster->GetName())) { - uint32 gid = raid->GetGroup(caster->GetName()); - if (gid < MAX_RAID_GROUPS) { - std::vector raid_group_members = raid->GetRaidGroupMembers(gid); - for (std::vector::iterator iter = raid_group_members.begin(); - iter != raid_group_members.end(); ++iter) { - if (iter->member && !iter->member->qglobal) { - if (iter->member->IsClient() && iter->member->GetHPRatio() < hpRatioToHeal) { - if (caster->AICastSpell(iter->member, 100, SpellType_Heal)) - return true; - } else if ( - (iter->member->GetClass() == Class::Warrior || iter->member->GetClass() == Class::Paladin || - iter->member->GetClass() == Class::ShadowKnight) && - iter->member->GetHPRatio() < hpRatioToHeal) { - if (caster->AICastSpell(iter->member, 100, SpellType_Heal)) - return true; - } else if (iter->member->GetClass() == Class::Enchanter && - iter->member->GetHPRatio() < hpRatioToHeal) { - if (caster->AICastSpell(iter->member, 100, SpellType_Heal)) - return true; - } else if (iter->member->GetHPRatio() < hpRatioToHeal / 2) { - if (caster->AICastSpell(iter->member, 100, SpellType_Heal)) - return true; - } - } + result = AttemptAICastSpell(spell_type, tar); - if (iter->member && !iter->member->qglobal && iter->member->HasPet() && - iter->member->GetPet()->GetHPRatio() < 25) { - if (iter->member->GetPet()->GetOwner() != caster && caster->IsEngaged() && - iter->member->IsCasting() && iter->member->GetClass() != Class::Enchanter) - continue; + if (!result) { + if (tar->HasPet()) { + Mob* pet = m->GetPet(); - if (caster->AICastSpell(iter->member->GetPet(), 100, SpellType_Heal)) - return true; - } - } - } - } - } - else if (caster->HasGroup()) { - if (auto g = caster->GetGroup()) { - for (int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if (g->members[i] && !g->members[i]->qglobal) { - if (g->members[i]->IsClient() && g->members[i]->GetHPRatio() < hpRatioToHeal) { - if (caster->AICastSpell(g->members[i], 100, SpellType_Heal)) - return true; - } else if ((g->members[i]->GetClass() == Class::Warrior || g->members[i]->GetClass() == Class::Paladin || - g->members[i]->GetClass() == Class::ShadowKnight) && - g->members[i]->GetHPRatio() < hpRatioToHeal) { - if (caster->AICastSpell(g->members[i], 100, SpellType_Heal)) - return true; - } else if (g->members[i]->GetClass() == Class::Enchanter && - g->members[i]->GetHPRatio() < hpRatioToHeal) { - if (caster->AICastSpell(g->members[i], 100, SpellType_Heal)) - return true; - } else if (g->members[i]->GetHPRatio() < hpRatioToHeal / 2) { - if (caster->AICastSpell(g->members[i], 100, SpellType_Heal)) - return true; - } - } + if (!pet->IsFamiliar() || RuleB(Bots, AllowBuffingHealingFamiliars)) { + tar = pet; - if (g->members[i] && !g->members[i]->qglobal && g->members[i]->HasPet() && - g->members[i]->GetPet()->GetHPRatio() < 25) { - if (g->members[i]->GetPet()->GetOwner() != caster && caster->IsEngaged() && - g->members[i]->IsCasting() && g->members[i]->GetClass() != Class::Enchanter) - continue; - - if (caster->AICastSpell(g->members[i]->GetPet(), 100, SpellType_Heal)) - return true; - } - } - } - } - } - } - - if (iSpellTypes == SpellType_Buff) { - uint8 chanceToCast = caster->IsEngaged() ? caster->GetChanceToCastBySpellType(SpellType_Buff) : 100; - if (botCasterClass == Class::Bard) { - if (caster->AICastSpell(caster, chanceToCast, SpellType_Buff)) { - return true; - } else - return false; - } - - if (caster->IsRaidGrouped()) { - Raid* raid = entity_list.GetRaidByBotName(caster->GetName()); - uint32 g = raid->GetGroup(caster->GetName()); - if (g < MAX_RAID_GROUPS) { - std::vector raid_group_members = raid->GetRaidGroupMembers(g); - for (std::vector::iterator iter = raid_group_members.begin(); iter != raid_group_members.end(); ++iter) { - if (iter->member) { - if (caster->AICastSpell(iter->member, chanceToCast, SpellType_Buff) || caster->AICastSpell(iter->member->GetPet(), chanceToCast, SpellType_Buff)) - return true; - } - } - } - } - if (caster->HasGroup()) { - Group *g = caster->GetGroup(); - if (g) { - for (int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if (g->members[i]) { - if (caster->AICastSpell(g->members[i], chanceToCast, SpellType_Buff) || caster->AICastSpell(g->members[i]->GetPet(), chanceToCast, SpellType_Buff)) - return true; - } - } - } - } - } - - if (iSpellTypes == SpellType_Cure) { - if (caster->IsRaidGrouped()) { - Raid* raid = entity_list.GetRaidByBotName(caster->GetName()); - uint32 gid = raid->GetGroup(caster->GetName()); - if (gid < MAX_RAID_GROUPS) { - std::vector raid_group_members = raid->GetRaidGroupMembers(gid); - for (std::vector::iterator iter = raid_group_members.begin(); iter != raid_group_members.end(); ++iter) { - if (iter->member && caster->GetNeedsCured(iter->member)) { - if (caster->AICastSpell(iter->member, caster->GetChanceToCastBySpellType(SpellType_Cure), SpellType_Cure)) - return true; - else if (botCasterClass == Class::Bard) { - return false; - } + if (!tar) { + continue; } - if (iter->member && iter->member->GetPet() && caster->GetNeedsCured(iter->member->GetPet())) { - if (caster->AICastSpell(iter->member->GetPet(), (int)caster->GetChanceToCastBySpellType(SpellType_Cure) / 4, SpellType_Cure)) - return true; - } - } - } - } - else if (caster->HasGroup()) { - Group *g = caster->GetGroup(); - if (g) { - for (int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if (g->members[i] && caster->GetNeedsCured(g->members[i])) { - if (caster->AICastSpell(g->members[i], caster->GetChanceToCastBySpellType(SpellType_Cure), SpellType_Cure)) - return true; - else if (botCasterClass == Class::Bard) - return false; - } - - if (g->members[i] && g->members[i]->GetPet() && caster->GetNeedsCured(g->members[i]->GetPet())) { - if (caster->AICastSpell(g->members[i]->GetPet(), (int)caster->GetChanceToCastBySpellType(SpellType_Cure)/4, SpellType_Cure)) - return true; - } - } - } - } - } - - if (iSpellTypes == SpellType_HateRedux) { - if (!caster->IsEngaged()) - return false; - - if (caster->IsRaidGrouped()) { - Raid* raid = entity_list.GetRaidByBotName(caster->GetName()); - uint32 gid = raid->GetGroup(caster->GetName()); - if (gid < MAX_RAID_GROUPS) { - std::vector raid_group_members = raid->GetRaidGroupMembers(gid); - for (std::vector::iterator iter = raid_group_members.begin(); iter != raid_group_members.end(); ++iter) { - if (iter->member && caster->GetNeedsHateRedux(iter->member)) { - if (caster->AICastSpell(iter->member, caster->GetChanceToCastBySpellType(SpellType_HateRedux), SpellType_HateRedux)) - return true; - } - } - } - } - else if (caster->HasGroup()) { - Group *g = caster->GetGroup(); - if (g) { - for (int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if (g->members[i] && caster->GetNeedsHateRedux(g->members[i])) { - if (caster->AICastSpell(g->members[i], caster->GetChanceToCastBySpellType(SpellType_HateRedux), SpellType_HateRedux)) - return true; - } - } - } - } - } - - if (iSpellTypes == SpellType_PreCombatBuff) { - if (botCasterClass == Class::Bard || caster->IsEngaged()) - return false; - - //added raid check - if (caster->IsRaidGrouped()) { - Raid* raid = entity_list.GetRaidByBotName(caster->GetName()); - uint32 g = raid->GetGroup(caster->GetName()); - if (g < MAX_RAID_GROUPS) { - std::vector raid_group_members = raid->GetRaidGroupMembers(g); - for (std::vector::iterator iter = raid_group_members.begin(); iter != raid_group_members.end(); ++iter) { - if (iter->member && - (caster->AICastSpell(iter->member, iChance, SpellType_PreCombatBuff) || - caster->AICastSpell(iter->member->GetPet(), iChance, SpellType_PreCombatBuff)) + if (tar->IsOfClientBot() || + ( + tar->IsPet() && + tar->GetOwner() && + tar->GetOwner()->IsOfClientBot() + ) ) { - return true; - } - } - } - } else if (caster->HasGroup()) { - const auto g = caster->GetGroup(); - if (g) { - for (int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if (g->members[i]) { - if (caster->AICastSpell(g->members[i], iChance, SpellType_PreCombatBuff) || caster->AICastSpell(g->members[i]->GetPet(), iChance, SpellType_PreCombatBuff)) - return true; + result = AttemptAICastSpell(spell_type, tar); } } } } + + if (result) { + break; + } } - if (iSpellTypes == SpellType_InCombatBuff) { - if (botCasterClass == Class::Bard) { - if (caster->AICastSpell(caster, iChance, SpellType_InCombatBuff)) { - return true; - } - else { - return false; - } - } - - if (caster->HasGroup()) { - Group* g = caster->GetGroup(); - if (g) { - for (int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if (g->members[i]) { - if (caster->AICastSpell(g->members[i], iChance, SpellType_InCombatBuff) || caster->AICastSpell(g->members[i]->GetPet(), iChance, SpellType_InCombatBuff)) { - return true; - } - } - } - } - } - } - - return false; + return result; } Mob* EntityList::GetMobByBotID(uint32 botID) { @@ -7179,65 +7472,54 @@ Mob* EntityList::GetMobByBotID(uint32 botID) { } Bot* EntityList::GetBotByBotID(uint32 botID) { - Bot* Result = nullptr; - if (botID > 0) { - for (std::list::iterator botListItr = bot_list.begin(); botListItr != bot_list.end(); ++botListItr) { - Bot* tempBot = *botListItr; - if (tempBot && tempBot->GetBotID() == botID) { - Result = tempBot; - break; - } - } + auto it = bot_list.begin(); + while (it != bot_list.end()) { + if (it->second->GetBotID() == botID) + return it->second; + ++it; } - return Result; + return nullptr; } -Bot* EntityList::GetBotByBotName(std::string_view botName) { - Bot* Result = nullptr; - if (!botName.empty()) { - for (const auto b : bot_list) { - if (b && std::string_view(b->GetName()) == botName) { - Result = b; - break; - } +Bot* EntityList::GetBotByBotName(std::string botName) { + for (const auto& e : bot_list) { + if (e.second && Strings::EqualFold(e.second->GetName(), botName)) { + return e.second; } } - return Result; + + return nullptr; } Client* EntityList::GetBotOwnerByBotEntityID(uint32 entity_id) { - Client* c = nullptr; - if (entity_id) { - for (const auto& b : bot_list) { - if (b && b->GetID() == entity_id) { - c = b->GetBotOwner()->CastToClient(); - break; - } + auto it = bot_list.begin(); + while (it != bot_list.end()) { + if (it->second->GetID() == entity_id) + return it->second->GetBotOwner()->CastToClient(); + ++it; } } - - return c; + return nullptr; } Client* EntityList::GetBotOwnerByBotID(const uint32 bot_id) { - Client* c = nullptr; - if (bot_id) { - const auto owner_id = database.botdb.GetOwnerID(bot_id); - if (owner_id) { - c = GetClientByCharID(owner_id); + auto it = bot_list.begin(); + while (it != bot_list.end()) { + if (it->second->GetBotID() == bot_id) + return it->second->GetBotOwner()->CastToClient(); + ++it; } } - - return c; + return nullptr; } void EntityList::AddBot(Bot *new_bot, bool send_spawn_packet, bool dont_queue) { if (new_bot) { new_bot->SetID(GetFreeID()); - bot_list.push_back(new_bot); - mob_list.insert(std::pair(new_bot->GetID(), new_bot)); + bot_list.emplace(std::pair(new_bot->GetID(), new_bot)); + mob_list.emplace(std::pair(new_bot->GetID(), new_bot)); if (parse->BotHasQuestSub(EVENT_SPAWN)) { parse->EventBot(EVENT_SPAWN, new_bot, nullptr, "", 0); @@ -7266,31 +7548,32 @@ void EntityList::AddBot(Bot *new_bot, bool send_spawn_packet, bool dont_queue) { } } -std::list EntityList::GetBotsByBotOwnerCharacterID(uint32 botOwnerCharacterID) { - std::list Result; - if (botOwnerCharacterID > 0) { - for (std::list::iterator botListItr = bot_list.begin(); botListItr != bot_list.end(); ++botListItr) { - Bot* tempBot = *botListItr; - if (tempBot && tempBot->GetBotOwnerCharacterID() == botOwnerCharacterID) - Result.push_back(tempBot); - } +std::vector EntityList::GetBotsByBotOwnerCharacterID(uint32 bot_owner_character_id) { + std::vector client_bot_list; + + if (bot_owner_character_id <= 0) { + return client_bot_list; } - return Result; + + auto it = bot_list.begin(); + + while (it != bot_list.end()) { + if (it->second->GetOwner() && it->second->GetBotOwnerCharacterID() == bot_owner_character_id) { + client_bot_list.push_back(it->second); + } + ++it; + } + + return client_bot_list; } bool EntityList::RemoveBot(uint16 entityID) { - bool Result = false; - if (entityID > 0) { - for (std::list::iterator botListItr = bot_list.begin(); botListItr != bot_list.end(); ++botListItr) { - Bot* tempBot = *botListItr; - if (tempBot && tempBot->GetID() == entityID) { - bot_list.erase(botListItr); - Result = true; - break; - } - } + auto it = bot_list.find(entityID); + if (it != bot_list.end()) { + bot_list.erase(it); // Already deleted + return true; } - return Result; + return false; } void EntityList::ShowSpawnWindow(Client* client, int Distance, bool NamedOnly) { @@ -7403,62 +7686,27 @@ void EntityList::ShowSpawnWindow(Client* client, int Distance, bool NamedOnly) { return; } -/** - * @param close_mobs - * @param scanning_mob - */ -void EntityList::ScanCloseClientMobs(std::unordered_map& close_mobs, Mob* scanning_mob) -{ - float scan_range = RuleI(Range, MobCloseScanDistance) * RuleI(Range, MobCloseScanDistance); +uint8 Bot::GetNumberNeedingHealedInGroup(Mob* tar, uint16 spell_type, uint16 spell_id, float range) { + if (!TargetValidation(tar)) { + return 0; + } - close_mobs.clear(); + uint8 count = 0; + std::vector target_list = tar->IsClient() ? GatherSpellTargets(false, tar) : tar->CastToBot()->GetSpellTargetList(); - for (auto& e : mob_list) { - auto mob = e.second; - - if (!mob->IsClient()) { - continue; - } - - if (mob->GetID() <= 0) { - continue; - } - - float distance = DistanceSquared(scanning_mob->GetPosition(), mob->GetPosition()); - if (distance <= scan_range) { - close_mobs.insert(std::pair(mob->GetID(), mob)); - } - else if (mob->GetAggroRange() >= scan_range) { - close_mobs.insert(std::pair(mob->GetID(), mob)); + for (auto* m : target_list) { + if ( + m && + entity_list.IsMobInZone(m) && + TargetValidation(m) && + tar->CalculateDistance(m) < range && + CastChecks(spell_id, m, spell_type, true, IsGroupBotSpellType(spell_type)) + ) { + ++count; } } - LogAIScanCloseDetail("Close Client Mob List Size [{}] for mob [{}]", close_mobs.size(), scanning_mob->GetCleanName()); -} - -uint8 Bot::GetNumberNeedingHealedInGroup(uint8 hpr, bool includePets, Raid* raid) { - - uint8 need_healed = 0; - if (HasGroup()) { - - auto group_members = GetGroup(); - if (group_members) { - - for (auto member : group_members->members) { - if (member && !member->qglobal) { - - if (member->GetHPRatio() <= hpr) { - need_healed++; - } - - if (includePets && member->GetPet() && member->GetPet()->GetHPRatio() <= hpr) { - need_healed++; - } - } - } - } - } - return GetNumberNeedingHealedInRaidGroup(need_healed, hpr, includePets, raid); + return count; } int Bot::GetRawACNoShield(int &shield_ac) { @@ -7580,134 +7828,129 @@ int Bot::GroupLeadershipAAOffenseEnhancement() { } bool Bot::GetNeedsCured(Mob *tar) { - bool needCured = false; + bool need_cured = false; + if (tar) { - if (tar->FindType(SE_PoisonCounter) || tar->FindType(SE_DiseaseCounter) || tar->FindType(SE_CurseCounter) || tar->FindType(SE_CorruptionCounter)) { + if (tar->FindType(SpellEffect::PoisonCounter) || tar->FindType(SpellEffect::DiseaseCounter) || tar->FindType(SpellEffect::CurseCounter) || tar->FindType(SpellEffect::CorruptionCounter)) { uint32 buff_count = tar->GetMaxTotalSlots(); - int buffsWithCounters = 0; - needCured = true; + for (unsigned int j = 0; j < buff_count; j++) { if (IsValidSpell(tar->GetBuffs()[j].spellid)) { if (CalculateCounters(tar->GetBuffs()[j].spellid) > 0) { - buffsWithCounters++; - if (buffsWithCounters == 1 && (tar->GetBuffs()[j].ticsremaining < 2 || (int32)((tar->GetBuffs()[j].ticsremaining * 6) / tar->GetBuffs()[j].counters) < 2)) { - needCured = false; - break; + if (tar->GetBuffs()[j].ticsremaining < 1) { + continue; } + + need_cured = true; } } } } } - return needCured; + + return need_cured; } bool Bot::GetNeedsHateRedux(Mob *tar) { - // This really should be a scalar function based in class Mob that returns 'this' state..but, is inline with current Bot coding... - // TODO: Good starting point..but, can be refined.. - // TODO: Still awaiting bot spell rework.. - if (!tar || !tar->IsEngaged() || !tar->HasTargetReflection() || !tar->GetTarget()->IsNPC()) + if (!tar || !tar->IsEngaged() || !tar->HasTargetReflection() || !tar->GetTarget()->IsNPC() || (tar->IsBot() && tar->CastToBot()->IsTaunting())) { return false; + } if (tar->IsBot()) { - switch (tar->GetClass()) { - case Class::Rogue: - if (tar->CanFacestab() || tar->CastToBot()->m_evade_timer.Check(false)) - return false; - case Class::Cleric: - case Class::Druid: - case Class::Shaman: - case Class::Necromancer: - case Class::Wizard: - case Class::Magician: - case Class::Enchanter: + if (tar->GetHPRatio() > GetUltimateSpellTypeMinThreshold(BotSpellTypes::HateRedux, tar) && tar->GetHPRatio() < + GetUltimateSpellTypeMaxThreshold( + BotSpellTypes::HateRedux, + tar + )) { return true; - default: - return false; } } return false; } -bool Bot::HasOrMayGetAggro() { - bool mayGetAggro = false; - if (GetTarget() && GetTarget()->GetHateTop()) { - Mob *topHate = GetTarget()->GetHateTop(); - if (topHate == this) - mayGetAggro = true; - else { - uint32 myHateAmt = GetTarget()->GetHateAmount(this); - uint32 topHateAmt = GetTarget()->GetHateAmount(topHate); +bool Bot::HasOrMayGetAggro(bool sit_aggro, uint32 spell_id) { + if ( + !GetTarget() || + !GetTarget()->GetHateTop() + ) { + return false; + } - if (myHateAmt > 0 && topHateAmt > 0 && (uint8)((myHateAmt / topHateAmt) * 100) > 90) - mayGetAggro = true; - } - } - return mayGetAggro; + Mob* top_hate = GetTarget()->GetHateTop(); + + if (top_hate == this) { + return true; + } + + uint32 my_hate = GetTarget()->GetHateAmount(this); + uint32 top_hate_amt = GetTarget()->GetHateAmount(top_hate); + + if (sit_aggro && !spell_id) { + my_hate *= (1 + (RuleI(Aggro, SittingAggroMod) / 100)); + } + + if (spell_id && IsValidSpell(spell_id)) { + my_hate += CheckAggroAmount(spell_id, GetTarget()); + } + + if ( + top_hate_amt < 1 || + ( + my_hate > 0 && + (my_hate * 100 / top_hate_amt) > RuleI(Bots, HasOrMayGetAggroThreshold) + ) + ) { + return true; + } + + return false; } void Bot::SetDefaultBotStance() { _botStance = GetClass() == Class::Warrior ? Stance::Aggressive : Stance::Balanced; } -void Bot::BotGroupSay(Mob* speaker, const char* msg, ...) { +void Bot::RaidGroupSay(const char* msg, ...) { + if ( + GetTempSpellType() != UINT16_MAX && + !GetSpellTypeAnnounceCast(GetTempSpellType()) + ) { + return; + } + char buf[1000]; va_list ap; va_start(ap, msg); vsnprintf(buf, 1000, msg, ap); va_end(ap); - if (speaker->IsRaidGrouped()) { - Raid* r = entity_list.GetRaidByBotName(speaker->GetName()); + if (IsRaidGrouped()) { + Raid* r = GetStoredRaid() ? GetStoredRaid() : GetRaid(); + if (r) { for (const auto& m : r->members) { - if (m.member && !m.is_bot) { - m.member->FilteredMessageString( - speaker, - Chat::PetResponse, - FilterSocials, - GENERIC_SAY, - speaker->GetCleanName(), - buf - ); + if (m.member && m.member->IsClient()) { + m.member->FilteredMessageString(this,Chat::PetResponse,FilterSocials,GENERIC_SAY,GetCleanName(),buf); } } } } - else if (speaker->HasGroup()) { - Group* g = speaker->GetGroup(); - if (g) { - for (auto& m : g->members) { - if (m && !m->IsBot()) { - m->FilteredMessageString( - speaker, - Chat::PetResponse, - FilterSocials, - GENERIC_SAY, - speaker->GetCleanName(), - buf - ); + else if (HasGroup()) { + for (auto& m : GetGroup()->members) { + if (m && m->IsClient()) { + m->FilteredMessageString(this,Chat::PetResponse,FilterSocials,GENERIC_SAY,GetCleanName(),buf); } } - } } - else { - //speaker->Say("%s", buf); - speaker->GetOwner()->FilteredMessageString( - speaker, - Chat::PetResponse, - FilterSocials, - GENERIC_SAY, - speaker->GetCleanName(), - buf - ); + else if (GetOwner()) { + GetOwner()->FilteredMessageString(this,Chat::PetResponse,FilterSocials,GENERIC_SAY,GetCleanName(),buf); } } bool Bot::UseDiscipline(uint32 spell_id, uint32 target) { if (!IsValidSpell(spell_id)) { - BotGroupSay(this, "Not a valid spell."); + RaidGroupSay("Not a valid spell."); return false; } @@ -8003,51 +8246,24 @@ bool Bot::CheckDataBucket(std::string bucket_name, const std::string& bucket_val int Bot::GetExpansionBitmask() { - if (m_expansion_bitmask >= 0) { - return m_expansion_bitmask; + if (_expansionBitmask >= 0) { + return _expansionBitmask; } return RuleI(Bots, BotExpansionSettings); } -void Bot::SetExpansionBitmask(int expansion_bitmask, bool save) +void Bot::SetExpansionBitmask(int expansionBitmask) { - m_expansion_bitmask = expansion_bitmask; - - if (save) { - if (!database.botdb.SaveExpansionBitmask(GetBotID(), expansion_bitmask)) { - if (GetBotOwner() && GetBotOwner()->IsClient()) { - GetBotOwner()->CastToClient()->Message( - Chat::White, - fmt::format( - "Failed to save expansion bitmask for {}.", - GetCleanName() - ).c_str() - ); - } - } - } + _expansionBitmask = expansionBitmask; LoadAAs(); } -void Bot::SetBotEnforceSpellSetting(bool enforce_spell_settings, bool save) +void Bot::SetBotEnforceSpellSetting(bool enforceSpellSettings) { - m_enforce_spell_settings = enforce_spell_settings; + _enforceSpellSettings = enforceSpellSettings; - if (save) { - if (!database.botdb.SaveEnforceSpellSetting(GetBotID(), enforce_spell_settings)) { - if (GetBotOwner() && GetBotOwner()->IsClient()) { - GetBotOwner()->CastToClient()->Message( - Chat::White, - fmt::format( - "Failed to save enforce spell settings for {}.", - GetCleanName() - ).c_str() - ); - } - } - } LoadBotSpellSettings(); AI_AddBotSpells(GetBotSpellID()); } @@ -8146,12 +8362,13 @@ void Bot::ListBotSpells(uint8 min_level) bot_owner->Message( Chat::White, fmt::format( - "Spell {} | Spell: {} | Add Spell: {}", + "Spell {} | Spell: {} (ID: {}) | Add Spell: {}", spell_number, Saylink::Silent( fmt::format("^spellinfo {}", s.spellid), spells[s.spellid].name ), + s.spellid, Saylink::Silent( fmt::format("^spellsettingsadd {} {} {} {}", s.spellid, s.priority, s.min_hp, s.max_hp), "Add") ).c_str() @@ -8297,24 +8514,6 @@ std::string Bot::GetHPString(int8 min_hp, int8 max_hp) return hp_string; } -void Bot::SetBotArcherySetting(bool bot_archer_setting, bool save) -{ - m_bot_archery_setting = bot_archer_setting; - if (save) { - if (!database.botdb.SaveBotArcherSetting(GetBotID(), bot_archer_setting)) { - if (GetBotOwner() && GetBotOwner()->IsClient()) { - GetBotOwner()->CastToClient()->Message( - Chat::White, - fmt::format( - "Failed to save archery settings for {}.", - GetCleanName() - ).c_str() - ); - } - } - } -} - std::vector Bot::GetApplySpellList( ApplySpellType apply_type, bool allow_pets, @@ -8417,7 +8616,7 @@ void Bot::Escape() } void Bot::Fling(float value, float target_x, float target_y, float target_z, bool ignore_los, bool clip_through_walls, bool calculate_speed) { - BuffFadeByEffect(SE_Levitate); + BuffFadeByEffect(SpellEffect::Levitate); if (CheckLosFN(target_x, target_y, target_z, 6.0f) || ignore_los) { auto p = new EQApplicationPacket(OP_Fling, sizeof(fling_struct)); auto* f = (fling_struct*) p->pBuffer; @@ -8487,60 +8686,126 @@ void Bot::SendSpellAnim(uint16 target_id, uint16 spell_id) entity_list.QueueCloseClients(this, &app, false, RuleI(Range, SpellParticles)); } -float Bot::GetBotCasterMaxRange(float melee_distance_max) {// Calculate caster distances - float caster_distance_max = 0.0f; - float caster_distance_min = 0.0f; - float caster_distance = 0.0f; - - caster_distance_max = GetBotCasterRange() * GetBotCasterRange(); - if (!GetBotCasterRange() && GetLevel() >= GetStopMeleeLevel() && GetClass() >= Class::Warrior && GetClass() <= Class::Berserker) { - caster_distance_max = MAX_CASTER_DISTANCE[GetClass() - 1]; - } - if (caster_distance_max) { - caster_distance_min = melee_distance_max; - if (caster_distance_max <= caster_distance_min) { - caster_distance_max = caster_distance_min * 1.25f; - } - } - return caster_distance_max; -} - - int32 Bot::CalcItemATKCap() { return RuleI(Character, ItemATKCap) + itembonuses.ItemATKCap + spellbonuses.ItemATKCap + aabonuses.ItemATKCap; } -bool Bot::CheckSpawnConditions(Client* c) { +bool Bot::CheckCampSpawnConditions(Client* c) { + + if (RuleB(Bots, PreventBotSpawnOnFD) && c->GetFeigned()) { + c->Message(Chat::White, "You cannot camp or spawn bots while feigned."); - if (c->GetFeigned()) { - c->Message(Chat::White, "You cannot spawn a bot-group while feigned."); return false; } - Raid* raid = entity_list.GetRaidByClient(c); - if (raid && raid->IsEngaged()) { - c->Message(Chat::White, "You cannot spawn bots while your raid is engaged."); - return false; - } + if (RuleB(Bots, PreventBotSpawnOnEngaged)) { + Raid* raid = entity_list.GetRaidByClient(c); + if (raid && raid->IsEngaged()) { + c->Message(Chat::White, "You cannot camp or spawn bots while your raid is engaged."); - auto* owner_group = c->GetGroup(); - if (owner_group) { - std::list member_list; - owner_group->GetClientList(member_list); - member_list.remove(nullptr); + return false; + } - for (auto member_iter : member_list) { - if (member_iter->IsEngaged() || member_iter->GetAggroCount() > 0) { - c->Message(Chat::White, "You cannot spawn bots while your group is engaged,"); + auto* owner_group = c->GetGroup(); + if (owner_group) { + std::list member_list; + owner_group->GetClientList(member_list); + member_list.remove(nullptr); + + for (auto member_iter : member_list) { + if (member_iter->IsEngaged() || member_iter->GetAggroCount() > 0) { + c->Message(Chat::White, "You cannot camp or spawn bots while your group is engaged,"); + return false; + } + } + } + else { + if (c->GetAggroCount() > 0) { + c->Message(Chat::White, "You cannot camp or spawn bots while you are engaged,"); return false; } } - } else { - if (c->GetAggroCount() > 0) { - c->Message(Chat::White, "You cannot spawn bots while you are engaged,"); - return false; + } + + return true; +} + +bool Bot::CheckHighEnoughLevelForBots(Client* c, uint8 bot_class) { + auto bot_character_level = c->GetBotRequiredLevel(bot_class); + bool not_high_enough_level = bot_character_level >= 0 && c->GetLevel() < bot_character_level; + + if (not_high_enough_level) { + c->Message( + Chat::White, + fmt::format( + "You must be level {} to spawn {}bots.", + bot_character_level, + bot_class ? GetClassIDName(bot_class) : "" + ).c_str() + ); + + return false; + } + + return true; +} + +bool Bot::CheckCreateLimit(Client* c, uint32 bot_count, uint8 bot_class) { + auto bot_creation_limit = c->GetBotCreationLimit(bot_class); + bool is_beyond_spawn_limit = bot_creation_limit >= 0 && bot_count >= bot_creation_limit; + + if (is_beyond_spawn_limit) { + std::string message; + + if (bot_creation_limit) { + message = fmt::format( + "You cannot create anymore than {} {}bot{}.", + bot_creation_limit, + bot_class ? GetClassIDName(bot_class) : "", + bot_creation_limit != 1 ? "s" : "" + ); + } else { + message = fmt::format( + "You cannot create any {}bots.", + bot_class ? GetClassIDName(bot_class) : "" + ); } + + c->Message(Chat::Yellow, message.c_str()); + + return false; + } + + return true; +} + +bool Bot::CheckSpawnLimit(Client* c, uint8 bot_class) { + auto bot_spawn_limit = c->GetBotSpawnLimit(bot_class); + auto spawned_bot_count = Bot::SpawnedBotCount(c->CharacterID(), bot_class); + bool is_beyond_spawn_limit = bot_spawn_limit >= 0 && spawned_bot_count >= bot_spawn_limit; + + if (is_beyond_spawn_limit) { + std::string message; + + if (bot_spawn_limit) { + message = fmt::format( + "You cannot have more than {} spawned {}bot{}.", + bot_spawn_limit, + bot_class ? GetClassIDName(bot_class) : "", + bot_spawn_limit != 1 ? "s" : "" + ); + } + else { + message = fmt::format( + "You are not currently allowed to spawn any {}bots.", + bot_class ? GetClassIDName(bot_class) : "" + ); + } + + c->Message(Chat::White, message.c_str()); + + return false; } return true; @@ -8610,14 +8875,14 @@ void Bot::SetSpellRecastTimer(uint16 spell_id, int32 recast_delay) { } if (CheckSpellRecastTimer(spell_id)) { - BotTimer_Struct t; + BotTimer t; - t.timer_id = spells[ spell_id ].timer_id; + t.timer_id = spells[spell_id].timer_id; t.timer_value = (Timer::GetCurrentTime() + recast_delay); t.recast_time = recast_delay; t.is_spell = true; t.is_disc = false; - t.spell_id = spells[ spell_id ].id; + t.spell_id = spells[spell_id].id; t.is_item = false; t.item_id = 0; @@ -8630,7 +8895,7 @@ void Bot::SetSpellRecastTimer(uint16 spell_id, int32 recast_delay) { ( ( spells[spell_id].timer_id != 0 && - spells[spell_id].timer_id == bot_timers[ i ].timer_id + spells[spell_id].timer_id == bot_timers[i].timer_id ) || bot_timers[i].spell_id == spell_id ) @@ -8716,14 +8981,14 @@ void Bot::SetDisciplineReuseTimer(uint16 spell_id, int32 reuse_timer) } if (CheckDisciplineReuseTimer(spell_id)) { - BotTimer_Struct t; + BotTimer t; - t.timer_id = spells[ spell_id ].timer_id; + t.timer_id = spells[spell_id].timer_id; t.timer_value = (Timer::GetCurrentTime() + reuse_timer); t.recast_time = reuse_timer; t.is_spell = false; t.is_disc = true; - t.spell_id = spells[ spell_id ].id; + t.spell_id = spells[spell_id].id; t.is_item = false; t.item_id = 0; @@ -8813,7 +9078,7 @@ void Bot::SetItemReuseTimer(uint32 item_id, uint32 reuse_timer) } if (CheckItemReuseTimer(item_id)) { - BotTimer_Struct t; + BotTimer t; t.timer_id = (item->RecastType == NegativeItemReuse ? item->ID : item->RecastType); t.timer_value = ( @@ -9184,14 +9449,17 @@ void Bot::DoItemClick(const EQ::ItemData *item, uint16 slot_id) bool is_casting_bard_song = false; Mob* tar = (GetOwner()->GetTarget() ? GetOwner()->GetTarget() : this); + if (!DoLosChecks(tar)) { + return; + } + if (IsCasting()) { InterruptSpell(); } SetIsUsingItemClick(true); - BotGroupSay( - this, + RaidGroupSay( fmt::format( "Attempting to cast [{}] on {}.", spells[item->Click.Effect].name, @@ -9223,3 +9491,3971 @@ void Bot::DoItemClick(const EQ::ItemData *item, uint16 slot_id) } uint8 Bot::spell_casting_chances[SPELL_TYPE_COUNT][Class::PLAYER_CLASS_COUNT][Stance::AEBurn][cntHSND] = { 0 }; + +bool Bot::PrecastChecks(Mob* tar, uint16 spell_type) { + if (!TargetValidation(tar)) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast due to PrecastChecks !tar.'", GetCleanName()); + return false; + } + + LogBotSpellChecksDetail("{} says, 'Running [{}] PreChecks on [{}].'", GetCleanName(), GetSpellTypeNameByID(spell_type), tar->GetCleanName()); + + if (GetUltimateSpellTypeHold(spell_type, tar)) { + if (!IsCommandedSpell()) { + SetSpellTypeAITimer(spell_type, RuleI(Bots, AICastSpellTypeHeldDelay)); + } + LogBotSpellChecksDetail("{} says, 'Cancelling cast of [{}] on [{}] due to GetUltimateSpellTypeHold.'", GetCleanName(), GetSpellTypeNameByID(spell_type), tar->GetCleanName()); + return false; + } + + if ( + IsPullingSpell() && + IsPullingBotSpellType(spell_type) + ) { //Skip remaining checks for commanded + return true; + } + + if (!EQ::ValueWithin(GetManaRatio(), GetSpellTypeMinManaLimit(spell_type), GetSpellTypeMaxManaLimit(spell_type))) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of [{}] on [{}] due to GetSpellTypeMinManaLimit or GetSpellTypeMaxManaLimit.'", GetCleanName(), GetSpellTypeNameByID(spell_type), tar->GetCleanName()); + return false; + } + + if (!EQ::ValueWithin(GetHPRatio(), GetSpellTypeMinHPLimit(spell_type), GetSpellTypeMaxHPLimit(spell_type))) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of [{}] on [{}] due to GetSpellTypeMinHPLimit or GetSpellTypeMaxHPLimit.'", GetCleanName(), GetSpellTypeNameByID(spell_type), tar->GetCleanName()); + return false; + } + + if (!GetUltimateSpellTypeRecastCheck(spell_type, tar)) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of [{}] on [{}] due to GetUltimateSpellTypeRecastCheck.'", GetCleanName(), GetSpellTypeNameByID(spell_type), tar->GetCleanName()); + return false; + } + + switch (spell_type) { //This will skip Threshold Checks during Precast for specific SpellTypes that are checked when acquiring new targets + case BotSpellTypes::Mez: + case BotSpellTypes::AEMez: + return true; + default: + if (!EQ::ValueWithin(GetHPRatioForSpellType(spell_type, tar), GetUltimateSpellTypeMinThreshold(spell_type, tar), GetUltimateSpellTypeMaxThreshold(spell_type, tar))) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of [{}] on [{}] due to GetUltimateSpellTypeMinThreshold or GetUltimateSpellTypeMaxThreshold.'", GetCleanName(), GetSpellTypeNameByID(spell_type), tar->GetCleanName()); + return false; + } + } + + return true; +} + +bool Bot::CastChecks(uint16 spell_id, Mob* tar, uint16 spell_type, bool prechecks, bool ae_check) { + if (prechecks) { + if (!tar || tar->GetAppearance() == eaDead || tar->GetHP() < 0) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast due to CastChecks !tar.'", GetCleanName()); + return false; + } + + if (spells[spell_id].target_type == ST_Self && tar != this) { + if ( + !IsEffectInSpell(spell_id, SpellEffect::SummonCorpse) || + ( + IsEffectInSpell(spell_id, SpellEffect::SummonCorpse) && + !RuleB(Bots, AllowCommandedSummonCorpse) + ) + ) { + tar = this; + } + } + + if (!PrecastChecks(tar, spell_type)) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast due to !PrecastChecks.'", GetCleanName()); + return false; + } + } + + LogBotSpellChecksDetail("{} says, 'Running [{}] CastChecks on [{}].'", GetCleanName(), GetSpellTypeNameByID(spell_type), (tar ? tar->GetCleanName() : "nobody")); + + if (!IsValidSpell(spell_id)) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast due to !IsValidSpell.'", GetCleanName()); + return false; + } + + if ( + IsFeared() || + IsSilenced() || + IsAmnesiad() + ) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to Incapacitated.'", GetCleanName(), GetSpellName(spell_id), (tar ? tar->GetCleanName() : "nobody")); + return false; + } + + if ( + ( + IsStunned() || + IsMezzed() || + DivineAura() + ) && + !IsCastNotStandingSpell(spell_id) + ) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to !IsCastNotStandingSpell.'", GetCleanName(), GetSpellName(spell_id), (tar ? tar->GetCleanName() : "nobody")); + return false; + } + + if ( + IsDetrimentalSpell(spell_id) && + !zone->CanDoCombat() + ) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to !CanDoCombat.'", GetCleanName(), GetSpellName(spell_id), (tar ? tar->GetCleanName() : "nobody")); + return false; + } + + if (!CheckSpellRecastTimer(spell_id)) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} due to !CheckSpellRecastTimer.'", GetCleanName(), GetSpellName(spell_id)); + return false; + } + + bool is_mana_exempt = RuleB(Bots, FinishBuffing) && !IsEngaged() && IsBotBuffSpellType(spell_type); + + if (!BotHasEnoughMana(spell_id) && !is_mana_exempt) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} due to !BotHasEnoughMana.'", GetCleanName(), GetSpellName(spell_id)); + return false; + } + + if (zone->IsSpellBlocked(spell_id, glm::vec3(GetPosition()))) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} due to IsSpellBlocked.'", GetCleanName(), GetSpellName(spell_id)); + return false; + } + + if ( + spells[spell_id].caster_requirement_id && + !PassCastRestriction(spells[spell_id].caster_requirement_id) + ) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} due to !PassCastRestriction.'", GetCleanName(), GetSpellName(spell_id)); + return false; + } + + if ( + !spells[spell_id].can_cast_in_combat && + spells[spell_id].can_cast_out_of_combat + ) { + if (IsBeneficialSpell(spell_id)) { + if (IsEngaged()) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} due to !can_cast_in_combat.'", GetCleanName(), GetSpellName(spell_id)); + return false; + } + } + } + else if ( + spells[spell_id].can_cast_in_combat && + !spells[spell_id].can_cast_out_of_combat + ) { + if (IsBeneficialSpell(spell_id)) { + if (!IsEngaged()) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} due to !can_cast_out_of_combat.'", GetCleanName(), GetSpellName(spell_id)); + return false; + } + } + } + + if (!IsDiscipline(spell_id)) { + int chance = GetFocusEffect(focusFcMute, spell_id); + + if ( + chance && + zone->random.Roll(chance) + ) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} due to focusFcMute.'", GetCleanName(), GetSpellName(spell_id)); + return false; + } + } + + if ( + !zone->CanLevitate() && + IsEffectInSpell(spell_id, SpellEffect::Levitate) + ) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} due to !CanLevitate.'", GetCleanName(), GetSpellName(spell_id)); + return false; + } + + if ( + spells[spell_id].time_of_day == SpellTimeRestrictions::Day && + !zone->zone_time.IsDayTime() + ) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} due to !IsDayTime.'", GetCleanName(), GetSpellName(spell_id)); + return false; + } + + if ( + spells[spell_id].time_of_day == SpellTimeRestrictions::Night && + !zone->zone_time.IsNightTime() + ) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} due to !IsNightTime.'", GetCleanName(), GetSpellName(spell_id)); + return false; + } + + if ( + spells[spell_id].zone_type == 1 && + !zone->CanCastOutdoor() + ) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} due to !CanCastOutdoor.'", GetCleanName(), GetSpellName(spell_id)); + return false; + } + + if ( + BotSpellTypeRequiresTarget(spell_type) && + !TargetValidation(tar) + ) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast due to CastChecks !tar.'", GetCleanName()); + return false; + } + + if (spells[spell_id].target_type == ST_Self && tar != this) { + if (spell_type == BotSpellTypes::SummonCorpse && RuleB(Bots, AllowCommandedSummonCorpse)) { + // Don't cancel (Summon Corpse is allowed) + } + else { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to ST_Self.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); + + return false; + } + } + + if ( + this == tar && + IsSacrificeSpell(spell_id) + ) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} due to IsSacrificeSpell.'", GetCleanName(), GetSpellName(spell_id)); + return false; + } + + if (tar->GetSpecialAbility(SpecialAbility::MagicImmunity)) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to MagicImmunity.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); + return false; + } + + if ( + tar->GetSpecialAbility(SpecialAbility::CastingFromRangeImmunity) && + !CombatRange(tar) + ) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to CastingFromRangeImmunity.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); + return false; + } + + if (tar->CastToBot()->IsImmuneToBotSpell(spell_id, this)) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsImmuneToBotSpell.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); + return false; + } + + if (!tar->CheckSpellLevelRestriction(this, spell_id)) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to CheckSpellLevelRestriction.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); + return false; + } + + if ( + IsBeneficialSpell(spell_id) && + ( + ( + RuleB(Spells, EnableBlockedBuffs) && + tar->IsClient() + ) || + ( + RuleB(Bots, AllowBotBlockedBuffs) && + tar->IsBot() + ) + ) + ) { + if (tar->IsBlockedBuff(spell_id)) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsBlockedPetBuff.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); + return false; + } + } + + if ( + IsBeneficialSpell(spell_id) && + tar->IsPet() && + ( + ( + RuleB(Spells, EnableBlockedBuffs) && + tar->GetOwner() && + tar->GetOwner()->IsClient() + ) || + ( + RuleB(Bots, AllowBotBlockedBuffs) && + tar->GetOwner() && + tar->GetOwner()->IsBot() + ) + ) + ) { + if (tar->GetOwner()->IsBlockedPetBuff(spell_id)) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsBlockedPetBuff.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); + return false; + } + } + //LogBotSpellChecksDetail("{} says, 'Doing CanCastSpellType checks of {} on {}.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); + if (!CanCastSpellType(spell_type, spell_id, tar)) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to CanCastSpellType.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); + return false; + } + + if (!IsValidTargetType(spell_id, GetSpellTargetType(spell_id), tar->GetBodyType())) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsValidTargetType.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); + return false; + } + + if ( + ( + RequiresStackCheck(spell_type) || + ( + !RequiresStackCheck(spell_type) && + CalcBuffDuration(this, tar, spell_id) != 0 + ) + ) + && + tar->CanBuffStack(spell_id, GetLevel(), true) < 0 + ) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to !CanBuffStack.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); + return false; + } + + if ( + IsBeneficialSpell(spell_id) && + tar->BuffCount() >= tar->GetCurrentBuffSlots() && + CalcBuffDuration(this, tar, spell_id) != 0 + ) { + return false; + } + + if ( + !ae_check && + !IsValidSpellRange(spell_id, tar) + ) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsValidSpellRange.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); + return false; + } + + if (IsCommandedSpell() || spell_type == UINT16_MAX) { //AA/Forced cast checks, return here + return true; + } + + if ( + !IsTaunting() && + GetSpellTypeAggroCheck(spell_type) && + HasOrMayGetAggro(IsSitting(), spell_id) && + !tar->IsFleeing() + ) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to HasOrMayGetAggro.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); + return false; + } + + if (!DoResistCheckBySpellType(tar, spell_id, spell_type)) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to DoResistCheckBySpellType.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); + return false; + } + + if ( + spells[spell_id].target_type != ST_Self && + IsBeneficialSpell(spell_id) && + !IsAnyHealSpell(spell_id) && + !IsCureSpell(spell_id) && + !IsHealOverTimeSpell(spell_id) && + !IsGroupHealOverTimeSpell(spell_id) && + IsTargetAlreadyReceivingSpell(tar, spell_id) + ) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsTargetAlreadyReceivingSpell.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); + return false; + } + + return true; +} + +bool Bot::CanCastSpellType(uint16 spell_type, uint16 spell_id, Mob* tar) { + if (!spell_id || !tar) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to failsafe checks.'", GetCleanName(), (spell_id ? GetSpellName(spell_id) : (spell_type ? GetSpellTypeNameByID(spell_type) : "Unknown")), (tar ? tar->GetCleanName() : "Unknown")); + return false; + } + + uint8 bot_class = GetClass(); + auto spell = spells[spell_id]; + + switch (spell_type) { + case BotSpellTypes::Buff: + case BotSpellTypes::PetBuffs: + case BotSpellTypes::PreCombatBuff: + case BotSpellTypes::InCombatBuff: + case BotSpellTypes::DamageShields: + case BotSpellTypes::PetDamageShields: + case BotSpellTypes::ResistBuffs: + case BotSpellTypes::PetResistBuffs: + case BotSpellTypes::Teleport: + case BotSpellTypes::Succor: + case BotSpellTypes::BindAffinity: + case BotSpellTypes::Identify: + case BotSpellTypes::Levitate: + case BotSpellTypes::Rune: + case BotSpellTypes::WaterBreathing: + case BotSpellTypes::Size: + case BotSpellTypes::Invisibility: + case BotSpellTypes::MovementSpeed: + case BotSpellTypes::SendHome: + if ( + tar == this && + 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; + } + + if ( + ( + spell_type != BotSpellTypes::Teleport && + spell_type != BotSpellTypes::Succor + ) && + ( + IsEffectInSpell(spell_id, SpellEffect::Teleport) || + IsEffectInSpell(spell_id, SpellEffect::Succor) + ) + ) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to Teleport.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); + return false; + } + + if ( + tar->IsPet() && + !RuleB(Bots, CanCastIllusionsOnPets) && + IsEffectInSpell(spell_id, SpellEffect::Illusion) + ) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to PetSE_Illusion.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); + return false; + } + + if ( + (spell.target_type == ST_Pet || spell.target_type == ST_SummonedPet) && + ( + !tar->IsPet() || + ( + tar->GetOwner() != this && + !RuleB(Bots, CanCastPetOnlyOnOthersPets) + ) + ) + ) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to PetOnly.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); + return false; + } + + if ( + ( + IsGroupSpell(spell_id) && + tar->IsPet() + ) && + ( + !tar->GetOwner() || + ( + RuleB(Bots, RequirePetAffinity) && + !tar->GetOwner()->HasPetAffinity() + ) + ) + ) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to PetGroupSpellTarget.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); + return false; + } + + if (!IsCommandedSpell()) { + switch (tar->GetArchetype()) { + case Archetype::Caster: + if ( + tar->IsBot() && + (tar->GetLevel() >= tar->CastToBot()->GetStopMeleeLevel()) && + ( + IsEffectInSpell(spell_id, SpellEffect::AttackSpeed) || + IsEffectInSpell(spell_id, SpellEffect::ReverseDS) + ) || + ( + SpellEffectsCount(spell_id) == 1 && + ( + IsEffectInSpell(spell_id, SpellEffect::ATK) || IsEffectInSpell(spell_id, SpellEffect::STR) + ) + ) + ) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to Archetype::Caster.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); + return false; + } + break; + case Archetype::Melee: + if ( + IsEffectInSpell(spell_id, SpellEffect::IncreaseSpellHaste) || + IsEffectInSpell(spell_id, SpellEffect::ManaPool) || + IsEffectInSpell(spell_id, SpellEffect::CastingLevel) || + IsEffectInSpell(spell_id, SpellEffect::ManaRegen_v2) || + IsEffectInSpell(spell_id, SpellEffect::CurrentMana) + ) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to Archetype::Melee.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); + return false; + } + break; + case Archetype::Hybrid: //Hybrids get all buffs + default: + break; + } + } + + // Differences for each type + if (spell_type != BotSpellTypes::InCombatBuff) { + if ( + IsEffectInSpell(spell_id, SpellEffect::AbsorbMagicAtt) || + IsEffectInSpell(spell_id, SpellEffect::Rune) + ) { + for (int i = 0; i < tar->GetMaxTotalSlots(); i++) { + uint32 buff_count = tar->GetMaxTotalSlots(); + + for (unsigned int j = 0; j < buff_count; j++) { + if (IsValidSpell(tar->GetBuffs()[j].spellid)) { + if (IsLichSpell(tar->GetBuffs()[j].spellid)) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsLichSpell.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); + return false; + } + } + } + } + } + } + + break; + case BotSpellTypes::PreCombatBuffSong: + case BotSpellTypes::InCombatBuffSong: + case BotSpellTypes::OutOfCombatBuffSong: + if (!IsCommandedSpell()) { + switch (tar->GetArchetype()) { + case Archetype::Caster: + if ( + tar->IsBot() && + (tar->GetLevel() >= tar->CastToBot()->GetStopMeleeLevel()) && + ( + IsEffectInSpell(spell_id, SpellEffect::AttackSpeed) || + IsEffectInSpell(spell_id, SpellEffect::ReverseDS) + ) || + ( + SpellEffectsCount(spell_id) == 1 && + ( + IsEffectInSpell(spell_id, SpellEffect::ATK) || + IsEffectInSpell(spell_id, SpellEffect::STR) + ) + ) + ) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to Archetype::Caster.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); + return false; + } + break; + case Archetype::Melee: + if ( + IsEffectInSpell(spell_id, SpellEffect::IncreaseSpellHaste) || + IsEffectInSpell(spell_id, SpellEffect::ManaPool) || + IsEffectInSpell(spell_id, SpellEffect::CastingLevel) || + IsEffectInSpell(spell_id, SpellEffect::ManaRegen_v2) || + IsEffectInSpell(spell_id, SpellEffect::CurrentMana) + ) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to Archetype::Melee.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); + return false; + } + break; + case Archetype::Hybrid: //Hybrids get all buffs + default: + break; + } + } + + break; + case BotSpellTypes::AELull: + case BotSpellTypes::Lull: + if ( + IsHarmonySpell(spell_id) && + !HarmonySpellLevelCheck(spell_id, tar) + ) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to HarmonySpellLevelCheck.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); + return false; + } + + break; + default: + break; + } + + //LogBotSpellChecksDetail("{} says, {} on {} passed CanCastSpellType.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); + return true; +} + +bool Bot::BotHasEnoughMana(uint16 spell_id) { + if (!IsValidSpell(spell_id)) { + return false; + } + + int32 mana_cost = spells[spell_id].mana; + + if (GetMana() < mana_cost) { + return false; + } + + return true; +} + +bool Bot::IsTargetAlreadyReceivingSpell(Mob* tar, uint16 spell_id) { + if (!tar || !spell_id) { + return true; + } + + uint16 target_id = tar->GetID(); + + for (Mob* m : GetSpellTargetList(RuleB(Bots, CrossRaidBuffingAndHealing))) { + if ( + m->IsBot() && + m->IsCasting() && + m->CastToBot()->casting_spell_targetid && + m->CastingSpellID() == spell_id + ) { + if (m->CastToBot()->casting_spell_targetid == target_id) { + return true; + } + + if (!RuleB(Bots, CrossRaidBuffingAndHealing) && IsGroupSpell(spell_id)) { + for (Mob* x : GatherSpellTargets(false, tar)) { + if (x->GetID() == m->CastToBot()->casting_spell_targetid) { + return true; + } + } + } + } + } + + return false; +} + +bool Bot::DoResistCheck(Mob* tar, uint16 spell_id, int32 resist_limit) { + + if (!tar || spell_id == 0) { + return false; + } + + int32 resist_difficulty = -spells[spell_id].resist_difficulty; + int32 level_mod = (tar->GetLevel() - GetLevel()) * (tar->GetLevel() - GetLevel()) / 2; + + if (tar->GetLevel() - GetLevel() < 0) { + level_mod = -level_mod; + } + + int32 target_resist = 0; + + switch (GetSpellResistType(spell_id)) { + case RESIST_NONE: + return true; + case RESIST_MAGIC: + target_resist = tar->GetMR(); + break; + case RESIST_COLD: + target_resist = tar->GetCR(); + break; + case RESIST_FIRE: + target_resist = tar->GetFR(); + break; + case RESIST_POISON: + target_resist = tar->GetPR(); + break; + case RESIST_DISEASE: + target_resist = tar->GetDR(); + break; + case RESIST_CORRUPTION: + target_resist = tar->GetCorrup(); + break; + default: + return true; + } + + if ((target_resist + level_mod - resist_difficulty) > resist_limit) { + return false; + } + + return true; +} + +bool Bot::DoResistCheckBySpellType(Mob* tar, uint16 spell_id, uint16 spell_type) { + if (!tar || !IsValidSpell(spell_id)) { + return false; + } + + if (GetSpellTypeResistLimit(spell_type) == 0) { + return true; + } + + return DoResistCheck(tar, spell_id, GetSpellTypeResistLimit(spell_type)); +} + +bool Bot::IsValidTargetType(uint16 spell_id, int target_type, uint8 body_type) { + if (!spell_id) { + return false; + } + + switch (target_type) { + case ST_Undead: + if ( + body_type == BodyType::Undead || + body_type == BodyType::SummonedUndead || + body_type == BodyType::Vampire + ) { + return true; + } + + break; + case ST_Summoned: + if ( + body_type == BodyType::Summoned || + body_type == BodyType::Summoned2 || + body_type == BodyType::Summoned3 + ) { + return true; + } + + break; + case ST_Animal: + if (body_type == BodyType::Animal) { + return true; + } + + break; + case ST_Plant: + if (body_type == BodyType::Plant) { + return true; + } + + break; + case ST_Giant: + if ( + body_type == BodyType::Giant || + body_type == BodyType::RaidGiant + ) { + return true; + } + + break; + case ST_Dragon: + if ( + body_type == BodyType::Dragon || + body_type == BodyType::VeliousDragon + ) { + return true; + } + + break; + default: + return true; + } + + return false; +} + +bool Bot::IsMobEngagedByAnyone(Mob* tar) { + if (!tar) { + return false; + } + + for (Mob* m : GetSpellTargetList(true)) { + if (m->GetTarget() != tar) { + continue; + } + + bool bot_is_engaged = m->IsBot() && m->IsEngaged(); + bool bot_melee_or_casting = + bot_is_engaged && + ( + !m->CastToBot()->IsBotNonSpellFighter() || + ( + m->GetLevel() >= m->CastToBot()->GetStopMeleeLevel() && + !m->IsCasting() + ) + ); + + if (bot_melee_or_casting) { + return true; + } + + if ( + m->IsCasting() && + SpellBreaksMez(m->CastingSpellID()) + ) { + return true; + } + + if ( + m->IsClient() && + ( + m->CastToClient()->AutoAttackEnabled() || + m->CastToClient()->AutoFireEnabled() + ) + ) { + return true; + } + } + + return false; +} + +bool Bot::IsValidMezTarget(Mob* owner, Mob* npc, uint16 spell_id) { + if (npc->GetSpecialAbility(SpecialAbility::MesmerizeImmunity)) { + return false; + } + + if (!npc->CastToNPC()->IsOnHatelist(owner)) { + return false; + } + + if (npc->IsMezzed() || HasBotAttackFlag(npc)) { + return false; + } + + if (npc->HasOwner() && npc->GetOwner() && npc->GetOwner()->IsOfClientBotMerc()) { + return false; + } + + if (!IsValidTargetType(spell_id, GetSpellTargetType(spell_id), npc->GetBodyType())) { + return false; + } + + if (!IsAttackAllowed(GetTarget())) { + return false; + } + + if (!DoLosChecks(npc)) { + return false; + } + + if (IsMobEngagedByAnyone(npc)) { + return false; + } + + int buff_count = npc->GetMaxTotalSlots(); + auto npc_buffs = npc->GetBuffs(); + + for (int i = 0; i < buff_count; i++) { + if (IsDetrimentalSpell(npc_buffs[i].spellid) && IsEffectInSpell(npc_buffs[i].spellid, SpellEffect::CurrentHP)) { + return false; + } + } + + return true; +} + +void Bot::SetBotSetting(uint8 setting_type, uint16 bot_setting, int setting_value) { + switch (setting_type) { + case BotSettingCategories::BaseSetting: + SetBotBaseSetting(bot_setting, setting_value); + break; + case BotSettingCategories::SpellHold: + SetSpellTypeHold(bot_setting, setting_value); + break; + case BotSettingCategories::SpellDelay: + SetSpellTypeDelay(bot_setting, setting_value); + break; + case BotSettingCategories::SpellMinThreshold: + SetSpellTypeMinThreshold(bot_setting, setting_value); + break; + case BotSettingCategories::SpellMaxThreshold: + SetSpellTypeMaxThreshold(bot_setting, setting_value); + break; + case BotSettingCategories::SpellTypeResistLimit: + SetSpellTypeResistLimit(bot_setting, setting_value); + break; + case BotSettingCategories::SpellTypeAggroCheck: + SetSpellTypeAggroCheck(bot_setting, setting_value); + break; + case BotSettingCategories::SpellTypeMinManaPct: + SetSpellTypeMinManaLimit(bot_setting, setting_value); + break; + case BotSettingCategories::SpellTypeMaxManaPct: + SetSpellTypeMaxManaLimit(bot_setting, setting_value); + break; + case BotSettingCategories::SpellTypeMinHPPct: + SetSpellTypeMinHPLimit(bot_setting, setting_value); + break; + case BotSettingCategories::SpellTypeMaxHPPct: + SetSpellTypeMaxHPLimit(bot_setting, setting_value); + break; + case BotSettingCategories::SpellTypeIdlePriority: + SetSpellTypePriority(bot_setting, BotPriorityCategories::Idle, setting_value); + break; + case BotSettingCategories::SpellTypeEngagedPriority: + SetSpellTypePriority(bot_setting, BotPriorityCategories::Engaged, setting_value); + break; + case BotSettingCategories::SpellTypePursuePriority: + SetSpellTypePriority(bot_setting, BotPriorityCategories::Pursue, setting_value); + break; + case BotSettingCategories::SpellTypeAEOrGroupTargetCount: + SetSpellTypeAEOrGroupTargetCount(bot_setting, setting_value); + break; + case BotSettingCategories::SpellTypeAnnounceCast: + SetSpellTypeAnnounceCast(bot_setting, setting_value); + break; + } +} + +void Bot::SetBotBaseSetting(uint16 bot_setting, int setting_value) { + switch (bot_setting) { + case BotBaseSettings::ExpansionBitmask: + SetExpansionBitmask(setting_value); + break; + case BotBaseSettings::ShowHelm: + SetShowHelm(setting_value); + break; + case BotBaseSettings::FollowDistance: + SetFollowDistance(EQ::Clamp(static_cast(setting_value * setting_value), static_cast(1), static_cast((RuleI(Bots, MaxFollowDistance) * RuleI(Bots, MaxFollowDistance))))); + break; + case BotBaseSettings::StopMeleeLevel: + SetStopMeleeLevel(setting_value); + break; + case BotBaseSettings::EnforceSpellSettings: + SetBotEnforceSpellSetting(setting_value); + break; + case BotBaseSettings::RangedSetting: + SetBotRangedSetting(setting_value); + break; + case BotBaseSettings::PetSetTypeSetting: + SetPetChooserID(setting_value); + break; + case BotBaseSettings::BehindMob: + SetBehindMob(setting_value); + break; + case BotBaseSettings::DistanceRanged: + SetBotDistanceRanged(setting_value); + break; + case BotBaseSettings::IllusionBlock: + SetIllusionBlock(setting_value); + break; + case BotBaseSettings::MaxMeleeRange: + SetMaxMeleeRange(setting_value); + break; + case BotBaseSettings::MedInCombat: + SetMedInCombat(setting_value); + break; + case BotBaseSettings::SitHPPct: + SetSitHPPct(setting_value); + break; + case BotBaseSettings::SitManaPct: + SetSitManaPct(setting_value); + break; + default: + break; + } +} + +int Bot::GetBotBaseSetting(uint16 bot_setting) { + switch (bot_setting) { + case BotBaseSettings::ExpansionBitmask: + return GetExpansionBitmask(); + case BotBaseSettings::ShowHelm: + return GetShowHelm(); + case BotBaseSettings::FollowDistance: + return sqrt(GetFollowDistance()); + case BotBaseSettings::StopMeleeLevel: + return GetStopMeleeLevel(); + case BotBaseSettings::EnforceSpellSettings: + return GetBotEnforceSpellSetting(); + case BotBaseSettings::RangedSetting: + return IsBotRanged(); + case BotBaseSettings::PetSetTypeSetting: + return GetPetChooserID(); + case BotBaseSettings::BehindMob: + return GetBehindMob(); + case BotBaseSettings::DistanceRanged: + return GetBotDistanceRanged(); + case BotBaseSettings::IllusionBlock: + return GetIllusionBlock(); + case BotBaseSettings::MaxMeleeRange: + return GetMaxMeleeRange(); + case BotBaseSettings::MedInCombat: + return GetMedInCombat(); + case BotBaseSettings::SitHPPct: + return GetSitHPPct(); + case BotBaseSettings::SitManaPct: + return GetSitManaPct(); + default: + return true; + } + + return true; +} + +int Bot::GetDefaultBotBaseSetting(uint16 bot_setting, uint8 stance) { + switch (bot_setting) { + case BotBaseSettings::ExpansionBitmask: + return RuleI(Bots, BotExpansionSettings); + case BotBaseSettings::ShowHelm: + return true; + case BotBaseSettings::FollowDistance: + return RuleI(Bots, DefaultFollowDistance); + case BotBaseSettings::StopMeleeLevel: + if (IsCasterClass(GetClass())) { + return RuleI(Bots, CasterStopMeleeLevel); + } + else { + return 255; + } + case BotBaseSettings::PetSetTypeSetting: + return 0; + case BotBaseSettings::BehindMob: + if (GetClass() == Class::Rogue || (IsPureMeleeClass() && GetClass() != Class::Warrior)) { + return true; + } + else { + return false; + } + case BotBaseSettings::DistanceRanged: + switch (GetClass()) { + case Class::Warrior: + case Class::Monk: + case Class::Rogue: + case Class::Berserker: + return 0; + case Class::Bard: + return 30; + default: + return 90; + } + case BotBaseSettings::MedInCombat: + if (IsCasterClass(GetClass())) { + return true; + } + + return false; + case BotBaseSettings::SitHPPct: + case BotBaseSettings::SitManaPct: + return 80; + case BotBaseSettings::EnforceSpellSettings: + case BotBaseSettings::RangedSetting: + case BotBaseSettings::IllusionBlock: + case BotBaseSettings::MaxMeleeRange: + default: + return false; + } + + return true; +} + + +void Bot::LoadDefaultBotSettings() { + m_bot_spell_settings.clear(); + + uint8 bot_stance = GetBotStance(); + + for (uint16 i = BotBaseSettings::START_ALL; i <= BotBaseSettings::END; ++i) { + SetBotBaseSetting(i, GetDefaultSetting(BotSettingCategories::BaseSetting, i, bot_stance)); + LogBotSettingsDetail("{} says, 'Setting default {} [{}] to [{}]'", GetCleanName(), GetBotSettingCategoryName(i), i, GetDefaultBotBaseSetting(i, bot_stance)); + } + + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + BotSpellSettings t; + + t.spell_type = i; + t.short_name = GetSpellTypeShortNameByID(i); + t.name = GetSpellTypeNameByID(i); + t.hold = GetDefaultSpellTypeHold(i, bot_stance); + t.delay = GetDefaultSpellTypeDelay(i, bot_stance); + t.min_threshold = GetDefaultSpellTypeMinThreshold(i, bot_stance); + t.max_threshold = GetDefaultSpellTypeMaxThreshold(i, bot_stance); + t.resist_limit = GetDefaultSpellTypeResistLimit(i, bot_stance); + t.aggro_check = GetDefaultSpellTypeAggroCheck(i, bot_stance); + t.min_mana_pct = GetDefaultSpellTypeMinManaLimit(i, bot_stance); + t.max_mana_pct = GetDefaultSpellTypeMaxManaLimit(i, bot_stance); + t.min_hp_pct = GetDefaultSpellTypeMinHPLimit(i, bot_stance); + t.max_hp_pct = GetDefaultSpellTypeMaxHPLimit(i, bot_stance); + t.idle_priority = GetDefaultSpellTypePriority( + i, + BotPriorityCategories::Idle, + GetClass(), + bot_stance + ); + t.engaged_priority = GetDefaultSpellTypePriority( + i, + BotPriorityCategories::Engaged, + GetClass(), + bot_stance + ); + t.pursue_priority = GetDefaultSpellTypePriority( + i, + BotPriorityCategories::Pursue, + GetClass(), + bot_stance + ); + t.ae_or_group_target_count = GetDefaultSpellTypeAEOrGroupTargetCount(i, bot_stance); + t.announce_cast = GetDefaultSpellTypeAnnounceCast(i, bot_stance); + t.recast_timer.Start(); + t.ai_delay.Start(); + + m_bot_spell_settings.push_back(t); + + LogBotSettingsDetail("{} says, 'Setting defaults for {} ({}) [#{}] - [{} [#{}] stance]'", GetCleanName(), t.name, t.short_name, t.spell_type, Stance::GetName(bot_stance), bot_stance); + LogBotSettingsDetail("{} says, 'Hold = [{}] | Delay = [{}ms] | MinThreshold = [{}\%] | MaxThreshold = [{}\%]'", GetCleanName(), + GetDefaultSpellTypeHold(i, bot_stance), + GetDefaultSpellTypeDelay(i, bot_stance), + GetDefaultSpellTypeMinThreshold(i, bot_stance), + GetDefaultSpellTypeMaxThreshold(i, bot_stance)); + LogBotSettingsDetail("{} says, 'AggroCheck = [{}] | MinManaPCT = [{}\%] | MaxManaPCT = [{}\%] | MinHPPCT = [{}\% | MaxHPPCT = [{}\%]'", GetCleanName(), GetDefaultSpellTypeAggroCheck(i, bot_stance), GetDefaultSpellTypeMinManaLimit(i, bot_stance), GetDefaultSpellTypeMaxManaLimit(i, bot_stance), GetDefaultSpellTypeMinHPLimit(i, bot_stance), GetDefaultSpellTypeMaxHPLimit(i, bot_stance)); + LogBotSettingsDetail("{} says, 'IdlePriority = [{}] | EngagedPriority = [{}] | PursuePriority = [{}]'", GetCleanName(), GetDefaultSpellTypeIdlePriority(i, GetClass(), bot_stance), GetDefaultSpellTypeEngagedPriority(i, GetClass(), bot_stance), GetDefaultSpellTypePursuePriority(i, GetClass(), bot_stance)); + LogBotSettingsDetail("{} says, 'TargetCount = [{}] | AnnounceCast = [{}]'", GetCleanName(), GetDefaultSpellTypeAEOrGroupTargetCount(i, bot_stance), GetDefaultSpellTypeAnnounceCast(i, bot_stance)); + } +} + +void Bot::SetBotSpellRecastTimer(uint16 spell_type, Mob* tar, bool precast) { + if (!tar) { + return; + } + + if (!precast && BotSpellTypeUsesTargetSettings(spell_type)) { + return; + } + + uint32 added_delay = 0; + + switch (spell_type) { //Additional delays + case BotSpellTypes::Mez: + added_delay = RuleI(Bots, MezSuccessDelay); + break; + case BotSpellTypes::AEMez: + added_delay = RuleI(Bots, AEMezSuccessDelay); + break; + } + + if (tar->IsPet() && tar->GetOwner() && tar->GetOwner()->IsOfClientBot()) { + Mob* owner = tar->GetOwner(); + + if (owner->IsClient()) { + owner->CastToClient()->SetSpellTypeRecastTimer(spell_type, (GetUltimateSpellTypeDelay(spell_type, tar) + added_delay)); + } + else { + owner->CastToBot()->SetSpellTypeRecastTimer(spell_type, (GetUltimateSpellTypeDelay(spell_type, tar) + added_delay)); + } + } + else if (BotSpellTypeUsesTargetSettings(spell_type)) { + if (tar->IsClient()) { + tar->CastToClient()->SetSpellTypeRecastTimer(spell_type, (GetUltimateSpellTypeDelay(spell_type, tar) + added_delay)); + } + else { + tar->CastToBot()->SetSpellTypeRecastTimer(spell_type, (GetUltimateSpellTypeDelay(spell_type, tar) + added_delay)); + } + } + else { + SetSpellTypeRecastTimer(spell_type, (GetUltimateSpellTypeDelay(spell_type, tar) + added_delay)); + } +} + +BotSpell Bot::GetSpellByHealType(uint16 spell_type, Mob* tar) { + if (!TargetValidation(tar)) { + BotSpell result; + + return result; + } + + switch (spell_type) { + case BotSpellTypes::VeryFastHeals: + case BotSpellTypes::PetVeryFastHeals: + return GetBestBotSpellForVeryFastHeal(this, tar, spell_type); + case BotSpellTypes::FastHeals: + case BotSpellTypes::PetFastHeals: + return GetBestBotSpellForFastHeal(this, tar, spell_type); + case BotSpellTypes::RegularHeal: + case BotSpellTypes::PetRegularHeals: + return GetBestBotSpellForRegularSingleTargetHeal(this, tar, spell_type); + case BotSpellTypes::GroupHeals: + return GetBestBotSpellForGroupHeal(this, tar, spell_type); + case BotSpellTypes::CompleteHeal: + case BotSpellTypes::PetCompleteHeals: + return GetBestBotSpellForPercentageHeal(this, tar, spell_type); + case BotSpellTypes::GroupCompleteHeals: + return GetBestBotSpellForGroupCompleteHeal(this, tar, spell_type); + case BotSpellTypes::HoTHeals: + case BotSpellTypes::PetHoTHeals: + 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 + } +} + +uint16 Bot::GetSpellTypePriority(uint16 spell_type, uint8 priority_type) { + switch (priority_type) { + case BotPriorityCategories::Idle: + return m_bot_spell_settings[spell_type].idle_priority; + case BotPriorityCategories::Engaged: + return m_bot_spell_settings[spell_type].engaged_priority; + case BotPriorityCategories::Pursue: + return m_bot_spell_settings[spell_type].pursue_priority; + default: + return 0; + } +} + +int Bot::GetDefaultSetting(uint16 setting_category, uint16 setting_type, uint8 stance) { + switch (setting_category) { + case BotSettingCategories::BaseSetting: + return GetDefaultBotBaseSetting(setting_type, stance); + case BotSettingCategories::SpellHold: + return GetDefaultSpellTypeHold(setting_type, stance); + case BotSettingCategories::SpellDelay: + return GetDefaultSpellTypeDelay(setting_type, stance); + case BotSettingCategories::SpellMinThreshold: + return GetDefaultSpellTypeMinThreshold(setting_type, stance); + case BotSettingCategories::SpellMaxThreshold: + return GetDefaultSpellTypeMaxThreshold(setting_type, stance); + case BotSettingCategories::SpellTypeResistLimit: + return GetDefaultSpellTypeResistLimit(setting_type, stance); + case BotSettingCategories::SpellTypeAggroCheck: + return GetDefaultSpellTypeAggroCheck(setting_type, stance); + case BotSettingCategories::SpellTypeMinManaPct: + return GetDefaultSpellTypeMinManaLimit(setting_type, stance); + case BotSettingCategories::SpellTypeMaxManaPct: + return GetDefaultSpellTypeMaxManaLimit(setting_type, stance); + case BotSettingCategories::SpellTypeMinHPPct: + return GetDefaultSpellTypeMinHPLimit(setting_type, stance); + case BotSettingCategories::SpellTypeMaxHPPct: + return GetDefaultSpellTypeMaxHPLimit(setting_type, stance); + case BotSettingCategories::SpellTypeIdlePriority: + return GetDefaultSpellTypePriority(setting_type, BotPriorityCategories::Idle, GetClass(), stance); + case BotSettingCategories::SpellTypeEngagedPriority: + return GetDefaultSpellTypePriority(setting_type, BotPriorityCategories::Engaged, GetClass(), stance); + case BotSettingCategories::SpellTypePursuePriority: + return GetDefaultSpellTypePriority(setting_type, BotPriorityCategories::Pursue, GetClass(), stance); + case BotSettingCategories::SpellTypeAEOrGroupTargetCount: + return GetDefaultSpellTypeAEOrGroupTargetCount(setting_type, stance); + case BotSettingCategories::SpellTypeAnnounceCast: + return GetDefaultSpellTypeAnnounceCast(setting_type, stance); + default: + return 0; // Default return value for unrecognized categories + } +} + +int Bot::GetSetting(uint16 setting_category, uint16 setting_type) { + switch (setting_category) { + case BotSettingCategories::BaseSetting: + return GetBotBaseSetting(setting_type); + case BotSettingCategories::SpellHold: + return GetSpellTypeHold(setting_type); + case BotSettingCategories::SpellDelay: + return GetSpellTypeDelay(setting_type); + case BotSettingCategories::SpellMinThreshold: + return GetSpellTypeMinThreshold(setting_type); + case BotSettingCategories::SpellMaxThreshold: + return GetSpellTypeMaxThreshold(setting_type); + case BotSettingCategories::SpellTypeResistLimit: + return GetSpellTypeResistLimit(setting_type); + case BotSettingCategories::SpellTypeAggroCheck: + return GetSpellTypeAggroCheck(setting_type); + case BotSettingCategories::SpellTypeMinManaPct: + return GetSpellTypeMinManaLimit(setting_type); + case BotSettingCategories::SpellTypeMaxManaPct: + return GetSpellTypeMaxManaLimit(setting_type); + case BotSettingCategories::SpellTypeMinHPPct: + return GetSpellTypeMinHPLimit(setting_type); + case BotSettingCategories::SpellTypeMaxHPPct: + return GetSpellTypeMaxHPLimit(setting_type); + case BotSettingCategories::SpellTypeIdlePriority: + return GetSpellTypePriority(setting_type, BotPriorityCategories::Idle); + case BotSettingCategories::SpellTypeEngagedPriority: + return GetSpellTypePriority(setting_type, BotPriorityCategories::Engaged); + case BotSettingCategories::SpellTypePursuePriority: + return GetSpellTypePriority(setting_type, BotPriorityCategories::Pursue); + case BotSettingCategories::SpellTypeAEOrGroupTargetCount: + return GetSpellTypeAEOrGroupTargetCount(setting_type); + case BotSettingCategories::SpellTypeAnnounceCast: + return GetSpellTypeAnnounceCast(setting_type); + default: + return 0; // Default return value for unrecognized categories + } +} + +bool Bot::GetDefaultSpellTypeHold(uint16 spell_type, uint8 stance) { + uint8 bot_class = GetClass(); + + switch (spell_type) { + case BotSpellTypes::FastHeals: + case BotSpellTypes::VeryFastHeals: + case BotSpellTypes::Pet: + case BotSpellTypes::Escape: + case BotSpellTypes::Lifetap: + case BotSpellTypes::Buff: + case BotSpellTypes::PetBuffs: + case BotSpellTypes::InCombatBuff: + case BotSpellTypes::PreCombatBuff: + case BotSpellTypes::DamageShields: + case BotSpellTypes::ResistBuffs: + return false; + case BotSpellTypes::GroupCompleteHeals: + case BotSpellTypes::GroupHeals: + case BotSpellTypes::GroupHoTHeals: + case BotSpellTypes::HoTHeals: + case BotSpellTypes::CompleteHeal: + case BotSpellTypes::PetFastHeals: + case BotSpellTypes::PetRegularHeals: + case BotSpellTypes::PetVeryFastHeals: + case BotSpellTypes::RegularHeal: + switch (stance) { + case Stance::Aggressive: + case Stance::AEBurn: + case Stance::Burn: + return true; + default: + return false; + } + case BotSpellTypes::Cure: + case BotSpellTypes::GroupCures: + switch (stance) { + case Stance::Aggressive: + case Stance::AEBurn: + case Stance::Burn: + return true; + default: + return false; + } + case BotSpellTypes::InCombatBuffSong: + case BotSpellTypes::OutOfCombatBuffSong: + case BotSpellTypes::PreCombatBuffSong: + if (bot_class == Class::Bard) { + return false; + } + else { + return true; + } + case BotSpellTypes::Nuke: + case BotSpellTypes::DOT: + case BotSpellTypes::Stun: + switch (stance) { + case Stance::Assist: + return true; + default: + return false; + } + case BotSpellTypes::AENukes: + case BotSpellTypes::AERains: + case BotSpellTypes::AEStun: + case BotSpellTypes::AEDoT: + case BotSpellTypes::AELifetap: + case BotSpellTypes::PBAENuke: + switch (stance) { + case Stance::AEBurn: + return false; + default: + return true; + } + case BotSpellTypes::Mez: + case BotSpellTypes::AEMez: + case BotSpellTypes::Debuff: + case BotSpellTypes::AEDebuff: + case BotSpellTypes::Slow: + case BotSpellTypes::AESlow: + case BotSpellTypes::HateRedux: + switch (stance) { + case Stance::AEBurn: + case Stance::Burn: + case Stance::Assist: + return true; + default: + return false; + } + case BotSpellTypes::Snare: + switch (stance) { + case Stance::AEBurn: + case Stance::Burn: + case Stance::Assist: + return true; + default: + return false; + } + case BotSpellTypes::HateLine: + if (bot_class == Class::ShadowKnight || bot_class == Class::Paladin) { + switch (stance) { + case Stance::Aggressive: + return false; + default: + return true; + } + } + else { + return true; + } + case BotSpellTypes::Charm: + case BotSpellTypes::Resurrect: + case BotSpellTypes::AESnare: + case BotSpellTypes::AERoot: + case BotSpellTypes::Root: + case BotSpellTypes::AEDispel: + case BotSpellTypes::Dispel: + case BotSpellTypes::AEFear: + case BotSpellTypes::Fear: + case BotSpellTypes::AEHateLine: + case BotSpellTypes::PetCures: + case BotSpellTypes::PetHoTHeals: + case BotSpellTypes::PetCompleteHeals: + case BotSpellTypes::PetDamageShields: + case BotSpellTypes::PetResistBuffs: + default: + return true; + } +} + +uint16 Bot::GetDefaultSpellTypePriority(uint16 spell_type, uint8 priority_type, uint8 bot_class, uint8 stance) { + switch (priority_type) { + case BotPriorityCategories::Idle: + return GetDefaultSpellTypeIdlePriority(spell_type, bot_class, stance); + case BotPriorityCategories::Engaged: + return GetDefaultSpellTypeEngagedPriority(spell_type, bot_class, stance); + case BotPriorityCategories::Pursue: + return GetDefaultSpellTypePursuePriority(spell_type, bot_class, stance); + default: + return 0; + } +} + +uint16 Bot::GetDefaultSpellTypeIdlePriority(uint16 spell_type, uint8 bot_class, uint8 stance) { + if (!IsBotSpellTypeBeneficial(spell_type)) { + return 0; + } + + switch (spell_type) { + case BotSpellTypes::VeryFastHeals: + return 1; + case BotSpellTypes::FastHeals: + return 2; + case BotSpellTypes::GroupHeals: + return 3; + case BotSpellTypes::RegularHeal: + return 4; + case BotSpellTypes::GroupCompleteHeals: + return 5; + case BotSpellTypes::CompleteHeal: + return 6; + case BotSpellTypes::GroupHoTHeals: + return 7; + case BotSpellTypes::HoTHeals: + return 8; + case BotSpellTypes::GroupCures: + return 9; + case BotSpellTypes::Cure: + return 10; + case BotSpellTypes::PetVeryFastHeals: + return 11; + case BotSpellTypes::PetFastHeals: + return 12; + case BotSpellTypes::PetRegularHeals: + return 13; + case BotSpellTypes::PetCompleteHeals: + return 14; + case BotSpellTypes::PetHoTHeals: + return 15; + case BotSpellTypes::PetCures: + return 16; + case BotSpellTypes::Pet: + return 17; + case BotSpellTypes::Buff: + return 18; + case BotSpellTypes::OutOfCombatBuffSong: + return 19; + case BotSpellTypes::ResistBuffs: + return 20; + case BotSpellTypes::DamageShields: + return 21; + case BotSpellTypes::PetBuffs: + return 22; + case BotSpellTypes::PreCombatBuff: + return 23; + case BotSpellTypes::PreCombatBuffSong: + return 24; + case BotSpellTypes::PetResistBuffs: + return 25; + case BotSpellTypes::PetDamageShields: + return 26; + default: + return 0; //unused + } + + return 0; +} + +uint16 Bot::GetDefaultSpellTypeEngagedPriority(uint16 spell_type, uint8 bot_class, uint8 stance) { + switch (spell_type) { + case BotSpellTypes::Escape: + return 1; + case BotSpellTypes::VeryFastHeals: + return 2; + case BotSpellTypes::FastHeals: + return 3; + case BotSpellTypes::GroupHeals: + return 4; + case BotSpellTypes::RegularHeal: + return 5; + case BotSpellTypes::GroupCompleteHeals: + return 6; + case BotSpellTypes::CompleteHeal: + return 7; + case BotSpellTypes::GroupHoTHeals: + return 8; + case BotSpellTypes::HoTHeals: + return 9; + case BotSpellTypes::GroupCures: + return 10; + case BotSpellTypes::Cure: + return 11; + case BotSpellTypes::PetVeryFastHeals: + return 12; + case BotSpellTypes::PetFastHeals: + return 13; + case BotSpellTypes::PetRegularHeals: + return 14; + case BotSpellTypes::PetCompleteHeals: + return 15; + case BotSpellTypes::PetHoTHeals: + return 16; + case BotSpellTypes::PetCures: + return 17; + case BotSpellTypes::AELifetap: + return 18; + case BotSpellTypes::Lifetap: + return 19; + case BotSpellTypes::HateRedux: + return 20; + case BotSpellTypes::AEMez: + return 21; + case BotSpellTypes::Mez: + return 22; + case BotSpellTypes::AEHateLine: + return 23; + case BotSpellTypes::HateLine: + return 24; + case BotSpellTypes::AEDispel: + return 25; + case BotSpellTypes::Dispel: + return 26; + case BotSpellTypes::AEDebuff: + return 27; + case BotSpellTypes::Debuff: + return 28; + case BotSpellTypes::AESnare: + return 29; + case BotSpellTypes::Snare: + return 30; + case BotSpellTypes::AESlow: + return 31; + case BotSpellTypes::Slow: + return 32; + case BotSpellTypes::AERoot: + return 33; + case BotSpellTypes::Root: + return 34; + case BotSpellTypes::AEDoT: + return 35; + case BotSpellTypes::DOT: + return 36; + case BotSpellTypes::AEStun: + return 37; + case BotSpellTypes::PBAENuke: + return 38; + case BotSpellTypes::AENukes: + return 39; + case BotSpellTypes::AERains: + return 40; + case BotSpellTypes::Stun: + return 41; + case BotSpellTypes::Nuke: + return 42; + case BotSpellTypes::InCombatBuff: + return 43; + case BotSpellTypes::InCombatBuffSong: + return 44; + default: + return 0; //unused + } +} + +uint16 Bot::GetDefaultSpellTypePursuePriority(uint16 spell_type, uint8 bot_class, uint8 stance) { + switch (spell_type) { + case BotSpellTypes::Escape: + return 1; + case BotSpellTypes::VeryFastHeals: + return 2; + case BotSpellTypes::FastHeals: + return 3; + case BotSpellTypes::GroupHeals: + return 4; + case BotSpellTypes::RegularHeal: + return 5; + case BotSpellTypes::GroupCompleteHeals: + return 6; + case BotSpellTypes::CompleteHeal: + return 7; + case BotSpellTypes::GroupHoTHeals: + return 8; + case BotSpellTypes::HoTHeals: + return 9; + case BotSpellTypes::GroupCures: + return 10; + case BotSpellTypes::Cure: + return 11; + case BotSpellTypes::Snare: + return 12; + case BotSpellTypes::Lifetap: + return 13; + case BotSpellTypes::Dispel: + return 14; + case BotSpellTypes::Stun: + return 15; + case BotSpellTypes::Nuke: + return 16; + case BotSpellTypes::DOT: + return 17; + case BotSpellTypes::PetVeryFastHeals: + return 18; + case BotSpellTypes::PetFastHeals: + return 19; + case BotSpellTypes::PetRegularHeals: + return 20; + case BotSpellTypes::PetCompleteHeals: + return 21; + case BotSpellTypes::PetHoTHeals: + return 22; + case BotSpellTypes::PetCures: + return 23; + default: + return 0; //unused + } +} + +uint16 Bot::GetDefaultSpellTypeResistLimit(uint16 spell_type, uint8 stance) { + return IsBotSpellTypeBeneficial(spell_type) ? 0 : RuleI(Bots, SpellResistLimit); +} + +bool Bot::GetDefaultSpellTypeAggroCheck(uint16 spell_type, uint8 stance) { + switch (stance) { + case Stance::AEBurn: + case Stance::Burn: + case Stance::Aggressive: + return false; + default: + break; + } + + switch (spell_type) { + case BotSpellTypes::AEDebuff: + case BotSpellTypes::AEDispel: + case BotSpellTypes::AEDoT: + case BotSpellTypes::AEFear: + case BotSpellTypes::AEHateLine: + case BotSpellTypes::AELifetap: + case BotSpellTypes::AENukes: + case BotSpellTypes::AERains: + case BotSpellTypes::AERoot: + case BotSpellTypes::AESlow: + case BotSpellTypes::AESnare: + case BotSpellTypes::AEStun: + case BotSpellTypes::DOT: + case BotSpellTypes::Debuff: + case BotSpellTypes::Dispel: + case BotSpellTypes::Fear: + case BotSpellTypes::HateLine: + case BotSpellTypes::Nuke: + case BotSpellTypes::PBAENuke: + case BotSpellTypes::Root: + case BotSpellTypes::Slow: + case BotSpellTypes::Snare: + case BotSpellTypes::Stun: + return true; + default: + return false; + } +} + +uint8 Bot::GetDefaultSpellTypeMinManaLimit(uint16 spell_type, uint8 stance) { + return 0; +} + +uint8 Bot::GetDefaultSpellTypeMaxManaLimit(uint16 spell_type, uint8 stance) { + switch (spell_type) { + case BotSpellTypes::InCombatBuff: + if (GetClass() == Class::Shaman) { + return 75; + } + + break; + default: + break; + } + + return 100; +} + +uint8 Bot::GetDefaultSpellTypeMinHPLimit(uint16 spell_type, uint8 stance) { + switch (spell_type) { + case BotSpellTypes::InCombatBuff: + if (GetClass() == Class::Shaman) { + return 40; + } + + break; + default: + break; + } + + return 0; +} + +uint8 Bot::GetDefaultSpellTypeMaxHPLimit(uint16 spell_type, uint8 stance) { + return 100; +} + +uint16 Bot::GetDefaultSpellTypeAEOrGroupTargetCount(uint16 spell_type, uint8 stance) { + if (IsAEBotSpellType(spell_type)) { + return RuleI(Bots, MinTargetsForAESpell); + } + + if (IsGroupBotSpellType(spell_type)) { + return RuleI(Bots, MinTargetsForGroupSpell); + } + + return 1; +} + +uint16 Bot::GetDefaultSpellTypeAnnounceCast(uint16 spell_type, uint8 stance) { + switch (GetClass()) { + case Class::Bard: + switch (spell_type) { + case BotSpellTypes::Mez: + case BotSpellTypes::AEMez: + case BotSpellTypes::Cure: + case BotSpellTypes::GroupCures: + case BotSpellTypes::PetCures: + case BotSpellTypes::Charm: + return 1; + default: + return 0; + } + + return 1; + default: + return 1; + } + + return 1; +} + +bool Bot::GetUltimateSpellTypeHold(uint16 spell_type, Mob* tar) { + if (!TargetValidation(tar)) { + return GetSpellTypeHold(spell_type); + } + + Mob* owner = tar->IsPet() ? tar->GetOwner() : nullptr; + Bot* bot_owner = (owner && owner->IsBot()) + ? owner->CastToBot() + : nullptr; + + return bot_owner + ? bot_owner->GetSpellTypeHold(GetPetBotSpellType(spell_type)) + : GetSpellTypeHold(spell_type); +} + +void Bot::SetSpellTypePriority(uint16 spell_type, uint8 priority_type, uint16 priority) { + switch (priority_type) { + case BotPriorityCategories::Idle: + m_bot_spell_settings[spell_type].idle_priority = priority; + break; + case BotPriorityCategories::Engaged: + m_bot_spell_settings[spell_type].engaged_priority = priority; + break; + case BotPriorityCategories::Pursue: + m_bot_spell_settings[spell_type].pursue_priority = priority; + break; + default: + return; + } +} + +std::list Bot::GetSpellTypesPrioritized(uint8 priority_type) { + std::list cast_order; + + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; i++) { + BotSpellTypeOrder typeSettings = { + .spellType = i, + .priority = GetSpellTypePriority(i, priority_type) + }; + + if (typeSettings.priority != 0) { + cast_order.emplace_back(typeSettings); + } + } + + if (cast_order.size() > 1) { + cast_order.sort( + [](BotSpellTypeOrder const& l, BotSpellTypeOrder const& r) { + return l.priority < r.priority; + } + ); + } + + return cast_order; +} + +bool Bot::AttemptAICastSpell(uint16 spell_type, Mob* tar) { + bool result = false; + + if (!tar) { + if (GetTarget()) { + tar = GetTarget(); + } + else { + if (!IsBotSpellTypeBeneficial(spell_type)) { + return result; + } + } + } + + if (!IsTaunting() && !IsCommandedSpell() && GetSpellTypeAggroCheck(spell_type) && HasOrMayGetAggro(IsSitting())) { + LogBotSpellChecksDetail("{} says, 'Cancelling cast of [{}] due to GetSpellTypeAggroCheck and HasOrMayGetAggro.'", GetCleanName(), GetSpellTypeNameByID(spell_type)); + return result; + } + + if (IsBotSpellTypeBeneficial(spell_type)) { + if (GetClass() == Class::Bard) { + tar = this; + } + + if (!PrecastChecks(tar, spell_type) || !AICastSpell(tar, GetChanceToCastBySpellType(spell_type), spell_type)) { + return result; + } + } + else { + if (!PrecastChecks(tar, spell_type) || !AICastSpell(tar, GetChanceToCastBySpellType(spell_type), spell_type)) { + return result; + } + } + + result = true; + + return result; +} + +bool Bot::AttemptAACastSpell(Mob* tar, uint16 spell_id, AA::Rank* rank) { + if (!IsValidSpell(spell_id)) { + return false; + } + + SPDat_Spell_Struct spell = spells[spell_id]; + + if (!tar || spell.target_type == ST_Self) { + tar = this; + } + + if (!DoLosChecks(tar)) { + return false; + } + + if (CheckSpellRecastTimer(spell_id)) { + if (IsBeneficialSpell(spell_id)) { + if ( + (tar->IsNPC() && !tar->GetOwner()) || + (tar->GetOwner() && tar->GetOwner()->IsOfClientBot() && !GetOwner()->IsInGroupOrRaid(tar->GetOwner())) || + (tar->IsOfClientBot() && !GetOwner()->IsInGroupOrRaid(tar)) + ) { + GetBotOwner()->Message(Chat::Yellow, "[%s] is an invalid target. Only players or their pet in your group or raid are eligible targets.", tar->GetCleanName()); + + return false; + } + } + + if (IsDetrimentalSpell(spell_id) && !IsAttackAllowed(tar)) { + GetBotOwner()->Message(Chat::Yellow, "%s says, 'I cannot attack [%s]'.", GetCleanName(), tar->GetCleanName()); + + return false; + } + + if (!CastChecks(spell_id, tar, UINT16_MAX)) { + GetBotOwner()->Message( + Chat::Red, + fmt::format( + "{} says, 'Ability failed to cast. This could be due to any number of things: range, mana, line of sight, immune, target type, etc.'", + GetCleanName() + ).c_str() + ); + + return false; + } + + + if (IsCasting()) { + RaidGroupSay( + fmt::format( + "Interrupting {}. I have been commanded to try to cast an AA - {} on {}.", + CastingSpellID() ? spells[CastingSpellID()].name : "my spell", + spell.name, + tar->GetCleanName() + ).c_str() + ); + + InterruptSpell(); + } + + if (CastSpell(spell_id, tar->GetID())) { + RaidGroupSay( + fmt::format( + "Casting an AA - {} on {}.", + GetSpellName(spell_id), + (tar == this ? "myself" : tar->GetCleanName()) + ).c_str() + ); + + int timer_duration = (rank->recast_time - GetAlternateAdvancementCooldownReduction(rank)) * 1000; + + if (timer_duration < 0) { + timer_duration = 0; + } + + SetSpellRecastTimer(spell_id, timer_duration); + } + else { + GetBotOwner()->Message( + Chat::Red, + fmt::format( + "{} says, 'Ability failed to cast. This could be due to any number of things: range, mana, line of sight, immune, target type, etc.'", + GetCleanName() + ).c_str() + ); + + return false; + } + } + else { + GetBotOwner()->Message( + Chat::Yellow, + fmt::format( + "{} says, 'Ability recovery time not yet met. {} remaining.'", + GetCleanName(), + Strings::SecondsToTime(GetSpellRecastRemainingTime(spell_id), true) + ).c_str() + ); + + return false; + } + + return true; +} + +bool Bot::AttemptForcedCastSpell(Mob* tar, uint16 spell_id, bool is_disc) { + if (!IsValidSpell(spell_id)) { + return false; + } + + SPDat_Spell_Struct spell = spells[spell_id]; + + if (!tar || (spell.target_type == ST_Self && tar != this)) { + tar = this; + } + + if ((IsCharmSpell(spell_id) || (IsPetSpell(spell_id) && HasPet()))) { + return false; + } + + if (IsResurrectSpell(spell_id) && (!tar->IsCorpse() || !tar->CastToCorpse()->IsPlayerCorpse())) { + return false; + } + + if (IsBeneficialSpell(spell_id)) { + bool invalid_beneficial_target = + (tar->IsNPC() && !tar->GetOwner()) || + (tar->GetOwner() && tar->GetOwner()->IsOfClientBot() && !GetBotOwner()->IsInGroupOrRaid(tar->GetOwner())) || + (tar->IsOfClientBot() && !GetBotOwner()->IsInGroupOrRaid(tar)); + + if (invalid_beneficial_target) { + GetBotOwner()->Message( + Chat::Yellow, + fmt::format("[{}] is an invalid target. Only players or their pet in your group or raid are eligible targets.", tar->GetCleanName()).c_str() + ); + return false; + } + } + + if (IsDetrimentalSpell(spell_id) && (!GetBotOwner()->IsAttackAllowed(tar) || !IsAttackAllowed(tar))) { + GetBotOwner()->Message( + Chat::Yellow, + fmt::format("{} says, 'I cannot attack [{}]'.", GetCleanName(), tar->GetCleanName()).c_str() + ); + return false; + } + + if (!is_disc && !CheckSpellRecastTimer(spell_id)) { + return false; + } + + if (!IsInGroupOrRaid(tar, true) && + (!RuleB(Bots, EnableBotTGB) || (IsGroupSpell(spell_id) && !IsTGBCompatibleSpell(spell_id)))) { + return false; + } + + if (!HasLoS() && !DoLosChecks(tar)) { + return false; + } + + if (!CastChecks(spell_id, tar, UINT16_MAX)) { + GetBotOwner()->Message( + Chat::Red, + fmt::format( + "{} says, 'Ability failed to cast. This could be due to any number of things: range, mana, line of sight, immune, target type, etc.'", + GetCleanName() + ).c_str() + ); + + return false; + } + + if (IsCasting()) { + RaidGroupSay( + fmt::format( + "Interrupting {}. I have been commanded to try to cast {} on {}.", + CastingSpellID() ? spells[CastingSpellID()].name : "my spell", + spell.name, + tar->GetCleanName() + ).c_str() + ); + InterruptSpell(); + } + + if (!CastSpell(spell_id, tar->GetID())) { + return false; + } + + RaidGroupSay( + fmt::format( + "Casting {} on {}.", + GetSpellName(spell_id), + (tar == this ? "myself" : tar->GetCleanName()) + ).c_str() + ); + + int timer_duration = 0; + + if (!is_disc) { + timer_duration = CalcBuffDuration(tar, this, spell_id); + timer_duration = std::max(0, GetActSpellDuration(spell_id, timer_duration)); + SetSpellRecastTimer(spell_id, timer_duration); + } + else if (spell.recast_time > 0) { + timer_duration = std::max(0, int(spell.recast_time / 1000 - GetFocusEffect(focusReduceRecastTime, spell_id))); + SetDisciplineReuseTimer(spell_id, timer_duration * 1000); + } + + return true; +} + +uint16 Bot::GetParentSpellType(uint16 spell_type) { + switch (spell_type) { + case BotSpellTypes::AENukes: + case BotSpellTypes::AERains: + case BotSpellTypes::AEStun: + case BotSpellTypes::PBAENuke: + case BotSpellTypes::Nuke: + case BotSpellTypes::Stun: + return BotSpellTypes::Nuke; + case BotSpellTypes::RegularHeal: + case BotSpellTypes::CompleteHeal: + case BotSpellTypes::GroupCompleteHeals: + case BotSpellTypes::FastHeals: + case BotSpellTypes::VeryFastHeals: + case BotSpellTypes::GroupHeals: + case BotSpellTypes::GroupHoTHeals: + case BotSpellTypes::HoTHeals: + case BotSpellTypes::PetRegularHeals: + case BotSpellTypes::PetCompleteHeals: + case BotSpellTypes::PetFastHeals: + case BotSpellTypes::PetVeryFastHeals: + case BotSpellTypes::PetHoTHeals: + return BotSpellTypes::RegularHeal; + case BotSpellTypes::Buff: + case BotSpellTypes::PetBuffs: + case BotSpellTypes::DamageShields: + case BotSpellTypes::PetDamageShields: + case BotSpellTypes::ResistBuffs: + case BotSpellTypes::PetResistBuffs: + case BotSpellTypes::BindAffinity: + case BotSpellTypes::Identify: + case BotSpellTypes::Levitate: + case BotSpellTypes::Rune: + case BotSpellTypes::WaterBreathing: + case BotSpellTypes::Size: + case BotSpellTypes::Invisibility: + case BotSpellTypes::MovementSpeed: + return BotSpellTypes::Buff; + case BotSpellTypes::AEMez: + case BotSpellTypes::Mez: + return BotSpellTypes::Mez; + case BotSpellTypes::AEDebuff: + case BotSpellTypes::Debuff: + return BotSpellTypes::Debuff; + case BotSpellTypes::AESlow: + case BotSpellTypes::Slow: + return BotSpellTypes::Slow; + case BotSpellTypes::AESnare: + case BotSpellTypes::Snare: + return BotSpellTypes::Snare; + case BotSpellTypes::AEFear: + case BotSpellTypes::Fear: + return BotSpellTypes::Fear; + case BotSpellTypes::GroupCures: + case BotSpellTypes::Cure: + case BotSpellTypes::PetCures: + return BotSpellTypes::Cure; + case BotSpellTypes::AERoot: + case BotSpellTypes::Root: + return BotSpellTypes::Root; + case BotSpellTypes::AEDispel: + case BotSpellTypes::Dispel: + return BotSpellTypes::Dispel; + case BotSpellTypes::AEDoT: + case BotSpellTypes::DOT: + return BotSpellTypes::DOT; + case BotSpellTypes::AELifetap: + case BotSpellTypes::Lifetap: + return BotSpellTypes::Lifetap; + case BotSpellTypes::AELull: + case BotSpellTypes::Lull: + return BotSpellTypes::Lull; + case BotSpellTypes::HateLine: + case BotSpellTypes::AEHateLine: + return BotSpellTypes::HateLine; + case BotSpellTypes::Charm: + case BotSpellTypes::Escape: + case BotSpellTypes::HateRedux: + case BotSpellTypes::InCombatBuff: + case BotSpellTypes::InCombatBuffSong: + case BotSpellTypes::OutOfCombatBuffSong: + case BotSpellTypes::Pet: + case BotSpellTypes::PreCombatBuff: + case BotSpellTypes::PreCombatBuffSong: + case BotSpellTypes::Resurrect: + default: + return spell_type; + } + + return spell_type; +} + +bool Bot::IsValidBotSpellType(uint16 spell_type) { + return ( + EQ::ValueWithin(spell_type, BotSpellTypes::START, BotSpellTypes::END) || + EQ::ValueWithin(spell_type, BotSpellTypes::COMMANDED_START, BotSpellTypes::COMMANDED_END) + ); +} + +bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) { + if (IsAEBotSpellType(spell_type) && !IsAnyAESpell(spell_id)) { + return false; + } + + if (IsGroupBotSpellType(spell_type) && !IsGroupSpell(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::Nuke: + if (IsAnyNukeOrStunSpell(spell_id) && !IsStunSpell(spell_id)) { + 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, SpellEffect::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, SpellEffect::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, SpellEffect::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, SpellEffect::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) && !IsEffectInSpell(spell_id, SpellEffect::DamageShield)) { + return true; + } + + return false; + case BotSpellTypes::DamageShields: + case BotSpellTypes::PetDamageShields: + if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SpellEffect::DamageShield)) { + return true; + } + + return false; + case BotSpellTypes::Teleport: + if ( + IsBeneficialSpell(spell_id) && + (IsEffectInSpell(spell_id, SpellEffect::Teleport) || IsEffectInSpell(spell_id, SpellEffect::Translocate)) + ) { + return true; + } + + return false; + case BotSpellTypes::Lull: + case BotSpellTypes::AELull: + if (IsHarmonySpell(spell_id)) { + return true; + } + + return false; + case BotSpellTypes::Succor: + if ( + IsBeneficialSpell(spell_id) && + IsEffectInSpell(spell_id, SpellEffect::Succor) + ) { + return true; + } + + return false; + case BotSpellTypes::BindAffinity: + if (IsEffectInSpell(spell_id, SpellEffect::BindAffinity)) { + return true; + } + + return false; + case BotSpellTypes::Identify: + if (IsEffectInSpell(spell_id, SpellEffect::Identify)) { + return true; + } + + return false; + case BotSpellTypes::Levitate: + if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SpellEffect::Levitate)) { + return true; + } + + return false; + case BotSpellTypes::Rune: + if (IsBeneficialSpell(spell_id) && + (IsEffectInSpell(spell_id, SpellEffect::AbsorbMagicAtt) || IsEffectInSpell(spell_id, SpellEffect::Rune)) + ) { + return true; + } + + return false; + case BotSpellTypes::WaterBreathing: + if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SpellEffect::WaterBreathing)) { + return true; + } + + return false; + case BotSpellTypes::Size: + if ( + IsBeneficialSpell(spell_id) && + (IsEffectInSpell(spell_id, SpellEffect::ModelSize) || IsEffectInSpell(spell_id, SpellEffect::ChangeHeight)) + ) { + return true; + } + + return false; + case BotSpellTypes::Invisibility: + if (IsBeneficialSpell(spell_id) && + (IsEffectInSpell(spell_id, SpellEffect::SeeInvis) ||IsInvisibleSpell(spell_id)) + ) { + return true; + } + + return false; + case BotSpellTypes::MovementSpeed: + if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SpellEffect::MovementSpeed)) { + return true; + } + + return false; + case BotSpellTypes::SendHome: + if ( + IsBeneficialSpell(spell_id) && + ( + IsEffectInSpell(spell_id, SpellEffect::GateToHomeCity) || + ( + teleport_zone.compare("") && + (IsEffectInSpell(spell_id, SpellEffect::Teleport) || IsEffectInSpell(spell_id, SpellEffect::Translocate)) + ) + ) + ) { + return true; + } + + return false; + case BotSpellTypes::SummonCorpse: + if (IsEffectInSpell(spell_id, SpellEffect::SummonCorpse)) { + return true; + } + + return false; + default: + return true; + } + + return true; +} + +void Bot::SetCastedSpellType(uint16 spell_type) { + _castedSpellType = spell_type; +} + +void Bot::RunToGoalWithJitter(glm::vec3 Goal) { + RunTo(Goal.x, Goal.y, Goal.z); + SetCombatJitter(); +} + +void Bot::SetCombatJitter() { + if (RuleI(Bots, MaxJitterTimer) > 0) { + m_combat_jitter_timer.Start(zone->random.Int(RuleI(Bots, MinJitterTimer), RuleI(Bots, MaxJitterTimer)), true); + } +} + +bool Bot::DoCombatPositioning(const CombatPositioningInput& input) +{ + bool adjustment_needed = false; + bool is_too_close = input.tar_distance < input.melee_distance_min; + bool los_adjust = !HasRequiredLoSForPositioning(input.tar); + bool behind_mob_set = !input.stop_melee_level && + !IsBotRanged() && + GetBehindMob(); // Don't want casters or ranged to find positions behind the target. + bool adjustment_allowed = !IsMoving() && + m_combat_jitter_timer.Check() && + (!spellend_timer.Enabled() || GetClass() == Class::Bard); + + + if (!IsMoving() && !IsSitting() && !IsFacingMob(input.tar)) { + FaceTarget(input.tar); + } + + FindPositionInput find_position_input = { + .tar = input.tar, + .distance_min = input.melee_distance_min, + .distance_max = input.melee_distance_max, + .behind_only = behind_mob_set, + .front_only = IsTaunting(), + .bypass_los = false, + }; + + bool is_melee = (!input.stop_melee_level && !IsBotRanged()); + + if (input.tar->IsRooted() && !IsTaunting()) { // Move non-taunting melee out of range + adjustment_needed = + (input.tar_distance <= input.melee_distance_max) && + HasTargetReflection(); + + if (adjustment_needed && adjustment_allowed) { + find_position_input.distance_min = input.melee_distance_max + 1; + find_position_input.distance_max = input.melee_distance_max * 1.25f; + + PlotBotPositionAroundTarget(find_position_input); + } + } else { + if (input.tar->IsFeared()) { + adjustment_needed = los_adjust; + + if (adjustment_needed && adjustment_allowed) { + find_position_input.distance_min = input.melee_distance_min; + find_position_input.distance_max = input.melee_distance; + find_position_input.behind_only = false; + find_position_input.front_only = false; + + PlotBotPositionAroundTarget(find_position_input); + } + } + else if (IsTaunting() || HasTargetReflection()) { // Taunting/Aggro adjustments + adjustment_needed = + (IsTaunting() && is_too_close) || + los_adjust || + (is_melee && !input.front_mob); + + if (adjustment_needed && adjustment_allowed) { + find_position_input.distance_min = input.melee_distance_min; + find_position_input.distance_max = input.melee_distance; + find_position_input.behind_only = false; + find_position_input.front_only = true; + + PlotBotPositionAroundTarget(find_position_input); + } + } else { + if (input.tar->IsEnraged() && is_melee) { // Move non-taunting melee bots behind target during enrage + adjustment_needed = + !behind_mob_set || + is_too_close || + los_adjust; + + if (adjustment_needed && adjustment_allowed) { + find_position_input.distance_min = input.melee_distance_min; + find_position_input.distance_max = input.melee_distance; + find_position_input.behind_only = true; + find_position_input.front_only = false; + + PlotBotPositionAroundTarget(find_position_input); + } + } else { // Regular adjustments + adjustment_needed = + is_too_close || + los_adjust || + (behind_mob_set && !input.behind_mob); + + if (adjustment_needed && adjustment_allowed) { + find_position_input.distance_min = input.melee_distance_min; + find_position_input.distance_max = input.melee_distance; + + PlotBotPositionAroundTarget(find_position_input); + } + } + } + } + + if (!adjustment_needed && IsMoving()) { + StopMoving(); + } + + return adjustment_needed; +} + +bool Bot::PlotBotPositionAroundTarget(const FindPositionInput& input) { + bool Result = false; + + if (input.tar) { + glm::vec3 temp_goal(0, 0, input.tar->GetZ()); + glm::vec3 tar_position(input.tar->GetX(), input.tar->GetY(), input.tar->GetZ()); + float look_heading = 0; + float best_z = 0; + float tar_distance = 0; + float desired_angle = 0; + const float offset = GetZOffset(); + const uint16 max_iterations_allowed = 50; + uint16 counter = 0; + + while (counter < max_iterations_allowed) { + temp_goal.x = tar_position.x + zone->random.Real(-input.distance_max, input.distance_max); + temp_goal.y = tar_position.y + zone->random.Real(-input.distance_max, input.distance_max); + best_z = GetFixedZ(temp_goal); + + if (best_z != BEST_Z_INVALID) { + temp_goal.z = best_z; + } + else { + counter++; + + continue; + } + + tar_distance = Distance(input.tar->GetPosition(), temp_goal); + + if (tar_distance > input.distance_max || tar_distance < std::max(input.distance_min, (input.distance_max * 0.75f))) { + counter++; + + continue; + } + + if (input.front_only && !InFrontMob(input.tar, temp_goal.x, temp_goal.y)) { + counter++; + + continue; + } + else if (input.behind_only && !BehindMob(input.tar, temp_goal.x, temp_goal.y)) { + counter++; + + continue; + } + + if (!input.bypass_los && CastToBot()->RequiresLoSForPositioning() && !CheckPositioningLosFN(input.tar, temp_goal.x, temp_goal.y, temp_goal.z)) { + counter++; + + continue; + } + + Result = true; + break; + } + + if (Result) { + RunToGoalWithJitter(temp_goal); + } + } + + return Result; +} + +bool Bot::CheckDoubleRangedAttack() { + int32 chance = spellbonuses.DoubleRangedAttack + itembonuses.DoubleRangedAttack + aabonuses.DoubleRangedAttack; + + return ( + chance && + zone->random.Roll(chance) + ); +} + +bool Bot::RequiresLoSForPositioning() { + if (GetLevel() < GetStopMeleeLevel()) { + return true; + } + + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + if (IsHealBotSpellType(i) || i == BotSpellTypes::PBAENuke) { + continue; + } + + if (!GetSpellTypeHold(i)) { + return true; + } + } + + return false; +} + +bool Bot::HasRequiredLoSForPositioning(Mob* tar) { + if (!tar) { + return true; + } + + if (RequiresLoSForPositioning() && !HasLoS()) { + return false; + } + + return true; +} + +bool Bot::HasValidAETarget(Bot* caster, uint16 spell_id, uint16 spell_type, Mob* tar) { + int spell_range = caster->GetActSpellRange(spell_id, spells[spell_id].range); + int spell_ae_range = caster->GetAOERange(spell_id); + int target_count = 0; + + for (auto& close_mob : caster->m_close_mobs) { + Mob* m = close_mob.second; + + switch (spell_type) { + case BotSpellTypes::AELull: + if (m->GetSpecialAbility(SpecialAbility::PacifyImmunity)) { + continue; + } + + break; + case BotSpellTypes::AEMez: + if (m->GetSpecialAbility(SpecialAbility::MesmerizeImmunity)) { + continue; + } + + break; + case BotSpellTypes::AEDispel: + if (m->GetSpecialAbility(SpecialAbility::DispellImmunity)) { + continue; + } + + break; + case BotSpellTypes::AEFear: + if (m->GetSpecialAbility(SpecialAbility::FearImmunity)) { + continue; + } + + break; + case BotSpellTypes::AESnare: + if (m->GetSpecialAbility(SpecialAbility::SnareImmunity)) { + continue; + } + + break; + case BotSpellTypes::AESlow: + if (m->GetSpecialAbility(SpecialAbility::SlowImmunity)) { + continue; + } + + break; + case BotSpellTypes::AEHateLine: + if (!m->IsNPC()) { + continue; + } + + break; + default: + break; + } + + if ( + !m->IsNPC() || + ( + !IsCommandedSpell() && + !m->CastToNPC()->IsOnHatelist(caster->GetOwner()) + ) + ) { + continue; + } + + if (SpellBreaksMez(spell_id) && m->IsMezzed()) { + continue; + } + + if (IsPBAESpell(spell_id)) { + if ( + spell_ae_range >= Distance(caster->GetPosition(), m->GetPosition()) && + caster->CastChecks(spell_id, m, spell_type, true, true) + ) { + ++target_count; + } + } + else { + if ( + !tar || + spell_range < Distance(caster->GetPosition(), tar->GetPosition()) || + !DoLosChecks(m) + ) { + continue; + } + + if ( + spell_ae_range >= Distance(tar->GetPosition(), m->GetPosition()) && + caster->CastChecks(spell_id, m, spell_type, true, true) + ) { + ++target_count; + } + } + } + + if (target_count < caster->GetSpellTypeAEOrGroupTargetCount(spell_type)) { + return false; + } + + return true; +} + +void Bot::CopyBotSpellSettings(Bot* to) +{ + if (!to) { + return; + } + + to->ResetBotSpellSettings(); + to->bot_spell_settings.clear(); + + auto s = BotSpellSettingsRepository::GetWhere(content_db, fmt::format("bot_id = {}", GetBotID())); + if (s.empty()) { + return; + } + + auto* spell_list = content_db.GetBotSpells(to->GetBotSpellID()); + + for (const auto& e : s) { + BotSpellSetting b; + + b.priority = e.priority; + b.min_hp = e.min_hp; + b.max_hp = e.max_hp; + b.is_enabled = e.is_enabled; + + if (IsSpellInBotList(spell_list, e.spell_id)) { + for (auto& se : spell_list->entries) { + if (se.spellid == e.spell_id) { + if (EQ::ValueWithin(to->GetLevel(), se.minlevel, se.maxlevel) && se.spellid) { + to->AddBotSpellSetting(e.spell_id, &b); + } + } + } + } + } + + to->LoadBotSpellSettings(); + to->AI_AddBotSpells(to->GetBotSpellID()); + to->SetBotEnforceSpellSetting(GetBotEnforceSpellSetting()); +} + +void Bot::ResetBotSpellSettings() +{ + auto s = BotSpellSettingsRepository::GetWhere(content_db, fmt::format("bot_id = {}", GetBotID())); + if (s.empty()) { + return; + } + + for (const auto& e : s) { + DeleteBotSpellSetting(e.spell_id); + } + + LoadBotSpellSettings(); + AI_AddBotSpells(GetBotSpellID()); + SetBotEnforceSpellSetting(false); +} + +void Bot::CopyBotBlockedBuffs(Bot* to) { + if (!to) { + return; + } + + to->ClearBotBlockedBuffs(); + + std::vector blocked_buffs = GetBotBlockedBuffs(); + + if (!blocked_buffs.empty()) { + for (auto& blocked_buff : blocked_buffs) { + to->SetBotBlockedBuff(blocked_buff.spell_id, blocked_buff.blocked); + } + } +} + +void Bot::CopyBotBlockedPetBuffs(Bot* to) { + if (!to) { + return; + } + + to->ClearBotBlockedBuffs(); + + std::vector blocked_buffs = GetBotBlockedBuffs(); + + if (!blocked_buffs.empty()) { + for (auto& blocked_buff : blocked_buffs) { + to->SetBotBlockedPetBuff(blocked_buff.spell_id, blocked_buff.blocked_pet); + } + } +} + +bool Bot::BotPassiveCheck() { + if (GetBotStance() == Stance::Passive) { + GetOwner()->Message( + Chat::Yellow, + fmt::format( + "{} says, 'I am currently set to stance {} [#{}]. I cannot do commands and my settings cannot be modified.'", + GetCleanName(), + Stance::GetName(Stance::Passive), + Stance::Passive + ).c_str() + ); + + return true; + } + + return false; +} + +bool Bot::ValidStateCheck(Mob* other, bool same_raid_group) { + if ( + GetBotStance() == Stance::Passive || + GetHoldFlag() || + GetAppearance() == eaDead || + IsFeared() || + IsSilenced() || + IsAmnesiad() || + GetHP() < 0 || + !IsInGroupOrRaid(other, same_raid_group) + ) { + return false; + } + + return true; +} + +bool Bot::IsValidSpellTypeSubType(uint16 spell_type, uint16 sub_type, uint16 spell_id) { + if (sub_type == UINT16_MAX) { + return true; + } + + switch (sub_type) { + case CommandedSubTypes::SingleTarget: + if ( + !IsAnyAESpell(spell_id) && + !IsGroupSpell(spell_id) + ) { + return true; + } + + break; + case CommandedSubTypes::GroupTarget: + if (IsGroupSpell(spell_id)) { + return true; + } + + break; + case CommandedSubTypes::AETarget: + if ( + IsAnyAESpell(spell_id) && + !IsGroupSpell(spell_id) + ) { + return true; + } + + break; + case CommandedSubTypes::SeeInvis: + if (IsEffectInSpell(spell_id, SpellEffect::SeeInvis)) { + return true; + } + + break; + case CommandedSubTypes::Invis: + if ( + IsEffectInSpell(spell_id, SpellEffect::Invisibility) || + IsEffectInSpell(spell_id, SpellEffect::Invisibility2) + ) { + return true; + } + + break; + case CommandedSubTypes::InvisUndead: + if ( + IsEffectInSpell(spell_id, SpellEffect::InvisVsUndead) || + IsEffectInSpell(spell_id, SpellEffect::InvisVsUndead2) + ) { + return true; + } + + break; + case CommandedSubTypes::InvisAnimals: + if ( + IsEffectInSpell(spell_id, SpellEffect::InvisVsAnimals) || + IsEffectInSpell(spell_id, SpellEffect::ImprovedInvisAnimals) + ) { + return true; + } + + break; + case CommandedSubTypes::Shrink: + if ( + ( + IsEffectInSpell(spell_id, SpellEffect::ModelSize) && + CalcSpellEffectValue(spell_id, GetSpellEffectIndex(spell_id, SpellEffect::ModelSize), GetLevel()) < 100 + ) + || + ( + IsEffectInSpell(spell_id, SpellEffect::ChangeHeight) && + CalcSpellEffectValue(spell_id, GetSpellEffectIndex(spell_id, SpellEffect::ChangeHeight), GetLevel()) < 100 + ) + ) { + return true; + } + + break; + case CommandedSubTypes::Grow: + if ( + ( + IsEffectInSpell(spell_id, SpellEffect::ModelSize) && + CalcSpellEffectValue(spell_id, GetSpellEffectIndex(spell_id, SpellEffect::ModelSize), GetLevel()) > 100 + ) + || + ( + IsEffectInSpell(spell_id, SpellEffect::ChangeHeight) && + CalcSpellEffectValue(spell_id, GetSpellEffectIndex(spell_id, SpellEffect::ChangeHeight), GetLevel()) > 100 + ) + ) { + return true; + } + + break; + case CommandedSubTypes::Selo: + if ( + IsBeneficialSpell(spell_id) && + IsEffectInSpell(spell_id, SpellEffect::MovementSpeed) && + IsBardSong(spell_id) + ) { + return true; + } + + break; + default: + break; + } + + return false; +} + +uint16 Bot::GetSpellByAA(int id, AA::Rank*& rank) { + uint16 spell_id = 0; + std::pair aa_ability = std::make_pair(nullptr, nullptr); + AA::Ability* ability = zone->GetAlternateAdvancementAbility(id); + + if (!ability || !ability->first_rank_id) { + return spell_id; + } + + uint32 points = GetAA(ability->first_rank_id); + + if (points > 0) { + aa_ability = zone->GetAlternateAdvancementAbilityAndRank(ability->id, points); + } + + rank = aa_ability.second; + + if (!points || !rank) { + return spell_id; + } + + spell_id = rank->spell; + + return spell_id; +} + +void Bot::SetBotBlockedBuff(uint16 spell_id, bool block) +{ + if (!IsValidSpell(spell_id)) { + OwnerMessage("Failed to set blocked buff."); + return; + } + + auto it = std::find_if( + bot_blocked_buffs.begin(), bot_blocked_buffs.end(), + [spell_id](const BotBlockedBuffs& buff) { return buff.spell_id == spell_id; } + ); + + if (it != bot_blocked_buffs.end()) { + it->blocked = block; + } + else { + bot_blocked_buffs.push_back({ GetBotID(), spell_id, block }); + } + + CleanBotBlockedBuffs(); +} + +bool Bot::IsBlockedBuff(int32 spell_id) +{ + bool result = false; + + if (!IsValidSpell(spell_id)) { + OwnerMessage("Failed to get blocked buff."); + + return result; + } + + CleanBotBlockedBuffs(); + + if (!bot_blocked_buffs.empty()) { + for (int i = 0; i < bot_blocked_buffs.size(); i++) { + if (bot_blocked_buffs[i].spell_id != spell_id) { + continue; + } + + return bot_blocked_buffs[i].blocked; + } + } + + return result; +} + +void Bot::SetBotBlockedPetBuff(uint16 spell_id, bool block) +{ + if (!IsValidSpell(spell_id)) { + OwnerMessage("Failed to set blocked pet buff."); + return; + } + + auto it = std::find_if( + bot_blocked_buffs.begin(), bot_blocked_buffs.end(), + [spell_id](const BotBlockedBuffs& buff) { return buff.spell_id == spell_id; } + ); + + if (it != bot_blocked_buffs.end()) { + it->blocked_pet = block; + } + else { + bot_blocked_buffs.push_back({ GetBotID(), spell_id, false, block }); + } + + CleanBotBlockedBuffs(); +} + +bool Bot::IsBlockedPetBuff(int32 spell_id) +{ + bool result = false; + + if (!IsValidSpell(spell_id)) { + OwnerMessage("Failed to get blocked pet buff."); + + return result; + } + + CleanBotBlockedBuffs(); + + if (!bot_blocked_buffs.empty()) { + for (int i = 0; i < bot_blocked_buffs.size(); i++) { + if (bot_blocked_buffs[i].spell_id != spell_id) { + continue; + } + + return bot_blocked_buffs[i].blocked_pet; + } + } + + return result; +} + +void Bot::CleanBotBlockedBuffs() +{ + if (!bot_blocked_buffs.empty()) { + int current = 0; + int end = bot_blocked_buffs.size(); + + while (current < end) { + if (!IsValidSpell(bot_blocked_buffs[current].spell_id) || (bot_blocked_buffs[current].blocked == 0 && bot_blocked_buffs[current].blocked_pet == 0)) { + bot_blocked_buffs.erase(bot_blocked_buffs.begin() + current); + } + else { + current++; + } + + end = bot_blocked_buffs.size(); + } + } +} + +const std::vector& Bot::BotGetSpellsByType(uint16 spell_type) const { + auto it = AIBot_spells_by_type.find(spell_type); + + if (it == AIBot_spells_by_type.end() || it->second.empty()) { + it = AIBot_spells_by_type.find(GetParentSpellType(spell_type)); + + if (it == AIBot_spells_by_type.end()) { + static const std::vector empty; + + return empty; + } + } + + return it->second; +} + +void Bot::AssignBotSpellsToTypes(std::vector& AIBot_spells, std::unordered_map>& AIBot_spells_by_type) { + AIBot_spells_by_type.clear(); + + for (size_t i = 0; i < AIBot_spells.size(); ++i) { + const auto& spell = AIBot_spells[i]; + + if (spell.spellid <= 0) { + continue; + } + + BotSpells_wIndex spell_with_index{ + static_cast(i), + spell.type, + spell.spellid, + spell.manacost, + spell.time_cancast, + spell.recast_delay, + spell.priority, + spell.resist_adjust, + spell.minlevel, + spell.maxlevel, + spell.min_hp, + spell.max_hp, + spell.bucket_name, + spell.bucket_value, + spell.bucket_comparison + }; + + AIBot_spells_by_type[spell.type].emplace_back(spell_with_index); + } +} + +std::vector Bot::GatherSpellTargets(bool entire_raid, Mob* target, bool no_clients, bool no_bots) { + std::vector valid_spell_targets; + + auto is_valid_target = [no_clients, no_bots](Mob* member) { + return member && + ((member->IsClient() && !no_clients) || (member->IsBot() && !no_bots)); + }; + + if (IsRaidGrouped()) { + Raid* raid = IsBot() ? CastToBot()->GetStoredRaid() : GetRaid(); + + if (raid) { + if (entire_raid) { + for (const auto& m : raid->members) { + if (is_valid_target(m.member) && m.group_number != RAID_GROUPLESS) { + valid_spell_targets.emplace_back(m.member); + } + } + } + else { + auto group_name = target ? raid->GetGroup(target->GetName()) : raid->GetGroup(GetName()); + auto raid_group = raid->GetRaidGroupMembers(group_name); + + for (const auto& m : raid_group) { + if (is_valid_target(m.member) && m.group_number != RAID_GROUPLESS) { + valid_spell_targets.emplace_back(m.member); + } + } + } + } + } + else if (IsGrouped()) { + Group* group = GetGroup(); + + if (group) { + for (const auto& m : group->members) { + if (is_valid_target(m)) { + valid_spell_targets.emplace_back(m); + } + } + } + } + else { + valid_spell_targets.emplace_back(this); + } + + return valid_spell_targets; +} + +std::vector Bot::GetBuffTargets(Mob* spellTarget) { + if (RuleB(Bots, RaidBuffing)) { + return GetSpellTargetList(true); + } + + return GatherSpellTargets(false, spellTarget); +} + +uint8 Bot::GetHPRatioForSpellType(uint16 spell_type, Mob* tar) { + switch (spell_type) { + case BotSpellTypes::Escape: + case BotSpellTypes::HateRedux: + case BotSpellTypes::InCombatBuff: + case BotSpellTypes::InCombatBuffSong: + case BotSpellTypes::AELifetap: + case BotSpellTypes::Lifetap: + case BotSpellTypes::OutOfCombatBuffSong: + case BotSpellTypes::Pet: + case BotSpellTypes::PreCombatBuff: + case BotSpellTypes::PreCombatBuffSong: + return GetHPRatio(); + default: + return tar->GetHPRatio(); + } + + return tar->GetHPRatio(); +} + +uint16 Bot::GetPetBotSpellType(uint16 spell_type) { + switch (spell_type) { + case BotSpellTypes::VeryFastHeals: + return BotSpellTypes::PetVeryFastHeals; + case BotSpellTypes::FastHeals: + return BotSpellTypes::PetFastHeals; + case BotSpellTypes::RegularHeal: + return BotSpellTypes::PetRegularHeals; + case BotSpellTypes::CompleteHeal: + return BotSpellTypes::PetCompleteHeals; + case BotSpellTypes::HoTHeals: + return BotSpellTypes::PetHoTHeals; + case BotSpellTypes::Buff: + return BotSpellTypes::PetBuffs; + case BotSpellTypes::Cure: + return BotSpellTypes::PetCures; + case BotSpellTypes::DamageShields: + return BotSpellTypes::PetDamageShields; + case BotSpellTypes::ResistBuffs: + return BotSpellTypes::PetResistBuffs; + default: + break; + } + + return spell_type; +} + +uint16 Bot::GetSpellTypeIDByShortName(std::string spell_type_string) { + + for (int i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + if (!Strings::ToLower(spell_type_string).compare(GetSpellTypeShortNameByID(i))) { + return i; + } + } + + for (int i = BotSpellTypes::COMMANDED_START; i <= BotSpellTypes::COMMANDED_END; ++i) { + if (!Strings::ToLower(spell_type_string).compare(GetSpellTypeShortNameByID(i))) { + return i; + } + } + + return UINT16_MAX; +} + +bool Bot::IsValidBotSpellCategory(uint8 setting_type) { + return EQ::ValueWithin(setting_type, BotSettingCategories::START, BotSettingCategories::END); +} + +std::string Bot::GetBotSpellCategoryName(uint8 setting_type) { + return Bot::IsValidBotSpellCategory(setting_type) ? bot_setting_category_names[setting_type] : "UNKNOWN CATEGORY"; +} + +std::string Bot::GetBotSpellCategoryShortName(uint8 setting_type) { + return Bot::IsValidBotSpellCategory(setting_type) ? bot_setting_category_short_names[setting_type] : "UNKNOWN CATEGORY"; +} + +uint16 Bot::GetBotSpellCategoryIDByShortName(std::string setting_string) { + for (int i = BotSettingCategories::START; i <= BotSettingCategories::END; ++i) { + if (!Strings::ToLower(setting_string).compare(Strings::ToLower(GetBotSpellCategoryShortName(i)))) { + return i; + } + } + + return UINT16_MAX; +} + +bool Bot::IsValidBotBaseSetting(uint16 setting_type) { + return EQ::ValueWithin(setting_type, BotBaseSettings::START_ALL, BotBaseSettings::END); +} + +std::string Bot::GetBotSettingCategoryName(uint16 setting_type) { + return Bot::IsValidBotBaseSetting(setting_type) ? botBaseSettings_names[setting_type] : "UNKNOWN SETTING"; +} + +uint16 Bot::GetBaseSettingIDByShortName(std::string setting_string) { + for (int i = BotSettingCategories::START; i <= BotSettingCategories::END; ++i) { + if (!Strings::ToLower(setting_string).compare(Strings::ToLower(GetBotSettingCategoryName(i)))) { + return i; + } + } + + return UINT16_MAX; +} + +std::string Bot::GetSpellTypeShortNameByID(uint16 spell_type) { + return IsValidBotSpellType(spell_type) ? spell_type_short_names[spell_type] : "UNKNOWN SPELLTYPE"; +} + +std::string Bot::GetSpellTypeNameByID(uint16 spell_type) { + return IsValidBotSpellType(spell_type) ? spell_type_names[spell_type] : "UNKNOWN SPELLTYPE"; +} + +bool Bot::IsValidSubType(uint16 sub_type) { + return EQ::ValueWithin(sub_type, CommandedSubTypes::START, CommandedSubTypes::END); +} + +std::string Bot::GetSubTypeNameByID(uint16 sub_type) { + return IsValidBotSpellType(sub_type) ? botSubType_names[sub_type] : "UNKNOWN SUBTYPE"; +} + +uint16 Bot::GetDefaultSpellTypeDelay(uint16 spell_type, uint8 stance) { + switch (spell_type) { + case BotSpellTypes::VeryFastHeals: + case BotSpellTypes::PetVeryFastHeals: + return 1500; + case BotSpellTypes::FastHeals: + case BotSpellTypes::PetFastHeals: + return 2500; + case BotSpellTypes::GroupHeals: + case BotSpellTypes::RegularHeal: + case BotSpellTypes::PetRegularHeals: + return 4000; + case BotSpellTypes::CompleteHeal: + case BotSpellTypes::GroupCompleteHeals: + case BotSpellTypes::PetCompleteHeals: + return 8000; + case BotSpellTypes::GroupHoTHeals: + case BotSpellTypes::HoTHeals: + case BotSpellTypes::PetHoTHeals: + return 22000; + case BotSpellTypes::Cure: + return 2000; + case BotSpellTypes::GroupCures: + return 3000; + case BotSpellTypes::PetCures: + return 5000; + case BotSpellTypes::AEDoT: + case BotSpellTypes::DOT: + switch (stance) { + case Stance::AEBurn: + case Stance::Burn: + return 100; + case Stance::Aggressive: + return 2000; + case Stance::Efficient: + return 8000; + default: + return 4000; + } + case BotSpellTypes::AENukes: + case BotSpellTypes::AERains: + case BotSpellTypes::PBAENuke: + case BotSpellTypes::Nuke: + case BotSpellTypes::AESnare: + case BotSpellTypes::Snare: + case BotSpellTypes::AEDebuff: + case BotSpellTypes::Debuff: + case BotSpellTypes::AESlow: + case BotSpellTypes::Slow: + case BotSpellTypes::AEStun: + case BotSpellTypes::Stun: + switch (stance) { + case Stance::AEBurn: + case Stance::Burn: + return 100; + case Stance::Aggressive: + return 3000; + case Stance::Efficient: + return 10000; + default: + return 6000; + } + case BotSpellTypes::AERoot: + case BotSpellTypes::Root: + return 8000; + case BotSpellTypes::Fear: + case BotSpellTypes::AEFear: + return 15000; + default: + return 100; + } +} + +uint8 Bot::GetDefaultSpellTypeMinThreshold(uint16 spell_type, uint8 stance) { + switch (spell_type) { + case BotSpellTypes::AEDebuff: + case BotSpellTypes::Debuff: + case BotSpellTypes::AEDispel: + case BotSpellTypes::Dispel: + case BotSpellTypes::AESlow: + case BotSpellTypes::Slow: + switch (stance) { + case Stance::AEBurn: + case Stance::Burn: + case Stance::Aggressive: + return 0; + default: + return 20; + } + case BotSpellTypes::AENukes: + case BotSpellTypes::AERains: + case BotSpellTypes::PBAENuke: + case BotSpellTypes::Nuke: + switch (stance) { + case Stance::AEBurn: + case Stance::Burn: + case Stance::Aggressive: + return 0; + default: + return 5; + } + case BotSpellTypes::AEDoT: + case BotSpellTypes::DOT: + switch (stance) { + case Stance::AEBurn: + case Stance::Burn: + case Stance::Aggressive: + return 0; + case Stance::Efficient: + return 40; + default: + return 25; + } + case BotSpellTypes::Mez: + case BotSpellTypes::AEMez: + return 85; + default: + return 0; + } +} + +uint8 Bot::GetDefaultSpellTypeMaxThreshold(uint16 spell_type, uint8 stance) { + uint8 bot_class = GetClass(); + + switch (spell_type) { + case BotSpellTypes::Escape: + case BotSpellTypes::VeryFastHeals: + case BotSpellTypes::PetVeryFastHeals: + switch (stance) { + case Stance::AEBurn: + case Stance::Burn: + case Stance::Aggressive: + return 40; + case Stance::Efficient: + default: + return 25; + } + case BotSpellTypes::AELifetap: + case BotSpellTypes::Lifetap: + case BotSpellTypes::FastHeals: + case BotSpellTypes::PetFastHeals: + switch (stance) { + case Stance::AEBurn: + case Stance::Burn: + case Stance::Aggressive: + return 55; + case Stance::Efficient: + return 35; + default: + return 40; + } + case BotSpellTypes::GroupHeals: + case BotSpellTypes::RegularHeal: + if (bot_class == Class::Necromancer || bot_class == Class::Shaman) { + return 60; + } + + switch (stance) { + case Stance::AEBurn: + case Stance::Burn: + case Stance::Aggressive: + return 70; + case Stance::Efficient: + return 50; + default: + return 60; + } + case BotSpellTypes::PetRegularHeals: + switch (stance) { + case Stance::AEBurn: + case Stance::Burn: + case Stance::Aggressive: + return 70; + case Stance::Efficient: + return 50; + default: + return 60; + } + case BotSpellTypes::CompleteHeal: + case BotSpellTypes::GroupCompleteHeals: + if (bot_class == Class::Necromancer || (bot_class == Class::Shaman && !GetSpellTypeHold(BotSpellTypes::InCombatBuff))) { + return 55; + } + + switch (stance) { + case Stance::AEBurn: + case Stance::Burn: + case Stance::Aggressive: + return 90; + case Stance::Efficient: + return 65; + default: + return 80; + } + case BotSpellTypes::PetCompleteHeals: + switch (stance) { + case Stance::AEBurn: + case Stance::Burn: + case Stance::Aggressive: + return 90; + case Stance::Efficient: + return 65; + default: + return 80; + } + case BotSpellTypes::AENukes: + case BotSpellTypes::AERains: + case BotSpellTypes::PBAENuke: + case BotSpellTypes::AEStun: + case BotSpellTypes::Nuke: + case BotSpellTypes::AEDoT: + case BotSpellTypes::DOT: + case BotSpellTypes::AERoot: + case BotSpellTypes::Root: + case BotSpellTypes::AESlow: + case BotSpellTypes::Slow: + case BotSpellTypes::AESnare: + case BotSpellTypes::Snare: + case BotSpellTypes::AEFear: + case BotSpellTypes::Fear: + case BotSpellTypes::AEDispel: + case BotSpellTypes::Dispel: + case BotSpellTypes::AEDebuff: + case BotSpellTypes::Debuff: + case BotSpellTypes::Stun: + switch (stance) { + case Stance::AEBurn: + case Stance::Burn: + return 100; + case Stance::Aggressive: + return 100; + case Stance::Efficient: + return 90; + default: + return 99; + } + case BotSpellTypes::GroupHoTHeals: + case BotSpellTypes::HoTHeals: + case BotSpellTypes::PetHoTHeals: + if (bot_class == Class::Necromancer || (bot_class == Class::Shaman && !GetSpellTypeHold(BotSpellTypes::InCombatBuff))) { + return 70; + } + else { + switch (stance) { + case Stance::AEBurn: + case Stance::Burn: + case Stance::Aggressive: + return 95; + case Stance::Efficient: + return 80; + default: + return 90; + } + } + case BotSpellTypes::Buff: + case BotSpellTypes::Charm: + case BotSpellTypes::Cure: + case BotSpellTypes::GroupCures: + case BotSpellTypes::PetCures: + case BotSpellTypes::DamageShields: + case BotSpellTypes::HateRedux: + case BotSpellTypes::InCombatBuff: + case BotSpellTypes::InCombatBuffSong: + case BotSpellTypes::Mez: + case BotSpellTypes::AEMez: + case BotSpellTypes::OutOfCombatBuffSong: + case BotSpellTypes::Pet: + case BotSpellTypes::PetBuffs: + case BotSpellTypes::PreCombatBuff: + case BotSpellTypes::PreCombatBuffSong: + case BotSpellTypes::PetDamageShields: + case BotSpellTypes::PetResistBuffs: + case BotSpellTypes::ResistBuffs: + case BotSpellTypes::Resurrect: + case BotSpellTypes::HateLine: + case BotSpellTypes::AEHateLine: + default: + return 100; + } +} + +uint16 Bot::GetUltimateSpellTypeDelay(uint16 spell_type, Mob* tar) { + if (!TargetValidation(tar)) { + return GetSpellTypeDelay(spell_type); + } + + Mob* owner = tar->IsPet() ? tar->GetOwner() : nullptr; + + if (owner && owner->IsOfClientBot()) { + return owner->IsClient() + ? owner->CastToClient()->GetSpellTypeDelay(GetPetBotSpellType(spell_type)) + : owner->CastToBot()->GetSpellTypeDelay(GetPetBotSpellType(spell_type)); + } + + if (BotSpellTypeUsesTargetSettings(spell_type) && tar->IsOfClientBot()) { + return tar->IsClient() + ? tar->CastToClient()->GetSpellTypeDelay(spell_type) + : tar->CastToBot()->GetSpellTypeDelay(spell_type); + } + + return GetSpellTypeDelay(spell_type); +} + +bool Bot::GetUltimateSpellTypeRecastCheck(uint16 spell_type, Mob* tar) { + if (!TargetValidation(tar)) { + return SpellTypeRecastCheck(spell_type); + } + + Mob* owner = tar->IsPet() ? tar->GetOwner() : nullptr; + + if (owner && owner->IsOfClientBot()) { + return owner->IsClient() + ? owner->CastToClient()->SpellTypeRecastCheck(GetPetBotSpellType(spell_type)) + : owner->CastToBot()->SpellTypeRecastCheck(GetPetBotSpellType(spell_type)); + } + + if (BotSpellTypeUsesTargetSettings(spell_type) && tar->IsOfClientBot()) { + return tar->IsClient() + ? tar->CastToClient()->SpellTypeRecastCheck(spell_type) + : tar->CastToBot()->SpellTypeRecastCheck(spell_type); + } + + return SpellTypeRecastCheck(spell_type); +} + +uint8 Bot::GetUltimateSpellTypeMinThreshold(uint16 spell_type, Mob* tar) { + if (!TargetValidation(tar)) { + return GetSpellTypeMinThreshold(spell_type); + } + + Mob* owner = tar->IsPet() ? tar->GetOwner() : nullptr; + + if (owner && owner->IsOfClientBot()) { + return owner->IsClient() + ? owner->CastToClient()->GetSpellTypeMinThreshold(GetPetBotSpellType(spell_type)) + : owner->CastToBot()->GetSpellTypeMinThreshold(GetPetBotSpellType(spell_type)); + } + + if (BotSpellTypeUsesTargetSettings(spell_type) && tar->IsOfClientBot()) { + return tar->IsClient() + ? tar->CastToClient()->GetSpellTypeMinThreshold(spell_type) + : tar->CastToBot()->GetSpellTypeMinThreshold(spell_type); + } + + return GetSpellTypeMinThreshold(spell_type); +} + +uint8 Bot::GetUltimateSpellTypeMaxThreshold(uint16 spell_type, Mob* tar) { + if (!TargetValidation(tar)) { + return GetSpellTypeMaxThreshold(spell_type); + } + + Mob* owner = tar->IsPet() ? tar->GetOwner() : nullptr; + + if (owner && owner->IsOfClientBot()) { + return owner->IsClient() + ? owner->CastToClient()->GetSpellTypeMaxThreshold(GetPetBotSpellType(spell_type)) + : owner->CastToBot()->GetSpellTypeMaxThreshold(GetPetBotSpellType(spell_type)); + } + + if (BotSpellTypeUsesTargetSettings(spell_type) && tar->IsOfClientBot()) { + return tar->IsClient() + ? tar->CastToClient()->GetSpellTypeMaxThreshold(spell_type) + : tar->CastToBot()->GetSpellTypeMaxThreshold(spell_type); + } + + return GetSpellTypeMaxThreshold(spell_type); +} + +bool Bot::IsImmuneToBotSpell(uint16 spell_id, Mob* caster) { + int effect_index; + + if (!caster) { + return false; + } + + LogSpells("Checking to see if we are immune to spell [{}] cast by [{}]", spell_id, caster->GetName()); + + if (!IsValidSpell(spell_id)) { + return true; + } + + if (GetSpecialAbility(SpecialAbility::DispellImmunity) && IsDispelSpell(spell_id)) { + return true; + } + + if (GetSpecialAbility(SpecialAbility::PacifyImmunity) && IsHarmonySpell(spell_id)) { + return true; + } + + if (!GetSpecialAbility(SpecialAbility::MesmerizeImmunity) && IsMesmerizeSpell(spell_id)) { + // check max level for spell + effect_index = GetSpellEffectIndex(spell_id, SpellEffect::Mez); + assert(effect_index >= 0); + // NPCs get to ignore the max level + if ( + (GetLevel() > spells[spell_id].max_value[effect_index]) && + (!caster->IsNPC() || (caster->IsNPC() && !RuleB(Spells, NPCIgnoreBaseImmunity))) + ) { + return true; + } + } + + // slow and haste spells + if (GetSpecialAbility(SpecialAbility::SlowImmunity) && IsEffectInSpell(spell_id, SpellEffect::AttackSpeed)) { + return true; + } + + // client vs client fear + if (!GetSpecialAbility(SpecialAbility::FearImmunity) && IsEffectInSpell(spell_id, SpellEffect::Fear)) { + effect_index = GetSpellEffectIndex(spell_id, SpellEffect::Fear); + + if (IsClient() && caster->IsClient() && (caster->CastToClient()->GetGM() == false)) { + LogSpells("Clients cannot fear eachother!"); + caster->MessageString(Chat::Red, IMMUNE_FEAR); // need to verify message type, not in MQ2Cast for easy look up + return true; + } + else if (GetLevel() > spells[spell_id].max_value[effect_index] && spells[spell_id].max_value[effect_index] != 0) { + return true; + } + else if (CheckAATimer(aaTimerWarcry)) { + return true; + } + } + + if (!GetSpecialAbility(SpecialAbility::CharmImmunity) && IsCharmSpell(spell_id)) { + + if (this == caster) { + return true; + } + + //let npcs cast whatever charm on anyone + if (!caster->IsNPC()) { + // check level limit of charm spell + effect_index = GetSpellEffectIndex(spell_id, SpellEffect::Charm); + assert(effect_index >= 0); + if (GetLevel() > spells[spell_id].max_value[effect_index] && spells[spell_id].max_value[effect_index] != 0) { + return true; + } + } + } + + if ( + GetSpecialAbility(SpecialAbility::SnareImmunity) && + ( + IsEffectInSpell(spell_id, SpellEffect::Root) || + IsEffectInSpell(spell_id, SpellEffect::MovementSpeed) + ) + ) { + return true; + } + + if (IsLifetapSpell(spell_id)) { + if (this == caster) { + return true; + } + } + + if (IsSacrificeSpell(spell_id)) { + if (this == caster) { + return true; + } + } + + return false; +} + +std::vector Bot::GetSpellTargetList(bool entire_raid) { + if (entire_raid && _spell_target_list.empty()) { + _spell_target_list = GatherSpellTargets(RuleB(Bots, CrossRaidBuffingAndHealing)); + } + else if (!entire_raid && _group_spell_target_list.empty()) { + _group_spell_target_list = GatherSpellTargets(); + } + + return entire_raid ? _spell_target_list : _group_spell_target_list; +} + +bool Bot::IsValidBotStance(uint8 stance) { + switch (stance) { + case Stance::Passive: + case Stance::Balanced: + case Stance::Efficient: + case Stance::Aggressive: + case Stance::Assist: + case Stance::Burn: + case Stance::AEBurn: + return true; + default: + return false; + } + + return false; +} + +bool Bot::HasControllablePet(uint8 ranks_required) { + if (!GetPet()) { + return false; + } + + return GetClass() != Class::Enchanter || + GetPet()->GetPetType() != PetType::Animation || + GetAA(aaAnimationEmpathy) >= ranks_required; +} diff --git a/zone/bot.h b/zone/bot.h index 68b027f05..434e8f703 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -37,18 +37,21 @@ #include -constexpr uint32 BOT_FOLLOW_DISTANCE_DEFAULT = 184; // as DSq value (~13.565 units) -constexpr uint32 BOT_FOLLOW_DISTANCE_DEFAULT_MAX = 2500; // as DSq value (50 units) - constexpr uint32 BOT_KEEP_ALIVE_INTERVAL = 5000; // 5 seconds constexpr uint32 MAG_EPIC_1_0 = 28034; extern WorldServer worldserver; -constexpr int BotAISpellRange = 100; // TODO: Write a method that calcs what the bot's spell range is based on spell, equipment, AA, whatever and replace this constexpr int NegativeItemReuse = -1; // Unlinked timer for items +constexpr uint8 SumWater = 1; +constexpr uint8 SumFire = 2; +constexpr uint8 SumAir = 3; +constexpr uint8 SumEarth = 4; +constexpr uint8 MonsterSum = 5; +constexpr uint8 SumMageMultiElement = 6; + // nHSND negative Healer/Slower/Nuker/Doter // pH positive Healer // pS positive Slower @@ -87,6 +90,150 @@ enum BotCastingChanceConditional : uint8 cntHSND = 16 }; +namespace BotSettingCategories { + constexpr uint8 BaseSetting = 0; + constexpr uint8 SpellHold = 1; + constexpr uint8 SpellDelay = 2; + constexpr uint8 SpellMinThreshold = 3; + constexpr uint8 SpellMaxThreshold = 4; + constexpr uint8 SpellTypeResistLimit = 5; + constexpr uint8 SpellTypeAggroCheck = 6; + constexpr uint8 SpellTypeMinManaPct = 7; + constexpr uint8 SpellTypeMaxManaPct = 8; + constexpr uint8 SpellTypeMinHPPct = 9; + constexpr uint8 SpellTypeMaxHPPct = 10; + constexpr uint8 SpellTypeIdlePriority = 11; + constexpr uint8 SpellTypeEngagedPriority = 12; + constexpr uint8 SpellTypePursuePriority = 13; + constexpr uint8 SpellTypeAEOrGroupTargetCount = 14; + constexpr uint8 SpellTypeAnnounceCast = 15; + + constexpr uint16 START = BotSettingCategories::BaseSetting; + constexpr uint16 START_NO_BASE = BotSettingCategories::SpellHold; + constexpr uint16 START_CLIENT = BotSettingCategories::SpellDelay; + constexpr uint16 END_CLIENT = BotSettingCategories::SpellMaxThreshold; + constexpr uint16 END = BotSettingCategories::SpellTypeAnnounceCast; +}; + +static std::map bot_setting_category_names = { + { BotSettingCategories::BaseSetting, "Base Setting" }, + { BotSettingCategories::SpellHold, "Spell Holds" }, + { BotSettingCategories::SpellDelay, "Spell Delays" }, + { BotSettingCategories::SpellMinThreshold, "Spell Minimum Thresholds" }, + { BotSettingCategories::SpellMaxThreshold, "Spell Maximum Thresholds" }, + { BotSettingCategories::SpellTypeResistLimit, "Spell Resist Limits" }, + { BotSettingCategories::SpellTypeAggroCheck, "Spell Aggro Checks" }, + { BotSettingCategories::SpellTypeMinManaPct, "Spell Min Mana Percent" }, + { BotSettingCategories::SpellTypeMaxManaPct, "Spell Max Mana Percent" }, + { BotSettingCategories::SpellTypeMinHPPct, "Spell Min HP Percent" }, + { BotSettingCategories::SpellTypeMaxHPPct, "Spell Max HP Percent" }, + { BotSettingCategories::SpellTypeIdlePriority, "Spell Idle Priority" }, + { BotSettingCategories::SpellTypeEngagedPriority, "Spell Engaged Priority" }, + { BotSettingCategories::SpellTypePursuePriority, "Spell Pursue Priority" }, + { BotSettingCategories::SpellTypeAEOrGroupTargetCount, "Spell Target Counts" }, + { BotSettingCategories::SpellTypeAnnounceCast, "Spell Announce Casts" } +}; + +static std::map bot_setting_category_short_names = { + { BotSettingCategories::BaseSetting, "BaseSetting" }, + { BotSettingCategories::SpellHold, "SpellHolds" }, + { BotSettingCategories::SpellDelay, "SpellDelays" }, + { BotSettingCategories::SpellMinThreshold, "SpellMinThresholds" }, + { BotSettingCategories::SpellMaxThreshold, "SpellMaxThresholds" }, + { BotSettingCategories::SpellTypeResistLimit, "SpellResistLimits" }, + { BotSettingCategories::SpellTypeAggroCheck, "SpellAggroChecks" }, + { BotSettingCategories::SpellTypeMinManaPct, "SpellMinManaPct" }, + { BotSettingCategories::SpellTypeMaxManaPct, "SpellMaxManaPct" }, + { BotSettingCategories::SpellTypeMinHPPct, "SpellMinHPPct" }, + { BotSettingCategories::SpellTypeMaxHPPct, "SpellMaxHPPct" }, + { BotSettingCategories::SpellTypeIdlePriority, "SpellIdlePriority" }, + { BotSettingCategories::SpellTypeEngagedPriority, "SpellEngagedPriority" }, + { BotSettingCategories::SpellTypePursuePriority, "SpellPursuePriority" }, + { BotSettingCategories::SpellTypeAEOrGroupTargetCount, "SpellTargetCounts" }, + { BotSettingCategories::SpellTypeAnnounceCast, "SpellAnnounceCasts" } +}; + +namespace BotPriorityCategories { + constexpr uint8 Idle = 0; + constexpr uint8 Engaged = 1; + constexpr uint8 Pursue = 2; + + constexpr uint16 START = BotPriorityCategories::Idle; + constexpr uint16 END = BotPriorityCategories::Pursue; // Increment as needed +}; + +namespace BotBaseSettings { + constexpr uint16 ExpansionBitmask = 0; + constexpr uint16 ShowHelm = 1; + constexpr uint16 FollowDistance = 2; + constexpr uint16 StopMeleeLevel = 3; + constexpr uint16 EnforceSpellSettings = 4; + constexpr uint16 RangedSetting = 5; + constexpr uint16 PetSetTypeSetting = 6; + constexpr uint16 BehindMob = 7; + constexpr uint16 DistanceRanged = 8; + constexpr uint16 IllusionBlock = 9; + constexpr uint16 MaxMeleeRange = 10; + constexpr uint16 MedInCombat = 11; + constexpr uint16 SitHPPct = 12; + constexpr uint16 SitManaPct = 13; + + constexpr uint16 START_ALL = ExpansionBitmask; + constexpr uint16 START = BotBaseSettings::ShowHelm; // Everything above this cannot be copied, changed or viewed by players + constexpr uint16 END = BotBaseSettings::SitManaPct; // Increment as needed +}; + +static std::map botBaseSettings_names = { + { BotBaseSettings::ExpansionBitmask, "ExpansionBitmask" }, + { BotBaseSettings::ShowHelm, "ShowHelm" }, + { BotBaseSettings::FollowDistance, "FollowDistance" }, + { BotBaseSettings::StopMeleeLevel, "StopMeleeLevel" }, + { BotBaseSettings::EnforceSpellSettings, "EnforceSpellSettings" }, + { BotBaseSettings::RangedSetting, "RangedSetting" }, + { BotBaseSettings::PetSetTypeSetting, "PetSetTypeSetting" }, + { BotBaseSettings::BehindMob, "BehindMob" }, + { BotBaseSettings::DistanceRanged, "DistanceRanged" }, + { BotBaseSettings::IllusionBlock, "IllusionBlock" }, + { BotBaseSettings::MaxMeleeRange, "MaxMeleeRange" }, + { BotBaseSettings::MedInCombat, "MedInCombat" }, + { BotBaseSettings::SitHPPct, "SitHPPct" }, + { BotBaseSettings::SitManaPct, "SitManaPct" } +}; + +namespace CommandedSubTypes { + constexpr uint16 SingleTarget = 1; + constexpr uint16 GroupTarget = 2; + constexpr uint16 AETarget = 3; + constexpr uint16 SeeInvis = 4; + constexpr uint16 Invis = 5; + constexpr uint16 InvisUndead = 6; + constexpr uint16 InvisAnimals = 7; + constexpr uint16 Shrink = 8; + constexpr uint16 Grow = 9; + constexpr uint16 Selo = 10; + + constexpr uint16 START = CommandedSubTypes::SingleTarget; + constexpr uint16 END = CommandedSubTypes::Selo; +}; + +static std::map botSubType_names = { + { CommandedSubTypes::SingleTarget, "SingleTarget" }, + { CommandedSubTypes::GroupTarget, "GroupTarget" }, + { CommandedSubTypes::AETarget, "AETarget" }, + { CommandedSubTypes::SeeInvis, "SeeInvis" }, + { CommandedSubTypes::Invis, "Invis" }, + { CommandedSubTypes::InvisUndead, "InvisUndead" }, + { CommandedSubTypes::InvisAnimals, "InvisAnimals" }, + { CommandedSubTypes::Shrink, "Shrink" }, + { CommandedSubTypes::Grow, "Grow" }, + { CommandedSubTypes::Selo, "Selo" } +}; + +namespace BotAnimEmpathy { + constexpr uint8 Guard = 1; + constexpr uint8 Attack = 2; + constexpr uint8 BackOff = 3; +}; class Bot : public NPC { friend class Mob; @@ -128,7 +275,7 @@ public: // Class Constructors Bot(NPCType *npcTypeData, Client* botOwner); - Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double totalPlayTime, uint32 lastZoneId, NPCType *npcTypeData, int32 expansion_bitmask); + Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double totalPlayTime, uint32 lastZoneId, NPCType *npcTypeData); //abstract virtual override function implementations requird by base abstract class bool Death(Mob* killer_mob, int64 damage, uint16 spell_id, EQ::skills::SkillType attack_skill, KilledByTypes killed_by = KilledByTypes::Killed_NPC, bool is_buff_tic = false) override; @@ -157,13 +304,14 @@ public: void SetLevel(uint8 in_level, bool command = false) override; void FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) override; bool Process() override; - void FinishTrade(Client* client, BotTradeType trade_type); + void FinishTrade(Client* client, BotTradeType trade_type, int16 chosen_slot = INVALID_INDEX); bool Save() override; void Depop(); void CalcBotStats(bool showtext = true); uint16 BotGetSpells(int spellslot) { return AIBot_spells[spellslot].spellid; } uint32 BotGetSpellType(int spellslot) { return AIBot_spells[spellslot].type; } uint16 BotGetSpellPriority(int spellslot) { return AIBot_spells[spellslot].priority; } + const std::vector& BotGetSpellsByType(uint16 spell_type) const; float GetProcChances(float ProcBonus, uint16 hand) override; int GetHandToHandDamage(void) override; bool TryFinishingBlow(Mob *defender, int64 &damage) override; @@ -177,9 +325,6 @@ public: inline uint16 MaxSkill(EQ::skills::SkillType skillid) { return MaxSkill(skillid, GetClass(), GetLevel()); } int GetBaseSkillDamage(EQ::skills::SkillType skill, Mob *target = nullptr) override; void DoSpecialAttackDamage(Mob *who, EQ::skills::SkillType skill, int32 max_damage, int32 min_damage = 1, int32 hate_override = -1, int ReuseTime = 10, bool HitChance = false); - void TryBackstab(Mob *other,int ReuseTime = 10) override; - void RogueBackstab(Mob* other, bool min_damage = false, int ReuseTime = 10) override; - void RogueAssassinate(Mob* other) override; void DoClassAttacks(Mob *target, bool IsRiposte=false); void CalcBonuses() override; @@ -199,8 +344,8 @@ public: void Camp(bool save_to_database = true); void SetTarget(Mob* mob) override; void Zone(); - bool IsArcheryRange(Mob* target); - void ChangeBotArcherWeapons(bool isArcher); + bool IsAtRange(Mob* target); + void ChangeBotRangedWeapons(bool is_ranged); void Sit(); void Stand(); bool IsSitting() const override; @@ -216,6 +361,7 @@ public: void SetHoldFlag(bool flag = true) { m_hold_flag = flag; } bool GetAttackFlag() const { return m_attack_flag; } void SetAttackFlag(bool flag = true) { m_attack_flag = flag; } + bool GetCombatRoundForAlerts() const { return m_combat_round_alert_flag; } bool GetAttackingFlag() const { return m_attacking_flag; } bool GetPullFlag() const { return m_pull_flag; } void SetPullFlag(bool flag = true) { m_pull_flag = flag; } @@ -224,11 +370,10 @@ public: bool GetIsUsingItemClick() { return is_using_item_click; } void SetIsUsingItemClick(bool flag = true) { is_using_item_click = flag; } bool UseDiscipline(uint32 spell_id, uint32 target); - uint8 GetNumberNeedingHealedInGroup(uint8 hpr, bool includePets, Raid* raid); - uint8 GetNumberNeedingHealedInRaidGroup(uint8& need_healed, uint8 hpr, bool includePets, Raid* raid); + uint8 GetNumberNeedingHealedInGroup(Mob* tar, uint16 spell_type, uint16 spell_id, float range); bool GetNeedsCured(Mob *tar); bool GetNeedsHateRedux(Mob *tar); - bool HasOrMayGetAggro(); + bool HasOrMayGetAggro(bool SitAggro, uint32 spell_id = 0); void SetDefaultBotStance(); void SetSurname(std::string_view bot_surname); void SetTitle(std::string_view bot_title); @@ -327,19 +472,26 @@ public: void AI_Bot_Event_SpellCastFinished(bool iCastSucceeded, uint16 slot); // AI Methods - bool AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes); + bool AICastSpell(Mob* tar, uint8 chance, uint16 spell_type, uint16 sub_target_type = UINT16_MAX, uint16 sub_type = UINT16_MAX); + bool AttemptAICastSpell(uint16 spell_type, Mob* tar = nullptr); + bool AttemptAACastSpell(Mob* tar, uint16 spell_id, AA::Rank* rank); + bool AttemptForcedCastSpell(Mob* tar, uint16 spell_id, bool is_disc = false); + bool AttemptCloseBeneficialSpells(uint16 spell_type); bool AI_EngagedCastCheck() override; bool AI_PursueCastCheck() override; bool AI_IdleCastCheck() override; bool AIHealRotation(Mob* tar, bool useFastHeals); bool GetPauseAI() const { return _pauseAI; } void SetPauseAI(bool pause_flag) { _pauseAI = pause_flag; } - uint8 GetStopMeleeLevel() const { return _stopMeleeLevel; } - void SetStopMeleeLevel(uint8 level); + bool IsCommandedSpell() const { return _commandedSpell; } + void SetCommandedSpell(bool value) { _commandedSpell = value; } + bool IsPullingSpell() const { return _pullingSpell; } + void SetPullingSpell(bool value) { _pullingSpell = value; } + void SetGuardMode(); void SetHoldMode(); - uint32 GetBotCasterRange() const { return m_bot_caster_range; } - bool IsValidSpellRange(uint16 spell_id, Mob const* tar); + + bool IsValidSpellRange(uint16 spell_id, Mob* tar); // Bot AI Methods void AI_Bot_Init(); @@ -377,6 +529,167 @@ public: inline bool Attack(Mob* other, int Hand = EQ::invslot::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = false, bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr) override { return Mob::Attack(other, Hand, FromRiposte, IsStrikethrough, IsFromSpell, opts); } + void DoAttackRounds(Mob* target, int hand); + + bool BotPassiveCheck(); + bool ValidStateCheck(Mob* other, bool same_raid_group = false); + Raid* GetStoredRaid() { return _storedRaid; } + void SetStoredRaid(Raid* stored_raid) { _storedRaid = stored_raid; } + bool GetVerifiedRaid() { return _verifiedRaid; } + void SetVerifiedRaid(bool status) { _verifiedRaid = status; } + uint16 GetTempSpellType() { return _tempSpellType; } + void SetTempSpellType(uint16 spell_type) { _tempSpellType = spell_type; } + bool IsMobEngagedByAnyone(Mob* tar); + void SetBotTimers(std::vector timers) { bot_timers = timers; } + + // Targeting + std::vector GatherSpellTargets(bool entireRaid = false, Mob* target = nullptr, bool no_clients = false, bool no_bots = false); + bool HasValidAETarget(Bot* caster, uint16 spell_id, uint16 spell_type, Mob* tar); + void SetHasLoS(bool has_los) { _hasLoS = has_los; } + bool HasLoS() const { return _hasLoS; } + bool IsValidMezTarget(Mob* owner, Mob* npc, uint16 spell_id); + + // Cast checks + bool PrecastChecks(Mob* tar, uint16 spell_type); + bool CastChecks(uint16 spell_id, Mob* tar, uint16 spell_type, bool prechecks = false, bool ae_check = false); + bool IsImmuneToBotSpell(uint16 spell_id, Mob* caster); + bool CanCastSpellType(uint16 spell_type, uint16 spell_id, Mob* tar); + bool BotHasEnoughMana(uint16 spell_id); + bool IsTargetAlreadyReceivingSpell(Mob* tar, uint16 spell_id); + bool DoResistCheck(Mob* target, uint16 spell_id, int32 resist_limit); + bool DoResistCheckBySpellType(Mob* tar, uint16 spell_id, uint16 spell_type); + bool IsValidTargetType(uint16 spell_id, int target_type, uint8 body_type); + + // Spell checks + static bool IsValidBotSpellType(uint16 spell_type); + uint16 GetPetBotSpellType(uint16 spell_type); + + // Movement checks + bool PlotBotPositionAroundTarget(const FindPositionInput& input); + std::vector GetSpellTargetList(bool entire_raid = false); + void SetSpellTargetList(std::vector spell_target_list) { _spell_target_list = spell_target_list; } + std::vector GetGroupSpellTargetList() { return _group_spell_target_list; } + void SetGroupSpellTargetList(std::vector spell_target_list) { _group_spell_target_list = spell_target_list; } + std::vector GetBuffTargets(Mob* spellTarget); + + // Bot settings + void LoadDefaultBotSettings(); + int GetDefaultSetting(uint16 setting_category, uint16 setting_type, uint8 stance = Stance::Balanced); + int GetDefaultBotBaseSetting(uint16 bot_setting, uint8 stance = Stance::Balanced); + bool GetDefaultSpellTypeHold(uint16 spell_type, uint8 stance = Stance::Balanced); + uint16 GetDefaultSpellTypePriority(uint16 spell_type, uint8 priority_type, uint8 bot_class, uint8 stance = Stance::Balanced); + uint16 GetDefaultSpellTypeIdlePriority(uint16 spell_type, uint8 bot_class, uint8 stance = Stance::Balanced); + uint16 GetDefaultSpellTypeEngagedPriority(uint16 spell_type, uint8 bot_class, uint8 stance = Stance::Balanced); + uint16 GetDefaultSpellTypePursuePriority(uint16 spell_type, uint8 bot_class, uint8 stance = Stance::Balanced); + uint16 GetDefaultSpellTypeResistLimit(uint16 spell_type, uint8 stance = Stance::Balanced); + bool GetDefaultSpellTypeAggroCheck(uint16 spell_type, uint8 stance = Stance::Balanced); + uint8 GetDefaultSpellTypeMinManaLimit(uint16 spell_type, uint8 stance = Stance::Balanced); + uint8 GetDefaultSpellTypeMaxManaLimit(uint16 spell_type, uint8 stance = Stance::Balanced); + uint8 GetDefaultSpellTypeMinHPLimit(uint16 spell_type, uint8 stance = Stance::Balanced); + uint8 GetDefaultSpellTypeMaxHPLimit(uint16 spell_type, uint8 stance = Stance::Balanced); + uint16 GetDefaultSpellTypeAnnounceCast(uint16 spell_type, uint8 stance = Stance::Balanced); + uint16 GetDefaultSpellTypeAEOrGroupTargetCount(uint16 spell_type, uint8 stance = Stance::Balanced); + + static bool IsValidBotBaseSetting(uint16 setting_type); + static std::string GetBotSettingCategoryName(uint16 setting_type); + uint16 GetBaseSettingIDByShortName(std::string setting_string); + int GetBotBaseSetting(uint16 bot_setting); + void SetBotBaseSetting(uint16 bot_setting, int setting_value); + int GetSetting(uint16 setting_category, uint16 setting_type); + void SetBotSetting(uint8 setting_type, uint16 bot_setting, int setting_value); + void CopyBotSpellSettings(Bot* to); + void ResetBotSpellSettings(); + + void CopyBotBlockedBuffs(Bot* to); + void CopyBotBlockedPetBuffs(Bot* to); + void CleanBotBlockedBuffs(); + void ClearBotBlockedBuffs() { bot_blocked_buffs.clear(); } + bool IsBlockedBuff(int32 spell_id) override; + bool IsBlockedPetBuff(int32 spell_id) override; + void SetBotBlockedBuff(uint16 spell_id, bool block); + void SetBotBlockedPetBuff(uint16 spell_id, bool block); + std::vector GetBotBlockedBuffs() { return bot_blocked_buffs; } + void SetBotBlockedBuffs(std::vector blocked_buffs) { bot_blocked_buffs = blocked_buffs; } + + void SetBotSpellRecastTimer(uint16 spell_type, Mob* spelltar, bool pre_cast = false); + uint16 GetSpellTypePriority(uint16 spell_type, uint8 priority_type); + void SetSpellTypePriority(uint16 spell_type, uint8 priority_type, uint16 priority); + inline uint16 GetSpellTypeResistLimit(uint16 spell_type) const { return m_bot_spell_settings[spell_type].resist_limit; } + inline void SetSpellTypeResistLimit(uint16 spell_type, uint16 value) { m_bot_spell_settings[spell_type].resist_limit = value; } + inline bool GetSpellTypeAggroCheck(uint16 spell_type) const { return m_bot_spell_settings[spell_type].aggro_check; } + inline void SetSpellTypeAggroCheck(uint16 spell_type, bool value) { m_bot_spell_settings[spell_type].aggro_check = value; } + uint8 GetHPRatioForSpellType(uint16 spell_type, Mob* tar); + inline uint8 GetSpellTypeMinManaLimit(uint16 spell_type) const { return m_bot_spell_settings[spell_type].min_mana_pct; } + inline uint8 GetSpellTypeMaxManaLimit(uint16 spell_type) const { return m_bot_spell_settings[spell_type].max_mana_pct; } + inline void SetSpellTypeMinManaLimit(uint16 spell_type, uint8 value) { m_bot_spell_settings[spell_type].min_mana_pct = value; } + inline void SetSpellTypeMaxManaLimit(uint16 spell_type, uint8 value) { m_bot_spell_settings[spell_type].max_mana_pct = value; } + inline uint8 GetSpellTypeMinHPLimit(uint16 spell_type) const { return m_bot_spell_settings[spell_type].min_hp_pct; } + inline uint8 GetSpellTypeMaxHPLimit(uint16 spell_type) const { return m_bot_spell_settings[spell_type].max_hp_pct; } + inline void SetSpellTypeMinHPLimit(uint16 spell_type, uint8 value) { m_bot_spell_settings[spell_type].min_hp_pct = value; } + inline void SetSpellTypeMaxHPLimit(uint16 spell_type, uint8 value) { m_bot_spell_settings[spell_type].max_hp_pct = value; } + inline uint16 GetSpellTypeAEOrGroupTargetCount(uint16 spell_type) const { return m_bot_spell_settings[spell_type].ae_or_group_target_count; } + inline void SetSpellTypeAEOrGroupTargetCount(uint16 spell_type, uint16 value) { m_bot_spell_settings[spell_type].ae_or_group_target_count = value; } + inline uint16 GetSpellTypeAnnounceCast(uint16 spell_type) const { return m_bot_spell_settings[spell_type].announce_cast; } + inline void SetSpellTypeAnnounceCast(uint16 spell_type, uint16 value) { m_bot_spell_settings[spell_type].announce_cast = value; } + inline bool GetSpellTypeHold(uint16 spell_type) const { return m_bot_spell_settings[spell_type].hold; } + inline void SetSpellTypeHold(uint16 spell_type, bool value) { m_bot_spell_settings[spell_type].hold = value; } + inline uint16 GetSpellTypeDelay(uint16 spell_type) const { return m_bot_spell_settings[spell_type].delay; } + inline void SetSpellTypeDelay(uint16 spell_type, uint16 delay_value) { m_bot_spell_settings[spell_type].delay = delay_value; } + inline uint8 GetSpellTypeMinThreshold(uint16 spell_type) const { return m_bot_spell_settings[spell_type].min_threshold; } + inline void SetSpellTypeMinThreshold(uint16 spell_type, uint8 threshold_value) { m_bot_spell_settings[spell_type].min_threshold = threshold_value; } + inline uint8 GetSpellTypeMaxThreshold(uint16 spell_type) const { return m_bot_spell_settings[spell_type].max_threshold; } + inline void SetSpellTypeMaxThreshold(uint16 spell_type, uint8 threshold_value) { m_bot_spell_settings[spell_type].max_threshold = threshold_value; } + inline bool SpellTypeRecastCheck(uint16 spellType) { return !m_bot_spell_settings[spellType].recast_timer.GetRemainingTime(); } + void SetSpellTypeRecastTimer(uint16 spell_type, uint32 recast_time) { m_bot_spell_settings[spell_type].recast_timer.Start(recast_time); } + inline bool SpellTypeAIDelayCheck(uint16 spellType) { return !m_bot_spell_settings[spellType].ai_delay.GetRemainingTime(); } + void SetSpellTypeAITimer(uint16 spell_type, uint32 recast_time) { m_bot_spell_settings[spell_type].ai_delay.Start(recast_time); } + bool GetUltimateSpellTypeHold(uint16 spell_type, Mob* tar); + uint16 GetDefaultSpellTypeDelay(uint16 spell_type, uint8 stance = Stance::Balanced); + uint8 GetDefaultSpellTypeMinThreshold(uint16 spell_type, uint8 stance = Stance::Balanced); + uint8 GetDefaultSpellTypeMaxThreshold(uint16 spell_type, uint8 stance = Stance::Balanced); + uint16 GetUltimateSpellTypeDelay(uint16 spell_type, Mob* tar); + bool GetUltimateSpellTypeRecastCheck(uint16 spell_type, Mob* tar); + uint8 GetUltimateSpellTypeMinThreshold(uint16 spell_type, Mob* tar); + uint8 GetUltimateSpellTypeMaxThreshold(uint16 spell_type, Mob* tar); + void SetIllusionBlock(bool value) { _illusionBlock = value; } + bool GetIllusionBlock() const override { return _illusionBlock; } + bool GetShowHelm() const { return _showHelm; } + void SetShowHelm(bool show_helm) { _showHelm = show_helm; } + bool GetBehindMob() const { return _behindMobStatus; } + void SetBehindMob(bool value) { _behindMobStatus = value; } + bool GetMaxMeleeRange() const { return _maxMeleeRangeStatus; } + void SetMaxMeleeRange(bool value) { _maxMeleeRangeStatus = value; } + uint8 GetStopMeleeLevel() const { return _stopMeleeLevel; } + void SetStopMeleeLevel(uint8 level) { _stopMeleeLevel = level; } + uint32 GetBotDistanceRanged() const { return _distanceRanged; } + void SetBotDistanceRanged(uint32 distance) { _distanceRanged = distance; } + bool GetMedInCombat() const { return _medInCombat; } + void SetMedInCombat(bool value) { _medInCombat = value; } + uint8 GetSitHPPct() const { return _SitHPPct; } + void SetSitHPPct(uint8 value) { _SitHPPct = value; } + uint8 GetSitManaPct() const { return _SitManaPct; } + void SetSitManaPct(uint8 value) { _SitManaPct = value; } + + // Spell lists + std::list GetSpellTypesPrioritized(uint8 priority_type); + static uint16 GetParentSpellType(uint16 spell_type); + 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); + static bool IsValidBotSpellCategory(uint8 setting_type); + static std::string GetBotSpellCategoryName(uint8 setting_type); + static std::string GetBotSpellCategoryShortName(uint8 setting_type); + static uint16 GetBotSpellCategoryIDByShortName(std::string setting_string); + void AssignBotSpellsToTypes(std::vector& AIBot_spells, std::unordered_map>& AIBot_spells_by_type); + uint16 GetSpellByAA(int id, AA::Rank*& rank); + + // Spell Type + static uint16 GetSpellTypeIDByShortName(std::string spellType_string); + static std::string GetSpellTypeNameByID(uint16 spell_type); + static std::string GetSpellTypeShortNameByID(uint16 spell_type); + bool IsValidSubType(uint16 sub_type); + static std::string GetSubTypeNameByID(uint16 sub_type); [[nodiscard]] int GetMaxBuffSlots() const final { return EQ::spells::LONG_BUFFS; } [[nodiscard]] int GetMaxSongSlots() const final { return EQ::spells::SHORT_BUFFS; } @@ -423,33 +736,36 @@ public: ProcessBotGroupAdd(Group* group, Raid* raid, Client* client = nullptr, bool new_raid = false, bool initial = false); - static std::list GetBotSpellsForSpellEffect(Bot* botCaster, int spellEffect); - static std::list GetBotSpellsForSpellEffectAndTargetType(Bot* botCaster, int spellEffect, SpellTargetType targetType); - static std::list GetBotSpellsBySpellType(Bot* botCaster, uint32 spellType); - static std::list GetPrioritizedBotSpellsBySpellType(Bot* botCaster, uint32 spellType); + static std::list GetBotSpellsForSpellEffect(Bot* caster, uint16 spell_type, int spell_effect); + static std::list GetBotSpellsForSpellEffectAndTargetType(Bot* caster, uint16 spell_type, int spell_effect, SpellTargetType target_type); + static std::list GetBotSpellsBySpellType(Bot* caster, uint16 spell_type); + static std::vector GetPrioritizedBotSpellsBySpellType(Bot* caster, uint16 spell_type, Mob* tar, bool AE = false, uint16 sub_target_type = UINT16_MAX, uint16 sub_type = UINT16_MAX); - static BotSpell GetFirstBotSpellBySpellType(Bot* botCaster, uint32 spellType); - static BotSpell GetBestBotSpellForFastHeal(Bot* botCaster); - static BotSpell GetBestBotSpellForHealOverTime(Bot* botCaster); - static BotSpell GetBestBotSpellForPercentageHeal(Bot* botCaster); - static BotSpell GetBestBotSpellForRegularSingleTargetHeal(Bot* botCaster); - static BotSpell GetFirstBotSpellForSingleTargetHeal(Bot* botCaster); - static BotSpell GetBestBotSpellForGroupHealOverTime(Bot* botCaster); - static BotSpell GetBestBotSpellForGroupCompleteHeal(Bot* botCaster); - static BotSpell GetBestBotSpellForGroupHeal(Bot* botCaster); - static BotSpell GetBestBotSpellForMagicBasedSlow(Bot* botCaster); - static BotSpell GetBestBotSpellForDiseaseBasedSlow(Bot* botCaster); + static BotSpell GetFirstBotSpellBySpellType(Bot* caster, uint16 spell_type); + BotSpell GetSpellByHealType(uint16 spell_type, Mob* tar); + static BotSpell GetBestBotSpellForVeryFastHeal(Bot* caster, Mob* tar, uint16 spell_type = BotSpellTypes::RegularHeal); + static BotSpell GetBestBotSpellForFastHeal(Bot* caster, Mob* tar, uint16 spell_type = BotSpellTypes::RegularHeal); + static BotSpell GetBestBotSpellForHealOverTime(Bot* caster, Mob* tar, uint16 spell_type = BotSpellTypes::RegularHeal); + static BotSpell GetBestBotSpellForPercentageHeal(Bot* caster, Mob* tar, uint16 spell_type = BotSpellTypes::RegularHeal); + static BotSpell GetBestBotSpellForRegularSingleTargetHeal(Bot* caster, Mob* tar, uint16 spell_type = BotSpellTypes::RegularHeal); + static BotSpell GetFirstBotSpellForSingleTargetHeal(Bot* caster, Mob* tar, uint16 spell_type = BotSpellTypes::RegularHeal); + static BotSpell GetBestBotSpellForGroupHealOverTime(Bot* caster, Mob* tar, uint16 spell_type = BotSpellTypes::RegularHeal); + static BotSpell GetBestBotSpellForGroupCompleteHeal(Bot* caster, Mob* tar, uint16 spell_type = BotSpellTypes::RegularHeal); + static BotSpell GetBestBotSpellForGroupHeal(Bot* caster, Mob* tar, uint16 spell_type = BotSpellTypes::RegularHeal); - static Mob* GetFirstIncomingMobToMez(Bot* botCaster, BotSpell botSpell); - static BotSpell GetBestBotSpellForMez(Bot* botCaster); - static BotSpell GetBestBotMagicianPetSpell(Bot* botCaster); - static std::string GetBotMagicianPetType(Bot* botCaster); - static BotSpell GetBestBotSpellForNukeByTargetType(Bot* botCaster, SpellTargetType targetType); - static BotSpell GetBestBotSpellForStunByTargetType(Bot* botCaster, SpellTargetType targetType); - static BotSpell GetBestBotWizardNukeSpellByTargetResists(Bot* botCaster, Mob* target); - static BotSpell GetDebuffBotSpell(Bot* botCaster, Mob* target); - static BotSpell GetBestBotSpellForCure(Bot* botCaster, Mob* target); - static BotSpell GetBestBotSpellForResistDebuff(Bot* botCaster, Mob* target); + static Mob* GetFirstIncomingMobToMez(Bot* caster, uint16 spell_id, uint16 spell_type, bool AE); + static BotSpell GetBestBotSpellForMez(Bot* caster, uint16 spell_type = BotSpellTypes::Mez); + static BotSpell GetBestBotMagicianPetSpell(Bot* caster, uint16 spell_type = BotSpellTypes::Pet); + static std::string GetBotMagicianPetType(Bot* caster); + static BotSpell GetBestBotSpellForNukeByTargetType(Bot* caster, SpellTargetType target_type, uint16 spell_type, bool AE = false, Mob* tar = nullptr); + static BotSpell GetBestBotSpellForStunByTargetType(Bot* caster, SpellTargetType target_type, uint16 spell_type, bool AE = false, Mob* tar = nullptr); + static BotSpell GetBestBotWizardNukeSpellByTargetResists(Bot* caster, Mob* target, uint16 spell_type); + static BotSpell GetDebuffBotSpell(Bot* caster, Mob* target, uint16 spell_type); + static BotSpell GetBestBotSpellForCure(Bot* caster, Mob* target, uint16 spell_type); + static BotSpell GetBestBotSpellForResistDebuff(Bot* caster, Mob* target, uint16 spell_type); + static BotSpell GetBestBotSpellForNukeByBodyType(Bot* caster, uint8 body_type, uint16 spell_type, bool AE = false, Mob* tar = nullptr); + static BotSpell GetBestBotSpellForRez(Bot* caster, Mob* target, uint16 spell_type); + static BotSpell GetBestBotSpellForCharm(Bot* caster, Mob* target, uint16 spell_type); static NPCType *CreateDefaultNPCTypeStructForBot( const std::string& botName, @@ -465,19 +781,19 @@ public: // Static Bot Group Methods static bool AddBotToGroup(Bot* bot, Group* group); static bool RemoveBotFromGroup(Bot* bot, Group* group); - static void BotGroupSay(Mob *speaker, const char *msg, ...); + void RaidGroupSay(const char *msg, ...); // "GET" Class Methods uint32 GetBotID() const { return _botID; } uint32 GetBotOwnerCharacterID() const { return _botOwnerCharacterID; } uint32 GetBotSpellID() const { return npc_spells_id; } Mob* GetBotOwner() { return this->_botOwner; } - uint32 GetBotArcheryRange(); + uint32 GetBotRangedValue(); EQ::ItemInstance* GetBotItem(uint16 slot_id); bool GetSpawnStatus() { return _spawnStatus; } uint8 GetPetChooserID() { return _petChooserID; } - bool IsPetChooser() { return _petChooser; } - bool IsBotArcher() { return m_bot_archery_setting; } + bool HasControllablePet(uint8 ranks_required = 0); + bool IsBotRanged() { return _botRangedSetting; } bool IsBotCharmer() { return _botCharmer; } bool IsBot() const override { return true; } bool IsOfClientBot() const override { return true; } @@ -485,9 +801,8 @@ public: bool GetRangerAutoWeaponSelect() { return _rangerAutoWeaponSelect; } uint8 GetBotStance() { return _botStance; } - uint8 GetChanceToCastBySpellType(uint32 spellType); - bool GetBotEnforceSpellSetting() { return m_enforce_spell_settings; } - float GetBotCasterMaxRange(float melee_distance_max); + static bool IsValidBotStance(uint8 stance); + uint8 GetChanceToCastBySpellType(uint16 spell_type); bool IsGroupHealer() const { return m_CastingRoles.GroupHealer; } bool IsGroupSlower() const { return m_CastingRoles.GroupSlower; } bool IsGroupNuker() const { return m_CastingRoles.GroupNuker; } @@ -500,7 +815,7 @@ public: bool IsBotWISCaster() { return IsWISCasterClass(GetClass()); } bool IsBotSpellFighter() { return IsSpellFighterClass(GetClass()); } bool IsBotFighter() { return IsFighterClass(GetClass()); } - bool IsBotNonSpellFighter() { return IsNonSpellFighterClass(GetClass()); } + bool IsBotNonSpellFighter() { return (GetClass() == Class::Bard ? false : IsNonSpellFighterClass(GetClass())); } uint8 GetBotClass() { return GetClass(); } int GetRawACNoShield(int &shield_ac); @@ -527,8 +842,6 @@ public: std::shared_ptr* MemberOfHealRotation() { return &m_member_of_heal_rotation; } - bool GetAltOutOfCombatBehavior() const { return _altoutofcombatbehavior;} - bool GetShowHelm() const { return _showhelm; } inline int32 GetSTR() const override { return STR; } inline int32 GetSTA() const override { return STA; } inline int32 GetDEX() const override { return DEX; } @@ -584,7 +897,8 @@ public: inline const InspectMessage_Struct& GetInspectMessage() const { return _botInspectMessage; } // "Quest API" Methods - bool HasBotSpellEntry(uint16 spellid); + bool HasBotSpellEntry(uint16 spell_id); + bool CanUseBotSpell(uint16 spell_id); void ApplySpell(int spell_id, int duration = 0, int level = -1, ApplySpellType apply_type = ApplySpellType::Solo, bool allow_pets = false, bool is_raid_group_only = true); void BreakInvis(); void Escape(); @@ -600,13 +914,11 @@ public: void SetBotSpellID(uint32 newSpellID); void SetSpawnStatus(bool spawnStatus) { _spawnStatus = spawnStatus; } void SetPetChooserID(uint8 id) { _petChooserID = id; } - void SetBotArcherySetting(bool bot_archer_setting, bool save = false); + void SetBotRangedSetting(bool value) { _botRangedSetting = value; } void SetBotCharmer(bool c) { _botCharmer = c; } - void SetPetChooser(bool p) { _petChooser = p; } void SetBotOwner(Mob* botOwner) { this->_botOwner = botOwner; } void SetRangerAutoWeaponSelect(bool enable) { GetClass() == Class::Ranger ? _rangerAutoWeaponSelect = enable : _rangerAutoWeaponSelect = false; } void SetBotStance(uint8 stance_id) { _botStance = Stance::IsValid(stance_id) ? stance_id : Stance::Passive; } - void SetBotCasterRange(uint32 bot_caster_range) { m_bot_caster_range = bot_caster_range; } uint32 GetSpellRecastTimer(uint16 spell_id = 0); bool CheckSpellRecastTimer(uint16 spell_id = 0); uint32 GetSpellRecastRemainingTime(uint16 spell_id = 0); @@ -624,8 +936,6 @@ public: void ClearSpellRecastTimer(uint16 spell_id = 0); uint32 GetItemReuseRemainingTime(uint32 item_id = 0); void ClearExpiredTimers(); - void SetAltOutOfCombatBehavior(bool behavior_flag) { _altoutofcombatbehavior = behavior_flag;} - void SetShowHelm(bool showhelm) { _showhelm = showhelm; } void SetBeardColor(uint8 value) { beardcolor = value; } void SetBeard(uint8 value) { beard = value; } void SetEyeColor1(uint8 value) { eyecolor1 = value; } @@ -639,7 +949,7 @@ public: bool DyeArmor(int16 slot_id, uint32 rgb, bool all_flag = false, bool save_flag = true); int GetExpansionBitmask(); - void SetExpansionBitmask(int expansion_bitmask, bool save = true); + void SetExpansionBitmask(int expansionBitmask); void ListBotSpells(uint8 min_level); @@ -651,15 +961,12 @@ public: void ListBotSpellSettings(); void LoadBotSpellSettings(); bool UpdateBotSpellSetting(uint16 spell_id, BotSpellSetting* bs); - void SetBotEnforceSpellSetting(bool enforcespellsettings, bool save = false); - bool GetBotEnforceSpellSetting() const { return m_enforce_spell_settings; } + void SetBotEnforceSpellSetting(bool enforceSpellSettings); + bool GetBotEnforceSpellSetting() { return _enforceSpellSettings; } // Class Destructors ~Bot() override; - // Publicized protected functions - void BotRangedAttack(Mob* other); - // Publicized private functions static NPCType *FillNPCTypeStruct( uint32 botSpellsID, @@ -722,8 +1029,7 @@ public: // New accessors for BotDatabase access bool DeleteBot(); - std::vector GetBotTimers() { return bot_timers; } - void SetBotTimers(std::vector timers) { bot_timers = timers; } + std::vector GetBotTimers() { return bot_timers; } uint32 GetLastZoneID() const { return _lastZoneId; } int32 GetBaseAC() const { return _baseAC; } int32 GetBaseATK() const { return _baseATK; } @@ -750,24 +1056,11 @@ public: static uint8 spell_casting_chances[SPELL_TYPE_COUNT][Class::PLAYER_CLASS_COUNT][Stance::AEBurn][cntHSND]; - bool BotCastMez(Mob* tar, uint8 botLevel, bool checked_los, BotSpell& botSpell, Raid* raid); - bool BotCastHeal(Mob* tar, uint8 botLevel, uint8 botClass, BotSpell& botSpell, Raid* raid); - bool BotCastRoot(Mob* tar, uint8 botLevel, uint32 iSpellTypes, BotSpell& botSpell, const bool& checked_los); - bool BotCastBuff(Mob* tar, uint8 botLevel, uint8 botClass); - bool BotCastEscape(Mob*& tar, uint8 botClass, BotSpell& botSpell, uint32 iSpellTypes); - bool BotCastNuke(Mob* tar, uint8 botLevel, uint8 botClass, BotSpell& botSpell, const bool& checked_los); - bool BotCastDispel(Mob* tar, BotSpell& botSpell, uint32 iSpellTypes, const bool& checked_los); - bool BotCastPet(Mob* tar, uint8 botClass, BotSpell& botSpell); - bool BotCastCombatBuff(Mob* tar, uint8 botLevel, uint8 botClass); - bool BotCastLifetap(Mob* tar, uint8 botLevel, BotSpell& botSpell, const bool& checked_los, uint32 iSpellTypes); - bool BotCastSnare(Mob* tar, uint8 botLevel, BotSpell& botSpell, const bool& checked_los, uint32 iSpellTypes); - bool BotCastDOT(Mob* tar, uint8 botLevel, const BotSpell& botSpell, const bool& checked_los); - bool BotCastSlow(Mob* tar, uint8 botLevel, uint8 botClass, BotSpell& botSpell, const bool& checked_los, Raid* raid); - bool BotCastDebuff(Mob* tar, uint8 botLevel, BotSpell& botSpell, bool checked_los); - bool BotCastCure(Mob* tar, uint8 botClass, BotSpell& botSpell, Raid* raid); - bool BotCastHateReduction(Mob* tar, uint8 botLevel, const BotSpell& botSpell); - bool BotCastCombatSong(Mob* tar, uint8 botLevel); - bool BotCastSong(Mob* tar, uint8 botLevel); + bool BotCastMez(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spell_type); + bool BotCastHeal(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spell_type); + bool BotCastNuke(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spell_type); + bool BotCastPet(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spell_type); + bool BotCastCure(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spell_type); bool CheckIfIncapacitated(); bool IsAIProcessValid(const Client* bot_owner, const Group* bot_group, const Raid* raid); @@ -785,51 +1078,49 @@ public: Mob* tar, float tar_distance ); + bool TargetValidation(Mob* other); bool PullingFlagChecks(Client* bot_owner); - bool ReturningFlagChecks(Client* bot_owner, float fm_distance); + bool ReturningFlagChecks(Client* bot_owner, Mob* leash_owner, float fm_distance); void BotPullerProcess(Client* bot_owner, Raid* raid); - - // Movement Methods - void CalcMeleeDistances( - const Mob* tar, - const EQ::ItemInstance* const& p_item, - const EQ::ItemInstance* const& s_item, - bool behind_mob, - bool backstab_weapon, - float& melee_distance_max, - float& melee_distance - ) const; - // Combat Checks + CombatRangeOutput EvaluateCombatRange(const CombatRangeInput& input); + void SetBerserkState(); bool CheckIfCasting(float fm_distance); void HealRotationChecks(); - void CheckCombatRange(Mob* tar, float tar_distance, bool& atCombatRange, const EQ::ItemInstance*& p_item, const EQ::ItemInstance*& s_item); + + void SetCombatJitter(); + bool DoCombatPositioning(const CombatPositioningInput& input); + void RunToGoalWithJitter(glm::vec3 Goal); + bool RequiresLoSForPositioning(); + bool HasRequiredLoSForPositioning(Mob* tar); // Try Combat Methods bool TryEvade(Mob* tar); bool TryFacingTarget(Mob* tar); - bool TryRangedAttack(Mob* tar); - bool TryClassAttacks(Mob* tar); - bool TryPrimaryWeaponAttacks(Mob* tar, const EQ::ItemInstance* p_item); - bool TrySecondaryWeaponAttacks(Mob* tar, const EQ::ItemInstance* s_item); - bool TryPursueTarget(float leash_distance, glm::vec3& Goal); + bool TryPursueTarget(float leash_distance); bool TryMeditate(); bool TryAutoDefend(Client* bot_owner, float leash_distance); bool TryIdleChecks(float fm_distance); - bool TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob, glm::vec3& Goal); + bool TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob); + void DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, float leash_distance, float fm_distance); bool TryBardMovementCasts(); - void SetRangerCombatWeapon(bool atArcheryRange); + bool BotRangedAttack(Mob* other, bool can_double_attack = false); + bool CheckDoubleRangedAttack(); // Public "Refactor" Methods - static bool CheckSpawnConditions(Client* c); + static bool CheckCampSpawnConditions(Client* c); + static bool CheckHighEnoughLevelForBots(Client* c, uint8 bot_class = Class::None); + static bool CheckCreateLimit(Client* c, uint32 bot_count, uint8 bot_class = Class::None); + static bool CheckSpawnLimit(Client* c, uint8 bot_class = Class::None); protected: - void BotMeditate(bool isSitting); - bool CheckBotDoubleAttack(bool Triple = false); - void PerformTradeWithClient(int16 begin_slot_id, int16 end_slot_id, Client* client); + void BotMeditate(bool is_sitting); + bool CheckBotDoubleAttack(bool triple_attack = false); + bool CheckTripleAttack(); + void PerformTradeWithClient(int16 begin_slot_id, int16 end_slot_id, Client* client, int16 chosen_slot = INVALID_INDEX); bool AIDoSpellCast(int32 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore = nullptr) override; BotCastingRoles& GetCastingRoles() { return m_CastingRoles; } @@ -839,9 +1130,12 @@ protected: void SetGroupDoter(bool flag = true) { m_CastingRoles.GroupDoter = flag; } std::deque bot_signal_q; - std::vector AIBot_spells; - std::vector AIBot_spells_enforced; - std::vector bot_timers; + std::vector AIBot_spells; + std::vector AIBot_spells_enforced; + std::unordered_map> AIBot_spells_by_type; + + std::vector bot_timers; + std::vector bot_blocked_buffs; private: // Class Members @@ -849,9 +1143,7 @@ private: uint32 _botOwnerCharacterID; bool _spawnStatus; Mob* _botOwner; - bool m_bot_archery_setting; bool _botCharmer; - bool _petChooser; uint8 _petChooserID; bool berserk; EQ::InventoryProfile m_inv; @@ -870,25 +1162,30 @@ private: unsigned int RestRegenMana; unsigned int RestRegenEndurance; Timer rest_timer; - Timer ping_timer; + Timer m_ping_timer; int32 base_end; int32 cur_end; int32 max_end; int32 end_regen; - Timer m_evade_timer; // can be moved to pTimers at some point + Timer m_rogue_evade_timer; // Rogue evade timer + Timer m_monk_evade_timer; // Monk evade FD timer Timer m_auto_defend_timer; - Timer auto_save_timer; + Timer m_auto_save_timer; + + Timer m_combat_jitter_timer; + bool m_dirtyautohaters; bool m_guard_flag; bool m_hold_flag; bool m_attack_flag; + bool m_combat_round_alert_flag; bool m_attacking_flag; bool m_pull_flag; bool m_pulling_flag; bool m_returning_flag; bool is_using_item_click; - uint32 m_bot_caster_range; + BotCastingRoles m_CastingRoles; std::map bot_spell_settings; @@ -896,12 +1193,31 @@ private: std::shared_ptr m_member_of_heal_rotation; InspectMessage_Struct _botInspectMessage; - bool _altoutofcombatbehavior; - bool _showhelm; bool _pauseAI; + + int _expansionBitmask; + bool _enforceSpellSettings; + bool _showHelm; + bool _botRangedSetting; uint8 _stopMeleeLevel; - int m_expansion_bitmask; - bool m_enforce_spell_settings; + uint32 _distanceRanged; + bool _behindMobStatus; + bool _maxMeleeRangeStatus; + bool _medInCombat; + uint8 _SitHPPct; + uint8 _SitManaPct; + uint16 _castedSpellType; + bool _hasLoS; + bool _commandedSpell; + bool _pullingSpell; + + bool _illusionBlock; + std::vector m_bot_spell_settings; + std::vector _spell_target_list; + std::vector _group_spell_target_list; + Raid* _storedRaid; + bool _verifiedRaid; + uint16 _tempSpellType; // this is used to check the spell type being cast against ^spellannouncecasts status // Private "base stats" Members int32 _baseMR; @@ -930,6 +1246,7 @@ private: int32 GenerateBaseManaPoints(); void GenerateSpecialAttacks(); void SetBotID(uint32 botID); + void SetCombatRoundForAlerts(bool flag = true) { m_combat_round_alert_flag = flag; } void SetAttackingFlag(bool flag = true) { m_attacking_flag = flag; } void SetPullingFlag(bool flag = true) { m_pulling_flag = flag; } void SetReturningFlag(bool flag = true) { m_returning_flag = flag; } @@ -948,6 +1265,6 @@ private: int32 CalcItemATKCap() final; }; -bool IsSpellInBotList(DBbotspells_Struct* spell_list, uint16 iSpellID); +bool IsSpellInBotList(DBbotspells_Struct* spell_list, uint16 spell_id); #endif // BOT_H diff --git a/zone/bot_command.cpp b/zone/bot_command.cpp index b15531dd7..a697fc6bb 100644 --- a/zone/bot_command.cpp +++ b/zone/bot_command.cpp @@ -57,1174 +57,12 @@ #include "water_map.h" #include "worldserver.h" #include "mob.h" +#include "bot_database.h" #include extern QueryServ* QServ; extern WorldServer worldserver; -extern TaskManager *task_manager; - -bcst_map bot_command_spells; -bcst_required_bot_classes_map required_bots_map; -bcst_required_bot_classes_map_by_class required_bots_map_by_class; - -class BCSpells -{ -public: - static void Load() { - bot_command_spells.clear(); - bcst_levels_map bot_levels_map; - - for (int i = BCEnum::SpellTypeFirst; i <= BCEnum::SpellTypeLast; ++i) { - bot_command_spells[static_cast(i)]; - bot_levels_map[static_cast(i)]; - } - - for (int spell_id = 2; spell_id < SPDAT_RECORDS; ++spell_id) { - if (!IsValidSpell(spell_id)) { - continue; - } - - if (spells[spell_id].player_1[0] == '\0') { - continue; - } - - if ( - spells[spell_id].target_type != ST_Target && - spells[spell_id].cast_restriction != 0 - ) { - continue; - } - - auto target_type = BCEnum::TT_None; - switch (spells[spell_id].target_type) { - case ST_GroupTeleport: - target_type = BCEnum::TT_GroupV1; - break; - case ST_AECaster: - // Disabled until bot code works correctly - //target_type = BCEnum::TT_AECaster; - break; - case ST_AEBard: - // Disabled until bot code works correctly - //target_type = BCEnum::TT_AEBard; - break; - case ST_Target: - switch (spells[spell_id].cast_restriction) { - case 0: - target_type = BCEnum::TT_Single; - break; - case 104: - target_type = BCEnum::TT_Animal; - break; - case 105: - target_type = BCEnum::TT_Plant; - break; - case 118: - target_type = BCEnum::TT_Summoned; - break; - case 120: - target_type = BCEnum::TT_Undead; - break; - default: - break; - } - break; - case ST_Self: - target_type = BCEnum::TT_Self; - break; - case ST_AETarget: - // Disabled until bot code works correctly - //target_type = BCEnum::TT_AETarget; - break; - case ST_Animal: - target_type = BCEnum::TT_Animal; - break; - case ST_Undead: - target_type = BCEnum::TT_Undead; - break; - case ST_Summoned: - target_type = BCEnum::TT_Summoned; - break; - case ST_Corpse: - target_type = BCEnum::TT_Corpse; - break; - case ST_Plant: - target_type = BCEnum::TT_Plant; - break; - case ST_Group: - target_type = BCEnum::TT_GroupV2; - break; - default: - break; - } - if (target_type == BCEnum::TT_None) - continue; - - uint8 class_levels[16] = {0}; - bool player_spell = false; - for (int class_type = Class::Warrior; class_type <= Class::Berserker; ++class_type) { - int class_index = CLASSIDTOINDEX(class_type); - if (spells[spell_id].classes[class_index] == 0 || - spells[spell_id].classes[class_index] > HARD_LEVEL_CAP) { - continue; - } - - class_levels[class_index] = spells[spell_id].classes[class_index]; - player_spell = true; - } - if (!player_spell) - continue; - - STBaseEntry* entry_prototype = nullptr; - while (true) { - switch (spells[spell_id].effect_id[EFFECTIDTOINDEX(1)]) { - case SE_BindAffinity: - entry_prototype = new STBaseEntry(BCEnum::SpT_BindAffinity); - break; - case SE_Charm: - if (spells[spell_id].spell_affect_index != 12) - break; - entry_prototype = new STCharmEntry(); - if (spells[spell_id].resist_difficulty <= -1000) - entry_prototype->SafeCastToCharm()->dire = true; - break; - case SE_Teleport: - entry_prototype = new STDepartEntry; - entry_prototype->SafeCastToDepart()->single = !BCSpells::IsGroupType(target_type); - break; - case SE_Succor: - if (!strcmp(spells[spell_id].teleport_zone, "same")) { - entry_prototype = new STEscapeEntry; - } else { - entry_prototype = new STDepartEntry; - entry_prototype->SafeCastToDepart()->single = !BCSpells::IsGroupType(target_type); - } - break; - case SE_Translocate: - if (spells[spell_id].teleport_zone[0] == '\0') { - entry_prototype = new STSendHomeEntry(); - entry_prototype->SafeCastToSendHome()->group = BCSpells::IsGroupType(target_type); - } else { - entry_prototype = new STDepartEntry; - entry_prototype->SafeCastToDepart()->single = !BCSpells::IsGroupType(target_type); - } - break; - case SE_ModelSize: - if (spells[spell_id].base_value[EFFECTIDTOINDEX(1)] > 100) { - entry_prototype = new STSizeEntry; - entry_prototype->SafeCastToSize()->size_type = BCEnum::SzT_Enlarge; - } else if (spells[spell_id].base_value[EFFECTIDTOINDEX(1)] > 0 && - spells[spell_id].base_value[EFFECTIDTOINDEX(1)] < 100) { - entry_prototype = new STSizeEntry; - entry_prototype->SafeCastToSize()->size_type = BCEnum::SzT_Reduce; - } - break; - case SE_Identify: - entry_prototype = new STBaseEntry(BCEnum::SpT_Identify); - break; - case SE_Invisibility: - if (spells[spell_id].spell_affect_index != 9) - break; - entry_prototype = new STInvisibilityEntry; - entry_prototype->SafeCastToInvisibility()->invis_type = BCEnum::IT_Living; - break; - case SE_SeeInvis: - if (spells[spell_id].spell_affect_index != 5) - break; - entry_prototype = new STInvisibilityEntry; - entry_prototype->SafeCastToInvisibility()->invis_type = BCEnum::IT_See; - break; - case SE_InvisVsUndead: - if (spells[spell_id].spell_affect_index != 9) - break; - entry_prototype = new STInvisibilityEntry; - entry_prototype->SafeCastToInvisibility()->invis_type = BCEnum::IT_Undead; - break; - case SE_InvisVsAnimals: - if (spells[spell_id].spell_affect_index != 9) - break; - entry_prototype = new STInvisibilityEntry; - entry_prototype->SafeCastToInvisibility()->invis_type = BCEnum::IT_Animal; - break; - case SE_Mez: - if (spells[spell_id].effect_id[EFFECTIDTOINDEX(1)] != 31) - break; - entry_prototype = new STBaseEntry(BCEnum::SpT_Mesmerize); - break; - case SE_Revive: - if (spells[spell_id].spell_affect_index != 1) - break; - entry_prototype = new STResurrectEntry(); - entry_prototype->SafeCastToResurrect()->aoe = BCSpells::IsCasterCentered(target_type); - break; - case SE_Rune: - if (spells[spell_id].spell_affect_index != 2) - break; - entry_prototype = new STBaseEntry(BCEnum::SpT_Rune); - break; - case SE_SummonCorpse: - entry_prototype = new STBaseEntry(BCEnum::SpT_SummonCorpse); - break; - case SE_WaterBreathing: - entry_prototype = new STBaseEntry(BCEnum::SpT_WaterBreathing); - break; - default: - break; - } - if (entry_prototype) - break; - - switch (spells[spell_id].effect_id[EFFECTIDTOINDEX(2)]) { - case SE_Succor: - entry_prototype = new STEscapeEntry; - std::string is_lesser = spells[spell_id].name; - if (is_lesser.find("Lesser") != std::string::npos) - entry_prototype->SafeCastToEscape()->lesser = true; - break; - } - if (entry_prototype) - break; - - switch (spells[spell_id].effect_id[EFFECTIDTOINDEX(3)]) { - case SE_Lull: - entry_prototype = new STBaseEntry(BCEnum::SpT_Lull); - break; - case SE_Levitate: // needs more criteria - entry_prototype = new STBaseEntry(BCEnum::SpT_Levitation); - break; - default: - break; - } - if (entry_prototype) - break; - - while (spells[spell_id].type_description_id == 27) { - if (!spells[spell_id].good_effect) - break; - if (spells[spell_id].skill != EQ::skills::SkillOffense && - spells[spell_id].skill != EQ::skills::SkillDefense) - break; - - entry_prototype = new STStanceEntry(); - if (spells[spell_id].skill == EQ::skills::SkillOffense) - entry_prototype->SafeCastToStance()->stance_type = BCEnum::StT_Aggressive; - else - entry_prototype->SafeCastToStance()->stance_type = BCEnum::StT_Defensive; - - break; - } - if (entry_prototype) - break; - - switch (spells[spell_id].spell_affect_index) { - case 1: { - bool valid_spell = false; - entry_prototype = new STCureEntry; - - for (int i = EffectIDFirst; i <= EffectIDLast; ++i) { - int effect_index = EFFECTIDTOINDEX(i); - if (spells[spell_id].effect_id[effect_index] != SE_Blind && - spells[spell_id].base_value[effect_index] >= 0) - continue; - else if (spells[spell_id].effect_id[effect_index] == SE_Blind && - !spells[spell_id].good_effect) - continue; - - switch (spells[spell_id].effect_id[effect_index]) { - case SE_Blind: - entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX( - BCEnum::AT_Blindness)] += spells[spell_id].base_value[effect_index]; - break; - case SE_DiseaseCounter: - entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX( - BCEnum::AT_Disease)] += spells[spell_id].base_value[effect_index]; - break; - case SE_PoisonCounter: - entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX( - BCEnum::AT_Poison)] += spells[spell_id].base_value[effect_index]; - break; - case SE_CurseCounter: - entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX( - BCEnum::AT_Curse)] += spells[spell_id].base_value[effect_index]; - break; - case SE_CorruptionCounter: - entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX( - BCEnum::AT_Corruption)] += spells[spell_id].base_value[effect_index]; - break; - default: - continue; - } - entry_prototype->SafeCastToCure()->cure_total += spells[spell_id].base_value[effect_index]; - valid_spell = true; - } - if (!valid_spell) { - safe_delete(entry_prototype); - entry_prototype = nullptr; - } - - break; - } - case 2: { - bool valid_spell = false; - entry_prototype = new STResistanceEntry; - - for (int i = EffectIDFirst; i <= EffectIDLast; ++i) { - int effect_index = EFFECTIDTOINDEX(i); - if (spells[spell_id].max_value[effect_index] <= 0) - continue; - - switch (spells[spell_id].effect_id[effect_index]) { - case SE_ResistFire: - entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX( - BCEnum::RT_Fire)] += spells[spell_id].max_value[effect_index]; - break; - case SE_ResistCold: - entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX( - BCEnum::RT_Cold)] += spells[spell_id].max_value[effect_index]; - break; - case SE_ResistPoison: - entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX( - BCEnum::RT_Poison)] += spells[spell_id].max_value[effect_index]; - break; - case SE_ResistDisease: - entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX( - BCEnum::RT_Disease)] += spells[spell_id].max_value[effect_index]; - break; - case SE_ResistMagic: - entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX( - BCEnum::RT_Magic)] += spells[spell_id].max_value[effect_index]; - break; - case SE_ResistCorruption: - entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX( - BCEnum::RT_Corruption)] += spells[spell_id].max_value[effect_index]; - break; - default: - continue; - } - entry_prototype->SafeCastToResistance()->resist_total += spells[spell_id].max_value[effect_index]; - valid_spell = true; - } - if (!valid_spell) { - safe_delete(entry_prototype); - entry_prototype = nullptr; - } - - break; - } - case 7: - case 10: - if (spells[spell_id].effect_description_id != 65) - break; - if (IsEffectInSpell(spell_id, SE_NegateIfCombat)) - break; - entry_prototype = new STMovementSpeedEntry(); - entry_prototype->SafeCastToMovementSpeed()->group = BCSpells::IsGroupType(target_type); - break; - default: - break; - } - if (entry_prototype) - break; - - break; - } - if (!entry_prototype) - continue; - - if (target_type == BCEnum::TT_Self && (entry_prototype->BCST() != BCEnum::SpT_Stance && - entry_prototype->BCST() != BCEnum::SpT_SummonCorpse)) { -#ifdef BCSTSPELLDUMP - LogError("DELETING entry_prototype (primary clause) - name: [{}], target_type: [{}], BCST: [{}]", - spells[spell_id].name, BCEnum::TargetTypeEnumToString(target_type).c_str(), BCEnum::SpellTypeEnumToString(entry_prototype->BCST()).c_str()); -#endif - safe_delete(entry_prototype); - continue; - } - if (entry_prototype->BCST() == BCEnum::SpT_Stance && target_type != BCEnum::TT_Self) { -#ifdef BCSTSPELLDUMP - LogError("DELETING entry_prototype (secondary clause) - name: [{}], BCST: [{}], target_type: [{}]", - spells[spell_id].name, BCEnum::SpellTypeEnumToString(entry_prototype->BCST()).c_str(), BCEnum::TargetTypeEnumToString(target_type).c_str()); -#endif - safe_delete(entry_prototype); - continue; - } - - assert(entry_prototype->BCST() != BCEnum::SpT_None); - - entry_prototype->spell_id = spell_id; - entry_prototype->target_type = target_type; - - bcst_levels& bot_levels = bot_levels_map[entry_prototype->BCST()]; - for (int class_type = Class::Warrior; class_type <= Class::Berserker; ++class_type) { - int class_index = CLASSIDTOINDEX(class_type); - if (!class_levels[class_index]) - continue; - - STBaseEntry* spell_entry = nullptr; - switch (entry_prototype->BCST()) { - case BCEnum::SpT_Charm: - if (entry_prototype->IsCharm()) - spell_entry = new STCharmEntry(entry_prototype->SafeCastToCharm()); - break; - case BCEnum::SpT_Cure: - if (entry_prototype->IsCure()) - spell_entry = new STCureEntry(entry_prototype->SafeCastToCure()); - break; - case BCEnum::SpT_Depart: - if (entry_prototype->IsDepart()) - spell_entry = new STDepartEntry(entry_prototype->SafeCastToDepart()); - break; - case BCEnum::SpT_Escape: - if (entry_prototype->IsEscape()) - spell_entry = new STEscapeEntry(entry_prototype->SafeCastToEscape()); - break; - case BCEnum::SpT_Invisibility: - if (entry_prototype->IsInvisibility()) - spell_entry = new STInvisibilityEntry(entry_prototype->SafeCastToInvisibility()); - break; - case BCEnum::SpT_MovementSpeed: - if (entry_prototype->IsMovementSpeed()) - spell_entry = new STMovementSpeedEntry(entry_prototype->SafeCastToMovementSpeed()); - break; - case BCEnum::SpT_Resistance: - if (entry_prototype->IsResistance()) - spell_entry = new STResistanceEntry(entry_prototype->SafeCastToResistance()); - break; - case BCEnum::SpT_Resurrect: - if (entry_prototype->IsResurrect()) - spell_entry = new STResurrectEntry(entry_prototype->SafeCastToResurrect()); - break; - case BCEnum::SpT_SendHome: - if (entry_prototype->IsSendHome()) - spell_entry = new STSendHomeEntry(entry_prototype->SafeCastToSendHome()); - break; - case BCEnum::SpT_Size: - if (entry_prototype->IsSize()) - spell_entry = new STSizeEntry(entry_prototype->SafeCastToSize()); - break; - case BCEnum::SpT_Stance: - if (entry_prototype->IsStance()) - spell_entry = new STStanceEntry(entry_prototype->SafeCastToStance()); - break; - default: - spell_entry = new STBaseEntry(entry_prototype); - break; - } - - assert(spell_entry); - - spell_entry->caster_class = class_type; - spell_entry->spell_level = class_levels[class_index]; - - bot_command_spells[spell_entry->BCST()].push_back(spell_entry); - - if (bot_levels.find(class_type) == bot_levels.end() || - bot_levels[class_type] > class_levels[class_index]) - bot_levels[class_type] = class_levels[class_index]; - } - - delete(entry_prototype); - } - - remove_inactive(); - order_all(); - load_teleport_zone_names(); - build_strings(bot_levels_map); - status_report(); - -#ifdef BCSTSPELLDUMP - spell_dump(); -#endif - } - - static void Unload() { - for (auto map_iter : bot_command_spells) { - if (map_iter.second.empty()) - continue; - for (auto list_iter: map_iter.second) { - safe_delete(list_iter); - } - map_iter.second.clear(); - } - bot_command_spells.clear(); - required_bots_map.clear(); - required_bots_map_by_class.clear(); - } - - static bool IsCasterCentered(BCEnum::TType target_type) { - switch (target_type) { - case BCEnum::TT_AECaster: - case BCEnum::TT_AEBard: - return true; - default: - return false; - } - } - - static bool IsGroupType(BCEnum::TType target_type) { - switch (target_type) { - case BCEnum::TT_GroupV1: - case BCEnum::TT_GroupV2: - return true; - default: - return false; - } - } -private: - static void remove_inactive() { - if (bot_command_spells.empty()) - return; - - for (auto map_iter = bot_command_spells.begin(); map_iter != bot_command_spells.end(); ++map_iter) { - if (map_iter->second.empty()) - continue; - - bcst_list* spells_list = &map_iter->second; - bcst_list* removed_spells_list = new bcst_list; - - spells_list->remove(nullptr); - spells_list->remove_if([removed_spells_list](STBaseEntry* l) { - if (l->spell_id < 2 || l->spell_id >= SPDAT_RECORDS || strlen(spells[l->spell_id].name) < 3) { - removed_spells_list->push_back(l); - return true; - } - else { - return false; - } - }); - - for (auto del_iter: *removed_spells_list) - { - safe_delete(del_iter); - } - removed_spells_list->clear(); - - if (RuleI(Bots, CommandSpellRank) == 1) { - spells_list->sort([](STBaseEntry* l, STBaseEntry* r) { - if (spells[l->spell_id].spell_group < spells[r->spell_id].spell_group) - return true; - if (spells[l->spell_id].spell_group == spells[r->spell_id].spell_group && l->caster_class < r->caster_class) - return true; - if (spells[l->spell_id].spell_group == spells[r->spell_id].spell_group && l->caster_class == r->caster_class && spells[l->spell_id].rank < spells[r->spell_id].rank) - return true; - - return false; - }); - spells_list->unique([removed_spells_list](STBaseEntry* l, STBaseEntry* r) { - std::string r_name = spells[r->spell_id].name; - if (spells[l->spell_id].spell_group == spells[r->spell_id].spell_group && l->caster_class == r->caster_class && spells[l->spell_id].rank < spells[r->spell_id].rank) { - removed_spells_list->push_back(r); - return true; - } - - return false; - }); - - for (auto del_iter: *removed_spells_list) { - safe_delete(del_iter); - } - removed_spells_list->clear(); - } - - if (RuleI(Bots, CommandSpellRank) == 2) { - spells_list->remove_if([removed_spells_list](STBaseEntry* l) { - std::string l_name = spells[l->spell_id].name; - if (spells[l->spell_id].rank == 10) { - removed_spells_list->push_back(l); - return true; - } - if (l_name.find("III") == (l_name.size() - 3)) { - removed_spells_list->push_back(l); - return true; - } - if (l_name.find("III ") == (l_name.size() - 4)) { - removed_spells_list->push_back(l); - return true; - } - - return false; - }); - - for (auto del_iter: *removed_spells_list) { - safe_delete(del_iter); - } - removed_spells_list->clear(); - } - - // needs rework - if (RuleI(Bots, CommandSpellRank) == 2 || RuleI(Bots, CommandSpellRank) == 3) { - spells_list->sort([](STBaseEntry* l, STBaseEntry* r) { - if (spells[l->spell_id].spell_group < spells[r->spell_id].spell_group) - return true; - if (spells[l->spell_id].spell_group == spells[r->spell_id].spell_group && l->caster_class < r->caster_class) - return true; - if (spells[l->spell_id].spell_group == spells[r->spell_id].spell_group && l->caster_class == r->caster_class && spells[l->spell_id].rank > spells[r->spell_id].rank) - return true; - - return false; - }); - spells_list->unique([removed_spells_list](STBaseEntry* l, STBaseEntry* r) { - std::string l_name = spells[l->spell_id].name; - if (spells[l->spell_id].spell_group == spells[r->spell_id].spell_group && l->caster_class == r->caster_class && spells[l->spell_id].rank > spells[r->spell_id].rank) { - removed_spells_list->push_back(r); - return true; - } - - return false; - }); - - for (auto del_iter: *removed_spells_list) { - safe_delete(del_iter); - } - removed_spells_list->clear(); - } - - safe_delete(removed_spells_list); - } - } - - static void order_all() { - // Example of a macro'd lambda using anonymous property dereference: - // #define XXX(p) ([](const <_Ty>* l, const <_Ty>* r) { return (l->p < r->p); }) - - -#define LT_STBASE(l, r, p) (l->p < r->p) -#define LT_STCHARM(l, r, p) (l->SafeCastToCharm()->p < r->SafeCastToCharm()->p) -#define LT_STCURE(l, r, p) (l->SafeCastToCure()->p < r->SafeCastToCure()->p) -#define LT_STCURE_VAL_ID(l, r, p, ctid) (l->SafeCastToCure()->p[AILMENTIDTOINDEX(ctid)] < r->SafeCastToCure()->p[AILMENTIDTOINDEX(ctid)]) -#define LT_STDEPART(l, r, p) (l->SafeCastToDepart()->p < r->SafeCastToDepart()->p) -#define LT_STESCAPE(l, r, p) (l->SafeCastToEscape()->p < r->SafeCastToEscape()->p) -#define LT_STINVISIBILITY(l, r, p) (l->SafeCastToInvisibility()->p < r->SafeCastToInvisibility()->p) -#define LT_STRESISTANCE(l, r, p) (l->SafeCastToResistance()->p < r->SafeCastToResistance()->p) -#define LT_STRESISTANCE_VAL_ID(l, r, p, rtid) (l->SafeCastToResistance()->p[RESISTANCEIDTOINDEX(rtid)] < r->SafeCastToResistance()->p[RESISTANCEIDTOINDEX(rtid)) -#define LT_STSTANCE(l, r, p) (l->SafeCastToStance()->p < r->SafeCastToStance()->p) -#define LT_SPELLS(l, r, p) (spells[l->spell_id].p < spells[r->spell_id].p) -#define LT_SPELLS_EFFECT_ID(l, r, p, eid) (spells[l->spell_id].p[EFFECTIDTOINDEX(eid)] < spells[r->spell_id].p[EFFECTIDTOINDEX(eid)]) -#define LT_SPELLS_STR(l, r, s) (strcasecmp(spells[l->spell_id].s, spells[r->spell_id].s) < 0) - -#define EQ_STBASE(l, r, p) (l->p == r->p) -#define EQ_STCHARM(l, r, p) (l->SafeCastToCharm()->p == r->SafeCastToCharm()->p) -#define EQ_STCURE(l, r, p) (l->SafeCastToCure()->p == r->SafeCastToCure()->p) -#define EQ_STCURE_VAL_ID(l, r, p, ctid) (l->SafeCastToCure()->p[AILMENTIDTOINDEX(ctid)] == r->SafeCastToCure()->p[AILMENTIDTOINDEX(ctid)]) -#define EQ_STDEPART(l, r, p) (l->SafeCastToDepart()->p == r->SafeCastToDepart()->p) -#define EQ_STESCAPE(l, r, p) (l->SafeCastToEscape()->p == r->SafeCastToEscape()->p) -#define EQ_STINVISIBILITY(l, r, p) (l->SafeCastToInvisibility()->p == r->SafeCastToInvisibility()->p) -#define EQ_STRESISTANCE(l, r, p) (l->SafeCastToResistance()->p == r->SafeCastToResistance()->p) -#define EQ_STRESISTANCE_VAL_ID(l, r, p, rtid) (l->SafeCastToResistance()->p[RESISTANCEIDTOINDEX(rtid)] == r->SafeCastToResistance()->p[RESISTANCEIDTOINDEX(rtid)) -#define EQ_STSTANCE(l, r, p) (l->SafeCastToStance()->p == r->SafeCastToStance()->p) -#define EQ_SPELLS(l, r, p) (spells[l->spell_id].p == spells[r->spell_id].p) -#define EQ_SPELLS_EFFECT_ID(l, r, p, eid) (spells[l->spell_id].p[EFFECTIDTOINDEX(eid)] == spells[r->spell_id].p[EFFECTIDTOINDEX(eid)]) -#define EQ_SPELLS_STR(l, r, s) (strcasecmp(spells[l->spell_id].s, spells[r->spell_id].s) == 0) - -#define GT_STBASE(l, r, p) (l->p > r->p) -#define GT_STCHARM(l, r, p) (l->SafeCastToCharm()->p > r->SafeCastToCharm()->p) -#define GT_STCURE(l, r, p) (l->SafeCastToCure()->p > r->SafeCastToCure()->p) -#define GT_STCURE_VAL_ID(l, r, p, ctid) (l->SafeCastToCure()->p[AILMENTIDTOINDEX(ctid)] > r->SafeCastToCure()->p[AILMENTIDTOINDEX(ctid)]) -#define GT_STDEPART(l, r, p) (l->SafeCastToDepart()->p > r->SafeCastToDepart()->p) -#define GT_STESCAPE(l, r, p) (l->SafeCastToEscape()->p > r->SafeCastToEscape()->p) -#define GT_STINVISIBILITY(l, r, p) (l->SafeCastToInvisibility()->p > r->SafeCastToInvisibility()->p) -#define GT_STRESISTANCE(l, r, p) (l->SafeCastToResistance()->p > r->SafeCastToResistance()->p) -#define GT_STRESISTANCE_VAL_ID(l, r, p, rtid) (l->SafeCastToResistance()->p[RESISTANCEIDTOINDEX(rtid)] > r->SafeCastToResistance()->p[RESISTANCEIDTOINDEX(rtid)) -#define GT_STSTANCE(l, r, p) (l->SafeCastToStance()->p > r->SafeCastToStance()->p) -#define GT_SPELLS(l, r, p) (spells[l->spell_id].p > spells[r->spell_id].p) -#define GT_SPELLS_EFFECT_ID(l, r, p, eid) (spells[l->spell_id].p[EFFECTIDTOINDEX(eid)] > spells[r->spell_id].p[EFFECTIDTOINDEX(eid)]) -#define GT_SPELLS_STR(l, r, s) (strcasecmp(spells[l->spell_id].s, spells[r->spell_id].s) > 0) - - - for (auto map_iter = bot_command_spells.begin(); map_iter != bot_command_spells.end(); ++map_iter) { - if (map_iter->second.size() < 2) - continue; - - auto spell_type = map_iter->first; - bcst_list* spell_list = &map_iter->second; - switch (spell_type) { - case BCEnum::SpT_BindAffinity: - if (RuleB(Bots, PreferNoManaCommandSpells)) { - spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { - if (LT_SPELLS(l, r, mana)) - return true; - if (EQ_SPELLS(l, r, mana) && LT_STBASE(l, r, target_type)) - return true; - if (EQ_SPELLS(l, r, mana) && EQ_STBASE(l, r, target_type) && LT_STBASE(l, r, spell_level)) - return true; - if (EQ_SPELLS(l, r, mana) && EQ_STBASE(l, r, target_type) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) - return true; - - return false; - }); - } - else { - spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { - if (LT_STBASE(l, r, target_type)) - return true; - if (EQ_STBASE(l, r, target_type) && LT_STBASE(l, r, spell_level)) - return true; - if (EQ_STBASE(l, r, target_type) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) - return true; - - return false; - }); - } - continue; - case BCEnum::SpT_Charm: - spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { - if (LT_SPELLS(l, r, resist_difficulty)) - return true; - if (EQ_SPELLS(l, r, resist_difficulty) && LT_STBASE(l, r, target_type)) - return true; - if (EQ_SPELLS(l, r, resist_difficulty) && EQ_STBASE(l, r, target_type) && GT_SPELLS_EFFECT_ID(l, r, max_value, 1)) - return true; - if (EQ_SPELLS(l, r, resist_difficulty) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max_value, 1) && LT_STBASE(l, r, spell_level)) - return true; - if (EQ_SPELLS(l, r, resist_difficulty) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max_value, 1) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) - return true; - - return false; - }); - continue; - case BCEnum::SpT_Cure: // per-use sorting in command handler - spell_list->sort([](STBaseEntry* l, STBaseEntry* r) { - if (l->spell_id < r->spell_id) - return true; - if (l->spell_id == r->spell_id && LT_STBASE(l, r, caster_class)) - return true; - - return false; - }); - continue; - case BCEnum::SpT_Depart: - spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { - if (LT_STBASE(l, r, target_type)) - return true; - if (EQ_STBASE(l, r, target_type) && LT_STBASE(l, r, caster_class)) - return true; - if (EQ_STBASE(l, r, target_type) && EQ_STBASE(l, r, caster_class) && LT_STBASE(l, r, spell_level)) - return true; - if (EQ_STBASE(l, r, target_type) && EQ_STBASE(l, r, caster_class) && EQ_STBASE(l, r, spell_level) && LT_SPELLS_STR(l, r, name)) - return true; - - return false; - }); - continue; - case BCEnum::SpT_Escape: - spell_list->sort([](STBaseEntry* l, STBaseEntry* r) { - if (LT_STESCAPE(l, r, lesser)) - return true; - if (EQ_STESCAPE(l, r, lesser) && LT_STBASE(l, r, target_type)) - return true; - if (EQ_STESCAPE(l, r, lesser) && EQ_STBASE(l, r, target_type) && GT_STBASE(l, r, spell_level)) - return true; - if (EQ_STESCAPE(l, r, lesser) && EQ_STBASE(l, r, target_type) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) - return true; - - return false; - }); - continue; - case BCEnum::SpT_Identify: - if (RuleB(Bots, PreferNoManaCommandSpells)) { - spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { - if (LT_SPELLS(l, r, mana)) - return true; - if (EQ_SPELLS(l, r, mana) && LT_STBASE(l, r, target_type)) - return true; - if (EQ_SPELLS(l, r, mana) && EQ_STBASE(l, r, target_type) && LT_STBASE(l, r, spell_level)) - return true; - if (EQ_SPELLS(l, r, mana) && EQ_STBASE(l, r, target_type) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) - return true; - - return false; - }); - } - else { - spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { - if (LT_STBASE(l, r, target_type)) - return true; - if (EQ_STBASE(l, r, target_type) && LT_STBASE(l, r, spell_level)) - return true; - if (EQ_STBASE(l, r, target_type) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) - return true; - - return false; - }); - } - continue; - case BCEnum::SpT_Invisibility: - spell_list->sort([](STBaseEntry* l, STBaseEntry* r) { - if (LT_STINVISIBILITY(l, r, invis_type)) - return true; - if (EQ_STINVISIBILITY(l, r, invis_type) && LT_STBASE(l, r, target_type)) - return true; - if (EQ_STINVISIBILITY(l, r, invis_type) && EQ_STBASE(l, r, target_type) && GT_STBASE(l, r, spell_level)) - return true; - if (EQ_STINVISIBILITY(l, r, invis_type) && EQ_STBASE(l, r, target_type) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) - return true; - return false; - }); - continue; - case BCEnum::SpT_Levitation: - spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { - if (LT_STBASE(l, r, target_type)) - return true; - if (EQ_STBASE(l, r, target_type) && LT_SPELLS(l, r, zone_type)) - return true; - if (EQ_STBASE(l, r, target_type) && EQ_SPELLS(l, r, zone_type) && GT_STBASE(l, r, spell_level)) - return true; - if (EQ_STBASE(l, r, target_type) && EQ_SPELLS(l, r, zone_type) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) - return true; - - return false; - }); - continue; - case BCEnum::SpT_Lull: - spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { - if (LT_SPELLS(l, r, resist_difficulty)) - return true; - if (EQ_SPELLS(l, r, resist_difficulty) && LT_STBASE(l, r, target_type)) - return true; - if (EQ_SPELLS(l, r, resist_difficulty) && EQ_STBASE(l, r, target_type) && GT_SPELLS_EFFECT_ID(l, r, max_value, 3)) - return true; - if (EQ_SPELLS(l, r, resist_difficulty) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max_value, 3) && LT_STBASE(l, r, spell_level)) - return true; - if (EQ_SPELLS(l, r, resist_difficulty) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max_value, 3) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) - return true; - - return false; - }); - continue; - case BCEnum::SpT_Mesmerize: - spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { - if (GT_SPELLS(l, r, resist_difficulty)) - return true; - if (EQ_SPELLS(l, r, resist_difficulty) && LT_STBASE(l, r, target_type)) - return true; - if (EQ_SPELLS(l, r, resist_difficulty) && EQ_STBASE(l, r, target_type) && GT_SPELLS_EFFECT_ID(l, r, max_value, 1)) - return true; - if (EQ_SPELLS(l, r, resist_difficulty) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max_value, 1) && GT_STBASE(l, r, spell_level)) - return true; - if (EQ_SPELLS(l, r, resist_difficulty) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max_value, 1) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) - return true; - - return false; - }); - continue; - case BCEnum::SpT_MovementSpeed: - spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { - if (LT_STBASE(l, r, target_type)) - return true; - if (EQ_STBASE(l, r, target_type) && GT_SPELLS_EFFECT_ID(l, r, base_value, 2)) - return true; - if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, base_value, 2) && LT_STBASE(l, r, spell_level)) - return true; - if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, base_value, 2) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) - return true; - - return false; - }); - continue; - case BCEnum::SpT_Resistance: // per-use sorting in command handler - spell_list->sort([](STBaseEntry* l, STBaseEntry* r) { - if (l->spell_id < r->spell_id) - return true; - if (l->spell_id == r->spell_id && LT_STBASE(l, r, caster_class)) - return true; - - return false; - }); - continue; - case BCEnum::SpT_Resurrect: - spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { - if (GT_SPELLS_EFFECT_ID(l, r, base_value, 1)) - return true; - if (EQ_SPELLS_EFFECT_ID(l, r, base_value, 1) && LT_STBASE(l, r, target_type)) - return true; - if (EQ_SPELLS_EFFECT_ID(l, r, base_value, 1) && EQ_STBASE(l, r, target_type) && LT_STBASE(l, r, spell_level)) - return true; - if (EQ_SPELLS_EFFECT_ID(l, r, base_value, 1) && EQ_STBASE(l, r, target_type) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) - return true; - - return false; - }); - continue; - case BCEnum::SpT_Rune: - spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { - if (LT_STBASE(l, r, target_type)) - return true; - if (EQ_STBASE(l, r, target_type) && GT_SPELLS_EFFECT_ID(l, r, max_value, 1)) - return true; - if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max_value, 1) && LT_STBASE(l, r, spell_level)) - return true; - if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max_value, 1) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) - return true; - - return false; - }); - continue; - case BCEnum::SpT_SendHome: - spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { - if (LT_STBASE(l, r, target_type)) - return true; - if (EQ_STBASE(l, r, target_type) && GT_STBASE(l, r, spell_level)) - return true; - if (EQ_STBASE(l, r, target_type) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) - return true; - - return false; - }); - continue; - case BCEnum::SpT_Size: - spell_list->sort([](STBaseEntry* l, STBaseEntry* r) { - if (LT_STBASE(l, r, target_type)) - return true; - - auto l_size_type = l->SafeCastToSize()->size_type; - auto r_size_type = r->SafeCastToSize()->size_type; - if (l_size_type < r_size_type) - return true; - if (l_size_type == BCEnum::SzT_Enlarge && r_size_type == BCEnum::SzT_Enlarge) { - if (EQ_STBASE(l, r, target_type) && GT_SPELLS_EFFECT_ID(l, r, base_value, 1)) - return true; - if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, base_value, 1) && GT_STBASE(l, r, spell_level)) - return true; - if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, base_value, 1) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) - return true; - } - if (l_size_type == BCEnum::SzT_Reduce && r_size_type == BCEnum::SzT_Reduce) { - if (EQ_STBASE(l, r, target_type) && LT_SPELLS_EFFECT_ID(l, r, base_value, 1)) - return true; - if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, base_value, 1) && GT_STBASE(l, r, spell_level)) - return true; - if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, base_value, 1) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) - return true; - } - - return false; - }); - continue; - case BCEnum::SpT_Stance: - spell_list->sort([](STBaseEntry* l, STBaseEntry* r) { - if (LT_STSTANCE(l, r, stance_type)) - return true; - - return false; - }); - continue; - case BCEnum::SpT_SummonCorpse: - spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { - if (GT_SPELLS_EFFECT_ID(l, r, base_value, 1)) - return true; - if (EQ_SPELLS_EFFECT_ID(l, r, base_value, 1) && LT_STBASE(l, r, spell_level)) - return true; - if (EQ_SPELLS_EFFECT_ID(l, r, base_value, 1) && EQ_STBASE(l, r, spell_level) && EQ_STBASE(l, r, caster_class)) - return true; - - return false; - }); - continue; - case BCEnum::SpT_WaterBreathing: - spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { - if (LT_STBASE(l, r, target_type)) - return true; - if (EQ_STBASE(l, r, target_type) && GT_STBASE(l, r, spell_level)) - return true; - if (EQ_STBASE(l, r, target_type) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) - return true; - - return false; - }); - continue; - default: - continue; - } - } - } - - static void load_teleport_zone_names() { - auto depart_list = &bot_command_spells[BCEnum::SpT_Depart]; - if (depart_list->empty()) - return; - - std::string query = "SELECT `short_name`, `long_name` FROM `zone` WHERE '' NOT IN (`short_name`, `long_name`)"; - auto results = content_db.QueryDatabase(query); - if (!results.Success()) { - LogError("load_teleport_zone_names() - Error in zone names query: [{}]", results.ErrorMessage().c_str()); - return; - } - - std::map zone_names; - for (auto row = results.begin(); row != results.end(); ++row) - zone_names[row[0]] = row[1]; - - for (auto list_iter = depart_list->begin(); list_iter != depart_list->end();) { - auto test_iter = zone_names.find(spells[(*list_iter)->spell_id].teleport_zone); - if (test_iter == zone_names.end()) { - list_iter = depart_list->erase(list_iter); - continue; - } - - (*list_iter)->SafeCastToDepart()->long_name = test_iter->second; - ++list_iter; - } - - } - - static void build_strings(bcst_levels_map& bot_levels_map) { - for (int i = BCEnum::SpellTypeFirst; i <= BCEnum::SpellTypeLast; ++i) - helper_bots_string(static_cast(i), bot_levels_map[static_cast(i)]); - } - - static void status_report() { - LogCommands("load_bot_command_spells(): - 'RuleI(Bots, CommandSpellRank)' set to [{}]", RuleI(Bots, CommandSpellRank)); - if (bot_command_spells.empty()) { - LogError("load_bot_command_spells() - 'bot_command_spells' is empty"); - return; - } - - for (int i = BCEnum::SpellTypeFirst; i <= BCEnum::SpellTypeLast; ++i) - LogCommands("load_bot_command_spells(): - [{}] returned [{}] spell entries", - BCEnum::SpellTypeEnumToString(static_cast(i)).c_str(), bot_command_spells[static_cast(i)].size()); - } - - static void helper_bots_string(BCEnum::SpType type_index, bcst_levels& bot_levels) { - for (int i = Class::Warrior; i <= Class::Berserker; ++i) - required_bots_map_by_class[type_index][i] = "Unavailable..."; - - if (bot_levels.empty()) { - required_bots_map[type_index] = "This command is currently unavailable..."; - return; - } - - required_bots_map[type_index] = ""; - - auto map_size = bot_levels.size(); - while (bot_levels.size()) { - bcst_levels::iterator test_iter = bot_levels.begin(); - for (bcst_levels::iterator levels_iter = bot_levels.begin(); levels_iter != bot_levels.end(); ++levels_iter) { - if (levels_iter->second < test_iter->second) - test_iter = levels_iter; - if (strcasecmp(GetClassIDName(levels_iter->first), GetClassIDName(test_iter->first)) < 0 && levels_iter->second <= test_iter->second) - test_iter = levels_iter; - } - - std::string bot_segment; - if (bot_levels.size() == map_size) - bot_segment = "%s(%u)"; - else if (bot_levels.size() > 1) - bot_segment = ", %s(%u)"; - else - bot_segment = " or %s(%u)"; - - required_bots_map[type_index].append(StringFormat(bot_segment.c_str(), GetClassIDName(test_iter->first), test_iter->second)); - required_bots_map_by_class[type_index][test_iter->first] = StringFormat("%s(%u)", GetClassIDName(test_iter->first), test_iter->second); - bot_levels.erase(test_iter); - } - } - -#ifdef BCSTSPELLDUMP - static void spell_dump() { - std::ofstream spell_dump; - spell_dump.open(StringFormat("bcs_dump/spell_dump_%i.txt", getpid()), std::ios_base::app | std::ios_base::out); - - if (bot_command_spells.empty()) { - spell_dump << "BCSpells::spell_dump() - 'bot_command_spells' map is empty.\n"; - spell_dump.close(); - return; - } - - int entry_count = 0; - for (int i = BCEnum::SpellTypeFirst; i <= BCEnum::SpellTypeLast; ++i) { - auto bcst_id = static_cast(i); - spell_dump << StringFormat("BCSpells::spell_dump(): - '%s' returned %u spells:\n", - BCEnum::SpellTypeEnumToString(bcst_id).c_str(), bot_command_spells[bcst_id].size()); - - bcst_list& map_entry = bot_command_spells[bcst_id]; - for (auto list_iter = map_entry.begin(); list_iter != map_entry.end(); ++list_iter) { - STBaseEntry* list_entry = *list_iter; - int spell_id = list_entry->spell_id; - spell_dump << StringFormat("\"%20s\" tt:%02u/cc:%02u/cl:%03u", - ((strlen(spells[spell_id].name) > 20) ? (std::string(spells[spell_id].name).substr(0, 20).c_str()) : (spells[spell_id].name)), - list_entry->target_type, - list_entry->caster_class, - list_entry->spell_level - ); - - spell_dump << StringFormat(" /mn:%05u/RD:%06i/zt:%02i/d#:%06i/td#:%05i/ed#:%05i/SAI:%03u", - spells[spell_id].mana, - spells[spell_id].resist_difficulty, - spells[spell_id].zone_type, - spells[spell_id].description_id, - spells[spell_id].type_description_id, - spells[spell_id].effect_description_id, - spells[spell_id].spell_affect_index - ); - - for (int i = EffectIDFirst; i <= 3/*EffectIDLast*/; ++i) { - int effect_index = EFFECTIDTOINDEX(i); - spell_dump << StringFormat(" /e%02i:%04i/b%02i:%06i/m%02i:%06i", - i, spells[spell_id].effect_id[effect_index], i, spells[spell_id].base_value[effect_index], i, spells[spell_id].max_value[effect_index]); - } - - switch (list_entry->BCST()) { - case BCEnum::SpT_Charm: - spell_dump << StringFormat(" /d:%c", ((list_entry->SafeCastToCharm()->dire) ? ('T') : ('F'))); - break; - case BCEnum::SpT_Cure: - spell_dump << ' '; - for (int i = 0; i < BCEnum::AilmentTypeCount; ++i) { - spell_dump << StringFormat("/cv%02i:%03i", i, list_entry->SafeCastToCure()->cure_value[i]); - } - break; - case BCEnum::SpT_Depart: { - std::string long_name = list_entry->SafeCastToDepart()->long_name.c_str(); - spell_dump << StringFormat(" /ln:%20s", ((long_name.size() > 20) ? (long_name.substr(0, 20).c_str()) : (long_name.c_str()))); - break; - } - case BCEnum::SpT_Escape: - spell_dump << StringFormat(" /l:%c", ((list_entry->SafeCastToEscape()->lesser) ? ('T') : ('F'))); - break; - case BCEnum::SpT_Invisibility: - spell_dump << StringFormat(" /it:%02i", list_entry->SafeCastToInvisibility()->invis_type); - break; - case BCEnum::SpT_MovementSpeed: - spell_dump << StringFormat(" /g:%c", ((list_entry->SafeCastToMovementSpeed()->group) ? ('T') : ('F'))); - break; - case BCEnum::SpT_Resistance: - spell_dump << ' '; - for (int i = 0; i < BCEnum::ResistanceTypeCount; ++i) { - spell_dump << StringFormat("/rv%02i:%03i", i, list_entry->SafeCastToResistance()->resist_value[i]); - } - break; - case BCEnum::SpT_Resurrect: - spell_dump << StringFormat(" /aoe:%c", ((list_entry->SafeCastToResurrect()->aoe) ? ('T') : ('F'))); - break; - case BCEnum::SpT_SendHome: - spell_dump << StringFormat(" /g:%c", ((list_entry->SafeCastToSendHome()->group) ? ('T') : ('F'))); - break; - case BCEnum::SpT_Size: - spell_dump << StringFormat(" /st:%02i", list_entry->SafeCastToSize()->size_type); - break; - case BCEnum::SpT_Stance: - spell_dump << StringFormat(" /st:%02i", list_entry->SafeCastToStance()->stance_type); - break; - default: - break; - } - - spell_dump << "\n"; - ++entry_count; - } - - spell_dump << StringFormat("required_bots_map[%s] = \"%s\"\n", - BCEnum::SpellTypeEnumToString(static_cast(i)).c_str(), required_bots_map[static_cast(i)].c_str()); - - spell_dump << "\n"; - } - - spell_dump << StringFormat("Total bcs entry count: %i\n", entry_count); - spell_dump.close(); - } -#endif -}; int bot_command_count; @@ -1237,7 +75,7 @@ LinkedList cleanup_bot_command_list; int bot_command_not_avail(Client *c, const char *message) { - c->Message(Chat::White, "Bot commands not available."); + c->Message(Chat::Yellow, "Bot commands not available."); return -1; } @@ -1247,11 +85,11 @@ int bot_command_init(void) if ( bot_command_add("actionable", "Lists actionable command arguments and use descriptions", AccountStatus::Player, bot_command_actionable) || - bot_command_add("aggressive", "Orders a bot to use a aggressive discipline", AccountStatus::Player, bot_command_aggressive) || bot_command_add("applypoison", "Applies cursor-held poison to a rogue bot's weapon", AccountStatus::Player, bot_command_apply_poison) || - bot_command_add("applypotion", "Applies cursor-held potion to a bot's effects", AccountStatus::Player, bot_command_apply_potion) || bot_command_add("attack", "Orders bots to attack a designated target", AccountStatus::Player, bot_command_attack) || - bot_command_add("bindaffinity", "Orders a bot to attempt an affinity binding", AccountStatus::Player, bot_command_bind_affinity) || + bot_command_add("behindmob", "Toggles whether or not your bot tries to stay behind a mob", AccountStatus::Player, bot_command_behind_mob) || + bot_command_add("blockedbuffs", "Set, view and clear blocked buffs for the selected bot(s)", AccountStatus::Player, bot_command_blocked_buffs) || + bot_command_add("blockedpetbuffs", "Set, view and clear blocked pet buffs for the selected bot(s)", AccountStatus::Player, bot_command_blocked_pet_buffs) || bot_command_add("bot", "Lists the available bot management [subcommands]", AccountStatus::Player, bot_command_bot) || bot_command_add("botappearance", "Lists the available bot appearance [subcommands]", AccountStatus::Player, bot_command_appearance) || bot_command_add("botbeardcolor", "Changes the beard color of a bot", AccountStatus::Player, bot_command_beard_color) || @@ -1270,8 +108,8 @@ int bot_command_init(void) bot_command_add("botheritage", "Changes the Drakkin heritage of a bot", AccountStatus::Player, bot_command_heritage) || bot_command_add("botinspectmessage", "Changes the inspect message of a bot", AccountStatus::Player, bot_command_inspect_message) || bot_command_add("botlist", "Lists the bots that you own", AccountStatus::Player, bot_command_list_bots) || - bot_command_add("botoutofcombat", "Toggles your bot between standard and out-of-combat spell/skill use - if any specialized behaviors exist", AccountStatus::Player, bot_command_out_of_combat) || bot_command_add("botreport", "Orders a bot to report its readiness", AccountStatus::Player, bot_command_report) || + bot_command_add("botsettings", "Lists settings related to spell types and bot combat", AccountStatus::Player, bot_command_bot_settings) || bot_command_add("botspawn", "Spawns a created bot", AccountStatus::Player, bot_command_spawn) || bot_command_add("botstance", "Changes the stance of a bot", AccountStatus::Player, bot_command_stance) || bot_command_add("botstopmeleelevel", "Sets the level a caster or spell-casting fighter bot will stop melee combat", AccountStatus::Player, bot_command_stop_melee_level) || @@ -1279,20 +117,20 @@ int bot_command_init(void) bot_command_add("botsummon", "Summons bot(s) to your location", AccountStatus::Player, bot_command_summon) || bot_command_add("botsurname", "Sets a bots surname (last name)", AccountStatus::Player, bot_command_surname) || bot_command_add("bottattoo", "Changes the Drakkin tattoo of a bot", AccountStatus::Player, bot_command_tattoo) || - bot_command_add("bottogglearcher", "Toggles a archer bot between melee and ranged weapon use", AccountStatus::Player, bot_command_toggle_archer) || bot_command_add("bottogglehelm", "Toggles the helm visibility of a bot between shown and hidden", AccountStatus::Player, bot_command_toggle_helm) || + bot_command_add("bottoggleranged", "Toggles a ranged bot between melee and ranged weapon use", AccountStatus::Player, bot_command_toggle_ranged) || bot_command_add("bottitle", "Sets a bots title", AccountStatus::Player, bot_command_title) || bot_command_add("botupdate", "Updates a bot to reflect any level changes that you have experienced", AccountStatus::Player, bot_command_update) || bot_command_add("botwoad", "Changes the Barbarian woad of a bot", AccountStatus::Player, bot_command_woad) || - bot_command_add("casterrange", "Controls the range casters will try to stay away from a mob (if too far, they will skip spells that are out-of-range)", AccountStatus::Player, bot_command_caster_range) || - bot_command_add("charm", "Attempts to have a bot charm your target", AccountStatus::Player, bot_command_charm) || - bot_command_add("circle", "Orders a Druid bot to open a magical doorway to a specified destination", AccountStatus::Player, bot_command_circle) || + bot_command_add("cast", "Tells the first found specified bot to cast the given spell type", AccountStatus::Player, bot_command_cast) || + bot_command_add("discipline", "Uses aggressive/defensive disciplines or can specify spell ID", AccountStatus::Player, bot_command_discipline) || + bot_command_add("distanceranged", "Controls the range casters and ranged will try to stay away from a mob", AccountStatus::Player, bot_command_distance_ranged) || + bot_command_add("classracelist", "Lists the classes and races and their appropriate IDs", AccountStatus::Player, bot_command_class_race_list) || bot_command_add("clickitem", "Orders your targeted bot to click the item in the provided inventory slot.", AccountStatus::Player, bot_command_click_item) || - bot_command_add("cure", "Orders a bot to remove any ailments", AccountStatus::Player, bot_command_cure) || - bot_command_add("defensive", "Orders a bot to use a defensive discipline", AccountStatus::Player, bot_command_defensive) || + bot_command_add("copysettings", "Copies settings from one bot to another", AccountStatus::Player, bot_command_copy_settings) || + bot_command_add("defaultsettings", "Restores a bot back to default settings", AccountStatus::Player, bot_command_default_settings) || bot_command_add("depart", "Orders a bot to open a magical doorway to a specified destination", AccountStatus::Player, bot_command_depart) || bot_command_add("enforcespellsettings", "Toggles your Bot to cast only spells in their spell settings list.", AccountStatus::Player, bot_command_enforce_spell_list) || - bot_command_add("escape", "Orders a bot to send a target group to a safe location within the zone", AccountStatus::Player, bot_command_escape) || bot_command_add("findaliases", "Find available aliases for a bot command", AccountStatus::Player, bot_command_find_aliases) || bot_command_add("follow", "Orders bots to follow a designated target (option 'chain' auto-links eligible spawned bots)", AccountStatus::Player, bot_command_follow) || bot_command_add("guard", "Orders bots to guard their current positions", AccountStatus::Player, bot_command_guard) || @@ -1319,18 +157,14 @@ int bot_command_init(void) bot_command_add("healrotationstop", "Stops a heal rotation", AccountStatus::Player, bot_command_heal_rotation_stop) || bot_command_add("help", "List available commands and their description - specify partial command as argument to search", AccountStatus::Player, bot_command_help) || bot_command_add("hold", "Prevents a bot from attacking until released", AccountStatus::Player, bot_command_hold) || - bot_command_add("identify", "Orders a bot to cast an item identification spell", AccountStatus::Player, bot_command_identify) || + bot_command_add("illusionblock", "Control whether or not illusion effects will land on the bot if casted by another player or bot", AccountStatus::Player, bot_command_illusion_block) || bot_command_add("inventory", "Lists the available bot inventory [subcommands]", AccountStatus::Player, bot_command_inventory) || bot_command_add("inventorygive", "Gives the item on your cursor to a bot", AccountStatus::Player, bot_command_inventory_give) || bot_command_add("inventorylist", "Lists all items in a bot's inventory", AccountStatus::Player, bot_command_inventory_list) || bot_command_add("inventoryremove", "Removes an item from a bot's inventory", AccountStatus::Player, bot_command_inventory_remove) || bot_command_add("inventorywindow", "Displays all items in a bot's inventory in a pop-up window", AccountStatus::Player, bot_command_inventory_window) || - bot_command_add("invisibility", "Orders a bot to cast a cloak of invisibility, or allow them to be seen", AccountStatus::Player, bot_command_invisibility) || bot_command_add("itemuse", "Elicits a report from spawned bots that can use the item on your cursor (option 'empty' yields only empty slots)", AccountStatus::Player, bot_command_item_use) || - bot_command_add("levitation", "Orders a bot to cast a levitation spell", AccountStatus::Player, bot_command_levitation) || - bot_command_add("lull", "Orders a bot to cast a pacification spell", AccountStatus::Player, bot_command_lull) || - bot_command_add("mesmerize", "Orders a bot to cast a mesmerization spell", AccountStatus::Player, bot_command_mesmerize) || - bot_command_add("movementspeed", "Orders a bot to cast a movement speed enhancement spell", AccountStatus::Player, bot_command_movement_speed) || + bot_command_add("maxmeleerange", "Toggles whether your bot is at max melee range or not. This will disable all special abilities, including taunt.", AccountStatus::Player, bot_command_max_melee_range) || bot_command_add("owneroption", "Sets options available to bot owners", AccountStatus::Player, bot_command_owner_option) || bot_command_add("pet", "Lists the available bot pet [subcommands]", AccountStatus::Player, bot_command_pet) || bot_command_add("petgetlost", "Orders a bot to remove its summoned pet", AccountStatus::Player, bot_command_pet_get_lost) || @@ -1339,14 +173,27 @@ int bot_command_init(void) bot_command_add("picklock", "Orders a capable bot to pick the lock of the closest door", AccountStatus::Player, bot_command_pick_lock) || bot_command_add("pickpocket", "Orders a capable bot to pickpocket a NPC", AccountStatus::Player, bot_command_pickpocket) || bot_command_add("precombat", "Sets flag used to determine pre-combat behavior", AccountStatus::Player, bot_command_precombat) || - bot_command_add("portal", "Orders a Wizard bot to open a magical doorway to a specified destination", AccountStatus::Player, bot_command_portal) || bot_command_add("pull", "Orders a designated bot to 'pull' an enemy", AccountStatus::Player, bot_command_pull) || bot_command_add("release", "Releases a suspended bot's AI processing (with hate list wipe)", AccountStatus::Player, bot_command_release) || - bot_command_add("resistance", "Orders a bot to cast a specified resistance buff", AccountStatus::Player, bot_command_resistance) || - bot_command_add("resurrect", "Orders a bot to resurrect a player's (players') corpse(s)", AccountStatus::Player, bot_command_resurrect) || - bot_command_add("rune", "Orders a bot to cast a rune of protection", AccountStatus::Player, bot_command_rune) || - bot_command_add("sendhome", "Orders a bot to open a magical doorway home", AccountStatus::Player, bot_command_send_home) || - bot_command_add("size", "Orders a bot to change a player's size", AccountStatus::Player, bot_command_size) || + bot_command_add("setassistee", "Sets your target to be the person your bots assist. Bots will always assist you before others", AccountStatus::Player, bot_command_set_assistee) || + bot_command_add("sithppercent", "HP threshold for a bot to start sitting in combat if allowed", AccountStatus::Player, bot_command_sit_hp_percent) || + bot_command_add("sitincombat", "Toggles whether or a not a bot will attempt to med or sit to heal in combat", AccountStatus::Player, bot_command_sit_in_combat) || + bot_command_add("sitmanapercent", "Mana threshold for a bot to start sitting in combat if allowed", AccountStatus::Player, bot_command_sit_mana_percent) || + bot_command_add("spellaggrochecks", "Toggles whether or not bots will cast a spell type if they think it will get them aggro", AccountStatus::Player, bot_command_spell_aggro_checks) || + bot_command_add("spellannouncecasts", "Turn on or off cast announcements by spell type", AccountStatus::Player, bot_command_spell_announce_cast) || + bot_command_add("spellengagedpriority", "Controls the order of casts by spell type when engaged in combat", AccountStatus::Player, bot_command_spell_engaged_priority) || + bot_command_add("spelldelays", "Controls the delay between casts for a specific spell type", AccountStatus::Player, bot_command_spell_delays) || + bot_command_add("spellholds", "Controls whether a bot holds the specified spell type or not", AccountStatus::Player, bot_command_spell_holds) || + bot_command_add("spellidlepriority", "Controls the order of casts by spell type when out of combat", AccountStatus::Player, bot_command_spell_idle_priority) || + bot_command_add("spellmaxhppct", "Controls at what HP percent a bot will stop casting different spell types", AccountStatus::Player, bot_command_spell_max_hp_pct) || + bot_command_add("spellmaxmanapct", "Controls at what mana percent a bot will stop casting different spell types", AccountStatus::Player, bot_command_spell_max_mana_pct) || + bot_command_add("spellmaxthresholds", "Controls the minimum target HP threshold for a spell to be cast for a specific type", AccountStatus::Player, bot_command_spell_max_thresholds) || + bot_command_add("spellminhppct", "Controls at what HP percent a bot will start casting different spell types", AccountStatus::Player, bot_command_spell_min_hp_pct) || + bot_command_add("spellminmanapct", "Controls at what mana percent a bot will start casting different spell types", AccountStatus::Player, bot_command_spell_min_mana_pct) || + bot_command_add("spellminthresholds", "Controls the maximum target HP threshold for a spell to be cast for a specific type", AccountStatus::Player, bot_command_spell_min_thresholds) || + bot_command_add("spellresistlimits", "Controls the resist limits for bots to cast spells on their target", AccountStatus::Player, bot_command_spell_resist_limits) || + bot_command_add("spellpursuepriority", "Controls the order of casts by spell type when pursuing in combat", AccountStatus::Player, bot_command_spell_pursue_priority) || + bot_command_add("spelltargetcount", "Sets the required target amount for group/AE spells by spell type", AccountStatus::Player, bot_command_spell_target_count) || bot_command_add("spellinfo", "Opens a dialogue window with spell info", AccountStatus::Player, bot_spell_info_dialogue_window) || bot_command_add("spells", "Lists all Spells learned by the Bot.", AccountStatus::Player, bot_command_spell_list) || bot_command_add("spellsettings", "Lists a bot's spell setting entries", AccountStatus::Player, bot_command_spell_settings_list) || @@ -1354,13 +201,13 @@ int bot_command_init(void) bot_command_add("spellsettingsdelete", "Delete a bot spell setting entry", AccountStatus::Player, bot_command_spell_settings_delete) || bot_command_add("spellsettingstoggle", "Toggle a bot spell use", AccountStatus::Player, bot_command_spell_settings_toggle) || bot_command_add("spellsettingsupdate", "Update a bot spell setting entry", AccountStatus::Player, bot_command_spell_settings_update) || - bot_command_add("summoncorpse", "Orders a bot to summon a corpse to its feet", AccountStatus::Player, bot_command_summon_corpse) || + bot_command_add("spelltypeids", "Lists spelltypes by ID", AccountStatus::Player, bot_command_spelltype_ids) || + bot_command_add("spelltypenames", "Lists spelltypes by shortname", AccountStatus::Player, bot_command_spelltype_names) || bot_command_add("suspend", "Suspends a bot's AI processing until released", AccountStatus::Player, bot_command_suspend) || bot_command_add("taunt", "Toggles taunt use by a bot", AccountStatus::Player, bot_command_taunt) || bot_command_add("timer", "Checks or clears timers of the chosen type.", AccountStatus::GMMgmt, bot_command_timer) || bot_command_add("track", "Orders a capable bot to track enemies", AccountStatus::Player, bot_command_track) || - bot_command_add("viewcombos", "Views bot race class combinations", AccountStatus::Player, bot_command_view_combos) || - bot_command_add("waterbreathing", "Orders a bot to cast a water breathing spell", AccountStatus::Player, bot_command_water_breathing) + bot_command_add("viewcombos", "Views bot race class combinations", AccountStatus::Player, bot_command_view_combos) ) { bot_command_deinit(); return -1; @@ -1372,6 +219,13 @@ int bot_command_init(void) std::vector> injected_bot_command_settings; std::vector 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); @@ -1458,8 +312,6 @@ int bot_command_init(void) bot_command_dispatch = bot_command_real_dispatch; - BCSpells::Load(); - return bot_command_count; } @@ -1470,8 +322,6 @@ void bot_command_deinit(void) bot_command_dispatch = bot_command_not_avail; bot_command_count = 0; - - BCSpells::Unload(); } int bot_command_add(std::string bot_command_name, const char *desc, int access, BotCmdFuncPtr function) @@ -1520,16 +370,10 @@ int bot_command_real_dispatch(Client *c, const char *message) BotCommandRecord *cur = bot_command_list[cstr]; if(c->Admin() < cur->access){ - c->Message(Chat::White, "Your access level is not high enough to use this bot command."); + c->Message(Chat::Yellow, "Your access level is not high enough to use this bot command."); return(-1); } - /* QS: Player_Log_Issued_Commands */ - if (RuleB(QueryServ, PlayerLogIssuedCommandes)){ - std::string event_desc = StringFormat("Issued bot command :: '%s' in zoneid:%i instid:%i", message, c->GetZoneID(), c->GetInstanceID()); - QServ->PlayerLogEvent(Player_Log_Issued_Commands, c->CharacterID(), event_desc); - } - if(cur->access >= COMMANDS_LOGGING_MIN_STATUS) { LogCommands("[{}] ([{}]) used bot command: [{}] (target=[{}])", c->GetName(), c->AccountName(), message, c->GetTarget()?c->GetTarget()->GetName():"NONE"); } @@ -1545,18 +389,18 @@ int bot_command_real_dispatch(Client *c, const char *message) } -bool helper_bot_appearance_fail(Client *bot_owner, Bot *my_bot, BCEnum::AFType fail_type, const char* type_desc) +bool helper_bot_appearance_fail(Client *bot_owner, Bot *my_bot, uint8 fail_type, const char* type_desc) { switch (fail_type) { - case BCEnum::AFT_Value: - bot_owner->Message(Chat::White, "Failed to change '%s' for %s due to invalid value for this command", type_desc, my_bot->GetCleanName()); - return true; - case BCEnum::AFT_GenderRace: - bot_owner->Message(Chat::White, "Failed to change '%s' for %s due to invalid bot gender and/or race for this command", type_desc, my_bot->GetCleanName()); - return true; - case BCEnum::AFT_Race: - bot_owner->Message(Chat::White, "Failed to change '%s' for %s due to invalid bot race for this command", type_desc, my_bot->GetCleanName()); - return true; + case AFT_Value: + bot_owner->Message(Chat::Yellow, "Failed to change '%s' for %s due to invalid value for this command", type_desc, my_bot->GetCleanName()); + return true; + case AFT_GenderRace: + bot_owner->Message(Chat::Yellow, "Failed to change '%s' for %s due to invalid bot gender and/or race for this command", type_desc, my_bot->GetCleanName()); + return true; + case AFT_Race: + bot_owner->Message(Chat::Yellow, "Failed to change '%s' for %s due to invalid bot race for this command", type_desc, my_bot->GetCleanName()); + return true; default: return false; } @@ -1567,7 +411,7 @@ void helper_bot_appearance_form_final(Client *bot_owner, Bot *my_bot) if (!MyBots::IsMyBot(bot_owner, my_bot)) return; if (!my_bot->Save()) { - bot_owner->Message(Chat::White, "Failed to save appearance change for %s due to unknown cause...", my_bot->GetCleanName()); + bot_owner->Message(Chat::Yellow, "Failed to save appearance change for %s due to unknown cause...", my_bot->GetCleanName()); return; } @@ -1611,35 +455,29 @@ uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_clas if (!Bot::IsValidName(bot_name)) { bot_owner->Message( - Chat::White, + Chat::Yellow, fmt::format( - "'{}' is an invalid name. You may only use characters 'A-Z' or 'a-z'. Mixed case {} allowed.", + "'{}' is an invalid name. You may only use characters 'A-Z' or 'a-z' and it must be between 4 and 15 characters with no spaces. Mixed case {} allowed.", bot_name, RuleB(Bots, AllowCamelCaseNames) ? "is" : "is not" ).c_str() ); + return bot_id; } bool available_flag = false; - if (!database.botdb.QueryNameAvailablity(bot_name, available_flag)) { - bot_owner->Message( - Chat::White, - fmt::format( - "Failed to query name availability for '{}'.", - bot_name - ).c_str() - ); - return bot_id; - } + + !database.botdb.QueryNameAvailability(bot_name, available_flag); if (!available_flag) { bot_owner->Message( - Chat::White, + Chat::Yellow, fmt::format( - "The name '{}' is already being used. Please choose a different name", + "The name '{}' is already being used or prohibited. Please choose a different name", bot_name ).c_str() ); + return bot_id; } @@ -1648,7 +486,7 @@ uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_clas const std::string bot_class_name = GetClassIDName(bot_class); bot_owner->Message( - Chat::White, + Chat::Yellow, fmt::format( "{} {} is an invalid race-class combination, would you like to {} proper combinations for {}?", bot_race_name, @@ -1678,93 +516,36 @@ uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_clas return bot_id; } - auto bot_creation_limit = bot_owner->GetBotCreationLimit(); - auto bot_creation_limit_class = bot_owner->GetBotCreationLimit(bot_class); + if (!Bot::CheckHighEnoughLevelForBots(bot_owner)) { + return bot_id; + } + + if (!Bot::CheckHighEnoughLevelForBots(bot_owner, bot_class)) { + return bot_id; + } uint32 bot_count = 0; uint32 bot_class_count = 0; + if (!database.botdb.QueryBotCount(bot_owner->CharacterID(), bot_class, bot_count, bot_class_count)) { - bot_owner->Message(Chat::White, "Failed to query bot count."); + bot_owner->Message(Chat::Yellow, "Failed to query bot count."); + return bot_id; } - if (bot_creation_limit >= 0 && bot_count >= bot_creation_limit) { - std::string message; - - if (bot_creation_limit) { - message = fmt::format( - "You cannot create anymore than {} bot{}.", - bot_creation_limit, - bot_creation_limit != 1 ? "s" : "" - ); - } else { - message = "You cannot create any bots."; - } - - bot_owner->Message(Chat::White, message.c_str()); + if (!Bot::CheckCreateLimit(bot_owner, bot_count)) { return bot_id; } - if (bot_creation_limit_class >= 0 && bot_class_count >= bot_creation_limit_class) { - std::string message; - - if (bot_creation_limit_class) { - message = fmt::format( - "You cannot create anymore than {} {} bot{}.", - bot_creation_limit_class, - GetClassIDName(bot_class), - bot_creation_limit_class != 1 ? "s" : "" - ); - } else { - message = fmt::format( - "You cannot create any {} bots.", - GetClassIDName(bot_class) - ); - } - - bot_owner->Message(Chat::White, message.c_str()); + if (!Bot::CheckCreateLimit(bot_owner, bot_class_count, bot_class)) { return bot_id; } - auto bot_character_level = bot_owner->GetBotRequiredLevel(); - - if ( - bot_character_level >= 0 && - bot_owner->GetLevel() < bot_character_level - ) { - bot_owner->Message( - Chat::White, - fmt::format( - "You must be level {} to use bots.", - bot_character_level - ).c_str() - ); - return bot_id; - } - - auto bot_character_level_class = bot_owner->GetBotRequiredLevel(bot_class); - - if ( - bot_character_level_class >= 0 && - bot_owner->GetLevel() < bot_character_level_class - ) { - bot_owner->Message( - Chat::White, - fmt::format( - "You must be level {} to use {} bots.", - bot_character_level_class, - GetClassIDName(bot_class) - ).c_str() - ); - return bot_id; - } - - auto my_bot = new Bot(Bot::CreateDefaultNPCTypeStructForBot(bot_name, "", bot_owner->GetLevel(), bot_race, bot_class, bot_gender), bot_owner); if (!my_bot->Save()) { bot_owner->Message( - Chat::White, + Chat::Yellow, fmt::format( "Failed to create '{}' due to unknown cause.", my_bot->GetCleanName() @@ -1806,47 +587,13 @@ uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_clas return bot_id; } -void helper_bot_out_of_combat(Client *bot_owner, Bot *my_bot) -{ - if (!bot_owner || !my_bot) - return; - - switch (my_bot->GetClass()) { - case Class::Warrior: - case Class::Cleric: - case Class::Paladin: - case Class::Ranger: - case Class::ShadowKnight: - case Class::Druid: - case Class::Monk: - bot_owner->Message(Chat::White, "%s has no out-of-combat behavior defined", my_bot->GetCleanName()); - break; - case Class::Bard: - bot_owner->Message(Chat::White, "%s will %s use out-of-combat behavior for bard songs", my_bot->GetCleanName(), ((my_bot->GetAltOutOfCombatBehavior()) ? ("now") : ("no longer"))); - break; - case Class::Rogue: - case Class::Shaman: - case Class::Necromancer: - case Class::Wizard: - case Class::Magician: - case Class::Enchanter: - case Class::Beastlord: - case Class::Berserker: - bot_owner->Message(Chat::White, "%s has no out-of-combat behavior defined", my_bot->GetCleanName()); - break; - default: - break; - bot_owner->Message(Chat::White, "Undefined bot class for %s", my_bot->GetCleanName()); - } -} - int helper_bot_follow_option_chain(Client* bot_owner) { if (!bot_owner) { return 0; } - std::list sbl; + std::vector sbl; MyBots::PopulateSBL_BySpawnedBots(bot_owner, sbl); if (sbl.empty()) { return 0; @@ -1909,30 +656,10 @@ int helper_bot_follow_option_chain(Client* bot_owner) return chain_follow_count; } -bool helper_cast_standard_spell(Bot* casting_bot, Mob* target_mob, int spell_id, bool annouce_cast, uint32* dont_root_before) -{ - if (!casting_bot || !target_mob) - return false; - - casting_bot->InterruptSpell(); - if (annouce_cast) { - Bot::BotGroupSay( - casting_bot, - fmt::format( - "Attempting to cast {} on {}.", - spells[spell_id].name, - target_mob->GetCleanName() - ).c_str() - ); - } - - return casting_bot->CastSpell(spell_id, target_mob->GetID(), EQ::spells::CastingSlot::Gem2, -1, -1, dont_root_before); -} - bool helper_command_disabled(Client* bot_owner, bool rule_value, const char* command) { if (!rule_value) { - bot_owner->Message(Chat::White, "Bot command %s is not enabled on this server.", command); + bot_owner->Message(Chat::Yellow, "Bot command %s is not enabled on this server.", command); return true; } @@ -1943,111 +670,13 @@ bool helper_command_alias_fail(Client *bot_owner, const char* command_handler, c { auto alias_iter = bot_command_aliases.find(&alias[1]); if (alias_iter == bot_command_aliases.end() || alias_iter->second.compare(command)) { - bot_owner->Message(Chat::White, "Undefined linker usage in %s (%s)", command_handler, &alias[1]); + bot_owner->Message(Chat::Yellow, "Undefined linker usage in %s (%s)", command_handler, &alias[1]); return true; } return false; } -void helper_command_depart_list(Client* bot_owner, Bot* druid_bot, Bot* wizard_bot, bcst_list* local_list, bool single_flag) -{ - if (!bot_owner) { - return; - } - - if (!MyBots::IsMyBot(bot_owner, druid_bot)) { - druid_bot = nullptr; - } - - if (!MyBots::IsMyBot(bot_owner, wizard_bot)) { - wizard_bot = nullptr; - } - - if (!druid_bot && !wizard_bot) { - bot_owner->Message(Chat::White, "No bots are capable of performing this action"); - return; - } - - if (!local_list) { - bot_owner->Message(Chat::White, "There are no destinations you can be taken to."); - return; - } - - auto destination_count = 0; - auto destination_number = 1; - for (auto list_iter : *local_list) { - auto local_entry = list_iter->SafeCastToDepart(); - if (!local_entry) { - continue; - } - - if ( - druid_bot && - druid_bot->GetClass() == local_entry->caster_class && - druid_bot->GetLevel() >= local_entry->spell_level - ) { - if (local_entry->single != single_flag) { - continue; - } - - druid_bot->OwnerMessage( - fmt::format( - "Destination {} | {} | {}", - destination_number, - local_entry->long_name, - Saylink::Silent( - fmt::format( - "^circle {}{}", - spells[local_entry->spell_id].teleport_zone, - single_flag ? " single" : "" - ), - "Goto" - ) - ) - ); - - destination_count++; - destination_number++; - continue; - } - - if ( - wizard_bot && - wizard_bot->GetClass() == local_entry->caster_class && - wizard_bot->GetLevel() >= local_entry->spell_level - ) { - if (local_entry->single != single_flag) { - continue; - } - - wizard_bot->OwnerMessage( - fmt::format( - "Destination {} | {} | {}", - destination_number, - local_entry->long_name, - Saylink::Silent( - fmt::format( - "^portal {}{}", - spells[local_entry->spell_id].teleport_zone, - single_flag ? " single" : "" - ), - "Goto" - ) - ) - ); - - destination_count++; - destination_number++; - continue; - } - } - - if (!destination_count) { - bot_owner->Message(Chat::White, "There are no destinations you can be taken to."); - } -} - bool helper_is_help_or_usage(const char* arg) { if (!arg) @@ -2063,14 +692,14 @@ bool helper_no_available_bots(Client *bot_owner, Bot *my_bot) if (!bot_owner) return true; if (!my_bot) { - bot_owner->Message(Chat::White, "No bots are capable of performing this action"); + bot_owner->Message(Chat::Yellow, "No bots are capable of performing this action"); return true; } return false; } -void helper_send_available_subcommands(Client *bot_owner, const char* command_simile, const std::list& subcommand_list) +void helper_send_available_subcommands(Client* bot_owner, const char* command_simile, std::vector subcommand_list) { bot_owner->Message(Chat::White, "Available %s management subcommands:", command_simile); @@ -2085,8 +714,11 @@ void helper_send_available_subcommands(Client *bot_owner, const char* command_si bot_owner->Message( Chat::White, fmt::format( - "^{} - {}", - subcommand_iter, + "{} - {}", + Saylink::Silent( + fmt::format("^{} help", subcommand_iter), + fmt::format("^{}", subcommand_iter) + ), find_iter != bot_command_list.end() ? find_iter->second->desc : "No Description" ).c_str() ); @@ -2097,64 +729,167 @@ void helper_send_available_subcommands(Client *bot_owner, const char* command_si bot_owner->Message(Chat::White, "%d bot subcommand%s listed.", bot_subcommands_shown, bot_subcommands_shown != 1 ? "s" : ""); } -void helper_send_usage_required_bots(Client *bot_owner, BCEnum::SpType spell_type, uint8 bot_class) +void helper_send_usage_required_bots(Client *bot_owner, uint16 spell_type) { - bot_owner->Message(Chat::White, "requires one of the following bot classes:"); - if (bot_class) - bot_owner->Message(Chat::White, "%s", required_bots_map_by_class[spell_type][bot_class].c_str()); - else - bot_owner->Message(Chat::White, "%s", required_bots_map[spell_type].c_str()); -} - -bool helper_spell_check_fail(STBaseEntry* local_entry) -{ - if (!local_entry) - return true; - if (spells[local_entry->spell_id].zone_type && zone->GetZoneType() && !(spells[local_entry->spell_id].zone_type & zone->GetZoneType())) - return true; - - return false; -} - -bool helper_spell_list_fail(Client *bot_owner, bcst_list* spell_list, BCEnum::SpType spell_type) -{ - if (!spell_list || spell_list->empty()) { - bot_owner->Message(Chat::White, "%s", required_bots_map[spell_type].c_str()); - return true; + if (!bot_owner) { + return; } - return false; + auto sbl = entity_list.GetBotListByCharacterID(bot_owner->CharacterID()); + Bot* bot = nullptr; + + for (const auto& b : sbl) { + if (b) { + bot = b; + + break; + } + } + + auto spell_map = database.botdb.GetCommandedSpellTypesMinLevels(); + + if (spell_map.empty()) { + bot_owner->Message(Chat::Yellow, "No bots are capable of casting this spell type."); + return; + } + + bool found = false; + std::string description; + + for (int i = Class::Warrior; i <= Class::Berserker; ++i) { + 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; + + if (!description.empty()) { + description.append(", "); + } + else { + bot_owner->Message(Chat::Yellow, "Required bots to cast: Class [Class ID]: [Level]"); + } + + description.append(spell_info.description); + } + } + + if (!found || description.empty()) { + bot_owner->Message(Chat::Yellow, "No bots are capable of casting this spell type"); + + return; + } + + bot_owner->Message(Chat::Green, "%s", description.c_str()); +} + +void SendSpellTypeWindow(Client* c, const Seperator* sep) { + std::string arg0 = sep->arg[0]; + std::string arg1 = sep->arg[1]; + + uint8 min_count = 0; + uint8 max_count = 0; + bool client_only = false; + + if (BotSpellTypes::END <= 19) { + min_count = BotSpellTypes::START; + max_count = BotSpellTypes::END; + } + else if (!arg1.compare("0-19")) { + min_count = BotSpellTypes::START; + max_count = 19; + } + else if (!arg1.compare("20-39")) { + min_count = std::min(static_cast(20), static_cast(BotSpellTypes::END)); + max_count = std::min(static_cast(39), static_cast(BotSpellTypes::END)); + } + else if (!arg1.compare("40+")) { + min_count = std::min(static_cast(40), static_cast(BotSpellTypes::END)); + max_count = BotSpellTypes::END; + } + else if (!arg1.compare("commanded")) { + min_count = BotSpellTypes::COMMANDED_START; + max_count = BotSpellTypes::COMMANDED_END; + } + else if (!arg1.compare("client")) { + min_count = BotSpellTypes::START; + max_count = BotSpellTypes::END; + client_only = true; + } + else { + c->Message(Chat::Yellow, "You must choose a valid range option"); + + return; + } + + const std::string& indian_red = "indian_red"; + const std::string& gold = "gold"; + const std::string& slate_blue = "slate_blue"; + const std::string& forest_green = "forest_green"; + const std::string& goldenrod = "goldenrod"; + + std::string filler_line = "-----------"; + std::string spell_type_field = "Spell Type"; + std::string id_field = "ID"; + std::string shortname_field = "Short Name"; + + std::string popup_text = DialogueWindow::TableRow( + DialogueWindow::TableCell(DialogueWindow::ColorMessage(goldenrod, spell_type_field)) + + + DialogueWindow::TableCell((!arg0.compare("^spelltypeids") ? DialogueWindow::ColorMessage(goldenrod, id_field) : DialogueWindow::ColorMessage(goldenrod, shortname_field))) + ); + + popup_text += DialogueWindow::TableRow( + DialogueWindow::TableCell(DialogueWindow::ColorMessage(gold, filler_line)) + + + DialogueWindow::TableCell(DialogueWindow::ColorMessage(gold, filler_line)) + ); + + for (int i = min_count; i <= max_count; ++i) { + if (client_only && !IsClientBotSpellType(i)) { + continue; + } + + popup_text += DialogueWindow::TableRow( + DialogueWindow::TableCell(DialogueWindow::ColorMessage(forest_green, Bot::GetSpellTypeNameByID(i))) + + + DialogueWindow::TableCell((!arg0.compare("^spelltypeids") ? DialogueWindow::ColorMessage(slate_blue, std::to_string(i)) : DialogueWindow::ColorMessage(slate_blue, Bot::GetSpellTypeShortNameByID(i)))) + ); + } + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient("Spell Types", popup_text.c_str()); } #include "bot_commands/actionable.cpp" -#include "bot_commands/aggressive.cpp" #include "bot_commands/appearance.cpp" #include "bot_commands/apply_poison.cpp" #include "bot_commands/apply_potion.cpp" #include "bot_commands/attack.cpp" -#include "bot_commands/bind_affinity.cpp" +#include "bot_commands/behind_mob.cpp" +#include "bot_commands/blocked_buffs.cpp" #include "bot_commands/bot.cpp" -#include "bot_commands/caster_range.cpp" -#include "bot_commands/charm.cpp" +#include "bot_commands/bot_settings.cpp" +#include "bot_commands/cast.cpp" +#include "bot_commands/class_race_list.cpp" #include "bot_commands/click_item.cpp" -#include "bot_commands/cure.cpp" -#include "bot_commands/defensive.cpp" +#include "bot_commands/copy_settings.cpp" +#include "bot_commands/default_settings.cpp" #include "bot_commands/depart.cpp" -#include "bot_commands/escape.cpp" +#include "bot_commands/discipline.cpp" +#include "bot_commands/distance_ranged.cpp" #include "bot_commands/find_aliases.cpp" #include "bot_commands/follow.cpp" #include "bot_commands/guard.cpp" #include "bot_commands/heal_rotation.cpp" #include "bot_commands/help.cpp" #include "bot_commands/hold.cpp" -#include "bot_commands/identify.cpp" +#include "bot_commands/illusion_block.cpp" #include "bot_commands/inventory.cpp" -#include "bot_commands/invisibility.cpp" #include "bot_commands/item_use.cpp" -#include "bot_commands/levitation.cpp" -#include "bot_commands/lull.cpp" -#include "bot_commands/mesmerize.cpp" -#include "bot_commands/movement_speed.cpp" +#include "bot_commands/max_melee_range.cpp" #include "bot_commands/name.cpp" #include "bot_commands/owner_option.cpp" #include "bot_commands/pet.cpp" @@ -2163,18 +898,30 @@ bool helper_spell_list_fail(Client *bot_owner, bcst_list* spell_list, BCEnum::Sp #include "bot_commands/precombat.cpp" #include "bot_commands/pull.cpp" #include "bot_commands/release.cpp" -#include "bot_commands/resistance.cpp" -#include "bot_commands/resurrect.cpp" -#include "bot_commands/rune.cpp" -#include "bot_commands/send_home.cpp" -#include "bot_commands/size.cpp" +#include "bot_commands/set_assistee.cpp" +#include "bot_commands/sit_hp_percent.cpp" +#include "bot_commands/sit_in_combat.cpp" +#include "bot_commands/sit_mana_percent.cpp" #include "bot_commands/spell.cpp" +#include "bot_commands/spell_aggro_checks.cpp" +#include "bot_commands/spell_announce_cast.cpp" +#include "bot_commands/spell_delays.cpp" +#include "bot_commands/spell_engaged_priority.cpp" +#include "bot_commands/spell_holds.cpp" +#include "bot_commands/spell_idle_priority.cpp" +#include "bot_commands/spell_max_hp_pct.cpp" +#include "bot_commands/spell_max_mana_pct.cpp" +#include "bot_commands/spell_max_thresholds.cpp" +#include "bot_commands/spell_min_hp_pct.cpp" +#include "bot_commands/spell_min_mana_pct.cpp" +#include "bot_commands/spell_min_thresholds.cpp" +#include "bot_commands/spell_pursue_priority.cpp" +#include "bot_commands/spell_resist_limits.cpp" +#include "bot_commands/spell_target_count.cpp" +#include "bot_commands/spelltypes.cpp" #include "bot_commands/summon.cpp" -#include "bot_commands/summon_corpse.cpp" #include "bot_commands/suspend.cpp" #include "bot_commands/taunt.cpp" -#include "bot_commands/teleport.cpp" #include "bot_commands/timer.cpp" #include "bot_commands/track.cpp" #include "bot_commands/view_combos.cpp" -#include "bot_commands/water_breathing.cpp" diff --git a/zone/bot_command.h b/zone/bot_command.h index 74dfa1401..77d449c44 100644 --- a/zone/bot_command.h +++ b/zone/bot_command.h @@ -27,194 +27,6 @@ class Seperator; #include "bot.h" #include "dialogue_window.h" -class BCEnum -{ -public: - typedef enum SpellType { - SpT_None = 0, - SpT_BindAffinity, - SpT_Charm, - SpT_Cure, - SpT_Depart, - SpT_Escape, - SpT_Identify, - SpT_Invisibility, - SpT_Levitation, - SpT_Lull, - SpT_Mesmerize, - SpT_MovementSpeed, - SpT_Resistance, - SpT_Resurrect, - SpT_Rune, - SpT_SendHome, - SpT_Size, - SpT_Stance, - SpT_SummonCorpse, - SpT_WaterBreathing - } SpType; - static const int SpellTypeFirst = SpT_BindAffinity; - static const int SpellTypeLast = SpT_WaterBreathing; - - typedef enum TargetType { - TT_None = 0, - TT_Corpse, - TT_Self, - TT_Animal, - TT_Undead, - TT_Summoned, - TT_Plant, - TT_Single, - TT_GroupV1, - TT_GroupV2, - TT_AECaster, - TT_AEBard, - TT_AETarget - } TType; - static const int TargetTypeFirst = TT_Corpse; - static const int TargetTypeLast = TT_AETarget; - static const int TargetTypeCount = 13; - - typedef enum TargetMask { - TM_None = 0, - TM_Corpse = 1, - TM_Self = 2, - TM_Animal = 4, - TM_Undead = 8, - TM_Summoned = 16, - TM_Plant = 32, - TM_Single = 124, // currently, 2^6 + 2^{2..5}) -or- (64+32+16+8+4) - TM_GroupV1 = 128, - TM_GroupV2 = 256, - TM_AECaster = 512, - TM_AEBard = 1024, - TM_AETarget = 2048 - } TMask; - - typedef enum AppearanceFailType { - AFT_None = 0, - AFT_Value, - AFT_GenderRace, - AFT_Race - } AFType; - - typedef enum AilmentType { - AT_None = 0, - AT_Blindness, // SE: 20 - AT_Disease, // SE: 35 - AT_Poison, // SE: 36 - AT_Curse, // SE: 116 - AT_Corruption // SE: 369 - } AType; - static const int AilmentTypeCount = 5; - - typedef enum InvisType { - IT_None = 0, - IT_Animal, - IT_Undead, - IT_Living, - IT_See - } IType; - - typedef enum ResistanceType { - RT_None = 0, - RT_Fire, // SE: 46 - RT_Cold, // SE: 47 - RT_Poison, // SE: 48 - RT_Disease, // SE: 49 - RT_Magic, // SE: 50 - RT_Corruption // SE: 370 - } RType; - static const int ResistanceTypeCount = 6; - - typedef enum SizeType { - SzT_None = 0, - SzT_Enlarge, - SzT_Reduce - } SzType; - - typedef enum StanceType { - StT_None = 0, - StT_Aggressive, - StT_Defensive - } StType; - - static std::string SpellTypeEnumToString(BCEnum::SpType spell_type) { - switch (spell_type) { - case SpT_BindAffinity: - return "SpT_BindAffinity"; - case SpT_Charm: - return "SpT_Charm"; - case SpT_Cure: - return "SpT_Cure"; - case SpT_Depart: - return "SpT_Depart"; - case SpT_Escape: - return "SpT_Escape"; - case SpT_Identify: - return "SpT_Identify"; - case SpT_Invisibility: - return "SpT_Invisibility"; - case SpT_Levitation: - return "SpT_Levitation"; - case SpT_Lull: - return "SpT_Lull"; - case SpT_Mesmerize: - return "SpT_Mesmerize"; - case SpT_MovementSpeed: - return "SpT_MovementSpeed"; - case SpT_Resistance: - return "SpT_Resistance"; - case SpT_Resurrect: - return "SpT_Resurrect"; - case SpT_Rune: - return "SpT_Rune"; - case SpT_SendHome: - return "SpT_SendHome"; - case SpT_Size: - return "SpT_Size"; - case SpT_Stance: - return "SpT_Stance"; - case SpT_SummonCorpse: - return "SpT_SummonCorpse"; - case SpT_WaterBreathing: - return "SpT_WaterBreathing"; - default: - return "SpT_None"; - } - } - - static std::string TargetTypeEnumToString(BCEnum::TType target_type) { - switch (target_type) { - case TT_Self: - return "TT_Self"; - case TT_Animal: - return "TT_Animal"; - case TT_Undead: - return "TT_Undead"; - case TT_Summoned: - return "TT_Summoned"; - case TT_Plant: - return "TT_Plant"; - case TT_Single: - return "TT_Single"; - case TT_GroupV1: - return "TT_GroupV1"; - case TT_GroupV2: - return "TT_GroupV2"; - case TT_AECaster: - return "TT_AECaster"; - case TT_AEBard: - return "TT_AEBard"; - case TT_AETarget: - return "TT_AETarget"; - case TT_Corpse: - return "TT_Corpse"; - default: - return "TT_None"; - } - } -}; - namespace { #define HP_RATIO_DELTA 5.0f @@ -222,14 +34,17 @@ namespace enum { EffectIDFirst = 1, EffectIDLast = 12 }; #define VALIDATECLASSID(x) ((x >= Class::Warrior && x <= Class::Berserker) ? (x) : (0)) -#define CLASSIDTOINDEX(x) ((x >= Class::Warrior && x <= Class::Berserker) ? (x - 1) : (0)) -#define EFFECTIDTOINDEX(x) ((x >= EffectIDFirst && x <= EffectIDLast) ? (x - 1) : (0)) -#define AILMENTIDTOINDEX(x) ((x >= BCEnum::AT_Blindness && x <= BCEnum::AT_Corruption) ? (x - 1) : (0)) -#define RESISTANCEIDTOINDEX(x) ((x >= BCEnum::RT_Fire && x <= BCEnum::RT_Corruption) ? (x - 1) : (0)) // ActionableTarget action_type #define FRIENDLY true #define ENEMY false + + enum { + AFT_None = 0, + AFT_Value, + AFT_GenderRace, + AFT_Race + }; } namespace MyBots @@ -272,13 +87,13 @@ namespace MyBots return (grouped_player->GetGroup() == grouped_bot->GetGroup()); } - static void UniquifySBL(std::list &sbl) { - sbl.remove(nullptr); - sbl.sort(); - sbl.unique(); + static void UniquifySBL(std::vector &sbl) { + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + std::sort(sbl.begin(), sbl.end()); + sbl.erase(std::unique(sbl.begin(), sbl.end()), sbl.end()); } - static void PopulateSBL_ByTargetedBot(Client *bot_owner, std::list &sbl, bool clear_list = true) { + static void PopulateSBL_ByTargetedBot(Client *bot_owner, std::vector &sbl, bool clear_list = true) { if (clear_list) { sbl.clear(); } @@ -292,7 +107,7 @@ namespace MyBots } } - static void PopulateSBL_ByNamedBot(Client *bot_owner, std::list &sbl, const char* name, bool clear_list = true) { + static void PopulateSBL_ByNamedBot(Client *bot_owner, std::vector &sbl, const char* name, bool clear_list = true) { if (clear_list) { sbl.clear(); } @@ -314,7 +129,7 @@ namespace MyBots } } - static void PopulateSBL_ByMyGroupedBots(Client *bot_owner, std::list &sbl, bool clear_list = true) { + static void PopulateSBL_ByMyGroupedBots(Client *bot_owner, std::vector &sbl, bool clear_list = true) { if (clear_list) { sbl.clear(); } @@ -363,7 +178,7 @@ namespace MyBots } } - static void PopulateSBL_ByMyRaidBots(Client* bot_owner, std::list& sbl, bool clear_list = true) { + static void PopulateSBL_ByMyRaidBots(Client* bot_owner, std::vector& sbl, bool clear_list = true) { if (clear_list) { sbl.clear(); } @@ -396,7 +211,7 @@ namespace MyBots } } - static void PopulateSBL_ByTargetsGroupedBots(Client *bot_owner, std::list &sbl, bool clear_list = true) { + static void PopulateSBL_ByTargetsGroupedBots(Client *bot_owner, std::vector &sbl, bool clear_list = true) { if (clear_list) { sbl.clear(); } @@ -448,7 +263,7 @@ namespace MyBots } } - static void PopulateSBL_ByNamesGroupedBots(Client *bot_owner, std::list &sbl, const char* name, bool clear_list = true) { + static void PopulateSBL_ByNamesGroupedBots(Client *bot_owner, std::vector &sbl, const char* name, bool clear_list = true) { if (clear_list) { sbl.clear(); } @@ -507,7 +322,7 @@ namespace MyBots } } - static void PopulateSBL_ByHealRotation(Client *bot_owner, std::list &sbl, const char* name, bool clear_list = true) { + static void PopulateSBL_ByHealRotation(Client *bot_owner, std::vector &sbl, const char* name, bool clear_list = true) { if (clear_list) { sbl.clear(); } @@ -516,7 +331,7 @@ namespace MyBots return; } - std::list selectable_bot_list; + std::vector selectable_bot_list; if (name) { PopulateSBL_ByNamedBot(bot_owner, selectable_bot_list, name); } @@ -545,7 +360,7 @@ namespace MyBots UniquifySBL(sbl); } - static void PopulateSBL_ByHealRotationMembers(Client *bot_owner, std::list &sbl, const char* name, bool clear_list = true) { + static void PopulateSBL_ByHealRotationMembers(Client *bot_owner, std::vector &sbl, const char* name, bool clear_list = true) { if (clear_list) { sbl.clear(); } @@ -554,7 +369,7 @@ namespace MyBots return; } - std::list selectable_bot_list; + std::vector selectable_bot_list; if (name) { PopulateSBL_ByNamedBot(bot_owner, selectable_bot_list, name); } @@ -578,7 +393,7 @@ namespace MyBots } } - static void PopulateSBL_ByHealRotationTargets(Client *bot_owner, std::list &sbl, const char* name, bool clear_list = true) { + static void PopulateSBL_ByHealRotationTargets(Client *bot_owner, std::vector &sbl, const char* name, bool clear_list = true) { if (clear_list) { sbl.clear(); } @@ -587,7 +402,7 @@ namespace MyBots return; } - std::list selectable_bot_list; + std::vector selectable_bot_list; if (name) { PopulateSBL_ByNamedBot(bot_owner, selectable_bot_list, name); } @@ -611,17 +426,17 @@ namespace MyBots } } - static void PopulateSBL_BySpawnedBots(Client *bot_owner, std::list &sbl) { // should be used for most spell casting commands + static void PopulateSBL_BySpawnedBots(Client *bot_owner, std::vector &sbl) { // should be used for most spell casting commands sbl.clear(); if (!bot_owner) { return; } sbl = entity_list.GetBotsByBotOwnerCharacterID(bot_owner->CharacterID()); - sbl.remove(nullptr); + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); } - static void PopulateSBL_BySpawnedBotsClass(Client * bot_owner, std::list &sbl, uint16 cls, bool clear_list = true) { + static void PopulateSBL_BySpawnedBotsClass(Client * bot_owner, std::vector &sbl, uint16 cls, bool clear_list = true) { if (clear_list) { sbl.clear(); } @@ -644,7 +459,7 @@ namespace MyBots } } - static void PopulateSBL_BySpawnedBotsRace(Client* bot_owner, std::list& sbl, uint16 race, bool clear_list = true) { + static void PopulateSBL_BySpawnedBotsRace(Client* bot_owner, std::vector& sbl, uint16 race, bool clear_list = true) { if (clear_list) { sbl.clear(); } @@ -666,6 +481,27 @@ namespace MyBots UniquifySBL(sbl); } } + + static void PopulateSBL_ByAtMMR(Client* bot_owner, std::vector& sbl, bool clear_list = true) { + if (clear_list) { + sbl.clear(); + } + + if (!bot_owner) { + return; + } + + auto selectable_bot_list = entity_list.GetBotsByBotOwnerCharacterID(bot_owner->CharacterID()); + for (auto bot_iter : selectable_bot_list) { + if (!bot_iter->GetMaxMeleeRange()) { + continue; + } + sbl.push_back(bot_iter); + } + if (!clear_list) { + UniquifySBL(sbl); + } + } }; namespace ActionableTarget @@ -738,128 +574,6 @@ namespace ActionableTarget return false; } - - static Mob* VerifyFriendly(Client* bot_owner, BCEnum::TType target_type, bool return_me_on_null_target = true) { - if (IsAttackable(bot_owner, bot_owner->GetTarget()) || target_type == BCEnum::TT_None) - return nullptr; - - auto target_mob = bot_owner->GetTarget(); - Mob* verified_friendly = nullptr; - switch (target_type) { - case BCEnum::TT_Single: - case BCEnum::TT_GroupV1: - case BCEnum::TT_GroupV2: - case BCEnum::TT_AETarget: - verified_friendly = target_mob; - break; - case BCEnum::TT_Animal: - if (target_mob && target_mob->GetBodyType() == BodyType::Animal) - verified_friendly = target_mob; - break; - case BCEnum::TT_Undead: - if (target_mob && target_mob->GetBodyType() == BodyType::Undead) - verified_friendly = target_mob; - break; - case BCEnum::TT_Summoned: - if (target_mob && target_mob->GetBodyType() == BodyType::Summoned) - verified_friendly = target_mob; - break; - case BCEnum::TT_Plant: - if (target_mob && target_mob->GetBodyType() == BodyType::Plant) - verified_friendly = target_mob; - break; - case BCEnum::TT_Corpse: - if (target_mob && target_mob->IsCorpse()) - verified_friendly = target_mob; - break; - default: - return nullptr; - } - - if (return_me_on_null_target && !target_mob && !verified_friendly) { - switch (target_type) { - case BCEnum::TT_Single: - case BCEnum::TT_GroupV1: - case BCEnum::TT_GroupV2: - case BCEnum::TT_AETarget: - verified_friendly = bot_owner; - break; - default: - break; - } - } - - return verified_friendly; - } - - static Mob* VerifyEnemy(Client* bot_owner, BCEnum::TType target_type) { - if (!IsAttackable(bot_owner, bot_owner->GetTarget()) || target_type == BCEnum::TT_None) - return nullptr; - - auto target_mob = bot_owner->GetTarget(); - Mob* verified_enemy = nullptr; - switch (target_type) { - case BCEnum::TT_Animal: - if (target_mob->GetBodyType() == BodyType::Animal) - verified_enemy = target_mob; - break; - case BCEnum::TT_Undead: - if (target_mob->GetBodyType() == BodyType::Undead) - verified_enemy = target_mob; - break; - case BCEnum::TT_Summoned: - if (target_mob->GetBodyType() == BodyType::Summoned) - verified_enemy = target_mob; - break; - case BCEnum::TT_Plant: - if (target_mob->GetBodyType() == BodyType::Plant) - verified_enemy = target_mob; - break; - case BCEnum::TT_Single: - case BCEnum::TT_GroupV1: - case BCEnum::TT_GroupV2: - case BCEnum::TT_AETarget: - verified_enemy = target_mob; - break; - case BCEnum::TT_Corpse: - if (target_mob->IsCorpse()) - verified_enemy = target_mob; - break; - default: - return nullptr; - } - - return verified_enemy; - } - - class Types { - Mob* target[BCEnum::TargetTypeCount]; - bool target_set[BCEnum::TargetTypeCount]; - - public: - Types() { Clear(); } - - void Clear() { - for (int i = BCEnum::TT_None; i <= BCEnum::TargetTypeLast; ++i) { - target[i] = nullptr; - target_set[i] = false; - } - target_set[BCEnum::TT_None] = true; - } - - Mob* Select(Client* bot_owner, BCEnum::TType target_type, bool action_type, bool return_me_on_null_target = true) { - if (target_set[target_type]) - return target[target_type]; - - if (action_type == FRIENDLY) - target[target_type] = VerifyFriendly(bot_owner, target_type, return_me_on_null_target); - else - target[target_type] = VerifyEnemy(bot_owner, target_type); - target_set[target_type] = true; - - return target[target_type]; - } - }; } namespace ActionableBots @@ -875,6 +589,7 @@ namespace ActionableBots ABT_HealRotation, ABT_HealRotationMembers, ABT_HealRotationTargets, + ABT_MMR, ABT_Class, ABT_Race, ABT_Spawned, @@ -892,6 +607,7 @@ namespace ActionableBots ABM_HealRotation = (1 << (ABT_HealRotation - 1)), ABM_HealRotationMembers = (1 << (ABT_HealRotationMembers - 1)), ABM_HealRotationTargets = (1 << (ABT_HealRotationTargets - 1)), + ABM_MMR = (1 << (ABT_MMR - 1)), ABM_Class = (1 << (ABT_Class - 1)), ABM_Race = (1 << (ABT_Race - 1)), ABM_Spawned = (1 << (ABT_Spawned - 1)), @@ -899,12 +615,12 @@ namespace ActionableBots ABM_Spawned_All = (3 << (ABT_Spawned - 1)), ABM_NoFilter = ~0, // grouped values - ABM_Type1 = (ABM_Target | ABM_ByName | ABM_OwnerGroup | ABM_OwnerRaid | ABM_TargetGroup | ABM_NamesGroup | ABM_HealRotationTargets | ABM_Spawned | ABM_Class | ABM_Race), - ABM_Type2 = (ABM_ByName | ABM_OwnerGroup | ABM_OwnerRaid | ABM_NamesGroup | ABM_HealRotation | ABM_Spawned | ABM_Class | ABM_Race) + ABM_Type1 = (ABM_Target | ABM_ByName | ABM_OwnerGroup | ABM_OwnerRaid | ABM_TargetGroup | ABM_NamesGroup | ABM_HealRotationTargets | ABM_Spawned | ABM_MMR | ABM_Class | ABM_Race), + ABM_Type2 = (ABM_ByName | ABM_OwnerGroup | ABM_OwnerRaid | ABM_NamesGroup | ABM_HealRotation | ABM_Spawned | ABM_MMR | ABM_Class | ABM_Race) }; // Populates 'sbl' - static ABType PopulateSBL(Client* bot_owner, std::string ab_type_arg, std::list &sbl, int ab_mask, const char* name = nullptr, uint16 classrace = 0, bool clear_list = true, bool suppress_message = false) { + static ABType PopulateSBL(Client* bot_owner, std::string ab_type_arg, std::vector &sbl, int ab_mask, const char* name = nullptr, uint16 classrace = 0, bool clear_list = true, bool suppress_message = false) { if (clear_list) { sbl.clear(); } @@ -941,6 +657,9 @@ namespace ActionableBots else if (!ab_type_arg.compare("healrotationtargets")) { ab_type = ABT_HealRotationTargets; } + else if (!ab_type_arg.compare("mmr")) { + ab_type = ABT_MMR; + } else if (!ab_type_arg.compare("byclass")) { ab_type = ABT_Class; } @@ -1004,6 +723,11 @@ namespace ActionableBots MyBots::PopulateSBL_ByHealRotationTargets(bot_owner, sbl, name, clear_list); } break; + case ABT_MMR: + if (ab_mask & ABM_MMR) { + MyBots::PopulateSBL_ByAtMMR(bot_owner, sbl, clear_list); + } + break; case ABT_Class: if (ab_mask & ABM_Class) { MyBots::PopulateSBL_BySpawnedBotsClass(bot_owner, sbl, classrace, clear_list); @@ -1133,7 +857,7 @@ namespace ActionableBots return nullptr; } - static Bot* AsSpawned_ByClass(Client *bot_owner, std::list &sbl, uint8 cls, bool petless = false) { + static Bot* AsSpawned_ByClass(Client *bot_owner, std::vector &sbl, uint8 cls, bool petless = false) { if (!bot_owner) { return nullptr; } @@ -1157,7 +881,7 @@ namespace ActionableBots return nullptr; } - static Bot* AsSpawned_ByMinLevelAndClass(Client *bot_owner, std::list &sbl, uint8 minlvl, uint8 cls, bool petless = false) { + static Bot* AsSpawned_ByMinLevelAndClass(Client *bot_owner, std::vector &sbl, uint8 minlvl, uint8 cls, bool petless = false) { // This function can be nixed if we can enforce bot level as owner level..and the level check can then be moved to the spell loop in the command function if (!bot_owner) return nullptr; @@ -1187,7 +911,7 @@ namespace ActionableBots if (!bot_owner || bot_name.empty()) return nullptr; - std::list selectable_bot_list; + std::vector selectable_bot_list; MyBots::PopulateSBL_BySpawnedBots(bot_owner, selectable_bot_list); for (auto bot_iter : selectable_bot_list) { if (!bot_name.compare(bot_iter->GetCleanName())) @@ -1197,7 +921,7 @@ namespace ActionableBots return nullptr; } - static Bot* Select_ByClass(Client* bot_owner, BCEnum::TType target_type, std::list& sbl, uint8 cls, Mob* target_mob = nullptr, bool petless = false) { + static Bot* Select_ByClass(Client* bot_owner, int target_type, std::vector& sbl, uint8 cls, Mob* target_mob = nullptr, bool petless = false) { if (!bot_owner || sbl.empty()) return nullptr; @@ -1208,7 +932,7 @@ namespace ActionableBots continue; if (petless && bot_iter->GetPet()) continue; - if (target_type == BCEnum::TT_GroupV1) { + if (target_type == ST_GroupTeleport) { if (!target_mob) return nullptr; else if (bot_iter->GetGroup() != target_mob->GetGroup()) @@ -1221,7 +945,7 @@ namespace ActionableBots return nullptr; } - static Bot* Select_ByMinLevelAndClass(Client* bot_owner, BCEnum::TType target_type, std::list& sbl, uint8 minlvl, uint8 cls, Mob* target_mob = nullptr, bool petless = false) { + static Bot* Select_ByMinLevelAndClass(Client* bot_owner, int target_type, std::vector& sbl, uint8 minlvl, uint8 cls, Mob* target_mob = nullptr, bool petless = false) { if (!bot_owner || sbl.empty()) return nullptr; @@ -1232,7 +956,7 @@ namespace ActionableBots continue; if (petless && bot_iter->GetPet()) continue; - if (target_type == BCEnum::TT_GroupV1) { + if (target_type == ST_GroupTeleport) { if (!target_mob) return nullptr; else if (bot_iter->GetGroup() != target_mob->GetGroup()) @@ -1246,23 +970,23 @@ namespace ActionableBots } // Filters actual 'sbl' list - static void Filter_ByClasses(Client* bot_owner, std::list& sbl, uint16 class_mask) { - sbl.remove_if([bot_owner](Bot* l) { return (!MyBots::IsMyBot(bot_owner, l)); }); - sbl.remove_if([class_mask](const Bot* l) { return (GetPlayerClassBit(l->GetClass()) & (~class_mask)); }); + static void Filter_ByClasses(Client* bot_owner, std::vector& sbl, uint16 class_mask) { + std::erase_if(sbl, [bot_owner](Bot* l) { return (!MyBots::IsMyBot(bot_owner, l)); }); + std::erase_if(sbl, [class_mask](Bot* l) { return (GetPlayerClassBit(l->GetClass()) & (~class_mask)); }); } - static void Filter_ByMinLevel(Client* bot_owner, std::list& sbl, uint8 min_level) { - sbl.remove_if([bot_owner](Bot* l) { return (!MyBots::IsMyBot(bot_owner, l)); }); - sbl.remove_if([min_level](const Bot* l) { return (l->GetLevel() < min_level); }); + static void Filter_ByMinLevel(Client* bot_owner, std::vector& sbl, uint8 min_level) { + std::erase_if(sbl, [bot_owner](Bot* l) { return (!MyBots::IsMyBot(bot_owner, l)); }); + std::erase_if(sbl, [min_level](Bot* l) { return (l->GetLevel() < min_level); }); } - static void Filter_ByArcher(Client* bot_owner, std::list& sbl) { - sbl.remove_if([bot_owner](Bot* l) { return (!MyBots::IsMyBot(bot_owner, l)); }); - sbl.remove_if([bot_owner](Bot* l) { return (!l->IsBotArcher()); }); + static void Filter_ByRanged(Client* bot_owner, std::vector& sbl) { + std::erase_if(sbl, [bot_owner](Bot* l) { return (!MyBots::IsMyBot(bot_owner, l)); }); + std::erase_if(sbl, [bot_owner](Bot* l) { return (l->IsBotRanged()); }); } - static void Filter_ByHighestSkill(Client* bot_owner, std::list& sbl, EQ::skills::SkillType skill_type, float& skill_value) { - sbl.remove_if([bot_owner](Bot* l) { return (!MyBots::IsMyBot(bot_owner, l)); }); + static void Filter_ByHighestSkill(Client* bot_owner, std::vector& sbl, EQ::skills::SkillType skill_type, float& skill_value) { + std::erase_if(sbl, [bot_owner](Bot* l) { return (!MyBots::IsMyBot(bot_owner, l)); }); skill_value = 0.0f; float mod_skill_value = 0.0f; @@ -1288,329 +1012,19 @@ namespace ActionableBots skilled_bot = bot_iter; } } - - sbl.remove_if([skilled_bot](const Bot* l) { return (l != skilled_bot); }); + std::erase_if(sbl, [skilled_bot](Bot* l) { return (l != skilled_bot); }); } - static void Filter_ByHighestPickLock(Client* bot_owner, std::list& sbl, float& pick_lock_value) { - sbl.remove_if([bot_owner](Bot* l) { return (!MyBots::IsMyBot(bot_owner, l)); }); - sbl.remove_if([bot_owner](const Bot* l) { return (l->GetClass() != Class::Rogue && l->GetClass() != Class::Bard); }); - sbl.remove_if([bot_owner](const Bot* l) { return (l->GetClass() == Class::Rogue && l->GetLevel() < 5); }); - sbl.remove_if([bot_owner](const Bot* l) { return (l->GetClass() == Class::Bard && l->GetLevel() < 40); }); + static void Filter_ByHighestPickLock(Client* bot_owner, std::vector& sbl, float& pick_lock_value) { + std::erase_if(sbl, [bot_owner](Bot* l) { return (!MyBots::IsMyBot(bot_owner, l)); }); + std::erase_if(sbl, [bot_owner](Bot* l) { return (l->GetClass() != Class::Rogue && l->GetClass() != Class::Bard); }); + std::erase_if(sbl, [bot_owner](Bot* l) { return (l->GetClass() == Class::Rogue && l->GetLevel() < 5); }); + std::erase_if(sbl, [bot_owner](Bot* l) { return (l->GetClass() == Class::Bard && l->GetLevel() < 40); }); ActionableBots::Filter_ByHighestSkill(bot_owner, sbl, EQ::skills::SkillPickLock, pick_lock_value); } } - -class STBaseEntry; -class STCharmEntry; -class STCureEntry; -class STDepartEntry; -class STEscapeEntry; -class STInvisibilityEntry; -class STMovementSpeedEntry; -class STResistanceEntry; -class STResurrectEntry; -class STSendHomeEntry; -class STSizeEntry; -class STStanceEntry; - -class STBaseEntry -{ -protected: - BCEnum::SpType m_bcst; - -public: - int spell_id; - uint8 spell_level; - uint8 caster_class; - BCEnum::TType target_type; - - // A non-polymorphic constructor requires an appropriate, non-'ST_None' BCEnum::SType - STBaseEntry(BCEnum::SpType init_bcst = BCEnum::SpT_None) { - spell_id = 0; - spell_level = 255; - caster_class = 255; - target_type = BCEnum::TT_None; - m_bcst = init_bcst; - } - STBaseEntry(STBaseEntry* prototype) { - spell_id = prototype->spell_id; - spell_level = 255; - caster_class = 255; - target_type = prototype->target_type; - m_bcst = prototype->BCST(); - } - virtual ~STBaseEntry() { return; }; - - BCEnum::SpType BCST() { return m_bcst; } - - virtual bool IsDerived() { return false; } - - bool IsCharm() const { return (m_bcst == BCEnum::SpT_Charm); } - bool IsCure() const { return (m_bcst == BCEnum::SpT_Cure); } - bool IsDepart() const { return (m_bcst == BCEnum::SpT_Depart); } - bool IsEscape() const { return (m_bcst == BCEnum::SpT_Escape); } - bool IsInvisibility() const { return (m_bcst == BCEnum::SpT_Invisibility); } - bool IsMovementSpeed() const { return (m_bcst == BCEnum::SpT_MovementSpeed); } - bool IsResistance() const { return (m_bcst == BCEnum::SpT_Resistance); } - bool IsResurrect() const { return (m_bcst == BCEnum::SpT_Resurrect); } - bool IsSendHome() const { return (m_bcst == BCEnum::SpT_SendHome); } - bool IsSize() const { return (m_bcst == BCEnum::SpT_Size); } - bool IsStance() const { return (m_bcst == BCEnum::SpT_Stance); } - - virtual STCharmEntry* SafeCastToCharm() { return nullptr; } - virtual STCureEntry* SafeCastToCure() { return nullptr; } - virtual STDepartEntry* SafeCastToDepart() { return nullptr; } - virtual STEscapeEntry* SafeCastToEscape() { return nullptr; } - virtual STInvisibilityEntry* SafeCastToInvisibility() { return nullptr; } - virtual STMovementSpeedEntry* SafeCastToMovementSpeed() { return nullptr; } - virtual STResistanceEntry* SafeCastToResistance() { return nullptr; } - virtual STResurrectEntry* SafeCastToResurrect() { return nullptr; } - virtual STSendHomeEntry* SafeCastToSendHome() { return nullptr; } - virtual STSizeEntry* SafeCastToSize() { return nullptr; } - virtual STStanceEntry* SafeCastToStance() { return nullptr; } -}; - -class STCharmEntry : public STBaseEntry -{ -public: - bool dire; - - STCharmEntry() { - m_bcst = BCEnum::SpT_Charm; - dire = false; - } - STCharmEntry(STCharmEntry* prototype) : STBaseEntry(prototype) { - m_bcst = BCEnum::SpT_Charm; - dire = prototype->dire; - } - virtual ~STCharmEntry() { return; }; - - virtual bool IsDerived() { return true; } - - virtual STCharmEntry* SafeCastToCharm() { return ((m_bcst == BCEnum::SpT_Charm) ? (static_cast(this)) : (nullptr)); } -}; - -class STCureEntry : public STBaseEntry -{ -public: - int cure_value[BCEnum::AilmentTypeCount]; - int cure_total; - - STCureEntry() { - m_bcst = BCEnum::SpT_Cure; - memset(&cure_value, 0, (sizeof(int) * BCEnum::AilmentTypeCount)); - cure_total = 0; - } - STCureEntry(STCureEntry* prototype) : STBaseEntry(prototype) { - m_bcst = BCEnum::SpT_Cure; - memcpy(&cure_value, prototype->cure_value, (sizeof(int) * BCEnum::AilmentTypeCount)); - cure_total = prototype->cure_total; - } - virtual ~STCureEntry() { return; }; - - virtual bool IsDerived() { return true; } - - virtual STCureEntry* SafeCastToCure() { return ((m_bcst == BCEnum::SpT_Cure) ? (static_cast(this)) : (nullptr)); } -}; - -class STDepartEntry : public STBaseEntry -{ -public: - bool single; - std::string long_name; - - STDepartEntry() { - m_bcst = BCEnum::SpT_Depart; - single = false; - long_name.clear(); - } - STDepartEntry(STDepartEntry* prototype) : STBaseEntry(prototype) { - m_bcst = BCEnum::SpT_Depart; - single = prototype->single; - long_name = prototype->long_name; - } - virtual ~STDepartEntry() { return; }; - - virtual bool IsDerived() { return true; } - - virtual STDepartEntry* SafeCastToDepart() { return ((m_bcst == BCEnum::SpT_Depart) ? (static_cast(this)) : (nullptr)); } -}; - -class STEscapeEntry : public STBaseEntry -{ -public: - bool lesser; - - STEscapeEntry() { - m_bcst = BCEnum::SpT_Escape; - lesser = false; - } - STEscapeEntry(STEscapeEntry* prototype) : STBaseEntry(prototype) { - m_bcst = BCEnum::SpT_Escape; - lesser = prototype->lesser; - } - virtual ~STEscapeEntry() { return; }; - - virtual bool IsDerived() { return true; } - - virtual STEscapeEntry* SafeCastToEscape() { return ((m_bcst == BCEnum::SpT_Escape) ? (static_cast(this)) : (nullptr)); } -}; - -class STInvisibilityEntry : public STBaseEntry -{ -public: - BCEnum::IType invis_type; - - STInvisibilityEntry() { - m_bcst = BCEnum::SpT_Invisibility; - invis_type = BCEnum::IT_None; - } - STInvisibilityEntry(STInvisibilityEntry* prototype) : STBaseEntry(prototype) { - m_bcst = BCEnum::SpT_Invisibility; - invis_type = prototype->invis_type; - } - virtual ~STInvisibilityEntry() { return; }; - - virtual bool IsDerived() { return true; } - - virtual STInvisibilityEntry* SafeCastToInvisibility() { return ((m_bcst == BCEnum::SpT_Invisibility) ? (static_cast(this)) : (nullptr)); } -}; - -class STMovementSpeedEntry : public STBaseEntry -{ -public: - bool group; - - STMovementSpeedEntry() { - m_bcst = BCEnum::SpT_MovementSpeed; - group = false; - } - STMovementSpeedEntry(STMovementSpeedEntry* prototype) : STBaseEntry(prototype) { - m_bcst = BCEnum::SpT_MovementSpeed; - group = prototype->group; - } - virtual ~STMovementSpeedEntry() { return; }; - - virtual bool IsDerived() { return true; } - - virtual STMovementSpeedEntry* SafeCastToMovementSpeed() { return ((m_bcst == BCEnum::SpT_MovementSpeed) ? (static_cast(this)) : (nullptr)); } -}; - -class STResistanceEntry : public STBaseEntry -{ -public: - int resist_value[BCEnum::ResistanceTypeCount]; - int resist_total; - - STResistanceEntry() { - m_bcst = BCEnum::SpT_Resistance; - memset(&resist_value, 0, (sizeof(int) * BCEnum::ResistanceTypeCount)); - resist_total = 0; - } - STResistanceEntry(STResistanceEntry* prototype) : STBaseEntry(prototype) { - m_bcst = BCEnum::SpT_Resistance; - memcpy(&resist_value, prototype->resist_value, (sizeof(int) * BCEnum::ResistanceTypeCount)); - resist_total = prototype->resist_total; - } - virtual ~STResistanceEntry() { return; }; - - virtual bool IsDerived() { return true; } - - virtual STResistanceEntry* SafeCastToResistance() { return ((m_bcst == BCEnum::SpT_Resistance) ? (static_cast(this)) : (nullptr)); } -}; - -class STResurrectEntry : public STBaseEntry -{ -public: - bool aoe; - - STResurrectEntry() { - m_bcst = BCEnum::SpT_Resurrect; - aoe = false; - } - STResurrectEntry(STResurrectEntry* prototype) : STBaseEntry(prototype) { - m_bcst = BCEnum::SpT_Resurrect; - aoe = prototype->aoe; - } - virtual ~STResurrectEntry() { return; }; - - virtual bool IsDerived() { return true; } - - virtual STResurrectEntry* SafeCastToResurrect() { return ((m_bcst == BCEnum::SpT_Resurrect) ? (static_cast(this)) : (nullptr)); } -}; - -class STSendHomeEntry : public STBaseEntry -{ -public: - bool group; - - STSendHomeEntry() { - m_bcst = BCEnum::SpT_SendHome; - group = false; - } - STSendHomeEntry(STSendHomeEntry* prototype) : STBaseEntry(prototype) { - m_bcst = BCEnum::SpT_SendHome; - group = prototype->group; - } - virtual ~STSendHomeEntry() { return; }; - - virtual bool IsDerived() { return true; } - - virtual STSendHomeEntry* SafeCastToSendHome() { return ((m_bcst == BCEnum::SpT_SendHome) ? (static_cast(this)) : (nullptr)); } -}; - -class STSizeEntry : public STBaseEntry -{ -public: - BCEnum::SzType size_type; - - STSizeEntry() { - m_bcst = BCEnum::SpT_Size; - size_type = BCEnum::SzT_None; - } - STSizeEntry(STSizeEntry* prototype) : STBaseEntry(prototype) { - m_bcst = BCEnum::SpT_Size; - size_type = prototype->size_type; - } - virtual ~STSizeEntry() { return; }; - - virtual bool IsDerived() { return true; } - - virtual STSizeEntry* SafeCastToSize() { return ((m_bcst == BCEnum::SpT_Size) ? (static_cast(this)) : (nullptr)); } -}; - -class STStanceEntry : public STBaseEntry { -public: - BCEnum::StType stance_type; - - STStanceEntry() { - m_bcst = BCEnum::SpT_Stance; - stance_type = BCEnum::StT_None; - } - STStanceEntry(STStanceEntry* prototype) : STBaseEntry(prototype) { - m_bcst = BCEnum::SpT_Stance; - stance_type = prototype->stance_type; - } - virtual ~STStanceEntry() { return; }; - - virtual bool IsDerived() { return true; } - - virtual STStanceEntry* SafeCastToStance() { return ((m_bcst == BCEnum::SpT_Stance) ? (static_cast(this)) : (nullptr)); } -}; - - -typedef std::list bcst_list; -typedef std::map bcst_map; - -typedef std::map bcst_required_bot_classes_map; -typedef std::map> bcst_required_bot_classes_map_by_class; - -typedef std::map bcst_levels; -typedef std::map bcst_levels_map; - #define BOT_COMMAND_CHAR '^' typedef void (*BotCmdFuncPtr)(Client *,const Seperator *); @@ -1633,33 +1047,32 @@ int bot_command_real_dispatch(Client *c, char const *message); // Bot Commands void bot_command_actionable(Client *c, const Seperator *sep); -void bot_command_aggressive(Client *c, const Seperator *sep); void bot_command_apply_poison(Client *c, const Seperator *sep); void bot_command_apply_potion(Client* c, const Seperator* sep); void bot_command_attack(Client *c, const Seperator *sep); -void bot_command_bind_affinity(Client *c, const Seperator *sep); +void bot_command_behind_mob(Client* c, const Seperator* sep); +void bot_command_blocked_buffs(Client* c, const Seperator* sep); +void bot_command_blocked_pet_buffs(Client* c, const Seperator* sep); void bot_command_bot(Client *c, const Seperator *sep); -void bot_command_caster_range(Client* c, const Seperator* sep); -void bot_command_charm(Client *c, const Seperator *sep); +void bot_command_bot_settings(Client* c, const Seperator* sep); +void bot_command_cast(Client* c, const Seperator* sep); +void bot_command_discipline(Client* c, const Seperator* sep); +void bot_command_distance_ranged(Client* c, const Seperator* sep); +void bot_command_class_race_list(Client* c, const Seperator* sep); void bot_command_click_item(Client* c, const Seperator* sep); -void bot_command_cure(Client *c, const Seperator *sep); -void bot_command_defensive(Client *c, const Seperator *sep); +void bot_command_copy_settings(Client* c, const Seperator* sep); +void bot_command_default_settings(Client* c, const Seperator* sep); void bot_command_depart(Client *c, const Seperator *sep); -void bot_command_escape(Client *c, const Seperator *sep); void bot_command_find_aliases(Client *c, const Seperator *sep); void bot_command_follow(Client *c, const Seperator *sep); void bot_command_guard(Client *c, const Seperator *sep); void bot_command_heal_rotation(Client *c, const Seperator *sep); void bot_command_help(Client *c, const Seperator *sep); void bot_command_hold(Client *c, const Seperator *sep); -void bot_command_identify(Client *c, const Seperator *sep); +void bot_command_illusion_block(Client* c, const Seperator* sep); void bot_command_inventory(Client *c, const Seperator *sep); -void bot_command_invisibility(Client *c, const Seperator *sep); void bot_command_item_use(Client *c, const Seperator *sep); -void bot_command_levitation(Client *c, const Seperator *sep); -void bot_command_lull(Client *c, const Seperator *sep); -void bot_command_mesmerize(Client *c, const Seperator *sep); -void bot_command_movement_speed(Client *c, const Seperator *sep); +void bot_command_max_melee_range(Client* c, const Seperator* sep); void bot_command_owner_option(Client *c, const Seperator *sep); void bot_command_pet(Client *c, const Seperator *sep); void bot_command_pick_lock(Client *c, const Seperator *sep); @@ -1667,26 +1080,39 @@ void bot_command_pickpocket(Client* c, const Seperator* sep); void bot_command_precombat(Client* c, const Seperator* sep); void bot_command_pull(Client *c, const Seperator *sep); void bot_command_release(Client *c, const Seperator *sep); -void bot_command_resistance(Client *c, const Seperator *sep); -void bot_command_resurrect(Client *c, const Seperator *sep); -void bot_command_rune(Client *c, const Seperator *sep); -void bot_command_send_home(Client *c, const Seperator *sep); -void bot_command_size(Client *c, const Seperator *sep); +void bot_command_sit_hp_percent(Client* c, const Seperator* sep); +void bot_command_sit_in_combat(Client* c, const Seperator* sep); +void bot_command_sit_mana_percent(Client* c, const Seperator* sep); +void bot_command_spell_aggro_checks(Client* c, const Seperator* sep); +void bot_command_spell_announce_cast(Client* c, const Seperator* sep); +void bot_command_spell_delays(Client* c, const Seperator* sep); +void bot_command_spell_engaged_priority(Client* c, const Seperator* sep); +void bot_command_spell_holds(Client* c, const Seperator* sep); +void bot_command_spell_idle_priority(Client* c, const Seperator* sep); +void bot_command_spell_max_hp_pct(Client* c, const Seperator* sep); +void bot_command_spell_max_mana_pct(Client* c, const Seperator* sep); +void bot_command_spell_max_thresholds(Client* c, const Seperator* sep); +void bot_command_spell_min_hp_pct(Client* c, const Seperator* sep); +void bot_command_spell_min_mana_pct(Client* c, const Seperator* sep); +void bot_command_spell_min_thresholds(Client* c, const Seperator* sep); +void bot_command_spell_pursue_priority(Client* c, const Seperator* sep); +void bot_command_spell_resist_limits(Client* c, const Seperator* sep); +void bot_command_spell_target_count(Client* c, const Seperator* sep); void bot_command_spell_list(Client* c, const Seperator *sep); void bot_command_spell_settings_add(Client* c, const Seperator *sep); void bot_command_spell_settings_delete(Client* c, const Seperator *sep); void bot_command_spell_settings_list(Client* c, const Seperator *sep); void bot_command_spell_settings_toggle(Client* c, const Seperator *sep); void bot_command_spell_settings_update(Client* c, const Seperator *sep); +void bot_command_spelltype_ids(Client* c, const Seperator* sep); +void bot_command_spelltype_names(Client* c, const Seperator* sep); void bot_spell_info_dialogue_window(Client* c, const Seperator *sep); void bot_command_enforce_spell_list(Client* c, const Seperator* sep); -void bot_command_summon_corpse(Client *c, const Seperator *sep); void bot_command_suspend(Client *c, const Seperator *sep); void bot_command_taunt(Client *c, const Seperator *sep); void bot_command_timer(Client* c, const Seperator* sep); void bot_command_track(Client *c, const Seperator *sep); void bot_command_view_combos(Client *c, const Seperator *sep); -void bot_command_water_breathing(Client *c, const Seperator *sep); // Bot Subcommands void bot_command_appearance(Client *c, const Seperator *sep); @@ -1706,8 +1132,8 @@ void bot_command_hairstyle(Client *c, const Seperator *sep); void bot_command_heritage(Client *c, const Seperator *sep); void bot_command_inspect_message(Client *c, const Seperator *sep); void bot_command_list_bots(Client *c, const Seperator *sep); -void bot_command_out_of_combat(Client *c, const Seperator *sep); void bot_command_report(Client *c, const Seperator *sep); +void bot_command_set_assistee(Client* c, const Seperator* sep); void bot_command_spawn(Client *c, const Seperator *sep); void bot_command_stance(Client *c, const Seperator *sep); void bot_command_stop_melee_level(Client *c, const Seperator *sep); @@ -1716,12 +1142,11 @@ void bot_command_summon(Client *c, const Seperator *sep); void bot_command_surname(Client *c, const Seperator *sep); void bot_command_tattoo(Client *c, const Seperator *sep); void bot_command_title(Client *c, const Seperator *sep); -void bot_command_toggle_archer(Client *c, const Seperator *sep); void bot_command_toggle_helm(Client *c, const Seperator *sep); +void bot_command_toggle_ranged(Client* c, const Seperator* sep); void bot_command_update(Client *c, const Seperator *sep); void bot_command_woad(Client *c, const Seperator *sep); -void bot_command_circle(Client *c, const Seperator *sep); void bot_command_heal_rotation_adaptive_targeting(Client *c, const Seperator *sep); void bot_command_heal_rotation_add_member(Client *c, const Seperator *sep); void bot_command_heal_rotation_add_target(Client *c, const Seperator *sep); @@ -1749,24 +1174,20 @@ void bot_command_inventory_window(Client *c, const Seperator *sep); void bot_command_pet_get_lost(Client *c, const Seperator *sep); void bot_command_pet_remove(Client *c, const Seperator *sep); void bot_command_pet_set_type(Client *c, const Seperator *sep); -void bot_command_portal(Client *c, const Seperator *sep); // bot command helpers -bool helper_bot_appearance_fail(Client *bot_owner, Bot *my_bot, BCEnum::AFType fail_type, const char* type_desc); +bool helper_bot_appearance_fail(Client *bot_owner, Bot *my_bot, uint8 fail_type, const char* type_desc); void helper_bot_appearance_form_final(Client *bot_owner, Bot *my_bot); void helper_bot_appearance_form_update(Bot *my_bot); uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_class, uint16 bot_race, uint8 bot_gender); -void helper_bot_out_of_combat(Client *bot_owner, Bot *my_bot); int helper_bot_follow_option_chain(Client *bot_owner); -bool helper_cast_standard_spell(Bot* casting_bot, Mob* target_mob, int spell_id, bool annouce_cast = true, uint32* dont_root_before = nullptr); bool helper_command_disabled(Client *bot_owner, bool rule_value, const char *command); bool helper_command_alias_fail(Client *bot_owner, const char* command_handler, const char *alias, const char *command); -void helper_command_depart_list(Client* bot_owner, Bot* druid_bot, Bot* wizard_bot, bcst_list* local_list, bool single_flag = false); bool helper_is_help_or_usage(const char* arg); bool helper_no_available_bots(Client *bot_owner, Bot *my_bot = nullptr); -void helper_send_available_subcommands(Client *bot_owner, const char* command_simile, const std::list& subcommand_list); -void helper_send_usage_required_bots(Client *bot_owner, BCEnum::SpType spell_type, uint8 bot_class = Class::None); -bool helper_spell_check_fail(STBaseEntry* local_entry); -bool helper_spell_list_fail(Client *bot_owner, bcst_list* spell_list, BCEnum::SpType spell_type); +void helper_send_available_subcommands(Client *bot_owner, const char* command_simile, std::vector subcommand_list); +void helper_send_usage_required_bots(Client *bot_owner, uint16 spell_type); +void SendSpellTypeWindow(Client* c, const Seperator* sep); + #endif diff --git a/zone/bot_commands/actionable.cpp b/zone/bot_commands/actionable.cpp index 84965b57d..94ed0b0c3 100644 --- a/zone/bot_commands/actionable.cpp +++ b/zone/bot_commands/actionable.cpp @@ -3,22 +3,37 @@ void bot_command_actionable(Client* c, const Seperator* sep) { if (helper_command_alias_fail(c, "bot_command_actionable", sep->arg[0], "actionable")) { + c->Message(Chat::White, "note: Lists actionable command arguments and use descriptions"); + return; } - c->Message(Chat::White, "Actionable command arguments:"); - c->Message(Chat::White, "target - selects target as single bot .. use ^command [target] or imply by empty actionable argument"); - c->Message(Chat::White, "byname [name] - selects single bot by name"); - c->Message(Chat::White, "ownergroup - selects all bots in the owner's group"); - c->Message(Chat::White, "ownerraid - selects all bots in the owner's raid"); - c->Message(Chat::White, "targetgroup - selects all bots in target's group"); - c->Message(Chat::White, "namesgroup [name] - selects all bots in name's group"); - c->Message(Chat::White, "healrotation [name] - selects all member and target bots of a heal rotation where name is a member"); - c->Message(Chat::White, "healrotationmembers [name] - selects all member bots of a heal rotation where name is a member"); - c->Message(Chat::White, "healrotationtargets [name] - selects all target bots of a heal rotation where name is a member"); - c->Message(Chat::White, "byclass - selects all bots of the chosen class"); - c->Message(Chat::White, "byrace - selects all bots of the chosen rsce"); - c->Message(Chat::White, "spawned - selects all spawned bots"); - c->Message(Chat::White, "all - selects all spawned bots .. argument use indicates en masse database updating"); - c->Message(Chat::White, "You may only select your bots as actionable"); + BotCommandHelpParams p; + + p.description = { "Lists actionable command arguments and use descriptions." }; + p.notes = { + "[target] - uses the command on the target. Some commands will default to target if no actionable is selected.", + "[byname] [name] - selects a bot by name their name.", + "[ownergroup] - selects all bots in the owner's group.", + "[ownerraid] - selects all bots in the owner's raid.", + "[targetgroup] - selects all bots in the target's group.", + "[namesgroup] [name] - selects all bots in [name]'s group.", + "[healrotation] [name] - selects all member and target bots of a heal rotation where [name] is a member.", + "[healrotationmembers] [name] - selects all member bots of a heal rotation where [name] is a member.", + "[healrotationtargets] [name] - selects all target bots of a heal rotation where [name] is a member.", + "[mmr] - selects all bots that are currently at max melee range.", + "[byclass] - selects all bots of the chosen class.", + "[byrace] - selects all bots of the chosen race.", + "[spawned] - selects all spawned bots.", + "[all] - selects all spawned bots.", + "
", + "You may only select your own bots." + }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + + return; } diff --git a/zone/bot_commands/aggressive.cpp b/zone/bot_commands/aggressive.cpp deleted file mode 100644 index 6a3881d98..000000000 --- a/zone/bot_commands/aggressive.cpp +++ /dev/null @@ -1,91 +0,0 @@ -#include "../bot_command.h" - -void bot_command_aggressive(Client* c, const Seperator* sep) -{ - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Stance]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Stance) || - helper_command_alias_fail(c, "bot_command_aggressive", sep->arg[0], "aggressive")) { - return; - } - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message( - Chat::White, - "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | byclass | byrace | spawned] ([actionable_name]))", - sep->arg[0] - ); - helper_send_usage_required_bots(c, BCEnum::SpT_Stance); - return; - } - const int ab_mask = ActionableBots::ABM_Type1; - - std::string class_race_arg = sep->arg[1]; - bool class_race_check = false; - if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { - class_race_check = true; - } - - std::list sbl; - if (ActionableBots::PopulateSBL( - c, - sep->arg[1], - sbl, - ab_mask, - !class_race_check ? sep->arg[2] : nullptr, - class_race_check ? atoi(sep->arg[2]) : 0 - ) == ActionableBots::ABT_None) { - return; - } - - sbl.remove(nullptr); - - int success_count = 0; - int candidate_count = sbl.size(); - for (auto list_iter: *local_list) { - if (sbl.empty()) { - break; - } - - auto local_entry = list_iter->SafeCastToStance(); - if (helper_spell_check_fail(local_entry)) { - continue; - } - if (local_entry->stance_type != BCEnum::StT_Aggressive) { - continue; - } - - for (auto bot_iter = sbl.begin(); bot_iter != sbl.end();) { - Bot* my_bot = *bot_iter; - if (local_entry->caster_class != my_bot->GetClass()) { - ++bot_iter; - continue; - } - if (local_entry->spell_level > my_bot->GetLevel()) { - ++bot_iter; - continue; - } - - my_bot->InterruptSpell(); - if (candidate_count == 1) { - Bot::BotGroupSay( - my_bot, - fmt::format( - "Using {}.", - spells[local_entry->spell_id].name - ).c_str() - ); - } - - my_bot->UseDiscipline(local_entry->spell_id, my_bot->GetID()); - ++success_count; - - bot_iter = sbl.erase(bot_iter); - } - } - - c->Message( - Chat::White, - "%i of %i bots have attempted to use aggressive disciplines", - success_count, - candidate_count - ); -} diff --git a/zone/bot_commands/appearance.cpp b/zone/bot_commands/appearance.cpp index fcaec025c..6707baea2 100644 --- a/zone/bot_commands/appearance.cpp +++ b/zone/bot_commands/appearance.cpp @@ -2,19 +2,19 @@ void bot_command_appearance(Client *c, const Seperator *sep) { - - std::list subcommand_list; - subcommand_list.push_back("botbeardcolor"); - subcommand_list.push_back("botbeardstyle"); - subcommand_list.push_back("botdetails"); - subcommand_list.push_back("botdyearmor"); - subcommand_list.push_back("boteyes"); - subcommand_list.push_back("botface"); - subcommand_list.push_back("bothaircolor"); - subcommand_list.push_back("bothairstyle"); - subcommand_list.push_back("botheritage"); - subcommand_list.push_back("bottattoo"); - subcommand_list.push_back("botwoad"); + std::vector subcommand_list = { + "botbeardcolor", + "botbeardstyle", + "botdetails", + "botdyearmor", + "boteyes", + "botface", + "bothaircolor", + "bothairstyle", + "botheritage", + "bottattoo", + "botwoad" + }; if (helper_command_alias_fail(c, "bot_command_appearance", sep->arg[0], "botappearance")) return; @@ -45,11 +45,11 @@ void bot_command_beard_color(Client *c, const Seperator *sep) uint8 uvalue = Strings::ToInt(sep->arg[1]); - auto fail_type = BCEnum::AFT_None; - if (my_bot->GetGender() != Gender::Male && my_bot->GetRace() != DWARF) - fail_type = BCEnum::AFT_GenderRace; - else if (!PlayerAppearance::IsValidBeardColor(my_bot->GetRace(), my_bot->GetGender(), uvalue)) - fail_type = BCEnum::AFT_Value; + auto fail_type = AFT_None; + if (my_bot->GetGender() != Gender::Male && my_bot->GetRace() != Race::Dwarf) + fail_type = AFT_GenderRace; + else if (!RaceAppearance::IsValidBeardColor(my_bot->GetRace(), my_bot->GetGender(), uvalue)) + fail_type = AFT_Value; else my_bot->SetBeardColor(uvalue); @@ -82,11 +82,11 @@ void bot_command_beard_style(Client *c, const Seperator *sep) uint8 uvalue = Strings::ToInt(sep->arg[1]); - auto fail_type = BCEnum::AFT_None; - if (my_bot->GetGender() != Gender::Male && my_bot->GetRace() != DWARF) - fail_type = BCEnum::AFT_GenderRace; - else if (!PlayerAppearance::IsValidBeard(my_bot->GetRace(), my_bot->GetGender(), uvalue)) - fail_type = BCEnum::AFT_Value; + auto fail_type = AFT_None; + if (my_bot->GetGender() != Gender::Male && my_bot->GetRace() != Race::Dwarf) + fail_type = AFT_GenderRace; + else if (!RaceAppearance::IsValidBeard(my_bot->GetRace(), my_bot->GetGender(), uvalue)) + fail_type = AFT_Value; else my_bot->SetBeard(uvalue); @@ -121,11 +121,11 @@ void bot_command_details(Client *c, const Seperator *sep) uint32 uvalue = Strings::ToInt(sep->arg[1]); - auto fail_type = BCEnum::AFT_None; - if (my_bot->GetRace() != DRAKKIN) - fail_type = BCEnum::AFT_Race; - else if (!PlayerAppearance::IsValidDetail(my_bot->GetRace(), my_bot->GetGender(), uvalue)) - fail_type = BCEnum::AFT_Value; + auto fail_type = AFT_None; + if (my_bot->GetRace() != Race::Drakkin) + fail_type = AFT_Race; + else if (!RaceAppearance::IsValidDetail(my_bot->GetRace(), my_bot->GetGender(), uvalue)) + fail_type = AFT_Value; else my_bot->SetDrakkinDetails(uvalue); @@ -158,7 +158,7 @@ void bot_command_dye_armor(Client *c, const Seperator *sep) c->Message( Chat::White, fmt::format( - "Usage: {} [Material Slot] [Red: 0-255] [Green: 0-255] [Blue: 0-255] ([actionable: target | byname | ownergroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", + "Usage: {} [Material Slot] [Red: 0-255] [Green: 0-255] [Blue: 0-255] ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationmembers | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0] ).c_str() ); @@ -214,7 +214,7 @@ void bot_command_dye_armor(Client *c, const Seperator *sep) uint32 rgb_value = ((uint32)red_value << 16) | ((uint32)green_value << 8) | ((uint32)blue_value); - std::list sbl; + std::vector sbl; auto ab_type = ActionableBots::PopulateSBL(c, sep->arg[5], sbl, ab_mask); if (ab_type == ActionableBots::ABT_None) { return; @@ -280,9 +280,9 @@ void bot_command_eyes(Client *c, const Seperator *sep) //else if (!arg2.compare("right")) // eye_bias = 2; - auto fail_type = BCEnum::AFT_None; - if (!PlayerAppearance::IsValidEyeColor(my_bot->GetRace(), my_bot->GetGender(), uvalue)) { - fail_type = BCEnum::AFT_Value; + auto fail_type = AFT_None; + if (!RaceAppearance::IsValidEyeColor(my_bot->GetRace(), my_bot->GetGender(), uvalue)) { + fail_type = AFT_Value; } else { //if (eye_bias == 1) { @@ -327,13 +327,13 @@ void bot_command_face(Client *c, const Seperator *sep) uint8 uvalue = Strings::ToInt(sep->arg[1]); - auto fail_type = BCEnum::AFT_None; - if (!PlayerAppearance::IsValidFace(my_bot->GetRace(), my_bot->GetGender(), uvalue)) { - fail_type = BCEnum::AFT_Value; + auto fail_type = AFT_None; + if (!RaceAppearance::IsValidFace(my_bot->GetRace(), my_bot->GetGender(), uvalue)) { + fail_type = AFT_Value; } else { uint8 old_woad = 0; - if (my_bot->GetRace() == BARBARIAN) + if (my_bot->GetRace() == Race::Barbarian) old_woad = ((my_bot->GetLuclinFace() / 10) * 10); my_bot->SetLuclinFace((old_woad + uvalue)); } @@ -367,9 +367,9 @@ void bot_command_hair_color(Client *c, const Seperator *sep) uint8 uvalue = Strings::ToInt(sep->arg[1]); - auto fail_type = BCEnum::AFT_None; - if (!PlayerAppearance::IsValidHairColor(my_bot->GetRace(), my_bot->GetGender(), uvalue)) - fail_type = BCEnum::AFT_Value; + auto fail_type = AFT_None; + if (!RaceAppearance::IsValidHairColor(my_bot->GetRace(), my_bot->GetGender(), uvalue)) + fail_type = AFT_Value; else my_bot->SetHairColor(uvalue); @@ -402,9 +402,9 @@ void bot_command_hairstyle(Client *c, const Seperator *sep) uint8 uvalue = Strings::ToInt(sep->arg[1]); - auto fail_type = BCEnum::AFT_None; - if (!PlayerAppearance::IsValidHair(my_bot->GetRace(), my_bot->GetGender(), uvalue)) - fail_type = BCEnum::AFT_Value; + auto fail_type = AFT_None; + if (!RaceAppearance::IsValidHair(my_bot->GetRace(), my_bot->GetGender(), uvalue)) + fail_type = AFT_Value; else my_bot->SetHairStyle(uvalue); @@ -439,11 +439,11 @@ void bot_command_heritage(Client *c, const Seperator *sep) uint32 uvalue = Strings::ToInt(sep->arg[1]); - auto fail_type = BCEnum::AFT_None; - if (my_bot->GetRace() != DRAKKIN) - fail_type = BCEnum::AFT_Race; - else if (!PlayerAppearance::IsValidHeritage(my_bot->GetRace(), my_bot->GetGender(), uvalue)) - fail_type = BCEnum::AFT_Value; + auto fail_type = AFT_None; + if (my_bot->GetRace() != Race::Drakkin) + fail_type = AFT_Race; + else if (!RaceAppearance::IsValidHeritage(my_bot->GetRace(), my_bot->GetGender(), uvalue)) + fail_type = AFT_Value; else my_bot->SetDrakkinHeritage(uvalue); @@ -478,11 +478,11 @@ void bot_command_tattoo(Client *c, const Seperator *sep) uint32 uvalue = Strings::ToInt(sep->arg[1]); - auto fail_type = BCEnum::AFT_None; - if (my_bot->GetRace() != DRAKKIN) - fail_type = BCEnum::AFT_Race; - else if (!PlayerAppearance::IsValidTattoo(my_bot->GetRace(), my_bot->GetGender(), uvalue)) - fail_type = BCEnum::AFT_Value; + auto fail_type = AFT_None; + if (my_bot->GetRace() != Race::Drakkin) + fail_type = AFT_Race; + else if (!RaceAppearance::IsValidTattoo(my_bot->GetRace(), my_bot->GetGender(), uvalue)) + fail_type = AFT_Value; else my_bot->SetDrakkinTattoo(uvalue); @@ -515,12 +515,12 @@ void bot_command_woad(Client *c, const Seperator *sep) uint8 uvalue = Strings::ToInt(sep->arg[1]); - auto fail_type = BCEnum::AFT_None; - if (my_bot->GetRace() != BARBARIAN) { - fail_type = BCEnum::AFT_Race; + auto fail_type = AFT_None; + if (my_bot->GetRace() != Race::Barbarian) { + fail_type = AFT_Race; } - else if (!PlayerAppearance::IsValidWoad(my_bot->GetRace(), my_bot->GetGender(), uvalue)) { - fail_type = BCEnum::AFT_Value; + else if (!RaceAppearance::IsValidWoad(my_bot->GetRace(), my_bot->GetGender(), uvalue)) { + fail_type = AFT_Value; } else { uint8 old_face = (my_bot->GetLuclinFace() % 10); diff --git a/zone/bot_commands/apply_poison.cpp b/zone/bot_commands/apply_poison.cpp index cfffff2cb..444e370ed 100644 --- a/zone/bot_commands/apply_poison.cpp +++ b/zone/bot_commands/apply_poison.cpp @@ -11,6 +11,7 @@ void bot_command_apply_poison(Client* c, const Seperator* sep) if (helper_is_help_or_usage(sep->arg[1])) { c->Message(Chat::White, "usage: %s", sep->arg[0]); + c->Message(Chat::White, "note: Applies cursor-held poison to a rogue bot's weapon"); return; } diff --git a/zone/bot_commands/attack.cpp b/zone/bot_commands/attack.cpp index 60cad8cd8..f91902d4c 100644 --- a/zone/bot_commands/attack.cpp +++ b/zone/bot_commands/attack.cpp @@ -5,17 +5,25 @@ void bot_command_attack(Client *c, const Seperator *sep) if (helper_command_alias_fail(c, "bot_command_attack", sep->arg[0], "attack")) { return; } + if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s [actionable: byname | ownergroup | ownerraid | namesgroup | healrotation | byclass | byrace | default: spawned] ([actionable_name])", sep->arg[0]); + c->Message(Chat::White, "usage: %s [actionable: byname | ownergroup | ownerraid | namesgroup | healrotation | mmr | byclass | byrace | default: spawned] ([actionable_name])", sep->arg[0]); + c->Message(Chat::White, "note: Orders bots to attack a designated target"); return; } - const int ab_mask = ActionableBots::ABM_Type2; + const int ab_mask = ActionableBots::ABM_Type2; Mob* target_mob = ActionableTarget::AsSingle_ByAttackable(c); + if (!target_mob) { - c->Message(Chat::White, "You must an enemy to use this command"); + c->Message(Chat::Yellow, "You must an enemy to use this command"); + return; + } + + if (RuleB(Bots, BotsRequireLoS) && !c->DoLosChecks(target_mob)) { + c->Message(Chat::Red, "You must have Line of Sight to use this command."); return; } @@ -26,11 +34,13 @@ void bot_command_attack(Client *c, const Seperator *sep) std::string class_race_arg(sep->arg[1]); bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { class_race_check = true; } - std::list sbl; + std::vector sbl; + if (ActionableBots::PopulateSBL(c, ab_arg.c_str(), sbl, ab_mask, !class_race_check ? sep->arg[2] : nullptr, class_race_check ? atoi(sep->arg[2]) : 0) == ActionableBots::ABT_None) { return; } @@ -42,36 +52,36 @@ void bot_command_attack(Client *c, const Seperator *sep) size_t attacker_count = 0; Bot *first_attacker = nullptr; - sbl.remove(nullptr); + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + for (auto bot_iter : sbl) { - - if (bot_iter->GetAppearance() != eaDead && bot_iter->GetBotStance() != Stance::Passive) { - - if (!first_attacker) { - first_attacker = bot_iter; - } - ++attacker_count; - - bot_iter->SetAttackFlag(); + if (!bot_iter->ValidStateCheck(c)) { + continue; } + + if (!first_attacker) { + first_attacker = bot_iter; + } + + ++attacker_count; + + bot_iter->SetAttackFlag(); } - if (attacker_count == 1 && first_attacker) { - Bot::BotGroupSay( - first_attacker, - fmt::format( - "Attacking {}.", - target_mob->GetCleanName() - ).c_str() - ); - } else { - c->Message( - Chat::White, - fmt::format( - "{} of your bots are attacking {}.", - sbl.size(), - target_mob->GetCleanName() - ).c_str() - ); + if (first_attacker) { + std::string message; + + if (attacker_count == 1) { + message = fmt::format("Attacking {}.", target_mob->GetCleanName()); + } else { + message = fmt::format("{} of your bots are attacking {}.", sbl.size(), target_mob->GetCleanName()); + } + + c->Message(Chat::Green, message.c_str()); } + else { + c->Message(Chat::Yellow,fmt::format("None of your bots are capable of attacking {}.", target_mob->GetCleanName()).c_str()); + } + + return; } diff --git a/zone/bot_commands/behind_mob.cpp b/zone/bot_commands/behind_mob.cpp new file mode 100644 index 000000000..48712a2e6 --- /dev/null +++ b/zone/bot_commands/behind_mob.cpp @@ -0,0 +1,148 @@ +#include "../bot_command.h" + +void bot_command_behind_mob(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_behind_mob", sep->arg[0], "behindmob")) { + c->Message(Chat::White, "note: Toggles whether or not bots will stay behind the mob during combat."); + + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + BotCommandHelpParams p; + + p.description = { "Toggles whether or not bots will stay behind the mob during combat." }; + p.notes = { "- Bots that are taunting will remain in front of their target"}; + p.example_format = { fmt::format("{} [value] [actionable]", sep->arg[0]) }; + p.examples_one = + { + "To set Monks to stay behind the mob:", + fmt::format("{} 1 byclass {}", sep->arg[0], Class::Monk) + }; + p.examples_two = + { + "To force all bots to stay behind mobs:", + fmt::format("{} 1 spawned", sep->arg[0]) + }; + p.examples_three = + { + "To check the behind mob status of all bots:", + fmt::format("{} current spawned", sep->arg[0]) + }; + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + + return; + } + + std::string arg1 = sep->arg[1]; + + int ab_arg = 1; + bool current_check = false; + uint32 type_value = 0; + + if (sep->IsNumber(1)) { + type_value = atoi(sep->arg[1]); + ++ab_arg; + if (type_value < 0 || type_value > 1) { + c->Message(Chat::Yellow, "You must enter either 0 for disabled or 1 for enabled."); + + return; + } + } + else if (!arg1.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + + if (!first_found) { + first_found = my_bot; + } + + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I {} stay behind mobs.'", + my_bot->GetCleanName(), + my_bot->GetBehindMob() ? "will" : "will not" + ).c_str() + ); + } + else { + my_bot->SetBehindMob(type_value); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I {} stay behind mobs.'", + first_found->GetCleanName(), + first_found->GetBehindMob() ? "will now" : "will no longer" + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots {} stay behind mobs.", + success_count, + type_value ? "will now" : "will no longer" + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/bind_affinity.cpp b/zone/bot_commands/bind_affinity.cpp deleted file mode 100644 index e6d01fe4c..000000000 --- a/zone/bot_commands/bind_affinity.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include "../bot_command.h" - -void bot_command_bind_affinity(Client *c, const Seperator *sep) -{ - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_BindAffinity]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_BindAffinity) || helper_command_alias_fail(c, "bot_command_bind_affinity", sep->arg[0], "bindaffinity")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: () %s", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_BindAffinity); - return; - } - - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - for (auto list_iter : *local_list) { - auto local_entry = list_iter; - if (helper_spell_check_fail(local_entry)) - continue; - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); - if (!target_mob) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; - - // Cast effect message is not being generated - if (helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id)) - c->Message(Chat::White, "Successfully bound %s to this location", target_mob->GetCleanName()); - else - c->Message(Chat::White, "Failed to bind %s to this location", target_mob->GetCleanName()); - break; - } - - helper_no_available_bots(c, my_bot); -} diff --git a/zone/bot_commands/blocked_buffs.cpp b/zone/bot_commands/blocked_buffs.cpp new file mode 100644 index 000000000..3b4ea5623 --- /dev/null +++ b/zone/bot_commands/blocked_buffs.cpp @@ -0,0 +1,460 @@ +#include "../bot_command.h" + +void bot_command_blocked_buffs(Client* c, const Seperator* sep) +{ + if (!RuleB(Bots, AllowBotBlockedBuffs)) { + c->Message(Chat::Yellow, "This command is disabled."); + + return; + } + + if (helper_command_alias_fail(c, "bot_command_blocked_buffs", sep->arg[0], "blockedbuffs")) { + c->Message(Chat::White, "note: Allows you to set, view and wipe beneficial blocked buffs for the selected bots."); + + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + BotCommandHelpParams p; + + p.description = { "Allows you to set, view and wipe beneficial blocked buffs for the selected bots." }; + p.notes = { "- You can 'set' spells to be blocked, 'remove' spells from the blocked list, 'list' the current blocked spells or 'wipe' the entire list." }; + p.example_format = { fmt::format("{} [add [ID] | remove [ID] | list | wipe] [actionable, default: target]", sep->arg[0]) }; + p.examples_one = + { + "To add Courage(Spell ID #202) to the targeted bot's blocked list:", + fmt::format("{} add 202", sep->arg[0]) + }; + p.examples_two = + { + "To view the targeted bot's blocked buff list:", + fmt::format("{} list", sep->arg[0]) + }; + p.examples_three = + { + "To wipe all Warriors bots' blocked buff list:", + fmt::format( "{} wipe byclass {}", sep->arg[0], Class::Warrior) + }; + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + + c->Message( + Chat::Yellow, + fmt::format( + "You can also control bot buffs ({}).", + Saylink::Silent("^blockedbuffs help", "^blockedbuffs") + ).c_str() + ); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + + return; + } + + std::string arg1 = sep->arg[1]; + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool add = false; + bool remove = false; + bool list = false; + bool wipe = false; + uint16 spell_id; + + if (!arg1.compare("add")) { + if (!sep->IsNumber(2) || !IsValidSpell(atoi(sep->arg[2])) || !IsBeneficialSpell(atoi(sep->arg[2]))) { + c->Message(Chat::Yellow, "You must enter a valid spell ID."); + return; + } + + add = true; + spell_id = atoi(sep->arg[2]); + ++ab_arg; + } + else if (!arg1.compare("remove")) { + if (!sep->IsNumber(2) || !IsValidSpell(atoi(sep->arg[2]))) { + c->Message(Chat::Yellow, "You must enter a valid spell ID."); + return; + } + + remove = true; + spell_id = atoi(sep->arg[2]); + ++ab_arg; + } + else if (!arg1.compare("list")) { + list = true; + } + else if (!arg1.compare("wipe")) { + wipe = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string actionable_arg = sep->arg[ab_arg]; + + if (actionable_arg.empty()) { + actionable_arg = "target"; + } + + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + + if (ActionableBots::PopulateSBL(c, actionable_arg, sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + + bool is_success = false; + uint16 success_count = 0; + Bot* first_found = nullptr; + + for (auto bot_iter : sbl) { + if (!bot_iter->IsInGroupOrRaid(c)) { + continue; + } + + if (!first_found) { + first_found = bot_iter; + } + + if (add) { + bot_iter->SetBotBlockedBuff(spell_id, true); + } + else if (remove) { + bot_iter->SetBotBlockedBuff(spell_id, false); + } + else if (list) { + std::vector blocked_buffs = bot_iter->GetBotBlockedBuffs(); + bool found = false; + + if (!blocked_buffs.empty()) { + for (auto& blocked_buff : blocked_buffs) { + if (blocked_buff.blocked == 1 && IsValidSpell(blocked_buff.spell_id)) { + found = true; + c->Message( + Chat::Yellow, + fmt::format( + "{} says, '{} [#{}] is currently blocked. [{}]'", + bot_iter->GetCleanName(), + spells[blocked_buff.spell_id].name, + blocked_buff.spell_id, + Saylink::Silent(fmt::format("^blockedbuffs remove {}", blocked_buff.spell_id),"Remove") + ).c_str() + ); + } + } + } + + if (!found) { + c->Message( + Chat::Yellow, + fmt::format( + "{} says, 'I am not currently blocking any buffs.'", + bot_iter->GetCleanName() + ).c_str() + ); + } + } + else if (wipe) { + bot_iter->ClearBotBlockedBuffs(); + + c->Message( + Chat::Yellow, + fmt::format( + "{} says, I have wiped my blocked buffs list.'", + bot_iter->GetCleanName() + ).c_str() + ); + } + + is_success = true; + ++success_count; + } + + if (!is_success) { + c->Message(Chat::Yellow, "No bots were selected."); + } + else { + if (add || remove) { + c->Message( + Chat::Yellow, + fmt::format( + "{} {} {} blocking {} [#{}]", + ((success_count == 1 && first_found) ? first_found->GetCleanName() : (fmt::format("{}", success_count).c_str())), + ((success_count == 1 && first_found) ? "is" : "of your bots"), + (add ? "now" : "no longer"), + spells[spell_id].name, + spell_id + ).c_str() + ); + } + } +} + + +void bot_command_blocked_pet_buffs(Client* c, const Seperator* sep) +{ + if (!RuleB(Bots, AllowBotBlockedBuffs)) { + c->Message(Chat::Yellow, "This command is disabled."); + + return; + } + + if (helper_command_alias_fail(c, "bot_command_blocked_pet_buffs", sep->arg[0], "blockedpetbuffs")) { + c->Message(Chat::White, "note: Allows you to set, view and wipe beneficial blocked pet buffs for the selected bots."); + + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + BotCommandHelpParams p; + + p.description = { "Allows you to set, view and wipe beneficial blocked pet buffs for the selected bots." }; + p.notes = + { + "- You can 'set' spells to be blocked, 'remove' spells from the blocked list, 'list' the current blocked spells or 'wipe' the entire list.", + "- This controls whether or not any pet the selected bot(s) own will prevent certain beneficial buffs from landing on them." + }; + p.example_format = { fmt::format("{} [add [ID] | remove [ID] | list | wipe] [actionable, default: target]", sep->arg[0]) }; + p.examples_one = + { + "To add Courage (Spell ID #202) to the targeted bot's blocked list:", + fmt::format( + "{} add 202", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::Nuke) + ) + }; + p.examples_two = + { + "To view the targeted bot's blocked buff list:", + fmt::format( + "{} list", + sep->arg[0] + ) + }; + p.examples_three = + { + "To wipe all Warriors bots' blocked buff list:", + fmt::format( + "{} wipe byclass {}", + sep->arg[0], + Class::Warrior + ) + }; + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + + c->Message( + Chat::Yellow, + fmt::format( + "You can also control pet buffs ({}).", + Saylink::Silent("^blockedpetbuffs help", "^blockedpetbuffs") + ).c_str() + ); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + + return; + } + + + std::string arg1 = sep->arg[1]; + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool add = false; + bool remove = false; + bool list = false; + bool wipe = false; + uint16 spell_id; + + if (!arg1.compare("add")) { + if (!sep->IsNumber(2) || !IsValidSpell(atoi(sep->arg[2])) || !IsBeneficialSpell(atoi(sep->arg[2]))) { + c->Message(Chat::Yellow, "You must enter a valid spell ID."); + return; + } + + add = true; + spell_id = atoi(sep->arg[2]); + ++ab_arg; + } + else if (!arg1.compare("remove")) { + if (!sep->IsNumber(2) || !IsValidSpell(atoi(sep->arg[2]))) { + c->Message(Chat::Yellow, "You must enter a valid spell ID."); + return; + } + + remove = true; + spell_id = atoi(sep->arg[2]); + ++ab_arg; + } + else if (!arg1.compare("list")) { + list = true; + } + else if (!arg1.compare("wipe")) { + wipe = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string actionable_arg = sep->arg[ab_arg]; + + if (actionable_arg.empty()) { + actionable_arg = "target"; + } + + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + + if (ActionableBots::PopulateSBL(c, actionable_arg, sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + + bool is_success = false; + uint16 success_count = 0; + Bot* first_found = nullptr; + + for (auto bot_iter : sbl) { + if (!bot_iter->IsInGroupOrRaid(c)) { + continue; + } + + if (!first_found) { + first_found = bot_iter; + } + + if (add) { + bot_iter->SetBotBlockedPetBuff(spell_id, true); + } + else if (remove) { + bot_iter->SetBotBlockedPetBuff(spell_id, false); + } + else if (list) { + std::vector blocked_buffs = bot_iter->GetBotBlockedBuffs(); + bool found = false; + + if (!blocked_buffs.empty()) { + for (auto& blocked_buff : blocked_buffs) { + if (blocked_buff.blocked_pet == 1 && IsValidSpell(blocked_buff.spell_id)) { + found = true; + c->Message( + Chat::Yellow, + fmt::format( + "{} says, '{} [#{}] is currently blocked for my pet. [{}]'", + bot_iter->GetCleanName(), + spells[blocked_buff.spell_id].name, + blocked_buff.spell_id, + Saylink::Silent(fmt::format("^blockedpetbuffs remove {}", blocked_buff.spell_id), "Remove") + ).c_str() + ); + } + } + } + + if (!found) { + c->Message( + Chat::Yellow, + fmt::format( + "{} says, 'I am not currently blocking any pet buffs.'", + bot_iter->GetCleanName() + ).c_str() + ); + } + } + else if (wipe) { + bot_iter->ClearBotBlockedBuffs(); + + c->Message( + Chat::Yellow, + fmt::format( + "{} says, I have wiped my blocked buffs list.'", + bot_iter->GetCleanName() + ).c_str() + ); + } + + is_success = true; + ++success_count; + } + + if (!is_success) { + c->Message(Chat::Yellow, "No bots were selected."); + } + else { + if (add || remove) { + c->Message( + Chat::Yellow, + fmt::format( + "{} {} {} blocking {} [#{}] on their pet.", + ((success_count == 1 && first_found) ? first_found->GetCleanName() : (fmt::format("{}", success_count).c_str())), + ((success_count == 1 && first_found) ? "is" : "of your bots"), + (add ? "now" : "no longer"), + spells[spell_id].name, + spell_id + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/bot.cpp b/zone/bot_commands/bot.cpp index 5d2bfe18e..2d37c503a 100644 --- a/zone/bot_commands/bot.cpp +++ b/zone/bot_commands/bot.cpp @@ -2,25 +2,25 @@ void bot_command_bot(Client *c, const Seperator *sep) { - - std::list subcommand_list; - subcommand_list.push_back("botappearance"); - subcommand_list.push_back("botcamp"); - subcommand_list.push_back("botclone"); - subcommand_list.push_back("botcreate"); - subcommand_list.push_back("botdelete"); - subcommand_list.push_back("botfollowdistance"); - subcommand_list.push_back("botinspectmessage"); - subcommand_list.push_back("botlist"); - subcommand_list.push_back("botoutofcombat"); - subcommand_list.push_back("botreport"); - subcommand_list.push_back("botspawn"); - subcommand_list.push_back("botstance"); - subcommand_list.push_back("botstopmeleelevel"); - subcommand_list.push_back("botsummon"); - subcommand_list.push_back("bottogglearcher"); - subcommand_list.push_back("bottogglehelm"); - subcommand_list.push_back("botupdate"); + std::vector subcommand_list = { + "botappearance", + "botcamp", + "botclone", + "botcreate", + "botdelete", + "botfollowdistance", + "botinspectmessage", + "botlist", + "botoutofcombat", + "botreport", + "botspawn", + "botstance", + "botstopmeleelevel", + "botsummon", + "bottoggleranged", + "bottogglehelm", + "botupdate" + }; if (helper_command_alias_fail(c, "bot_command_bot", sep->arg[0], "bot")) return; @@ -30,12 +30,19 @@ void bot_command_bot(Client *c, const Seperator *sep) void bot_command_camp(Client *c, const Seperator *sep) { - if (helper_command_alias_fail(c, "bot_command_camp", sep->arg[0], "botcamp")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); + if (helper_command_alias_fail(c, "bot_command_camp", sep->arg[0], "botcamp")) { return; } + + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); + return; + } + + if (!Bot::CheckCampSpawnConditions(c)) { + return; + } + const int ab_mask = ActionableBots::ABM_Type1; std::string class_race_arg = sep->arg[1]; @@ -44,13 +51,18 @@ void bot_command_camp(Client *c, const Seperator *sep) class_race_check = true; } - std::list sbl; + std::vector sbl; if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, !class_race_check ? sep->arg[2] : nullptr, class_race_check ? atoi(sep->arg[2]) : 0) == ActionableBots::ABT_None) { return; } - for (auto bot_iter : sbl) + for (auto bot_iter : sbl) { bot_iter->Camp(); + } + + if (!sbl.empty()) { + c->Message(Chat::White, fmt::format("{} of your bots have been camped.", sbl.size()).c_str()); + } } void bot_command_clone(Client *c, const Seperator *sep) @@ -106,87 +118,51 @@ void bot_command_clone(Client *c, const Seperator *sep) if (!Bot::IsValidName(bot_name)) { c->Message( - Chat::White, + Chat::Yellow, fmt::format( - "'{}' is an invalid name. You may only use characters 'A-Z', 'a-z' and '_'.", - bot_name + "'{}' is an invalid name. You may only use characters 'A-Z' or 'a-z' and it must be between 4 and 15 characters. Mixed case {} allowed.", + bot_name, RuleB(Bots, AllowCamelCaseNames) ? "is" : "is not" ).c_str() ); + return; } bool available_flag = false; - if (!database.botdb.QueryNameAvailablity(bot_name, available_flag)) { - c->Message( - Chat::White, - fmt::format( - "Failed to query name availability for '{}'.", - bot_name - ).c_str() - ); - return; - } + + !database.botdb.QueryNameAvailability(bot_name, available_flag); if (!available_flag) { c->Message( - Chat::White, + Chat::Yellow, fmt::format( - "The name '{}' is already being used. Please choose a different name.", + "The name '{}' is already being used or prohibited. Please choose a different name", bot_name ).c_str() ); + return; } - auto bot_creation_limit = c->GetBotCreationLimit(); - auto bot_creation_limit_class = c->GetBotCreationLimit(my_bot->GetClass()); - uint32 bot_count = 0; uint32 bot_class_count = 0; + if (!database.botdb.QueryBotCount(c->CharacterID(), my_bot->GetClass(), bot_count, bot_class_count)) { - c->Message(Chat::White, "Failed to query bot count."); + c->Message(Chat::Yellow, "Failed to query bot count."); + return; } - if (bot_creation_limit >= 0 && bot_count >= bot_creation_limit) { - std::string message; - - if (bot_creation_limit) { - message = fmt::format( - "You have reached the maximum limit of {} bot{}.", - bot_creation_limit, - bot_creation_limit != 1 ? "s" : "" - ); - } else { - message = "You cannot create any bots."; - } - - c->Message(Chat::White, message.c_str()); + if (!Bot::CheckCreateLimit(c, bot_count)) { return; } - if (bot_creation_limit_class >= 0 && bot_class_count >= bot_creation_limit_class) { - std::string message; - - if (bot_creation_limit_class) { - message = fmt::format( - "You cannot create anymore than {} {} bot{}.", - bot_creation_limit_class, - GetClassIDName(my_bot->GetClass()), - bot_creation_limit_class != 1 ? "s" : "" - ); - } else { - message = fmt::format( - "You cannot create any {} bots.", - GetClassIDName(my_bot->GetClass()) - ); - } - - c->Message(Chat::White, message.c_str()); + if (!Bot::CheckCreateLimit(c, bot_class_count, my_bot->GetClass())) { return; } uint32 clone_id = 0; + if (!database.botdb.CreateCloneBot(my_bot->GetBotID(), bot_name, clone_id) || !clone_id) { c->Message( Chat::White, @@ -199,6 +175,7 @@ void bot_command_clone(Client *c, const Seperator *sep) } int clone_stance = Stance::Passive; + if (!database.botdb.LoadStance(my_bot->GetBotID(), clone_stance)) { c->Message( Chat::White, @@ -428,12 +405,25 @@ void bot_command_delete(Client *c, const Seperator *sep) return; } + if (!Bot::CheckCampSpawnConditions(c)) { + return; + } + auto my_bot = ActionableBots::AsTarget_ByBot(c); if (!my_bot) { c->Message(Chat::White, "You must a bot that you own to use this command"); return; } + std::string delete_confirm = sep->arg[1]; + + std::string deleted_check = "confirm"; + + if (!(delete_confirm.find(deleted_check) != std::string::npos)) { + c->Message(Chat::White, "You must type %s confirm to confirm the deletion of %s.", sep->arg[0], my_bot->GetCleanName()); + return; + } + if (!my_bot->DeleteBot()) { c->Message(Chat::White, "Failed to delete '%s' due to database error", my_bot->GetCleanName()); return; @@ -449,65 +439,164 @@ void bot_command_delete(Client *c, const Seperator *sep) void bot_command_follow_distance(Client *c, const Seperator *sep) { - if (helper_command_alias_fail(c, "bot_command_follow_distance", sep->arg[0], "botfollowdistance")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s [set] [distance] ([actionable: target | byname | ownergroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]); - c->Message(Chat::White, "usage: %s [clear] ([actionable: target | byname | ownergroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]); + if (helper_command_alias_fail(c, "bot_command_follow_distance", sep->arg[0], "botfollowdistance")) { + c->Message(Chat::White, "note: Sets or resets the follow distance of the selected bots."); + return; } - const int ab_mask = ActionableBots::ABM_NoFilter; - uint32 bfd = BOT_FOLLOW_DISTANCE_DEFAULT; + if (helper_is_help_or_usage(sep->arg[1])) { + BotCommandHelpParams p; + + p.description = { "Sets or resets the follow distance of the selected bots." }; + p.notes = + { + fmt::format("[Default]: {}", RuleI(Bots, DefaultFollowDistance)), + fmt::format("- You must use a value between 1 and {}.", RuleI(Bots, MaxFollowDistance)) + }; + p.example_format = { fmt::format("{} [reset]/[set [value]] [actionable]", sep->arg[0]) }; + p.examples_one = { + "To set all bots to follow at a distance of 25:", + fmt::format("{} set 25 spawned", sep->arg[0]) + }; + p.examples_two = { + "To check the curret following distance of all bots:", + fmt::format("{} current spawned", sep->arg[0]) + }; + p.examples_three = + { + "To reset the following distance of all Wizards:", + fmt::format( + "{} reset byclass {}", + sep->arg[0], + Class::Wizard + ) + }; + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type2; + + uint32 bfd = RuleI(Bots, DefaultFollowDistance); bool set_flag = false; + bool current_check = false; int ab_arg = 2; - if (!strcasecmp(sep->arg[1], "set")) { + std::string arg1 = sep->arg[1]; + + if (!arg1.compare("set")) { if (!sep->IsNumber(2)) { - c->Message(Chat::White, "A numeric [distance] is required to use this command"); + c->Message(Chat::Yellow, "You must enter a value between 1 and %i.", RuleI(Bots, MaxFollowDistance)); + return; } bfd = Strings::ToInt(sep->arg[2]); - if (bfd < 1) - bfd = 1; - if (bfd > BOT_FOLLOW_DISTANCE_DEFAULT_MAX) - bfd = BOT_FOLLOW_DISTANCE_DEFAULT_MAX; + + if (bfd < 1) { + c->Message(Chat::Yellow, "You must enter a value between 1 and %i.", RuleI(Bots, MaxFollowDistance)); + + return; + } + + if (bfd > RuleI(Bots, MaxFollowDistance)) { + c->Message(Chat::Yellow, "You must enter a value between 1 and %i.", RuleI(Bots, MaxFollowDistance)); + + return; + } + set_flag = true; - ab_arg = 3; + ++ab_arg; } - else if (strcasecmp(sep->arg[1], "clear")) { - c->Message(Chat::White, "This command requires a [set | clear] argument"); + else if (!arg1.compare("current")) { + current_check = true; + } + else if (arg1.compare("reset")) { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + return; } - std::list sbl; - auto ab_type = ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, sep->arg[(ab_arg + 1)]); - if (ab_type == ActionableBots::ABT_None) + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { return; + } + + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); int bot_count = 0; for (auto bot_iter : sbl) { - if (!bot_iter) + if (!bot_iter) { continue; - - bot_iter->SetFollowDistance(bfd); - if (ab_type != ActionableBots::ABT_All && !database.botdb.SaveFollowDistance(bot_iter->GetBotID(), bfd)) { - return; } - ++bot_count; + if (current_check) { + Mob* follow_mob = entity_list.GetMob(bot_iter->GetFollowID()); + + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I am currently following {} at a distance of {}.'", + bot_iter->GetCleanName(), + follow_mob ? follow_mob->GetCleanName() : "no one", + sqrt(bot_iter->GetFollowDistance()) + ).c_str() + ); + } + else { + bot_iter->SetFollowDistance(bfd * bfd); + ++bot_count; + } } - if (ab_type == ActionableBots::ABT_All) { - if (!database.botdb.SaveAllFollowDistances(c->CharacterID(), bfd)) { - return; - } + if (current_check) { + return; + } - c->Message(Chat::White, "%s all of your bot follow distances", set_flag ? "Set" : "Cleared"); + if (bot_count == 1) { + Mob* follow_mob = entity_list.GetMob(sbl.front()->GetFollowID()); + + c->Message( + Chat::Green, + fmt::format( + "{} says, 'Following {} at a distance of {}.'", + sbl.front()->GetCleanName(), + follow_mob ? follow_mob->GetCleanName() : "you", + bfd + ).c_str() + ); } else { - c->Message(Chat::White, "%s %i of your spawned bot follow distances", (set_flag ? "Set" : "Cleared"), bot_count); + c->Message( + Chat::Green, + fmt::format( + "{} of your bots are now following at a distance of {}.", + bot_count, + bfd + ).c_str() + ); } } @@ -516,7 +605,7 @@ void bot_command_inspect_message(Client *c, const Seperator *sep) if (helper_command_alias_fail(c, "bot_command_inspect_message", sep->arg[0], "botinspectmessage")) return; if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s [set | clear] ([actionable: target | byname | ownergroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]); + c->Message(Chat::White, "usage: %s [set | clear] ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationmembers | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); c->Message(Chat::White, "Notes:"); if (c->ClientVersion() >= EQ::versions::ClientVersion::SoF) { c->Message(Chat::White, "- Self-inspect and type your bot's inspect message"); @@ -542,7 +631,7 @@ void bot_command_inspect_message(Client *c, const Seperator *sep) return; } - std::list sbl; + std::vector sbl; auto ab_type = ActionableBots::PopulateSBL(c, sep->arg[2], sbl, ab_mask, sep->arg[3]); if (ab_type == ActionableBots::ABT_None) return; @@ -611,6 +700,7 @@ void bot_command_list_bots(Client *c, const Seperator *sep) return; } + int NO_BOT_LIMIT = -1; bool Account = false; int seps = 1; uint32 filter_value[FilterCount]; @@ -749,7 +839,7 @@ void bot_command_list_bots(Client *c, const Seperator *sep) for (uint8 class_id = Class::Warrior; class_id <= Class::Berserker; class_id++) { auto class_creation_limit = c->GetBotCreationLimit(class_id); - if (class_creation_limit != overall_bot_creation_limit) { + if (class_creation_limit != NO_BOT_LIMIT && class_creation_limit != overall_bot_creation_limit) { c->Message( Chat::White, fmt::format( @@ -764,75 +854,31 @@ void bot_command_list_bots(Client *c, const Seperator *sep) } } -void bot_command_out_of_combat(Client *c, const Seperator *sep) -{ - if (helper_command_alias_fail(c, "bot_command_out_of_combat", sep->arg[0], "botoutofcombat")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s ([option: on | off]) ([actionable: target | byname] ([actionable_name]))", sep->arg[0]); - return; - } - const int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_ByName); - - std::string arg1 = sep->arg[1]; - - bool behavior_state = false; - bool toggle_behavior = true; - int ab_arg = 1; - if (!arg1.compare("on")) { - behavior_state = true; - toggle_behavior = false; - ab_arg = 2; - } - else if (!arg1.compare("off")) { - toggle_behavior = false; - ab_arg = 2; - } - - std::list sbl; - if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, sep->arg[(ab_arg + 1)]) == ActionableBots::ABT_None) - return; - - for (auto bot_iter : sbl) { - if (!bot_iter) - continue; - - if (toggle_behavior) - bot_iter->SetAltOutOfCombatBehavior(!bot_iter->GetAltOutOfCombatBehavior()); - else - bot_iter->SetAltOutOfCombatBehavior(behavior_state); - - helper_bot_out_of_combat(c, bot_iter); - } -} - void bot_command_report(Client *c, const Seperator *sep) { if (helper_command_alias_fail(c, "bot_command_report", sep->arg[0], "botreport")) return; if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]); + c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationmembers | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); return; } - const int ab_mask = ActionableBots::ABM_NoFilter; - std::string ab_type_arg = sep->arg[1]; - if (ab_type_arg.empty()) { - auto t = c->GetTarget(); - if (t && t->IsClient()) { - if (t->CastToClient() == c) { - ab_type_arg = "ownergroup"; - } else { - ab_type_arg = "targetgroup"; - } - } else { - ab_type_arg = "spawned"; - } + const int ab_mask = ActionableBots::ABM_Type1; + + std::string arg1 = sep->arg[1]; + int ab_arg = 1; + + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; } - std::list sbl; - if (ActionableBots::PopulateSBL(c, ab_type_arg, sbl, ab_mask, sep->arg[2]) == ActionableBots::ABT_None) + std::vector sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { return; + } for (auto bot_iter : sbl) { if (!bot_iter) @@ -864,46 +910,15 @@ void bot_command_spawn(Client *c, const Seperator *sep) return; } - auto bot_character_level = c->GetBotRequiredLevel(); - if ( - bot_character_level >= 0 && - c->GetLevel() < bot_character_level && - !c->GetGM() - ) { - c->Message( - Chat::White, - fmt::format( - "You must be level {} to spawn bots.", - bot_character_level - ).c_str() - ); + if (!Bot::CheckHighEnoughLevelForBots(c)) { return; } - if (!Bot::CheckSpawnConditions(c)) { + if (!Bot::CheckCampSpawnConditions(c)) { return; } - auto bot_spawn_limit = c->GetBotSpawnLimit(); - auto spawned_bot_count = Bot::SpawnedBotCount(c->CharacterID()); - - if ( - bot_spawn_limit >= 0 && - spawned_bot_count >= bot_spawn_limit && - !c->GetGM() - ) { - std::string message; - if (bot_spawn_limit) { - message = fmt::format( - "You cannot have more than {} spawned bot{}.", - bot_spawn_limit, - bot_spawn_limit != 1 ? "s" : "" - ); - } else { - message = "You are not currently allowed to spawn any bots."; - } - - c->Message(Chat::White, message.c_str()); + if (!Bot::CheckSpawnLimit(c)) { return; } @@ -916,6 +931,7 @@ void bot_command_spawn(Client *c, const Seperator *sep) uint32 bot_id = 0; uint8 bot_class = Class::None; + if (!database.botdb.LoadBotID(bot_name, bot_id, bot_class)) { c->Message( Chat::White, @@ -927,51 +943,6 @@ void bot_command_spawn(Client *c, const Seperator *sep) return; } - auto bot_spawn_limit_class = c->GetBotSpawnLimit(bot_class); - auto spawned_bot_count_class = Bot::SpawnedBotCount(c->CharacterID(), bot_class); - - if ( - bot_spawn_limit_class >= 0 && - spawned_bot_count_class >= bot_spawn_limit_class && - !c->GetGM() - ) { - std::string message; - - if (bot_spawn_limit_class) { - message = fmt::format( - "You cannot have more than {} spawned {} bot{}.", - bot_spawn_limit_class, - GetClassIDName(bot_class), - bot_spawn_limit_class != 1 ? "s" : "" - ); - } else { - message = fmt::format( - "You are not currently allowed to spawn any {} bots.", - GetClassIDName(bot_class) - ); - } - - c->Message(Chat::White, message.c_str()); - return; - } - - auto bot_character_level_class = c->GetBotRequiredLevel(bot_class); - if ( - bot_character_level_class >= 0 && - c->GetLevel() < bot_character_level_class && - !c->GetGM() - ) { - c->Message( - Chat::White, - fmt::format( - "You must be level {} to spawn {} bots.", - bot_character_level_class, - GetClassIDName(bot_class) - ).c_str() - ); - return; - } - if (!bot_id) { c->Message( Chat::White, @@ -983,6 +954,14 @@ void bot_command_spawn(Client *c, const Seperator *sep) return; } + if (!Bot::CheckHighEnoughLevelForBots(c, bot_class)) { + return; + } + + if (!Bot::CheckSpawnLimit(c, bot_class)) { + return; + } + if (entity_list.GetMobByBotID(bot_id)) { c->Message( Chat::White, @@ -991,10 +970,12 @@ void bot_command_spawn(Client *c, const Seperator *sep) bot_name ).c_str() ); + return; } auto my_bot = Bot::LoadBot(bot_id); + if (!my_bot) { c->Message( Chat::White, @@ -1004,6 +985,7 @@ void bot_command_spawn(Client *c, const Seperator *sep) bot_id ).c_str() ); + return; } @@ -1016,7 +998,9 @@ void bot_command_spawn(Client *c, const Seperator *sep) bot_id ).c_str() ); + safe_delete(my_bot); + return; } @@ -1041,144 +1025,400 @@ void bot_command_spawn(Client *c, const Seperator *sep) }; uint8 message_index = 0; + if (c->GetBotOption(Client::booSpawnMessageClassSpecific)) { message_index = VALIDATECLASSID(my_bot->GetClass()); } std::string silent_confirm = sep->arg[2]; - bool silentTell = false; + bool silent_tell = false; if (!silent_confirm.compare("silent")) { - silentTell = true; + silent_tell = true; } - if (!silentTell && c->GetBotOption(Client::booSpawnMessageSay)) { - Bot::BotGroupSay(my_bot, bot_spawn_message[message_index].c_str()); + if (!silent_tell && c->GetBotOption(Client::booSpawnMessageSay)) { + my_bot->RaidGroupSay(bot_spawn_message[message_index].c_str()); } - else if (!silentTell && c->GetBotOption(Client::booSpawnMessageTell)) { + else if (!silent_tell && c->GetBotOption(Client::booSpawnMessageTell)) { my_bot->OwnerMessage(bot_spawn_message[message_index]); } } void bot_command_stance(Client *c, const Seperator *sep) { - if (helper_command_alias_fail(c, "bot_command_stance", sep->arg[0], "botstance")) + if (helper_command_alias_fail(c, "bot_command_stance", sep->arg[0], "botstance")) { + c->Message(Chat::White, "note: Change a bot's stance to control the way it behaves."); + return; + } + if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s [current | value: 1-9] ([actionable: target | byname] ([actionable_name]))", sep->arg[0]); - c->Message( - Chat::White, + BotCommandHelpParams p; + + p.description = { "Change a bot's stance to control the way it behaves." }; + p.notes = + { + "- Changing a stance will reset all settings to match that stance type.", + "- Any changes made will only save to that stance for future use.", fmt::format( - "Value: {} ({}), {} ({}), {} ({}), {} ({}), {} ({}), {} ({}), {} ({}), {} ({}), {} ({})", - Stance::Passive, - Stance::GetName(Stance::Passive), - Stance::Balanced, - Stance::GetName(Stance::Balanced), - Stance::Efficient, - Stance::GetName(Stance::Efficient), - Stance::Reactive, - Stance::GetName(Stance::Reactive), - Stance::Aggressive, + "- {} (#{}) will tell Non-Warrior classes to taunt automatically.", Stance::GetName(Stance::Aggressive), - Stance::Assist, + Stance::Aggressive + ), + "
", + "Available stances:", + fmt::format( + "{} (#{}), {} (#{}), {} (#{}), {} (#{}), {} (#{}), {} (#{}), {} (#{})", + Stance::GetName(Stance::Passive), + Stance::Passive, + Stance::GetName(Stance::Balanced), + Stance::Balanced, + Stance::GetName(Stance::Efficient), + Stance::Efficient, + Stance::GetName(Stance::Aggressive), + Stance::Aggressive, Stance::GetName(Stance::Assist), - Stance::Burn, + Stance::Assist, Stance::GetName(Stance::Burn), - Stance::Efficient2, - Stance::GetName(Stance::Efficient2), - Stance::AEBurn, - Stance::GetName(Stance::AEBurn) + Stance::Burn, + Stance::GetName(Stance::AEBurn), + Stance::AEBurn + ), + "
", + fmt::format( + "- {} (#{}) - Idle. Does not cast or engage in combat.", + Stance::GetName(Stance::Passive), + Stance::Passive + ), + fmt::format( + "- {} (#{}) [Default] - Overall balance and casts most spell types by default.", + Stance::GetName(Stance::Balanced), + Stance::Balanced + ), + fmt::format( + "- {} (#{}) - More mana and aggro efficient (SKs will still cast hate line). Longer delays between detrimental spells, thresholds adjusted to cast less often.", + Stance::GetName(Stance::Efficient), + Stance::Efficient + ), + fmt::format( + "- {} (#{}) - Much more aggressive in their cast times and thresholds. More DPS, debuffs and slow but a higher risk of snagging aggro.", + Stance::GetName(Stance::Aggressive), + Stance::Aggressive + ), + fmt::format( + "- {} (#{}) - Support role. Most offensive spell types are disabled. Focused on heals, cures, CC, debuffs and slows.", + Stance::GetName(Stance::Assist), + Stance::Assist + ), + fmt::format( + "- {} (#{}) - Murder. Doesn't care about aggro, just wants to kill. DPS Machine.", + Stance::GetName(Stance::Burn), + Stance::Burn + ), + fmt::format( + "- {} (#{}) - Murder EVERYTHING. Doesn't care about aggro, casts AEs. Everything must die ASAP.", + Stance::GetName(Stance::AEBurn), + Stance::AEBurn + ) + }; + p.example_format = + { fmt::format( "{} [current | value]", sep->arg[0]) }; + p.examples_one = + { + "To set all bots to BurnAE:", + fmt::format("{} {} spawned {}", + sep->arg[0], + Stance::Aggressive, + Class::ShadowKnight + ) + }; + p.examples_two = + { + "To set all Shadowknights to Aggressive:", + fmt::format("{} {} byclass {}", + sep->arg[0], + Stance::Aggressive, + Class::ShadowKnight + ) + }; + p.examples_three = { + "To check the current stances of all bots:", + fmt::format("{} current spawned", sep->arg[0]) + }; + + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + + bool current_check = false; + int ab_arg = 1; + uint32 value = 0; + + std::string arg1 = sep->arg[1]; + + if (sep->IsNumber(1)) { + ++ab_arg; + value = atoi(sep->arg[1]); + if (!Bot::IsValidBotStance(value)) { + c->Message( + Chat::Yellow, + fmt::format( + "You must choose a valid stance ID, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + else if (!arg1.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) ).c_str() ); - return; - } - int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_ByName); - bool current_flag = false; - uint8 bst = Stance::Unknown; - - if (!strcasecmp(sep->arg[1], "current")) - current_flag = true; - else if (sep->IsNumber(1)) { - bst = static_cast(Strings::ToUnsignedInt(sep->arg[1])); - if (!Stance::IsValid(bst)) { - bst = Stance::Unknown; - } - } - - if (!current_flag && bst == Stance::Unknown) { - c->Message(Chat::White, "A [current] argument or valid numeric [value] is required to use this command"); return; } - std::list sbl; - if (ActionableBots::PopulateSBL(c, sep->arg[2], sbl, ab_mask, sep->arg[3]) == ActionableBots::ABT_None) - return; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + Bot* first_found = nullptr; + int success_count = 0; for (auto bot_iter : sbl) { - if (!bot_iter) - continue; - - if (!current_flag) { - bot_iter->SetBotStance(bst); - bot_iter->Save(); + if (!first_found) { + first_found = bot_iter; } - Bot::BotGroupSay( - bot_iter, + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My current stance is {} ({}).'", + bot_iter->GetCleanName(), + Stance::GetName(bot_iter->GetBotStance()), + bot_iter->GetBotStance() + ).c_str() + ); + + continue; + } + + bot_iter->Save(); + bot_iter->SetBotStance(value); + bot_iter->LoadDefaultBotSettings(); + database.botdb.LoadBotSettings(bot_iter); + + if ( + (bot_iter->GetClass() == Class::Warrior || bot_iter->GetClass() == Class::Paladin || bot_iter->GetClass() == Class::ShadowKnight) && + (bot_iter->GetBotStance() == Stance::Aggressive) + ) { + bot_iter->SetTaunting(true); + + if (bot_iter->HasPet() && bot_iter->GetPet()->GetSkill(EQ::skills::SkillTaunt)) { + bot_iter->GetPet()->CastToNPC()->SetTaunting(true); + } + } + else { + bot_iter->SetTaunting(false); + + if (bot_iter->HasPet() && bot_iter->GetPet()->GetSkill(EQ::skills::SkillTaunt)) { + bot_iter->GetPet()->CastToNPC()->SetTaunting(false); + } + } + + bot_iter->Save(); + ++success_count; + } + + if (current_check) { + return; + } + + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, fmt::format( - "My current stance is {} ({}).", - Stance::GetName(bot_iter->GetBotStance()), - bot_iter->GetBotStance() + "{} says, 'I am now set to the stance [{}].'", + first_found->GetCleanName(), + Stance::GetName(value) + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots are now set to the stance [{}].", + success_count, + Stance::GetName(value) ).c_str() ); } } -void bot_command_stop_melee_level(Client *c, const Seperator *sep) +void bot_command_stop_melee_level(Client* c, const Seperator* sep) { - if (helper_command_alias_fail(c, "bot_command_stop_melee_level", sep->arg[0], "botstopmeleelevel")) + if (helper_command_alias_fail(c, "bot_command_stop_melee_level", sep->arg[0], "botstopmeleelevel")) { return; + } + if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s [current | reset | sync | value: 0-255]", sep->arg[0]); - c->Message(Chat::White, "note: Only caster or hybrid class bots may be modified"); + c->Message(Chat::White, "usage: %s [current | reset | sync | value: 0-255] ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); c->Message(Chat::White, "note: Use [reset] to set stop melee level to server rule"); c->Message(Chat::White, "note: Use [sync] to set stop melee level to current bot level"); return; } - auto my_bot = ActionableBots::AsTarget_ByBot(c); - if (!my_bot) { - c->Message(Chat::White, "You must a bot that you own to use this command"); - return; - } - if (!IsCasterClass(my_bot->GetClass()) && !IsHybridClass(my_bot->GetClass())) { - c->Message(Chat::White, "You must a caster or hybrid class bot to use this command"); - return; - } + const int ab_mask = ActionableBots::ABM_Type1; + std::string arg1 = sep->arg[1]; + int ab_arg = 1; uint8 sml = RuleI(Bots, CasterStopMeleeLevel); + bool sync_sml = false; + bool reset_sml = false; + bool current_check = false; if (sep->IsNumber(1)) { + ab_arg = 2; sml = Strings::ToInt(sep->arg[1]); + if (sml <= 0 || sml > 255) { + c->Message(Chat::White, "You must provide a value between 0-255."); + return; + } } else if (!strcasecmp(sep->arg[1], "sync")) { - sml = my_bot->GetLevel(); + ab_arg = 2; + sync_sml = true; } - else if (!strcasecmp(sep->arg[1], "current")) { - c->Message(Chat::White, "My current melee stop level is %u", my_bot->GetStopMeleeLevel()); + else if (!arg1.compare("current")) { + ab_arg = 2; + current_check = true; + } + else if (!strcasecmp(sep->arg[1], "reset")) { + ab_arg = 2; + reset_sml = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + return; } - else if (strcasecmp(sep->arg[1], "reset")) { - c->Message(Chat::White, "A [current] or [reset] argument, or numeric [value] is required to use this command"); + + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { return; } - // [reset] falls through with initialization value - my_bot->SetStopMeleeLevel(sml); - database.botdb.SaveStopMeleeLevel(my_bot->GetBotID(), sml); + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); - c->Message(Chat::White, "Successfully set stop melee level for %s to %u", my_bot->GetCleanName(), sml); + Bot* first_found = nullptr; + int success_count = 0; + + for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + + if (!first_found) { + first_found = my_bot; + } + + if (sync_sml) { + sml = my_bot->GetLevel(); + } + + if (reset_sml) { + sml = my_bot->GetDefaultBotBaseSetting(BotBaseSettings::StopMeleeLevel); + } + + if (current_check) { + c->Message( + Chat::White, + fmt::format( + "{} says, 'My current stop melee level is {}.'", + my_bot->GetCleanName(), + my_bot->GetStopMeleeLevel() + ).c_str() + ); + continue; + } + else { + my_bot->SetStopMeleeLevel(sml); + ++success_count; + } + } + + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::White, + fmt::format( + "{} says, 'My stop melee level was {} to {}.'", + first_found->GetCleanName(), + reset_sml ? "reset" : "set", + sml + ).c_str() + ); + } + else { + c->Message( + Chat::White, + fmt::format( + "{} of your bots {} their stop melee{}'", // level to {}. + success_count, + reset_sml ? "reset" : "set", + fmt::format("{}", reset_sml ? "." : fmt::format(" level to {}.", sml).c_str()).c_str(), + sml + ).c_str() + ); + } + } } void bot_command_summon(Client *c, const Seperator *sep) @@ -1188,23 +1428,28 @@ void bot_command_summon(Client *c, const Seperator *sep) } if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); + c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); return; } + const int ab_mask = ActionableBots::ABM_Type1; - std::string class_race_arg = sep->arg[1]; + std::string arg1 = sep->arg[1]; + int ab_arg = 1; + + std::string class_race_arg = sep->arg[ab_arg]; bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { class_race_check = true; } - std::list sbl; - if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, !class_race_check ? sep->arg[2] : nullptr, class_race_check ? atoi(sep->arg[2]) : 0) == ActionableBots::ABT_None) { + std::vector sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { return; } - sbl.remove(nullptr); + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); for (auto bot_iter : sbl) { if (!bot_iter) { @@ -1220,8 +1465,11 @@ void bot_command_summon(Client *c, const Seperator *sep) continue; } - bot_iter->GetPet()->WipeHateList(); - bot_iter->GetPet()->SetTarget(nullptr); + if (bot_iter->HasControllablePet(BotAnimEmpathy::BackOff)) { + bot_iter->GetPet()->WipeHateList(); + bot_iter->GetPet()->SetTarget(nullptr); + } + bot_iter->GetPet()->Teleport(c->GetPosition()); } @@ -1245,127 +1493,331 @@ void bot_command_summon(Client *c, const Seperator *sep) } } -void bot_command_toggle_archer(Client *c, const Seperator *sep) +void bot_command_toggle_ranged(Client *c, const Seperator *sep) { - if (helper_command_alias_fail(c, "bot_command_toggle_archer", sep->arg[0], "bottogglearcher")) { + if (helper_command_alias_fail(c, "bot_command_toggle_ranged", sep->arg[0], "bottoggleranged")) { + c->Message(Chat::White, "note: Toggles a ranged bot between melee and ranged weapon use."); + return; } if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s ([option: on | off]) ([actionable: target | byname] ([actionable_name]))", sep->arg[0]); + BotCommandHelpParams p; + + p.description = { "Toggles a ranged bot between melee and ranged weapon use." }; + p.example_format = { fmt::format("{} [value] [actionable]", sep->arg[0]) }; + p.examples_one = + { + "To set BotA to use their ranged:", + fmt::format( + "{} 1 byname BotA", + sep->arg[0] + ) + }; + p.examples_two = + { + "To set all ranger bots to ranged:", + fmt::format( + "{} 1 byclass {}", + sep->arg[0], + Class::Ranger + ) + }; + p.examples_two = + { + "To check the ranged status of all bots:", + fmt::format( + "{} current spawned", + sep->arg[0], + Class::Ranger + ) + }; + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + return; } - const int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_ByName); std::string arg1 = sep->arg[1]; - bool archer_state = false; - bool toggle_archer = true; int ab_arg = 1; - if (!arg1.compare("on")) { - archer_state = true; - toggle_archer = false; - ab_arg = 2; - } - else if (!arg1.compare("off")) { - toggle_archer = false; - ab_arg = 2; - } + bool current_check = false; + uint32 type_value = 0; + + if (sep->IsNumber(1)) { + type_value = atoi(sep->arg[1]); + ++ab_arg; + if (type_value < 0 || type_value > 1) { + c->Message(Chat::Yellow, "You must enter either 0 for disabled or 1 for enabled."); + + return; + } + } + else if (!arg1.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); - std::list sbl; - if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, sep->arg[(ab_arg + 1)]) == ActionableBots::ABT_None) { return; } - for (auto bot_iter : sbl) { - if (!bot_iter) { + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { continue; } - if (toggle_archer) { - bot_iter->SetBotArcherySetting(!bot_iter->IsBotArcher(), true); + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I {} currently ranged.'", + my_bot->GetCleanName(), + my_bot->IsBotRanged() ? "am" : "am no longer" + ).c_str() + ); } else { - bot_iter->SetBotArcherySetting(archer_state, true); - } - bot_iter->ChangeBotArcherWeapons(bot_iter->IsBotArcher()); + if (my_bot->GetBotRangedValue() < RuleI(Combat, MinRangedAttackDist)) { + c->Message(Chat::Yellow, "%s does not have proper weapons or ammo to be at range.", my_bot->GetCleanName()); + continue; + } - if (bot_iter->GetClass() == Class::Ranger && bot_iter->GetLevel() >= 61) { - bot_iter->SetRangerAutoWeaponSelect(bot_iter->IsBotArcher()); + if (!first_found) { + first_found = my_bot; + } + + my_bot->SetBotRangedSetting(type_value); + my_bot->ChangeBotRangedWeapons(my_bot->IsBotRanged()); + ++success_count; + } + } + + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I {} ranged.'", + first_found->GetCleanName(), + first_found->IsBotRanged() ? "am now" : "am no longer" + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots {} ranged.", + success_count, + type_value ? "are now" : "are no longer" + ).c_str() + ); } } } void bot_command_toggle_helm(Client *c, const Seperator *sep) { - if (helper_command_alias_fail(c, "bot_command_toggle_helm", sep->arg[0], "bottogglehelm")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s ([option: on | off]) ([actionable: target | byname | ownergroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]); + if (helper_command_alias_fail(c, "bot_command_toggle_helm", sep->arg[0], "bottogglehelm")) { + c->Message(Chat::White, "note: Toggles whether or not bots will show their helm."); + + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + BotCommandHelpParams p; + + p.description = { "Toggles whether or not bots will show their helm." }; + p.example_format = { fmt::format("{} [value] [actionable]", sep->arg[0]) }; + p.examples_one = + { + "To set BotA to show their helm:", + fmt::format( + "{} 1 byname BotA", + sep->arg[0] + ) + }; + p.examples_two = + { + "To set all bots to show their helm:", + fmt::format( + "{} 1 spawned", + sep->arg[0] + ) + }; + p.examples_three = + { + "To check the toggle helm status of all bots:", + fmt::format( + "{} current spawned", + sep->arg[0] + ) + }; + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + return; } - const int ab_mask = ActionableBots::ABM_NoFilter; std::string arg1 = sep->arg[1]; - bool helm_state = false; - bool toggle_helm = true; int ab_arg = 1; - if (!arg1.compare("on")) { - helm_state = true; - toggle_helm = false; - ab_arg = 2; - } - else if (!arg1.compare("off")) { - toggle_helm = false; - ab_arg = 2; - } + bool current_check = false; + uint32 type_value = 0; - std::list sbl; - auto ab_type = ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, sep->arg[(ab_arg + 1)]); - if (ab_type == ActionableBots::ABT_None) - return; + if (sep->IsNumber(1)) { + type_value = atoi(sep->arg[1]); + ++ab_arg; + if (type_value < 0 || type_value > 1) { + c->Message(Chat::Yellow, "You must enter either 0 for disabled or 1 for enabled."); - int bot_count = 0; - for (auto bot_iter : sbl) { - if (!bot_iter) - continue; - - if (toggle_helm) - bot_iter->SetShowHelm(!bot_iter->GetShowHelm()); - else - bot_iter->SetShowHelm(helm_state); - - if (ab_type != ActionableBots::ABT_All) { - if (!database.botdb.SaveHelmAppearance(bot_iter->GetBotID(), bot_iter->GetShowHelm())) { - return; - } - - EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct)); - SpawnAppearance_Struct* saptr = (SpawnAppearance_Struct*)outapp->pBuffer; - saptr->spawn_id = bot_iter->GetID(); - saptr->type = AppearanceType::ShowHelm; - saptr->parameter = bot_iter->GetShowHelm(); - - entity_list.QueueClients(bot_iter, outapp); - safe_delete(outapp); - - //helper_bot_appearance_form_update(bot_iter); + return; } - ++bot_count; } - - if (ab_type == ActionableBots::ABT_All) { - if (toggle_helm) { - database.botdb.ToggleAllHelmAppearances(c->CharacterID()); - } - else { - database.botdb.SaveAllHelmAppearances(c->CharacterID(), helm_state); - } - - c->Message(Chat::White, "%s all of your bot show helm flags", toggle_helm ? "Toggled" : (helm_state ? "Set" : "Cleared")); + else if (!arg1.compare("current")) { + ++ab_arg; + current_check = true; } else { - c->Message(Chat::White, "%s %i of your spawned bot show helm flags", toggle_helm ? "Toggled" : (helm_state ? "Set" : "Cleared"), bot_count); + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + + if (!first_found) { + first_found = my_bot; + } + + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I {} show my helm.'", + my_bot->GetCleanName(), + my_bot->GetShowHelm() ? "will" : "will not" + ).c_str() + ); + } + else { + my_bot->SetShowHelm(type_value); + + auto outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct)); + SpawnAppearance_Struct* saptr = (SpawnAppearance_Struct*) outapp->pBuffer; + saptr->spawn_id = my_bot->GetID(); + saptr->type = AppearanceType::ShowHelm; + saptr->parameter = my_bot->GetShowHelm(); + + entity_list.QueueClients(my_bot, outapp, true); + safe_delete(outapp); + + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I {} show my helm.'", + first_found->GetCleanName(), + first_found->GetShowHelm() ? "will now" : "will no longer" + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots {} show their helm.", + success_count, + type_value ? "will now" : "will no longer" + ).c_str() + ); + } } // Notes: @@ -1427,7 +1879,7 @@ void bot_command_update(Client *c, const Seperator *sep) return; } - std::list sbl; + std::vector sbl; MyBots::PopulateSBL_BySpawnedBots(c, sbl); if (sbl.empty()) { c->Message(Chat::White, "You currently have no spawned bots"); @@ -1439,7 +1891,6 @@ void bot_command_update(Client *c, const Seperator *sep) if (!bot_iter || bot_iter->IsEngaged() || bot_iter->GetLevel() == c->GetLevel()) continue; - bot_iter->SetPetChooser(false); bot_iter->CalcBotStats(c->GetBotOption(Client::booStatsUpdate)); bot_iter->SendAppearancePacket(AppearanceType::WhoLevel, bot_iter->GetLevel(), true, true); ++bot_count; diff --git a/zone/bot_commands/bot_settings.cpp b/zone/bot_commands/bot_settings.cpp new file mode 100644 index 000000000..98c4dccd7 --- /dev/null +++ b/zone/bot_commands/bot_settings.cpp @@ -0,0 +1,47 @@ +#include "../bot_command.h" + +void bot_command_bot_settings(Client* c, const Seperator* sep) +{ + std::vector subcommand_list = { + "behindmob", + "blockedbuffs", + "blockedpetbuffs", + "distanceranged", + "copysettings", + "defaultsettings", + "enforcespelllist", + "follow", + "followdistance", + "illusionblock", + "maxmeleerange", + "owneroption", + "petsettype", + "sithppercent", + "sitincombat", + "sitmanapercent", + "spellaggrochecks", + "spellannouncecasts", + "spelldelays", + "spellengagedpriority", + "spellholds", + "spellidlepriority", + "spellmaxhppct", + "spellmaxmanapct", + "spellmaxthresholds", + "spellminhppct", + "spellminmanapct", + "spellminthresholds", + "spellpursuepriority", + "spellresistlimits", + "spelltargetcount", + "spelllist", + "stance", + "togglehelm", + "bottoggleranged" + }; + + if (helper_command_alias_fail(c, "bot_command_bot_settings", sep->arg[0], "botsettings")) + return; + + helper_send_available_subcommands(c, "botsettings", subcommand_list); +} diff --git a/zone/bot_commands/cast.cpp b/zone/bot_commands/cast.cpp new file mode 100644 index 000000000..19368992c --- /dev/null +++ b/zone/bot_commands/cast.cpp @@ -0,0 +1,627 @@ +#include "../bot_command.h" + +void bot_command_cast(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_cast", sep->arg[0], "cast")) { + c->Message(Chat::White, "note: Commands bots to force cast a specific spell type, ignoring all settings (holds, delays, thresholds, etc)."); + + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + BotCommandHelpParams p; + + p.description = { "Commands bots to force cast a specific spell type, ignoring all settings (holds, delays, thresholds, etc)." }; + p.notes = + { + "- This will interrupt any spell currently being cast by bots told to use the command", + "- Bots will still check to see if they have the spell in their spell list, whether the target is immune, spell is allowed and all other sanity checks for spells", + fmt::format( + "- You can use {} aa # to cast any clickable AA or specifically {} harmtouch / {} layonhands" + , sep->arg[0] + , sep->arg[0] + , sep->arg[0] + ) + }; + p.example_format = + { + fmt::format("{} [Type Shortname] [actionable, default: spawned]", sep->arg[0]), + fmt::format("{} [Type ID] [actionable, default: spawned]", sep->arg[0]) + }; + p.examples_one = + { + "To tell everyone to Nuke the target:", + fmt::format( + "{} {}", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::Nuke) + ), + fmt::format( + "{} {}", + sep->arg[0], + BotSpellTypes::Nuke + ) + }; + p.examples_two = + { + "To tell Skbot to Harm Touch the target:", + fmt::format( + "{} aa 6000 byname Skbot", + sep->arg[0] + ), + fmt::format( + "{} harmtouch byname Skbot", + sep->arg[0] + ) + }; + p.examples_three = + { + "To tell all bots to try to cast spell #93 (Burst of Flame)", + fmt::format( + "{} spellid 93", + sep->arg[0] + ) + }; + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->SendSpellTypePrompts(true); + + c->Message( + Chat::Yellow, + fmt::format( + "Use help after any command type for more subtypes to use, for example: {}.", + Saylink::Silent("^cast invisibility help") + ).c_str() + ); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + + return; + } + + std::string arg1 = sep->arg[1]; + std::string arg2 = sep->arg[2]; + + //AA help + if (!arg1.compare("aa") && !arg2.compare("help")) { + c->Message(Chat::Yellow, "Enter the ID of an AA to attempt to cast.", sep->arg[0]); + } + + //Commanded type help prompts + if (!arg2.compare("help")) { + c->Message(Chat::Yellow, "You can also use [single], [group], [ae]. Ex: ^cast movementspeed group.", sep->arg[0]); + } + + if (!arg1.compare("invisibility") && !arg2.compare("help")) { + c->Message( + Chat::Yellow, + fmt::format( + "Available options for {} are: {}, {}, {}, {}.", + sep->arg[0], + Saylink::Silent("^cast invisibility see", "see"), + Saylink::Silent("^cast invisibility invis", "invis"), + Saylink::Silent("^cast invisibility undead", "undead"), + Saylink::Silent("^cast invisibility animals", "animals") + ).c_str() + ); + + return; + } + + if (!arg1.compare("size") && !arg2.compare("help")) { + c->Message( + Chat::Yellow, + fmt::format( + "Available options for {} are: {}, {}.", + sep->arg[0], + Saylink::Silent("^cast size grow", "grow"), + Saylink::Silent("^cast size shrink", "shrink") + ).c_str() + ); + + return; + } + + if (!arg1.compare("movementspeed") && !arg2.compare("help")) { + c->Message( + Chat::Yellow, + fmt::format( + "Available options for {} are: {}, {}.", + sep->arg[0], + Saylink::Silent("^cast movementspeed selo"), "selo" + ).c_str() + ); + + return; + } + + if (!arg2.compare("help")) { + c->Message(Chat::Yellow, "There are no additional options for {}.", sep->arg[0]); + return; + } + + int ab_arg = 2; + uint16 spell_type = UINT16_MAX; + uint16 sub_type = UINT16_MAX; + uint16 sub_target_type = UINT16_MAX; + bool aa_type = false; + int aa_id = 0; + bool by_spell_id = false; + uint16 chosen_spell_id = UINT16_MAX; + + if (!arg1.compare("aa") || !arg1.compare("harmtouch") || !arg1.compare("layonhands")) { + if (!RuleB(Bots, AllowCastAAs)) { + c->Message(Chat::Yellow, "This commanded type is currently disabled."); + return; + } + + if (!arg1.compare("harmtouch")) { + aa_id = zone->GetAlternateAdvancementAbilityByRank(aaHarmTouch)->id; + } + else if (!arg1.compare("layonhands")) { + aa_id = zone->GetAlternateAdvancementAbilityByRank(aaLayonHands)->id; + } + else if (!sep->IsNumber(2) || !zone->GetAlternateAdvancementAbility(Strings::ToInt(arg2))) { + c->Message(Chat::Yellow, "You must enter an AA ID."); + return; + } + else { + ++ab_arg; + aa_id = Strings::ToInt(arg2); + } + + aa_type = true; + } + + if (!arg1.compare("spellid")) { + if (!RuleB(Bots, AllowForcedCastsBySpellID)) { + c->Message(Chat::Yellow, "This commanded type is currently disabled."); + return; + } + + if (sep->IsNumber(2) && IsValidSpell(atoi(sep->arg[2]))) { + ++ab_arg; + chosen_spell_id = atoi(sep->arg[2]); + by_spell_id = true; + } + else { + c->Message(Chat::Yellow, "You must enter a valid spell ID."); + + return; + } + } + + if (!aa_type && !by_spell_id) { + if (sep->IsNumber(1)) { + spell_type = atoi(sep->arg[1]); + + if (!Bot::IsValidBotSpellType(spell_type)) { + c->Message( + Chat::Yellow, + fmt::format( + "You must choose a valid spell type. Use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + else { + if (Bot::GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { + spell_type = Bot::GetSpellTypeIDByShortName(arg1); + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + switch (spell_type) { //Allowed command checks + case BotSpellTypes::Charm: + if (!RuleB(Bots, AllowCommandedCharm)) { + c->Message(Chat::Yellow, "This commanded type is currently disabled."); + return; + } + + break; + case BotSpellTypes::AEMez: + case BotSpellTypes::Mez: + if (!RuleB(Bots, AllowCommandedMez)) { + c->Message(Chat::Yellow, "This commanded type is currently disabled."); + return; + } + + break; + case BotSpellTypes::Resurrect: + if (!RuleB(Bots, AllowCommandedResurrect)) { + c->Message(Chat::Yellow, "This commanded type is currently disabled."); + return; + } + + break; + case BotSpellTypes::AELull: + case BotSpellTypes::Lull: + if (!RuleB(Bots, AllowCommandedLull)) { + c->Message(Chat::Yellow, "This commanded type is currently disabled."); + return; + } + + break; + case BotSpellTypes::SummonCorpse: + if (!RuleB(Bots, AllowCommandedSummonCorpse)) { + c->Message(Chat::Yellow, "This commanded type is currently disabled."); + return; + } + + break; + default: + break; + } + + std::string arg_string = sep->arg[ab_arg]; + + + if (!arg_string.compare("shrink")) { + sub_type = CommandedSubTypes::Shrink; + ++ab_arg; + } + else if (!arg_string.compare("grow")) { + sub_type = CommandedSubTypes::Grow; + ++ab_arg; + } + else if (!arg_string.compare("see")) { + sub_type = CommandedSubTypes::SeeInvis; + ++ab_arg; + } + else if (!arg_string.compare("invis")) { + sub_type = CommandedSubTypes::Invis; + ++ab_arg; + } + else if (!arg_string.compare("undead")) { + sub_type = CommandedSubTypes::InvisUndead; + ++ab_arg; + } + else if (!arg_string.compare("animals")) { + sub_type = CommandedSubTypes::InvisAnimals; + ++ab_arg; + } + else if (!arg_string.compare("selo")) { + sub_type = CommandedSubTypes::Selo; + ++ab_arg; + } + + arg_string = sep->arg[ab_arg]; + + if (!arg_string.compare("single")) { + sub_target_type = CommandedSubTypes::SingleTarget; + ++ab_arg; + } + else if (!arg_string.compare("group")) { + sub_target_type = CommandedSubTypes::GroupTarget; + ++ab_arg; + } + else if (!arg_string.compare("ae")) { + sub_target_type = CommandedSubTypes::AETarget; + ++ab_arg; + } + } + + Mob* tar = c->GetTarget(); + + if (!tar) { + if ((!aa_type && !by_spell_id) && spell_type != BotSpellTypes::Escape && spell_type != BotSpellTypes::Pet) { + c->Message(Chat::Yellow, "You need a target for that."); + return; + } + } + + if (!aa_type && !by_spell_id) { + if (IsPetBotSpellType(spell_type) && !tar->IsPet()) { + c->Message( + Chat::Yellow, + fmt::format( + "[{}] is an invalid target. {} requires a pet to be targeted.", + tar->GetCleanName(), + Bot::GetSpellTypeNameByID(spell_type) + ).c_str() + ); + + return; + } + + switch (spell_type) { //Target Checks + case BotSpellTypes::Resurrect: + if (!tar->IsCorpse() || !tar->CastToCorpse()->IsPlayerCorpse()) { + c->Message( + Chat::Yellow, + fmt::format( + "[{}] is not a player's corpse.", + tar->GetCleanName() + ).c_str() + ); + + return; + } + + break; + case BotSpellTypes::Identify: + case BotSpellTypes::SendHome: + case BotSpellTypes::BindAffinity: + case BotSpellTypes::SummonCorpse: + if (!tar->IsClient() || !c->IsInGroupOrRaid(tar)) { + c->Message( + Chat::Yellow, + fmt::format( + "[{}] is an invalid target. Only players in your group or raid are eligible targets.", + tar->GetCleanName() + ).c_str() + ); + + return; + } + + break; + default: + if ( + (IsBotSpellTypeDetrimental(spell_type) && !c->IsAttackAllowed(tar)) || + ( + spell_type == BotSpellTypes::Charm && + ( + tar->IsClient() || + tar->IsCorpse() || + tar->GetOwner() + ) + ) + ) { + c->Message( + Chat::Yellow, + fmt::format( + "You cannot attack [{}].", + tar->GetCleanName() + ).c_str() + ); + + return; + } + + if (IsBotSpellTypeBeneficial(spell_type)) { + if ( + (tar->IsNPC() && !tar->GetOwner()) || + (tar->GetOwner() && tar->GetOwner()->IsOfClientBot() && !c->IsInGroupOrRaid(tar->GetOwner())) || + (tar->IsOfClientBot() && !c->IsInGroupOrRaid(tar)) + ) { + c->Message( + Chat::Yellow, + fmt::format( + "[{}] is an invalid target. Only players or their pet in your group or raid are eligible targets.", + tar->GetCleanName() + ).c_str() + ); + + return; + } + } + + break; + } + } + + if ( + (spell_type == BotSpellTypes::Cure || spell_type == BotSpellTypes::GroupCures || spell_type == BotSpellTypes::PetCures) && + !c->CastToBot()->GetNeedsCured(tar) + ) { + c->Message( + Chat::Yellow, + fmt::format( + "[{}] doesn't have anything that needs to be cured.", + tar->GetCleanName() + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string actionable_arg = sep->arg[ab_arg]; + + if (actionable_arg.empty()) { + actionable_arg = "spawned"; + } + + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + + if (ActionableBots::PopulateSBL(c, actionable_arg, sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + + BotSpell bot_spell; + bot_spell.SpellId = 0; + bot_spell.SpellIndex = 0; + bot_spell.ManaCost = 0; + bool is_success = false; + uint16 success_count = 0; + Bot* first_found = nullptr; + + for (auto bot_iter : sbl) { + if (!bot_iter->ValidStateCheck(c)) { + continue; + } + + Mob* new_tar = tar; + + if (!aa_type && !by_spell_id) { + if (!BotSpellTypeRequiresTarget(spell_type)) { + new_tar = bot_iter; + } + + if (!new_tar) { + continue; + } + + if ( + IsBotSpellTypeBeneficial(spell_type) && + !RuleB(Bots, CrossRaidBuffingAndHealing) && + !bot_iter->IsInGroupOrRaid(new_tar, true) + ) { + continue; + } + + if (IsBotSpellTypeDetrimental(spell_type) && !bot_iter->IsAttackAllowed(new_tar)) { + bot_iter->RaidGroupSay( + fmt::format( + "I cannot attack [{}].", + new_tar->GetCleanName() + ).c_str() + ); + + continue; + } + } + + if (aa_type) { + if (!bot_iter->GetAA(zone->GetAlternateAdvancementAbility(aa_id)->first_rank_id)) { + continue; + } + + AA::Rank* temp_rank = nullptr; + AA::Rank*& rank = temp_rank; + uint16 spell_id = bot_iter->GetSpellByAA(aa_id, rank); + + if (!IsValidSpell(spell_id)) { + 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; + } + + is_success = true; + ++success_count; + + continue; + } + else if (by_spell_id) { + if (!bot_iter->CanUseBotSpell(chosen_spell_id)) { + continue; + } + + if (!tar || (spells[chosen_spell_id].target_type == ST_Self && tar != bot_iter)) { + tar = bot_iter; + } + + bool los_required = bot_iter != tar && !IsAnyHealSpell(chosen_spell_id) && !IsPBAESpell(chosen_spell_id); + bot_iter->SetHasLoS(los_required ? bot_iter->DoLosChecks(new_tar) : true); + + if (bot_iter->AttemptForcedCastSpell(tar, chosen_spell_id)) { + if (!first_found) { + first_found = bot_iter; + } + + is_success = true; + ++success_count; + } + + continue; + } + 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; + } + + is_success = true; + ++success_count; + } + + bot_iter->SetCommandedSpell(false); + + continue; + } + + continue; + } + + std::string type = ""; + + if (aa_type) { + type = zone->GetAAName(zone->GetAlternateAdvancementAbility(aa_id)->first_rank_id); + } + else if (by_spell_id) { + type = "Forced"; + } + else { + if (sub_type == UINT16_MAX) { + type = Bot::GetSpellTypeNameByID(spell_type); + } + else { + type = Bot::GetSubTypeNameByID(sub_type); + } + } + + if (!is_success) { + c->Message( + Chat::Yellow, + fmt::format( + "No bots are capable of casting [{}] on {}. This could be due to this to any number of things: range, mana, immune, target type, etc.", + (by_spell_id ? spells[chosen_spell_id].name : type), + tar ? tar->GetCleanName() : "your target" + ).c_str() + ); + + if (!aa_type && !by_spell_id) { + helper_send_usage_required_bots(c, spell_type); + } + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "{} {} [{}]{}", + ((success_count == 1 && first_found) ? first_found->GetCleanName() : (fmt::format("{}", success_count).c_str())), + ((success_count == 1 && first_found) ? "casted" : "of your bots casted"), + (by_spell_id ? spells[chosen_spell_id].name : type), + tar ? (fmt::format(" on {}.", tar->GetCleanName()).c_str()) : "." + ).c_str() + ); + } +} diff --git a/zone/bot_commands/caster_range.cpp b/zone/bot_commands/caster_range.cpp deleted file mode 100644 index 2c1faf639..000000000 --- a/zone/bot_commands/caster_range.cpp +++ /dev/null @@ -1,107 +0,0 @@ -#include "../bot_command.h" - -void bot_command_caster_range(Client* c, const Seperator* sep) -{ - if (helper_command_alias_fail(c, "bot_command_caster_range", sep->arg[0], "casterrange")) { - return; - } - - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s [current | value: 0 - 300] ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); - c->Message(Chat::White, "note: Can only be used for Casters or Hybrids."); - c->Message(Chat::White, "note: Use [current] to check the current setting."); - c->Message(Chat::White, "note: Set the value to the minimum distance you want your bot to try to remain from its target."); - c->Message(Chat::White, "note: If they are too far for a spell, it will be skipped."); - c->Message(Chat::White, "note: This is set to (90) units by default."); - return; - } - const int ab_mask = ActionableBots::ABM_Type1; - - std::string arg1 = sep->arg[1]; - int ab_arg = 1; - bool current_check = false; - uint32 crange = 0; - - if (sep->IsNumber(1)) { - ab_arg = 2; - crange = atoi(sep->arg[1]); - if (crange < 0 || crange > 300) { - c->Message(Chat::White, "You must enter a value within the range of 0 - 300."); - return; - } - } - else if (!arg1.compare("current")) { - ab_arg = 2; - current_check = true; - } - else { - c->Message(Chat::White, "Incorrect argument, use %s help for a list of options.", sep->arg[0]); - return; - } - - std::string class_race_arg = sep->arg[ab_arg]; - bool class_race_check = false; - - if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { - class_race_check = true; - } - - std::list sbl; - if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { - return; - } - - sbl.remove(nullptr); - - Bot* first_found = nullptr; - int success_count = 0; - - for (auto my_bot : sbl) { - if (!IsCasterClass(my_bot->GetClass()) && !IsHybridClass(my_bot->GetClass())) { - continue; - } - - if (!first_found) { - first_found = my_bot; - } - - if (current_check) { - c->Message( - Chat::White, - fmt::format( - "{} says, 'My current Caster Range is {}.'", - my_bot->GetCleanName(), - my_bot->GetBotCasterRange() - ).c_str() - ); - } - else { - my_bot->SetBotCasterRange(crange); - ++success_count; - - database.botdb.SaveBotCasterRange(my_bot->GetBotID(), crange); - } - } - if (!current_check) { - if (success_count == 1 && first_found) { - c->Message( - Chat::White, - fmt::format( - "{} says, 'My Caster Range was set to {}.'", - first_found->GetCleanName(), - crange - ).c_str() - ); - } - else { - c->Message( - Chat::White, - fmt::format( - "{} of your bots set their Caster Range to {}.", - success_count, - crange - ).c_str() - ); - } - } -} diff --git a/zone/bot_commands/charm.cpp b/zone/bot_commands/charm.cpp deleted file mode 100644 index 2df4694b6..000000000 --- a/zone/bot_commands/charm.cpp +++ /dev/null @@ -1,54 +0,0 @@ -#include "../bot_command.h" - -void bot_command_charm(Client *c, const Seperator *sep) -{ - auto local_list = &bot_command_spells[BCEnum::SpT_Charm]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Charm) || helper_command_alias_fail(c, "bot_command_charm", sep->arg[0], "charm")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s ([option: dire])", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Charm); - return; - } - - bool dire = false; - std::string dire_arg = sep->arg[1]; - if (!dire_arg.compare("dire")) - dire = true; - - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - for (auto list_iter : *local_list) { - auto local_entry = list_iter->SafeCastToCharm(); - if (helper_spell_check_fail(local_entry)) - continue; - if (local_entry->dire != dire) - continue; - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, ENEMY); - if (!target_mob) - continue; - if (target_mob->IsCharmed()) { - c->Message(Chat::White, "Your is already charmed"); - return; - } - - if (spells[local_entry->spell_id].max_value[EFFECTIDTOINDEX(1)] < target_mob->GetLevel()) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob, true); - if (!my_bot) - continue; - - uint32 dont_root_before = 0; - if (helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id, true, &dont_root_before)) - target_mob->SetDontRootMeBefore(dont_root_before); - - break; - } - - helper_no_available_bots(c, my_bot); -} diff --git a/zone/bot_commands/class_race_list.cpp b/zone/bot_commands/class_race_list.cpp new file mode 100644 index 000000000..84c611ab3 --- /dev/null +++ b/zone/bot_commands/class_race_list.cpp @@ -0,0 +1,113 @@ +#include "../bot_command.h" + +void bot_command_class_race_list(Client* c, const Seperator* sep) +{ + const std::string class_substrs[17] = { + "", + "WAR", "CLR", "PAL", "RNG", + "SHD", "DRU", "MNK", "BRD", + "ROG", "SHM", "NEC", "WIZ", + "MAG", "ENC", "BST", "BER" + }; + + const std::string race_substrs[17] = { + "", + "HUM", "BAR", "ERU", "ELF", + "HIE", "DEF", "HEF", "DWF", + "TRL", "OGR", "HFL", "GNM", + "IKS", "VAH", "FRG", "DRK" + }; + + const uint16 race_values[17] = { + Race::Doug, + Race::Human, Race::Barbarian, Race::Erudite, Race::WoodElf, + Race::HighElf, Race::DarkElf, Race::HalfElf, Race::Dwarf, + Race::Troll, Race::Ogre, Race::Halfling, Race::Gnome, + Race::Iksar, Race::VahShir, Race::Froglok2, Race::Drakkin + }; + + std::string window_text; + std::string message_separator; + int object_count = 0; + const int object_max = 4; + + window_text.append( + fmt::format( + "Classes{}", + DialogueWindow::Break() + ) + ); + + window_text.append( + fmt::format( + "--------------------------------------------------------------------", + DialogueWindow::Break() + ) + ); + + window_text.append(DialogueWindow::Break()); + + message_separator = " "; + object_count = 0; + for (int i = 0; i <= 15; ++i) { + window_text.append(message_separator); + + if (object_count >= object_max) { + window_text.append(DialogueWindow::Break()); + object_count = 0; + } + + window_text.append( + fmt::format("{} ({})", + class_substrs[i + 1], + (i + 1) + ) + ); + + ++object_count; + message_separator = ", "; + } + + window_text.append(DialogueWindow::Break(2)); + + window_text.append( + fmt::format( + "Races{}", + DialogueWindow::Break() + ) + ); + + window_text.append( + fmt::format( + "--------------------------------------------------------------------", + DialogueWindow::Break() + ) + ); + + window_text.append(DialogueWindow::Break()); + + message_separator = " "; + object_count = 0; + for (int i = 0; i <= 15; ++i) { + window_text.append(message_separator); + + if (object_count >= object_max) { + window_text.append(DialogueWindow::Break()); + object_count = 0; + } + + window_text.append( + fmt::format("{} ({})", + race_substrs[i + 1], + race_values[i + 1] + ) + ); + + ++object_count; + message_separator = ", "; + } + + c->SendPopupToClient("Bot Creation Options", window_text.c_str()); + + return; +} diff --git a/zone/bot_commands/click_item.cpp b/zone/bot_commands/click_item.cpp index b236815d8..492741821 100644 --- a/zone/bot_commands/click_item.cpp +++ b/zone/bot_commands/click_item.cpp @@ -8,7 +8,7 @@ void bot_command_click_item(Client* c, const Seperator* sep) } if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); + c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); c->Message(Chat::White, "This will cause the selected bots to click the item in the given slot ID."); c->Message(Chat::White, "Use ^invlist to see their items along with slot IDs."); return; @@ -25,7 +25,7 @@ void bot_command_click_item(Client* c, const Seperator* sep) uint32 slot_id = 0; if (sep->IsNumber(1)) { - ab_arg = 2; + ++ab_arg; slot_id = atoi(sep->arg[1]); if (slot_id < EQ::invslot::EQUIPMENT_BEGIN || slot_id > EQ::invslot::EQUIPMENT_END) { c->Message(Chat::Yellow, "You must specify a valid inventory slot from 0 to 22. Use %s help for more information", sep->arg[0]); @@ -39,18 +39,41 @@ void bot_command_click_item(Client* c, const Seperator* sep) class_race_check = true; } - std::list sbl; + std::vector sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { return; } - sbl.remove(nullptr); + + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + + Mob* tar = c->GetTarget(); + bool is_success = false; for (auto my_bot : sbl) { + if (!my_bot->ValidStateCheck(c)) { + continue; + } + + if ( + tar && + tar != c && + tar->GetOwner() != c && + !c->DoLosChecks(tar) + ) { + continue; + } + if (RuleI(Bots, BotsClickItemsMinLvl) > my_bot->GetLevel()) { c->Message(Chat::White, "%s must be level %i to use clickable items.", my_bot->GetCleanName(), RuleI(Bots, BotsClickItemsMinLvl)); continue; } + is_success = true; my_bot->TryItemClick(slot_id); } + + if (!is_success) { + c->Message(Chat::Yellow, "None of your bots are capable of doing that currently."); + } } diff --git a/zone/bot_commands/copy_settings.cpp b/zone/bot_commands/copy_settings.cpp new file mode 100644 index 000000000..ef9d3a8fd --- /dev/null +++ b/zone/bot_commands/copy_settings.cpp @@ -0,0 +1,367 @@ +#include "../bot_command.h" + +void bot_command_copy_settings(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_copy_settings", sep->arg[0], "copysettings")) { + c->Message(Chat::White, "note: Copies settings from one bot to another."); + + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + BotCommandHelpParams p; + + p.description = + { + "Copies settings from one bot to another." + }; + p.notes = + { + "- You can put a spell type ID or shortname after any option except [all], [misc] and [spellsettings] to restore that specifc spell type only" + }; + p.example_format = { fmt::format("{} [from] [to] [option] [optional: spelltype id/short name]", sep->arg[0]) }; + p.examples_one = + { + "To copy all settings from BotA to BotB:", + fmt::format("{} BotA BotB all", sep->arg[0]) + }; + p.examples_two = + { + "To copy only Nuke spelltypesettings from BotA to BotB:", + fmt::format( + "{} BotA BotB spelltypesettings {}", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::Nuke) + ), + fmt::format( + "{} BotA BotB spelltypesettings {}", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::Nuke) + ), + }; + p.examples_three = + { + "To copy only spellsettings from BotA to BotB:", + fmt::format( + "{} BotA BotB spellsettings", + sep->arg[0] + ), + fmt::format( + "{} BotA BotB spellsettings", + sep->arg[0] + ), + }; + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + p.options = { "all, misc, spellsettings, spelltypesettings, spellholds, spelldelays, spellminthresholds, spellmaxthresholds, spellresistlimits, spellaggrochecks, spellminmanapct, spellmaxmanapct, spellminhppct, spellmaxhppct, spellidlepriority, spellengagedpriority, spellpursuepriority, spelltargetcounts, spellannouncecasts, blockedbuffs, blockedpetbuffs" }; + p.options_one = + { + "[spellsettings] will copy ^spellsettings options", + "[spelltypesettings] copies all spell type settings", + "[all] copies all settings" + }; + p.options_two = + { + "[misc] copies all miscellaneous options such as:", + "- ^showhelm, ^followd, ^stopmeleelevel, ^enforcespellsettings, ^bottoggleranged, ^petsettype, ^behindmob, ^distanceranged, ^illusionblock, ^sitincombat, ^sithppercent, ^sitmanapercent, ^blockedbuffs, ^blockedpetbuffs" + }; + p.options_three = { "The remaining options copy that specific type" }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->SendSpellTypePrompts(); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + + return; + } + + std::vector options = + { + "all", + "misc", + "spellsettings", + "spelltypesettings", + "spellholds", + "spelldelays", + "spellminthresholds", + "spellmaxthresholds", + "spellresistlimits", + "spellaggrochecks", + "spellminmanapct", + "spellmaxmanapct", + "spellminhppct", + "spellmaxhppct", + "spellidlepriority", + "spellengagedpriority", + "spellpursuepriority", + "spelltargetcounts", + "spellannouncecasts", + "blockedbuffs", + "blockedpetbuffs" + }; + + std::string option_arg = Strings::ToLower(sep->arg[3]); + bool valid_option = false; + bool copy_all = false; + bool copy_misc = false; + bool copy_spell_settings = false; + bool copy_spell_type_settings = false; + bool copy_blocked_buffs = false; + bool copy_blocked_pet_buffs = false; + uint16 setting_type = UINT16_MAX; + + for (int i = 0; i < options.size(); i++) { + if (option_arg == options[i]) { + if (option_arg == "all") { + copy_all = true; + valid_option = true; + + break; + } + else if (option_arg == "misc") { + copy_misc = true; + valid_option = true; + + break; + } + else if (option_arg == "spellsettings") { + copy_spell_settings = true; + valid_option = true; + + break; + } + else if (option_arg == "spelltypesettings") { + copy_spell_type_settings = true; + valid_option = true; + + break; + } + else if (option_arg == "blockedbuffs") { + copy_blocked_buffs = true; + valid_option = true; + + break; + } + else if (option_arg == "blockedpetbuffs") { + copy_blocked_pet_buffs = true; + valid_option = true; + + break; + } + else { + setting_type = Bot::GetBotSpellCategoryIDByShortName(option_arg); + + if (setting_type != UINT16_MAX) { + valid_option = true; + + break; + } + } + } + } + + if (!valid_option) { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + auto from = entity_list.GetBotByBotName(sep->arg[1]); + + if (!from) { + c->Message(Chat::Yellow, "Could not find %s.", sep->arg[1]); + return; + } + + if (!from->IsBot()) { + c->Message(Chat::Yellow, "%s is not a bot.", from->GetCleanName()); + return; + } + + if (!from->GetOwner()) { + c->Message(Chat::Yellow, "Could not find %s's owner.", from->GetCleanName()); + } + + if (RuleB(Bots, CopySettingsOwnBotsOnly) && from->GetOwner() != c) { + c->Message(Chat::Yellow, "You name a bot you own to use this command."); + return; + } + + if (!RuleB(Bots, AllowCopySettingsAnon) && from->GetOwner() != c && from->GetOwner()->CastToClient()->GetAnon()) { + c->Message(Chat::Yellow, "You name a bot you own to use this command."); + return; + } + + auto to = ActionableBots::AsNamed_ByBot(c, sep->arg[2]); + + if (!to) { + c->Message(Chat::Yellow, "Could not find %s.", sep->arg[1]); + return; + } + + if (!to->IsBot()) { + c->Message(Chat::Yellow, "%s is not a bot.", to->GetCleanName()); + return; + } + + if (!to->GetOwner()) { + c->Message(Chat::Yellow, "Could not find %s's owner.", to->GetCleanName()); + } + + if (to->GetOwner() != c) { + c->Message(Chat::Yellow, "You must name a spawned bot that you own to use this command."); + return; + } + + if (to == from) { + c->Message(Chat::Yellow, "You cannot copy to the same bot that you're copying from."); + return; + } + + int ab_arg = 4; + std::string spell_type_arg = sep->arg[ab_arg]; + uint16 spell_type = UINT16_MAX; + + if (sep->IsNumber(ab_arg)) { + spell_type = atoi(sep->arg[ab_arg]); + + if (!EQ::ValueWithin(spell_type, BotSpellTypes::START, BotSpellTypes::END)) { + c->Message( + Chat::Yellow, + fmt::format( + "You must choose a valid spell type. Use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + ++ab_arg; + } + else if (!spell_type_arg.empty()) { + if (Bot::GetSpellTypeIDByShortName(spell_type_arg) != UINT16_MAX) { + spell_type = Bot::GetSpellTypeIDByShortName(spell_type_arg); + ++ab_arg; + } + else { + if ( + !copy_all && + !copy_misc && + !copy_spell_settings && + !copy_blocked_buffs && + !copy_blocked_pet_buffs + ) { + c->Message(Chat::Yellow, "If you are trying to specify a spell type, you must enter a valid spell type. Otherwise you can ignore this message."); + } + } + } + + std::string output = ""; + + if (copy_all) { + for (uint16 i = BotBaseSettings::START; i <= BotBaseSettings::END; ++i) { + to->SetBotBaseSetting(i, from->GetBotBaseSetting(i)); + } + + for (uint16 i = BotSettingCategories::START_NO_BASE; i <= BotSettingCategories::END; ++i) { + for (uint16 x = BotSpellTypes::START; x <= BotSpellTypes::END; ++x) { + to->SetBotSetting(i, x, from->GetSetting(i, x)); + } + } + + from->CopyBotSpellSettings(to); + from->CopyBotBlockedBuffs(to); + from->CopyBotBlockedPetBuffs(to); + output = "settings were"; + } + else if (copy_misc) { + for (uint16 i = BotBaseSettings::START; i <= BotBaseSettings::END; ++i) { + to->SetBotBaseSetting(i, from->GetBotBaseSetting(i)); + output = "miscellaneous settings were"; + } + } + else if (copy_spell_settings) { + from->ResetBotSpellSettings(); + output = "^spellsettings were"; + } + else if (copy_spell_type_settings) { + if (spell_type != UINT16_MAX) { + for (uint16 i = BotSettingCategories::START_NO_BASE; i <= BotSettingCategories::END; ++i) { + to->SetBotSetting(i, spell_type, from->GetSetting(i, spell_type)); + } + + output = fmt::format( + "[{}] settings were", + Bot::GetSpellTypeNameByID(spell_type) + ); + } + else { + for (uint16 i = BotSettingCategories::START_NO_BASE; i <= BotSettingCategories::END; ++i) { + for (uint16 x = BotSpellTypes::START; x <= BotSpellTypes::END; ++x) { + to->SetBotSetting(i, x, from->GetSetting(i, x)); + } + } + + output = "spell type settings were"; + } + } + else if (copy_blocked_buffs) { + from->CopyBotBlockedBuffs(to); + } + else if (copy_blocked_pet_buffs) { + from->CopyBotBlockedPetBuffs(to); + } + else if (setting_type != UINT16_MAX) { + if (spell_type != UINT16_MAX) { + to->SetBotSetting(setting_type, spell_type, from->GetSetting(setting_type, spell_type)); + output = fmt::format( + "[{}] {} were", + Bot::GetSpellTypeNameByID(spell_type), + Bot::GetBotSpellCategoryName(Bot::GetBotSpellCategoryIDByShortName(option_arg)) + ); + } + else { + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + to->SetBotSetting(setting_type, i, from->GetSetting(setting_type, i)); + } + + output = fmt::format( + "{} settings for all spell types", + Bot::GetBotSpellCategoryName(Bot::GetBotSpellCategoryIDByShortName(option_arg)) + ); + } + } + + to->Save(); + + c->Message( + Chat::Green, + fmt::format( + "{}'s {} copied to {}.", + from->GetCleanName(), + output, + to->GetCleanName() + ).c_str() + ); +} diff --git a/zone/bot_commands/cure.cpp b/zone/bot_commands/cure.cpp deleted file mode 100644 index 4452f8088..000000000 --- a/zone/bot_commands/cure.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include "../bot_command.h" - -void bot_command_cure(Client *c, const Seperator *sep) -{ - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Cure]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Cure) || helper_command_alias_fail(c, "bot_command_cure", sep->arg[0], "cure")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: () %s [ailment: blindness | disease | poison | curse | corruption]", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Cure); - return; - } - - std::string ailment_arg = sep->arg[1]; - - auto ailment_type = BCEnum::AT_None; - if (!ailment_arg.compare("blindness")) - ailment_type = BCEnum::AT_Blindness; - else if (!ailment_arg.compare("disease")) - ailment_type = BCEnum::AT_Disease; - else if (!ailment_arg.compare("poison")) - ailment_type = BCEnum::AT_Poison; - else if (!ailment_arg.compare("curse")) - ailment_type = BCEnum::AT_Curse; - else if (!ailment_arg.compare("corruption")) - ailment_type = BCEnum::AT_Corruption; - - if (ailment_type == BCEnum::AT_None) { - c->Message(Chat::White, "You must specify a cure [ailment] to use this command"); - return; - } - - local_list->sort([ailment_type](STBaseEntry* l, STBaseEntry* r) { - auto _l = l->SafeCastToCure(), _r = r->SafeCastToCure(); - if (_l->cure_value[AILMENTIDTOINDEX(ailment_type)] < _r->cure_value[AILMENTIDTOINDEX(ailment_type)]) - return true; - if (_l->cure_value[AILMENTIDTOINDEX(ailment_type)] == _r->cure_value[AILMENTIDTOINDEX(ailment_type)] && spells[_l->spell_id].mana < spells[_r->spell_id].mana) - return true; - if (_l->cure_value[AILMENTIDTOINDEX(ailment_type)] == _r->cure_value[AILMENTIDTOINDEX(ailment_type)] && spells[_l->spell_id].mana == spells[_r->spell_id].mana && _l->cure_total < _r->cure_total) - return true; - - return false; - }); - - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - bool cast_success = false; - for (auto list_iter : *local_list) { - auto local_entry = list_iter->SafeCastToCure(); - if (helper_spell_check_fail(local_entry)) - continue; - if (!local_entry->cure_value[AILMENTIDTOINDEX(ailment_type)]) - continue; - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); - if (!target_mob) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; - - cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); - break; - } - - helper_no_available_bots(c, my_bot); -} diff --git a/zone/bot_commands/default_settings.cpp b/zone/bot_commands/default_settings.cpp new file mode 100644 index 000000000..030eb028e --- /dev/null +++ b/zone/bot_commands/default_settings.cpp @@ -0,0 +1,315 @@ +#include "../bot_command.h" + +void bot_command_default_settings(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_default_settings", sep->arg[0], "defaultsettings")) { + c->Message(Chat::White, "note: Restores a bot's setting(s) to defaults."); + + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + BotCommandHelpParams p; + + p.description = { "Restores a bot's setting(s) to defaults." }; + p.notes = { "- You can put a spell type ID or shortname after any option except [all], [misc] and [spellsettings] to restore that specifc spell type only"}; + p.example_format = { fmt::format("{} [option] [optional: spelltype id/short name] [actionable]", sep->arg[0]) }; + p.examples_one = + { + "To restore delays for Clerics:", + fmt::format( + "{} delays byclass {}", + sep->arg[0], + Class::Cleric + ) + }; + p.examples_two = + { + "To restore only Snare delays for BotA:", + fmt::format( + "{} delays {} byname BotA", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::Snare) + ), + fmt::format( + "{} delays {} byname BotA", + sep->arg[0], + BotSpellTypes::Snare + ) + }; + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + p.options = { "all, misc, spellsettings, spelltypesettings, spellholds, spelldelays, spellminthresholds, spellmaxthresholds, spellresistlimits, spellaggrocheck, spellminmanapct, spellmaxmanapct, spellminhppct, spellmaxhppct, spellidlepriority, spellengagedpriority, spellpursuepriority, spelltargetcounts, spellannouncecasts" }; + p.options_one = + { + "[spellsettings] will restore ^spellsettings options", + "[spelltypesettings] restores all spell type settings", + "[all] restores all settings" + }; + p.options_two = + { + "[misc] restores all miscellaneous options such as:", + "- ^showhelm, ^followd, ^stopmeleelevel, ^enforcespellsettings, ^bottoggleranged, ^petsettype, ^behindmob, ^distanceranged, ^illusionblock, ^sitincombat, ^sithppercent and ^sitmanapercent", + }; + p.options_three = + { + "
", + "**The remaining options restore that specific type**" + }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->SendSpellTypePrompts(); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + + return; + } + + std::vector options = + { + "all", + "misc", + "spellsettings", + "spelltypesettings", + "spellholds", + "spelldelays", + "spellminthresholds", + "spellmaxthresholds", + "spellresistlimits", + "spellaggrochecks", + "spellminmanapct", + "spellmaxmanapct", + "spellminhppct", + "spellmaxhppct", + "spellidlepriority", + "spellengagedpriority", + "spellpursuepriority", + "spelltargetcounts", + "spellannouncecasts" + }; + + std::string option_arg = Strings::ToLower(sep->arg[1]); + bool valid_option = false; + bool default_all = false; + bool default_misc = false; + bool default_spell_settings = false; + bool default_spell_type_settings = false; + uint16 setting_type = UINT16_MAX; + + for (int i = 0; i < options.size(); i++) { + if (option_arg == options[i]) { + if (option_arg == "all") { + default_all = true; + valid_option = true; + + break; + } + else if (option_arg == "misc") { + default_misc = true; + valid_option = true; + + break; + } + else if (option_arg == "spellsettings") { + default_spell_settings = true; + valid_option = true; + + break; + } + else if (option_arg == "spelltypesettings") { + default_spell_type_settings = true; + valid_option = true; + + break; + } + else { + setting_type = Bot::GetBotSpellCategoryIDByShortName(option_arg); + + if (setting_type != UINT16_MAX) { + valid_option = true; + + break; + } + } + } + } + + if (!valid_option) { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + int ab_arg = 2; + std::string spell_type_arg = sep->arg[ab_arg]; + uint16 spell_type = UINT16_MAX; + + if (sep->IsNumber(ab_arg)) { + spell_type = atoi(sep->arg[ab_arg]); + + if (!EQ::ValueWithin(spell_type, BotSpellTypes::START, BotSpellTypes::END)) { + c->Message( + Chat::Yellow, + fmt::format( + "You must choose a valid spell type. Use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + ++ab_arg; + } + else if (!spell_type_arg.empty()) { + if (Bot::GetSpellTypeIDByShortName(spell_type_arg) != UINT16_MAX) { + spell_type = Bot::GetSpellTypeIDByShortName(spell_type_arg); + ++ab_arg; + } + else { + if (!default_all && !default_misc && !default_spell_settings) { + c->Message(Chat::Yellow, "If you are trying to specify a spell type, you must enter a valid spell type. Otherwise you can ignore this message."); + } + } + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + + Bot* first_found = nullptr; + int success_count = 0; + std::string output = ""; + uint8 bot_stance = 2; + + for (auto my_bot : sbl) { + if (!first_found) { + first_found = my_bot; + } + + bot_stance = my_bot->GetBotStance(); + + if (default_all) { + for (uint16 i = BotBaseSettings::START; i <= BotBaseSettings::END; ++i) { + my_bot->SetBotBaseSetting(i, my_bot->GetDefaultBotBaseSetting(i, bot_stance)); + } + + for (uint16 i = BotSettingCategories::START_NO_BASE; i <= BotSettingCategories::END; ++i) { + for (uint16 x = BotSpellTypes::START; x <= BotSpellTypes::END; ++x) { + my_bot->SetBotSetting(i, x, my_bot->GetDefaultSetting(i, x, bot_stance)); + } + } + + my_bot->ResetBotSpellSettings(); + my_bot->ClearBotBlockedBuffs(); + output = "settings were reset"; + } + else if (default_misc) { + for (uint16 i = BotBaseSettings::START; i <= BotBaseSettings::END; ++i) { + my_bot->SetBotBaseSetting(i, my_bot->GetDefaultBotBaseSetting(i, bot_stance)); + output = "miscellaneous settings were reset"; + } + } + else if (default_spell_settings) { + my_bot->ResetBotSpellSettings(); + output = "^spellsettings were reset"; + } + else if (default_spell_type_settings) { + if (spell_type != UINT16_MAX) { + for (uint16 i = BotSettingCategories::START_NO_BASE; i <= BotSettingCategories::END; ++i) { + my_bot->SetBotSetting(i, spell_type, my_bot->GetDefaultSetting(i, spell_type, bot_stance)); + } + + output = fmt::format( + "[{}] settings were reset", + Bot::GetSpellTypeNameByID(spell_type) + ); + } + else { + for (uint16 i = BotSettingCategories::START_NO_BASE; i <= BotSettingCategories::END; ++i) { + for (uint16 x = BotSpellTypes::START; x <= BotSpellTypes::END; ++x) { + my_bot->SetBotSetting(i, x, my_bot->GetDefaultSetting(i, x, bot_stance)); + } + } + + output = "spell type settings were reset"; + } + } + else if (setting_type != UINT16_MAX) { + if (spell_type != UINT16_MAX) { + my_bot->SetBotSetting(setting_type, spell_type, my_bot->GetDefaultSetting(setting_type, spell_type, bot_stance)); + output = fmt::format( + "[{}] {} were reset", + Bot::GetSpellTypeNameByID(spell_type), + Bot::GetBotSpellCategoryName(Bot::GetBotSpellCategoryIDByShortName(option_arg)) + ); + } + else { + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + my_bot->SetBotSetting(setting_type, i, my_bot->GetDefaultSetting(setting_type, i, bot_stance)); + } + + output = fmt::format( + "{} settings were reset for all spell types", + Bot::GetBotSpellCategoryName(Bot::GetBotSpellCategoryIDByShortName(option_arg)) + ); + } + } + + my_bot->Save(); + ++success_count; + } + + if (success_count == 1) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My {}.'", + first_found->GetCleanName(), + output + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots' {}.", + success_count, + output + ).c_str() + ); + } +} diff --git a/zone/bot_commands/defensive.cpp b/zone/bot_commands/defensive.cpp deleted file mode 100644 index a2edc3e6e..000000000 --- a/zone/bot_commands/defensive.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include "../bot_command.h" - -void bot_command_defensive(Client *c, const Seperator *sep) -{ - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Stance]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Stance) || helper_command_alias_fail(c, "bot_command_defensive", sep->arg[0], "defensive")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Stance); - return; - } - const int ab_mask = ActionableBots::ABM_Type1; - - std::string class_race_arg = sep->arg[1]; - bool class_race_check = false; - if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { - class_race_check = true; - } - - std::list sbl; - if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, !class_race_check ? sep->arg[2] : nullptr, class_race_check ? atoi(sep->arg[2]) : 0) == ActionableBots::ABT_None) { - return; - } - sbl.remove(nullptr); - - int success_count = 0; - int candidate_count = sbl.size(); - for (auto list_iter : *local_list) { - if (sbl.empty()) - break; - - auto local_entry = list_iter->SafeCastToStance(); - if (helper_spell_check_fail(local_entry)) - continue; - if (local_entry->stance_type != BCEnum::StT_Defensive) - continue; - - for (auto bot_iter = sbl.begin(); bot_iter != sbl.end(); ) { - Bot* my_bot = *bot_iter; - if (local_entry->caster_class != my_bot->GetClass()) { - ++bot_iter; - continue; - } - if (local_entry->spell_level > my_bot->GetLevel()) { - ++bot_iter; - continue; - } - - my_bot->InterruptSpell(); - if (candidate_count == 1) { - Bot::BotGroupSay( - my_bot, - fmt::format( - "Using {}.", - spells[local_entry->spell_id].name - ).c_str() - ); - } - - my_bot->UseDiscipline(local_entry->spell_id, my_bot->GetID()); - ++success_count; - - bot_iter = sbl.erase(bot_iter); - } - } - - c->Message(Chat::White, "%i of %i bots have attempted to use defensive disciplines", success_count, candidate_count); -} diff --git a/zone/bot_commands/depart.cpp b/zone/bot_commands/depart.cpp index b337ef404..0a560e59c 100644 --- a/zone/bot_commands/depart.cpp +++ b/zone/bot_commands/depart.cpp @@ -1,59 +1,269 @@ #include "../bot_command.h" -void bot_command_depart(Client *c, const Seperator *sep) +void bot_command_depart(Client* c, const Seperator* sep) { - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Depart]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Depart) || helper_command_alias_fail(c, "bot_command_depart", sep->arg[0], "depart")) + if (helper_command_alias_fail(c, "bot_command_depart", sep->arg[0], "depart")) { + c->Message(Chat::White, "note: Tells bots to list their port locations or port to a specific location."); + return; + } + if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s [list | destination] ([option: single])", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Depart); + BotCommandHelpParams p; + + p.description = { "Tells bots to list their port locations or port to a specific location." }; + p.notes = + { + "- This will interrupt any spell currently being cast by bots told to use the command.", + "- Bots will still check to see if they have the spell in their spell list, whether the target is immune, spell is allowed and all other sanity checks for spells" + }; + p.example_format = { fmt::format("{} [list | zone shortname] [optional: single | group | ae] [actionable, default: spawned]", sep->arg[0]) }; + p.examples_one = + { + "To tell everyone to list their portable locations:", + fmt::format("{} list spawned", + sep->arg[0] + ) + }; + p.examples_two = + { + "To tell all bots to port to Nexus:", + fmt::format( + "{} nexus spawned", + sep->arg[0] + ) + }; + p.examples_three = + { + "To tell Druidbot to single target port to Butcher:", + fmt::format( + "{} butcher single byname Druidbot", + sep->arg[0] + ) + }; + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + return; } bool single = false; + bool group = true; + bool ae = false; std::string single_arg = sep->arg[2]; - if (!single_arg.compare("single")) - single = true; - + bool list = false; std::string destination = sep->arg[1]; - if (!destination.compare("list")) { - Bot* my_druid_bot = ActionableBots::AsGroupMember_ByClass(c, c, Class::Druid); - Bot* my_wizard_bot = ActionableBots::AsGroupMember_ByClass(c, c, Class::Wizard); - helper_command_depart_list(c, my_druid_bot, my_wizard_bot, local_list, single); - return; + int ab_arg = 2; + + if (!single_arg.compare("single")) { + ++ab_arg; + single = true; + group = false; } - else if (destination.empty()) { - c->Message(Chat::White, "A [destination] or [list] argument is required to use this command"); + else if (!single_arg.compare("group")) { + ++ab_arg; + single = false; + group = true; + } + else if (!single_arg.compare("ae")) { + ++ab_arg; + single = false; + group = false; + ae = true; + } + + if (!destination.compare("list") || destination.empty()) { + list = true; + + if (destination.empty()) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + } + } + + Mob* tar = c->GetTarget(); + + if (!tar) { + tar = c; + } + + std::string arg_string = sep->arg[ab_arg]; + + const int ab_mask = ActionableBots::ABM_Type1; + std::string actionable_arg = sep->arg[ab_arg]; + + if (actionable_arg.empty()) { + actionable_arg = "spawned"; + } + + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + + if (ActionableBots::PopulateSBL(c, actionable_arg, sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { return; } - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); - bool cast_success = false; - for (auto list_iter : *local_list) { - auto local_entry = list_iter->SafeCastToDepart(); - if (helper_spell_check_fail(local_entry)) - continue; - if (local_entry->single != single) - continue; - if (destination.compare(spells[local_entry->spell_id].teleport_zone)) - continue; + BotSpell bot_spell; + bot_spell.SpellId = 0; + bot_spell.SpellIndex = 0; + bot_spell.ManaCost = 0; - auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); - if (!target_mob) - continue; + bool is_success = false; + std::map> list_zones; - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) + for (auto bot_iter : sbl) { + if (!bot_iter->ValidStateCheck(c)) { continue; + } - cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); - break; + std::vector bot_spell_list_itr = bot_iter->GetPrioritizedBotSpellsBySpellType(bot_iter, BotSpellTypes::Teleport, tar); + + for (std::vector::iterator itr = bot_spell_list_itr.begin(); itr != bot_spell_list_itr.end(); ++itr) { + if (!IsValidSpell(itr->SpellId)) { + continue; + } + + if ( + (single && spells[itr->SpellId].target_type != ST_Target) || + (group && !IsGroupSpell(itr->SpellId)) || + (ae && !IsAnyAESpell(itr->SpellId)) + ) { + continue; + } + + if (list) { + auto it = list_zones.find(spells[itr->SpellId].teleport_zone); + + if (it != list_zones.end()) { + const auto& [val1, val2] = it->second; + + if (val1 == spells[itr->SpellId].target_type && val2 == bot_iter->GetClass()) { + continue; + } + } + + bot_iter->RaidGroupSay( + fmt::format( + "I can port you to {}.", + Saylink::Silent( + fmt::format( + "{} {} {} byname {}", + sep->arg[0], + spells[itr->SpellId].teleport_zone, + (spells[itr->SpellId].target_type == ST_Target ? "single" : (IsGroupSpell(itr->SpellId) ? "group" : "ae")), + bot_iter->GetCleanName() + ).c_str() + , ZoneLongName(ZoneID(spells[itr->SpellId].teleport_zone))) + ).c_str() + ); + + list_zones.insert({ spells[itr->SpellId].teleport_zone, {spells[itr->SpellId].target_type, bot_iter->GetClass()} }); + + continue; + } + + if (destination.compare(spells[itr->SpellId].teleport_zone)) { + continue; + } + + bot_iter->SetCommandedSpell(true); + + if (!IsValidSpell(itr->SpellId)) { + continue; + } + + if (BotRequiresLoSToCast(BotSpellTypes::Teleport, itr->SpellId) && !bot_iter->HasLoS()) { + continue; + } + + if (IsInvulnerabilitySpell(itr->SpellId)) { + tar = bot_iter; //target self for invul type spells + } + + if (bot_iter->IsCommandedSpell() && bot_iter->IsCasting()) { + bot_iter->RaidGroupSay( + fmt::format( + "Interrupting {}. I have been commanded to try to cast a [{}] spell, {} on {}.", + bot_iter->CastingSpellID() ? spells[bot_iter->CastingSpellID()].name : "my spell", + bot_iter->GetSpellTypeNameByID(BotSpellTypes::Teleport), + spells[itr->SpellId].name, + tar->GetCleanName() + ).c_str() + ); + + bot_iter->InterruptSpell(); + } + + if (bot_iter->CastSpell(itr->SpellId, tar->GetID(), EQ::spells::CastingSlot::Gem2, -1, -1)) { + if (BotSpellTypeUsesTargetSettings(BotSpellTypes::Teleport)) { + bot_iter->SetCastedSpellType(UINT16_MAX); + } + else { + bot_iter->SetCastedSpellType(BotSpellTypes::Teleport); + } + + bot_iter->RaidGroupSay( + fmt::format( + "Casting {} [{}] on {}.", + GetSpellName(itr->SpellId), + bot_iter->GetSpellTypeNameByID(BotSpellTypes::Teleport), + (tar == bot_iter ? "myself" : tar->GetCleanName()) + ).c_str() + ); + + is_success = true; + } + + bot_iter->SetCommandedSpell(false); + + if (is_success) { + return; + } + else { + continue; + } + } } - helper_no_available_bots(c, my_bot); + if ( + (list && list_zones.empty()) || + (!list && !is_success) + ) { + c->Message( + Chat::Yellow, + fmt::format( + "No bots are capable of that on {}. Be sure they are in the same group, raid or raid group if necessary.", + tar ? tar->GetCleanName() : "you" + ).c_str() + ); + } } diff --git a/zone/bot_commands/discipline.cpp b/zone/bot_commands/discipline.cpp new file mode 100644 index 000000000..570bdc56f --- /dev/null +++ b/zone/bot_commands/discipline.cpp @@ -0,0 +1,258 @@ +#include "../bot_command.h" + +void bot_command_discipline(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_discipline", sep->arg[0], "discipline")) { + c->Message(Chat::White, "note: Tells applicable bots to use the specified disciplines."); + + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + BotCommandHelpParams p; + + p.description = + { + "Tells applicable bots to use the specified disciplines." + }; + p.notes = { "Aside from Lay On Hands and Harm Touch, you will need to know the spell ID of the discipline to tell a bot to attempt to use it." }; + p.example_format = { fmt::format("{} [aggressive | defensive | spell ID] [actionable, default: spawned]", sep->arg[0]) }; + p.examples_one = + { + "To tell all bots to use an aggressive discipline:", + fmt::format( + "{} aggressive spawned", + sep->arg[0] + ) + }; + p.examples_two = + { + "To tell Warrior bots to use a defensive discipline:", + fmt::format( + "{} defensive byclass {}", + sep->arg[0], + Class::Warrior + ) + }; + p.examples_three = + { + "To tell all bots to use their Fearless discipline:", + fmt::format( + "{} 4587 spawned", + sep->arg[0] + ) + }; + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + + return; + } + + std::string arg1 = sep->arg[1]; + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool aggressive = false; + bool defensive = false; + Mob* tar = c->GetTarget(); + uint16 spell_id = UINT16_MAX; + + if (!arg1.compare("aggressive")) { + aggressive = true; + } + else if (!arg1.compare("defensive")) { + defensive = true; + } + else if (sep->IsNumber(1)) { + if (!IsValidSpell(atoi(sep->arg[1]))) { + c->Message(Chat::Yellow, "You must enter a valid spell ID."); + return; + } + + spell_id = atoi(sep->arg[1]); + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string actionable_arg = sep->arg[ab_arg]; + + if (actionable_arg.empty()) { + actionable_arg = "spawned"; + } + + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + + if (ActionableBots::PopulateSBL(c, actionable_arg, sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + + bool is_success = false; + uint16 success_count = 0; + Bot* first_found = nullptr; + + for (auto bot_iter : sbl) { + if (!bot_iter->ValidStateCheck(c)) { + continue; + } + + if (spell_id == UINT16_MAX) { // Aggressive/Defensive type + const std::vector& bot_spell_list = bot_iter->BotGetSpellsByType(aggressive ? BotSpellTypes::DiscAggressive : BotSpellTypes::DiscDefensive); + + for (int i = bot_spell_list.size() - 1; i >= 0; i--) { + if (!IsValidSpell(bot_spell_list[i].spellid)) { + continue; + } + + if (!bot_iter->CheckDisciplineReuseTimer(bot_spell_list[i].spellid)) { + uint32 remaining_time = (bot_iter->GetDisciplineReuseRemainingTime(bot_spell_list[i].spellid) / 1000); + + bot_iter->OwnerMessage( + fmt::format( + "I can use this discipline in {}.", + Strings::SecondsToTime(remaining_time) + ) + ); + + continue; + } + + if (bot_iter->GetEndurance() < spells[bot_spell_list[i].spellid].endurance_cost) { + continue; + } + + if (bot_iter->DivineAura() && !IsCastNotStandingSpell(bot_spell_list[i].spellid)) { + continue; + } + + if (spells[bot_spell_list[i].spellid].buff_duration_formula != 0 && spells[bot_spell_list[i].spellid].target_type == ST_Self && bot_iter->HasDiscBuff()) { + continue; + } + + if (!tar || (spells[bot_spell_list[i].spellid].target_type == ST_Self && tar != bot_iter)) { + tar = bot_iter; + } + + if (bot_iter->AttemptForcedCastSpell(tar, bot_spell_list[i].spellid, true)) { + if (!first_found) { + first_found = bot_iter; + } + + is_success = true; + ++success_count; + spell_id = bot_spell_list[i].spellid; + } + } + } + else { // Direct spell ID + if (!IsValidSpell(spell_id)) { + continue; + } + + SPDat_Spell_Struct spell = spells[spell_id]; + + if (!bot_iter->CanUseBotSpell(spell_id)) { + continue; + } + + if (!bot_iter->CheckDisciplineReuseTimer(spell_id)) { + uint32 remaining_time = (bot_iter->GetDisciplineReuseRemainingTime(spell_id) / 1000); + + bot_iter->OwnerMessage( + fmt::format( + "I can use this item in {}.", + Strings::SecondsToTime(remaining_time) + ) + ); + + continue; + } + + if (bot_iter->GetEndurance() < spell.endurance_cost) { + continue; + } + + if (bot_iter->DivineAura() && !IsCastNotStandingSpell(spell_id)) { + continue; + } + + if (!tar || (spell.target_type == ST_Self && tar != bot_iter)) { + tar = bot_iter; + } + + if (bot_iter->AttemptForcedCastSpell(tar, spell_id, true)) { + if (!first_found) { + first_found = bot_iter; + } + + is_success = true; + ++success_count; + } + } + + continue; + } + + if (!is_success) { + c->Message(Chat::Yellow, "No bots were capable of doing that. This could be because they don't have the ability or things like Line of Sight, range, endurance, etc."); + } + else { + if (aggressive || defensive) { + c->Message( + Chat::Yellow, + fmt::format( + "{} {} {} {} discipline.", + ((success_count == 1 && first_found) ? first_found->GetCleanName() : (fmt::format("{}", success_count).c_str())), + ((success_count == 1 && first_found) ? "used" : "of your bots used"), + (aggressive ? "an" : "a"), + (aggressive ? "aggressive" : "defensive") + ).c_str() + ); + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "{} {} their {} [#{}] discipline.", + ((success_count == 1 && first_found) ? first_found->GetCleanName() : (fmt::format("{}", success_count).c_str())), + ((success_count == 1 && first_found) ? "used" : "of your bots used"), + spells[spell_id].name, + spell_id + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/distance_ranged.cpp b/zone/bot_commands/distance_ranged.cpp new file mode 100644 index 000000000..9ce66bf6c --- /dev/null +++ b/zone/bot_commands/distance_ranged.cpp @@ -0,0 +1,157 @@ +#include "../bot_command.h" + +void bot_command_distance_ranged(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_distance_ranged", sep->arg[0], "distanceranged")) { + c->Message(Chat::White, "note: Sets the distance bots will attempt to stay away from their target to cast or use ranged items."); + + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + BotCommandHelpParams p; + + p.description = { "Sets the distance bots will attempt to stay away from their target to cast or use ranged items." }; + p.notes = + { + "- Bots will stay between half the value of the setting and the current value. IE, if set to 60, bots will stay between 30 and 60.", + "- Casters will never go closer than their maximum melee range.", + "- Archery & Throwing bots will never get closer than the minimum value for ranged to work, or beyond the range of their items." + }; + p.example_format = { fmt::format("{} [value] [actionable]", sep->arg[0]) }; + p.examples_one = { + "To set Wizards to a range of 100:", + fmt::format( + "{} 100 byclass {}", + sep->arg[0], + Class::Wizard + ) + }; + p.examples_two = { + "To set Rangers to a range of 175:", + fmt::format( + "{} 175 byclass {}", + sep->arg[0], + Class::Ranger + ) + }; + p.examples_three = { + "To view the current setting of all bots:", + fmt::format( + "{} current spawned", + sep->arg[0] + ) + }; + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + + return; + c->Message(Chat::White, "usage: %s [current | value: 0 - 300] ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); + c->Message(Chat::White, "note: Use [current] to check the current setting."); + c->Message(Chat::White, "note: Set the value to the minimum distance you want your bot to try to remain from its target."); + c->Message(Chat::White, "note: If they are too far for a spell, it will be skipped."); + c->Message(Chat::White, "note: This is set to (90) units by default."); + } + + const int ab_mask = ActionableBots::ABM_Type1; + + std::string arg1 = sep->arg[1]; + int ab_arg = 1; + bool current_check = false; + uint32 value = 0; + + if (sep->IsNumber(1)) { + ++ab_arg; + value = atoi(sep->arg[1]); + if (value < 0 || value > RuleI(Bots, MaxDistanceRanged)) { + c->Message(Chat::Yellow, "You must enter a value within the range of 0 - 300."); + return; + } + } + else if (!arg1.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message(Chat::Yellow, "Incorrect argument, use %s help for a list of options.", sep->arg[0]); + return; + } + + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + + Bot* first_found = nullptr; + int success_count = 0; + + for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + + if (!first_found) { + first_found = my_bot; + } + + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My current Distance Ranged is {}.'", + my_bot->GetCleanName(), + my_bot->GetBotDistanceRanged() + ).c_str() + ); + } + else { + my_bot->SetBotDistanceRanged(value); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My Distance Ranged was set to {}.'", + first_found->GetCleanName(), + value + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots set their Distance Ranged to {}.", + success_count, + value + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/escape.cpp b/zone/bot_commands/escape.cpp deleted file mode 100644 index 31fde82a6..000000000 --- a/zone/bot_commands/escape.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include "../bot_command.h" - -void bot_command_escape(Client *c, const Seperator *sep) -{ - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Escape]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Escape) || helper_command_alias_fail(c, "bot_command_escape", sep->arg[0], "escape")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: () %s ([option: lesser])", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Escape); - return; - } - - bool use_lesser = false; - if (!strcasecmp(sep->arg[1], "lesser")) - use_lesser = true; - - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - bool cast_success = false; - for (auto list_iter : *local_list) { - auto local_entry = list_iter->SafeCastToEscape(); - if (helper_spell_check_fail(local_entry)) - continue; - if (local_entry->lesser != use_lesser) - continue; - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); - if (!target_mob) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; - - cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); - break; - } - - helper_no_available_bots(c, my_bot); -} diff --git a/zone/bot_commands/follow.cpp b/zone/bot_commands/follow.cpp index 65ec73f8b..fe780d32e 100644 --- a/zone/bot_commands/follow.cpp +++ b/zone/bot_commands/follow.cpp @@ -1,40 +1,85 @@ #include "../bot_command.h" -void bot_command_follow(Client *c, const Seperator *sep) +void bot_command_follow(Client* c, const Seperator* sep) { - if (helper_command_alias_fail(c, "bot_command_follow", sep->arg[0], "follow")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: () %s ([option: reset]) [actionable: byname | ownergroup | ownerraid | namesgroup | healrotation | byclass | byrace | spawned]] ([actionable_name])", sep->arg[0]); - c->Message(Chat::White, "usage: %s chain", sep->arg[0]); + if (helper_command_alias_fail(c, "bot_command_follow", sep->arg[0], "follow")) { + c->Message(Chat::White, "note: Sets bots of your choosing to follow your target, view their current following state or reset their following state."); + return; } + + if (helper_is_help_or_usage(sep->arg[1])) { + BotCommandHelpParams p; + + p.description = { "Sets bots of your choosing to follow your target, view their current following state or reset their following state." }; + p.notes = { "- You can only follow players, bots or mercenaries belonging to your group or raid." }; + p.example_format = { fmt::format("{} [optional] [actionable]", sep->arg[0]) }; + p.examples_one = + { + "To set all Clerics to follow your target:", + fmt::format( + "{} byclass {}", + sep->arg[0], + Class::Cleric + ) + }; + p.examples_two = + { + "To check the current state of all bots:", + fmt::format( + "{} current spawned", + sep->arg[0] + ) + }; + p.examples_three = + { + "To reset all bots:", + fmt::format( + "{} reset spawned", + sep->arg[0] + ) + }; + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + + return; + } + const int ab_mask = ActionableBots::ABM_Type2; + bool chain = false; bool reset = false; + bool current_check = false; int ab_arg = 1; - int name_arg = 2; Mob* target_mob = nullptr; std::string optional_arg = sep->arg[1]; - if (!optional_arg.compare("chain")) { - - auto chain_count = helper_bot_follow_option_chain(c); - c->Message(Chat::White, "%i of your bots %s now chain following you", chain_count, (chain_count == 1 ? "is" : "are")); - - return; - } - else if (!optional_arg.compare("reset")) { + + if (!optional_arg.compare("reset")) { + target_mob = c; reset = true; - ab_arg = 2; - name_arg = 3; + ++ab_arg; + } + else if (!optional_arg.compare("current")) { + current_check = true; + ++ab_arg; } else { - target_mob = ActionableTarget::VerifyFriendly(c, BCEnum::TT_Single); - if (!target_mob) { - c->Message(Chat::White, "You must a friendly mob to use this command"); + target_mob = c->GetTarget(); + + if (!target_mob || !target_mob->IsOfClientBotMerc() || !c->IsInGroupOrRaid(target_mob)) { + c->Message(Chat::Yellow, "You must a friendly player, bot or merc within your group or raid to use this command"); return; } + + if (!optional_arg.compare("chain")) { + chain = true; + ++ab_arg; + } } std::string class_race_arg = sep->arg[ab_arg]; @@ -43,69 +88,147 @@ void bot_command_follow(Client *c, const Seperator *sep) class_race_check = true; } - std::list sbl; - if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[name_arg] : nullptr, class_race_check ? atoi(sep->arg[name_arg]) : 0) == ActionableBots::ABT_None) { + std::vector sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { return; } - sbl.remove(nullptr); - for (auto bot_iter : sbl) { - bot_iter->WipeHateList(); - auto my_group = bot_iter->GetGroup(); - if (my_group) { - if (reset) { - if (!my_group->GetLeader() || my_group->GetLeader() == bot_iter) - bot_iter->SetFollowID(c->GetID()); - else - bot_iter->SetFollowID(my_group->GetLeader()->GetID()); + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + auto bot_count = sbl.size(); + Mob* follow_mob = nullptr; + std::list chain_list; + std::list::const_iterator it = chain_list.begin(); + uint16 count = 0; + for (auto bot_iter : sbl) { + if (current_check) { + follow_mob = entity_list.GetMob(bot_iter->GetFollowID()); + + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I am currently following {}.'", + bot_iter->GetCleanName(), + follow_mob ? follow_mob->GetCleanName() : "no one" + ).c_str() + ); + + if (!follow_mob && RuleB(Bots, DoResponseAnimations)) { + bot_iter->DoAnim(28); + } + + continue; + } + + if (bot_iter == target_mob) { + if (bot_count == 1) { + c->Message( + Chat::Yellow, + fmt::format( + "{} says, 'I cannot follow myself, you want me to run circles?", + bot_iter->GetCleanName() + ).c_str() + ); + + if (RuleB(Bots, DoResponseAnimations)) { + bot_iter->DoAnim(60); + } + + return; + } + + bot_iter->WipeHateList(); + --bot_count; + + continue; + } + + if (!bot_iter->IsInGroupOrRaid(target_mob)) { + --bot_count; + + continue; + } + + bot_iter->WipeHateList(); + + if (!bot_iter->GetGroup() && !bot_iter->GetRaid()) { + bot_iter->SetFollowID(0); + bot_iter->SetManualFollow(false); + } + else { + if (reset) { + bot_iter->SetFollowID(c->GetID()); bot_iter->SetManualFollow(false); } else { - if (bot_iter == target_mob) - bot_iter->SetFollowID(c->GetID()); - else + if (chain) { + Mob* next_tar = target_mob; + + if (count > 0) { + next_tar = *it; + + if (!next_tar) { + next_tar = target_mob; + } + } + + chain_list.push_back(bot_iter); + ++it; + ++count; + bot_iter->SetFollowID(next_tar->GetID()); + } + else { bot_iter->SetFollowID(target_mob->GetID()); + } bot_iter->SetManualFollow(true); } } - else { - bot_iter->SetFollowID(0); - bot_iter->SetManualFollow(false); - } - if (!bot_iter->GetPet()) + + if (!bot_iter->GetPet()) { continue; + } bot_iter->GetPet()->WipeHateList(); bot_iter->GetPet()->SetFollowID(bot_iter->GetID()); } - if (sbl.size() == 1) { - Mob* follow_mob = entity_list.GetMob(sbl.front()->GetFollowID()); - Bot::BotGroupSay( - sbl.front(), + if (current_check || !bot_count) { + return; + } + + follow_mob = target_mob; + + if (bot_count == 1) { + follow_mob = entity_list.GetMob(sbl.front()->GetFollowID()); + + c->Message( + Chat::Green, fmt::format( - "Following {}.", - follow_mob ? follow_mob->GetCleanName() : "no one" + "{} says, 'Following {}.'", + sbl.front()->GetCleanName(), + follow_mob ? follow_mob->GetCleanName() : "you" ).c_str() ); - } else { + } + else { if (reset) { c->Message( - Chat::White, + Chat::Green, fmt::format( - "{} of your bots are following their default assignments.", - sbl.size() + "{} of your bots are following you.", + bot_count ).c_str() ); - } else { + } + else { c->Message( - Chat::White, + Chat::Green, fmt::format( - "{} of your bots are following {}.", - sbl.size(), - target_mob->GetCleanName() + "{} of your bots are {} {}.", + bot_count, + chain ? "chain following" : "following", + follow_mob ? follow_mob->GetCleanName() : "you" ).c_str() ); } diff --git a/zone/bot_commands/guard.cpp b/zone/bot_commands/guard.cpp index 27a9ed343..3138914ee 100644 --- a/zone/bot_commands/guard.cpp +++ b/zone/bot_commands/guard.cpp @@ -7,7 +7,7 @@ void bot_command_guard(Client *c, const Seperator *sep) } if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s ([option: clear]) [actionable: byname | ownergroup | ownerraid | namesgroup | healrotation | byclass | byrace | spawned]] ([actionable_name])", sep->arg[0]); + c->Message(Chat::White, "usage: %s ([option: clear]) [actionable: byname | ownergroup | ownerraid | namesgroup | healrotation | mmr | byclass | byrace | default: spawned] ([actionable_name])", sep->arg[0]); return; } const int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_Type2); @@ -20,8 +20,8 @@ void bot_command_guard(Client *c, const Seperator *sep) if (!clear_arg.compare("clear")) { clear = true; - ab_arg = 2; - name_arg = 3; + ++ab_arg; + ++name_arg; } std::string class_race_arg = sep->arg[ab_arg]; @@ -30,12 +30,12 @@ void bot_command_guard(Client *c, const Seperator *sep) class_race_check = true; } - std::list sbl; + std::vector sbl; if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[name_arg] : nullptr, class_race_check ? atoi(sep->arg[name_arg]) : 0) == ActionableBots::ABT_None) { return; } - sbl.remove(nullptr); + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); for (auto bot_iter : sbl) { if (clear) { @@ -47,8 +47,7 @@ void bot_command_guard(Client *c, const Seperator *sep) } if (sbl.size() == 1) { - Bot::BotGroupSay( - sbl.front(), + sbl.front()->RaidGroupSay( fmt::format( "{}uarding this position.", clear ? "No longer g" : "G" diff --git a/zone/bot_commands/heal_rotation.cpp b/zone/bot_commands/heal_rotation.cpp index b755016fc..2ffd6df23 100644 --- a/zone/bot_commands/heal_rotation.cpp +++ b/zone/bot_commands/heal_rotation.cpp @@ -2,28 +2,28 @@ void bot_command_heal_rotation(Client *c, const Seperator *sep) { - - std::list subcommand_list; - subcommand_list.push_back("healrotationadaptivetargeting"); - subcommand_list.push_back("healrotationaddmember"); - subcommand_list.push_back("healrotationaddtarget"); - subcommand_list.push_back("healrotationadjustcritical"); - subcommand_list.push_back("healrotationadjustsafe"); - subcommand_list.push_back("healrotationcastoverride"); - subcommand_list.push_back("healrotationchangeinterval"); - subcommand_list.push_back("healrotationclearhot"); - subcommand_list.push_back("healrotationcleartargets"); - subcommand_list.push_back("healrotationcreate"); - subcommand_list.push_back("healrotationdelete"); - subcommand_list.push_back("healrotationfastheals"); - subcommand_list.push_back("healrotationlist"); - subcommand_list.push_back("healrotationremovemember"); - subcommand_list.push_back("healrotationremovetarget"); - subcommand_list.push_back("healrotationresetlimits"); - subcommand_list.push_back("healrotationsave"); - subcommand_list.push_back("healrotationsethot"); - subcommand_list.push_back("healrotationstart"); - subcommand_list.push_back("healrotationstop"); + std::vector subcommand_list = { + "healrotationadaptivetargeting", + "healrotationaddmember", + "healrotationaddtarget", + "healrotationadjustcritical", + "healrotationadjustsafe", + "healrotationcastoverride", + "healrotationchangeinterval", + "healrotationclearhot", + "healrotationcleartargets", + "healrotationcreate", + "healrotationdelete", + "healrotationfastheals", + "healrotationlist", + "healrotationremovemember", + "healrotationremovetarget", + "healrotationresetlimits", + "healrotationsave", + "healrotationsethot", + "healrotationstart", + "healrotationstop" + }; if (helper_command_alias_fail(c, "bot_command_heal_rotation", sep->arg[0], "healrotation")) return; @@ -63,7 +63,7 @@ void bot_command_heal_rotation_adaptive_targeting(Client* c, const Seperator* se std::string adaptive_targeting_arg; - std::list sbl; + std::vector sbl; MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]); if (!sbl.empty()) { adaptive_targeting_arg = sep->arg[2]; @@ -123,7 +123,7 @@ void bot_command_heal_rotation_add_member(Client* c, const Seperator* sep) return; } - std::list sbl; + std::vector sbl; MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]); if (sbl.empty()) { c->Message(Chat::White, "You must [name] a new member as a bot that you own to use this command"); @@ -191,7 +191,7 @@ void bot_command_heal_rotation_add_target(Client* c, const Seperator* sep) return; } - std::list sbl; + std::vector sbl; MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[2]); if (sbl.empty()) { MyBots::PopulateSBL_ByTargetedBot(c, sbl); @@ -283,7 +283,7 @@ void bot_command_heal_rotation_adjust_critical(Client* c, const Seperator* sep) return; } - std::list sbl; + std::vector sbl; MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[3]); if (sbl.empty()) { MyBots::PopulateSBL_ByTargetedBot(c, sbl); @@ -383,7 +383,7 @@ void bot_command_heal_rotation_adjust_safe(Client* c, const Seperator* sep) return; } - std::list sbl; + std::vector sbl; MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[3]); if (sbl.empty()) { MyBots::PopulateSBL_ByTargetedBot(c, sbl); @@ -460,7 +460,7 @@ void bot_command_heal_rotation_casting_override(Client* c, const Seperator* sep) std::string casting_override_arg; - std::list sbl; + std::vector sbl; MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]); if (!sbl.empty()) { casting_override_arg = sep->arg[2]; @@ -534,7 +534,7 @@ void bot_command_heal_rotation_change_interval(Client* c, const Seperator* sep) std::string change_interval_arg; - std::list sbl; + std::vector sbl; MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]); if (!sbl.empty()) { change_interval_arg = sep->arg[2]; @@ -603,7 +603,7 @@ void bot_command_heal_rotation_clear_hot(Client* c, const Seperator* sep) return; } - std::list sbl; + std::vector sbl; MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]); if (sbl.empty()) { MyBots::PopulateSBL_ByTargetedBot(c, sbl); @@ -649,7 +649,7 @@ void bot_command_heal_rotation_clear_targets(Client* c, const Seperator* sep) return; } - std::list sbl; + std::vector sbl; MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]); if (sbl.empty()) { MyBots::PopulateSBL_ByTargetedBot(c, sbl); @@ -703,7 +703,7 @@ void bot_command_heal_rotation_create(Client* c, const Seperator* sep) std::string adaptive_targeting_arg; std::string casting_override_arg; - std::list sbl; + std::vector sbl; MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]); if (!sbl.empty()) { interval_arg = sep->arg[2]; @@ -858,7 +858,7 @@ void bot_command_heal_rotation_delete(Client* c, const Seperator* sep) return; } - std::list sbl; + std::vector sbl; MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[name_arg]); if (sbl.empty()) { MyBots::PopulateSBL_ByTargetedBot(c, sbl); @@ -896,7 +896,7 @@ void bot_command_heal_rotation_fast_heals(Client* c, const Seperator* sep) std::string fast_heals_arg; - std::list sbl; + std::vector sbl; MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]); if (!sbl.empty()) { fast_heals_arg = sep->arg[2]; @@ -956,7 +956,7 @@ void bot_command_heal_rotation_list(Client* c, const Seperator* sep) return; } - std::list sbl; + std::vector sbl; MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]); if (sbl.empty()) { MyBots::PopulateSBL_ByTargetedBot(c, sbl); @@ -1076,7 +1076,7 @@ void bot_command_heal_rotation_remove_member(Client* c, const Seperator* sep) return; } - std::list sbl; + std::vector sbl; MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]); if (sbl.empty()) { MyBots::PopulateSBL_ByTargetedBot(c, sbl); @@ -1123,7 +1123,7 @@ void bot_command_heal_rotation_remove_target(Client* c, const Seperator* sep) return; } - std::list sbl; + std::vector sbl; MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[2]); if (sbl.empty()) { MyBots::PopulateSBL_ByTargetedBot(c, sbl); @@ -1181,7 +1181,7 @@ void bot_command_heal_rotation_reset_limits(Client* c, const Seperator* sep) return; } - std::list sbl; + std::vector sbl; MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]); if (sbl.empty()) { MyBots::PopulateSBL_ByTargetedBot(c, sbl); @@ -1223,7 +1223,7 @@ void bot_command_heal_rotation_save(Client* c, const Seperator* sep) return; } - std::list sbl; + std::vector sbl; MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]); if (sbl.empty()) { MyBots::PopulateSBL_ByTargetedBot(c, sbl); @@ -1272,7 +1272,7 @@ void bot_command_heal_rotation_set_hot(Client* c, const Seperator* sep) return; } - std::list sbl; + std::vector sbl; MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[2]); if (sbl.empty()) { MyBots::PopulateSBL_ByTargetedBot(c, sbl); @@ -1337,7 +1337,7 @@ void bot_command_heal_rotation_start(Client* c, const Seperator* sep) return; } - std::list sbl; + std::vector sbl; MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]); if (sbl.empty()) { MyBots::PopulateSBL_ByTargetedBot(c, sbl); @@ -1384,7 +1384,7 @@ void bot_command_heal_rotation_stop(Client* c, const Seperator* sep) return; } - std::list sbl; + std::vector sbl; MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]); if (sbl.empty()) { MyBots::PopulateSBL_ByTargetedBot(c, sbl); diff --git a/zone/bot_commands/hold.cpp b/zone/bot_commands/hold.cpp index 49f0f516f..4d79fcce7 100644 --- a/zone/bot_commands/hold.cpp +++ b/zone/bot_commands/hold.cpp @@ -7,7 +7,7 @@ void bot_command_hold(Client *c, const Seperator *sep) } if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s ([option: clear]) [actionable: byname | ownergroup | ownerraid | namesgroup | healrotation | byclass | byrace | spawned]] ([actionable_name])", sep->arg[0]); + c->Message(Chat::White, "usage: %s ([option: clear]) [actionable: byname | ownergroup | ownerraid | namesgroup | healrotation | mmr | byclass | byrace | default: spawned] ([actionable_name])", sep->arg[0]); return; } const int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_Type2); @@ -20,8 +20,8 @@ void bot_command_hold(Client *c, const Seperator *sep) if (!clear_arg.compare("clear")) { clear = true; - ab_arg = 2; - name_arg = 3; + ++ab_arg; + ++name_arg; } std::string class_race_arg = sep->arg[ab_arg]; @@ -30,12 +30,12 @@ void bot_command_hold(Client *c, const Seperator *sep) class_race_check = true; } - std::list sbl; + std::vector sbl; if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[name_arg] : nullptr, class_race_check ? atoi(sep->arg[name_arg]) : 0) == ActionableBots::ABT_None) { return; } - sbl.remove(nullptr); + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); for (auto bot_iter : sbl) { if (clear) { @@ -47,8 +47,7 @@ void bot_command_hold(Client *c, const Seperator *sep) } if (sbl.size() == 1) { - Bot::BotGroupSay( - sbl.front(), + sbl.front()->RaidGroupSay( fmt::format( "{}olding my attacks.", clear ? "No longer h" : "H" diff --git a/zone/bot_commands/identify.cpp b/zone/bot_commands/identify.cpp deleted file mode 100644 index 7ac90a4e4..000000000 --- a/zone/bot_commands/identify.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "../bot_command.h" - -void bot_command_identify(Client *c, const Seperator *sep) -{ - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Identify]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Identify) || helper_command_alias_fail(c, "bot_command_identify", sep->arg[0], "identify")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: () %s", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Identify); - return; - } - - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - bool cast_success = false; - for (auto list_iter : *local_list) { - auto local_entry = list_iter; - if (helper_spell_check_fail(local_entry)) - continue; - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); - if (!target_mob) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; - - cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); - break; - } - - helper_no_available_bots(c, my_bot); -} diff --git a/zone/bot_commands/illusion_block.cpp b/zone/bot_commands/illusion_block.cpp new file mode 100644 index 000000000..01c9eafa4 --- /dev/null +++ b/zone/bot_commands/illusion_block.cpp @@ -0,0 +1,157 @@ +#include "../bot_command.h" + +void bot_command_illusion_block(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_illusion_block", sep->arg[0], "illusionblock")) { + c->Message(Chat::White, "note: Toggles whether or not bots will block the illusion effects of spells cast by players or bots."); + + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + BotCommandHelpParams p; + + p.description = { "Toggles whether or not bots will block the illusion effects of spells cast by players or bots." }; + p.example_format = { fmt::format("{} [value] [actionable]", sep->arg[0]) }; + p.examples_one = + { + "To set BotA to block illusions:", + fmt::format( + "{} 1 byname BotA", + sep->arg[0] + ) + }; + p.examples_two = + { + "To set all bots to block illusions:", + fmt::format( + "{} 1 spawned", + sep->arg[0] + ) + }; + p.examples_three = + { + "To check the illusion block status for all bots:", + fmt::format( + "{} current spawned", + sep->arg[0] + ) + }; + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + + return; + } + + std::string arg1 = sep->arg[1]; + + int ab_arg = 1; + bool current_check = false; + uint32 type_value = 0; + + if (sep->IsNumber(1)) { + type_value = atoi(sep->arg[1]); + ++ab_arg; + if (type_value < 0 || type_value > 1) { + c->Message(Chat::Yellow, "You must enter either 0 for disabled or 1 for enabled."); + + return; + } + } + else if (!arg1.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + + if (!first_found) { + first_found = my_bot; + } + + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I {} block illusions.'", + my_bot->GetCleanName(), + my_bot->GetIllusionBlock() ? "will" : "will not" + ).c_str() + ); + } + else { + my_bot->SetIllusionBlock(type_value); + ++success_count; + } + } + + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I {} block illusions.'", + first_found->GetCleanName(), + first_found->GetIllusionBlock() ? "will now" : "will no longer" + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots {} block illusions.", + success_count, + type_value ? "will now" : "will no longer" + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/inventory.cpp b/zone/bot_commands/inventory.cpp index b934f96a2..9d8511cf9 100644 --- a/zone/bot_commands/inventory.cpp +++ b/zone/bot_commands/inventory.cpp @@ -2,12 +2,12 @@ void bot_command_inventory(Client *c, const Seperator *sep) { - - std::list subcommand_list; - subcommand_list.push_back("inventorygive"); - subcommand_list.push_back("inventorylist"); - subcommand_list.push_back("inventoryremove"); - subcommand_list.push_back("inventorywindow"); + std::vector subcommand_list = { + "inventorygive", + "inventorylist", + "inventoryremove", + "inventorywindow" + }; if (helper_command_alias_fail(c, "bot_command_inventory", sep->arg[0], "inventory")) return; @@ -25,7 +25,7 @@ void bot_command_inventory_give(Client* c, const Seperator* sep) c->Message( Chat::White, fmt::format( - "Usage: {} ([actionable: target | byname] ([actionable_name]))", + "Usage: {} ([actionable: target | byname] ([actionable_name])) [optional: slot ID]", sep->arg[0] ).c_str() ); @@ -33,19 +33,45 @@ void bot_command_inventory_give(Client* c, const Seperator* sep) } int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_ByName); + int ab_arg = 1; + int slot_arg = 1; + int16 chosen_slot = INVALID_INDEX; + bool byname = false; - std::list sbl; - if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, sep->arg[2]) == ActionableBots::ABT_None) { + std::string byname_arg = sep->arg[ab_arg]; + + if (!byname_arg.compare("byname")) { + byname = true; + slot_arg = ab_arg + 2; + } + + if (sep->IsNumber(slot_arg)) { + chosen_slot = atoi(sep->arg[slot_arg]); + + if (!EQ::ValueWithin(chosen_slot, EQ::invslot::EQUIPMENT_BEGIN, EQ::invslot::EQUIPMENT_END)) { + c->Message(Chat::Yellow, "Please enter a valid inventory slot."); + + return; + } + + if (!byname) { + ++ab_arg; + } + } + + std::vector sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, sep->arg[ab_arg + 1]) == ActionableBots::ABT_None) { return; } auto my_bot = sbl.front(); if (!my_bot) { - c->Message(Chat::White, "ActionableBots returned 'nullptr'"); + c->Message(Chat::Yellow, "ActionableBots returned 'nullptr'"); + return; } - my_bot->FinishTrade(c, Bot::BotTradeClientNoDropNoTrade); + my_bot->FinishTrade(c, Bot::BotTradeClientNoDropNoTrade, chosen_slot); } void bot_command_inventory_list(Client* c, const Seperator* sep) @@ -67,7 +93,7 @@ void bot_command_inventory_list(Client* c, const Seperator* sep) int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_ByName); - std::list sbl; + std::vector sbl; if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, sep->arg[2]) == ActionableBots::ABT_None) { return; } @@ -168,7 +194,7 @@ void bot_command_inventory_remove(Client* c, const Seperator* sep) return; } - std::list sbl; + std::vector sbl; if (ActionableBots::PopulateSBL(c, sep->arg[2], sbl, ab_mask, sep->arg[3]) == ActionableBots::ABT_None) { return; } @@ -217,9 +243,18 @@ void bot_command_inventory_remove(Client* c, const Seperator* sep) } const auto* itm = inst->GetItem(); + EQ::SayLinkEngine linker; + linker.SetLinkType(EQ::saylink::SayLinkItemInst); + linker.SetItemInst(inst); if (inst && itm && c->CheckLoreConflict(itm)) { - c->MessageString(Chat::White, PICK_LORE); + c->Message( + Chat::White, + fmt::format( + "You cannot pick up {} because it is a lore item you already possess.", + linker.GenerateLink() + ).c_str() + ); return; } @@ -233,7 +268,13 @@ void bot_command_inventory_remove(Client* c, const Seperator* sep) continue; } - c->MessageString(Chat::White, PICK_LORE); + c->Message( + Chat::White, + fmt::format( + "You cannot pick up {} because it is a lore item you already possess.", + linker.GenerateLink() + ).c_str() + ); return; } @@ -247,7 +288,7 @@ void bot_command_inventory_remove(Client* c, const Seperator* sep) slot_id == EQ::invslot::slotRange || slot_id == EQ::invslot::slotAmmo ) { - my_bot->SetBotArcherySetting(false, true); + my_bot->SetBotRangedSetting(false); } my_bot->RemoveBotItemBySlot(slot_id); @@ -296,7 +337,7 @@ void bot_command_inventory_window(Client* c, const Seperator* sep) int ab_mask = ActionableBots::ABM_Target; - std::list sbl; + std::vector sbl; if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, sep->arg[2]) == ActionableBots::ABT_None) { return; } diff --git a/zone/bot_commands/invisibility.cpp b/zone/bot_commands/invisibility.cpp deleted file mode 100644 index dd826ebc8..000000000 --- a/zone/bot_commands/invisibility.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include "../bot_command.h" - -void bot_command_invisibility(Client *c, const Seperator *sep) -{ - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Invisibility]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Invisibility) || helper_command_alias_fail(c, "bot_command_invisibility", sep->arg[0], "invisibility")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: () %s [invisibility: living | undead | animal | see]", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Invisibility); - return; - } - - std::string invisibility = sep->arg[1]; - - BCEnum::IType invisibility_type = BCEnum::IT_None; - if (!invisibility.compare("living")) - invisibility_type = BCEnum::IT_Living; - else if (!invisibility.compare("undead")) - invisibility_type = BCEnum::IT_Undead; - else if (!invisibility.compare("animal")) - invisibility_type = BCEnum::IT_Animal; - else if (!invisibility.compare("see")) - invisibility_type = BCEnum::IT_See; - - if (invisibility_type == BCEnum::IT_None) { - c->Message(Chat::White, "You must specify an [invisibility]"); - return; - } - - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - bool cast_success = false; - for (auto list_iter : *local_list) { - auto local_entry = list_iter->SafeCastToInvisibility(); - if (helper_spell_check_fail(local_entry)) - continue; - if (local_entry->invis_type != invisibility_type) - continue; - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); - if (!target_mob) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; - - cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); - break; - } - - helper_no_available_bots(c, my_bot); -} diff --git a/zone/bot_commands/item_use.cpp b/zone/bot_commands/item_use.cpp index 1640ff0e3..b9cf6a803 100644 --- a/zone/bot_commands/item_use.cpp +++ b/zone/bot_commands/item_use.cpp @@ -4,16 +4,17 @@ void bot_command_item_use(Client* c, const Seperator* sep) { if (helper_is_help_or_usage(sep->arg[1])) { c->Message(Chat::White, "usage: [%s empty] will display only bots that can use the item in an empty slot.", sep->arg[0]); - c->Message(Chat::White, "usage: [%s byclass classID] - Example: [%s byclass 7] will display only bots that match the class that can use the item. Example is a Monk, use [^create help] for a list of class IDs.", sep->arg[0], sep->arg[0]); - c->Message(Chat::White, "usage: [%s casteronly] will display only caster bots that can use the item.", sep->arg[0]); - c->Message(Chat::White, "usage: [%s hybridonly] will display only hybrid bots that can use the item.", sep->arg[0]); - c->Message(Chat::White, "usage: [%s meleeonly] will display only melee bots that can use the item.", sep->arg[0]); - c->Message(Chat::White, "usage: [%s wiscasteronly] will display only Wisdom-based Caster bots that can use the item.", sep->arg[0]); - c->Message(Chat::White, "usage: [%s intcasteronly] will display only Intelligence-based Caster bots that can use the item.", sep->arg[0]); - c->Message(Chat::White, "usage: [%s plateonly] will display only Plate-wearing bots that can use the item.", sep->arg[0]); - c->Message(Chat::White, "usage: [%s chainonly] will display only Chain-wearing bots that can use the item.", sep->arg[0]); - c->Message(Chat::White, "usage: [%s leatheronly] will display only Leather-wearing bots that can use the item.", sep->arg[0]); - c->Message(Chat::White, "usage: [%s clothonly] will display only Cloth-wearing bots that can use the item.", sep->arg[0]); + c->Message(Chat::White, "usage: [%s caster] will display only caster bots that can use the item.", sep->arg[0]); + c->Message(Chat::White, "usage: [%s hybrid] will display only hybrid bots that can use the item.", sep->arg[0]); + c->Message(Chat::White, "usage: [%s melee] will display only melee bots that can use the item.", sep->arg[0]); + c->Message(Chat::White, "usage: [%s wiscaster] will display only Wisdom-based Caster bots that can use the item.", sep->arg[0]); + c->Message(Chat::White, "usage: [%s intcaster] will display only Intelligence-based Caster bots that can use the item.", sep->arg[0]); + c->Message(Chat::White, "usage: [%s plate] will display only Plate-wearing bots that can use the item.", sep->arg[0]); + c->Message(Chat::White, "usage: [%s chain] will display only Chain-wearing bots that can use the item.", sep->arg[0]); + c->Message(Chat::White, "usage: [%s leather] will display only Leather-wearing bots that can use the item.", sep->arg[0]); + c->Message(Chat::White, "usage: [%s cloth] will display only Cloth-wearing bots that can use the item.", sep->arg[0]); + c->Message(Chat::White, "usage: [%s haste] will display bots that have no or lesser haste than the item.", sep->arg[0]); + c->Message(Chat::White, "usage: You can also use empty or haste as an argument to narrow down further, for example [%s caster empty], [%s plate haste] or even [%s empty haste]", sep->arg[0], sep->arg[0], sep->arg[0]); return; } @@ -28,136 +29,252 @@ void bot_command_item_use(Client* c, const Seperator* sep) bool chain_only = false; bool leather_only = false; bool cloth_only = false; + bool haste_only = false; + int haste_value = 0; + int ab_arg = 2; std::string arg1 = sep->arg[1]; std::string arg2 = sep->arg[2]; - if (arg1.compare("empty") == 0) { + + if (arg1.compare("empty") == 0 || arg2.compare("empty") == 0) { empty_only = true; + + if (arg2.compare("empty") == 0) { + ++ab_arg; + } } - else if (arg1.compare("byclass") == 0) { - if (Strings::IsNumber(sep->arg[2])) { - class_mask = Strings::ToUnsignedInt(sep->arg[2]); - if (!(class_mask >= Class::Warrior && class_mask <= Class::Berserker)) { - c->Message(Chat::White, "Invalid class range, you must choose between 1 (Warrior) and 15 (Beastlord)"); + + if (arg1.compare("haste") == 0 || arg2.compare("haste") == 0) { + haste_only = true; + + if (arg2.compare("haste") == 0) { + ++ab_arg; + } + } + + if (arg1.compare("caster") == 0) { + caster_only = true; + } + else if (arg1.compare("hybrid") == 0) { + hybrid_only = true; + } + else if (arg1.compare("melee") == 0) { + melee_only = true; + } + else if (arg1.compare("wiscaster") == 0) { + wis_caster_only = true; + } + else if (arg1.compare("intcaster") == 0) { + int_caster_only = true; + } + else if (arg1.compare("plate") == 0) { + plate_only = true; + } + else if (arg1.compare("chain") == 0) { + chain_only = true; + } + else if (arg1.compare("leather") == 0) { + leather_only = true; + } + else if (arg1.compare("cloth") == 0) { + cloth_only = true; + } + else { + if (arg1.empty()) { + --ab_arg; + } + else { + if (!(arg1.compare("empty") == 0) && !(arg1.compare("haste") == 0)) { + c->Message(Chat::White, "Please choose the correct subtype. For help use %s help.", sep->arg[0]); + return; } } } - else if (arg1.compare("casteronly") == 0) { - caster_only = true; - } - else if (arg1.compare("hybridonly") == 0) { - hybrid_only = true; - } - else if (arg1.compare("meleeonly") == 0) { - melee_only = true; - } - else if (arg1.compare("wiscasteronly") == 0) { - wis_caster_only = true; - } - else if (arg1.compare("intcasteronly") == 0) { - int_caster_only = true; - } - else if (arg1.compare("plateonly") == 0) { - plate_only = true; - } - else if (arg1.compare("chainonly") == 0) { - chain_only = true; - } - else if (arg1.compare("leatheronly") == 0) { - leather_only = true; - } - else if (arg1.compare("clothonly") == 0) { - cloth_only = true; - } - else if (!arg1.empty()) { - c->Message(Chat::White, "Please choose the correct subtype. For help use %s help.", sep->arg[0]); - return; - } + const auto item_instance = c->GetInv().GetItem(EQ::invslot::slotCursor); + if (!item_instance) { - c->Message(Chat::White, "No item found on cursor!"); + c->Message(Chat::Yellow, "No item found on cursor! For help use %s help.", sep->arg[0]); + return; } auto item_data = item_instance->GetItem(); + if (!item_data) { - c->Message(Chat::White, "No data found for cursor item!"); + c->Message(Chat::Yellow, "No data found for cursor item!"); + return; } if (item_data->ItemClass != EQ::item::ItemClassCommon || item_data->Slots == 0) { - c->Message(Chat::White, "'%s' is not an equipable item!", item_data->Name); + c->Message(Chat::Yellow, "'%s' is not an equipable item!", item_data->Name); + return; } std::vector equipable_slot_list; + for (int16 equipable_slot = EQ::invslot::EQUIPMENT_BEGIN; equipable_slot <= EQ::invslot::EQUIPMENT_END; ++equipable_slot) { if (item_data->Slots & (1 << equipable_slot)) { equipable_slot_list.emplace_back(equipable_slot); } } - EQ::SayLinkEngine linker; - linker.SetLinkType(EQ::saylink::SayLinkItemInst); + const int ab_mask = ActionableBots::ABM_Type1; + std::string actionable_arg = sep->arg[ab_arg]; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - if (class_mask) { - ActionableBots::Filter_ByClasses(c, sbl, GetPlayerClassBit(class_mask)); + if (actionable_arg.empty()) { + actionable_arg = "spawned"; } + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + + if (ActionableBots::PopulateSBL(c, actionable_arg, sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + + EQ::SayLinkEngine linker; + linker.SetLinkType(EQ::saylink::SayLinkItemData); + for (const auto& bot_iter : sbl) { if (!bot_iter) { continue; } + if (caster_only && !IsCasterClass(bot_iter->GetClass())) { continue; } + if (hybrid_only && !IsSpellFighterClass(bot_iter->GetClass())) { continue; } + if (melee_only && !IsNonSpellFighterClass(bot_iter->GetClass())) { continue; } + if (wis_caster_only && !IsWISCasterClass(bot_iter->GetClass())) { continue; } + if (int_caster_only && !IsINTCasterClass(bot_iter->GetClass())) { continue; } + if (plate_only && !IsPlateClass(bot_iter->GetClass())) { continue; } + if (chain_only && !IsChainClass(bot_iter->GetClass())) { continue; } + if (leather_only && !IsLeatherClass(bot_iter->GetClass())) { continue; } + if (cloth_only && !IsClothClass(bot_iter->GetClass())) { continue; } - if (((~item_data->Races) & GetPlayerRaceBit(bot_iter->GetRace())) || ((~item_data->Classes) & GetPlayerClassBit(bot_iter->GetClass()))) { + + if ( + (!RuleB(Bots, AllowBotEquipAnyRaceGear) && ((~item_data->Races) & GetPlayerRaceBit(bot_iter->GetRace()))) || + (!RuleB(Bots, AllowBotEquipAnyClassGear) && ((~item_data->Classes) & GetPlayerClassBit(bot_iter->GetClass()))) + ) { continue; } + std::list refined_equipable_slot_list; + bool skip_bot = false; + const EQ::ItemData* equipped_item = nullptr; + const EQ::ItemInstance* equipped_inst = nullptr; + for (const auto& slot_iter : equipable_slot_list) { // needs more failure criteria - this should cover the bulk for now if (slot_iter == EQ::invslot::slotSecondary && item_data->Damage && !bot_iter->CanThisClassDualWield()) { continue; } - auto equipped_item = bot_iter->GetInv()[slot_iter]; + if (item_data->ReqLevel > bot_iter->GetLevel()) { + continue; + } - if (equipped_item && !empty_only) { - linker.SetItemInst(equipped_item); + haste_value = 0; + equipped_item = nullptr; + equipped_inst = nullptr; + + + for (int16 equipable_slot = EQ::invslot::EQUIPMENT_BEGIN; equipable_slot <= EQ::invslot::EQUIPMENT_END; ++equipable_slot) { + equipped_inst = bot_iter->GetInv()[equipable_slot]; + if (equipped_inst && equipped_inst->GetItem()) { + equipped_item = equipped_inst->GetItem(); + + if (item_data->CheckLoreConflict(equipped_item)) { + skip_bot = true; + break; + } + + if (haste_only) { + if (equipped_item->Haste > haste_value) { + haste_value = equipped_item->Haste; + } + } + } + } + + if (skip_bot) { + break; + } + + if (haste_only && item_data->Haste < haste_value) { + continue; + } + + equipped_inst = bot_iter->GetInv()[slot_iter]; + + if (equipped_inst && empty_only) { + continue; + } + + refined_equipable_slot_list.push_back(slot_iter); + } + + if (skip_bot) { + continue; + } + + if (refined_equipable_slot_list.empty()) { + continue; + } + + for (auto slot_iter : refined_equipable_slot_list) { + equipped_item = nullptr; + equipped_inst = nullptr; + + equipped_inst = bot_iter->GetInv()[slot_iter]; + + if (equipped_inst && equipped_inst->GetItem()) { + equipped_item = equipped_inst->GetItem(); + } + + if (equipped_item) { + linker.SetItemData(equipped_item); c->Message( Chat::Say, fmt::format( - "{} says, 'I can use that for my {} instead of my {}! Would you like to {} my {}?'", + "{} says, 'I can use that for my {} instead of my {}! Would you like to {}?'", Saylink::Silent( fmt::format( "^inventorygive byname {}", @@ -173,19 +290,16 @@ void bot_command_item_use(Client* c, const Seperator* sep) slot_iter, bot_iter->GetCleanName() ), - "remove" - ), - linker.GenerateLink() + "remove my item" + ) ).c_str() ); - - bot_iter->DoAnim(29); } - else if (!equipped_item) { + else { c->Message( Chat::Say, fmt::format( - "{} says, 'I can use that for my {}! Would you like to {} it to me?'", + "{} says, 'I can use that for my {}! Would you like to {}?'", Saylink::Silent( fmt::format( "^inventorygive byname {}", @@ -199,11 +313,13 @@ void bot_command_item_use(Client* c, const Seperator* sep) "^inventorygive byname {}", bot_iter->GetCleanName() ), - "give" + "give it to me" ) ).c_str() ); + } + if (RuleB(Bots, DoResponseAnimations)) { bot_iter->DoAnim(29); } } diff --git a/zone/bot_commands/levitation.cpp b/zone/bot_commands/levitation.cpp deleted file mode 100644 index d4b5cee28..000000000 --- a/zone/bot_commands/levitation.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "../bot_command.h" - -void bot_command_levitation(Client *c, const Seperator *sep) -{ - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Levitation]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Levitation) || helper_command_alias_fail(c, "bot_command_levitation", sep->arg[0], "levitation")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: () %s", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Levitation); - return; - } - - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - bool cast_success = false; - for (auto list_iter : *local_list) { - auto local_entry = list_iter; - if (helper_spell_check_fail(local_entry)) - continue; - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); - if (!target_mob) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; - - cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); - break; - } - - helper_no_available_bots(c, my_bot); -} diff --git a/zone/bot_commands/lull.cpp b/zone/bot_commands/lull.cpp deleted file mode 100644 index d39f534d1..000000000 --- a/zone/bot_commands/lull.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#include "../bot_command.h" - -void bot_command_lull(Client *c, const Seperator *sep) -{ - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Lull]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Lull) || helper_command_alias_fail(c, "bot_command_lull", sep->arg[0], "lull")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Lull); - return; - } - - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - for (auto list_iter : *local_list) { - auto local_entry = list_iter; - if (helper_spell_check_fail(local_entry)) - continue; - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, ENEMY); - if (!target_mob) - continue; - - //if (spells[local_entry->spell_id].max[EFFECTIDTOINDEX(3)] && spells[local_entry->spell_id].max[EFFECTIDTOINDEX(3)] < target_mob->GetLevel()) - // continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; - - uint32 dont_root_before = 0; - if (helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id, true, &dont_root_before)) - target_mob->SetDontRootMeBefore(dont_root_before); - - break; - } - - helper_no_available_bots(c, my_bot); -} diff --git a/zone/bot_commands/max_melee_range.cpp b/zone/bot_commands/max_melee_range.cpp new file mode 100644 index 000000000..6f4630ab5 --- /dev/null +++ b/zone/bot_commands/max_melee_range.cpp @@ -0,0 +1,161 @@ +#include "../bot_command.h" + +void bot_command_max_melee_range(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_max_melee_range", sep->arg[0], "maxmeleerange")) { + c->Message(Chat::White, "note: Toggles whether or not bots will stay at max melee range during combat."); + + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + BotCommandHelpParams p; + + p.description = { "Toggles whether or not bots will stay at max melee range during combat."}; + p.notes = { + fmt::format( + "- Bots {} use special abilities when set to max melee range (taunt, kick, bash, etc).", + RuleB(Bots, DisableSpecialAbilitiesAtMaxMelee) ? "will not" : "will" + ) + }; + p.example_format ={ fmt::format("{} [value] [actionable]", sep->arg[0]) }; + p.examples_one = + { + "To set BotA to stay at max melee range:", + fmt::format( + "{} 1 byname BotA", + sep->arg[0] + ) + }; + p.examples_two = + { + "To set all bots to stay at max melee range:", + fmt::format( + "{} 1 spawned", + sep->arg[0] + ) + }; + p.examples_three = + { + "To check the max melee range status for all bots:", + fmt::format( + "{} current spawned", + sep->arg[0] + ) + }; + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + + return; + } + + std::string arg1 = sep->arg[1]; + + int ab_arg = 1; + bool current_check = false; + uint32 type_value = 0; + + if (sep->IsNumber(1)) { + type_value = atoi(sep->arg[1]); + ++ab_arg; + if (type_value < 0 || type_value > 1) { + c->Message(Chat::Yellow, "You must enter either 0 for disabled or 1 for enabled."); + + return; + } + } + else if (!arg1.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + + if (!first_found) { + first_found = my_bot; + } + + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I {} stay at max melee range.'", + my_bot->GetCleanName(), + my_bot->GetMaxMeleeRange() ? "will" : "will not" + ).c_str() + ); + } + else { + my_bot->SetMaxMeleeRange(type_value); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I {} stay at max melee range.'", + first_found->GetCleanName(), + first_found->GetMaxMeleeRange() ? "will now" : "will no longer" + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots {} stay at max melee range.", + success_count, + type_value ? "will now" : "will no longer" + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/mesmerize.cpp b/zone/bot_commands/mesmerize.cpp deleted file mode 100644 index 532a3c404..000000000 --- a/zone/bot_commands/mesmerize.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#include "../bot_command.h" - -void bot_command_mesmerize(Client *c, const Seperator *sep) -{ - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Mesmerize]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Mesmerize) || helper_command_alias_fail(c, "bot_command_mesmerize", sep->arg[0], "mesmerize")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Mesmerize); - return; - } - - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - for (auto list_iter : *local_list) { - auto local_entry = list_iter; - if (helper_spell_check_fail(local_entry)) - continue; - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, ENEMY); - if (!target_mob) - continue; - - if (spells[local_entry->spell_id].max_value[EFFECTIDTOINDEX(1)] < target_mob->GetLevel()) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; - - uint32 dont_root_before = 0; - if (helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id, true, &dont_root_before)) - target_mob->SetDontRootMeBefore(dont_root_before); - - break; - } - - helper_no_available_bots(c, my_bot); -} diff --git a/zone/bot_commands/movement_speed.cpp b/zone/bot_commands/movement_speed.cpp deleted file mode 100644 index d1b884154..000000000 --- a/zone/bot_commands/movement_speed.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "../bot_command.h" - -void bot_command_movement_speed(Client *c, const Seperator *sep) -{ - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_MovementSpeed]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_MovementSpeed) || helper_command_alias_fail(c, "bot_command_movement_speed", sep->arg[0], "movementspeed")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: () %s ([group | sow])", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_MovementSpeed); - return; - } - - bool group = false; - bool sow = false; - std::string arg1 = sep->arg[1]; - if (!arg1.compare("group")) - group = true; - else if (!arg1.compare("sow")) - sow = true; - - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - bool cast_success = false; - for (auto list_iter : *local_list) { - auto local_entry = list_iter->SafeCastToMovementSpeed(); - if (helper_spell_check_fail(local_entry)) - continue; - if (!sow && (local_entry->group != group)) - continue; - if (sow && (local_entry->spell_id != 278)) // '278' = single-target "Spirit of Wolf" - continue; - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); - if (!target_mob) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; - - cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); - break; - } - - helper_no_available_bots(c, my_bot); -} diff --git a/zone/bot_commands/pet.cpp b/zone/bot_commands/pet.cpp index f6c6ae21d..d47726e5c 100644 --- a/zone/bot_commands/pet.cpp +++ b/zone/bot_commands/pet.cpp @@ -1,12 +1,13 @@ #include "../bot_command.h" +#include "../bot.h" void bot_command_pet(Client *c, const Seperator *sep) { - - std::list subcommand_list; - subcommand_list.push_back("petgetlost"); - subcommand_list.push_back("petremove"); - subcommand_list.push_back("petsettype"); + std::vector subcommand_list = { + "petgetlost", + "petremove", + "petsettype" + }; if (helper_command_alias_fail(c, "bot_command_pet", sep->arg[0], "pet")) return; @@ -19,12 +20,12 @@ void bot_command_pet_get_lost(Client *c, const Seperator *sep) if (helper_command_alias_fail(c, "bot_command_pet_get_lost", sep->arg[0], "petgetlost")) return; if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]); + c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationmembers | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); return; } int ab_mask = ActionableBots::ABM_NoFilter; - std::list sbl; + std::vector sbl; if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, sep->arg[2]) == ActionableBots::ABT_None) return; @@ -55,7 +56,7 @@ void bot_command_pet_remove(Client *c, const Seperator *sep) } int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_ByName); - std::list sbl; + std::vector sbl; if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, sep->arg[2]) == ActionableBots::ABT_None) return; @@ -65,7 +66,7 @@ void bot_command_pet_remove(Client *c, const Seperator *sep) c->Message(Chat::White, "You have no spawned bots capable of charming"); return; } - sbl.remove(nullptr); + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); int charmed_pet = 0; int summoned_pet = 0; @@ -73,7 +74,7 @@ void bot_command_pet_remove(Client *c, const Seperator *sep) if (bot_iter->IsBotCharmer()) { bot_iter->SetBotCharmer(false); if (sbl.size() == 1) - Bot::BotGroupSay(bot_iter, "Using a summoned pet"); + bot_iter->RaidGroupSay("Using a summoned pet"); ++summoned_pet; continue; } @@ -85,7 +86,7 @@ void bot_command_pet_remove(Client *c, const Seperator *sep) } bot_iter->SetBotCharmer(true); if (sbl.size() == 1) - Bot::BotGroupSay(bot_iter, "Available for Charming"); + bot_iter->RaidGroupSay("Available for Charming"); ++charmed_pet; } @@ -95,74 +96,330 @@ void bot_command_pet_remove(Client *c, const Seperator *sep) void bot_command_pet_set_type(Client *c, const Seperator *sep) { - if (helper_command_alias_fail(c, "bot_command_pet_set_type", sep->arg[0], "petsettype")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s [type: water | fire | air | earth | monster] ([actionable: target | byname] ([actionable_name]))", sep->arg[0]); - c->Message(Chat::White, "requires one of the following bot classes:"); - c->Message(Chat::White, "Magician(1)"); + if (helper_command_alias_fail(c, "bot_command_pet_set_type", sep->arg[0], "petsettype")) { + c->Message(Chat::White, "note: Allows you to change the type of pet Magician bots will cast."); + return; } - int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_ByName); // this can be expanded without code modification - std::string pet_arg = sep->arg[1]; + if (helper_is_help_or_usage(sep->arg[1])) { + BotCommandHelpParams p; + p.description = { "Allows you to change the type of pet Magician bots will cast." }; + p.notes = { + fmt::format( + "- Epic pets are currently {} on this server.", + RuleB(Bots, AllowMagicianEpicPet) ? "allowed" : "not allowed" + ) + }; + p.example_format = { fmt::format("{} [current | water | fire | air | earth | monster | epic] [actionable, default: target]", sep->arg[0]) }; + p.examples_one = + { + "To set all spawned bots to use Water pets:", + fmt::format( + "{} fire spawned", + sep->arg[0] + ) + }; + p.examples_two = + { + "To set Magelulz to use Fire pets:", + fmt::format( + "{} fire byname Magelulz", + sep->arg[0] + ) + }; + p.examples_three = + { + "To check the current pet type for all bots:", + fmt::format( + "{} current spawned", + sep->arg[0] + ) + }; + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + + return; + } + + std::string arg1 = sep->arg[1]; + int ab_arg = 2; + bool current_check = false; uint8 pet_type = 255; uint8 level_req = 255; - if (!pet_arg.compare("water")) { + + if (!arg1.compare("auto")) { pet_type = 0; - level_req = 1; } - else if (!pet_arg.compare("fire")) { + else if (!arg1.compare("water")) { pet_type = 1; - level_req = 3; } - else if (!pet_arg.compare("air")) { + else if (!arg1.compare("fire")) { pet_type = 2; - level_req = 4; } - else if (!pet_arg.compare("earth")) { + else if (!arg1.compare("air")) { pet_type = 3; - level_req = 5; } - else if (!pet_arg.compare("monster")) { + else if (!arg1.compare("earth")) { pet_type = 4; - level_req = 30; + } + else if (!arg1.compare("monster")) { + pet_type = 5; + } + else if (!arg1.compare("epic")) { + if (!RuleB(Bots, AllowMagicianEpicPet)) { + c->Message(Chat::Yellow, "Epic pets are currently disabled for bots."); + return; + } + + pet_type = 6; + } + else if (!arg1.compare("current")) { + current_check = true; } - if (pet_type == 255) { - c->Message(Chat::White, "You must specify a pet [type: water | fire | air | earth | monster]"); + if (!current_check && pet_type == 255) { + c->Message( + Chat::Yellow, + fmt::format( + "You must specify a pet [type: auto | water | fire | air | earth | monster | epic]. Use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); return; } - std::list sbl; - if (ActionableBots::PopulateSBL(c, sep->arg[2], sbl, ab_mask, sep->arg[3]) == ActionableBots::ABT_None) - return; + const int ab_mask = ActionableBots::ABM_Type1; + std::string actionable_arg = sep->arg[ab_arg]; - uint16 class_mask = player_class_bitmasks[Class::Magician]; - ActionableBots::Filter_ByClasses(c, sbl, class_mask); - if (sbl.empty()) { - c->Message(Chat::White, "You have no spawned Magician bots"); + if (actionable_arg.empty()) { + actionable_arg = "target"; + } + + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + + if (ActionableBots::PopulateSBL(c, actionable_arg, sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { return; } - ActionableBots::Filter_ByMinLevel(c, sbl, level_req); - if (sbl.empty()) { - c->Message(Chat::White, "You have no spawned Magician bots capable of using this pet type: '%s'", pet_arg.c_str()); - return; - } + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + + std::string current_type; + uint16 reclaim_energy_id = RuleI(Bots, ReclaimEnergySpellID); + bool is_success = false; + uint16 success_count = 0; + Bot* first_found = nullptr; - uint16 reclaim_energy_id = 331; for (auto bot_iter : sbl) { - if (!bot_iter) + if (bot_iter->GetClass() != Class::Magician) { continue; + } + + if (!bot_iter->IsInGroupOrRaid(c)) { + continue; + } + + if (pet_type == 6 && RuleI(Bots, RequiredMagicianEpicPetItemID) > 0) { + bool has_item = bot_iter->HasBotItem(RuleI(Bots, RequiredMagicianEpicPetItemID)) != INVALID_INDEX; + + if (!has_item) { + c->Message( + Chat::Say, + fmt::format( + "{} says, 'I require {} to cast an epic pet which I do not currently possess.'", + bot_iter->GetCleanName(), + (database.GetItem(RuleI(Bots, RequiredMagicianEpicPetItemID)) ? database.CreateItemLink(RuleI(Bots, RequiredMagicianEpicPetItemID)) : "an item") + ).c_str() + ); + + continue; + } + } + + if (current_check) { + switch (bot_iter->GetPetChooserID()) { + case 0: + current_type = "auto"; + break; + case SumWater: + current_type = "water"; + break; + case SumFire: + current_type = "fire"; + break; + case SumAir: + current_type = "air"; + break; + case SumEarth: + current_type = "earth"; + break; + case MonsterSum: + current_type = "monster"; + break; + case SumMageMultiElement: + current_type = "epic"; + break; + default: + current_type = "null"; + break; + } + + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I'm currently summoning {} pets.'", + bot_iter->GetCleanName(), + current_type + ).c_str() + ); + + continue; + } + + uint8 air_min_level = 255; + uint8 fire_min_level = 255; + uint8 water_min_level = 255; + uint8 earth_min_level = 255; + uint8 monster_min_level = 255; + uint8 epic_min_level = 255; + std::list bot_spell_list = bot_iter->GetBotSpellsBySpellType(bot_iter, BotSpellTypes::Pet); + + for (const auto& s : bot_spell_list) { + if (!IsValidSpell(s.SpellId)) { + continue; + } + + if (!IsEffectInSpell(s.SpellId, SpellEffect::SummonPet)) { + continue; + } + + auto spell = spells[s.SpellId]; + + if (!strncmp(spell.teleport_zone, "SumWater", 8) && spell.classes[Class::Magician - 1] < water_min_level) { + water_min_level = spell.classes[Class::Magician - 1]; + } + else if (!strncmp(spell.teleport_zone, "SumFire", 7) && spell.classes[Class::Magician - 1] < fire_min_level) { + fire_min_level = spell.classes[Class::Magician - 1]; + } + else if (!strncmp(spell.teleport_zone, "SumAir", 6) && spell.classes[Class::Magician - 1] < air_min_level) { + air_min_level = spell.classes[Class::Magician - 1]; + } + else if (!strncmp(spell.teleport_zone, "SumEarth", 8) && spell.classes[Class::Magician - 1] < earth_min_level) { + earth_min_level = spell.classes[Class::Magician - 1]; + } + else if (!strncmp(spell.teleport_zone, "MonsterSum", 10) && spell.classes[Class::Magician - 1] < monster_min_level) { + monster_min_level = spell.classes[Class::Magician - 1]; + } + } + + uint8 min_level = std::min({ + water_min_level, + fire_min_level, + air_min_level, + earth_min_level, + monster_min_level + }); + + epic_min_level = RuleI(Bots, AllowMagicianEpicPetLevel); + + switch (pet_type) { + case 0: + level_req = min_level; + break; + case SumWater: + level_req = water_min_level; + break; + case SumFire: + level_req = fire_min_level; + break; + case SumAir: + level_req = air_min_level; + break; + case SumEarth: + level_req = earth_min_level; + break; + case MonsterSum: + level_req = monster_min_level; + break; + case SumMageMultiElement: + level_req = epic_min_level; + break; + default: + break; + } + + if (bot_iter->GetLevel() < level_req) { + continue; + } - bot_iter->SetPetChooser(true); bot_iter->SetPetChooserID(pet_type); + if (bot_iter->GetPet()) { auto pet_id = bot_iter->GetPetID(); bot_iter->SetPetID(0); bot_iter->CastSpell(reclaim_energy_id, pet_id); } + + if (!first_found) { + first_found = bot_iter; + } + + is_success = true; + ++success_count; + } + + if (current_check) { + return; + } + + if (!is_success) { + c->Message(Chat::Yellow, "No bots were selected."); + + return; + } + + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I will now summon {} pets.'", + first_found->GetCleanName(), + current_type + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots will now summon {} pets.", + success_count, + arg1 + ).c_str() + ); } } diff --git a/zone/bot_commands/pick_lock.cpp b/zone/bot_commands/pick_lock.cpp index 7c4379458..7815833ee 100644 --- a/zone/bot_commands/pick_lock.cpp +++ b/zone/bot_commands/pick_lock.cpp @@ -11,7 +11,7 @@ void bot_command_pick_lock(Client *c, const Seperator *sep) return; } - std::list sbl; + std::vector sbl; MyBots::PopulateSBL_BySpawnedBots(c, sbl); float pick_lock_value = 0.0f; @@ -24,7 +24,7 @@ void bot_command_pick_lock(Client *c, const Seperator *sep) Bot* my_bot = sbl.front(); my_bot->InterruptSpell(); - Bot::BotGroupSay(my_bot, "Attempting to pick the lock."); + my_bot->RaidGroupSay("Attempting to pick the lock."); std::list door_list; entity_list.GetDoorsList(door_list); @@ -51,7 +51,7 @@ void bot_command_pick_lock(Client *c, const Seperator *sep) ++open_count; } else { - Bot::BotGroupSay(my_bot, "I am not skilled enough for this lock."); + my_bot->RaidGroupSay("I am not skilled enough for this lock."); } } c->Message(Chat::White, "%i door%s attempted - %i door%s successful", door_count, ((door_count != 1) ? ("s") : ("")), open_count, ((open_count != 1) ? ("s") : (""))); diff --git a/zone/bot_commands/pickpocket.cpp b/zone/bot_commands/pickpocket.cpp index 5aec32383..8b5339cca 100644 --- a/zone/bot_commands/pickpocket.cpp +++ b/zone/bot_commands/pickpocket.cpp @@ -15,7 +15,7 @@ void bot_command_pickpocket(Client *c, const Seperator *sep) return; } - std::list sbl; + std::vector sbl; MyBots::PopulateSBL_BySpawnedBots(c, sbl); // Check for capable rogue diff --git a/zone/bot_commands/precombat.cpp b/zone/bot_commands/precombat.cpp index 86ac7569a..a059e8eb1 100644 --- a/zone/bot_commands/precombat.cpp +++ b/zone/bot_commands/precombat.cpp @@ -5,15 +5,22 @@ void bot_command_precombat(Client* c, const Seperator* sep) if (helper_command_alias_fail(c, "bot_command_precombat", sep->arg[0], "precombat")) { return; } - if (helper_is_help_or_usage(sep->arg[1])) { + if (helper_is_help_or_usage(sep->arg[1])) { c->Message(Chat::White, "usage: %s ([set | clear])", sep->arg[0]); + return; } if (!c->GetTarget() || !c->IsAttackAllowed(c->GetTarget())) { - c->Message(Chat::White, "This command requires an attackable target."); + + return; + } + + if (RuleB(Bots, BotsRequireLoS) && !c->DoLosChecks(c->GetTarget())) { + c->Message(Chat::Red, "You must have Line of Sight to use this command."); + return; } diff --git a/zone/bot_commands/pull.cpp b/zone/bot_commands/pull.cpp index 6265d141e..6bab8b0a6 100644 --- a/zone/bot_commands/pull.cpp +++ b/zone/bot_commands/pull.cpp @@ -5,36 +5,69 @@ void bot_command_pull(Client *c, const Seperator *sep) if (helper_command_alias_fail(c, "bot_command_pull", sep->arg[0], "pull")) { return; } + if (helper_is_help_or_usage(sep->arg[1])) { - - c->Message(Chat::White, "usage: %s", sep->arg[0]); + c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); return; } - int ab_mask = ActionableBots::ABM_OwnerGroup; // existing behavior - need to add c->IsGrouped() check and modify code if different behavior is desired - std::list sbl; - if (ActionableBots::PopulateSBL(c, "ownergroup", sbl, ab_mask) == ActionableBots::ABT_None) { + const int ab_mask = ActionableBots::ABM_Type1; + + std::string arg1 = sep->arg[1]; + int ab_arg = 1; + std::string actionable_arg = sep->arg[ab_arg]; + + if (actionable_arg.empty()) { + actionable_arg = "spawned"; + } + + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + + if (ActionableBots::PopulateSBL(c, actionable_arg, sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { return; } - sbl.remove(nullptr); - auto target_mob = ActionableTarget::VerifyEnemy(c, BCEnum::TT_Single); - if (!target_mob) { + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + auto target_mob = c->GetTarget(); + + if ( + !target_mob || + target_mob->IsOfClientBotMerc() || + !c->IsAttackAllowed(target_mob) + ) { c->Message(Chat::White, "Your current target is not attackable!"); + + return; + } + + if (RuleB(Bots, BotsRequireLoS) && !c->DoLosChecks(target_mob)) { + c->Message(Chat::Red, "You must have Line of Sight to use this command."); + return; } if (target_mob->IsNPC() && target_mob->GetHateList().size()) { - c->Message(Chat::White, "Your current target is already engaged!"); + return; } Bot* bot_puller = nullptr; - for (auto bot_iter : sbl) { + Bot* backup_bot_puller = nullptr; + Bot* alternate_bot_puller = nullptr; + bool backup_puller_found = false; + bool alternate_puller_found = false; - if (bot_iter->GetAppearance() == eaDead || bot_iter->GetBotStance() == Stance::Passive) { + for (auto bot_iter : sbl) { + if (!bot_iter->ValidStateCheck(c)) { continue; } @@ -43,72 +76,37 @@ void bot_command_pull(Client *c, const Seperator *sep) case Class::Monk: case Class::Bard: case Class::Ranger: - bot_puller = bot_iter; - break; - case Class::Warrior: - case Class::ShadowKnight: - case Class::Paladin: - case Class::Berserker: - case Class::Beastlord: - if (!bot_puller) { + bot_iter->SetPullFlag(); - bot_puller = bot_iter; - continue; - } - - switch (bot_puller->GetClass()) { - case Class::Druid: - case Class::Shaman: - case Class::Cleric: - case Class::Wizard: - case Class::Necromancer: - case Class::Magician: - case Class::Enchanter: - bot_puller = bot_iter; - default: - continue; - } - - continue; - case Class::Druid: - case Class::Shaman: - case Class::Cleric: - if (!bot_puller) { - - bot_puller = bot_iter; - continue; - } - - switch (bot_puller->GetClass()) { - case Class::Wizard: - case Class::Necromancer: - case Class::Magician: - case Class::Enchanter: - bot_puller = bot_iter; - default: - continue; - } - - continue; - case Class::Wizard: - case Class::Necromancer: - case Class::Magician: - case Class::Enchanter: - if (!bot_puller) { - bot_puller = bot_iter; - } - - continue; + return; default: - continue; + break; } + if (!backup_puller_found) { + switch (bot_iter->GetClass()) { + case Class::Warrior: + case Class::ShadowKnight: + case Class::Paladin: + case Class::Berserker: + case Class::Beastlord: + backup_bot_puller = bot_iter; + backup_puller_found = true; - bot_puller = bot_iter; + break; + default: + break; + } + } - break; + if (!backup_puller_found && !alternate_puller_found) { + alternate_bot_puller = bot_iter; + alternate_puller_found = true; + } } + bot_puller = backup_bot_puller ? backup_bot_puller : alternate_bot_puller; + if (bot_puller) { bot_puller->SetPullFlag(); } diff --git a/zone/bot_commands/release.cpp b/zone/bot_commands/release.cpp index 1872d43d6..36f5df96a 100644 --- a/zone/bot_commands/release.cpp +++ b/zone/bot_commands/release.cpp @@ -5,18 +5,24 @@ void bot_command_release(Client *c, const Seperator *sep) if (helper_command_alias_fail(c, "bot_command_release", sep->arg[0], "release")) return; if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s ([actionable: ] ([actionable_name]))", sep->arg[0]); + c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationmembers | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); return; } const int ab_mask = ActionableBots::ABM_NoFilter; - std::list sbl; + std::vector sbl; if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, sep->arg[2]) == ActionableBots::ABT_None) return; - sbl.remove(nullptr); + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); for (auto bot_iter : sbl) { bot_iter->WipeHateList(); + + if (bot_iter->GetPet() && bot_iter->HasControllablePet(BotAnimEmpathy::BackOff)) { + bot_iter->GetPet()->WipeHateList(); + bot_iter->GetPet()->SetTarget(nullptr); + } + bot_iter->SetPauseAI(false); } diff --git a/zone/bot_commands/resistance.cpp b/zone/bot_commands/resistance.cpp deleted file mode 100644 index 0b84b4b88..000000000 --- a/zone/bot_commands/resistance.cpp +++ /dev/null @@ -1,73 +0,0 @@ -#include "../bot_command.h" - -void bot_command_resistance(Client *c, const Seperator *sep) -{ - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Resistance]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Resistance) || helper_command_alias_fail(c, "bot_command_resistance", sep->arg[0], "resistance")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: () %s [resistance: fire | cold | poison | disease | magic | corruption]", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Resistance); - return; - } - - std::string resistance_arg = sep->arg[1]; - - auto resistance_type = BCEnum::RT_None; - if (!resistance_arg.compare("fire")) - resistance_type = BCEnum::RT_Fire; - else if (!resistance_arg.compare("cold")) - resistance_type = BCEnum::RT_Cold; - else if (!resistance_arg.compare("poison")) - resistance_type = BCEnum::RT_Poison; - else if (!resistance_arg.compare("disease")) - resistance_type = BCEnum::RT_Disease; - else if (!resistance_arg.compare("magic")) - resistance_type = BCEnum::RT_Magic; - else if (!resistance_arg.compare("corruption")) - resistance_type = BCEnum::RT_Corruption; - - if (resistance_type == BCEnum::RT_None) { - c->Message(Chat::White, "You must specify a [resistance]"); - return; - } - - local_list->sort([resistance_type](STBaseEntry* l, STBaseEntry* r) { - auto _l = l->SafeCastToResistance(), _r = r->SafeCastToResistance(); - if (_l->resist_value[RESISTANCEIDTOINDEX(resistance_type)] > _r->resist_value[RESISTANCEIDTOINDEX(resistance_type)]) - return true; - if (_l->resist_value[RESISTANCEIDTOINDEX(resistance_type)] == _r->resist_value[RESISTANCEIDTOINDEX(resistance_type)] && spells[_l->spell_id].mana < spells[_r->spell_id].mana) - return true; - if (_l->resist_value[RESISTANCEIDTOINDEX(resistance_type)] == _r->resist_value[RESISTANCEIDTOINDEX(resistance_type)] && spells[_l->spell_id].mana == spells[_r->spell_id].mana && _l->resist_total > _r->resist_total) - return true; - - return false; - }); - - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - bool cast_success = false; - for (auto list_iter : *local_list) { - auto local_entry = list_iter->SafeCastToResistance(); - if (helper_spell_check_fail(local_entry)) - continue; - if (!local_entry->resist_value[RESISTANCEIDTOINDEX(resistance_type)]) - continue; - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); - if (!target_mob) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; - - cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); - break; - } - - helper_no_available_bots(c, my_bot); -} diff --git a/zone/bot_commands/resurrect.cpp b/zone/bot_commands/resurrect.cpp deleted file mode 100644 index 0b15ef3e1..000000000 --- a/zone/bot_commands/resurrect.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include "../bot_command.h" - -void bot_command_resurrect(Client *c, const Seperator *sep) -{ - // Obscure bot spell code prohibits the aoe portion from working correctly... - - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Resurrect]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Resurrect) || helper_command_alias_fail(c, "bot_command_resurrect", sep->arg[0], "resurrect")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - //c->Message(Chat::White, "usage: %s ([option: aoe])", sep->arg[0]); - c->Message(Chat::White, "usage: %s", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Resurrect); - return; - } - - bool aoe = false; - //std::string aoe_arg = sep->arg[1]; - //if (!aoe_arg.compare("aoe")) - // aoe = true; - - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - for (auto list_iter : *local_list) { - auto local_entry = list_iter->SafeCastToResurrect(); - if (helper_spell_check_fail(local_entry)) - continue; - //if (local_entry->aoe != aoe) - // continue; - if (local_entry->aoe) - continue; - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); - //if (!target_mob && !local_entry->aoe) - // continue; - if (!target_mob) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; - - //if (local_entry->aoe) - // target_mob = my_bot; - - uint32 dont_root_before = 0; - if (helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id, true, &dont_root_before)) - target_mob->SetDontRootMeBefore(dont_root_before); - - break; - } - - helper_no_available_bots(c, my_bot); -} diff --git a/zone/bot_commands/rune.cpp b/zone/bot_commands/rune.cpp deleted file mode 100644 index 71b1cf572..000000000 --- a/zone/bot_commands/rune.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "../bot_command.h" - -void bot_command_rune(Client *c, const Seperator *sep) -{ - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Rune]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Rune) || helper_command_alias_fail(c, "bot_command_rune", sep->arg[0], "rune")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: () %s", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Rune); - return; - } - - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - bool cast_success = false; - for (auto list_iter : *local_list) { - auto local_entry = list_iter; - if (helper_spell_check_fail(local_entry)) - continue; - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); - if (!target_mob) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; - - cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); - break; - } - - helper_no_available_bots(c, my_bot); -} diff --git a/zone/bot_commands/send_home.cpp b/zone/bot_commands/send_home.cpp deleted file mode 100644 index 6950e2bee..000000000 --- a/zone/bot_commands/send_home.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "../bot_command.h" - -void bot_command_send_home(Client *c, const Seperator *sep) -{ - // Obscure bot spell code prohibits the aoe portion from working correctly... - - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_SendHome]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_SendHome) || helper_command_alias_fail(c, "bot_command_send_home", sep->arg[0], "sendhome")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: () %s ([option: group])", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_SendHome); - return; - } - - bool group = false; - std::string group_arg = sep->arg[1]; - if (!group_arg.compare("group")) - group = true; - - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - bool cast_success = false; - for (auto list_iter : *local_list) { - auto local_entry = list_iter->SafeCastToSendHome(); - if (helper_spell_check_fail(local_entry)) - continue; - if (local_entry->group != group) - continue; - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); - if (!target_mob) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; - - cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); - break; - } - - helper_no_available_bots(c, my_bot); -} diff --git a/zone/bot_commands/set_assistee.cpp b/zone/bot_commands/set_assistee.cpp new file mode 100644 index 000000000..f5a1845fe --- /dev/null +++ b/zone/bot_commands/set_assistee.cpp @@ -0,0 +1,42 @@ +#include "../bot_command.h" + +void bot_command_set_assistee(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_set_assistee", sep->arg[0], "setassistee")) { + c->Message(Chat::White, "note: Sets your bots to assist your target in addition to yourself."); + + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + BotCommandHelpParams p; + + p.description = { "Sets your bots to assist your target in addition to yourself." }; + p.notes = + { + "- Your target must be another player in your group or raid.", + "- This needs to be set on every zone/camp you do.", + "- If a Raid or Group assist is set and you do not want your bots to auto assist that person, set yourself as the assistee." + }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + + return; + } + + Mob* assistee = c->GetTarget(); + + if (assistee && assistee->IsClient() && c->IsInGroupOrRaid(assistee)) { + c->SetAssistee(assistee->CastToClient()->CharacterID()); + c->Message(Chat::Green, "Your bots will now assist %s.", assistee->GetCleanName()); + + return; + } + + c->Message(Chat::Yellow, "You can only set your bots to assist clients that are in your group or raid."); + + return; +} diff --git a/zone/bot_commands/sit_hp_percent.cpp b/zone/bot_commands/sit_hp_percent.cpp new file mode 100644 index 000000000..0843aa5b9 --- /dev/null +++ b/zone/bot_commands/sit_hp_percent.cpp @@ -0,0 +1,156 @@ +#include "../bot_command.h" + +void bot_command_sit_hp_percent(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_sit_hp_percent", sep->arg[0], "sithppercent")) { + c->Message(Chat::White, "note: HP % threshold when bots will sit in combat if allowed."); + + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + BotCommandHelpParams p; + + p.description = { "HP % threshold when bots will sit in combat if allowed." }; + p.example_format = { fmt::format("{} [value] [actionable]", sep->arg[0]) }; + p.examples_one = + { + "To set Clerics to sit at 45% HP:", + fmt::format( + "{} 45 byclass {}", + sep->arg[0], + Class::Cleric + ) + }; + p.examples_two = + { + "To set all bots to sit at 50% HP:", + fmt::format( + "{} 50 spawned", + sep->arg[0] + ) + }; + p.examples_three = + { + "To check the HP threshold for all bots:", + fmt::format( + "{} current spawned", + sep->arg[0] + ) + }; + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + + return; + } + + std::string arg1 = sep->arg[1]; + + int ab_arg = 1; + bool current_check = false; + uint32 type_value = 0; + + if (sep->IsNumber(1)) { + type_value = atoi(sep->arg[1]); + ++ab_arg; + if (type_value < 0 || type_value > 100) { + c->Message(Chat::Yellow, "You must enter a value between 0-100 (0%% to 100%% of health)."); + + return; + } + } + else if (!arg1.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + + if (!first_found) { + first_found = my_bot; + } + + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I sit in combat whem at or below [{}%%] HP.'", + my_bot->GetCleanName(), + my_bot->GetSitHPPct() + ).c_str() + ); + } + else { + my_bot->SetSitHPPct(type_value); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I will now sit in combat whem at or below [{}%%] HP.'", + first_found->GetCleanName(), + first_found->GetSitHPPct() + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots will now sit in combat whem at or below [{}%%] HP.'", + success_count, + type_value + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/sit_in_combat.cpp b/zone/bot_commands/sit_in_combat.cpp new file mode 100644 index 000000000..07a2f917e --- /dev/null +++ b/zone/bot_commands/sit_in_combat.cpp @@ -0,0 +1,156 @@ +#include "../bot_command.h" + +void bot_command_sit_in_combat(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_sit_in_combat", sep->arg[0], "sitincombat")) { + c->Message(Chat::White, "note: Toggles whether or not bots will sit in combat to heal or med."); + + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + BotCommandHelpParams p; + + p.description = { "Toggles whether or not bots will sit in combat to heal or med." }; + p.example_format = { fmt::format("{} [value] [actionable]", sep->arg[0]) }; + p.examples_one = + { + "To set Clerics to sit in combat:", + fmt::format( + "{} 1 byclass {}", + sep->arg[0], + Class::Cleric + ) + }; + p.examples_two = + { + "To set all bots to sit/med in combat:", + fmt::format( + "{} 1 spawned", + sep->arg[0] + ) + }; + p.examples_three = + { + "To check the sit in combat state for all bots:", + fmt::format( + "{} current spawned", + sep->arg[0] + ) + }; + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + + return; + } + + std::string arg1 = sep->arg[1]; + + int ab_arg = 1; + bool current_check = false; + uint32 type_value = 0; + + if (sep->IsNumber(1)) { + type_value = atoi(sep->arg[1]); + ++ab_arg; + if (type_value < 0 || type_value > 1) { + c->Message(Chat::Yellow, "You must enter either 0 for disabled or 1 for enabled."); + + return; + } + } + else if (!arg1.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + + if (!first_found) { + first_found = my_bot; + } + + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I {} sit in combat.'", + my_bot->GetCleanName(), + my_bot->GetMedInCombat() ? "will" : "will not" + ).c_str() + ); + } + else { + my_bot->SetMedInCombat(type_value); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I {} sit in combat.'", + first_found->GetCleanName(), + first_found->GetMedInCombat() ? "will now" : "will no longer" + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots {} sit in combat.", + success_count, + type_value ? "will now" : "will no longer" + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/sit_mana_percent.cpp b/zone/bot_commands/sit_mana_percent.cpp new file mode 100644 index 000000000..e85d976c8 --- /dev/null +++ b/zone/bot_commands/sit_mana_percent.cpp @@ -0,0 +1,156 @@ +#include "../bot_command.h" + +void bot_command_sit_mana_percent(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_sit_mana_percent", sep->arg[0], "sitmanapercent")) { + c->Message(Chat::White, "note: Mana % threshold when bots will sit in combat if allowed."); + + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + BotCommandHelpParams p; + + p.description = { "Mana % threshold when bots will sit in combat if allowed." }; + p.example_format = { fmt::format("{} [value] [actionable]", sep->arg[0]) }; + p.examples_one = + { + "To set Clerics to sit at 45% Mana:", + fmt::format( + "{} 45 byclass {}", + sep->arg[0], + Class::Cleric + ) + }; + p.examples_two = + { + "To set all bots to sit at 50% Mana:", + fmt::format( + "{} 50 spawned", + sep->arg[0] + ) + }; + p.examples_three = + { + "To check the Mana threshold for all bots:", + fmt::format( + "{} current spawned", + sep->arg[0] + ) + }; + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + + return; + } + + std::string arg1 = sep->arg[1]; + + int ab_arg = 1; + bool current_check = false; + uint32 type_value = 0; + + if (sep->IsNumber(1)) { + type_value = atoi(sep->arg[1]); + ++ab_arg; + if (type_value < 0 || type_value > 100) { + c->Message(Chat::Yellow, "You must enter a value between 0-100 (0%% to 100%% of health)."); + + return; + } + } + else if (!arg1.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + + if (!first_found) { + first_found = my_bot; + } + + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I sit in combat whem at or below [{}%%] mana.'", + my_bot->GetCleanName(), + my_bot->GetSitManaPct() + ).c_str() + ); + } + else { + my_bot->SetSitManaPct(type_value); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I will now sit in combat whem at or below [{}%%] mana.'", + first_found->GetCleanName(), + first_found->GetSitManaPct() + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots will now sit in combat whem at or below [{}%%] mana.'", + success_count, + type_value + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/size.cpp b/zone/bot_commands/size.cpp deleted file mode 100644 index 69e2fd1a2..000000000 --- a/zone/bot_commands/size.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "../bot_command.h" - -void bot_command_size(Client *c, const Seperator *sep) -{ - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Size]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Size) || helper_command_alias_fail(c, "bot_command_size", sep->arg[0], "size")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: () %s [grow | shrink]", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Size); - return; - } - - std::string size_arg = sep->arg[1]; - auto size_type = BCEnum::SzT_Reduce; - if (!size_arg.compare("grow")) { - size_type = BCEnum::SzT_Enlarge; - } - else if (size_arg.compare("shrink")) { - c->Message(Chat::White, "This command requires a [grow | shrink] argument"); - return; - } - - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - bool cast_success = false; - for (auto list_iter : *local_list) { - auto local_entry = list_iter->SafeCastToSize(); - if (helper_spell_check_fail(local_entry)) - continue; - if (local_entry->size_type != size_type) - continue; - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); - if (!target_mob) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; - - cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); - break; - } - - helper_no_available_bots(c, my_bot); -} diff --git a/zone/bot_commands/spell.cpp b/zone/bot_commands/spell.cpp index 45d9ec6eb..833c8ef7a 100644 --- a/zone/bot_commands/spell.cpp +++ b/zone/bot_commands/spell.cpp @@ -503,7 +503,7 @@ void bot_spell_info_dialogue_window(Client* c, const Seperator *sep) auto results = database.QueryDatabase( fmt::format( "SELECT value FROM db_str WHERE id = {} and type = 6 LIMIT 1", - spells[spell_id].effect_description_id + spells[spell_id].description_id ) ); @@ -557,7 +557,7 @@ void bot_command_enforce_spell_list(Client* c, const Seperator *sep) } bool enforce_state = (sep->argnum > 0) ? Strings::ToBool(sep->arg[1]) : !my_bot->GetBotEnforceSpellSetting(); - my_bot->SetBotEnforceSpellSetting(enforce_state, true); + my_bot->SetBotEnforceSpellSetting(enforce_state); c->Message( Chat::White, diff --git a/zone/bot_commands/spell_aggro_checks.cpp b/zone/bot_commands/spell_aggro_checks.cpp new file mode 100644 index 000000000..41ae40444 --- /dev/null +++ b/zone/bot_commands/spell_aggro_checks.cpp @@ -0,0 +1,222 @@ +#include "../bot_command.h" + +void bot_command_spell_aggro_checks(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_spell_aggro_checks", sep->arg[0], "spellaggrochecks")) { + c->Message(Chat::White, "note: Toggles whether or not bots will cast a spell type if they think it will get them aggro."); + + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + BotCommandHelpParams p; + + p.description = { "Toggles whether or not bots will cast a spell type if they think it will get them aggro." }; + p.example_format = + { + fmt::format("{} [Type Shortname] [value] [actionable]", sep->arg[0]), + fmt::format("{} [Type ID] [value] [actionable]", sep->arg[0]) + }; + p.examples_one = + { + "To set all bots to check aggro on nukes:", + fmt::format( + "{} {} 1 spawned", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::Nuke) + ), + fmt::format( + "{} {} 1 spawned", + sep->arg[0], + BotSpellTypes::Nuke + ) + }; + p.examples_two = + { + "To set Shadowknights to ignore aggro checks on snares:", + fmt::format( + "{} {} 0 byclass {}", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::Snare), + Class::ShadowKnight + ), + fmt::format( + "{} {} 0 byclass {}", + sep->arg[0], + BotSpellTypes::Snare, + Class::ShadowKnight + ) + }; + p.examples_three = + { + "To check the current DoT aggro check on all bots:", + fmt::format( + "{} {} current spawned", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::DOT) + ), + fmt::format( + "{} {} current spawned", + sep->arg[0], + BotSpellTypes::DOT + ) + }; + + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->SendSpellTypePrompts(); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + + return; + } + + std::string arg1 = sep->arg[1]; + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool current_check = false; + uint16 spell_type = 0; + uint32 type_value = 0; + + // String/Int type checks + if (sep->IsNumber(1)) { + spell_type = atoi(sep->arg[1]); + + if (!EQ::ValueWithin(spell_type, BotSpellTypes::START, BotSpellTypes::END)) { + c->Message( + Chat::Yellow, + fmt::format( + "You must choose a valid spell type. Use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + else { + if (Bot::GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { + spell_type = Bot::GetSpellTypeIDByShortName(arg1); + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + if (sep->IsNumber(2)) { + type_value = atoi(sep->arg[2]); + ++ab_arg; + if (type_value < 0 || type_value > 1) { + c->Message(Chat::Yellow, "You must enter either 0 for disabled or 1 for enabled."); + + return; + } + } + else if (!arg2.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + + if (!first_found) { + first_found = my_bot; + } + + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] aggro check is currently [{}].'", + my_bot->GetCleanName(), + Bot::GetSpellTypeNameByID(spell_type), + my_bot->GetSpellTypeAggroCheck(spell_type) ? "enabled" : "disabled" + ).c_str() + ); + } + else { + my_bot->SetSpellTypeAggroCheck(spell_type, type_value); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] aggro check was [{}].'", + first_found->GetCleanName(), + Bot::GetSpellTypeNameByID(spell_type), + first_found->GetSpellTypeAggroCheck(spell_type) ? "enabled" : "disabled" + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots [{}] their [{}] aggro check.", + success_count, + Bot::GetSpellTypeNameByID(spell_type), + type_value ? "enabled" : "disabled" + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/spell_announce_cast.cpp b/zone/bot_commands/spell_announce_cast.cpp new file mode 100644 index 000000000..482599724 --- /dev/null +++ b/zone/bot_commands/spell_announce_cast.cpp @@ -0,0 +1,214 @@ +#include "../bot_command.h" + +void bot_command_spell_announce_cast(Client* c, const Seperator* sep) { + if (helper_command_alias_fail(c, "bot_command_spell_announce_cast", sep->arg[0], "spellannouncecasts")) { + c->Message(Chat::White, "note: Allows you to enable or disable cast announcements for bots by spell type."); + + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + BotCommandHelpParams p; + + p.description = { "Allows you to enable or disable cast announcements for bots by spell type." }; + p.example_format = + { + fmt::format("{} [Type Shortname] [value] [actionable]", sep->arg[0]), + fmt::format("{} [Type ID] [value] [actionable]", sep->arg[0]) + }; + p.examples_one = + { + "To set all bots to stop announcing dispels:", + fmt::format( + "{} {} 0 spawned", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::Dispel) + ), + fmt::format( + "{} {} 0 spawned", + sep->arg[0], + BotSpellTypes::Dispel + ) + }; + p.examples_two = + { + "To set Wizards to not announce nukes:", + fmt::format( + "{} {} 0 byclass {}", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::Nuke), + Class::Wizard + ), + fmt::format( + "{} {} 0 byclass {}", + sep->arg[0], + BotSpellTypes::Nuke, + Class::Wizard + + ) + }; + p.examples_three = + { + "To check the current announcement setting for debuffs:", + fmt::format( + "{} {} current spawned", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::Debuff) + ), + fmt::format( + "{} {} current spawned", + sep->arg[0], + BotSpellTypes::Debuff + ) + }; + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->SendSpellTypePrompts(); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + + return; + } + + std::string arg1 = sep->arg[1]; + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool current_check = false; + uint16 spell_type = 0; + uint32 type_value = 0; + + // String/Int type checks + if (sep->IsNumber(1)) { + spell_type = atoi(sep->arg[1]); + + if (!EQ::ValueWithin(spell_type, BotSpellTypes::START, BotSpellTypes::END)) { + c->Message(Chat::Yellow, "You must choose a valid spell type. Spell types range from %i to %i", BotSpellTypes::START, BotSpellTypes::END); + + return; + } + } + else { + if (Bot::GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { + spell_type = Bot::GetSpellTypeIDByShortName(arg1); + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + if (sep->IsNumber(2)) { + type_value = atoi(sep->arg[2]); + ++ab_arg; + + if (type_value != 0 && type_value != 1) { + c->Message(Chat::Yellow, "You must enter either 0 for [Disabled] or 1 for [Enabled]."); + + return; + } + } + else if (!arg2.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + + if (!first_found) { + first_found = my_bot; + } + + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I currently {} announce [{}] casts.'", + my_bot->GetCleanName(), + (my_bot->GetSpellTypeAnnounceCast(spell_type) ? "do" : "do not"), + Bot::GetSpellTypeNameByID(spell_type) + ).c_str() + ); + } + else { + my_bot->SetSpellTypeAnnounceCast(spell_type, type_value); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'I will {} announce [{}] casts.'", + first_found->GetCleanName(), + (first_found->GetSpellTypeAnnounceCast(spell_type) ? "now" : "no longer"), + Bot::GetSpellTypeNameByID(spell_type) + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots will {} announce [{}] casts.", + success_count, + (type_value ? "now" : "no longer"), + Bot::GetSpellTypeNameByID(spell_type) + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/spell_delays.cpp b/zone/bot_commands/spell_delays.cpp new file mode 100644 index 000000000..98efb3f22 --- /dev/null +++ b/zone/bot_commands/spell_delays.cpp @@ -0,0 +1,282 @@ +#include "../bot_command.h" + +void bot_command_spell_delays(Client* c, const Seperator* sep) { + if (helper_command_alias_fail(c, "bot_command_spell_delays", sep->arg[0], "spelldelays")) { + c->Message(Chat::White, "note: Controls how long a bot will wait between casts of different spell types."); + + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + BotCommandHelpParams p; + + p.description = { "Controls how long a bot will wait between casts of different spell types." }; + p.notes = + { + "- Targeting yourself for this command will allow you to control your own settings for how bots cast on you", + "- All pet types are based off the pet's owner's setting", + "- Any remaining types use the owner's setting when a pet is the target", + "- All Heals, Cures, Buffs (DS and resists included) are based off the target's setting, not the caster", + "- e.g., BotA is healing BotB using BotB's settings" + }; + p.example_format = + { + fmt::format("{} [Type Shortname] [value] [actionable, default: target]", sep->arg[0]), + fmt::format("{} [Type ID] [value] [actionable, default: target]", sep->arg[0]) + }; + p.examples_one = + { + "To set all Necromancers to an 8s DoT delay:", + fmt::format( + "{} {} 8000 byclass {}", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::DOT), + Class::Necromancer + ), + fmt::format( + "{} {} 8000 byclass {}", + sep->arg[0], + BotSpellTypes::DOT, + Class::Necromancer + ) + }; + p.examples_two = + { + "To set all Warriors to receive Fast Heals every 2.5s:", + fmt::format( + "{} {} 2500 byclass {}", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::FastHeals), + Class::Warrior + ), + fmt::format( + "{} {} 2500 byclass {}", + sep->arg[0], + BotSpellTypes::FastHeals, + Class::Warrior + ) + }; + p.examples_three = + { + "To check the current Nuke delay on all bots:", + fmt::format( + "{} {} current spawned", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::Nuke) + ), + fmt::format( + "{} {} current spawned", + sep->arg[0], + BotSpellTypes::Nuke + ) + }; + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->SendSpellTypePrompts(); + c->SendSpellTypePrompts(false, true); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + + return; + } + + std::string arg1 = sep->arg[1]; + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool current_check = false; + uint16 spell_type = 0; + uint32 type_value = 0; + Mob* target = c->GetTarget(); + bool clientSetting = (target && target == c); + + if (sep->IsNumber(1)) { + spell_type = atoi(sep->arg[1]); + + if ( + (clientSetting && !IsClientBotSpellType(spell_type)) || + (!clientSetting && !EQ::ValueWithin(spell_type, BotSpellTypes::START, BotSpellTypes::END)) + ) { + c->Message( + Chat::Yellow, + fmt::format( + "{}. Use {} for information regarding this command.", + (!clientSetting ? "You must choose a valid spell type" : "You must choose a valid client spell type"), + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + if (clientSetting) { + c->SendSpellTypePrompts(false, true); + } + + return; + } + } + else { + if (Bot::GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { + spell_type = Bot::GetSpellTypeIDByShortName(arg1); + + if (clientSetting && !IsClientBotSpellType(spell_type)) { + c->Message(Chat::Yellow, "Invalid spell type for clients."); + c->SendSpellTypePrompts(false, true); + } + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + if (sep->IsNumber(2)) { + type_value = atoi(sep->arg[2]); + ++ab_arg; + if (type_value < 100 || type_value > 60000) { + c->Message(Chat::Yellow, "You must enter a value between 100-60000 (100ms to 60s)."); + + return; + } + } + else if (!arg2.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string actionable_arg = sep->arg[ab_arg]; + + if (actionable_arg.empty()) { + actionable_arg = "target"; + } + + if (!clientSetting) { + + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + + if (ActionableBots::PopulateSBL(c, actionable_arg, sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + + Bot* first_found = nullptr; + int success_count = 0; + + for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + + if (!first_found) { + first_found = my_bot; + } + + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] spell delay is currently [{}] seconds.'", + my_bot->GetCleanName(), + Bot::GetSpellTypeNameByID(spell_type), + my_bot->GetSpellTypeDelay(spell_type) / 1000.00 + ).c_str() + ); + } + else { + my_bot->SetSpellTypeDelay(spell_type, type_value); + ++success_count; + } + } + + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] spell delay was set to [{}] seconds.'", + first_found->GetCleanName(), + Bot::GetSpellTypeNameByID(spell_type), + first_found->GetSpellTypeDelay(spell_type) / 1000.00 + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots set their [{}] spell delay to [{}] seconds.", + success_count, + Bot::GetSpellTypeNameByID(spell_type), + type_value / 1000.00 + ).c_str() + ); + } + } + } + else { + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "Your [{}] spell delay is currently [{}] seconds.", + Bot::GetSpellTypeNameByID(spell_type), + c->GetSpellTypeDelay(spell_type) / 1000.00 + ).c_str() + ); + } + else { + c->SetSpellTypeDelay(spell_type, type_value); + + c->Message( + Chat::Green, + fmt::format( + "Your [{}] spell delay was set to [{}] seconds.", + Bot::GetSpellTypeNameByID(spell_type), + c->GetSpellTypeDelay(spell_type) / 1000.00 + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/spell_engaged_priority.cpp b/zone/bot_commands/spell_engaged_priority.cpp new file mode 100644 index 000000000..8f7c0afa9 --- /dev/null +++ b/zone/bot_commands/spell_engaged_priority.cpp @@ -0,0 +1,255 @@ +#include "../bot_command.h" + +void bot_command_spell_engaged_priority(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_spell_engaged_priority", sep->arg[0], "spellengagedpriority")) { + c->Message(Chat::White, "note: Sets the order of spell casts when engaged in combat by spell type."); + + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + BotCommandHelpParams p; + + p.description = { "Sets the order of spell casts when engaged in combat by spell type." }; + p.notes = + { + "- Setting a spell type to 0 will prevent that type from being cast.", + "- If 2 or more are set to the same priority they will sort by spell type ID." + }; + p.example_format = + { + fmt::format("{} [Type Shortname] [value] [actionable]", sep->arg[0]), + fmt::format("{} [Type ID] [value] [actionable]", sep->arg[0]) + }; + p.examples_one = + { + "To list your targeted bot's priorities:", + fmt::format( + "{} list", + sep->arg[0] + ) + }; + p.examples_two = + { + "To set all Shaman to cast slows first:", + fmt::format( + "{} {} 1 byclass {}", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::Slow), + Class::Shaman + ), + fmt::format( + "{} {} 1 byclass {}", + sep->arg[0], + BotSpellTypes::Slow, + Class::Shaman + ) + }; + p.examples_three = + { + "To check the current engaged priority of dispels on all bots:", + fmt::format( + "{} {} current spawned", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::Dispel) + ), + fmt::format( + "{} {} current spawned", + sep->arg[0], + BotSpellTypes::Dispel + ) + }; + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->SendSpellTypePrompts(); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + + return; + } + + std::string arg1 = sep->arg[1]; + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool current_check = false; + bool list_check = false; + uint16 spell_type = 0; + uint32 type_value = 0; + int ab_mask = ActionableBots::ABM_Type1; + + // String/Int type checks + if (sep->IsNumber(1)) { + spell_type = atoi(sep->arg[1]); + + if (!EQ::ValueWithin(spell_type, BotSpellTypes::START, BotSpellTypes::END)) { + c->Message(Chat::Yellow, "You must choose a valid spell type. Spell types range from %i to %i", BotSpellTypes::START, BotSpellTypes::END); + + return; + } + } + else { + if (!arg1.compare("list")) { + ++ab_arg; + list_check = true; + ab_mask = ActionableBots::ABM_Target; + + if ( + !c->GetTarget() || + !c->GetTarget()->IsBot() || + c->GetTarget()->GetOwner() != c + ) { + c->Message(Chat::Yellow, "You must target your own bot to list priorities."); + return; + } + } + else if (Bot::GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { + spell_type = Bot::GetSpellTypeIDByShortName(arg1); + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + if (sep->IsNumber(2)) { + type_value = atoi(sep->arg[2]); + ++ab_arg; + if (!EQ::ValueWithin(type_value, BotSpellTypes::START, BotSpellTypes::END)) { + c->Message(Chat::Yellow, "You must enter a value between %u and %u.", BotSpellTypes::START, BotSpellTypes::END); + + return; + } + } + else if (!arg2.compare("current")) { + ++ab_arg; + current_check = true; + } + else if (!list_check) { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + + if (!first_found) { + first_found = my_bot; + } + + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] engaged cast priority is currently [{}].'", + my_bot->GetCleanName(), + Bot::GetSpellTypeNameByID(spell_type), + my_bot->GetSpellTypePriority(spell_type, BotPriorityCategories::Engaged) + ).c_str() + ); + } + else if (list_check) { + auto cast_order = my_bot->GetSpellTypesPrioritized(BotPriorityCategories::Engaged); + + BotCommandHelpParams p; + p.description = { + "Anything not listed is currently disabled (0)", + "----------", + "Spell Type - Priority" + }; + p.notes = { }; + + for (auto& current_cast : cast_order) { + p.notes.push_back( + fmt::format( + "{}s - {}", + Bot::GetSpellTypeNameByID(current_cast.spellType), + (current_cast.priority == 0 ? "disabled (0)" : std::to_string(current_cast.priority)) + ).c_str() + ); + } + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + + return; + } + else { + my_bot->SetSpellTypePriority(spell_type, BotPriorityCategories::Engaged, type_value); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] engaged cast priority was set to [{}].'", + first_found->GetCleanName(), + Bot::GetSpellTypeNameByID(spell_type), + first_found->GetSpellTypePriority(spell_type, BotPriorityCategories::Engaged) + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots set their [{}] engaged cast priority to [{}].", + success_count, + Bot::GetSpellTypeNameByID(spell_type), + type_value + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/spell_holds.cpp b/zone/bot_commands/spell_holds.cpp new file mode 100644 index 000000000..26d42ad82 --- /dev/null +++ b/zone/bot_commands/spell_holds.cpp @@ -0,0 +1,193 @@ +#include "../bot_command.h" + +void bot_command_spell_holds(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_spell_holds", sep->arg[0], "spellholds")) { + c->Message(Chat::White, "note: Toggles whether or not bots can cast certain spell types."); + + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + BotCommandHelpParams p; + + p.description = { "Toggles whether or not bots can cast certain spell types." }; + p.notes = { "- All pet types are based off the pet owner's setting when a pet is the target" }; + p.example_format = + { + fmt::format("{} [Type Shortname] [value] [actionable]", sep->arg[0]), + fmt::format("{} [Type ID] [value] [actionable]", sep->arg[0]) + }; + p.examples_one = + { + "To set all bots to hold DoTs:", + fmt::format( + "{} {} 1 spawned", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::DOT) + ), + fmt::format( + "{} {} 1 spawned", + sep->arg[0], + BotSpellTypes::DOT + ) + }; + p.examples_two = + { + "To check the current DoT settings on all bots:", + fmt::format( + "{} {} current spawned", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::DOT) + ), + fmt::format( + "{} {} current spawned", + sep->arg[0], + BotSpellTypes::DOT + ) + }; + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->SendSpellTypePrompts(); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + + return; + } + + std::string arg1 = sep->arg[1]; + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool current_check = false; + uint16 spell_type = 0; + uint32 type_value = 0; + + // String/Int type checks + if (sep->IsNumber(1)) { + spell_type = atoi(sep->arg[1]); + + if (!EQ::ValueWithin(spell_type, BotSpellTypes::START, BotSpellTypes::END)) { + c->Message(Chat::Yellow, "You must choose a valid spell type. Spell types range from %i to %i", BotSpellTypes::START, BotSpellTypes::END); + + return; + } + } + else { + if (Bot::GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { + spell_type = Bot::GetSpellTypeIDByShortName(arg1); + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + if (sep->IsNumber(2)) { + type_value = atoi(sep->arg[2]); + ++ab_arg; + if (type_value < 0 || type_value > 1) { + c->Message(Chat::Yellow, "You must enter either 0 for disabled or 1 for enabled."); + + return; + } + } + else if (!arg2.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (!first_found) { + first_found = my_bot; + } + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] spell hold is currently [{}].'", + my_bot->GetCleanName(), + Bot::GetSpellTypeNameByID(spell_type), + my_bot->GetSpellTypeHold(spell_type) ? "enabled" : "disabled" + ).c_str() + ); + } + else { + my_bot->SetSpellTypeHold(spell_type, type_value); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] spell hold was [{}].'", + first_found->GetCleanName(), + Bot::GetSpellTypeNameByID(spell_type), + first_found->GetSpellTypeHold(spell_type) ? "enabled" : "disabled" + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots [{}] their [{}] spell hold.", + success_count, + type_value ? "enabled" : "disabled", + Bot::GetSpellTypeNameByID(spell_type) + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/spell_idle_priority.cpp b/zone/bot_commands/spell_idle_priority.cpp new file mode 100644 index 000000000..881fd5e98 --- /dev/null +++ b/zone/bot_commands/spell_idle_priority.cpp @@ -0,0 +1,255 @@ +#include "../bot_command.h" + +void bot_command_spell_idle_priority(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_spell_idle_priority", sep->arg[0], "spellidlepriority")) { + c->Message(Chat::White, "note: Sets the order of spell casts when not in combat by spell type."); + + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + BotCommandHelpParams p; + + p.description = { "Sets the order of spell casts when not in combat by spell type." }; + p.notes = + { + "- Setting a spell type to 0 will prevent that type from being cast.", + "- If 2 or more are set to the same priority they will sort by spell type ID." + }; + p.example_format = + { + fmt::format("{} [Type Shortname] [value] [actionable]", sep->arg[0]), + fmt::format("{} [Type ID] [value] [actionable]", sep->arg[0]) + }; + p.examples_one = + { + "To list your targeted bot's priorities:", + fmt::format( + "{} list", + sep->arg[0] + ) + }; + p.examples_two = + { + "To set all Clerics to cast fast heals third:", + fmt::format( + "{} {} 3 byclass {}", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::FastHeals), + Class::Cleric + ), + fmt::format( + "{} {} 3 byclass {}", + sep->arg[0], + BotSpellTypes::FastHeals, + Class::Cleric + ) + }; + p.examples_three = + { + "To check the current idle priority of buffs on all bots:", + fmt::format( + "{} {} current spawned", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::Buff) + ), + fmt::format( + "{} {} current spawned", + sep->arg[0], + BotSpellTypes::Buff + ) + }; + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->SendSpellTypePrompts(); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + + return; + } + + std::string arg1 = sep->arg[1]; + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool current_check = false; + bool list_check = false; + uint16 spell_type = 0; + uint32 type_value = 0; + int ab_mask = ActionableBots::ABM_Type1; + + // String/Int type checks + if (sep->IsNumber(1)) { + spell_type = atoi(sep->arg[1]); + + if (!EQ::ValueWithin(spell_type, BotSpellTypes::START, BotSpellTypes::END)) { + c->Message(Chat::Yellow, "You must choose a valid spell type. Spell types range from %i to %i", BotSpellTypes::START, BotSpellTypes::END); + + return; + } + } + else { + if (!arg1.compare("list")) { + ++ab_arg; + list_check = true; + ab_mask = ActionableBots::ABM_Target; + + if ( + !c->GetTarget() || + !c->GetTarget()->IsBot() || + c->GetTarget()->GetOwner() != c + ) { + c->Message(Chat::Yellow, "You must target your own bot to list priorities."); + return; + } + } + else if (Bot::GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { + spell_type = Bot::GetSpellTypeIDByShortName(arg1); + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + if (sep->IsNumber(2)) { + type_value = atoi(sep->arg[2]); + ++ab_arg; + if (!EQ::ValueWithin(type_value, BotSpellTypes::START, BotSpellTypes::END)) { + c->Message(Chat::Yellow, "You must enter a value between %u and %u.", BotSpellTypes::START, BotSpellTypes::END); + + return; + } + } + else if (!arg2.compare("current")) { + ++ab_arg; + current_check = true; + } + else if (!list_check) { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + + if (!first_found) { + first_found = my_bot; + } + + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] idle cast priority is currently [{}].'", + my_bot->GetCleanName(), + Bot::GetSpellTypeNameByID(spell_type), + my_bot->GetSpellTypePriority(spell_type, BotPriorityCategories::Idle) + ).c_str() + ); + } + else if (list_check) { + auto cast_order = my_bot->GetSpellTypesPrioritized(BotPriorityCategories::Idle); + + BotCommandHelpParams p; + p.description = { + "Anything not listed is currently disabled (0)", + "----------", + "Spell Type - Priority" + }; + p.notes = { }; + + for (auto& current_cast : cast_order) { + p.notes.push_back( + fmt::format( + "{}s - {}", + Bot::GetSpellTypeNameByID(current_cast.spellType), + (current_cast.priority == 0 ? "disabled (0)" : std::to_string(current_cast.priority)) + ).c_str() + ); + } + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + + return; + } + else { + my_bot->SetSpellTypePriority(spell_type, BotPriorityCategories::Idle, type_value); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] idle cast priority was set to [{}].'", + first_found->GetCleanName(), + Bot::GetSpellTypeNameByID(spell_type), + first_found->GetSpellTypePriority(spell_type, BotPriorityCategories::Idle) + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots set their [{}] idle cast priority to [{}].", + success_count, + Bot::GetSpellTypeNameByID(spell_type), + type_value + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/spell_max_hp_pct.cpp b/zone/bot_commands/spell_max_hp_pct.cpp new file mode 100644 index 000000000..249e9179d --- /dev/null +++ b/zone/bot_commands/spell_max_hp_pct.cpp @@ -0,0 +1,211 @@ +#include "../bot_command.h" + +void bot_command_spell_max_hp_pct(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_spell_max_hp_pct", sep->arg[0], "spellmaxhppct")) { + c->Message(Chat::White, "note: Controls at what health percentage a bot will start casting different spell types."); + + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + BotCommandHelpParams p; + + p.description = { "Controls at what health percentage a bot will start casting different spell types." }; + p.example_format = + { + fmt::format("{} [Type Shortname] [value] [actionable]", sep->arg[0]), + fmt::format("{} [Type ID] [value] [actionable]", sep->arg[0]) + }; + p.examples_one = + { + "To set all bots to allow snaring when their health is at or below 100% HP:", + fmt::format( + "{} {} 100 spawned", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::Snare) + ), + fmt::format( + "{} {} 10 spawned", + sep->arg[0], + BotSpellTypes::Snare + ) + }; + p.examples_two = + { + "To set BotA to allow casting of fast heals at 30% HP:", + fmt::format( + "{} {} 30 byname BotA", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::FastHeals) + ), + fmt::format( + "{} {} 30 byname BotA", + sep->arg[0], + BotSpellTypes::FastHeals + ) + }; + p.examples_three = + { + "To check the current Stun max HP percent on all bots:", + fmt::format( + "{} {} current spawned", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::Stun) + ), + fmt::format( + "{} {} current spawned", + sep->arg[0], + BotSpellTypes::Stun + ) + }; + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->SendSpellTypePrompts(); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + + return; + } + + std::string arg1 = sep->arg[1]; + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool current_check = false; + uint16 spell_type = 0; + uint32 type_value = 0; + + // String/Int type checks + if (sep->IsNumber(1)) { + spell_type = atoi(sep->arg[1]); + + if (!EQ::ValueWithin(spell_type, BotSpellTypes::START, BotSpellTypes::END)) { + c->Message(Chat::Yellow, "You must choose a valid spell type. Spell types range from %i to %i", BotSpellTypes::START, BotSpellTypes::END); + + return; + } + } + else { + if (Bot::GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { + spell_type = Bot::GetSpellTypeIDByShortName(arg1); + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + if (sep->IsNumber(2)) { + type_value = atoi(sep->arg[2]); + ++ab_arg; + if (type_value < 0 || type_value > 100) { + c->Message(Chat::Yellow, "You must enter a value between 0-100 (0%% to 100%% of health)."); + + return; + } + } + else if (!arg2.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + + if (!first_found) { + first_found = my_bot; + } + + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] maximum HP is currently [{}%%].'", + my_bot->GetCleanName(), + Bot::GetSpellTypeNameByID(spell_type), + my_bot->GetSpellTypeMaxHPLimit(spell_type) + ).c_str() + ); + } + else { + my_bot->SetSpellTypeMaxHPLimit(spell_type, type_value); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] maximum HP was set to [{}%%].'", + first_found->GetCleanName(), + Bot::GetSpellTypeNameByID(spell_type), + first_found->GetSpellTypeMaxHPLimit(spell_type) + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots set their [{}] maximum HP to [{}%%].", + success_count, + Bot::GetSpellTypeNameByID(spell_type), + type_value + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/spell_max_mana_pct.cpp b/zone/bot_commands/spell_max_mana_pct.cpp new file mode 100644 index 000000000..ab42ce8a5 --- /dev/null +++ b/zone/bot_commands/spell_max_mana_pct.cpp @@ -0,0 +1,211 @@ +#include "../bot_command.h" + +void bot_command_spell_max_mana_pct(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_spell_max_mana_pct", sep->arg[0], "spellmaxmanapct")) { + c->Message(Chat::White, "note: Controls at what mana percentage a bot will stop casting different spell types."); + + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + BotCommandHelpParams p; + + p.description = { "Controls at what mana percentage a bot will stop casting different spell types." }; + p.example_format = + { + fmt::format("{} [Type Shortname] [value] [actionable]", sep->arg[0]), + fmt::format("{} [Type ID] [value] [actionable]", sep->arg[0]) + }; + p.examples_one = + { + "To set all bots to allow snaring when their mana is at or below 100% HP:", + fmt::format( + "{} {} 10 spawned", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::Snare) + ), + fmt::format( + "{} {} 10 spawned", + sep->arg[0], + BotSpellTypes::Snare + ) + }; + p.examples_two = + { + "To set BotA to allow casting of fast heals at 90% mana:", + fmt::format( + "{} {} 90 byname BotA", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::FastHeals) + ), + fmt::format( + "{} {} 90 byname BotA", + sep->arg[0], + BotSpellTypes::FastHeals + ) + }; + p.examples_three = + { + "To check the current Stun max mana percent on all bots:", + fmt::format( + "{} {} current spawned", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::Stun) + ), + fmt::format( + "{} {} current spawned", + sep->arg[0], + BotSpellTypes::Stun + ) + }; + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->SendSpellTypePrompts(); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + + return; + } + + std::string arg1 = sep->arg[1]; + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool current_check = false; + uint16 spell_type = 0; + uint32 type_value = 0; + + // String/Int type checks + if (sep->IsNumber(1)) { + spell_type = atoi(sep->arg[1]); + + if (!EQ::ValueWithin(spell_type, BotSpellTypes::START, BotSpellTypes::END)) { + c->Message(Chat::Yellow, "You must choose a valid spell type. Spell types range from %i to %i", BotSpellTypes::START, BotSpellTypes::END); + + return; + } + } + else { + if (Bot::GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { + spell_type = Bot::GetSpellTypeIDByShortName(arg1); + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + if (sep->IsNumber(2)) { + type_value = atoi(sep->arg[2]); + ++ab_arg; + if (type_value < 0 || type_value > 100) { + c->Message(Chat::Yellow, "You must enter a value between 0-100 (0%% to 100%% of mana)."); + + return; + } + } + else if (!arg2.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + + if (!first_found) { + first_found = my_bot; + } + + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] maximum mana is currently [{}%%].'", + my_bot->GetCleanName(), + Bot::GetSpellTypeNameByID(spell_type), + my_bot->GetSpellTypeMaxManaLimit(spell_type) + ).c_str() + ); + } + else { + my_bot->SetSpellTypeMaxManaLimit(spell_type, type_value); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] maximum mana was set to [{}%%].'", + first_found->GetCleanName(), + Bot::GetSpellTypeNameByID(spell_type), + first_found->GetSpellTypeMaxManaLimit(spell_type) + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots set their [{}] maximum mana to [{}%%].", + success_count, + Bot::GetSpellTypeNameByID(spell_type), + type_value + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/spell_max_thresholds.cpp b/zone/bot_commands/spell_max_thresholds.cpp new file mode 100644 index 000000000..ca7f78aa3 --- /dev/null +++ b/zone/bot_commands/spell_max_thresholds.cpp @@ -0,0 +1,278 @@ +#include "../bot_command.h" + +void bot_command_spell_max_thresholds(Client* c, const Seperator* sep) { + if (helper_command_alias_fail(c, "bot_command_spell_max_thresholds", sep->arg[0], "spellmaxthresholds")) { + c->Message(Chat::White, "note: Controls at what target HP % the bot will start casting different spell types."); + + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + BotCommandHelpParams p; + + p.description = { "Controls at what target HP % the bot will start casting different spell types." }; + p.notes = + { + "- Targeting yourself for this command will allow you to control your own settings for how bots cast on you", + "- All pet types are based off the pet's owner's setting", + "- Any remaining types use the owner's setting when a pet is the target", + "- All Heals, Cures, Buffs (DS and resists included) are based off the target's setting, not the caster", + "- e.g., BotA is healing BotB using BotB's settings", + }; + p.example_format = + { + fmt::format("{} [Type Shortname] [value] [actionable, default: target]", sep->arg[0]), + fmt::format("{} [Type ID] [value] [actionable, default: target]", sep->arg[0]) + }; + p.examples_one = + { + "To set all bots to start snaring at 99%:", + fmt::format( + "{} {} 99 spawned", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::Snare) + ), + fmt::format( + "{} {} 99 spawned", + sep->arg[0], + BotSpellTypes::Snare + ) + }; + p.examples_two = + { + "To set bot Enchbot to start casting Debuffs at 99%:", + fmt::format( + "{} {} 99 byname Enchbot", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::Debuff) + ), + fmt::format( + "{} {} 99 byname Enchbot", + sep->arg[0], + BotSpellTypes::Debuff + ) + }; + p.examples_three = + { + "To check the current Nuke max threshold on all bots:", + fmt::format( + "{} {} current spawned", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::Nuke) + ), + fmt::format( + "{} {} current spawned", + sep->arg[0], + BotSpellTypes::Nuke + ) + }; + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->SendSpellTypePrompts(); + c->SendSpellTypePrompts(false, true); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + + return; + } + + std::string arg1 = sep->arg[1]; + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool current_check = false; + uint16 spell_type = 0; + uint32 type_value = 0; + Mob* target = c->GetTarget(); + bool clientSetting = (target && target == c); + + if (sep->IsNumber(1)) { + spell_type = atoi(sep->arg[1]); + + if ( + (clientSetting && !IsClientBotSpellType(spell_type)) || + (!clientSetting && !EQ::ValueWithin(spell_type, BotSpellTypes::START, BotSpellTypes::END)) + ) { + c->Message( + Chat::Yellow, + fmt::format( + "{}. Use {} for information regarding this command.", + (!clientSetting ? "You must choose a valid spell type" : "You must choose a valid client spell type"), + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + if (clientSetting) { + c->SendSpellTypePrompts(false, true); + } + + return; + } + } + else { + if (Bot::GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { + spell_type = Bot::GetSpellTypeIDByShortName(arg1); + + if (clientSetting && !IsClientBotSpellType(spell_type)) { + c->Message(Chat::Yellow, "Invalid spell type for clients."); + c->SendSpellTypePrompts(false, true); + } + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + if (sep->IsNumber(2)) { + type_value = atoi(sep->arg[2]); + ++ab_arg; + if (type_value < 0 || type_value > 100) { + c->Message(Chat::Yellow, "You must enter a value between 0-100 (0%% to 100%% of health)."); + + return; + } + } + else if (!arg2.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string actionable_arg = sep->arg[ab_arg]; + + if (actionable_arg.empty()) { + actionable_arg = "target"; + } + + if (!clientSetting) { + + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + + if (ActionableBots::PopulateSBL(c, actionable_arg, sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + + Bot* first_found = nullptr; + int success_count = 0; + + for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + + if (!first_found) { + first_found = my_bot; + } + + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] maximum threshold is currently [{}%%].'", + my_bot->GetCleanName(), + Bot::GetSpellTypeNameByID(spell_type), + my_bot->GetSpellTypeMaxThreshold(spell_type) + ).c_str() + ); + } + else { + my_bot->SetSpellTypeMaxThreshold(spell_type, type_value); + ++success_count; + } + } + + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] maximum threshold was set to [{}%%].'", + first_found->GetCleanName(), + Bot::GetSpellTypeNameByID(spell_type), + first_found->GetSpellTypeMaxThreshold(spell_type) + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots set their [{}] maximum threshold to [{}%%].", + success_count, + Bot::GetSpellTypeNameByID(spell_type), + type_value + ).c_str() + ); + } + } + } + else { + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "Your [{}] maximum threshold is currently [{}%%].", + Bot::GetSpellTypeNameByID(spell_type), + c->GetSpellTypeMaxThreshold(spell_type) + ).c_str() + ); + } + else { + c->SetSpellTypeMaxThreshold(spell_type, type_value); + + c->Message( + Chat::Green, + fmt::format( + "Your [{}] maximum threshold was set to [{}%%].", + Bot::GetSpellTypeNameByID(spell_type), + c->GetSpellTypeMaxThreshold(spell_type) + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/spell_min_hp_pct.cpp b/zone/bot_commands/spell_min_hp_pct.cpp new file mode 100644 index 000000000..61d6313e3 --- /dev/null +++ b/zone/bot_commands/spell_min_hp_pct.cpp @@ -0,0 +1,211 @@ +#include "../bot_command.h" + +void bot_command_spell_min_hp_pct(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_spell_min_hp_pct", sep->arg[0], "spellminhppct")) { + c->Message(Chat::White, "note: Controls at what health percentage a bot will stop casting different spell types."); + + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + BotCommandHelpParams p; + + p.description = { "Controls at what health percentage a bot will stop casting different spell types." }; + p.example_format = + { + fmt::format("{} [Type Shortname] [value] [actionable]", sep->arg[0]), + fmt::format("{} [Type ID] [value] [actionable]", sep->arg[0]) + }; + p.examples_one = + { + "To set all bots to stop snaring when their health is below 10% HP:", + fmt::format( + "{} {} 10 spawned", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::Snare) + ), + fmt::format( + "{} {} 10 spawned", + sep->arg[0], + BotSpellTypes::Snare + ) + }; + p.examples_two = + { + "To set BotA to stop casting fast heals at 30% HP:", + fmt::format( + "{} {} 30 byname BotA", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::FastHeals) + ), + fmt::format( + "{} {} 30 byname BotA", + sep->arg[0], + BotSpellTypes::FastHeals + ) + }; + p.examples_three = + { + "To check the current Stun min HP percent on all bots:", + fmt::format( + "{} {} current spawned", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::Stun) + ), + fmt::format( + "{} {} current spawned", + sep->arg[0], + BotSpellTypes::Stun + ) + }; + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->SendSpellTypePrompts(); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + + return; + } + + std::string arg1 = sep->arg[1]; + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool current_check = false; + uint16 spell_type = 0; + uint32 type_value = 0; + + // String/Int type checks + if (sep->IsNumber(1)) { + spell_type = atoi(sep->arg[1]); + + if (!EQ::ValueWithin(spell_type, BotSpellTypes::START, BotSpellTypes::END)) { + c->Message(Chat::Yellow, "You must choose a valid spell type. Spell types range from %i to %i", BotSpellTypes::START, BotSpellTypes::END); + + return; + } + } + else { + if (Bot::GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { + spell_type = Bot::GetSpellTypeIDByShortName(arg1); + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + if (sep->IsNumber(2)) { + type_value = atoi(sep->arg[2]); + ++ab_arg; + if (type_value < 0 || type_value > 100) { + c->Message(Chat::Yellow, "You must enter a value between 0-100 (0%% to 100%% of mana)."); + + return; + } + } + else if (!arg2.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + + if (!first_found) { + first_found = my_bot; + } + + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] minimum HP is currently [{}%%].'", + my_bot->GetCleanName(), + Bot::GetSpellTypeNameByID(spell_type), + my_bot->GetSpellTypeMinHPLimit(spell_type) + ).c_str() + ); + } + else { + my_bot->SetSpellTypeMinHPLimit(spell_type, type_value); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] minimum HP was set to [{}%%].'", + first_found->GetCleanName(), + Bot::GetSpellTypeNameByID(spell_type), + first_found->GetSpellTypeMinHPLimit(spell_type) + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots set their [{}] minimum HP to [{}%%].", + success_count, + Bot::GetSpellTypeNameByID(spell_type), + type_value + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/spell_min_mana_pct.cpp b/zone/bot_commands/spell_min_mana_pct.cpp new file mode 100644 index 000000000..71672cee9 --- /dev/null +++ b/zone/bot_commands/spell_min_mana_pct.cpp @@ -0,0 +1,211 @@ +#include "../bot_command.h" + +void bot_command_spell_min_mana_pct(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_spell_min_mana_pct", sep->arg[0], "spellminmanapct")) { + c->Message(Chat::White, "note: Controls at what mana percentage a bot will stop casting different spell types."); + + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + BotCommandHelpParams p; + + p.description = { "Controls at what mana percentage a bot will stop casting different spell types." }; + p.example_format = + { + fmt::format("{} [Type Shortname] [value] [actionable]", sep->arg[0]), + fmt::format("{} [Type ID] [value] [actionable]", sep->arg[0]) + }; + p.examples_one = + { + "To set all bots to stop snaring when their mana is below 10% HP:", + fmt::format( + "{} {} 10 spawned", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::Snare) + ), + fmt::format( + "{} {} 10 spawned", + sep->arg[0], + BotSpellTypes::Snare + ) + }; + p.examples_two = + { + "To set BotA to stop casting fast heals at 30% mana:", + fmt::format( + "{} {} 30 byname BotA", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::FastHeals) + ), + fmt::format( + "{} {} 30 byname BotA", + sep->arg[0], + BotSpellTypes::FastHeals + ) + }; + p.examples_three = + { + "To check the current Stun min mana percent on all bots:", + fmt::format( + "{} {} current spawned", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::Stun) + ), + fmt::format( + "{} {} current spawned", + sep->arg[0], + BotSpellTypes::Stun + ) + }; + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->SendSpellTypePrompts(); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + + return; + } + + std::string arg1 = sep->arg[1]; + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool current_check = false; + uint16 spell_type = 0; + uint32 type_value = 0; + + // String/Int type checks + if (sep->IsNumber(1)) { + spell_type = atoi(sep->arg[1]); + + if (!EQ::ValueWithin(spell_type, BotSpellTypes::START, BotSpellTypes::END)) { + c->Message(Chat::Yellow, "You must choose a valid spell type. Spell types range from %i to %i", BotSpellTypes::START, BotSpellTypes::END); + + return; + } + } + else { + if (Bot::GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { + spell_type = Bot::GetSpellTypeIDByShortName(arg1); + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + if (sep->IsNumber(2)) { + type_value = atoi(sep->arg[2]); + ++ab_arg; + if (type_value < 0 || type_value > 100) { + c->Message(Chat::Yellow, "You must enter a value between 0-100 (0%% to 100%% of mana)."); + + return; + } + } + else if (!arg2.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + + if (!first_found) { + first_found = my_bot; + } + + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] minimum mana is currently [{}%%].'", + my_bot->GetCleanName(), + Bot::GetSpellTypeNameByID(spell_type), + my_bot->GetSpellTypeMinManaLimit(spell_type) + ).c_str() + ); + } + else { + my_bot->SetSpellTypeMinManaLimit(spell_type, type_value); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] minimum mana was set to [{}%%].'", + first_found->GetCleanName(), + Bot::GetSpellTypeNameByID(spell_type), + first_found->GetSpellTypeMinManaLimit(spell_type) + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots set their [{}] minimum mana to [{}%%].", + success_count, + Bot::GetSpellTypeNameByID(spell_type), + type_value + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/spell_min_thresholds.cpp b/zone/bot_commands/spell_min_thresholds.cpp new file mode 100644 index 000000000..ba614530c --- /dev/null +++ b/zone/bot_commands/spell_min_thresholds.cpp @@ -0,0 +1,280 @@ +#include "../bot_command.h" + +void bot_command_spell_min_thresholds(Client* c, const Seperator* sep) { + if (helper_command_alias_fail(c, "bot_command_spell_min_thresholds", sep->arg[0], "spellminthresholds")) { + c->Message(Chat::White, "note: Controls at what target HP % the bot will stop casting different spell types."); + + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + BotCommandHelpParams p; + + p.description = { "Controls at what target HP % the bot will stop casting different spell types." }; + p.notes = + { + "- Targeting yourself for this command will allow you to control your own settings for how bots cast on you", + "- All pet types are based off the pet's owner's setting", + "- Any remaining types use the owner's setting when a pet is the target", + "- All Heals, Cures, Buffs (DS and resists included) are based off the target's setting, not the caster", + "- e.g., BotA is healing BotB using BotB's settings", + }; + p.example_format = + { + fmt::format("{} [Type Shortname] [value] [actionable, default: target]", sep->arg[0]), + fmt::format("{} [Type ID] [value] [actionable, default: target]", sep->arg[0]) + }; + p.examples_one = + { + "To set all bots to stop debuffing at 10%:", + fmt::format( + "{} {} 10 spawned", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::Debuff) + ), + fmt::format( + "{} {} 10 spawned", + sep->arg[0], + BotSpellTypes::Debuff + ) + }; + p.examples_two = + { + "To set all Druids to stop casting DoTs at 15%:", + fmt::format( + "{} {} 15 byclass {}", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::DOT), + Class::Druid + ), + fmt::format( + "{} {} 15 byclass {}", + sep->arg[0], + BotSpellTypes::DOT, + Class::Druid + ) + }; + p.examples_three = + { + "To check the current Fast Heal min threshold on all bots:", + fmt::format( + "{} {} current spawned", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::FastHeals) + ), + fmt::format( + "{} {} current spawned", + sep->arg[0], + BotSpellTypes::FastHeals + ) + }; + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->SendSpellTypePrompts(); + c->SendSpellTypePrompts(false, true); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + + return; + } + + std::string arg1 = sep->arg[1]; + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool current_check = false; + uint16 spell_type = 0; + uint32 type_value = 0; + Mob* target = c->GetTarget(); + bool clientSetting = (target && target == c); + + if (sep->IsNumber(1)) { + spell_type = atoi(sep->arg[1]); + + if ( + (clientSetting && !IsClientBotSpellType(spell_type)) || + (!clientSetting && !EQ::ValueWithin(spell_type, BotSpellTypes::START, BotSpellTypes::END)) + ) { + c->Message( + Chat::Yellow, + fmt::format( + "{}. Use {} for information regarding this command.", + (!clientSetting ? "You must choose a valid spell type" : "You must choose a valid client spell type"), + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + if (clientSetting) { + c->SendSpellTypePrompts(false, true); + } + + return; + } + } + else { + if (Bot::GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { + spell_type = Bot::GetSpellTypeIDByShortName(arg1); + + if (clientSetting && !IsClientBotSpellType(spell_type)) { + c->Message(Chat::Yellow, "Invalid spell type for clients."); + c->SendSpellTypePrompts(false, true); + } + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + if (sep->IsNumber(2)) { + type_value = atoi(sep->arg[2]); + ++ab_arg; + if (type_value < 0 || type_value > 100) { + c->Message(Chat::Yellow, "You must enter a value between 0-100 (0%% to 100%% of health)."); + + return; + } + } + else if (!arg2.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string actionable_arg = sep->arg[ab_arg]; + + if (actionable_arg.empty()) { + actionable_arg = "target"; + } + + if (!clientSetting) { + + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + + if (ActionableBots::PopulateSBL(c, actionable_arg, sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + + Bot* first_found = nullptr; + int success_count = 0; + + for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + + if (!first_found) { + first_found = my_bot; + } + + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] minimum threshold is currently [{}%%].'", + my_bot->GetCleanName(), + Bot::GetSpellTypeNameByID(spell_type), + my_bot->GetSpellTypeMinThreshold(spell_type) + ).c_str() + ); + } + else { + my_bot->SetSpellTypeMinThreshold(spell_type, type_value); + ++success_count; + } + } + + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] minimum threshold was set to [{}%%].'", + first_found->GetCleanName(), + Bot::GetSpellTypeNameByID(spell_type), + first_found->GetSpellTypeMinThreshold(spell_type) + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots set their [{}] minimum threshold to [{}%%].", + success_count, + Bot::GetSpellTypeNameByID(spell_type), + type_value + ).c_str() + ); + } + } + } + else { + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "Your [{}] minimum threshold is currently [{}%%].", + Bot::GetSpellTypeNameByID(spell_type), + c->GetSpellTypeMinThreshold(spell_type) + ).c_str() + ); + } + else { + c->SetSpellTypeMinThreshold(spell_type, type_value); + + c->Message( + Chat::Green, + fmt::format( + "Your [{}] minimum threshold was set to [{}%%].", + Bot::GetSpellTypeNameByID(spell_type), + c->GetSpellTypeMinThreshold(spell_type) + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/spell_pursue_priority.cpp b/zone/bot_commands/spell_pursue_priority.cpp new file mode 100644 index 000000000..2b395d74f --- /dev/null +++ b/zone/bot_commands/spell_pursue_priority.cpp @@ -0,0 +1,255 @@ +#include "../bot_command.h" + +void bot_command_spell_pursue_priority(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_spell_pursue_priority", sep->arg[0], "spellpursuepriority")) { + c->Message(Chat::White, "note: Sets the order of spell casts when the mob is fleeing in combat by spell type."); + + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + BotCommandHelpParams p; + + p.description = { "Sets the order of spell casts when the mob is fleeing in combat by spell type." }; + p.notes = + { + "- Setting a spell type to 0 will prevent that type from being cast.", + "- If 2 or more are set to the same priority they will sort by spell type ID." + }; + p.example_format = + { + fmt::format("{} [Type Shortname] [value] [actionable]", sep->arg[0]), + fmt::format("{} [Type ID] [value] [actionable]", sep->arg[0]) + }; + p.examples_one = + { + "To list your targeted bot's priorities:", + fmt::format( + "{} list", + sep->arg[0] + ) + }; + p.examples_two = + { + "To set all Shaman to not cast cures:", + fmt::format( + "{} {} 0 byclass {}", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::Cure), + Class::Shaman + ), + fmt::format( + "{} {} 0 byclass {}", + sep->arg[0], + BotSpellTypes::Cure, + Class::Shaman + ) + }; + p.examples_three = + { + "To check the current pursue priority of buffs on all bots:", + fmt::format( + "{} {} current spawned", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::Buff) + ), + fmt::format( + "{} {} current spawned", + sep->arg[0], + BotSpellTypes::Buff + ) + }; + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->SendSpellTypePrompts(); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + + return; + } + + std::string arg1 = sep->arg[1]; + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool current_check = false; + bool list_check = false; + uint16 spell_type = 0; + uint32 type_value = 0; + int ab_mask = ActionableBots::ABM_Type1; + + // String/Int type checks + if (sep->IsNumber(1)) { + spell_type = atoi(sep->arg[1]); + + if (!EQ::ValueWithin(spell_type, BotSpellTypes::START, BotSpellTypes::END)) { + c->Message(Chat::Yellow, "You must choose a valid spell type. Spell types range from %i to %i", BotSpellTypes::START, BotSpellTypes::END); + + return; + } + } + else { + if (!arg1.compare("list")) { + ++ab_arg; + list_check = true; + ab_mask = ActionableBots::ABM_Target; + + if ( + !c->GetTarget() || + !c->GetTarget()->IsBot() || + c->GetTarget()->GetOwner() != c + ) { + c->Message(Chat::Yellow, "You must target your own bot to list priorities."); + return; + } + } + else if (Bot::GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { + spell_type = Bot::GetSpellTypeIDByShortName(arg1); + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + if (sep->IsNumber(2)) { + type_value = atoi(sep->arg[2]); + ++ab_arg; + if (!EQ::ValueWithin(type_value, BotSpellTypes::START, BotSpellTypes::END)) { + c->Message(Chat::Yellow, "You must enter a value between %u and %u.", BotSpellTypes::START, BotSpellTypes::END); + + return; + } + } + else if (!arg2.compare("current")) { + ++ab_arg; + current_check = true; + } + else if (!list_check) { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + + if (!first_found) { + first_found = my_bot; + } + + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] pursue cast priority is currently [{}].'", + my_bot->GetCleanName(), + Bot::GetSpellTypeNameByID(spell_type), + my_bot->GetSpellTypePriority(spell_type, BotPriorityCategories::Pursue) + ).c_str() + ); + } + else if (list_check) { + auto cast_order = my_bot->GetSpellTypesPrioritized(BotPriorityCategories::Pursue); + + BotCommandHelpParams p; + p.description = { + "Anything not listed is currently disabled (0)", + "----------", + "Spell Type - Priority" + }; + p.notes = { }; + + for (auto& current_cast : cast_order) { + p.notes.push_back( + fmt::format( + "{}s - {}", + Bot::GetSpellTypeNameByID(current_cast.spellType), + (current_cast.priority == 0 ? "disabled (0)" : std::to_string(current_cast.priority)) + ).c_str() + ); + } + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + + return; + } + else { + my_bot->SetSpellTypePriority(spell_type, BotPriorityCategories::Pursue, type_value); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] pursue cast priority was set to [{}].'", + first_found->GetCleanName(), + Bot::GetSpellTypeNameByID(spell_type), + first_found->GetSpellTypePriority(spell_type, BotPriorityCategories::Pursue) + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots set their [{}] pursue cast priority to [{}].", + success_count, + Bot::GetSpellTypeNameByID(spell_type), + type_value + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/spell_resist_limits.cpp b/zone/bot_commands/spell_resist_limits.cpp new file mode 100644 index 000000000..e7c928c33 --- /dev/null +++ b/zone/bot_commands/spell_resist_limits.cpp @@ -0,0 +1,214 @@ +#include "../bot_command.h" + +void bot_command_spell_resist_limits(Client* c, const Seperator* sep) { + if (helper_command_alias_fail(c, "bot_command_spell_resist_limits", sep->arg[0], "spellresistlimits")) { + c->Message(Chat::White, "note: Sets the limit of a target's resists to where a bot won't attempt to cast due to resist chances."); + + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + BotCommandHelpParams p; + + p.description = { "Sets the limit of a target's resists to where a bot won't attempt to cast due to resist chances." }; + p.example_format = + { + fmt::format("{} [Type Shortname] [value] [actionable]", sep->arg[0]), + fmt::format("{} [Type ID] [value] [actionable]", sep->arg[0]) + }; + p.examples_one = + { + "To set all bots' slow resist limit to 250:", + fmt::format( + "{} {} 250 spawned", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::Slow) + ), + fmt::format( + "{} {} 250 spawned", + sep->arg[0], + BotSpellTypes::Slow + ) + }; + p.examples_two = + { + "To set Magicians to limit their resist to 175 for nukes:", + fmt::format( + "{} {} 175 byclass {}", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::Nuke), + Class::Magician + ), + fmt::format( + "{} {} 175 byclass {}", + sep->arg[0], + BotSpellTypes::Nuke, + Class::Magician + + ) + }; + p.examples_three = + { + "To check the current debuff resist limit on all bots:", + fmt::format( + "{} {} current spawned", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::Debuff) + ), + fmt::format( + "{} {} current spawned", + sep->arg[0], + BotSpellTypes::Debuff + ) + }; + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->SendSpellTypePrompts(); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + + return; + } + + std::string arg1 = sep->arg[1]; + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool current_check = false; + uint16 spell_type = 0; + uint32 type_value = 0; + + // String/Int type checks + if (sep->IsNumber(1)) { + spell_type = atoi(sep->arg[1]); + + if (!EQ::ValueWithin(spell_type, BotSpellTypes::START, BotSpellTypes::END)) { + c->Message(Chat::Yellow, "You must choose a valid spell type. Spell types range from %i to %i", BotSpellTypes::START, BotSpellTypes::END); + + return; + } + } + else { + if (Bot::GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { + spell_type = Bot::GetSpellTypeIDByShortName(arg1); + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + if (sep->IsNumber(2)) { + type_value = atoi(sep->arg[2]); + ++ab_arg; + + if (type_value < 0 || type_value > 1000) { + c->Message(Chat::Yellow, "You must enter a value between 1-1000."); + + return; + } + } + else if (!arg2.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + + if (!first_found) { + first_found = my_bot; + } + + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] resist limit is currently [{}].'", + my_bot->GetCleanName(), + Bot::GetSpellTypeNameByID(spell_type), + my_bot->GetSpellTypeResistLimit(spell_type) + ).c_str() + ); + } + else { + my_bot->SetSpellTypeResistLimit(spell_type, type_value); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] resist limit was set to [{}].'", + first_found->GetCleanName(), + Bot::GetSpellTypeNameByID(spell_type), + first_found->GetSpellTypeResistLimit(spell_type) + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots set their [{}] resist limit to [{}].", + success_count, + Bot::GetSpellTypeNameByID(spell_type), + type_value + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/spell_target_count.cpp b/zone/bot_commands/spell_target_count.cpp new file mode 100644 index 000000000..ad938d9b5 --- /dev/null +++ b/zone/bot_commands/spell_target_count.cpp @@ -0,0 +1,214 @@ +#include "../bot_command.h" + +void bot_command_spell_target_count(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_spell_target_count", sep->arg[0], "spelltargetcount")) { + c->Message(Chat::White, "note: Decides how many eligible targets are required for an AE or group spell to cast by spell type."); + + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + BotCommandHelpParams p; + + p.description = { "Decides how many eligible targets are required for an AE or group spell to cast by spell type." }; + p.example_format = + { + fmt::format("{} [Type Shortname] [value] [actionable]", sep->arg[0]), + fmt::format("{} [Type ID] [value] [actionable]", sep->arg[0]) + }; + p.examples_one = + { + "To set all bots to AEMez with 5 or more targets:", + fmt::format( + "{} {} 5 spawned", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::AEMez) + ), + fmt::format( + "{} {} 5 spawned", + sep->arg[0], + BotSpellTypes::AEMez + ) + }; + p.examples_two = + { + "To set Wizards to require 5 targets for AENukes:", + fmt::format( + "{} {} byclass {}", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::AENukes), + Class::Wizard + ), + fmt::format( + "{} {} byclass {}", + sep->arg[0], + BotSpellTypes::AENukes, + Class::Wizard + ) + }; + p.examples_three = + { + "To check the current AESlow count on all bots:", + fmt::format( + "{} {} current spawned", + sep->arg[0], + Bot::GetSpellTypeShortNameByID(BotSpellTypes::AESlow) + ), + fmt::format( + "{} {} current spawned", + sep->arg[0], + BotSpellTypes::AESlow + ) + }; + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + c->SendSpellTypePrompts(); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + + return; + } + + std::string arg1 = sep->arg[1]; + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool current_check = false; + uint16 spell_type = 0; + uint32 type_value = 0; + + // String/Int type checks + if (sep->IsNumber(1)) { + spell_type = atoi(sep->arg[1]); + + if (!EQ::ValueWithin(spell_type, BotSpellTypes::START, BotSpellTypes::END)) { + c->Message(Chat::Yellow, "You must choose a valid spell type. Spell types range from %i to %i", BotSpellTypes::START, BotSpellTypes::END); + + return; + } + } + else { + if (Bot::GetSpellTypeIDByShortName(arg1) != UINT16_MAX) { + spell_type = Bot::GetSpellTypeIDByShortName(arg1); + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + } + + if (sep->IsNumber(2)) { + type_value = atoi(sep->arg[2]); + ++ab_arg; + + if (type_value < 1 || type_value > 100) { + c->Message(Chat::Yellow, "You must enter a value between 1-100."); + + return; + } + } + else if (!arg2.compare("current")) { + ++ab_arg; + current_check = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::vector sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + + Bot* first_found = nullptr; + int success_count = 0; + for (auto my_bot : sbl) { + if (my_bot->BotPassiveCheck()) { + continue; + } + + if (!first_found) { + first_found = my_bot; + } + + if (current_check) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] target count is currently [{}].'", + my_bot->GetCleanName(), + Bot::GetSpellTypeNameByID(spell_type), + my_bot->GetSpellTypeAEOrGroupTargetCount(spell_type) + ).c_str() + ); + } + else { + my_bot->SetSpellTypeAEOrGroupTargetCount(spell_type, type_value); + ++success_count; + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::Green, + fmt::format( + "{} says, 'My [{}] target count was set to [{}].'", + first_found->GetCleanName(), + Bot::GetSpellTypeNameByID(spell_type), + first_found->GetSpellTypeAEOrGroupTargetCount(spell_type) + ).c_str() + ); + } + else { + c->Message( + Chat::Green, + fmt::format( + "{} of your bots set their [{}] target count to [{}].", + success_count, + Bot::GetSpellTypeNameByID(spell_type), + type_value + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/spelltypes.cpp b/zone/bot_commands/spelltypes.cpp new file mode 100644 index 000000000..f91b3ed7e --- /dev/null +++ b/zone/bot_commands/spelltypes.cpp @@ -0,0 +1,9 @@ +void bot_command_spelltype_ids(Client* c, const Seperator* sep) +{ + SendSpellTypeWindow(c, sep); +} + +void bot_command_spelltype_names(Client* c, const Seperator* sep) +{ + SendSpellTypeWindow(c, sep); +} diff --git a/zone/bot_commands/summon_corpse.cpp b/zone/bot_commands/summon_corpse.cpp deleted file mode 100644 index 2a7a5b80f..000000000 --- a/zone/bot_commands/summon_corpse.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "../bot_command.h" - -void bot_command_summon_corpse(Client *c, const Seperator *sep) -{ - // Same methodology as old command..but, does not appear to work... (note: didn't work there, either...) - - // temp - c->Message(Chat::White, "This command is currently unavailable..."); - return; - - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_SummonCorpse]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_SummonCorpse) || helper_command_alias_fail(c, "bot_command_summon_corpse", sep->arg[0], "summoncorpse")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_SummonCorpse); - return; - } - - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - bool cast_success = false; - for (auto list_iter : *local_list) { - auto local_entry = list_iter; - if (helper_spell_check_fail(local_entry)) - continue; - - auto target_mob = ActionableTarget::AsSingle_ByPlayer(c); - if (!target_mob) - continue; - - if (spells[local_entry->spell_id].base_value[EFFECTIDTOINDEX(1)] < target_mob->GetLevel()) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; - - cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); - - break; - } - - helper_no_available_bots(c, my_bot); -} diff --git a/zone/bot_commands/suspend.cpp b/zone/bot_commands/suspend.cpp index e3198e79e..63c728fd3 100644 --- a/zone/bot_commands/suspend.cpp +++ b/zone/bot_commands/suspend.cpp @@ -6,17 +6,17 @@ void bot_command_suspend(Client *c, const Seperator *sep) return; } if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s ([actionable: ] ([actionable_name]))", sep->arg[0]); + c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationmembers | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); return; } const int ab_mask = ActionableBots::ABM_NoFilter; - std::list sbl; + std::vector sbl; if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, sep->arg[2]) == ActionableBots::ABT_None) { return; } - sbl.remove(nullptr); + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); for (auto bot_iter : sbl) { bot_iter->SetPauseAI(true); } diff --git a/zone/bot_commands/taunt.cpp b/zone/bot_commands/taunt.cpp index a15d29a5b..72391b2d3 100644 --- a/zone/bot_commands/taunt.cpp +++ b/zone/bot_commands/taunt.cpp @@ -1,118 +1,188 @@ #include "../bot_command.h" -void bot_command_taunt(Client *c, const Seperator *sep) +void bot_command_taunt(Client* c, const Seperator* sep) { - if (helper_command_alias_fail(c, "bot_command_taunt", sep->arg[0], "taunt")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s ([option: on | off]) ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); + if (helper_command_alias_fail(c, "bot_command_taunt", sep->arg[0], "taunt")) { + c->Message(Chat::White, "note: TAllows you to turn on/off the taunting state of your bots and/or their pets."); + + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + BotCommandHelpParams p; + + p.description = { "Allows you to turn on/off the taunting state of your bots and/or their pets." }; + p.notes = { "- If a taunting class is set to Aggressive (5) stance, they will automatically taunt and don't need to have it enabled manually." }; + p.example_format = { fmt::format("{} [on / off / pet] [optional: pet] [actionable, default: target]", sep->arg[0]) }; + p.examples_one = + { + "To turn off taunt on all bots:", + fmt::format( + "{} off spawned", + sep->arg[0] + ) + }; + p.examples_two = + { + "To turn on taunt on all bots' pets:", + fmt::format( + "{} on pet spawned", + sep->arg[0] + ) + }; + p.examples_three = + { + "To turn on taunt for all ShadowKnights:", + fmt::format( + "{} on byclass {}", + sep->arg[0], + Class::ShadowKnight + ) + }; + p.actionables = { "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets, mmr, byclass, byrace, spawned" }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + return; } - const int ab_mask = ActionableBots::ABM_Type1; std::string arg1 = sep->arg[1]; + std::string arg2 = sep->arg[2]; bool taunt_state = false; - bool toggle_taunt = true; + bool pet_taunt = false; + bool valid_option = false; + int ab_arg = 1; + if (!arg1.compare("on")) { taunt_state = true; - toggle_taunt = false; - ab_arg = 2; + valid_option = true; + ++ab_arg; } else if (!arg1.compare("off")) { - toggle_taunt = false; - ab_arg = 2; + valid_option = true; + ++ab_arg; + } + + if (!arg2.compare("pet")) { + pet_taunt = true; + valid_option = true; + ++ab_arg; + } + + if (!valid_option) { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string actionable_arg = sep->arg[ab_arg]; + + if (actionable_arg.empty()) { + actionable_arg = "target"; } std::string class_race_arg = sep->arg[ab_arg]; bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { class_race_check = true; } - std::list sbl; - if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[(ab_arg + 1)] : nullptr, class_race_check ? atoi(sep->arg[(ab_arg + 1)]) : 0) == ActionableBots::ABT_None) { + std::vector sbl; + + if (ActionableBots::PopulateSBL(c, actionable_arg, sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { return; } - sbl.remove(nullptr); - int taunting_count = 0; - for (auto bot_iter : sbl) { - if (!bot_iter->GetSkill(EQ::skills::SkillTaunt)) { - continue; - } + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + + int bot_taunting_count = 0; + int pet_taunting_count = 0; + + if (!pet_taunt) { + for (auto bot_iter : sbl) { + if (!bot_iter->GetSkill(EQ::skills::SkillTaunt)) { + continue; + } - if (toggle_taunt) { - bot_iter->SetTaunting(!bot_iter->IsTaunting()); - } else { bot_iter->SetTaunting(taunt_state); - } - if (sbl.size() == 1) { - Bot::BotGroupSay( - bot_iter, + bot_iter->RaidGroupSay( fmt::format( "I am {} taunting.", bot_iter->IsTaunting() ? "now" : "no longer" ).c_str() ); - } - ++taunting_count; + ++bot_taunting_count; + } } - for (auto bot_iter : sbl) { - if (!bot_iter->HasPet()) { - continue; - } + if (pet_taunt) { + for (auto bot_iter : sbl) { + if (!bot_iter->HasPet()) { + continue; + } - if (!bot_iter->GetPet()->GetSkill(EQ::skills::SkillTaunt)) { - continue; - } + if (!bot_iter->GetPet()->GetSkill(EQ::skills::SkillTaunt)) { + continue; + } - if (toggle_taunt) { - bot_iter->GetPet()->CastToNPC()->SetTaunting(!bot_iter->GetPet()->CastToNPC()->IsTaunting()); - } else { bot_iter->GetPet()->CastToNPC()->SetTaunting(taunt_state); - } - if (sbl.size() == 1) { - Bot::BotGroupSay( - bot_iter, + bot_iter->RaidGroupSay( fmt::format( "My Pet is {} taunting.", bot_iter->GetPet()->CastToNPC()->IsTaunting() ? "now" : "no longer" ).c_str() ); - } - ++taunting_count; + ++pet_taunting_count; + } } - if (taunting_count) { - if (toggle_taunt) { - c->Message( - Chat::White, - fmt::format( - "{} of your bots and their pets {} toggled their taunting state", - taunting_count, - taunting_count != 1 ? "have" : "has" - ).c_str() - ); - } else { - c->Message( - Chat::White, - fmt::format( - "{} of your bots and their pets {} {} taunting.", - taunting_count, - taunting_count != 1 ? "have" : "has", - taunt_state ? "started" : "stopped" - ).c_str() - ); - } + if (bot_taunting_count || pet_taunting_count) { + c->Message( + Chat::Green, + fmt::format( + "{} of your {} are {} taunting.", + (bot_taunting_count ? bot_taunting_count : pet_taunting_count), + (bot_taunting_count ? "bots" : "bots' pets"), + taunt_state ? "now" : "no longer" + ).c_str() + ); } else { - c->Message(Chat::White, "None of your bots are capable of taunting"); + c->Message( + Chat::Yellow, + fmt::format( + "None of your {} are capable of taunting.", + !pet_taunt ? "bots" : "bots' pets" + ).c_str() + ); } } diff --git a/zone/bot_commands/teleport.cpp b/zone/bot_commands/teleport.cpp deleted file mode 100644 index 94e8ee2b0..000000000 --- a/zone/bot_commands/teleport.cpp +++ /dev/null @@ -1,119 +0,0 @@ -#include "../bot_command.h" - -void bot_command_circle(Client *c, const Seperator *sep) -{ - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Depart]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Depart) || helper_command_alias_fail(c, "bot_command_circle", sep->arg[0], "circle")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s [list | destination] ([option: single])", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Depart, Class::Druid); - return; - } - - bool single = false; - std::string single_arg = sep->arg[2]; - if (!single_arg.compare("single")) - single = true; - - std::string destination = sep->arg[1]; - if (!destination.compare("list")) { - auto my_druid_bot = ActionableBots::AsGroupMember_ByClass(c, c, Class::Druid); - helper_command_depart_list(c, my_druid_bot, nullptr, local_list, single); - return; - } - else if (destination.empty()) { - c->Message(Chat::White, "A [destination] or [list] argument is required to use this command"); - return; - } - - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - bool cast_success = false; - for (auto list_iter : *local_list) { - auto local_entry = list_iter->SafeCastToDepart(); - if (helper_spell_check_fail(local_entry)) - continue; - if (local_entry->caster_class != Class::Druid) - continue; - if (local_entry->single != single) - continue; - if (destination.compare(spells[local_entry->spell_id].teleport_zone)) - continue; - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); - if (!target_mob) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; - - cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); - break; - } - - helper_no_available_bots(c, my_bot); -} - -void bot_command_portal(Client *c, const Seperator *sep) -{ - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Depart]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Depart) || helper_command_alias_fail(c, "bot_command_portal", sep->arg[0], "portal")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s [list | destination] ([option: single])", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Depart, Class::Wizard); - return; - } - - bool single = false; - std::string single_arg = sep->arg[2]; - if (!single_arg.compare("single")) - single = true; - - std::string destination = sep->arg[1]; - if (!destination.compare("list")) { - auto my_wizard_bot = ActionableBots::AsGroupMember_ByClass(c, c, Class::Wizard); - helper_command_depart_list(c, nullptr, my_wizard_bot, local_list, single); - return; - } - else if (destination.empty()) { - c->Message(Chat::White, "A [destination] or [list] argument is required to use this command"); - return; - } - - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - bool cast_success = false; - for (auto list_iter : *local_list) { - auto local_entry = list_iter->SafeCastToDepart(); - if (helper_spell_check_fail(local_entry)) - continue; - if (local_entry->caster_class != Class::Wizard) - continue; - if (local_entry->single != single) - continue; - if (destination.compare(spells[local_entry->spell_id].teleport_zone)) - continue; - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); - if (!target_mob) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; - - cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); - break; - } - - helper_no_available_bots(c, my_bot); -} diff --git a/zone/bot_commands/timer.cpp b/zone/bot_commands/timer.cpp index 93eab687d..99ad38001 100644 --- a/zone/bot_commands/timer.cpp +++ b/zone/bot_commands/timer.cpp @@ -2,10 +2,12 @@ void bot_command_timer(Client* c, const Seperator* sep) { - if (helper_command_alias_fail(c, "bot_command_timer", sep->arg[0], "timer")) + if (helper_command_alias_fail(c, "bot_command_timer", sep->arg[0], "timer")) { return; + } + if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s [clear | has | set] [disc | item | spell] [timer ID | item ID | spell ID | all] [optional ms for set] [actionable].", sep->arg[0]); + c->Message(Chat::White, "usage: %s [clear | has | set] [disc | item | spell] [timer ID | item ID | spell ID | all] [optional ms for set] ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name])).", sep->arg[0]); c->Message(Chat::White, "When setting, you can leave the value blank to use the default for the item or specify a value in ms to set the timer to."); c->Message(Chat::White, "Returns or sets the provided timer(s) for the selected bot(s) or clears the selected timer(s) for the selected bot(s)."); return; @@ -88,15 +90,18 @@ void bot_command_timer(Client* c, const Seperator* sep) std::string class_race_arg = sep->arg[ab_arg]; bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { class_race_check = true; } - std::list sbl; + std::vector sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { return; } - sbl.remove(nullptr); + + sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); for (auto my_bot : sbl) { bool found = false; diff --git a/zone/bot_commands/track.cpp b/zone/bot_commands/track.cpp index 9ac0904a2..18a21679a 100644 --- a/zone/bot_commands/track.cpp +++ b/zone/bot_commands/track.cpp @@ -13,7 +13,7 @@ void bot_command_track(Client *c, const Seperator *sep) std::string tracking_scope = sep->arg[1]; - std::list sbl; + std::vector sbl; MyBots::PopulateSBL_BySpawnedBots(c, sbl); uint16 class_mask = (player_class_bitmasks[Class::Ranger] | player_class_bitmasks[Class::Druid] | player_class_bitmasks[Class::Bard]); @@ -67,6 +67,6 @@ void bot_command_track(Client *c, const Seperator *sep) } my_bot->InterruptSpell(); - Bot::BotGroupSay(my_bot, tracking_msg.c_str()); + my_bot->RaidGroupSay(tracking_msg.c_str()); entity_list.ShowSpawnWindow(c, (c->GetLevel() * base_distance), track_named); } diff --git a/zone/bot_commands/water_breathing.cpp b/zone/bot_commands/water_breathing.cpp deleted file mode 100644 index cb9b792c4..000000000 --- a/zone/bot_commands/water_breathing.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "../bot_command.h" - -void bot_command_water_breathing(Client *c, const Seperator *sep) -{ - bcst_list* local_list = &bot_command_spells[BCEnum::SpT_WaterBreathing]; - if (helper_spell_list_fail(c, local_list, BCEnum::SpT_WaterBreathing) || helper_command_alias_fail(c, "bot_command_water_breathing", sep->arg[0], "waterbreathing")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: () %s", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_WaterBreathing); - return; - } - - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - bool cast_success = false; - for (auto list_iter : *local_list) { - auto local_entry = list_iter; - if (helper_spell_check_fail(local_entry)) - continue; - - auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); - if (!target_mob) - continue; - - my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); - if (!my_bot) - continue; - - cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); - break; - } - - helper_no_available_bots(c, my_bot); -} diff --git a/zone/bot_database.cpp b/zone/bot_database.cpp index a24cd0c30..10d681913 100644 --- a/zone/bot_database.cpp +++ b/zone/bot_database.cpp @@ -22,6 +22,7 @@ #include "../common/strings.h" #include "../common/eqemu_logsys.h" +#include "../common/repositories/bot_blocked_buffs_repository.h" #include "../common/repositories/bot_buffs_repository.h" #include "../common/repositories/bot_create_combinations_repository.h" #include "../common/repositories/bot_data_repository.h" @@ -35,6 +36,8 @@ #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" #include "../common/repositories/character_data_repository.h" @@ -71,13 +74,61 @@ bool BotDatabase::LoadBotCommandSettings(std::map +inline std::vector join_pair( + const std::string &glue, + const std::pair &encapsulation, + const std::vector> &src +) +{ + if (src.empty()) { + return {}; + } + + std::vector output; + + for (const std::pair &src_iter: src) { + output.emplace_back( + + fmt::format( + "{}{}{}{}{}{}{}", + encapsulation.first, + src_iter.first, + encapsulation.second, + glue, + encapsulation.first, + src_iter.second, + encapsulation.second + ) + ); + } + + return output; +} + +template +inline std::string +ImplodePair(const std::string &glue, const std::pair &encapsulation, const std::vector &src) +{ + if (src.empty()) { + return {}; + } + std::ostringstream oss; + for (const T &src_iter: src) { + oss << encapsulation.first << src_iter << encapsulation.second << glue; + } + std::string output(oss.str()); + output.resize(output.size() - glue.size()); + return output; +} + bool BotDatabase::UpdateInjectedBotCommandSettings(const std::vector> &injected) { if (injected.size()) { query = fmt::format( "REPLACE INTO `bot_command_settings`(`bot_command`, `access`) VALUES {}", - Strings::ImplodePair( + ImplodePair( ",", std::pair('(', ')'), join_pair(",", std::pair('\'', '\''), injected) @@ -104,7 +155,7 @@ bool BotDatabase::UpdateOrphanedBotCommandSettings(const std::vector('\'', '\''), orphaned) + ImplodePair(",", std::pair('\'', '\''), orphaned) ); if (!database.QueryDatabase(query).Success()) { @@ -179,11 +230,12 @@ bool BotDatabase::LoadBotSpellCastingChances() return true; } -bool BotDatabase::QueryNameAvailablity(const std::string& bot_name, bool& available_flag) +bool BotDatabase::QueryNameAvailability(const std::string& bot_name, bool& available_flag) { if ( bot_name.empty() || bot_name.size() > 60 || + !database.CheckNameFilter(bot_name) || database.IsNameUsed(bot_name) ) { return false; @@ -203,7 +255,7 @@ bool BotDatabase::QueryBotCount(const uint32 owner_id, int class_id, uint32& bot bot_count = BotDataRepository::Count( database, fmt::format( - "`owner_id` = {}", + "`owner_id` = {} AND `name` NOT LIKE '%-deleted-%'", owner_id ) ); @@ -212,7 +264,7 @@ bool BotDatabase::QueryBotCount(const uint32 owner_id, int class_id, uint32& bot bot_class_count = BotDataRepository::Count( database, fmt::format( - "`owner_id` = {} AND `class` = {}", + "`owner_id` = {} AND `class` = {} AND `name` NOT LIKE '%-deleted-%'", owner_id, class_id ) @@ -244,6 +296,8 @@ bool BotDatabase::LoadBotsList(const uint32 owner_id, std::listSetSurname(e.last_name); loaded_bot->SetTitle(e.title); loaded_bot->SetSuffix(e.suffix); - - loaded_bot->SetShowHelm(e.show_helm); - - auto bfd = EQ::Clamp(e.follow_distance, static_cast(1), BOT_FOLLOW_DISTANCE_DEFAULT_MAX); - - loaded_bot->SetFollowDistance(bfd); - - loaded_bot->SetStopMeleeLevel(e.stop_melee_level); - - loaded_bot->SetBotEnforceSpellSetting(e.enforce_spell_settings); - - loaded_bot->SetBotArcherySetting(e.archery_setting); - - loaded_bot->SetBotCasterRange(e.caster_range); } return true; @@ -476,13 +515,6 @@ bool BotDatabase::SaveNewBot(Bot* b, uint32& bot_id) e.poison = b->GetBasePR(); e.disease = b->GetBaseDR(); e.corruption = b->GetBaseCorrup(); - e.show_helm = b->GetShowHelm() ? 1 : 0; - e.follow_distance = b->GetFollowDistance(); - e.stop_melee_level = b->GetStopMeleeLevel(); - e.expansion_bitmask = b->GetExpansionBitmask(); - e.enforce_spell_settings = b->GetBotEnforceSpellSetting(); - e.archery_setting = b->IsBotArcher() ? 1 : 0; - e.caster_range = b->GetBotCasterRange(); e = BotDataRepository::InsertOne(database, e); @@ -547,12 +579,6 @@ bool BotDatabase::SaveBot(Bot* b) e.poison = b->GetBasePR(); e.disease = b->GetBaseDR(); e.corruption = b->GetBaseCorrup(); - e.show_helm = b->GetShowHelm() ? 1 : 0; - e.follow_distance = b->GetFollowDistance(); - e.stop_melee_level = b->GetStopMeleeLevel(); - e.expansion_bitmask = b->GetExpansionBitmask(); - e.enforce_spell_settings = b->GetBotEnforceSpellSetting(); - e.archery_setting = b->IsBotArcher() ? 1 : 0; return BotDataRepository::UpdateOne(database, e); } @@ -823,9 +849,9 @@ bool BotDatabase::LoadTimers(Bot* b) ) ); - std::vector v; + std::vector v; - BotTimer_Struct t{ }; + BotTimer t{ }; for (const auto& e : l) { if (e.timer_value < (Timer::GetCurrentTime() + e.recast_time)) { @@ -859,7 +885,7 @@ bool BotDatabase::SaveTimers(Bot* b) return false; } - std::vector v = b->GetBotTimers(); + std::vector v = b->GetBotTimers(); if (v.empty()) { return true; @@ -868,7 +894,7 @@ bool BotDatabase::SaveTimers(Bot* b) std::vector l; if (!v.empty()) { - for (auto & bot_timer : v) { + for (auto& bot_timer : v) { if (bot_timer.timer_value <= Timer::GetCurrentTime()) { continue; } @@ -1670,59 +1696,6 @@ bool BotDatabase::SaveAllArmorColors(const uint32 owner_id, const uint32 rgb_val return BotInventoriesRepository::SaveAllArmorColors(database, owner_id, rgb_value); } -bool BotDatabase::SaveHelmAppearance(const uint32 bot_id, const bool show_flag) -{ - if (!bot_id) { - return false; - } - - auto e = BotDataRepository::FindOne(database, bot_id); - - e.show_helm = show_flag ? 1 : 0; - - return BotDataRepository::UpdateOne(database, e); -} - -bool BotDatabase::SaveAllHelmAppearances(const uint32 owner_id, const bool show_flag) -{ - if (!owner_id) { - return false; - } - - return BotDataRepository::SaveAllHelmAppearances(database, owner_id, show_flag); -} - -bool BotDatabase::ToggleAllHelmAppearances(const uint32 owner_id) -{ - if (!owner_id) { - return false; - } - - return BotDataRepository::ToggleAllHelmAppearances(database, owner_id); -} - -bool BotDatabase::SaveFollowDistance(const uint32 bot_id, const uint32 follow_distance) -{ - if (!bot_id || !follow_distance) { - return false; - } - - auto e = BotDataRepository::FindOne(database, bot_id); - - e.follow_distance = follow_distance; - - return BotDataRepository::UpdateOne(database, e); -} - -bool BotDatabase::SaveAllFollowDistances(const uint32 owner_id, const uint32 follow_distance) -{ - if (!owner_id || !follow_distance) { - return false; - } - - return BotDataRepository::SaveAllFollowDistances(database, owner_id, follow_distance); -} - bool BotDatabase::CreateCloneBot(const uint32 bot_id, const std::string& clone_name, uint32& clone_id) { if (!bot_id || clone_name.empty()) { @@ -1771,19 +1744,6 @@ bool BotDatabase::CreateCloneBotInventory(const uint32 bot_id, const uint32 clon return BotInventoriesRepository::InsertMany(database, l); } -bool BotDatabase::SaveStopMeleeLevel(const uint32 bot_id, const uint8 sml_value) -{ - if (!bot_id) { - return false; - } - - auto e = BotDataRepository::FindOne(database, bot_id); - - e.stop_melee_level = sml_value; - - return BotDataRepository::UpdateOne(database, e); -} - bool BotDatabase::LoadOwnerOptions(Client* c) { if (!c || !c->CharacterID()) { @@ -2224,74 +2184,6 @@ uint32 BotDatabase::GetRaceClassBitmask(uint32 bot_race) return e.race ? e.classes : 0; } -bool BotDatabase::SaveExpansionBitmask(const uint32 bot_id, const int expansion_bitmask) -{ - if (!bot_id) { - return false; - } - - auto e = BotDataRepository::FindOne(database, bot_id); - - if (!e.bot_id) { - return false; - } - - e.expansion_bitmask = expansion_bitmask; - - return BotDataRepository::UpdateOne(database, e); -} - -bool BotDatabase::SaveEnforceSpellSetting(const uint32 bot_id, const bool enforce_spell_setting) -{ - if (!bot_id) { - return false; - } - - auto e = BotDataRepository::FindOne(database, bot_id); - - if (!e.bot_id) { - return false; - } - - e.enforce_spell_settings = enforce_spell_setting ? 1 : 0; - - return BotDataRepository::UpdateOne(database, e); -} - -bool BotDatabase::SaveBotArcherSetting(const uint32 bot_id, const bool bot_archer_setting) -{ - if (!bot_id) { - return false; - } - - auto e = BotDataRepository::FindOne(database, bot_id); - - if (!e.bot_id) { - return false; - } - - e.archery_setting = bot_archer_setting ? 1 : 0; - - return BotDataRepository::UpdateOne(database, e); -} - -bool BotDatabase::SaveBotCasterRange(const uint32 bot_id, const uint32 bot_caster_range_value) -{ - if (!bot_id) { - return false; - } - - auto e = BotDataRepository::FindOne(database, bot_id); - - if (!e.bot_id) { - return false; - } - - e.caster_range = bot_caster_range_value; - - return BotDataRepository::UpdateOne(database, e); -} - const uint8 BotDatabase::GetBotClassByID(const uint32 bot_id) { const auto& e = BotDataRepository::FindOne(database, bot_id); @@ -2322,7 +2214,7 @@ std::vector BotDatabase::GetBotIDsByCharacterID(const uint32 character_i class_id ) : "" - ) + ) ) ); @@ -2360,3 +2252,526 @@ const int BotDatabase::GetBotExtraHasteByID(const uint32 bot_id) return e.bot_id ? e.extra_haste : 0; } + +bool BotDatabase::LoadBotSettings(Mob* m) +{ + if (!m) { + return false; + } + + if (!m->IsOfClientBot()) { + return false; + } + + uint32 mob_id = (m->IsClient() ? m->CastToClient()->CharacterID() : m->CastToBot()->GetBotID()); + uint8 stance_id = (m->IsBot() ? m->CastToBot()->GetBotStance() : 0); + + std::string query = ""; + + if (m->IsClient()) { + query = fmt::format("`character_id` = {} AND `stance` = {}", mob_id, stance_id); + } + else { + query = fmt::format("`bot_id` = {} AND `stance` = {}", mob_id, stance_id); + } + + if (stance_id == Stance::Passive) { + LogBotSettings("{} is currently set to {} [#{}]. No saving or loading required.", m->GetCleanName(), Stance::GetName(Stance::Passive), Stance::Passive); + return true; + } + + const auto& l = BotSettingsRepository::GetWhere(database, query); + + if (l.empty()) { + return true; + } + + for (const auto& e : l) { + if (e.setting_type == BotSettingCategories::BaseSetting) { + LogBotSettings("[{}] says, 'Loading {} [{}] - setting to [{}].", + m->GetCleanName(), + Bot::GetBotSettingCategoryName(e.setting_type), + e.setting_type, + e.value + ); + } + else { + LogBotSettings("[{}] says, 'Loading {} [{}], {} [{}] - setting to [{}].", + m->GetCleanName(), + Bot::GetBotSpellCategoryShortName(e.setting_type), + e.setting_type, + Bot::GetSpellTypeNameByID(e.setting_id), + e.setting_id, + e.value + ); + } + + if (m->IsClient()) { + m->CastToClient()->SetBotSetting(e.setting_type, e.setting_id, e.value); + } + else { + m->CastToBot()->SetBotSetting(e.setting_type, e.setting_id, e.value); + } + } + + return true; +} + +bool BotDatabase::SaveBotSettings(Mob* m) +{ + if (!m) { + return false; + } + + if (!m->IsOfClientBot()) { + return false; + } + + uint32 bot_id = (m->IsBot() ? m->CastToBot()->GetBotID() : 0); + uint32 character_id = (m->IsClient() ? m->CastToClient()->CharacterID() : 0); + uint8 stance_id = (m->IsBot() ? m->CastToBot()->GetBotStance() : 0); + + if (stance_id == Stance::Passive) { + LogBotSettings("{} is currently set to {} [#{}]. No saving or loading required.", m->GetCleanName(), Stance::GetName(Stance::Passive), Stance::Passive); + return true; + } + + std::string query = ""; + + if (m->IsClient()) { + query = fmt::format("`character_id` = {} AND `stance` = {}", character_id, stance_id); + } + else { + query = fmt::format("`bot_id` = {} AND `stance` = {}", bot_id, stance_id); + } + + BotSettingsRepository::DeleteWhere(database, query); + + std::vector v; + + if (m->IsBot()) { + uint8 bot_stance = m->CastToBot()->GetBotStance(); + + for (uint16 i = BotBaseSettings::START_ALL; i <= BotBaseSettings::END; ++i) { + if (m->CastToBot()->GetBotBaseSetting(i) != m->CastToBot()->GetDefaultBotBaseSetting(i, bot_stance)) { + auto e = BotSettingsRepository::BotSettings{ + .character_id = character_id, + .bot_id = bot_id, + .stance = stance_id, + .setting_id = static_cast(i), + .setting_type = static_cast(BotSettingCategories::BaseSetting), + .value = static_cast(m->CastToBot()->GetBotBaseSetting(i)), + .category_name = Bot::GetBotSpellCategoryShortName(BotSettingCategories::BaseSetting), + .setting_name = Bot::GetBotSettingCategoryName(i) + }; + + v.emplace_back(e); + + LogBotSettings("{} says, 'Saving {} [{}] - set to [{}] default [{}].'", m->GetCleanName(), Bot::GetBotSettingCategoryName(i), i, e.value, m->CastToBot()->GetDefaultBotBaseSetting(i)); + } + } + + for (uint16 i = BotSettingCategories::START_NO_BASE; i <= BotSettingCategories::END; ++i) { + for (uint16 x = BotSpellTypes::START; x <= BotSpellTypes::END; ++x) { + if (m->CastToBot()->GetSetting(i, x) != m->CastToBot()->GetDefaultSetting(i, x, bot_stance)) { + auto e = BotSettingsRepository::BotSettings{ + .character_id = character_id, + .bot_id = bot_id, + .stance = stance_id, + .setting_id = static_cast(x), + .setting_type = static_cast(i), + .value = m->CastToBot()->GetSetting(i, x), + .category_name = Bot::GetBotSpellCategoryShortName(i), + .setting_name = Bot::GetSpellTypeNameByID(x) + }; + + v.emplace_back(e); + + LogBotSettings("{} says, 'Saving {} {} [{}] - set to [{}] default [{}].'", m->GetCleanName(), Bot::GetBotSpellCategoryShortName(i), Bot::GetSpellTypeNameByID(x), x, e.value, m->CastToBot()->GetDefaultSetting(i, x, bot_stance)); + } + } + } + } + + if (m->IsClient()) { + /* Currently unused + if (m->CastToClient()->GetDefaultBotSettings(BotSettingCategories::BaseSetting, BotBaseSettings::IllusionBlock) != m->CastToClient()->GetIllusionBlock()) { // Only illusion block supported + auto e = BotSettingsRepository::BotSettings{ + .character_id = character_id, + .bot_id = bot_id, + .stance = stance_id, + .setting_id = static_cast(BotBaseSettings::IllusionBlock), + .setting_type = static_cast(BotSettingCategories::BaseSetting), + .value = m->CastToClient()->GetIllusionBlock(), + .category_name = Bot::GetBotSpellCategoryShortName(BotSettingCategories::BaseSetting), + .setting_name = Bot::GetBotSettingCategoryName(BotBaseSettings::IllusionBlock) + }; + + v.emplace_back(e); + + LogBotSettings("{} says, 'Saving {} [{}] - set to [{}] default [{}].'", m->GetCleanName(), Bot::GetBotSettingCategoryName(BotBaseSettings::IllusionBlock), BotBaseSettings::IllusionBlock, e.value, m->CastToClient()->GetIllusionBlock()); + } + */ + + for (uint16 i = BotSettingCategories::START_CLIENT; i <= BotSettingCategories::END_CLIENT; ++i) { + for (uint16 x = BotSpellTypes::START; x <= BotSpellTypes::END; ++x) { + LogBotSettings("{} says, 'Checking {} {} [{}] - set to [{}] default [{}].'", m->GetCleanName(), Bot::GetBotSpellCategoryShortName(i), Bot::GetSpellTypeNameByID(x), x, m->CastToClient()->GetBotSetting(i, x), m->CastToClient()->GetDefaultBotSettings(i, x)); + if (m->CastToClient()->GetBotSetting(i, x) != m->CastToClient()->GetDefaultBotSettings(i, x)) { + auto e = BotSettingsRepository::BotSettings{ + .character_id = character_id, + .bot_id = bot_id, + .stance = stance_id, + .setting_id = static_cast(x), + .setting_type = static_cast(i), + .value = m->CastToClient()->GetBotSetting(i, x), + .category_name = Bot::GetBotSpellCategoryShortName(i), + .setting_name = Bot::GetSpellTypeNameByID(x) + }; + + v.emplace_back(e); + + LogBotSettings("{} says, 'Saving {} {} [{}] - set to [{}] default [{}].'", m->GetCleanName(), Bot::GetBotSpellCategoryShortName(i), Bot::GetSpellTypeNameByID(x), x, e.value, m->CastToClient()->GetDefaultBotSettings(i, x)); + } + } + } + } + + if (!v.empty()) { + const int inserted = BotSettingsRepository::ReplaceMany(database, v); + + if (!inserted) { + return false; + } + } + + return true; +} + +bool BotDatabase::DeleteBotSettings(const uint32 bot_id) +{ + if (!bot_id) { + return false; + } + + BotSettingsRepository::DeleteWhere( + database, + fmt::format( + "`bot_id` = {}", + bot_id + ) + ); + + return true; +} + +bool BotDatabase::LoadBotBlockedBuffs(Bot* b) +{ + if (!b) { + return false; + } + + const auto& l = BotBlockedBuffsRepository::GetWhere( + database, + fmt::format( + "`bot_id` = {}", + b->GetBotID() + ) + ); + + std::vector v; + + BotBlockedBuffs t{ }; + + for (const auto& e : l) { + t.spell_id = e.spell_id; + t.blocked = e.blocked; + t.blocked_pet = e.blocked_pet; + + v.push_back(t); + } + + if (!v.empty()) { + b->SetBotBlockedBuffs(v); + } + + return true; +} + +bool BotDatabase::SaveBotBlockedBuffs(Bot* b) +{ + if (!b) { + return false; + } + + if (!DeleteBotBlockedBuffs(b->GetBotID())) { + return false; + } + + std::vector v = b->GetBotBlockedBuffs(); + + if (v.empty()) { + return true; + } + + std::vector l; + + if (!v.empty()) { + for (auto& blocked_buff : v) { + if (blocked_buff.blocked == 0 && blocked_buff.blocked_pet == 0) { + continue; + } + + auto e = BotBlockedBuffsRepository::BotBlockedBuffs{ + .bot_id = b->GetBotID(), + .spell_id = blocked_buff.spell_id, + .blocked = blocked_buff.blocked, + .blocked_pet = blocked_buff.blocked_pet + }; + + l.push_back(e); + } + + if (l.empty()) { + return true; + } + + BotBlockedBuffsRepository::DeleteWhere( + database, + fmt::format( + "`bot_id` = {}", + b->GetBotID() + ) + ); + + const int inserted = BotBlockedBuffsRepository::InsertMany(database, l); + + if (!inserted) { + DeleteBotBlockedBuffs(b->GetBotID()); + return false; + } + } + + return true; +} + +bool BotDatabase::DeleteBotBlockedBuffs(const uint32 bot_id) +{ + if (!bot_id) { + return false; + } + + BotBlockedBuffsRepository::DeleteWhere( + database, + fmt::format( + "`bot_id` = {}", + 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 + ); + } + } + } +} diff --git a/zone/bot_database.h b/zone/bot_database.h index 139ebc643..7dcdebc28 100644 --- a/zone/bot_database.h +++ b/zone/bot_database.h @@ -24,7 +24,7 @@ #include #include #include - +#include "bot_structs.h" class Bot; class Client; @@ -48,7 +48,7 @@ public: /* Bot functions */ - bool QueryNameAvailablity(const std::string& bot_name, bool& available_flag); + bool QueryNameAvailability(const std::string& bot_name, bool& available_flag); bool QueryBotCount(const uint32 owner_id, int class_id, uint32& bot_count, uint32& bot_class_count); bool LoadBotsList(const uint32 owner_id, std::list& bots_list, bool by_account = false); @@ -74,6 +74,9 @@ public: bool SaveTimers(Bot* b); bool DeleteTimers(const uint32 bot_id); + bool LoadBotBlockedBuffs(Bot* b); + bool SaveBotBlockedBuffs(Bot* b); + bool DeleteBotBlockedBuffs(const uint32 bot_id); /* Bot inventory functions */ bool QueryInventoryCount(const uint32 bot_id, uint32& item_count); @@ -88,10 +91,6 @@ public: bool SaveEquipmentColor(const uint32 bot_id, const int16 slot_id, const uint32 color); - bool SaveExpansionBitmask(const uint32 bot_id, const int expansion_bitmask); - bool SaveEnforceSpellSetting(const uint32 bot_id, const bool enforce_spell_setting); - - /* Bot pet functions */ bool LoadPetIndex(const uint32 bot_id, uint32& pet_index); bool LoadPetSpellID(const uint32 bot_id, uint32& pet_spell_id); @@ -120,26 +119,20 @@ public: bool SaveAllArmorColorBySlot(const uint32 owner_id, const int16 slot_id, const uint32 rgb_value); bool SaveAllArmorColors(const uint32 owner_id, const uint32 rgb_value); - bool SaveHelmAppearance(const uint32 bot_id, const bool show_flag = true); - bool SaveAllHelmAppearances(const uint32 owner_id, const bool show_flag = true); - - bool ToggleAllHelmAppearances(const uint32 owner_id); - - bool SaveFollowDistance(const uint32 bot_id, const uint32 follow_distance); - bool SaveAllFollowDistances(const uint32 owner_id, const uint32 follow_distance); - bool CreateCloneBot(const uint32 bot_id, const std::string& clone_name, uint32& clone_id); bool CreateCloneBotInventory(const uint32 bot_id, const uint32 clone_id); - bool SaveStopMeleeLevel(const uint32 bot_id, const uint8 sml_value); - - bool SaveBotArcherSetting(const uint32 bot_id, const bool bot_archer_setting); - bool LoadOwnerOptions(Client *owner); bool SaveOwnerOption(const uint32 owner_id, size_t type, const bool flag); bool SaveOwnerOption(const uint32 owner_id, const std::pair type, const std::pair flag); - bool SaveBotCasterRange(const uint32 bot_id, const uint32 bot_caster_range_value); + bool LoadBotSettings(Mob* m); + bool SaveBotSettings(Mob* m); + bool DeleteBotSettings(const uint32 bot_id); + + void CheckBotSpells(); + void MapCommandedSpellTypeMinLevels(); + std::map> GetCommandedSpellTypesMinLevels() { return commanded_spell_type_min_levels; } /* Bot group functions */ bool LoadGroupedBotsByGroupID(const uint32 owner_id, const uint32 group_id, std::list& group_list); @@ -210,12 +203,6 @@ public: static const char* SaveAllInspectMessages(); static const char* SaveAllArmorColorBySlot(); static const char* SaveAllArmorColors(); - static const char* SaveAllHelmAppearances(); - static const char* ToggleAllHelmAppearances(); - static const char* SaveFollowDistance(); - static const char* SaveAllFollowDistances(); - static const char* SaveStopMeleeLevel(); - static const char* SaveBotCasterRange(); /* fail::Bot heal rotation functions */ static const char* LoadHealRotation(); @@ -228,6 +215,10 @@ public: private: std::string query; + + protected: + std::map> commanded_spell_type_min_levels; + }; #endif diff --git a/zone/bot_raid.cpp b/zone/bot_raid.cpp index 25d98ef86..e1eb60748 100644 --- a/zone/bot_raid.cpp +++ b/zone/bot_raid.cpp @@ -96,19 +96,10 @@ void Raid::HandleBotGroupDisband(uint32 owner, uint32 gid) // Remove the entire BOT group in this case if (b && gid != RAID_GROUPLESS && IsRaidMember(b->GetName()) && IsGroupLeader(b->GetName())) { auto r_group_members = GetRaidGroupMembers(GetGroup(b->GetName())); - auto g = new Group(b); - entity_list.AddGroup(g); - g->AddToGroup(b); - database.SetGroupLeaderName(g->GetID(), b->GetName()); for (auto m: r_group_members) { if (m.member->IsBot()) { auto b_member = m.member->CastToBot(); - if (strcmp(b_member->GetName(), b->GetName()) == 0) { - b->SetFollowID(owner); - } else { - Bot::AddBotToGroup(b_member, g); - } Bot::RemoveBotFromRaid(b_member); } } @@ -142,26 +133,6 @@ void Raid::HandleOfflineBots(uint32 owner) { } } -uint8 Bot::GetNumberNeedingHealedInRaidGroup(uint8& need_healed, uint8 hpr, bool includePets, Raid* raid) { - - if (raid) { - uint32 r_group = raid->GetGroup(GetName()); - - for (auto& m: raid->GetRaidGroupMembers(r_group)) { - if (m.member && !m.member->qglobal) { - if (m.member->GetHPRatio() <= hpr) { - need_healed++; - } - - if (includePets && m.member->GetPet() && m.member->GetPet()->GetHPRatio() <= hpr) { - need_healed++; - } - } - } - } - return need_healed; -} - void Bot::ProcessRaidInvite(Mob* invitee, Client* invitor, bool group_invite) { if (!invitee || !invitor) { @@ -176,13 +147,7 @@ void Bot::ProcessRaidInvite(Mob* invitee, Client* invitor, bool group_invite) { // If the Bot Owner is in our raid we need to be able to invite their Bots } else if (invitee->IsBot() && (invitee->CastToBot()->GetBotOwnerCharacterID() != invitor->CharacterID())) { - invitor->Message( - Chat::Red, - fmt::format( - "{} is not your Bot. You can only invite your own Bots, or Bots that belong to a Raid member.", - invitee->GetCleanName() - ).c_str() - ); + invitor->Message(Chat::Red, "%s's owner needs to be in your raid to be able to invite them.", invitee->GetCleanName()); return; } @@ -257,10 +222,6 @@ void Bot::CreateBotRaid(Mob* invitee, Client* invitor, bool group_invite, Raid* } else { raid->AddBot(b); } - - if (new_raid) { - invitee->SetFollowID(invitor->GetID()); - } } } @@ -323,7 +284,9 @@ void Client::SpawnRaidBotsOnConnect(Raid* raid) { if (bot) { bot->SetRaidGrouped(true); + bot->SetStoredRaid(raid); bot->p_raid_instance = raid; + bot->SetVerifiedRaid(false); } } } diff --git a/zone/bot_structs.h b/zone/bot_structs.h index 50e319e9e..2917cfc0b 100644 --- a/zone/bot_structs.h +++ b/zone/bot_structs.h @@ -20,6 +20,8 @@ #define BOT_STRUCTS #include "../common/types.h" +#include "../common/timer.h" +#include "mob.h" #include @@ -64,9 +66,9 @@ struct BotSpellSetting { bool is_enabled = true; }; -struct BotSpells_Struct { +struct BotSpells { uint32 type; // 0 = never, must be one (and only one) of the defined values - int16 spellid; // <= 0 = no spell + uint16 spellid; // <= 0 = no spell int16 manacost; // -1 = use spdat, -2 = no cast time uint32 time_cancast; // when we can cast this spell next int32 recast_delay; @@ -81,7 +83,25 @@ struct BotSpells_Struct { uint8 bucket_comparison; }; -struct BotTimer_Struct { +struct BotSpells_wIndex { + uint32 index; //index of AIBot_spells + uint32 type; // 0 = never, must be one (and only one) of the defined values + uint16 spellid; // <= 0 = no spell + int16 manacost; // -1 = use spdat, -2 = no cast time + uint32 time_cancast; // when we can cast this spell next + int32 recast_delay; + int16 priority; + int16 resist_adjust; + uint8 minlevel; + uint8 maxlevel; + int16 min_hp; // >0 won't cast if HP is below + int16 max_hp; // >0 won't cast if HP is above + std::string bucket_name; + std::string bucket_value; + uint8 bucket_comparison; +}; + +struct BotTimer { uint32 timer_id; uint32 timer_value; uint32 recast_time; @@ -92,4 +112,79 @@ struct BotTimer_Struct { uint32 item_id; }; +struct BotSpellSettings { + uint16 spell_type; // type ID of bot category + std::string short_name; // type short name of bot category + std::string name; // type name of bot category + bool hold; // 0 = allow spell type, 1 = hold spell type + uint16 delay; // delay between casts of spell type, 1ms-60,000ms + uint8 min_threshold; // minimum target health threshold to allow casting of spell type + uint8 max_threshold; // maximum target health threshold to allow casting of spell type + uint16 resist_limit; // resist limit to skip spell type + bool aggro_check; // whether or not to check for possible aggro before casting + uint8 min_mana_pct; // lower mana percentage limit to allow spell cast + uint8 max_mana_pct; // upper mana percentage limit to allow spell cast + uint8 min_hp_pct; // lower HP percentage limit to allow spell cast + uint8 max_hp_pct; // upper HP percentage limit to allow spell cast + uint16 idle_priority; // idle priority of the spell type + uint16 engaged_priority; // engaged priority of the spell type + uint16 pursue_priority; // pursue priority of the spell type + uint16 ae_or_group_target_count; // require target count to cast an AE or Group spell type + uint16 announce_cast; // announce when casting a certain spell type + Timer recast_timer; // recast timer based off delay + Timer ai_delay; // spell timer based off delay +}; + +struct BotSpellTypeOrder { + uint16 spellType; + uint16 priority; +}; + +struct BotBlockedBuffs { + uint32_t bot_id; + uint32_t spell_id; + uint8_t blocked; + uint8_t blocked_pet; +}; + +struct BotSpellTypesByClass { + uint8_t min_level = 255; + std::string description; +}; + +struct CombatRangeInput { + Mob* target; + float target_distance; + bool stop_melee_level; + const EQ::ItemInstance* p_item; + const EQ::ItemInstance* s_item; +}; + +struct CombatRangeOutput { + bool at_combat_range = false; + float melee_distance_min = 0.0f; + float melee_distance = 0.0f; + float melee_distance_max = 0.0f; +}; + +struct CombatPositioningInput { + Mob* tar; + bool stop_melee_level; + float tar_distance; + float melee_distance_min; + float melee_distance; + float melee_distance_max; + bool behind_mob; + bool front_mob; +}; + +struct FindPositionInput { + Mob* tar; + float distance_min; + float distance_max; + bool behind_only; + bool front_only; + bool bypass_los; +}; + #endif // BOT_STRUCTS diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index 0050a58b2..b6ad79214 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -21,1226 +21,547 @@ #include "../common/repositories/bot_spells_entries_repository.h" #include "../common/repositories/npc_spells_repository.h" -bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { - - // Bot AI - Raid* raid = entity_list.GetRaidByBotName(GetName()); - +bool Bot::AICastSpell(Mob* tar, uint8 chance, uint16 spell_type, uint16 sub_target_type, uint16 sub_type) { if (!tar) { return false; } - if (!AI_HasSpells()) { + LogBotSpellChecksDetail("{} says, 'Attempting {} AICastSpell on {}.'", GetCleanName(), GetSpellTypeNameByID(spell_type), tar->GetCleanName()); + + if ( + !AI_HasSpells() || + (spell_type == BotSpellTypes::Pet && tar != this) || + (IsPetBotSpellType(spell_type) && !tar->IsPet()) || + (!IsPetBotSpellType(spell_type) && tar->IsPet()) || + (!RuleB(Bots, AllowBuffingHealingFamiliars) && tar->IsFamiliar()) || + (tar->IsPet() && tar->IsCharmed() && spell_type == BotSpellTypes::PetBuffs && !RuleB(Bots, AllowCharmedPetBuffs)) || + (tar->IsPet() && tar->IsCharmed() && spell_type == BotSpellTypes::PetCures && !RuleB(Bots, AllowCharmedPetCures)) || + (tar->IsPet() && tar->IsCharmed() && IsHealBotSpellType(spell_type) && !RuleB(Bots, AllowCharmedPetHeals)) + ) { return false; } - if (iChance < 100 && zone->random.Int(0, 100) > iChance) { + if ( + !IsCommandedSpell() && + zone->random.Int(0, 100) > chance + ) { return false; } - if (tar->GetAppearance() == eaDead && ((tar->IsClient() && !tar->CastToClient()->GetFeigned()) || tar->IsBot())) { - return false; - } - - uint8 botClass = GetClass(); - uint8 botLevel = GetLevel(); - - bool checked_los = false; //we do not check LOS until we are absolutely sure we need to, and we only do it once. - - BotSpell botSpell; - botSpell.SpellId = 0; - botSpell.SpellIndex = 0; - botSpell.ManaCost = 0; - - switch (iSpellTypes) { - case SpellType_Mez: - return BotCastMez(tar, botLevel, checked_los, botSpell, raid); - case SpellType_Heal: - return BotCastHeal(tar, botLevel, botClass, botSpell, raid); - case SpellType_Root: - return BotCastRoot(tar, botLevel, iSpellTypes, botSpell, checked_los); - case SpellType_Buff: - return BotCastBuff(tar, botLevel, botClass); - case SpellType_Escape: - return BotCastEscape(tar, botClass, botSpell, iSpellTypes); - case SpellType_Nuke: - return BotCastNuke(tar, botLevel, botClass, botSpell, checked_los); - case SpellType_Dispel: - return BotCastDispel(tar, botSpell, iSpellTypes, checked_los); - case SpellType_Pet: - return BotCastPet(tar, botClass, botSpell); - case SpellType_InCombatBuff: - return BotCastCombatBuff(tar, botLevel, botClass); - case SpellType_Lifetap: - return BotCastLifetap(tar, botLevel, botSpell, checked_los, iSpellTypes); - case SpellType_Snare: - return BotCastSnare(tar, botLevel, botSpell, checked_los, iSpellTypes); - case SpellType_DOT: - return BotCastDOT(tar, botLevel, botSpell, checked_los); - case SpellType_Slow: - return BotCastSlow(tar, botLevel, botClass, botSpell, checked_los, raid); - case SpellType_Debuff: - return BotCastDebuff(tar, botLevel, botSpell, checked_los); - case SpellType_Cure: - return BotCastCure(tar, botClass, botSpell, raid); - case SpellType_HateRedux: - return BotCastHateReduction(tar, botLevel, botSpell); - case SpellType_InCombatBuffSong: - return BotCastCombatSong(tar, botLevel); - case SpellType_OutOfCombatBuffSong: - return BotCastSong(tar, botLevel); - case SpellType_Resurrect: - case SpellType_PreCombatBuff: - case SpellType_PreCombatBuffSong: - default: + if ((spell_type != BotSpellTypes::Resurrect && spell_type != BotSpellTypes::SummonCorpse) && tar->GetAppearance() == eaDead) { + if (!((tar->IsClient() && tar->CastToClient()->GetFeigned()) || tar->IsBot())) { return false; + } + } + + uint8 bot_class = GetClass(); + + SetCastedSpellType(UINT16_MAX); // this is for recast timers + SetTempSpellType(spell_type); // this is for spell checks + + BotSpell bot_spell; + bot_spell.SpellId = 0; + bot_spell.SpellIndex = 0; + bot_spell.ManaCost = 0; + + if (!BotSpellTypeRequiresLoS(spell_type) || tar == this) { + SetHasLoS(true); + } + + switch (spell_type) { + case BotSpellTypes::Slow: + if (tar->GetSpecialAbility(SpecialAbility::SlowImmunity)) { + return false; + } + + break; + case BotSpellTypes::Snare: + if (tar->GetSpecialAbility(SpecialAbility::SnareImmunity)) { + return false; + } + + break; + case BotSpellTypes::AELull: + case BotSpellTypes::Lull: + if (tar->GetSpecialAbility(SpecialAbility::PacifyImmunity)) { + return false; + } + + break; + case BotSpellTypes::Fear: + if (tar->GetSpecialAbility(SpecialAbility::FearImmunity)) { + return false; + } + + if (!IsCommandedSpell() && (tar->IsRooted() || tar->GetSnaredAmount() == -1)) { + return false; + } + + break; + case BotSpellTypes::Dispel: + if (tar->GetSpecialAbility(SpecialAbility::DispellImmunity)) { + return false; + } + + if (!IsCommandedSpell() && tar->CountDispellableBuffs() <= 0) { + return false; + } + + break; + case BotSpellTypes::HateRedux: + if (!IsCommandedSpell() && !GetNeedsHateRedux(tar)) { + return false; + } + + break; + case BotSpellTypes::InCombatBuff: + if (!IsCommandedSpell() && GetClass() != Class::Shaman && spell_type == BotSpellTypes::InCombatBuff && IsCasterClass(GetClass()) && GetLevel() >= GetStopMeleeLevel()) { + return false; + } + + break; + case BotSpellTypes::HateLine: + if (!tar->IsNPC()) { + return false; + } + + break; + case BotSpellTypes::InCombatBuffSong: + case BotSpellTypes::Buff: + case BotSpellTypes::PetBuffs: + case BotSpellTypes::PreCombatBuff: + case BotSpellTypes::DamageShields: + case BotSpellTypes::PetDamageShields: + case BotSpellTypes::ResistBuffs: + case BotSpellTypes::PetResistBuffs: + case BotSpellTypes::Teleport: + case BotSpellTypes::Succor: + case BotSpellTypes::BindAffinity: + case BotSpellTypes::Identify: + case BotSpellTypes::Levitate: + case BotSpellTypes::Rune: + case BotSpellTypes::WaterBreathing: + case BotSpellTypes::Size: + case BotSpellTypes::Invisibility: + case BotSpellTypes::MovementSpeed: + case BotSpellTypes::SendHome: + if (!tar->IsOfClientBot() && !(tar->IsPet() && tar->GetOwner() && tar->GetOwner()->IsOfClientBot())) { + return false; + } + + break; + case BotSpellTypes::PreCombatBuffSong: + case BotSpellTypes::OutOfCombatBuffSong: + if (!IsCommandedSpell() && (IsEngaged() || tar->IsEngaged())) { // Out-of-Combat songs can not be cast in combat + return false; + } + + break; + case BotSpellTypes::AEMez: + case BotSpellTypes::Mez: + return BotCastMez(tar, bot_class, bot_spell, spell_type); + case BotSpellTypes::AENukes: + case BotSpellTypes::AERains: + case BotSpellTypes::PBAENuke: + case BotSpellTypes::Nuke: + case BotSpellTypes::AEStun: + case BotSpellTypes::Stun: + return BotCastNuke(tar, bot_class, bot_spell, spell_type); + case BotSpellTypes::RegularHeal: + case BotSpellTypes::GroupCompleteHeals: + case BotSpellTypes::CompleteHeal: + case BotSpellTypes::FastHeals: + case BotSpellTypes::VeryFastHeals: + case BotSpellTypes::GroupHeals: + case BotSpellTypes::GroupHoTHeals: + case BotSpellTypes::HoTHeals: + case BotSpellTypes::PetRegularHeals: + case BotSpellTypes::PetCompleteHeals: + case BotSpellTypes::PetFastHeals: + case BotSpellTypes::PetVeryFastHeals: + case BotSpellTypes::PetHoTHeals: + if (!tar->IsOfClientBot() && !(tar->IsPet() && tar->GetOwner() && tar->GetOwner()->IsOfClientBot())) { + return false; + } + + return BotCastHeal(tar, bot_class, bot_spell, spell_type); + case BotSpellTypes::GroupCures: + case BotSpellTypes::Cure: + case BotSpellTypes::PetCures: + if (!tar->IsOfClientBot() && !(tar->IsPet() && tar->GetOwner() && tar->GetOwner()->IsOfClientBot())) { + return false; + } + + return BotCastCure(tar, bot_class, bot_spell, spell_type); + case BotSpellTypes::Pet: + if (HasPet() || IsBotCharmer()) { + return false; + } + + return BotCastPet(tar, bot_class, bot_spell, spell_type); + case BotSpellTypes::Resurrect: + if (!tar->IsCorpse() || !tar->CastToCorpse()->IsPlayerCorpse()) { + return false; + } + + break; + case BotSpellTypes::Charm: + if (HasPet() || tar->IsCharmed() || !tar->IsNPC() || tar->GetSpecialAbility(SpecialAbility::CharmImmunity)) { + return false; + } + + break; + default: + break; + } + + std::vector 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 (BotRequiresLoSToCast(spell_type, s.SpellId) && !HasLoS()) { + continue; + } + + if (IsInvulnerabilitySpell(s.SpellId)) { + tar = this; //target self for invul type spells + } + + if (IsCommandedSpell() && IsCasting()) { + RaidGroupSay( + fmt::format( + "Interrupting {}. I have been commanded to try to cast a [{}] spell, {} on {}.", + CastingSpellID() ? spells[CastingSpellID()].name : "my spell", + GetSpellTypeNameByID(spell_type), + spells[s.SpellId].name, + tar->GetCleanName() + ).c_str() + ); + + InterruptSpell(); + } + + if (AIDoSpellCast(s.SpellIndex, tar, s.ManaCost)) { + if (BotSpellTypeUsesTargetSettings(spell_type)) { + SetCastedSpellType(UINT16_MAX); + + if (!IsCommandedSpell()) { + SetBotSpellRecastTimer(spell_type, tar, true); + } + } + else { + SetCastedSpellType(spell_type); + } + + RaidGroupSay( + fmt::format( + "Casting {} [{}] on {}.", + GetSpellName(s.SpellId), + GetSpellTypeNameByID(spell_type), + (tar == this ? "myself" : tar->GetCleanName()) + ).c_str() + ); + + return true; + } } return false; } -bool Bot::BotCastSong(Mob* tar, uint8 botLevel) { - bool casted_spell = false; - if (GetClass() != Class::Bard || tar != this || IsEngaged()) // Out-of-Combat songs can not be cast in combat - return casted_spell; +bool Bot::BotCastMez(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spell_type) { + std::vector bot_spell_list = GetPrioritizedBotSpellsBySpellType(this, spell_type, tar, IsAEBotSpellType(spell_type)); - for (auto botSongList = GetPrioritizedBotSpellsBySpellType(this, SpellType_OutOfCombatBuffSong); - auto iter : botSongList) { - if (!iter.SpellId) - continue; - if (!CheckSpellRecastTimer(iter.SpellId)) - continue; - if (!IsSpellUsableInThisZoneType(iter.SpellId, zone->GetZoneType())) - continue; - switch (spells[iter.SpellId].target_type) { - case ST_AEBard: - case ST_AECaster: - case ST_GroupTeleport: - case ST_Group: - case ST_Self: - break; - default: + for (const auto& s : bot_spell_list) { + if (!IsValidSpell(s.SpellId)) { continue; } - if (tar->CanBuffStack(iter.SpellId, botLevel, true) < 0) - continue; - casted_spell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost); - if (casted_spell) - break; - } - - return casted_spell; -} - -bool Bot::BotCastCombatSong(Mob* tar, uint8 botLevel) { - bool casted_spell = false; - if (tar != this) { // In-Combat songs can be cast Out-of-Combat in preparation for battle - return casted_spell; - } - for (auto botSongList = GetPrioritizedBotSpellsBySpellType(this, SpellType_InCombatBuffSong); - auto iter : botSongList) { - if (!iter.SpellId) - continue; - if (!CheckSpellRecastTimer(iter.SpellId)) - continue; - if (!IsSpellUsableInThisZoneType(iter.SpellId, zone->GetZoneType())) - continue; - switch (spells[iter.SpellId].target_type) { - case ST_AEBard: - case ST_AECaster: - case ST_GroupTeleport: - case ST_Group: - case ST_Self: - break; - default: + if (BotRequiresLoSToCast(spell_type, s.SpellId) && !HasLoS()) { continue; } - if (tar->CanBuffStack(iter.SpellId, botLevel, true) < 0) - continue; - casted_spell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost); - if (casted_spell) - break; - } + if (!IsCommandedSpell()) { + Mob* add_mob = GetFirstIncomingMobToMez(this, s.SpellId, spell_type, IsAEBotSpellType(spell_type)); - return casted_spell; -} - -bool Bot::BotCastHateReduction(Mob* tar, uint8 botLevel, const BotSpell& botSpell) { - bool casted_spell = false; - if (GetClass() == Class::Bard) { - std::list botSongList = GetPrioritizedBotSpellsBySpellType(this, SpellType_HateRedux); - for (auto iter : botSongList) { - if (!iter.SpellId) - continue; - if (!CheckSpellRecastTimer(iter.SpellId)) - continue; - if (!IsSpellUsableInThisZoneType(iter.SpellId, zone->GetZoneType())) - continue; - if (spells[iter.SpellId].target_type != ST_Target) - continue; - if (tar->CanBuffStack(iter.SpellId, botLevel, true) < 0) - continue; - - if (IsValidSpellRange(iter.SpellId, tar)) { - casted_spell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost); - } - if (casted_spell) { - BotGroupSay( - this, - fmt::format( - "Attempting to reduce hate on {} with {}.", - tar->GetCleanName(), - spells[iter.SpellId].name - ).c_str() - ); - } - } - } - - return casted_spell; -} - -bool Bot::BotCastCure(Mob* tar, uint8 botClass, BotSpell& botSpell, Raid* raid) { - bool casted_spell = false; - if ( - GetNeedsCured(tar) && - (tar->DontCureMeBefore() < Timer::GetCurrentTime()) && - GetNumberNeedingHealedInGroup(25, false, raid) <= 0 && - GetNumberNeedingHealedInGroup(40, false, raid) <= 2 - ) { - botSpell = GetBestBotSpellForCure(this, tar); - - if (!IsValidSpell(botSpell.SpellId)) { - return casted_spell; - } - - uint32 TempDontCureMeBeforeTime = tar->DontCureMeBefore(); - - casted_spell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontCureMeBeforeTime); - - if (casted_spell && botClass != Class::Bard) { - if (IsGroupSpell(botSpell.SpellId)) { - if (HasGroup()) { - Group const* group = GetGroup(); - if (group) { - for (auto member : group->members) { - if ( - member && - !member->qglobal && - TempDontCureMeBeforeTime != tar->DontCureMeBefore() - ) { - member->SetDontCureMeBefore(Timer::GetCurrentTime() + 4000); - } - } - } - } else if (IsRaidGrouped()) { - uint32 r_group = raid->GetGroup(GetName()); - if (r_group) { - std::vector raid_group_members = raid->GetRaidGroupMembers(r_group); - for (auto& iter: raid_group_members) { - if ( - iter.member && - !iter.member->qglobal && - TempDontCureMeBeforeTime != tar->DontCureMeBefore() - ) { - iter.member->SetDontCureMeBefore(Timer::GetCurrentTime() + 4000); - } - } - } - } - } else if (TempDontCureMeBeforeTime != tar->DontCureMeBefore()) { - tar->SetDontCureMeBefore(Timer::GetCurrentTime() + 4000); - } - } - } - return casted_spell; -} - -bool Bot::BotCastDebuff(Mob* tar, uint8 botLevel, BotSpell& botSpell, bool checked_los) { - bool casted_spell = false; - if ((tar->GetHPRatio() <= 99.0f) && (tar->GetHPRatio() > 20.0f)) - { - if (!checked_los && (!CheckLosFN(tar) || !CheckWaterLoS(tar))) { - return casted_spell; - } - - botSpell = GetBestBotSpellForResistDebuff(this, tar); - - if (!IsValidSpell(botSpell.SpellId)) { - botSpell = GetDebuffBotSpell(this, tar); - } - - if (!IsValidSpell(botSpell.SpellId)) { - return casted_spell; - } - - if (! - ( - !tar->IsImmuneToSpell(botSpell.SpellId, this) && - (tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0) - ) - ) { - return casted_spell; - } - - if (IsValidSpellRange(botSpell.SpellId, tar)) { - casted_spell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); - } - } - return casted_spell; -} - -bool Bot::BotCastSlow(Mob* tar, uint8 botLevel, uint8 botClass, BotSpell& botSpell, const bool& checked_los, Raid* raid) { - bool casted_spell = false; - if (tar->GetHPRatio() <= 99.0f) { - - if (!checked_los && (!CheckLosFN(tar) || !CheckWaterLoS(tar))) { - return casted_spell; - } - - switch (botClass) { - case Class::Bard: { - // probably needs attackable check - for ( - auto botSongList = GetPrioritizedBotSpellsBySpellType(this, SpellType_Slow); - auto iter : botSongList - ) { - - if (!iter.SpellId) { - continue; - } - - if (!CheckSpellRecastTimer(iter.SpellId)) { - continue; - } - - if (!IsSpellUsableInThisZoneType(iter.SpellId, zone->GetZoneType())) { - continue; - } - - if (spells[iter.SpellId].target_type != ST_Target) { - continue; - } - - if (tar->CanBuffStack(iter.SpellId, botLevel, true) < 0) { - continue; - } - - if (IsValidSpellRange(iter.SpellId, tar)) { - casted_spell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost); - } - - if (casted_spell) { - return casted_spell; - } - } - - break; - } - case Class::Enchanter: { - botSpell = GetBestBotSpellForMagicBasedSlow(this); - break; - } - case Class::Shaman: - case Class::Beastlord: { - botSpell = GetBestBotSpellForDiseaseBasedSlow(this); - - if (botSpell.SpellId == 0 || ((tar->GetMR() - 50) < (tar->GetDR() + spells[botSpell.SpellId].resist_difficulty))) - botSpell = GetBestBotSpellForMagicBasedSlow(this); - break; - } - } - - if (!IsValidSpell(botSpell.SpellId)) { - return casted_spell; - } - - if (!(!tar->IsImmuneToSpell(botSpell.SpellId, this) && tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0)) { - return casted_spell; - } - if (IsValidSpellRange(botSpell.SpellId, tar)) { - casted_spell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); - } - - if (casted_spell && GetClass() != Class::Bard) { - if (raid) { - const auto msg = fmt::format("Attempting to slow {}.", tar->GetCleanName()); - raid->RaidSay(msg.c_str(), GetCleanName(), Language::CommonTongue, Language::MaxValue); - } else { - BotGroupSay( - this, - fmt::format( - "Attempting to slow {} with {}.", - tar->GetCleanName(), - spells[botSpell.SpellId].name - ).c_str() - ); - } - } - } - return casted_spell; -} - -bool Bot::BotCastDOT(Mob* tar, uint8 botLevel, const BotSpell& botSpell, const bool& checked_los) { - bool casted_spell = false; - - if ((tar->GetHPRatio() <= 98.0f) && (tar->DontDotMeBefore() < Timer::GetCurrentTime()) && (tar->GetHPRatio() > 15.0f)) { - if (!checked_los && (!CheckLosFN(tar) || !CheckWaterLoS(tar))) { - return casted_spell; - } - - if (GetClass() == Class::Bard) { - std::list dotList = GetPrioritizedBotSpellsBySpellType(this, SpellType_DOT); - - const int maxDotSelect = 5; - int dotSelectCounter = 0; - - for (const auto& s : dotList) { - - if (!IsValidSpell(s.SpellId)) { - continue; - } - - if (CheckSpellRecastTimer(s.SpellId)) - { - - if (!(!tar->IsImmuneToSpell(s.SpellId, this) && tar->CanBuffStack(s.SpellId, botLevel, true) >= 0)) { - continue; - } - - uint32 TempDontDotMeBefore = tar->DontDotMeBefore(); - - casted_spell = AIDoSpellCast(s.SpellIndex, tar, s.ManaCost, &TempDontDotMeBefore); - - if (TempDontDotMeBefore != tar->DontDotMeBefore()) { - tar->SetDontDotMeBefore(TempDontDotMeBefore); - } - } - - dotSelectCounter++; - - if ((dotSelectCounter == maxDotSelect) || casted_spell) { - break; - } - } - } - else { - std::list dotList = GetBotSpellsBySpellType(this, SpellType_DOT); - - const int maxDotSelect = 5; - int dotSelectCounter = 0; - - for (const auto& s : dotList) { - - if (!IsValidSpell(s.SpellId)) { - continue; - } - - if (CheckSpellRecastTimer(s.SpellId)) { - - if (!(!tar->IsImmuneToSpell(s.SpellId, this) && - tar->CanBuffStack(s.SpellId, botLevel, true) >= 0)) { - continue; - } - - uint32 TempDontDotMeBefore = tar->DontDotMeBefore(); - - if (IsValidSpellRange(s.SpellId, tar)) { - casted_spell = AIDoSpellCast(s.SpellIndex, tar, s.ManaCost, &TempDontDotMeBefore); - } - - if (TempDontDotMeBefore != tar->DontDotMeBefore()) { - tar->SetDontDotMeBefore(TempDontDotMeBefore); - } - } - - dotSelectCounter++; - - if ((dotSelectCounter == maxDotSelect) || casted_spell) { - return casted_spell; - } - } - } - } - return casted_spell; -} - -bool Bot::BotCastSnare(Mob* tar, uint8 botLevel, BotSpell& botSpell, const bool& checked_los, uint32 iSpellTypes) { - bool casted_spell = false; - if (tar->DontSnareMeBefore() < Timer::GetCurrentTime()) { - if (!checked_los && (!CheckLosFN(tar) || !CheckWaterLoS(tar))) { - return casted_spell; - } - - - botSpell = GetFirstBotSpellBySpellType(this, iSpellTypes); - - if (!IsValidSpell(botSpell.SpellId)) { - return casted_spell; - } - - if (!(!tar->IsImmuneToSpell(botSpell.SpellId, this) && tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0)) { - return casted_spell; - } - - uint32 TempDontSnareMeBefore = tar->DontSnareMeBefore(); - - if (IsValidSpellRange(botSpell.SpellId, tar)) { - casted_spell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontSnareMeBefore); - } - - if (TempDontSnareMeBefore != tar->DontSnareMeBefore()) { - tar->SetDontSnareMeBefore(TempDontSnareMeBefore); - } - } - return casted_spell; -} - -bool Bot::BotCastLifetap(Mob* tar, uint8 botLevel, BotSpell& botSpell, const bool& checked_los, uint32 iSpellTypes) { - bool casted_spell = false; - if (GetHPRatio() < 90.0f) { - if (!checked_los && (!CheckLosFN(tar) || !CheckWaterLoS(tar))) { - return casted_spell; - } - - - botSpell = GetFirstBotSpellBySpellType(this, iSpellTypes); - - if (!IsValidSpell(botSpell.SpellId)) { - return casted_spell; - } - - if (!(!tar->IsImmuneToSpell(botSpell.SpellId, this) && (tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0))) { - return casted_spell; - } - - if (IsValidSpellRange(botSpell.SpellId, tar)) { - casted_spell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); - } - } - return casted_spell; -} - -bool Bot::BotCastCombatBuff(Mob* tar, uint8 botLevel, uint8 botClass) { - - bool casted_spell = false; - if (tar->DontBuffMeBefore() < Timer::GetCurrentTime()) { - std::list buffSpellList = GetBotSpellsBySpellType(this, SpellType_InCombatBuff); - - for (const auto& s : buffSpellList) { - - if (!IsValidSpell(s.SpellId)) { - continue; - } - // no buffs with illusions.. use #bot command to cast illusions - if (IsEffectInSpell(s.SpellId, SE_Illusion) && tar != this) { - continue; - } - //no teleport spells use #bot command to cast teleports - if (IsEffectInSpell(s.SpellId, SE_Teleport) || IsEffectInSpell(s.SpellId, SE_Succor)) { - continue; - } - // can not cast buffs for your own pet only on another pet that isn't yours - if ((spells[s.SpellId].target_type == ST_Pet) && (tar != GetPet())) { + if (!add_mob) { continue; } - //Conversion Spells - if ( - IsSelfConversionSpell(s.SpellId) && - ( - GetManaRatio() > 90.0f || - GetHPRatio() < 50.0f || - GetHPRatio() < (GetManaRatio() + 10.0f) - ) - ) { - break; //don't cast if low hp, lots of mana, or if mana is higher than hps - } - - // Validate target - // TODO: Add ST_TargetsTarget support for Buffing. - if ( - !( - ( - spells[s.SpellId].target_type == ST_Target || - spells[s.SpellId].target_type == ST_Pet || - (tar == this && spells[s.SpellId].target_type != ST_TargetsTarget) || - spells[s.SpellId].target_type == ST_Group || - spells[s.SpellId].target_type == ST_GroupTeleport || - (botClass == Class::Bard && spells[s.SpellId].target_type == ST_AEBard) - ) && - !tar->IsImmuneToSpell(s.SpellId, this) && - tar->CanBuffStack(s.SpellId, botLevel, true) >= 0 - ) - ) { - continue; - } - - // Put the zone levitate and movement check here since bots are able to bypass the client casting check - if ( - ((IsEffectInSpell(s.SpellId, SE_Levitate) && !zone->CanLevitate()) || - (IsEffectInSpell(s.SpellId, SE_MovementSpeed) && !zone->CanCastOutdoor())) && - (botClass != Class::Bard || !IsSpellUsableInThisZoneType(s.SpellId, zone->GetZoneType())) - ) { - continue; - } - - if (!IsGroupSpell(s.SpellId)) { - //Only check archetype if spell is not a group spell - //Hybrids get all buffs - switch (tar->GetArchetype()) { - case Archetype::Caster: - //TODO: probably more caster specific spell effects in here - if ( - ( - IsEffectInSpell(s.SpellId, SE_AttackSpeed) || - IsEffectInSpell(s.SpellId, SE_ATK) || - IsEffectInSpell(s.SpellId, SE_STR) || - IsEffectInSpell(s.SpellId, SE_ReverseDS) || - IsEffectInSpell(s.SpellId, SE_DamageShield) - ) && - spells[s.SpellId].target_type != ST_Self - ) { - continue; - } - break; - case Archetype::Melee: - if ( - ( - IsEffectInSpell(s.SpellId, SE_IncreaseSpellHaste) || - IsEffectInSpell(s.SpellId, SE_ManaPool) || - IsEffectInSpell(s.SpellId, SE_CastingLevel) || - IsEffectInSpell(s.SpellId, SE_ManaRegen_v2) || - IsEffectInSpell(s.SpellId, SE_CurrentMana) - ) && - spells[s.SpellId].target_type != ST_Self - ) { - continue; - } - break; - default: - break; - } - } - // TODO: Add TriggerSpell Support for Exchanter Runes - if (botClass == Class::Enchanter && IsEffectInSpell(s.SpellId, SE_Rune)) { - float manaRatioToCast = 75.0f; - - switch(GetBotStance()) { - case Stance::Efficient: - manaRatioToCast = 90.0f; - break; - case Stance::Balanced: - case Stance::Aggressive: - manaRatioToCast = 75.0f; - break; - case Stance::Reactive: - case Stance::Burn: - case Stance::AEBurn: - manaRatioToCast = 50.0f; - break; - default: - manaRatioToCast = 75.0f; - break; - } - - //If we're at specified mana % or below, don't rune as enchanter - if (GetManaRatio() <= manaRatioToCast) { - break; - } - } - - if (CheckSpellRecastTimer(s.SpellId)) { - uint32 TempDontBuffMeBefore = tar->DontBuffMeBefore(); - casted_spell = AIDoSpellCast(s.SpellIndex, tar, s.ManaCost, &TempDontBuffMeBefore); - if (TempDontBuffMeBefore != tar->DontBuffMeBefore()) { - tar->SetDontBuffMeBefore(TempDontBuffMeBefore); - } - } - - if (casted_spell) { - return casted_spell; - } - } - } - return casted_spell; -} - -bool Bot::BotCastPet(Mob* tar, uint8 botClass, BotSpell& botSpell) { - bool casted_spell = false; - if (!IsPet() && !GetPetID() && !IsBotCharmer()) { - if (botClass == Class::Wizard) { - auto buffs_max = GetMaxBuffSlots(); - auto my_buffs = GetBuffs(); - int familiar_buff_slot = -1; - if (buffs_max && my_buffs) { - for (int index = 0; index < buffs_max; ++index) { - if (IsEffectInSpell(my_buffs[index].spellid, SE_Familiar)) { - MakePet(my_buffs[index].spellid, spells[my_buffs[index].spellid].teleport_zone); - familiar_buff_slot = index; - break; - } - } - } - if (GetPetID()) { - return casted_spell; - } - if (familiar_buff_slot >= 0) { - BuffFadeBySlot(familiar_buff_slot); - return casted_spell; - } - - botSpell = GetFirstBotSpellBySpellType(this, SpellType_Pet); - } - else if (botClass == Class::Magician) { - botSpell = GetBestBotMagicianPetSpell(this); - } - else { - botSpell = GetFirstBotSpellBySpellType(this, SpellType_Pet); + tar = add_mob; } - if (!IsValidSpell(botSpell.SpellId)) { - return casted_spell; - } + if (AIDoSpellCast(s.SpellIndex, tar, s.ManaCost)) { + if (BotSpellTypeUsesTargetSettings(spell_type)) { + SetCastedSpellType(UINT16_MAX); - casted_spell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); - } - return casted_spell; -} - -bool Bot::BotCastDispel(Mob* tar, BotSpell& botSpell, uint32 iSpellTypes, const bool& checked_los) { - - bool casted_spell = false; - if (tar->GetHPRatio() > 95.0f) { - if (!checked_los && (!CheckLosFN(tar) || !CheckWaterLoS(tar))) { - return casted_spell; - } - - botSpell = GetFirstBotSpellBySpellType(this, iSpellTypes); - - if (!IsValidSpell(botSpell.SpellId)) { - return casted_spell; - } - // TODO: Check target to see if there is anything to dispel - - if (tar->CountDispellableBuffs() > 0 && IsValidSpellRange(botSpell.SpellId, tar)) { - casted_spell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); - } - } - return casted_spell; -} - -bool Bot::BotCastNuke(Mob* tar, uint8 botLevel, uint8 botClass, BotSpell& botSpell, const bool& checked_los) { - - bool casted_spell = false; - if ((tar->GetHPRatio() <= 95.0f) || ((botClass == Class::Bard) || (botClass == Class::Shaman) || (botClass == Class::Enchanter) || (botClass == Class::Paladin) || (botClass == Class::ShadowKnight) || (botClass == Class::Warrior))) - { - if (!checked_los && (!CheckLosFN(tar) || !CheckWaterLoS(tar))) { - return casted_spell; - } - - if (botClass == Class::Cleric || botClass == Class::Enchanter) - { - float manaRatioToCast = 75.0f; - - switch(GetBotStance()) { - case Stance::Efficient: - manaRatioToCast = 90.0f; - break; - case Stance::Balanced: - manaRatioToCast = 75.0f; - break; - case Stance::Reactive: - case Stance::Aggressive: - manaRatioToCast = 50.0f; - break; - case Stance::Burn: - case Stance::AEBurn: - manaRatioToCast = 25.0f; - break; - default: - manaRatioToCast = 50.0f; - break; - } - - //If we're at specified mana % or below, don't nuke as cleric or enchanter - if (GetManaRatio() <= manaRatioToCast) - return casted_spell; - } - - if (botClass == Class::Magician || botClass == Class::ShadowKnight || botClass == Class::Necromancer || botClass == Class::Paladin || botClass == Class::Ranger || botClass == Class::Druid || botClass == Class::Cleric) { - if (tar->GetBodyType() == BodyType::Undead || tar->GetBodyType() == BodyType::SummonedUndead || tar->GetBodyType() == BodyType::Vampire) - botSpell = GetBestBotSpellForNukeByTargetType(this, ST_Undead); - else if (tar->GetBodyType() == BodyType::Summoned || tar->GetBodyType() == BodyType::Summoned2 || tar->GetBodyType() == BodyType::Summoned3) - botSpell = GetBestBotSpellForNukeByTargetType(this, ST_Summoned); - } - - if ((botClass == Class::Paladin || botClass == Class::Druid || botClass == Class::Cleric || botClass == Class::Enchanter || botClass == Class::Wizard) && !IsValidSpell(botSpell.SpellId)) { - uint8 stunChance = (tar->IsCasting() ? 30: 15); - - if (botClass == Class::Paladin) { - stunChance = 50; - } - - if (!tar->GetSpecialAbility(SpecialAbility::StunImmunity) && !tar->IsStunned() && (zone->random.Int(1, 100) <= stunChance)) { - botSpell = GetBestBotSpellForStunByTargetType(this, ST_Target); - } - } - - if (botClass == Class::Wizard && botSpell.SpellId == 0) { - botSpell = GetBestBotWizardNukeSpellByTargetResists(this, tar); - } - - if (!IsValidSpell(botSpell.SpellId)) { - botSpell = GetBestBotSpellForNukeByTargetType(this, ST_Target); - } - if (!IsValidSpell(botSpell.SpellId)) { - return casted_spell; - } - if (!(!tar->IsImmuneToSpell(botSpell.SpellId, this) && (tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0))) { - return casted_spell; - } - if (IsFearSpell(botSpell.SpellId) && tar->GetSnaredAmount() == -1 && !tar->IsRooted()) { - return casted_spell; - } - - if (IsValidSpellRange(botSpell.SpellId, tar)) { - casted_spell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); - } - } - return casted_spell; -} - -bool Bot::BotCastEscape(Mob*& tar, uint8 botClass, BotSpell& botSpell, uint32 iSpellTypes) { - - bool casted_spell = false; - auto hpr = (uint8) GetHPRatio(); - bool mayGetAggro = false; - - if (hpr > 15 && ((botClass == Class::Wizard) || (botClass == Class::Enchanter) || (botClass == Class::Ranger))) { - mayGetAggro = HasOrMayGetAggro(); - } - - if (hpr <= 15 || mayGetAggro) - { - botSpell = GetFirstBotSpellBySpellType(this, iSpellTypes); - - if (!IsValidSpell(botSpell.SpellId)) { - return casted_spell; - } - - if (IsInvulnerabilitySpell(botSpell.SpellId)) { - tar = this; //target self for invul type spells - } - - if (IsValidSpellRange(botSpell.SpellId, tar) || botClass == Class::Bard) { - casted_spell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); - } - } - return casted_spell; -} - -bool Bot::BotCastBuff(Mob* tar, uint8 botLevel, uint8 botClass) { - bool casted_spell = false; - if (tar->DontBuffMeBefore() < Timer::GetCurrentTime()) { - std::list buffSpellList = GetBotSpellsBySpellType(this, SpellType_Buff); - - for(const auto& s : buffSpellList) { - - if (!IsValidSpell(s.SpellId)) { - continue; - } - - // no buffs with illusions.. use #bot command to cast illusions - if (IsEffectInSpell(s.SpellId, SE_Illusion) && tar != this) { - continue; - } - - //no teleport spells use #bot command to cast teleports - if (IsEffectInSpell(s.SpellId, SE_Teleport) || IsEffectInSpell(s.SpellId, SE_Succor)) { - continue; - } - // can not cast buffs for your own pet only on another pet that isn't yours - if ((spells[s.SpellId].target_type == ST_Pet) && (tar != GetPet())) { - continue; - } - - // Validate target - // TODO: Add ST_TargetsTarget support for Buffing. - if ( - !( - ( - spells[s.SpellId].target_type == ST_Target || - spells[s.SpellId].target_type == ST_Pet || - (tar == this && spells[s.SpellId].target_type != ST_TargetsTarget) || - spells[s.SpellId].target_type == ST_Group || - spells[s.SpellId].target_type == ST_GroupTeleport || - (botClass == Class::Bard && spells[s.SpellId].target_type == ST_AEBard) - ) && - !tar->IsImmuneToSpell(s.SpellId, this) && - tar->CanBuffStack(s.SpellId, botLevel, true) >= 0 - ) - ) { - continue; - } - - // Put the zone levitate and movement check here since bots are able to bypass the client casting check - if ( - ( - (IsEffectInSpell(s.SpellId, SE_Levitate) && !zone->CanLevitate()) || - (IsEffectInSpell(s.SpellId, SE_MovementSpeed) && !zone->CanCastOutdoor()) - ) && - (botClass != Class::Bard || !IsSpellUsableInThisZoneType(s.SpellId, zone->GetZoneType())) - ) { - continue; - } - - switch (tar->GetArchetype()) - { - case Archetype::Caster: - //TODO: probably more caster specific spell effects in here - if (IsEffectInSpell(s.SpellId, SE_AttackSpeed) || IsEffectInSpell(s.SpellId, SE_ATK) || - IsEffectInSpell(s.SpellId, SE_STR) || IsEffectInSpell(s.SpellId, SE_ReverseDS)) - { - continue; - } - break; - case Archetype::Melee: - if (IsEffectInSpell(s.SpellId, SE_IncreaseSpellHaste) || IsEffectInSpell(s.SpellId, SE_ManaPool) || - IsEffectInSpell(s.SpellId, SE_CastingLevel) || IsEffectInSpell(s.SpellId, SE_ManaRegen_v2) || - IsEffectInSpell(s.SpellId, SE_CurrentMana)) - { - continue; - } - break; - default: //Hybrids get all buffs - break; - } - - if (botClass == Class::Enchanter && IsEffectInSpell(s.SpellId, SE_Rune)) - { - float manaRatioToCast = 75.0f; - - switch (GetBotStance()) { - case Stance::Efficient: - manaRatioToCast = 90.0f; - break; - case Stance::Balanced: - case Stance::Aggressive: - manaRatioToCast = 75.0f; - break; - case Stance::Reactive: - case Stance::Burn: - case Stance::AEBurn: - manaRatioToCast = 50.0f; - break; - default: - manaRatioToCast = 75.0f; - break; - } - - //If we're at specified mana % or below, don't rune as enchanter - if (GetManaRatio() <= manaRatioToCast) { - return casted_spell; - } - } - - if (CheckSpellRecastTimer(s.SpellId)) - { - uint32 TempDontBuffMeBefore = tar->DontBuffMeBefore(); - - casted_spell = AIDoSpellCast(s.SpellIndex, tar, s.ManaCost, &TempDontBuffMeBefore); - - if (TempDontBuffMeBefore != tar->DontBuffMeBefore()) { - tar->SetDontBuffMeBefore(TempDontBuffMeBefore); - } - } - } - } - return casted_spell; -} - -bool Bot::BotCastRoot(Mob* tar, uint8 botLevel, uint32 iSpellTypes, BotSpell& botSpell, const bool& checked_los) { - bool casted_spell = false; - if (!tar->IsRooted() && tar->DontRootMeBefore() < Timer::GetCurrentTime()) { - if (!checked_los && (!CheckLosFN(tar) || !CheckWaterLoS(tar))) { - return casted_spell; - } - - // TODO: If there is a ranger in the group then don't allow root spells - - botSpell = GetFirstBotSpellBySpellType(this, iSpellTypes); - - if (!IsValidSpell(botSpell.SpellId)) { - return casted_spell; - } - if (tar->CanBuffStack(botSpell.SpellId, botLevel, true) == 0) { - return casted_spell; - } - uint32 TempDontRootMeBefore = tar->DontRootMeBefore(); - - if (IsValidSpellRange(botSpell.SpellId, tar)) { - casted_spell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontRootMeBefore); - } - - if (TempDontRootMeBefore != tar->DontRootMeBefore()) { - tar->SetDontRootMeBefore(TempDontRootMeBefore); - } - } - return casted_spell; -} - -bool Bot::BotCastHeal(Mob* tar, uint8 botLevel, uint8 botClass, BotSpell& botSpell, Raid* raid) { - bool casted_spell = false; - if (tar->DontHealMeBefore() < Timer::GetCurrentTime()) { - auto hpr = (uint8)tar->GetHPRatio(); - bool hasAggro = false; - bool isPrimaryHealer = false; - - if (HasGroup() || IsRaidGrouped()) { - isPrimaryHealer = IsGroupHealer(); - } - - if (hpr < 95 || tar->IsClient() || botClass == Class::Bard) { - if (tar->GetClass() == Class::Necromancer && hpr >= 40) { - return false; - } - - if (tar->GetClass() == Class::Shaman && hpr >= 80) { - return false; - } - - // Evaluate the situation - if ((IsEngaged()) && ((botClass == Class::Cleric) || (botClass == Class::Druid) || (botClass == Class::Shaman) || (botClass == Class::Paladin))) { - if (tar->GetTarget() && tar->GetTarget()->GetHateTop() && tar->GetTarget()->GetHateTop() == tar) { - hasAggro = true; - } - - if (hpr < 35) { - botSpell = GetBestBotSpellForFastHeal(this); - } - else if (hpr < 70) { - if (GetNumberNeedingHealedInGroup(60, false, raid) >= 3) { - botSpell = GetBestBotSpellForGroupHeal(this); - } - - if (!IsValidSpell(botSpell.SpellId)) { - botSpell = GetBestBotSpellForPercentageHeal(this); - } - } - else if (hpr < 95) { - if (GetNumberNeedingHealedInGroup(80, false, raid) >= 3) { - botSpell = GetBestBotSpellForGroupHealOverTime(this); - } - - if (hasAggro) { - botSpell = GetBestBotSpellForPercentageHeal(this); - } - } - else { - if (!tar->FindType(SE_HealOverTime)) { - botSpell = GetBestBotSpellForHealOverTime(this); - } - } - } - else if ((botClass == Class::Cleric) || (botClass == Class::Druid) || (botClass == Class::Shaman) || (botClass == Class::Paladin)) { - if (GetNumberNeedingHealedInGroup(40, true, raid) >= 2) { - botSpell = GetBestBotSpellForGroupCompleteHeal(this); - - if (!IsValidSpell(botSpell.SpellId)) { - botSpell = GetBestBotSpellForGroupHeal(this); - } - - if (!IsValidSpell(botSpell.SpellId)) { - botSpell = GetBestBotSpellForGroupHealOverTime(this); - } - - if (hpr < 40 && !IsValidSpell(botSpell.SpellId)) { - botSpell = GetBestBotSpellForPercentageHeal(this); - } - } - else if (GetNumberNeedingHealedInGroup(60, true, raid) >= 2) { - botSpell = GetBestBotSpellForGroupHeal(this); - - if (!IsValidSpell(botSpell.SpellId)) { - botSpell = GetBestBotSpellForGroupHealOverTime(this); - } - - if (hpr < 40 && !IsValidSpell(botSpell.SpellId)) { - botSpell = GetBestBotSpellForPercentageHeal(this); - } - } - else if (hpr < 40) { - botSpell = GetBestBotSpellForPercentageHeal(this); - } - else if (hpr < 75) { - botSpell = GetBestBotSpellForRegularSingleTargetHeal(this); - } - else { - if (hpr < 90 && !tar->FindType(SE_HealOverTime)) { - botSpell = GetBestBotSpellForHealOverTime(this); - } + if (!IsCommandedSpell()) { + SetBotSpellRecastTimer(spell_type, tar, true); } } else { - float hpRatioToCast = 0.0f; - - switch (GetBotStance()) { - case Stance::Efficient: - case Stance::Aggressive: - hpRatioToCast = isPrimaryHealer ? 90.0f : 50.0f; - break; - case Stance::Balanced: - hpRatioToCast = isPrimaryHealer ? 95.0f : 75.0f; - break; - case Stance::Reactive: - hpRatioToCast = isPrimaryHealer ? 100.0f : 90.0f; - break; - case Stance::Burn: - case Stance::AEBurn: - hpRatioToCast = isPrimaryHealer ? 75.0f : 25.0f; - break; - default: - hpRatioToCast = isPrimaryHealer ? 100.0f : 0.0f; - break; - } - - //If we're at specified mana % or below, don't heal as hybrid - if (tar->GetHPRatio() <= hpRatioToCast) { - botSpell = GetBestBotSpellForRegularSingleTargetHeal(this); - } + SetCastedSpellType(spell_type); } - if (!IsValidSpell(botSpell.SpellId)) { - botSpell = GetBestBotSpellForRegularSingleTargetHeal(this); - } - if (!IsValidSpell(botSpell.SpellId)) { - botSpell = GetFirstBotSpellForSingleTargetHeal(this); - } - if (botSpell.SpellId == 0 && botClass == Class::Bard) { - botSpell = GetFirstBotSpellBySpellType(this, SpellType_Heal); - } + RaidGroupSay( + fmt::format( + "Casting {} [{}] on {}.", + GetSpellName(s.SpellId), + GetSpellTypeNameByID(spell_type), + (tar == this ? "myself" : tar->GetCleanName()) + ).c_str() + ); - if (!IsValidSpell(botSpell.SpellId)) { - return false; - } - // Can we cast this spell on this target? - if (!(spells[botSpell.SpellId].target_type==ST_GroupTeleport || spells[botSpell.SpellId].target_type == ST_Target || tar == this) - && tar->CanBuffStack(botSpell.SpellId, botLevel, true) < 0) { - return false; - } - - uint32 TempDontHealMeBeforeTime = tar->DontHealMeBefore(); - - if (IsValidSpellRange(botSpell.SpellId, tar) || botClass == Class::Bard) { - casted_spell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontHealMeBeforeTime); - } - - if (casted_spell && botClass != Class::Bard) { - if (IsGroupSpell(botSpell.SpellId)) { - if (HasGroup()) { - Group *group = GetGroup(); - if (group) { - BotGroupSay( - this, - fmt::format( - "Casting {}.", - spells[botSpell.SpellId].name - ).c_str() - ); - - for (const auto& member : group->members) { - if (member && !member->qglobal) { - member->SetDontHealMeBefore(Timer::GetCurrentTime() + 1000); - } - } - } else if (IsRaidGrouped()) { - uint32 r_group = raid->GetGroup(GetName()); - const auto msg = fmt::format("Casting {}.", spells[botSpell.SpellId].name); - raid->RaidGroupSay(msg.c_str(), GetCleanName(), Language::CommonTongue, Language::MaxValue); - std::vector raid_group_members = raid->GetRaidGroupMembers(r_group); - for (const auto& rgm : raid_group_members) { - if (rgm.member && !rgm.member->qglobal) { - rgm.member->SetDontHealMeBefore(Timer::GetCurrentTime() + 1000); - } - } - } - } else { - if (tar != this) { //we don't need spam of bots healing themselves - BotGroupSay( - this, - fmt::format( - "Casting {} on {}.", - spells[botSpell.SpellId].name, - tar->GetCleanName() - ).c_str() - ); - } - } - } - tar->SetDontHealMeBefore(Timer::GetCurrentTime() + 2000); - } + return true; } } - return casted_spell; + + return false; } -bool Bot::BotCastMez(Mob* tar, uint8 botLevel, bool checked_los, BotSpell& botSpell, Raid* raid) { - bool casted_spell = false; - if (!checked_los && (!CheckLosFN(tar) || !CheckWaterLoS(tar))) { +bool Bot::BotCastCure(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spell_type) { + uint32 current_time = Timer::GetCurrentTime(); + uint32 next_cure_time = tar->DontCureMeBefore(); + + if (!IsCommandedSpell()) { + if ((next_cure_time > current_time) || !GetNeedsCured(tar)) { + return false; + } + } + + bot_spell = GetBestBotSpellForCure(this, tar, spell_type); + + if (!IsValidSpell(bot_spell.SpellId)) { return false; } - //TODO - //Check if single target or AoE mez is best - //if (TARGETS ON MT IS => 3 THEN botSpell = AoEMez) - //if (TARGETS ON MT IS <= 2 THEN botSpell = BestMez) - - botSpell = GetBestBotSpellForMez(this); - - if (!IsValidSpell(botSpell.SpellId)) { + if (BotRequiresLoSToCast(spell_type, bot_spell.SpellId) && !HasLoS()) { return false; } - Mob* addMob = GetFirstIncomingMobToMez(this, botSpell); + if (AIDoSpellCast(bot_spell.SpellIndex, tar, bot_spell.ManaCost)) { + if (IsGroupSpell(bot_spell.SpellId)) { + if (!IsCommandedSpell()) { + for (Mob* m : GatherSpellTargets(false, tar)) { + SetBotSpellRecastTimer(spell_type, m, true); + } + } - if (!addMob) { - return false; - } - - if (!(!addMob->IsImmuneToSpell(botSpell.SpellId, this) && addMob->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0)) { - return false; - } - - if (IsValidSpellRange(botSpell.SpellId, addMob)) { - casted_spell = AIDoSpellCast(botSpell.SpellIndex, addMob, botSpell.ManaCost); - } - if (casted_spell) { - if (raid) { - raid->RaidSay( - GetCleanName(), + RaidGroupSay( fmt::format( - "Attempting to mesmerize {} with {}.", - addMob->GetCleanName(), - spells[botSpell.SpellId].name - ).c_str(), - 0, - 100 - ); - } else { - BotGroupSay( - this, - fmt::format( - "Attempting to mesmerize {} with {}.", - addMob->GetCleanName(), - spells[botSpell.SpellId].name + "Curing the group with {}.", + GetSpellName(bot_spell.SpellId) ).c_str() ); } + else { + if (!IsCommandedSpell()) { + SetBotSpellRecastTimer(spell_type, tar, true); + } + + RaidGroupSay( + fmt::format( + "Curing {} with {}.", + (tar == this ? "myself" : tar->GetCleanName()), + GetSpellName(bot_spell.SpellId) + ).c_str() + ); + } + + return true; } - return casted_spell; + + return false; +} + +bool Bot::BotCastPet(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spell_type) { + if (bot_class == Class::Wizard) { + auto buffs_max = GetMaxBuffSlots(); + auto my_buffs = GetBuffs(); + int familiar_buff_slot = -1; + if (buffs_max && my_buffs) { + for (int index = 0; index < buffs_max; ++index) { + if (IsEffectInSpell(my_buffs[index].spellid, SpellEffect::Familiar)) { + MakePet(my_buffs[index].spellid, spells[my_buffs[index].spellid].teleport_zone); + familiar_buff_slot = index; + break; + } + } + } + if (GetPetID()) { + return false; + } + if (familiar_buff_slot >= 0) { + BuffFadeBySlot(familiar_buff_slot); + return false; + } + + bot_spell = GetFirstBotSpellBySpellType(this, spell_type); + } + else if (bot_class == Class::Magician) { + bot_spell = GetBestBotMagicianPetSpell(this, spell_type); + } + else { + bot_spell = GetFirstBotSpellBySpellType(this, spell_type); + } + + if (!IsValidSpell(bot_spell.SpellId)) { + return false; + } + + if (BotRequiresLoSToCast(spell_type, bot_spell.SpellId) && !HasLoS()) { + return false; + } + + if (AIDoSpellCast(bot_spell.SpellIndex, tar, bot_spell.ManaCost)) { + SetCastedSpellType(spell_type); + + RaidGroupSay( + fmt::format( + "Summoning a pet [{}].", + GetSpellName(bot_spell.SpellId) + ).c_str() + ); + + return true; + } + + return false; +} + +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)); + + if (bot_class == Class::Paladin) { + stun_chance = RuleI(Bots, StunCastChancePaladins); + } + + if ( + !tar->GetSpecialAbility(SpecialAbility::StunImmunity) && + ( + IsCommandedSpell() || + (!tar->IsStunned() && (zone->random.Int(1, 100) <= stun_chance)) + ) + ) { + bot_spell = GetBestBotSpellForStunByTargetType(this, ST_TargetOptional, spell_type, IsAEBotSpellType(spell_type), tar); + } + + if (!IsValidSpell(bot_spell.SpellId)) { + return false; + } + } + + if (!IsValidSpell(bot_spell.SpellId)) { + bot_spell = GetBestBotSpellForNukeByBodyType(this, tar->GetBodyType(), spell_type, IsAEBotSpellType(spell_type), tar); + } + + if (spell_type == BotSpellTypes::Nuke && bot_class == Class::Wizard && !IsValidSpell(bot_spell.SpellId)) { + bot_spell = GetBestBotWizardNukeSpellByTargetResists(this, tar, spell_type); + } + + if (!IsValidSpell(bot_spell.SpellId)) { + std::vector bot_spell_list = GetPrioritizedBotSpellsBySpellType(this, spell_type, tar, IsAEBotSpellType(spell_type)); + + for (const auto& s : bot_spell_list) { + if (!IsValidSpell(s.SpellId)) { + continue; + } + + if (AIDoSpellCast(s.SpellIndex, tar, s.ManaCost)) { + SetCastedSpellType(spell_type); + + RaidGroupSay( + fmt::format( + "Casting {} [{}] on {}.", + GetSpellName(s.SpellId), + GetSpellTypeNameByID(spell_type), + tar->GetCleanName() + ).c_str() + ); + + return true; + } + } + } + else { + if (AIDoSpellCast(bot_spell.SpellIndex, tar, bot_spell.ManaCost)) { + SetCastedSpellType(spell_type); + + RaidGroupSay( + fmt::format( + "Casting {} [{}] on {}.", + GetSpellName(bot_spell.SpellId), + GetSpellTypeNameByID(spell_type), + tar->GetCleanName() + ).c_str() + ); + + return true; + } + } + + return false; +} + +bool Bot::BotCastHeal(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spell_type) { + if (!TargetValidation(tar)) { + return false; + } + + bot_spell = GetSpellByHealType(spell_type, tar); + + if (!IsValidSpell(bot_spell.SpellId)) { + return false; + } + + if (AIDoSpellCast(bot_spell.SpellIndex, tar, bot_spell.ManaCost)) { + if (IsGroupSpell(bot_spell.SpellId)) { + if (bot_class != Class::Bard) { + if (!IsCommandedSpell()) { + for (Mob* m : GatherSpellTargets(false, tar)) { + SetBotSpellRecastTimer(spell_type, m, true); + } + } + } + + RaidGroupSay( + fmt::format( + "Healing the group with {} [{}].", + GetSpellName(bot_spell.SpellId), + GetSpellTypeNameByID(spell_type) + ).c_str() + ); + + } + else { + if (bot_class != Class::Bard) { + if (!IsCommandedSpell()) { + SetBotSpellRecastTimer(spell_type, tar, true); + } + } + + RaidGroupSay( + fmt::format( + "Healing {} with {} [{}].", + (tar == this ? "myself" : tar->GetCleanName()), + GetSpellName(bot_spell.SpellId), + GetSpellTypeNameByID(spell_type) + ).c_str() + ); + } + + return true; + } + + return false; } bool Bot::AIDoSpellCast(int32 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore) { @@ -1260,39 +581,21 @@ 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 & SpellType_Buff + manaCost > hasMana && + !IsEngaged() && + IsBotBuffSpellType(AIBot_spells[i].type) ) { SetMana(manaCost); } float dist2 = 0; - if (AIBot_spells[i].type & SpellType_Escape) { + if (AIBot_spells[i].type == BotSpellTypes::Escape) { dist2 = 0; } else dist2 = DistanceSquared(m_Position, tar->GetPosition()); - if ( - ( - ( - ( - (spells[AIBot_spells[i].spellid].target_type==ST_GroupTeleport && AIBot_spells[i].type == SpellType_Heal) || - spells[AIBot_spells[i].spellid].target_type ==ST_AECaster || - spells[AIBot_spells[i].spellid].target_type ==ST_Group || - spells[AIBot_spells[i].spellid].target_type ==ST_AEBard || - ( - tar == this && spells[AIBot_spells[i].spellid].target_type != ST_TargetsTarget - ) - ) && - dist2 <= spells[AIBot_spells[i].spellid].aoe_range*spells[AIBot_spells[i].spellid].aoe_range - ) || - dist2 <= GetActSpellRange(AIBot_spells[i].spellid, spells[AIBot_spells[i].spellid].range)*GetActSpellRange(AIBot_spells[i].spellid, spells[AIBot_spells[i].spellid].range) - ) && - ( - mana_cost <= GetMana() || - IsBotNonSpellFighter() - ) - ) { + if (IsValidSpellRange(AIBot_spells[i].spellid, tar) && (mana_cost <= GetMana() || IsBotNonSpellFighter())) { casting_spell_AIindex = i; LogAI("spellid [{}] tar [{}] mana [{}] Name [{}]", AIBot_spells[i].spellid, tar->GetName(), mana_cost, spells[AIBot_spells[i].spellid].name); result = Mob::CastSpell(AIBot_spells[i].spellid, tar->GetID(), EQ::spells::CastingSlot::Gem2, spells[AIBot_spells[i].spellid].cast_time, AIBot_spells[i].manacost == -2 ? 0 : mana_cost, oDontDoAgainBefore, -1, -1, 0, &(AIBot_spells[i].resist_adjust)); @@ -1305,32 +608,69 @@ bool Bot::AIDoSpellCast(int32 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain if (!result) { SetMana(hasMana); } - else { - if (CalcSpellRecastTimer(AIBot_spells[i].spellid) > 0) { - SetSpellRecastTimer(AIBot_spells[i].spellid); - } - } return result; } bool Bot::AI_PursueCastCheck() { + if (GetAppearance() == eaDead || delaytimer || spellend_timer.Enabled() || IsFeared() || IsSilenced() || IsAmnesiad() || GetHP() < 0) { + return false; + } + bool result = false; - if (AIautocastspell_timer->Check(false)) { + if (GetTarget() && AIautocastspell_timer->Check(false)) { + + LogAIDetail("Bot Pursue autocast check triggered: [{}]", GetCleanName()); + LogBotSpellChecksDetail("{} says, 'AI_PursueCastCheck started.'", GetCleanName()); AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting. - LogAIDetail("Bot Engaged (pursuing) autocast check triggered. Trying to cast offensive spells"); + if (!IsAttackAllowed(GetTarget())) { + return false; + } - if (!AICastSpell(GetTarget(), 100, SpellType_Snare)) { - if (!AICastSpell(GetTarget(), 100, SpellType_Lifetap) && !AICastSpell(GetTarget(), 100, SpellType_Nuke)) { + auto cast_order = GetSpellTypesPrioritized(BotPriorityCategories::Pursue); + Mob* tar = nullptr; + + for (auto& current_cast : cast_order) { + if (current_cast.priority == 0) { + SetSpellTypeAITimer(current_cast.spellType, RuleI(Bots, AICastSpellTypeHeldDelay)); + LogBotSpellChecksDetail("{} says, '[{}] is priority 0, skipping.'", GetCleanName(), GetSpellTypeNameByID(current_cast.spellType)); + continue; + } + + if (!RuleB(Bots, AllowAIMez) && (current_cast.spellType == BotSpellTypes::AEMez || current_cast.spellType == BotSpellTypes::Mez)) { + continue; + } + + if (IsCommandedBotSpellType(current_cast.spellType)) { // Unsupported by AI currently. + continue; + } + + if (AIBot_spells_by_type[current_cast.spellType].empty() && AIBot_spells_by_type[GetParentSpellType(current_cast.spellType)].empty()) { + continue; + } + + if (!SpellTypeAIDelayCheck(current_cast.spellType)) { + continue; + } + + result = AttemptAICastSpell(current_cast.spellType, nullptr); + + if (!result && IsBotSpellTypeBeneficial(current_cast.spellType)) { + result = AttemptCloseBeneficialSpells(current_cast.spellType); + } + + SetSpellTypeAITimer(current_cast.spellType, RuleI(Bots, AICastSpellTypeDelay)); + + if (result) { + break; } - result = true; } if (!AIautocastspell_timer->Enabled()) { - AIautocastspell_timer->Start(RandomTimer(100, 250), false); + AIautocastspell_timer->Start(RandomTimer(RuleI(Bots, MinDelayBetweenInCombatCastAttempts), RuleI(Bots, MaxDelayBetweenInCombatCastAttempts)), false); } } @@ -1338,10 +678,17 @@ bool Bot::AI_PursueCastCheck() { } bool Bot::AI_IdleCastCheck() { + if (GetAppearance() == eaDead || delaytimer || spellend_timer.Enabled() || IsFeared() || IsSilenced() || IsAmnesiad() || GetHP() < 0) { + return false; + } + bool result = false; if (AIautocastspell_timer->Check(false)) { + LogAIDetail("Bot Non-Engaged autocast check triggered: [{}]", GetCleanName()); + LogBotSpellChecksDetail("{} says, 'AI_IdleCastCheck started.'", GetCleanName()); + AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting. bool pre_combat = false; @@ -1349,7 +696,8 @@ bool Bot::AI_IdleCastCheck() { if (HasGroup() && GetGroup()->GetLeader() && GetGroup()->GetLeader()->IsClient()) { test_against = GetGroup()->GetLeader()->CastToClient(); - } else if (GetOwner() && GetOwner()->IsClient()) { + } + else if (GetOwner() && GetOwner()->IsClient()) { test_against = GetOwner()->CastToClient(); } @@ -1357,434 +705,122 @@ bool Bot::AI_IdleCastCheck() { pre_combat = test_against->GetBotPrecombat(); } - //Ok, IdleCastCheck depends of class. - switch (GetClass()) { - // Healers WITHOUT pets will check if a heal is needed before buffing. - case Class::Cleric: - case Class::Paladin: - case Class::Ranger: { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Cure)) { - if (!AICastSpell(this, 100, SpellType_Heal)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Heal)) { - if (!AICastSpell(this, 100, SpellType_Buff)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Buff)) { - } - } - } - } + auto cast_order = GetSpellTypesPrioritized(BotPriorityCategories::Idle); + Mob* tar = nullptr; + + for (auto& current_cast : cast_order) { + if (current_cast.priority == 0) { + SetSpellTypeAITimer(current_cast.spellType, RuleI(Bots, AICastSpellTypeHeldDelay)); + LogBotSpellChecksDetail("{} says, '[{}] is priority 0, skipping.'", GetCleanName(), GetSpellTypeNameByID(current_cast.spellType)); + continue; } - result = true; - break; - } - case Class::Monk: - case Class::Rogue: - case Class::Warrior: - case Class::Berserker: { - if (!AICastSpell(this, 100, SpellType_Cure)) { - if (!AICastSpell(this, 100, SpellType_Heal)) { - if (!AICastSpell(this, 100, SpellType_Buff)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Buff)) { - } - } - } + if (!pre_combat && (current_cast.spellType == BotSpellTypes::PreCombatBuff || current_cast.spellType == BotSpellTypes::PreCombatBuffSong)) { + continue; } - result = true; - break; - } - // Pets class will first cast their pet, then buffs - - case Class::Magician: - case Class::ShadowKnight: - case Class::Necromancer: - case Class::Enchanter: { - if (!AICastSpell(this, 100, SpellType_Pet)) { - if (!AICastSpell(this, 100, SpellType_Cure)) { - if (!AICastSpell(GetPet(), 100, SpellType_Cure)) { - if (!AICastSpell(this, 100, SpellType_Buff)) { - if (!AICastSpell(GetPet(), 100, SpellType_Heal)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Buff)) { - } - } - } - } - } + if (!RuleB(Bots, AllowAIMez) && (current_cast.spellType == BotSpellTypes::AEMez || current_cast.spellType == BotSpellTypes::Mez)) { + continue; } - result = true; - break; - } - case Class::Druid: - case Class::Shaman: - case Class::Beastlord: { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Cure)) { - if (!AICastSpell(this, 100, SpellType_Pet)) { - if (!AICastSpell(this, 100, SpellType_Heal)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Heal)) { - if (!AICastSpell(this, 100, SpellType_Buff)) { - if (!AICastSpell(GetPet(), 100, SpellType_Heal)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Buff)) { - } - } - } - } - } - } + if (IsCommandedBotSpellType(current_cast.spellType)) { // Unsupported by AI currently. + continue; } - result = true; - break; - } - case Class::Wizard: { // This can eventually be move into the Class::Beastlord case handler once pre-combat is fully implemented - if (pre_combat) { - if (!AICastSpell(this, 100, SpellType_Pet)) { - if (!AICastSpell(this, 100, SpellType_Cure)) { - if (!AICastSpell(this, 100, SpellType_Heal)) { - if (!AICastSpell(this, 100, SpellType_Buff)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_PreCombatBuff)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Buff)) { - } - } - } - } - } - } - } - else { - if (!AICastSpell(this, 100, SpellType_Cure)) { - if (!AICastSpell(this, 100, SpellType_Pet)) { - if (!AICastSpell(this, 100, SpellType_Heal)) { - if (!AICastSpell(this, 100, SpellType_Buff)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Buff)) { - } - } - } - } - } + if (!IsBotSpellTypeBeneficial(current_cast.spellType)) { + continue; } - result = true; - break; - } - case Class::Bard: { - if (pre_combat) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Cure)) { - if (!AICastSpell(this, 100, SpellType_Buff)) { - if (!AICastSpell(this, 100, SpellType_PreCombatBuffSong)) { - if (!AICastSpell(this, 100, SpellType_InCombatBuffSong)) { - } - } - } - } - } - else { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Cure)) { - if (!AICastSpell(this, 100, SpellType_Buff)) { - if (!AICastSpell(this, 100, SpellType_OutOfCombatBuffSong)) { - if (!AICastSpell(this, 100, SpellType_InCombatBuffSong)) { - } - } - } - } + if (AIBot_spells_by_type[current_cast.spellType].empty() && AIBot_spells_by_type[GetParentSpellType(current_cast.spellType)].empty()) { + continue; } - result = true; - break; - } - default: - break; + if (!SpellTypeAIDelayCheck(current_cast.spellType)) { + continue; + } + + result = AttemptAICastSpell(current_cast.spellType, nullptr); + + if (result) { + break; + } + + result = AttemptCloseBeneficialSpells(current_cast.spellType); + + SetSpellTypeAITimer(current_cast.spellType, RuleI(Bots, AICastSpellTypeDelay)); + + if (result) { + break; + } } - if (!AIautocastspell_timer->Enabled()) - AIautocastspell_timer->Start(RandomTimer(500, 2000), false); // avg human response is much less than 5 seconds..even for non-combat situations... + if (!AIautocastspell_timer->Enabled()) { + AIautocastspell_timer->Start(RandomTimer(RuleI(Bots, MinDelayBetweenOutCombatCastAttempts), RuleI(Bots, MaxDelayBetweenOutCombatCastAttempts)), false); + } } return result; } bool Bot::AI_EngagedCastCheck() { + if (GetAppearance() == eaDead || delaytimer || spellend_timer.Enabled() || IsFeared() || IsSilenced() || IsAmnesiad() || GetHP() < 0) { + return false; + } + bool result = false; - bool failedToCast = false; if (GetTarget() && AIautocastspell_timer->Check(false)) { + LogAIDetail("Bot Engaged autocast check triggered: [{}]", GetCleanName()); + LogBotSpellChecksDetail("{} says, 'AI_EngagedCastCheck started.'", GetCleanName()); + AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting. - uint8 botClass = GetClass(); - bool mayGetAggro = HasOrMayGetAggro(); + if (!IsAttackAllowed(GetTarget())) { + return false; + } - LogAIDetail("Engaged autocast check triggered (BOTS). Trying to cast healing spells then maybe offensive spells"); + auto cast_order = GetSpellTypesPrioritized(BotPriorityCategories::Engaged); + Mob* tar = nullptr; - if (botClass == Class::Cleric) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Escape), SpellType_Escape)) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_Heal), SpellType_Heal)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_Heal), BotAISpellRange, SpellType_Heal)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Debuff), SpellType_Debuff)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_InCombatBuff), BotAISpellRange, SpellType_InCombatBuff)) { - if (!AICastSpell(GetTarget(), mayGetAggro?0:GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) { - //AIautocastspell_timer->Start(RandomTimer(100, 250), false); // Do not give healer classes a lot of time off or your tank's die - failedToCast = true; - } - } - } - } - } + for (auto& current_cast : cast_order) { + if (current_cast.priority == 0) { + SetSpellTypeAITimer(current_cast.spellType, RuleI(Bots, AICastSpellTypeHeldDelay)); + LogBotSpellChecksDetail("{} says, '[{}] is priority 0, skipping.'", GetCleanName(), GetSpellTypeNameByID(current_cast.spellType)); + continue; } - } - else if (botClass == Class::Druid) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Escape), SpellType_Escape)) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_Heal), SpellType_Heal)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_Heal), BotAISpellRange, SpellType_Heal)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Debuff), SpellType_Debuff)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_InCombatBuff), BotAISpellRange, SpellType_InCombatBuff)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_DOT), SpellType_DOT)) { - if (!AICastSpell(GetTarget(), mayGetAggro?0:GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) { - //AIautocastspell_timer->Start(RandomTimer(100, 250), false); // Do not give healer classes a lot of time off or your tank's die - failedToCast = true; - } - } - } - } - } - } + + if (!RuleB(Bots, AllowAIMez) && (current_cast.spellType == BotSpellTypes::AEMez || current_cast.spellType == BotSpellTypes::Mez)) { + continue; } - } - else if (botClass == Class::Shaman) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Escape), SpellType_Escape)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Slow), SpellType_Slow)) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_Heal), SpellType_Heal)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_Heal), BotAISpellRange, SpellType_Heal)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Debuff), SpellType_Debuff)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_InCombatBuff), BotAISpellRange, SpellType_InCombatBuff)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_DOT), SpellType_DOT)) { - if (!AICastSpell(GetTarget(), mayGetAggro?0:GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) { - if (!AICastSpell(GetPet(), GetChanceToCastBySpellType(SpellType_Heal), SpellType_Heal)) { - failedToCast = true; - } - } - } - } - } - } - } - } + + if (IsCommandedBotSpellType(current_cast.spellType)) { // Unsupported by AI currently. + continue; } - } - else if (botClass == Class::Ranger) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Escape), SpellType_Escape)) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_Heal), SpellType_Heal)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_InCombatBuff), BotAISpellRange, SpellType_InCombatBuff)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_DOT), SpellType_DOT)) { - if (!AICastSpell(GetTarget(), mayGetAggro?0:GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_Heal), BotAISpellRange, SpellType_Heal)) { - failedToCast = true; - } - } - } - } - } + + if (AIBot_spells_by_type[current_cast.spellType].empty() && AIBot_spells_by_type[GetParentSpellType(current_cast.spellType)].empty()) { + continue; } - } - else if (botClass == Class::Beastlord) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Slow), SpellType_Slow)) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_Heal), SpellType_Heal)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Debuff), SpellType_Debuff)) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_Pet), SpellType_Pet)) { - if (!AICastSpell(GetPet(), GetChanceToCastBySpellType(SpellType_Heal), SpellType_Heal)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_InCombatBuff), BotAISpellRange, SpellType_InCombatBuff)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_DOT), SpellType_DOT)) { - if (!AICastSpell(GetTarget(), mayGetAggro?0:GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_Heal), BotAISpellRange, SpellType_Heal)) { - failedToCast = true; - } - } - } - } - } - } - } - } + + if (!SpellTypeAIDelayCheck(current_cast.spellType)) { + continue; } - } - else if (botClass == Class::Wizard) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Escape), SpellType_Escape)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Debuff), SpellType_Debuff)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_InCombatBuff), BotAISpellRange, SpellType_InCombatBuff)) { - if (!AICastSpell(GetTarget(), mayGetAggro?0:GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) { - failedToCast = true; - } - } - } + + result = AttemptAICastSpell(current_cast.spellType, nullptr); + + SetSpellTypeAITimer(current_cast.spellType, RuleI(Bots, AICastSpellTypeDelay)); + + if (!result && IsBotSpellTypeBeneficial(current_cast.spellType)) { + result = AttemptCloseBeneficialSpells(current_cast.spellType); } - } - else if (botClass == Class::Paladin) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_Heal), SpellType_Heal)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_InCombatBuff), BotAISpellRange, SpellType_InCombatBuff)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Debuff), SpellType_Debuff)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_Heal), BotAISpellRange, SpellType_Heal)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) { - failedToCast = true; - } - } - } - } - } - } - else if (botClass == Class::ShadowKnight) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Lifetap), SpellType_Lifetap)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_InCombatBuff), BotAISpellRange, SpellType_InCombatBuff)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Debuff), SpellType_Debuff)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_DOT), SpellType_DOT)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) { - failedToCast = true; - } - } - } - } - } - } - else if (botClass == Class::Magician) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_Pet), SpellType_Pet)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Debuff), SpellType_Debuff)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_InCombatBuff), BotAISpellRange, SpellType_InCombatBuff)) { - if (!AICastSpell(GetPet(), GetChanceToCastBySpellType(SpellType_Heal), SpellType_Heal)) { - if (!AICastSpell(GetTarget(), mayGetAggro?0:GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) { - failedToCast = true; - } - } - } - } - } - } - else if (botClass == Class::Necromancer) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Escape), SpellType_Escape)) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_Pet), SpellType_Pet)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Debuff), SpellType_Debuff)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Lifetap), SpellType_Lifetap)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_InCombatBuff), BotAISpellRange, SpellType_InCombatBuff)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_DOT), SpellType_DOT)) { - if (!AICastSpell(GetPet(), GetChanceToCastBySpellType(SpellType_Heal), SpellType_Heal)) { - if (!AICastSpell(GetTarget(), mayGetAggro?0:GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) { - failedToCast = true; - } - } - } - } - } - } - } - } - } - else if (botClass == Class::Enchanter) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Mez), SpellType_Mez)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Escape), SpellType_Escape)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Slow), SpellType_Slow)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Debuff), SpellType_Debuff)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_InCombatBuff), BotAISpellRange, SpellType_InCombatBuff)) { - if (!AICastSpell(GetTarget(), mayGetAggro?0:GetChanceToCastBySpellType(SpellType_DOT), SpellType_DOT)) { - if (!AICastSpell(GetTarget(), mayGetAggro?0:GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) { - failedToCast = true; - } - } - } - } - } - } - } - } - else if (botClass == Class::Bard) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Escape), SpellType_Escape)) {// Bards will use their escape songs - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_HateRedux), BotAISpellRange, SpellType_HateRedux)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Slow), SpellType_Slow)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Debuff), SpellType_Debuff)) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_InCombatBuffSong), SpellType_InCombatBuffSong)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_InCombatBuff), BotAISpellRange, SpellType_InCombatBuff)) { - if (!AICastSpell(GetTarget(), mayGetAggro ? 0 : GetChanceToCastBySpellType(SpellType_DOT), SpellType_DOT)) {// Bards will use their dot songs - if (!AICastSpell(GetTarget(), mayGetAggro ? 0 : GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) {// Bards will use their nuke songs - failedToCast = true; - } - } - } - } - } - } - } - } - } - else if (botClass == Class::Berserker) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Escape), SpellType_Escape)) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_Heal), SpellType_Heal)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Debuff), SpellType_Debuff)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_InCombatBuff), BotAISpellRange, SpellType_InCombatBuff)) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_InCombatBuffSong), SpellType_InCombatBuffSong)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_DOT), SpellType_DOT)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) { - failedToCast = true; - } - } - } - } - } - } - } - } - else if (botClass == Class::Monk) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Escape), SpellType_Escape)) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_Heal), SpellType_Heal)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Debuff), SpellType_Debuff)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_InCombatBuff), BotAISpellRange, SpellType_InCombatBuff)) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_InCombatBuffSong), SpellType_InCombatBuffSong)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_DOT), SpellType_DOT)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) { - failedToCast = true; - } - } - } - } - } - } - } - } - else if (botClass == Class::Rogue) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Escape), SpellType_Escape)) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_Heal), SpellType_Heal)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Debuff), SpellType_Debuff)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_InCombatBuff), BotAISpellRange, SpellType_InCombatBuff)) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_InCombatBuffSong), SpellType_InCombatBuffSong)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_DOT), SpellType_DOT)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) { - failedToCast = true; - } - } - } - } - } - } - } - } - else if (botClass == Class::Warrior) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_Heal), SpellType_Heal)) { - if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, GetChanceToCastBySpellType(SpellType_InCombatBuff), BotAISpellRange, SpellType_InCombatBuff)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Debuff), SpellType_Debuff)) { - if (!AICastSpell(this, GetChanceToCastBySpellType(SpellType_InCombatBuffSong), SpellType_InCombatBuffSong)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_DOT), SpellType_DOT)) { - if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) { - failedToCast = true; - } - } - } - } - } + + if (result) { + break; } } if (!AIautocastspell_timer->Enabled()) { - AIautocastspell_timer->Start(RandomTimer(150, 300), false); - } - - if (!failedToCast) { - result = true; + AIautocastspell_timer->Start(RandomTimer(RuleI(Bots, MinDelayBetweenInCombatCastAttempts), RuleI(Bots, MaxDelayBetweenInCombatCastAttempts)), false); } } @@ -1819,22 +855,22 @@ bool Bot::AIHealRotation(Mob* tar, bool useFastHeals) { botSpell.ManaCost = 0; if (useFastHeals) { - botSpell = GetBestBotSpellForRegularSingleTargetHeal(this); + botSpell = GetBestBotSpellForRegularSingleTargetHeal(this, tar); if (!IsValidSpell(botSpell.SpellId)) - botSpell = GetBestBotSpellForFastHeal(this); + botSpell = GetBestBotSpellForFastHeal(this, tar); } else { - botSpell = GetBestBotSpellForPercentageHeal(this); + botSpell = GetBestBotSpellForPercentageHeal(this, tar); if (!IsValidSpell(botSpell.SpellId)) { - botSpell = GetBestBotSpellForRegularSingleTargetHeal(this); + botSpell = GetBestBotSpellForRegularSingleTargetHeal(this, tar); } if (!IsValidSpell(botSpell.SpellId)) { - botSpell = GetFirstBotSpellForSingleTargetHeal(this); + botSpell = GetFirstBotSpellForSingleTargetHeal(this, tar); } if (!IsValidSpell(botSpell.SpellId)) { - botSpell = GetFirstBotSpellBySpellType(this, SpellType_Heal); + botSpell = GetFirstBotSpellBySpellType(this, BotSpellTypes::RegularHeal); } } @@ -1866,8 +902,7 @@ bool Bot::AIHealRotation(Mob* tar, bool useFastHeals) { } if (castedSpell) { - BotGroupSay( - this, + RaidGroupSay( fmt::format( "Casting {} on {}, please stay in range!", spells[botSpell.SpellId].name, @@ -1879,74 +914,41 @@ bool Bot::AIHealRotation(Mob* tar, bool useFastHeals) { return castedSpell; } -std::list Bot::GetBotSpellsForSpellEffect(Bot* botCaster, int spellEffect) { +std::list Bot::GetBotSpellsForSpellEffect(Bot* caster, uint16 spell_type, int spell_effect) { std::list result; - if (!botCaster) { + if (!caster) { return result; } - if (auto bot_owner = botCaster->GetBotOwner(); !bot_owner) { + if (auto bot_owner = caster->GetBotOwner(); !bot_owner) { return result; } - if (botCaster->AI_HasSpells()) { - std::vector botSpellList = botCaster->AIBot_spells; + if (caster->AI_HasSpells()) { + const std::vector& bot_spell_list = caster->BotGetSpellsByType(spell_type); - for (int i = botSpellList.size() - 1; i >= 0; i--) { - if (!IsValidSpell(botSpellList[i].spellid)) { - // this is both to quit early to save cpu and to avoid casting bad spells - // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here + for (int i = bot_spell_list.size() - 1; i >= 0; i--) { + if (!IsValidSpell(bot_spell_list[i].spellid)) { continue; } - if (IsEffectInSpell(botSpellList[i].spellid, spellEffect) || GetSpellTriggerSpellID(botSpellList[i].spellid, spellEffect)) { - BotSpell botSpell; - botSpell.SpellId = botSpellList[i].spellid; - botSpell.SpellIndex = i; - botSpell.ManaCost = botSpellList[i].manacost; - - result.push_back(botSpell); - } - } - } - - return result; -} - -std::list Bot::GetBotSpellsForSpellEffectAndTargetType(Bot* botCaster, int spellEffect, SpellTargetType targetType) { - std::list result; - - if (!botCaster) { - return result; - } - - if (auto bot_owner = botCaster->GetBotOwner(); !bot_owner) { - return result; - } - - if (botCaster->AI_HasSpells()) { - std::vector botSpellList = botCaster->AIBot_spells; - - for (int i = botSpellList.size() - 1; i >= 0; i--) { - if (!IsValidSpell(botSpellList[i].spellid)) { - // this is both to quit early to save cpu and to avoid casting bad spells - // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here + if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) { continue; } if ( - ( - IsEffectInSpell(botSpellList[i].spellid, spellEffect) || - GetSpellTriggerSpellID(botSpellList[i].spellid, spellEffect) - ) && - spells[botSpellList[i].spellid].target_type == targetType + caster->CheckSpellRecastTimer(bot_spell_list[i].spellid) && + (bot_spell_list[i].type == spell_type || bot_spell_list[i].type == GetParentSpellType(spell_type)) && + caster->IsValidSpellTypeBySpellID(spell_type, bot_spell_list[i].spellid) && + (IsEffectInSpell(bot_spell_list[i].spellid, spell_effect) || GetSpellTriggerSpellID(bot_spell_list[i].spellid, spell_effect)) ) { - BotSpell botSpell; - botSpell.SpellId = botSpellList[i].spellid; - botSpell.SpellIndex = i; - botSpell.ManaCost = botSpellList[i].manacost; - result.push_back(botSpell); + BotSpell bot_spell; + bot_spell.SpellId = bot_spell_list[i].spellid; + bot_spell.SpellIndex = bot_spell_list[i].index; + bot_spell.ManaCost = bot_spell_list[i].manacost; + + result.push_back(bot_spell); } } } @@ -1954,34 +956,44 @@ std::list Bot::GetBotSpellsForSpellEffectAndTargetType(Bot* botCaster, return result; } -std::list Bot::GetBotSpellsBySpellType(Bot* botCaster, uint32 spellType) { +std::list Bot::GetBotSpellsForSpellEffectAndTargetType(Bot* caster, uint16 spell_type, int spell_effect, SpellTargetType target_type) { std::list result; - if (!botCaster) { + if (!caster) { return result; } - if (auto bot_owner = botCaster->GetBotOwner(); !bot_owner) { + if (auto bot_owner = caster->GetBotOwner(); !bot_owner) { return result; } - if (botCaster->AI_HasSpells()) { - std::vector botSpellList = botCaster->AIBot_spells; + if (caster->AI_HasSpells()) { + const std::vector& bot_spell_list = caster->BotGetSpellsByType(spell_type); - for (int i = botSpellList.size() - 1; i >= 0; i--) { - if (!IsValidSpell(botSpellList[i].spellid)) { - // this is both to quit early to save cpu and to avoid casting bad spells - // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here + for (int i = bot_spell_list.size() - 1; i >= 0; i--) { + if (!IsValidSpell(bot_spell_list[i].spellid)) { continue; } - if (botSpellList[i].type & spellType) { - BotSpell botSpell; - botSpell.SpellId = botSpellList[i].spellid; - botSpell.SpellIndex = i; - botSpell.ManaCost = botSpellList[i].manacost; + if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) { + continue; + } - result.push_back(botSpell); + if ( + caster->CheckSpellRecastTimer(bot_spell_list[i].spellid) && + (bot_spell_list[i].type == spell_type || bot_spell_list[i].type == GetParentSpellType(spell_type)) && + caster->IsValidSpellTypeBySpellID(spell_type, bot_spell_list[i].spellid) && + ( + IsEffectInSpell(bot_spell_list[i].spellid, spell_effect) || + GetSpellTriggerSpellID(bot_spell_list[i].spellid, spell_effect) + ) && + (target_type == ST_TargetOptional || spells[bot_spell_list[i].spellid].target_type == target_type) + ) { + BotSpell bot_spell; + bot_spell.SpellId = bot_spell_list[i].spellid; + bot_spell.SpellIndex = bot_spell_list[i].index; + bot_spell.ManaCost = bot_spell_list[i].manacost; + result.push_back(bot_spell); } } } @@ -1989,212 +1001,162 @@ std::list Bot::GetBotSpellsBySpellType(Bot* botCaster, uint32 spellTyp return result; } -std::list Bot::GetPrioritizedBotSpellsBySpellType(Bot* botCaster, uint32 spellType) { - std::list result; +std::list Bot::GetBotSpellsBySpellType(Bot* caster, uint16 spell_type) { + std::list result; - if (botCaster && botCaster->AI_HasSpells()) { - std::vector botSpellList = botCaster->AIBot_spells; + if (!caster) { + return result; + } - for (int i = botSpellList.size() - 1; i >= 0; i--) { - if (!IsValidSpell(botSpellList[i].spellid)) { - // this is both to quit early to save cpu and to avoid casting bad spells - // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here + if (auto bot_owner = caster->GetBotOwner(); !bot_owner) { + return result; + } + + if (caster->AI_HasSpells()) { + const std::vector& bot_spell_list = caster->BotGetSpellsByType(spell_type); + + for (int i = bot_spell_list.size() - 1; i >= 0; i--) { + if (!IsValidSpell(bot_spell_list[i].spellid)) { continue; } - if (botSpellList[i].type & spellType) { - BotSpell_wPriority botSpell; - botSpell.SpellId = botSpellList[i].spellid; - botSpell.SpellIndex = i; - botSpell.ManaCost = botSpellList[i].manacost; - botSpell.Priority = botSpellList[i].priority; + if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) { + continue; + } - result.push_back(botSpell); + if ( + caster->CheckSpellRecastTimer(bot_spell_list[i].spellid) && + (bot_spell_list[i].type == spell_type || bot_spell_list[i].type == GetParentSpellType(spell_type)) && + caster->IsValidSpellTypeBySpellID(spell_type, bot_spell_list[i].spellid) + ) { + BotSpell bot_spell; + bot_spell.SpellId = bot_spell_list[i].spellid; + bot_spell.SpellIndex = bot_spell_list[i].index; + bot_spell.ManaCost = bot_spell_list[i].manacost; + + result.push_back(bot_spell); + } + } + } + + return result; +} + +std::vector Bot::GetPrioritizedBotSpellsBySpellType(Bot* caster, uint16 spell_type, Mob* tar, bool AE, uint16 sub_target_type, uint16 sub_type) { + std::vector result; + + if (caster && caster->AI_HasSpells()) { + const std::vector& bot_spell_list = caster->BotGetSpellsByType(spell_type); + + for (int i = bot_spell_list.size() - 1; i >= 0; i--) { + if (!IsValidSpell(bot_spell_list[i].spellid)) { + continue; + } + + if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) { + continue; + } + + if (spell_type == BotSpellTypes::HateRedux && caster->GetClass() == Class::Bard) { + if (spells[bot_spell_list[i].spellid].target_type != ST_Target) { + continue; + } + } + + if ( + caster->CheckSpellRecastTimer(bot_spell_list[i].spellid) && + (bot_spell_list[i].type == spell_type || bot_spell_list[i].type == GetParentSpellType(spell_type)) && + caster->IsValidSpellTypeBySpellID(spell_type, bot_spell_list[i].spellid) + ) { + if ( + caster->IsCommandedSpell() && + ( + !caster->IsValidSpellTypeSubType(spell_type, sub_target_type, bot_spell_list[i].spellid) || + !caster->IsValidSpellTypeSubType(spell_type, sub_type, bot_spell_list[i].spellid) + ) + ) { + continue; + } + + if (!AE && IsAnyAESpell(bot_spell_list[i].spellid) && !IsGroupSpell(bot_spell_list[i].spellid)) { + continue; + } + else if (AE && !IsAnyAESpell(bot_spell_list[i].spellid)) { + continue; + } + + if ( + !caster->IsInGroupOrRaid(tar, true) && + ( + !RuleB(Bots, EnableBotTGB) || + ( + IsGroupSpell(bot_spell_list[i].spellid) && + !IsTGBCompatibleSpell(bot_spell_list[i].spellid) + ) + ) + ) { + continue; + } + + if (!IsPBAESpell(bot_spell_list[i].spellid) && !caster->CastChecks(bot_spell_list[i].spellid, tar, spell_type, false, IsAEBotSpellType(spell_type))) { + continue; + } + + if ( + caster->IsCommandedSpell() || + !AE || + !BotSpellTypeRequiresAEChecks(spell_type) || + caster->HasValidAETarget(caster, bot_spell_list[i].spellid, spell_type, tar) + ) { + BotSpell_wPriority bot_spell; + bot_spell.SpellId = bot_spell_list[i].spellid; + bot_spell.SpellIndex = bot_spell_list[i].index; + bot_spell.ManaCost = bot_spell_list[i].manacost; + bot_spell.Priority = bot_spell_list[i].priority; + + result.emplace_back(bot_spell); + } } } if (result.size() > 1) { - result.sort( - [](BotSpell_wPriority const& l, BotSpell_wPriority const& r) { - return l.Priority < r.Priority; - } - ); + std::sort(result.begin(), result.end(), [](BotSpell_wPriority const& l, BotSpell_wPriority const& r) { + return l.Priority < r.Priority; + }); } } return result; } -BotSpell Bot::GetFirstBotSpellBySpellType(Bot* botCaster, uint32 spellType) { +BotSpell Bot::GetFirstBotSpellBySpellType(Bot* caster, uint16 spell_type) { BotSpell result; result.SpellId = 0; result.SpellIndex = 0; result.ManaCost = 0; - if (botCaster && botCaster->AI_HasSpells()) { - std::vector botSpellList = botCaster->AIBot_spells; + if (caster && caster->AI_HasSpells()) { + const std::vector& bot_spell_list = caster->BotGetSpellsByType(spell_type); - for (int i = botSpellList.size() - 1; i >= 0; i--) { - if (!IsValidSpell(botSpellList[i].spellid)) { - // this is both to quit early to save cpu and to avoid casting bad spells - // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here + for (int i = bot_spell_list.size() - 1; i >= 0; i--) { + if (!IsValidSpell(bot_spell_list[i].spellid)) { continue; } - if ((botSpellList[i].type & spellType) && botCaster->CheckSpellRecastTimer(botSpellList[i].spellid)) { - result.SpellId = botSpellList[i].spellid; - result.SpellIndex = i; - result.ManaCost = botSpellList[i].manacost; - - break; - } - } - } - - return result; -} - -BotSpell Bot::GetBestBotSpellForFastHeal(Bot *botCaster) { - BotSpell result; - - result.SpellId = 0; - result.SpellIndex = 0; - result.ManaCost = 0; - - if (botCaster) { - std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, SE_CurrentHP); - - for (auto botSpellListItr : botSpellList) { - // Assuming all the spells have been loaded into this list by level and in descending order - if (IsFastHealSpell(botSpellListItr.SpellId) && botCaster->CheckSpellRecastTimer(botSpellListItr.SpellId)) { - result.SpellId = botSpellListItr.SpellId; - result.SpellIndex = botSpellListItr.SpellIndex; - result.ManaCost = botSpellListItr.ManaCost; - - break; - } - } - } - - return result; -} - -BotSpell Bot::GetBestBotSpellForHealOverTime(Bot* botCaster) { - BotSpell result; - - result.SpellId = 0; - result.SpellIndex = 0; - result.ManaCost = 0; - - if (botCaster) { - std::list botHoTSpellList = GetBotSpellsForSpellEffect(botCaster, SE_HealOverTime); - std::vector botSpellList = botCaster->AIBot_spells; - - for (auto botSpellListItr : botHoTSpellList) { - // Assuming all the spells have been loaded into this list by level and in descending order - if (IsHealOverTimeSpell(botSpellListItr.SpellId)) { - for (int i = botSpellList.size() - 1; i >= 0; i--) { - if (!IsValidSpell(botSpellList[i].spellid)) { - // this is both to quit early to save cpu and to avoid casting bad spells - // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here - continue; - } - - if ( - botSpellList[i].spellid == botSpellListItr.SpellId && - (botSpellList[i].type & SpellType_Heal) && - botCaster->CheckSpellRecastTimer(botSpellListItr.SpellId) - ) { - result.SpellId = botSpellListItr.SpellId; - result.SpellIndex = botSpellListItr.SpellIndex; - result.ManaCost = botSpellListItr.ManaCost; - } - } - - break; - } - } - } - - return result; -} - -BotSpell Bot::GetBestBotSpellForPercentageHeal(Bot *botCaster) { - BotSpell result; - - result.SpellId = 0; - result.SpellIndex = 0; - result.ManaCost = 0; - - if (botCaster && botCaster->AI_HasSpells()) { - std::vector botSpellList = botCaster->AIBot_spells; - - for (int i = botSpellList.size() - 1; i >= 0; i--) { - if (!IsValidSpell(botSpellList[i].spellid)) { - // this is both to quit early to save cpu and to avoid casting bad spells - // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here + if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) { continue; } - if (IsCompleteHealSpell(botSpellList[i].spellid) && botCaster->CheckSpellRecastTimer(botSpellList[i].spellid)) { - result.SpellId = botSpellList[i].spellid; - result.SpellIndex = i; - result.ManaCost = botSpellList[i].manacost; - break; - } - } - } - - return result; -} - -BotSpell Bot::GetBestBotSpellForRegularSingleTargetHeal(Bot* botCaster) { - BotSpell result; - - result.SpellId = 0; - result.SpellIndex = 0; - result.ManaCost = 0; - - if (botCaster) { - std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, SE_CurrentHP); - - for (std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { - // Assuming all the spells have been loaded into this list by level and in descending order - if (IsRegularSingleTargetHealSpell(botSpellListItr->SpellId) && botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId)) { - result.SpellId = botSpellListItr->SpellId; - result.SpellIndex = botSpellListItr->SpellIndex; - result.ManaCost = botSpellListItr->ManaCost; - break; - } - } - } - - return result; -} - -BotSpell Bot::GetFirstBotSpellForSingleTargetHeal(Bot* botCaster) { - BotSpell result; - - result.SpellId = 0; - result.SpellIndex = 0; - result.ManaCost = 0; - - if (botCaster) { - std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, SE_CurrentHP); - - for (std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { - // Assuming all the spells have been loaded into this list by level and in descending order if ( - ( - IsRegularSingleTargetHealSpell(botSpellListItr->SpellId) || - IsFastHealSpell(botSpellListItr->SpellId) - ) && - botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId) + caster->CheckSpellRecastTimer(bot_spell_list[i].spellid) && + (bot_spell_list[i].type == spell_type || bot_spell_list[i].type == GetParentSpellType(spell_type)) && + caster->IsValidSpellTypeBySpellID(spell_type, bot_spell_list[i].spellid) ) { - result.SpellId = botSpellListItr->SpellId; - result.SpellIndex = botSpellListItr->SpellIndex; - result.ManaCost = botSpellListItr->ManaCost; + result.SpellId = bot_spell_list[i].spellid; + result.SpellIndex = bot_spell_list[i].index; + result.ManaCost = bot_spell_list[i].manacost; + break; } } @@ -2203,25 +1165,23 @@ BotSpell Bot::GetFirstBotSpellForSingleTargetHeal(Bot* botCaster) { return result; } -BotSpell Bot::GetBestBotSpellForGroupHeal(Bot* botCaster) { +BotSpell Bot::GetBestBotSpellForVeryFastHeal(Bot* caster, Mob* tar, uint16 spell_type) { BotSpell result; result.SpellId = 0; result.SpellIndex = 0; result.ManaCost = 0; - if (botCaster) { - std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, SE_CurrentHP); + if (caster) { + std::list bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SpellEffect::CurrentHP); - for (std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { - // Assuming all the spells have been loaded into this list by level and in descending order + for (auto bot_spell_list_itr : bot_spell_list) { if ( - IsRegularGroupHealSpell(botSpellListItr->SpellId) && - botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId) - ) { - result.SpellId = botSpellListItr->SpellId; - result.SpellIndex = botSpellListItr->SpellIndex; - result.ManaCost = botSpellListItr->ManaCost; + IsVeryFastHealSpell(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; + result.ManaCost = bot_spell_list_itr.ManaCost; + break; } } @@ -2230,219 +1190,378 @@ BotSpell Bot::GetBestBotSpellForGroupHeal(Bot* botCaster) { return result; } -BotSpell Bot::GetBestBotSpellForGroupHealOverTime(Bot* botCaster) { +BotSpell Bot::GetBestBotSpellForFastHeal(Bot* caster, Mob* tar, uint16 spell_type) { BotSpell result; result.SpellId = 0; result.SpellIndex = 0; result.ManaCost = 0; - if (botCaster) { - std::list botHoTSpellList = GetBotSpellsForSpellEffect(botCaster, SE_HealOverTime); - std::vector botSpellList = botCaster->AIBot_spells; + if (caster) { + std::list bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SpellEffect::CurrentHP); - for (std::list::iterator botSpellListItr = botHoTSpellList.begin(); botSpellListItr != botHoTSpellList.end(); ++botSpellListItr) { - // Assuming all the spells have been loaded into this list by level and in descending order - if (IsGroupHealOverTimeSpell(botSpellListItr->SpellId)) { + for (auto bot_spell_list_itr : bot_spell_list) { + 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; + result.ManaCost = bot_spell_list_itr.ManaCost; - for (int i = botSpellList.size() - 1; i >= 0; i--) { - if (!IsValidSpell(botSpellList[i].spellid)) { - // this is both to quit early to save cpu and to avoid casting bad spells - // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here + break; + } + } + } + + return result; +} + +BotSpell Bot::GetBestBotSpellForHealOverTime(Bot* caster, Mob* tar, uint16 spell_type) { + BotSpell result; + + result.SpellId = 0; + result.SpellIndex = 0; + result.ManaCost = 0; + + if (caster) { + std::list bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SpellEffect::HealOverTime); + + for (auto bot_spell_list_itr : bot_spell_list) { + 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; + result.ManaCost = bot_spell_list_itr.ManaCost; + + break; + } + } + } + + return result; +} + +BotSpell Bot::GetBestBotSpellForPercentageHeal(Bot* caster, Mob* tar, uint16 spell_type) { + BotSpell result; + + result.SpellId = 0; + result.SpellIndex = 0; + result.ManaCost = 0; + + if (caster && caster->AI_HasSpells()) { + const std::vector& bot_spell_list = caster->BotGetSpellsByType(spell_type); + for (int i = bot_spell_list.size() - 1; i >= 0; i--) { + if (!IsValidSpell(bot_spell_list[i].spellid)) { + continue; + } + + if ( + (bot_spell_list[i].type == spell_type || bot_spell_list[i].type == GetParentSpellType(spell_type)) && + caster->IsValidSpellTypeBySpellID(spell_type, bot_spell_list[i].spellid) && + IsCompleteHealSpell(bot_spell_list[i].spellid) && + caster->CastChecks(bot_spell_list[i].spellid, tar, spell_type) + ) { + result.SpellId = bot_spell_list[i].spellid; + result.SpellIndex = bot_spell_list[i].index; + result.ManaCost = bot_spell_list[i].manacost; + + break; + } + } + } + + return result; +} + +BotSpell Bot::GetBestBotSpellForRegularSingleTargetHeal(Bot* caster, Mob* tar, uint16 spell_type) { + BotSpell result; + + result.SpellId = 0; + result.SpellIndex = 0; + result.ManaCost = 0; + + if (caster) { + std::list bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SpellEffect::CurrentHP); + + for (std::list::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) { + 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; + result.ManaCost = bot_spell_list_itr->ManaCost; + + break; + } + } + } + + return result; +} + +BotSpell Bot::GetFirstBotSpellForSingleTargetHeal(Bot* caster, Mob* tar, uint16 spell_type) { + BotSpell result; + + result.SpellId = 0; + result.SpellIndex = 0; + result.ManaCost = 0; + + if (caster) { + std::list bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SpellEffect::CurrentHP); + + for (std::list::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) { + 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; + result.ManaCost = bot_spell_list_itr->ManaCost; + + break; + } + } + } + + return result; +} + +BotSpell Bot::GetBestBotSpellForGroupHeal(Bot* caster, Mob* tar, uint16 spell_type) { + BotSpell result; + + result.SpellId = 0; + result.SpellIndex = 0; + result.ManaCost = 0; + + if (!caster->TargetValidation(tar)) { + return result; + } + + std::list bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SpellEffect::CurrentHP); + int target_count = 0; + int required_count = caster->GetSpellTypeAEOrGroupTargetCount(spell_type); + + for (std::list::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) { + if (IsRegularGroupHealSpell(bot_spell_list_itr->SpellId)) { + uint16 spell_id = bot_spell_list_itr->SpellId; + + if (caster->TargetValidation(tar) && !caster->IsCommandedSpell() && caster->IsValidSpellRange(spell_id, tar)) { + target_count = caster->GetNumberNeedingHealedInGroup(tar, spell_type, spell_id, caster->GetAOERange(spell_id)); + + if (target_count < required_count) { + continue; + } + } + + result.SpellId = bot_spell_list_itr->SpellId; + result.SpellIndex = bot_spell_list_itr->SpellIndex; + result.ManaCost = bot_spell_list_itr->ManaCost; + + break; + } + } + + return result; +} + +BotSpell Bot::GetBestBotSpellForGroupHealOverTime(Bot* caster, Mob* tar, uint16 spell_type) { + BotSpell result; + + result.SpellId = 0; + result.SpellIndex = 0; + result.ManaCost = 0; + + if (!caster->TargetValidation(tar)) { + return result; + } + + std::list bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SpellEffect::HealOverTime); + int target_count = 0; + int required_count = caster->GetSpellTypeAEOrGroupTargetCount(spell_type); + + for (std::list::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) { + if (IsGroupHealOverTimeSpell(bot_spell_list_itr->SpellId)) { + uint16 spell_id = bot_spell_list_itr->SpellId; + + if (caster->TargetValidation(tar) && !caster->IsCommandedSpell() && caster->IsValidSpellRange(spell_id, tar)) { + target_count = caster->GetNumberNeedingHealedInGroup(tar, spell_type, spell_id, caster->GetAOERange(spell_id)); + + if (target_count < required_count) { + continue; + } + } + + result.SpellId = bot_spell_list_itr->SpellId; + result.SpellIndex = bot_spell_list_itr->SpellIndex; + result.ManaCost = bot_spell_list_itr->ManaCost; + + break; + } + } + + return result; +} + +BotSpell Bot::GetBestBotSpellForGroupCompleteHeal(Bot* caster, Mob* tar, uint16 spell_type) { + BotSpell result; + + result.SpellId = 0; + result.SpellIndex = 0; + result.ManaCost = 0; + + if (!caster->TargetValidation(tar)) { + return result; + } + + std::list bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SpellEffect::CompleteHeal); + int target_count = 0; + int required_count = caster->GetSpellTypeAEOrGroupTargetCount(spell_type); + + for (std::list::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) { + if (IsGroupCompleteHealSpell(bot_spell_list_itr->SpellId)) { + uint16 spell_id = bot_spell_list_itr->SpellId; + + if (caster->TargetValidation(tar) && !caster->IsCommandedSpell() && caster->IsValidSpellRange(spell_id, tar)) { + target_count = caster->GetNumberNeedingHealedInGroup(tar, spell_type, spell_id, caster->GetAOERange(spell_id)); + + if (target_count < required_count) { + continue; + } + } + + result.SpellId = bot_spell_list_itr->SpellId; + result.SpellIndex = bot_spell_list_itr->SpellIndex; + result.ManaCost = bot_spell_list_itr->ManaCost; + + break; + } + } + + return result; +} + +BotSpell Bot::GetBestBotSpellForMez(Bot* caster, uint16 spell_type) { + BotSpell result; + + result.SpellId = 0; + result.SpellIndex = 0; + result.ManaCost = 0; + + if (caster) { + std::list bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SpellEffect::Mez); + + for (std::list::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) { + if ( + IsMesmerizeSpell(bot_spell_list_itr->SpellId) && + caster->CheckSpellRecastTimer(bot_spell_list_itr->SpellId) + ) { + result.SpellId = bot_spell_list_itr->SpellId; + result.SpellIndex = bot_spell_list_itr->SpellIndex; + result.ManaCost = bot_spell_list_itr->ManaCost; + + break; + } + } + } + + return result; +} + +Mob* Bot::GetFirstIncomingMobToMez(Bot* caster, uint16 spell_id, uint16 spell_type, bool AE) { + Mob* result = nullptr; + + if (caster && caster->GetOwner()) { + int spell_range = caster->GetActSpellRange(spell_id, spells[spell_id].range); + int spell_ae_range = caster->GetAOERange(spell_id); + bool is_pbae_spell = IsPBAESpell(spell_id); + NPC* npc = nullptr; + + for (auto& close_mob : caster->m_close_mobs) { + npc = close_mob.second->CastToNPC(); + + if (!npc) { + continue; + } + + if (!caster->IsValidMezTarget(caster->GetOwner(), npc, spell_id)) { + 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 (!caster->IsValidMezTarget(caster->GetOwner(), m, spell_id)) { continue; } - if ( - botSpellList[i].spellid == botSpellListItr->SpellId && - (botSpellList[i].type & SpellType_Heal) && - botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId) - ) { - result.SpellId = botSpellListItr->SpellId; - result.SpellIndex = botSpellListItr->SpellIndex; - result.ManaCost = botSpellListItr->ManaCost; + if (spell_ae_range < Distance(npc->GetPosition(), m->GetPosition())) { + continue; + } + + if (caster->CastChecks(spell_id, m, spell_type, true, true)) { + ++target_count; + } + + if (target_count >= caster->GetSpellTypeAEOrGroupTargetCount(spell_type)) { + break; } } - break; - } - } - } - - return result; -} - -BotSpell Bot::GetBestBotSpellForGroupCompleteHeal(Bot* botCaster) { - BotSpell result; - - result.SpellId = 0; - result.SpellIndex = 0; - result.ManaCost = 0; - - if (botCaster) { - std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, SE_CompleteHeal); - - for(std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { - // Assuming all the spells have been loaded into this list by level and in descending order - if ( - IsGroupCompleteHealSpell(botSpellListItr->SpellId) && - botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId) - ) { - result.SpellId = botSpellListItr->SpellId; - result.SpellIndex = botSpellListItr->SpellIndex; - result.ManaCost = botSpellListItr->ManaCost; - break; - } - } - } - - return result; -} - -BotSpell Bot::GetBestBotSpellForMez(Bot* botCaster) { - BotSpell result; - - result.SpellId = 0; - result.SpellIndex = 0; - result.ManaCost = 0; - - if (botCaster) { - std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, SE_Mez); - - for (std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { - // Assuming all the spells have been loaded into this list by level and in descending order - if ( - IsMesmerizeSpell(botSpellListItr->SpellId) && - botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId) - ) { - result.SpellId = botSpellListItr->SpellId; - result.SpellIndex = botSpellListItr->SpellIndex; - result.ManaCost = botSpellListItr->ManaCost; - - break; - } - } - } - - return result; -} - -BotSpell Bot::GetBestBotSpellForMagicBasedSlow(Bot* botCaster) { - BotSpell result; - - result.SpellId = 0; - result.SpellIndex = 0; - result.ManaCost = 0; - - if (botCaster) { - std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, SE_AttackSpeed); - - for (std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { - // Assuming all the spells have been loaded into this list by level and in descending order - if ( - IsSlowSpell(botSpellListItr->SpellId) && - spells[botSpellListItr->SpellId].resist_type == RESIST_MAGIC && - botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId) - ) { - result.SpellId = botSpellListItr->SpellId; - result.SpellIndex = botSpellListItr->SpellIndex; - result.ManaCost = botSpellListItr->ManaCost; - break; - } - } - } - - return result; -} - -BotSpell Bot::GetBestBotSpellForDiseaseBasedSlow(Bot* botCaster) { - BotSpell result; - - result.SpellId = 0; - result.SpellIndex = 0; - result.ManaCost = 0; - - if (botCaster) { - std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, SE_AttackSpeed); - - for (std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { - // Assuming all the spells have been loaded into this list by level and in descending order - if ( - IsSlowSpell(botSpellListItr->SpellId) && - spells[botSpellListItr->SpellId].resist_type == RESIST_DISEASE && - botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId) - ) { - result.SpellId = botSpellListItr->SpellId; - result.SpellIndex = botSpellListItr->SpellIndex; - result.ManaCost = botSpellListItr->ManaCost; - - break; - } - } - } - - return result; -} - -Mob* Bot::GetFirstIncomingMobToMez(Bot* botCaster, BotSpell botSpell) { - Mob* result = 0; - - if (botCaster && IsMesmerizeSpell(botSpell.SpellId)) { - - std::list npc_list; - entity_list.GetNPCList(npc_list); - - for(std::list::iterator itr = npc_list.begin(); itr != npc_list.end(); ++itr) { - NPC* npc = *itr; - - if (DistanceSquaredNoZ(npc->GetPosition(), botCaster->GetPosition()) <= botCaster->GetActSpellRange(botSpell.SpellId, spells[botSpell.SpellId].range)) { - if (!npc->IsMezzed()) { - if (botCaster->HasGroup()) { - Group* g = botCaster->GetGroup(); - - if (g) { - for (int counter = 0; counter < g->GroupCount(); counter++) { - if ( - npc->IsOnHatelist(g->members[counter]) && - g->members[counter]->GetTarget() != npc && g->members[counter]->IsEngaged()) { - result = npc; - break; - } - } - } - } + if (target_count < caster->GetSpellTypeAEOrGroupTargetCount(spell_type)) { + continue; } + + result = npc; + } + else { + if (spell_range < Distance(caster->GetPosition(), npc->GetPosition())) { + continue; + } + + if (!caster->CastChecks(spell_id, npc, spell_type, true)) { + continue; + } + + result = npc; } - if (result) - break; + if (result) { + return result; + } } } return result; } -BotSpell Bot::GetBestBotMagicianPetSpell(Bot *botCaster) { +BotSpell Bot::GetBestBotMagicianPetSpell(Bot* caster, uint16 spell_type) { BotSpell result; result.SpellId = 0; result.SpellIndex = 0; result.ManaCost = 0; - if (botCaster) { - std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, SE_SummonPet); + if (caster) { + std::list bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SpellEffect::SummonPet); + std::string pet_type = GetBotMagicianPetType(caster); - std::string petType = GetBotMagicianPetType(botCaster); - - for(std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { - // Assuming all the spells have been loaded into this list by level and in descending order - if (IsSummonPetSpell(botSpellListItr->SpellId) && botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId)) { - if (!strncmp(spells[botSpellListItr->SpellId].teleport_zone, petType.c_str(), petType.length())) { - result.SpellId = botSpellListItr->SpellId; - result.SpellIndex = botSpellListItr->SpellIndex; - result.ManaCost = botSpellListItr->ManaCost; + for(std::list::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) { + if ( + IsSummonPetSpell(bot_spell_list_itr->SpellId) && + caster->CheckSpellRecastTimer(bot_spell_list_itr->SpellId) && + !strncmp(spells[bot_spell_list_itr->SpellId].teleport_zone, pet_type.c_str(), pet_type.length()) + ) { + result.SpellId = bot_spell_list_itr->SpellId; + result.SpellIndex = bot_spell_list_itr->SpellIndex; + result.ManaCost = bot_spell_list_itr->ManaCost; break; - } } } } @@ -2450,80 +1569,147 @@ BotSpell Bot::GetBestBotMagicianPetSpell(Bot *botCaster) { return result; } -std::string Bot::GetBotMagicianPetType(Bot* botCaster) { +std::string Bot::GetBotMagicianPetType(Bot* caster) { std::string result; - if (botCaster) { - if (botCaster->IsPetChooser()) { - switch(botCaster->GetPetChooserID()) { - case 0: + if (caster) { + uint8 pet_type = caster->GetPetChooserID(); + uint8 bot_level = caster->GetLevel(); + bool epic_allowed = false; + std::string epic_spell_name = RuleS(Bots, EpicPetSpellName); + + if (epic_spell_name.empty()) { + epic_spell_name = "SumMageMultiElement"; + } + + if (RuleB(Bots, AllowMagicianEpicPet)) { + if (bot_level >= RuleI(Bots, AllowMagicianEpicPetLevel)) { + if (!RuleI(Bots, RequiredMagicianEpicPetItemID)) { + epic_allowed = true; + } + else { + bool has_item = caster->HasBotItem(RuleI(Bots, RequiredMagicianEpicPetItemID)) != INVALID_INDEX; + + if (has_item) { + epic_allowed = true; + } + } + } + } + + if (pet_type > 0) { + switch (pet_type) { + case SumWater: result = std::string("SumWater"); break; - case 1: + case SumFire: result = std::string("SumFire"); break; - case 2: + case SumAir: result = std::string("SumAir"); break; - case 3: + case SumEarth: result = std::string("SumEarth"); break; - default: + case MonsterSum: result = std::string("MonsterSum"); break; + case SumMageMultiElement: + if (epic_allowed) { + result = epic_spell_name; + } + else { + caster->SetPetChooserID(0); + } + break; } } else { - if (botCaster->GetLevel() == 2) - result = std::string("SumWater"); - else if (botCaster->GetLevel() == 3) - result = std::string("SumFire"); - else if (botCaster->GetLevel() == 4) - result = std::string("SumAir"); - else if (botCaster->GetLevel() == 5) - result = std::string("SumEarth"); - else if (botCaster->GetLevel() < 30) { - // Under level 30 - int counter = zone->random.Int(0, 3); + uint8 air_min_level = 255; + uint8 fire_min_level = 255; + uint8 water_min_level = 255; + uint8 earth_min_level = 255; + uint8 monster_min_level = 255; + uint8 epic_min_level = 255; + std::list bot_spell_list = caster->GetBotSpellsBySpellType(caster, BotSpellTypes::Pet); - switch(counter) { - case 0: - result = std::string("SumWater"); - break; - case 1: - result = std::string("SumFire"); - break; - case 2: - result = std::string("SumAir"); - break; - case 3: - result = std::string("SumEarth"); - break; - default: - result = std::string("MonsterSum"); - break; + for (const auto& s : bot_spell_list) { + if (!IsValidSpell(s.SpellId)) { + continue; + } + + if (!IsEffectInSpell(s.SpellId, SpellEffect::SummonPet)) { + continue; + } + + auto spell = spells[s.SpellId]; + + if (!strncmp(spell.teleport_zone, "SumWater", 8) && spell.classes[Class::Magician - 1] < water_min_level) { + water_min_level = spell.classes[Class::Magician - 1]; + } + else if (!strncmp(spell.teleport_zone, "SumFire", 7) && spell.classes[Class::Magician - 1] < fire_min_level) { + fire_min_level = spell.classes[Class::Magician - 1]; + } + else if (!strncmp(spell.teleport_zone, "SumAir", 6) && spell.classes[Class::Magician - 1] < air_min_level) { + air_min_level = spell.classes[Class::Magician - 1]; + } + else if (!strncmp(spell.teleport_zone, "SumEarth", 8) && spell.classes[Class::Magician - 1] < earth_min_level) { + earth_min_level = spell.classes[Class::Magician - 1]; + } + else if (!strncmp(spell.teleport_zone, "MonsterSum", 10) && spell.classes[Class::Magician - 1] < monster_min_level) { + monster_min_level = spell.classes[Class::Magician - 1]; + } + else if (!strncmp(spell.teleport_zone, epic_spell_name.c_str(), epic_spell_name.length()) && spell.classes[Class::Magician - 1] < epic_min_level) { + epic_min_level = spell.classes[Class::Magician - 1]; + } + } + + if (epic_allowed) { + epic_min_level = std::max(int(epic_min_level), RuleI(Bots, AllowMagicianEpicPetLevel)); + + if (bot_level >= epic_min_level) { + result = epic_spell_name; } } else { - // Over level 30 - int counter = zone->random.Int(0, 4); + bool found = false; + uint8 count = 0; - switch(counter) { - case 0: - result = std::string("SumWater"); - break; - case 1: - result = std::string("SumFire"); - break; - case 2: - result = std::string("SumAir"); - break; - case 3: - result = std::string("SumEarth"); - break; - default: - result = std::string("MonsterSum"); - break; + while (count <= 4 && !found) { + int counter = zone->random.Int(1, 4); + + switch (counter) { + case SumWater: + if (bot_level >= water_min_level) { + result = std::string("SumWater"); + } + + found = true; + break; + case SumFire: + if (bot_level >= fire_min_level) { + result = std::string("SumFire"); + } + + found = true; + break; + case SumAir: + if (bot_level >= air_min_level) { + result = std::string("SumAir"); + } + + found = true; + break; + case SumEarth: + if (bot_level >= earth_min_level) { + result = std::string("SumEarth"); + } + + found = true; + break; + } + + ++count; } } } @@ -2532,24 +1718,44 @@ std::string Bot::GetBotMagicianPetType(Bot* botCaster) { return result; } -BotSpell Bot::GetBestBotSpellForNukeByTargetType(Bot* botCaster, SpellTargetType targetType) { +BotSpell Bot::GetBestBotSpellForNukeByTargetType(Bot* caster, SpellTargetType target_type, uint16 spell_type, bool AE, Mob* tar) { BotSpell result; result.SpellId = 0; result.SpellIndex = 0; result.ManaCost = 0; - if (botCaster) { - std::list botSpellList = GetBotSpellsForSpellEffectAndTargetType(botCaster, SE_CurrentHP, targetType); + if (tar == nullptr) { + tar = caster->GetTarget(); + } - for(std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { - // Assuming all the spells have been loaded into this list by level and in descending order - if ((IsPureNukeSpell(botSpellListItr->SpellId) || IsDamageSpell(botSpellListItr->SpellId)) && botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId)) { - result.SpellId = botSpellListItr->SpellId; - result.SpellIndex = botSpellListItr->SpellIndex; - result.ManaCost = botSpellListItr->ManaCost; + if (caster) { + std::list bot_spell_list = GetBotSpellsForSpellEffectAndTargetType(caster, spell_type, SpellEffect::CurrentHP, target_type); - break; + for(std::list::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) { + 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; + } + else if (AE && !IsAnyAESpell(bot_spell_list_itr->SpellId)) { + continue; + } + + if (!IsPBAESpell(bot_spell_list_itr->SpellId) && !caster->CastChecks(bot_spell_list_itr->SpellId, tar, spell_type, false, IsAEBotSpellType(spell_type))) { + continue; + } + + if ( + caster->IsCommandedSpell() || + !AE || + (AE && caster->HasValidAETarget(caster, bot_spell_list_itr->SpellId, spell_type, tar)) + ) { + result.SpellId = bot_spell_list_itr->SpellId; + result.SpellIndex = bot_spell_list_itr->SpellIndex; + result.ManaCost = bot_spell_list_itr->ManaCost; + + break; + } } } } @@ -2557,7 +1763,7 @@ BotSpell Bot::GetBestBotSpellForNukeByTargetType(Bot* botCaster, SpellTargetType return result; } -BotSpell Bot::GetBestBotSpellForStunByTargetType(Bot* botCaster, SpellTargetType targetType) +BotSpell Bot::GetBestBotSpellForStunByTargetType(Bot* caster, SpellTargetType target_type, uint16 spell_type, bool AE, Mob* tar) { BotSpell result; @@ -2565,19 +1771,39 @@ BotSpell Bot::GetBestBotSpellForStunByTargetType(Bot* botCaster, SpellTargetType result.SpellIndex = 0; result.ManaCost = 0; - if (botCaster) - { - std::list botSpellList = GetBotSpellsForSpellEffectAndTargetType(botCaster, SE_Stun, targetType); + if (tar == nullptr) { + tar = caster->GetTarget(); + } - for(std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) + if (caster) + { + std::list bot_spell_list = GetBotSpellsForSpellEffectAndTargetType(caster, spell_type, SpellEffect::Stun, target_type); + + for(std::list::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(botSpellListItr->SpellId) && botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId)) - { - result.SpellId = botSpellListItr->SpellId; - result.SpellIndex = botSpellListItr->SpellIndex; - result.ManaCost = botSpellListItr->ManaCost; - break; + if (IsStunSpell(bot_spell_list_itr->SpellId)) { + if (!AE && IsAnyAESpell(bot_spell_list_itr->SpellId) && !IsGroupSpell(bot_spell_list_itr->SpellId)) { + continue; + } + else if (AE && !IsAnyAESpell(bot_spell_list_itr->SpellId)) { + continue; + } + + if (!IsPBAESpell(bot_spell_list_itr->SpellId) && !caster->CastChecks(bot_spell_list_itr->SpellId, tar, spell_type, false, IsAEBotSpellType(spell_type))) { + continue; + } + + if ( + caster->IsCommandedSpell() || + !AE || + (AE && caster->HasValidAETarget(caster, bot_spell_list_itr->SpellId, spell_type, tar)) + ) { + result.SpellId = bot_spell_list_itr->SpellId; + result.SpellIndex = bot_spell_list_itr->SpellIndex; + result.ManaCost = bot_spell_list_itr->ManaCost; + + break; + } } } } @@ -2585,105 +1811,147 @@ BotSpell Bot::GetBestBotSpellForStunByTargetType(Bot* botCaster, SpellTargetType return result; } -BotSpell Bot::GetBestBotWizardNukeSpellByTargetResists(Bot* botCaster, Mob* target) { +BotSpell Bot::GetBestBotWizardNukeSpellByTargetResists(Bot* caster, Mob* target, uint16 spell_type) { BotSpell result; result.SpellId = 0; result.SpellIndex = 0; result.ManaCost = 0; - if (botCaster && target) { - const int lureResisValue = -100; - const int maxTargetResistValue = 300; - bool selectLureNuke = false; + if (caster && target) { - if ((target->GetMR() > maxTargetResistValue) && (target->GetCR() > maxTargetResistValue) && (target->GetFR() > maxTargetResistValue)) - selectLureNuke = true; + const int lure_resis_value = -100; + + int32 level_mod = (target->GetLevel() - caster->GetLevel()) * (target->GetLevel() - caster->GetLevel()) / 2; + + if (target->GetLevel() - caster->GetLevel() < 0) { + level_mod = -level_mod; + } + const int max_target_resist_value = caster->GetSpellTypeResistLimit(spell_type); + bool select_lure_nuke = false; + + if (((target->GetMR() + level_mod) > max_target_resist_value) && ((target->GetCR() + level_mod) > max_target_resist_value) && ((target->GetFR() + level_mod) > max_target_resist_value)) { + select_lure_nuke = true; + } - std::list botSpellList = GetBotSpellsForSpellEffectAndTargetType(botCaster, SE_CurrentHP, ST_Target); + std::list bot_spell_list = GetBotSpellsForSpellEffectAndTargetType(caster, spell_type, SpellEffect::CurrentHP, ST_Target); - BotSpell firstWizardMagicNukeSpellFound; - firstWizardMagicNukeSpellFound.SpellId = 0; - firstWizardMagicNukeSpellFound.SpellIndex = 0; - firstWizardMagicNukeSpellFound.ManaCost = 0; + BotSpell first_wizard_magic_nuke_spell_found; + first_wizard_magic_nuke_spell_found.SpellId = 0; + first_wizard_magic_nuke_spell_found.SpellIndex = 0; + first_wizard_magic_nuke_spell_found.ManaCost = 0; + bool spell_selected = false; - for(std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { - // Assuming all the spells have been loaded into this list by level and in descending order - bool spellSelected = false; + for (std::list::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) { + if (!caster->IsValidSpellRange(bot_spell_list_itr->SpellId, target)) { + continue; + } - if (botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId)) { - if (selectLureNuke && (spells[botSpellListItr->SpellId].resist_difficulty < lureResisValue)) { - spellSelected = true; + if (select_lure_nuke && (spells[bot_spell_list_itr->SpellId].resist_difficulty < lure_resis_value)) { + if (caster->CastChecks(bot_spell_list_itr->SpellId, target, spell_type)) { + spell_selected = true; } - else if (IsPureNukeSpell(botSpellListItr->SpellId)) { - if (((target->GetMR() < target->GetCR()) || (target->GetMR() < target->GetFR())) && (GetSpellResistType(botSpellListItr->SpellId) == RESIST_MAGIC) - && (spells[botSpellListItr->SpellId].resist_difficulty > lureResisValue)) - { - spellSelected = true; - } - else if (((target->GetCR() < target->GetMR()) || (target->GetCR() < target->GetFR())) && (GetSpellResistType(botSpellListItr->SpellId) == RESIST_COLD) - && (spells[botSpellListItr->SpellId].resist_difficulty > lureResisValue)) - { - spellSelected = true; - } - else if (((target->GetFR() < target->GetCR()) || (target->GetFR() < target->GetMR())) && (GetSpellResistType(botSpellListItr->SpellId) == RESIST_FIRE) - && (spells[botSpellListItr->SpellId].resist_difficulty > lureResisValue)) - { - spellSelected = true; - } - else if ((GetSpellResistType(botSpellListItr->SpellId) == RESIST_MAGIC) && (spells[botSpellListItr->SpellId].resist_difficulty > lureResisValue) && !IsStunSpell(botSpellListItr->SpellId)) { - firstWizardMagicNukeSpellFound.SpellId = botSpellListItr->SpellId; - firstWizardMagicNukeSpellFound.SpellIndex = botSpellListItr->SpellIndex; - firstWizardMagicNukeSpellFound.ManaCost = botSpellListItr->ManaCost; - } + } + else if (!select_lure_nuke && IsPureNukeSpell(bot_spell_list_itr->SpellId)) { + if ( + ((target->GetMR() < target->GetCR()) || (target->GetMR() < target->GetFR())) && + (GetSpellResistType(bot_spell_list_itr->SpellId) == RESIST_MAGIC) && + (spells[bot_spell_list_itr->SpellId].resist_difficulty > lure_resis_value) && + caster->CastChecks(bot_spell_list_itr->SpellId, target, spell_type) + ) { + spell_selected = true; + } + else if ( + ((target->GetCR() < target->GetMR()) || (target->GetCR() < target->GetFR())) && + (GetSpellResistType(bot_spell_list_itr->SpellId) == RESIST_COLD) && + (spells[bot_spell_list_itr->SpellId].resist_difficulty > lure_resis_value) && + caster->CastChecks(bot_spell_list_itr->SpellId, target, spell_type) + ) { + spell_selected = true; + } + else if ( + ((target->GetFR() < target->GetCR()) || (target->GetFR() < target->GetMR())) && + (GetSpellResistType(bot_spell_list_itr->SpellId) == RESIST_FIRE) && + (spells[bot_spell_list_itr->SpellId].resist_difficulty > lure_resis_value) && + caster->CastChecks(bot_spell_list_itr->SpellId, target, spell_type) + ) { + spell_selected = true; + } + else if ( + (GetSpellResistType(bot_spell_list_itr->SpellId) == RESIST_MAGIC) && + (spells[bot_spell_list_itr->SpellId].resist_difficulty > lure_resis_value) && + caster->CastChecks(bot_spell_list_itr->SpellId, target, spell_type) + ) { + first_wizard_magic_nuke_spell_found.SpellId = bot_spell_list_itr->SpellId; + first_wizard_magic_nuke_spell_found.SpellIndex = bot_spell_list_itr->SpellIndex; + first_wizard_magic_nuke_spell_found.ManaCost = bot_spell_list_itr->ManaCost; } } - if (spellSelected) { - result.SpellId = botSpellListItr->SpellId; - result.SpellIndex = botSpellListItr->SpellIndex; - result.ManaCost = botSpellListItr->ManaCost; + if (spell_selected) { + result.SpellId = bot_spell_list_itr->SpellId; + result.SpellIndex = bot_spell_list_itr->SpellIndex; + result.ManaCost = bot_spell_list_itr->ManaCost; break; } } + if (!spell_selected) { + for (std::list::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) { + if (caster->CheckSpellRecastTimer(bot_spell_list_itr->SpellId)) { + if (caster->CastChecks(bot_spell_list_itr->SpellId, target, spell_type)) { + spell_selected = true; + } + } + if (spell_selected) { + result.SpellId = bot_spell_list_itr->SpellId; + result.SpellIndex = bot_spell_list_itr->SpellIndex; + result.ManaCost = bot_spell_list_itr->ManaCost; + + break; + } + } + } + if (result.SpellId == 0) { - result = firstWizardMagicNukeSpellFound; + result = first_wizard_magic_nuke_spell_found; } } return result; } -BotSpell Bot::GetDebuffBotSpell(Bot* botCaster, Mob *tar) { +BotSpell Bot::GetDebuffBotSpell(Bot* caster, Mob *tar, uint16 spell_type) { BotSpell result; result.SpellId = 0; result.SpellIndex = 0; result.ManaCost = 0; - if (!tar || !botCaster) + if (!tar || !caster) return result; - if (botCaster->AI_HasSpells()) { - std::vector botSpellList = botCaster->AIBot_spells; + if (caster->AI_HasSpells()) { + const std::vector& bot_spell_list = caster->BotGetSpellsByType(spell_type); - for (int i = botSpellList.size() - 1; i >= 0; i--) { - if (!IsValidSpell(botSpellList[i].spellid)) { - // this is both to quit early to save cpu and to avoid casting bad spells - // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here + for (int i = bot_spell_list.size() - 1; i >= 0; i--) { + if (!IsValidSpell(bot_spell_list[i].spellid)) { continue; } - if (((botSpellList[i].type & SpellType_Debuff) || IsDebuffSpell(botSpellList[i].spellid)) - && (!tar->IsImmuneToSpell(botSpellList[i].spellid, botCaster) - && tar->CanBuffStack(botSpellList[i].spellid, botCaster->GetLevel(), true) >= 0) - && botCaster->CheckSpellRecastTimer(botSpellList[i].spellid)) { - result.SpellId = botSpellList[i].spellid; - result.SpellIndex = i; - result.ManaCost = botSpellList[i].manacost; + if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) { + continue; + } + + if (((bot_spell_list[i].type == BotSpellTypes::Debuff) || IsDebuffSpell(bot_spell_list[i].spellid)) + && (!tar->IsImmuneToSpell(bot_spell_list[i].spellid, caster) + && tar->CanBuffStack(bot_spell_list[i].spellid, caster->GetLevel(), true) >= 0) + && caster->CheckSpellRecastTimer(bot_spell_list[i].spellid)) { + result.SpellId = bot_spell_list[i].spellid; + result.SpellIndex = bot_spell_list[i].index; + result.ManaCost = bot_spell_list[i].manacost; break; } @@ -2693,50 +1961,56 @@ BotSpell Bot::GetDebuffBotSpell(Bot* botCaster, Mob *tar) { return result; } -BotSpell Bot::GetBestBotSpellForResistDebuff(Bot* botCaster, Mob *tar) { +BotSpell Bot::GetBestBotSpellForResistDebuff(Bot* caster, Mob *tar, uint16 spell_type) { BotSpell result; result.SpellId = 0; result.SpellIndex = 0; result.ManaCost = 0; - if (!tar || !botCaster) { + if (!tar || !caster) { return result; } - int level_mod = (tar->GetLevel() - botCaster->GetLevel())* (tar->GetLevel() - botCaster->GetLevel()) / 2; - if (tar->GetLevel() - botCaster->GetLevel() < 0) { + int level_mod = (tar->GetLevel() - caster->GetLevel())* (tar->GetLevel() - caster->GetLevel()) / 2; + if (tar->GetLevel() - caster->GetLevel() < 0) { level_mod = -level_mod; } - bool needsMagicResistDebuff = (tar->GetMR() + level_mod) > 100; - bool needsColdResistDebuff = (tar->GetCR() + level_mod) > 100; - bool needsFireResistDebuff = (tar->GetFR() + level_mod) > 100; - bool needsPoisonResistDebuff = (tar->GetPR() + level_mod) > 100; - bool needsDiseaseResistDebuff = (tar->GetDR() + level_mod) > 100; + bool needs_magic_resist_debuff = (tar->GetMR() + level_mod) > 100; + bool needs_cold_resist_debuff = (tar->GetCR() + level_mod) > 100; + bool needs_fire_resist_debuff = (tar->GetFR() + level_mod) > 100; + bool needs_poison_resist_debuff = (tar->GetPR() + level_mod) > 100; + bool needs_disease_resist_debuff = (tar->GetDR() + level_mod) > 100; - if (botCaster->AI_HasSpells()) { - std::vector botSpellList = botCaster->AIBot_spells; + if (caster->AI_HasSpells()) { + const std::vector& bot_spell_list = caster->BotGetSpellsByType(spell_type); - for (int i = botSpellList.size() - 1; i >= 0; i--) { - if (!IsValidSpell(botSpellList[i].spellid)) { - // this is both to quit early to save cpu and to avoid casting bad spells - // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here + for (int i = bot_spell_list.size() - 1; i >= 0; i--) { + if (!IsValidSpell(bot_spell_list[i].spellid)) { continue; } - if (((botSpellList[i].type & SpellType_Debuff) || IsResistDebuffSpell(botSpellList[i].spellid)) - && ((needsMagicResistDebuff && (IsEffectInSpell(botSpellList[i].spellid, SE_ResistMagic)) || IsEffectInSpell(botSpellList[i].spellid, SE_ResistAll)) - || (needsColdResistDebuff && (IsEffectInSpell(botSpellList[i].spellid, SE_ResistCold)) || IsEffectInSpell(botSpellList[i].spellid, SE_ResistAll)) - || (needsFireResistDebuff && (IsEffectInSpell(botSpellList[i].spellid, SE_ResistFire)) || IsEffectInSpell(botSpellList[i].spellid, SE_ResistAll)) - || (needsPoisonResistDebuff && (IsEffectInSpell(botSpellList[i].spellid, SE_ResistPoison)) || IsEffectInSpell(botSpellList[i].spellid, SE_ResistAll)) - || (needsDiseaseResistDebuff && (IsEffectInSpell(botSpellList[i].spellid, SE_ResistDisease)) || IsEffectInSpell(botSpellList[i].spellid, SE_ResistAll))) - && (!tar->IsImmuneToSpell(botSpellList[i].spellid, botCaster) - && tar->CanBuffStack(botSpellList[i].spellid, botCaster->GetLevel(), true) >= 0) - && botCaster->CheckSpellRecastTimer(botSpellList[i].spellid)) { - result.SpellId = botSpellList[i].spellid; - result.SpellIndex = i; - result.ManaCost = botSpellList[i].manacost; + if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) { + continue; + } + + if ( + (bot_spell_list[i].type == BotSpellTypes::Debuff || IsResistDebuffSpell(bot_spell_list[i].spellid)) && + ( + (needs_magic_resist_debuff && (IsEffectInSpell(bot_spell_list[i].spellid, SpellEffect::ResistMagic) || IsEffectInSpell(bot_spell_list[i].spellid, SpellEffect::ResistAll))) || + (needs_cold_resist_debuff && (IsEffectInSpell(bot_spell_list[i].spellid, SpellEffect::ResistCold) || IsEffectInSpell(bot_spell_list[i].spellid, SpellEffect::ResistAll))) || + (needs_fire_resist_debuff && (IsEffectInSpell(bot_spell_list[i].spellid, SpellEffect::ResistFire) || IsEffectInSpell(bot_spell_list[i].spellid, SpellEffect::ResistAll))) || + (needs_poison_resist_debuff && (IsEffectInSpell(bot_spell_list[i].spellid, SpellEffect::ResistPoison) || IsEffectInSpell(bot_spell_list[i].spellid, SpellEffect::ResistAll))) || + (needs_disease_resist_debuff && (IsEffectInSpell(bot_spell_list[i].spellid, SpellEffect::ResistDisease) || IsEffectInSpell(bot_spell_list[i].spellid, SpellEffect::ResistAll))) + ) && + !tar->IsImmuneToSpell(bot_spell_list[i].spellid, caster) && + tar->CanBuffStack(bot_spell_list[i].spellid, caster->GetLevel(), true) >= 0 && + caster->CheckSpellRecastTimer(bot_spell_list[i].spellid) + ) { + result.SpellId = bot_spell_list[i].spellid; + result.SpellIndex = bot_spell_list[i].index; + result.ManaCost = bot_spell_list[i].manacost; break; } @@ -2746,109 +2020,81 @@ BotSpell Bot::GetBestBotSpellForResistDebuff(Bot* botCaster, Mob *tar) { return result; } -BotSpell Bot::GetBestBotSpellForCure(Bot* botCaster, Mob *tar) { +BotSpell Bot::GetBestBotSpellForCure(Bot* caster, Mob* tar, uint16 spell_type) { BotSpell_wPriority result; - bool spellSelected = false; result.SpellId = 0; result.SpellIndex = 0; result.ManaCost = 0; - if (!tar) + if (!tar) { return result; + } - int countNeedsCured = 0; - bool isPoisoned = tar->FindType(SE_PoisonCounter); - bool isDiseased = tar->FindType(SE_DiseaseCounter); - bool isCursed = tar->FindType(SE_CurseCounter); - bool isCorrupted = tar->FindType(SE_CorruptionCounter); + if (caster) { + std::vector bot_spell_list_itr = GetPrioritizedBotSpellsBySpellType(caster, spell_type, tar); - if (botCaster && botCaster->AI_HasSpells()) { - std::list cureList = GetPrioritizedBotSpellsBySpellType(botCaster, SpellType_Cure); + if (IsGroupBotSpellType(spell_type)) { + int count_needs_cured = 0; + uint16 count_poisoned = 0; + uint16 count_diseased = 0; + uint16 count_cursed = 0; + uint16 count_corrupted = 0; - if (tar->HasGroup()) { - Group *g = tar->GetGroup(); + for (std::vector::iterator itr = bot_spell_list_itr.begin(); itr != bot_spell_list_itr.end(); ++itr) { + if (!IsValidSpell(itr->SpellId) || !IsGroupSpell(itr->SpellId)) { + continue; + } - if (g) { - for( int i = 0; imembers[i] && !g->members[i]->qglobal) { - if (botCaster->GetNeedsCured(g->members[i])) - countNeedsCured++; + for (Mob* m : (IsGroupBotSpellType(spell_type) ? caster->GetSpellTargetList() : caster->GetSpellTargetList(true))) { + if (caster->GetNeedsCured(m)) { + if (caster->CastChecks(itr->SpellId, m, spell_type, true, IsGroupBotSpellType(spell_type))) { + if (m->FindType(SpellEffect::PoisonCounter)) { + ++count_poisoned; + } + if (m->FindType(SpellEffect::DiseaseCounter)) { + ++count_diseased; + } + if (m->FindType(SpellEffect::CurseCounter)) { + ++count_cursed; + } + if (m->FindType(SpellEffect::CorruptionCounter)) { + ++count_corrupted; + } + } } } + + if ( + (count_poisoned >= caster->GetSpellTypeAEOrGroupTargetCount(spell_type) && IsEffectInSpell(itr->SpellId, SpellEffect::PoisonCounter)) || + (count_diseased >= caster->GetSpellTypeAEOrGroupTargetCount(spell_type) && IsEffectInSpell(itr->SpellId, SpellEffect::DiseaseCounter)) || + (count_cursed >= caster->GetSpellTypeAEOrGroupTargetCount(spell_type) && IsEffectInSpell(itr->SpellId, SpellEffect::CurseCounter)) || + (count_corrupted >= caster->GetSpellTypeAEOrGroupTargetCount(spell_type) && IsEffectInSpell(itr->SpellId, SpellEffect::CorruptionCounter)) + ) { + result.SpellId = itr->SpellId; + result.SpellIndex = itr->SpellIndex; + result.ManaCost = itr->ManaCost; + + break; + } } } - - //Check for group cure first - if (countNeedsCured > 2) { - for (std::list::iterator itr = cureList.begin(); itr != cureList.end(); ++itr) { - BotSpell selectedBotSpell = *itr; - - if (IsGroupSpell(itr->SpellId) && botCaster->CheckSpellRecastTimer(selectedBotSpell.SpellId)) { - if (selectedBotSpell.SpellId == 0) - continue; - - if (isPoisoned && IsEffectInSpell(selectedBotSpell.SpellId, SE_PoisonCounter)) { - spellSelected = true; - } - else if (isDiseased && IsEffectInSpell(selectedBotSpell.SpellId, SE_DiseaseCounter)) { - spellSelected = true; - } - else if (isCursed && IsEffectInSpell(selectedBotSpell.SpellId, SE_CurseCounter)) { - spellSelected = true; - } - else if (isCorrupted && IsEffectInSpell(selectedBotSpell.SpellId, SE_CorruptionCounter)) { - spellSelected = true; - } - else if (IsEffectInSpell(selectedBotSpell.SpellId, SE_DispelDetrimental)) { - spellSelected = true; - } - - if (spellSelected) - { - result.SpellId = selectedBotSpell.SpellId; - result.SpellIndex = selectedBotSpell.SpellIndex; - result.ManaCost = selectedBotSpell.ManaCost; - - break; - } + else { + for (std::vector::iterator itr = bot_spell_list_itr.begin(); itr != bot_spell_list_itr.end(); ++itr) { + if (!IsValidSpell(itr->SpellId) || IsGroupSpell(itr->SpellId)) { + continue; } - } - } - //no group cure for target- try to find single target spell - if (!spellSelected) { - for(std::list::iterator itr = cureList.begin(); itr != cureList.end(); ++itr) { - BotSpell selectedBotSpell = *itr; - - if (botCaster->CheckSpellRecastTimer(selectedBotSpell.SpellId)) { - if (selectedBotSpell.SpellId == 0) - continue; - - if (isPoisoned && IsEffectInSpell(selectedBotSpell.SpellId, SE_PoisonCounter)) { - spellSelected = true; - } - else if (isDiseased && IsEffectInSpell(selectedBotSpell.SpellId, SE_DiseaseCounter)) { - spellSelected = true; - } - else if (isCursed && IsEffectInSpell(selectedBotSpell.SpellId, SE_CurseCounter)) { - spellSelected = true; - } - else if (isCorrupted && IsEffectInSpell(selectedBotSpell.SpellId, SE_CorruptionCounter)) { - spellSelected = true; - } - else if (IsEffectInSpell(selectedBotSpell.SpellId, SE_DispelDetrimental)) { - spellSelected = true; - } - - if (spellSelected) - { - result.SpellId = selectedBotSpell.SpellId; - result.SpellIndex = selectedBotSpell.SpellIndex; - result.ManaCost = selectedBotSpell.ManaCost; - - break; - } + if ( + tar->FindType(SpellEffect::PoisonCounter) && IsEffectInSpell(itr->SpellId, SpellEffect::PoisonCounter) || + tar->FindType(SpellEffect::DiseaseCounter) && IsEffectInSpell(itr->SpellId, SpellEffect::DiseaseCounter) || + tar->FindType(SpellEffect::CurseCounter) && IsEffectInSpell(itr->SpellId, SpellEffect::CurseCounter) || + tar->FindType(SpellEffect::CorruptionCounter) && IsEffectInSpell(itr->SpellId, SpellEffect::CorruptionCounter) + ) { + result.SpellId = itr->SpellId; + result.SpellIndex = itr->SpellIndex; + result.ManaCost = itr->ManaCost; + break; } } } @@ -2857,108 +2103,85 @@ BotSpell Bot::GetBestBotSpellForCure(Bot* botCaster, Mob *tar) { return result; } -uint8 Bot::GetChanceToCastBySpellType(uint32 spellType) +uint8 Bot::GetChanceToCastBySpellType(uint16 spell_type) { - uint8 spell_type_index = SPELL_TYPE_COUNT; - switch (spellType) { - case SpellType_Nuke: - spell_type_index = spellTypeIndexNuke; - break; - case SpellType_Heal: - spell_type_index = spellTypeIndexHeal; - break; - case SpellType_Root: - spell_type_index = spellTypeIndexRoot; - break; - case SpellType_Buff: - spell_type_index = spellTypeIndexBuff; - break; - case SpellType_Escape: - spell_type_index = spellTypeIndexEscape; - break; - case SpellType_Pet: - spell_type_index = spellTypeIndexPet; - break; - case SpellType_Lifetap: - spell_type_index = spellTypeIndexLifetap; - break; - case SpellType_Snare: - spell_type_index = spellTypeIndexSnare; - break; - case SpellType_DOT: - spell_type_index = spellTypeIndexDot; - break; - case SpellType_Dispel: - spell_type_index = spellTypeIndexDispel; - break; - case SpellType_InCombatBuff: - spell_type_index = spellTypeIndexInCombatBuff; - break; - case SpellType_Mez: - spell_type_index = spellTypeIndexMez; - break; - case SpellType_Charm: - spell_type_index = spellTypeIndexCharm; - break; - case SpellType_Slow: - spell_type_index = spellTypeIndexSlow; - break; - case SpellType_Debuff: - spell_type_index = spellTypeIndexDebuff; - break; - case SpellType_Cure: - spell_type_index = spellTypeIndexCure; - break; - case SpellType_Resurrect: - spell_type_index = spellTypeIndexResurrect; - break; - case SpellType_HateRedux: - spell_type_index = spellTypeIndexHateRedux; - break; - case SpellType_InCombatBuffSong: - spell_type_index = spellTypeIndexInCombatBuffSong; - break; - case SpellType_OutOfCombatBuffSong: - spell_type_index = spellTypeIndexOutOfCombatBuffSong; - break; - case SpellType_PreCombatBuff: - spell_type_index = spellTypeIndexPreCombatBuff; - break; - case SpellType_PreCombatBuffSong: - spell_type_index = spellTypeIndexPreCombatBuffSong; - break; - default: - break; + switch (spell_type) { + case BotSpellTypes::AENukes: + case BotSpellTypes::AERains: + case BotSpellTypes::AEStun: + case BotSpellTypes::AESnare: + case BotSpellTypes::AESlow: + case BotSpellTypes::AEDebuff: + case BotSpellTypes::AEFear: + case BotSpellTypes::AEDispel: + case BotSpellTypes::AEDoT: + case BotSpellTypes::AELifetap: + case BotSpellTypes::AERoot: + case BotSpellTypes::PBAENuke: + case BotSpellTypes::AEHateLine: + return RuleI(Bots, PercentChanceToCastAEs); + case BotSpellTypes::GroupHeals: + case BotSpellTypes::GroupCompleteHeals: + case BotSpellTypes::GroupHoTHeals: + return RuleI(Bots, PercentChanceToCastGroupHeal); + case BotSpellTypes::Nuke: + return RuleI(Bots, PercentChanceToCastNuke); + case BotSpellTypes::Root: + return RuleI(Bots, PercentChanceToCastRoot); + case BotSpellTypes::Buff: + case BotSpellTypes::PetBuffs: + case BotSpellTypes::ResistBuffs: + case BotSpellTypes::PetResistBuffs: + case BotSpellTypes::DamageShields: + case BotSpellTypes::PetDamageShields: + return RuleI(Bots, PercentChanceToCastBuff); + case BotSpellTypes::Escape: + return RuleI(Bots, PercentChanceToCastEscape); + case BotSpellTypes::Lifetap: + return RuleI(Bots, PercentChanceToCastLifetap); + case BotSpellTypes::Snare: + return RuleI(Bots, PercentChanceToCastSnare); + case BotSpellTypes::DOT: + return RuleI(Bots, PercentChanceToCastDOT); + case BotSpellTypes::Dispel: + return RuleI(Bots, PercentChanceToCastDispel); + case BotSpellTypes::InCombatBuff: + return RuleI(Bots, PercentChanceToCastInCombatBuff); + case BotSpellTypes::HateLine: + return RuleI(Bots, PercentChanceToCastHateLine); + case BotSpellTypes::Mez: + return RuleI(Bots, PercentChanceToCastMez); + case BotSpellTypes::Slow: + return RuleI(Bots, PercentChanceToCastSlow); + case BotSpellTypes::Debuff: + return RuleI(Bots, PercentChanceToCastDebuff); + case BotSpellTypes::Cure: + case BotSpellTypes::PetCures: + return RuleI(Bots, PercentChanceToCastCure); + case BotSpellTypes::GroupCures: + return RuleI(Bots, PercentChanceToCastGroupCure); + case BotSpellTypes::HateRedux: + return RuleI(Bots, PercentChanceToCastHateRedux); + case BotSpellTypes::Fear: + return RuleI(Bots, PercentChanceToCastFear); + case BotSpellTypes::RegularHeal: + case BotSpellTypes::CompleteHeal: + case BotSpellTypes::FastHeals: + case BotSpellTypes::VeryFastHeals: + case BotSpellTypes::HoTHeals: + case BotSpellTypes::PetRegularHeals: + case BotSpellTypes::PetCompleteHeals: + case BotSpellTypes::PetFastHeals: + case BotSpellTypes::PetVeryFastHeals: + case BotSpellTypes::PetHoTHeals: + return RuleI(Bots, PercentChanceToCastHeal); + case BotSpellTypes::AEMez: + return RuleI(Bots, PercentChanceToCastAEMez); + default: + return RuleI(Bots, PercentChanceToCastOtherType); } - if (spell_type_index >= SPELL_TYPE_COUNT) - return 0; - - uint8 class_index = GetClass(); - if (class_index > Class::Berserker || class_index < Class::Warrior) - return 0; - --class_index; - - uint32 stance_id = GetBotStance(); - if (!Stance::IsValid(stance_id)) { - return 0; - } - - uint8 stance_index = Stance::GetIndex(stance_id); - uint8 type_index = nHSND; - - if (HasGroup()) { - if (IsGroupHealer()/* || IsRaidHealer()*/) - type_index |= pH; - if (IsGroupSlower()/* || IsRaidSlower()*/) - type_index |= pS; - if (IsGroupNuker()/* || IsRaidNuker()*/) - type_index |= pN; - if (IsGroupDoter()/* || IsRaidDoter()*/) - type_index |= pD; - } - - return database.botdb.GetSpellCastingChance(spell_type_index, class_index, stance_index, type_index); + return RuleI(Bots, PercentChanceToCastOtherType); } bool Bot::AI_AddBotSpells(uint32 bot_spell_id) { @@ -2966,6 +2189,8 @@ bool Bot::AI_AddBotSpells(uint32 bot_spell_id) { npc_spells_id = bot_spell_id; AIBot_spells.clear(); AIBot_spells_enforced.clear(); + AIBot_spells_by_type.clear(); + if (!bot_spell_id) { AIautocastspell_timer->Disable(); return false; @@ -3217,7 +2442,7 @@ bool Bot::AI_AddBotSpells(uint32 bot_spell_id) { } } - std::sort(AIBot_spells.begin(), AIBot_spells.end(), [](const BotSpells_Struct& a, const BotSpells_Struct& b) { + std::sort(AIBot_spells.begin(), AIBot_spells.end(), [](const BotSpells& a, const BotSpells& b) { return a.priority > b.priority; }); @@ -3256,6 +2481,7 @@ bool Bot::AI_AddBotSpells(uint32 bot_spell_id) { AIautocastspell_timer->Disable(); } else { AIautocastspell_timer->Trigger(); + AssignBotSpellsToTypes(AIBot_spells, AIBot_spells_by_type); // Assign AIBot_spells to AIBot_spells_by_type with an index } return true; } @@ -3340,7 +2566,7 @@ DBbotspells_Struct* ZoneDatabase::GetBotSpells(uint32 bot_spell_id) entry.bucket_comparison = e.bucket_comparison; // some spell types don't make much since to be priority 0, so fix that - if (!(entry.type & SPELL_TYPES_INNATE) && entry.priority == 0) { + if (!IsBotSpellTypeInnate(entry.type) && entry.priority == 0) { entry.priority = 1; } @@ -3383,7 +2609,7 @@ void Bot::AddSpellToBotList( } HasAISpell = true; - BotSpells_Struct t; + BotSpells t; t.priority = in_priority; t.spellid = in_spell_id; @@ -3429,22 +2655,22 @@ void Bot::AddSpellToBotEnforceList( } HasAISpell = true; - BotSpells_Struct t; + BotSpells t; - t.priority = iPriority; - t.spellid = iSpellID; - t.type = iType; - t.manacost = iManaCost; - t.recast_delay = iRecastDelay; - t.time_cancast = 0; - t.resist_adjust = iResistAdjust; - t.minlevel = min_level; - t.maxlevel = maxlevel; - t.min_hp = min_hp; - t.max_hp = max_hp; - t.bucket_name = bucket_name; - t.bucket_value = bucket_value; - t.bucket_comparison = bucket_comparison; + t.priority = iPriority; + t.spellid = iSpellID; + t.type = iType; + t.manacost = iManaCost; + t.recast_delay = iRecastDelay; + t.time_cancast = 0; + t.resist_adjust = iResistAdjust; + t.minlevel = min_level; + t.maxlevel = maxlevel; + t.min_hp = min_hp; + t.max_hp = max_hp; + t.bucket_name = bucket_name; + t.bucket_value = bucket_value; + t.bucket_comparison = bucket_comparison; AIBot_spells_enforced.push_back(t); } @@ -3477,7 +2703,7 @@ void Bot::AI_Bot_Event_SpellCastFinished(bool iCastSucceeded, uint16 slot) { } } -bool Bot::HasBotSpellEntry(uint16 spellid) { +bool Bot::HasBotSpellEntry(uint16 spell_id) { auto* spell_list = content_db.GetBotSpells(GetBotSpellID()); if (!spell_list) { @@ -3486,7 +2712,7 @@ bool Bot::HasBotSpellEntry(uint16 spellid) { // Check if Spell ID is found in Bot Spell Entries for (auto& e : spell_list->entries) { - if (spellid == e.spellid) { + if (spell_id == e.spellid) { return true; } } @@ -3494,16 +2720,166 @@ bool Bot::HasBotSpellEntry(uint16 spellid) { return false; } -bool Bot::IsValidSpellRange(uint16 spell_id, Mob const* tar) { - if (!IsValidSpell(spell_id)) { +bool Bot::CanUseBotSpell(uint16 spell_id) { + if (AIBot_spells.empty()) { return false; } - if (tar) { - int spellrange = (GetActSpellRange(spell_id, spells[spell_id].range) * GetActSpellRange(spell_id, spells[spell_id].range)); - if (spellrange >= DistanceSquared(m_Position, tar->GetPosition())) { - return true; + for (const auto& s : AIBot_spells) { + if (!IsValidSpell(s.spellid)) { + return false; } + + if (s.spellid != spell_id) { + continue; + } + + if (s.minlevel > GetLevel()) { + return false; + } + + return true; } + return false; } + +bool Bot::IsValidSpellRange(uint16 spell_id, Mob* tar) { + if (!IsValidSpell(spell_id) || !tar) { + return false; + } + + float range = spells[spell_id].range + GetRangeDistTargetSizeMod(tar); + + if ( + spells[spell_id].target_type != ST_AETargetHateList && + !IsTargetableAESpell(spell_id) && + IsAnyAESpell(spell_id) + ) { + range = GetAOERange(spell_id); + } + + if (RuleB(Bots, EnableBotTGB) && IsTGBCompatibleSpell(spell_id) && IsGroupSpell(spell_id)) { + range = spells[spell_id].aoe_range; + } + + range = GetActSpellRange(spell_id, range); + + if (HasProjectIllusion() && IsIllusionSpell(spell_id)) { + range = 100; + } + + float dist2 = DistanceSquared(m_Position, tar->GetPosition()); + float range2 = range * range; + float min_range2 = spells[spell_id].min_range * spells[spell_id].min_range; + + if (dist2 > range2) { + //target is out of range. + return false; + } + else if (dist2 < min_range2) { + //target is too close range. + return false; + } + + return true; +} + +BotSpell Bot::GetBestBotSpellForNukeByBodyType(Bot* caster, uint8 body_type, uint16 spell_type, bool AE, Mob* tar) { + BotSpell result; + + result.SpellId = 0; + result.SpellIndex = 0; + result.ManaCost = 0; + + if (!caster || !body_type) { + return result; + } + + if (tar == nullptr) { + tar = caster->GetTarget(); + } + + switch (body_type) { + case BodyType::Undead: + case BodyType::SummonedUndead: + case BodyType::Vampire: + result = GetBestBotSpellForNukeByTargetType(caster, (!AE ? ST_Undead : ST_UndeadAE), spell_type, AE, tar); + break; + case BodyType::Summoned: + case BodyType::Summoned2: + case BodyType::Summoned3: + result = GetBestBotSpellForNukeByTargetType(caster, (!AE ? ST_Summoned : ST_SummonedAE), spell_type, AE, tar); + break; + case BodyType::Animal: + result = GetBestBotSpellForNukeByTargetType(caster, ST_Animal, spell_type, AE, tar); + break; + case BodyType::Plant: + result = GetBestBotSpellForNukeByTargetType(caster, ST_Plant, spell_type, AE, tar); + break; + case BodyType::Giant: + result = GetBestBotSpellForNukeByTargetType(caster, ST_Giant, spell_type, AE, tar); + break; + case BodyType::Dragon: + result = GetBestBotSpellForNukeByTargetType(caster, ST_Dragon, spell_type, AE, tar); + break; + default: + break; + } + + return result; +} + +BotSpell Bot::GetBestBotSpellForRez(Bot* caster, Mob* target, uint16 spell_type) { + BotSpell result; + + result.SpellId = 0; + result.SpellIndex = 0; + result.ManaCost = 0; + + if (caster) { + std::list bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SpellEffect::Revive); + + for (std::list::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) { + if ( + IsResurrectSpell(bot_spell_list_itr->SpellId) && + caster->CheckSpellRecastTimer(bot_spell_list_itr->SpellId) + ) { + result.SpellId = bot_spell_list_itr->SpellId; + result.SpellIndex = bot_spell_list_itr->SpellIndex; + result.ManaCost = bot_spell_list_itr->ManaCost; + + break; + } + } + } + + return result; +} + +BotSpell Bot::GetBestBotSpellForCharm(Bot* caster, Mob* target, uint16 spell_type) { + BotSpell result; + + result.SpellId = 0; + result.SpellIndex = 0; + result.ManaCost = 0; + + if (caster) { + std::list bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SpellEffect::Charm); + + for (std::list::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) { + if ( + IsCharmSpell(bot_spell_list_itr->SpellId) && + caster->CastChecks(bot_spell_list_itr->SpellId, target, spell_type) + ) { + result.SpellId = bot_spell_list_itr->SpellId; + result.SpellIndex = bot_spell_list_itr->SpellIndex; + result.ManaCost = bot_spell_list_itr->ManaCost; + + break; + } + } + } + + return result; +} diff --git a/zone/cheat_manager.cpp b/zone/cheat_manager.cpp index 5a84dc09d..2d4696658 100644 --- a/zone/cheat_manager.cpp +++ b/zone/cheat_manager.cpp @@ -3,8 +3,10 @@ #include "quest_parser_collection.h" #include "../common/events/player_event_logs.h" #include "worldserver.h" +#include "queryserv.h" extern WorldServer worldserver; +extern QueryServ *QServ; void CheatManager::SetClient(Client *cli) { diff --git a/zone/cli/benchmark_databuckets.cpp b/zone/cli/benchmark_databuckets.cpp new file mode 100644 index 000000000..af0dcb84a --- /dev/null +++ b/zone/cli/benchmark_databuckets.cpp @@ -0,0 +1,296 @@ +#include +#include +#include +#include "../../common/http/httplib.h" +#include "../../common/eqemu_logsys.h" +#include "../sidecar_api/sidecar_api.h" +#include "../../common/platform.h" +#include "../../common/data_bucket.h" +#include "../zonedb.h" +#include "../../common/repositories/data_buckets_repository.h" + +void RunBenchmarkCycle(uint64_t target_rows) +{ + const size_t OPERATIONS_PER_TEST = 5000; + const std::string test_key_prefix = "test_key_"; + + std::cout << Strings::Repeat("-", 70) << "\n"; + std::cout << "📊 Running Benchmark at " << Strings::Commify(target_rows) << " Rows...\n"; + std::cout << Strings::Repeat("-", 70) << "\n"; + + // 🧹 **Purge `test_key_*` Keys Before Each Run** + std::cout << "🧹 Purging test keys (`test_key_*`)...\n"; + auto purge_start = std::chrono::high_resolution_clock::now(); + DataBucketsRepository::DeleteWhere(database, "`key` LIKE '" + test_key_prefix + "%'"); + auto purge_end = std::chrono::high_resolution_clock::now(); + std::chrono::duration purge_time = purge_end - purge_start; + std::cout << "✅ Purged test keys in " << purge_time.count() << " seconds.\n"; + + // 📊 **Ensure the Table Contains At Least `target_rows`** + auto populate_start = std::chrono::high_resolution_clock::now(); + uint64_t current_count = DataBucketsRepository::Count(database); + if (current_count < target_rows) { + std::cout << "📌 Populating table to " << Strings::Commify(target_rows) << " rows...\n"; + std::mt19937 rng(std::random_device{}()); + std::uniform_int_distribution entity_type(1, 5); + std::uniform_int_distribution id_dist(1, 1000000); + std::uniform_int_distribution expiry_dist(0, 86400 * 30); // Expiry up to 30 days + + while (current_count < target_rows) { + std::vector batch; + for (size_t i = 0; i < 100000; ++i) { + if (i > target_rows - current_count) { + break; + } + + int entity_choice = entity_type(rng); + int entity_id = id_dist(rng); + std::string key = "test_key_" + std::to_string(current_count + i); + std::string value = "value_" + std::to_string(current_count + i); + int expires = static_cast(std::time(nullptr)) + expiry_dist(rng); + + DataBucketsRepository::DataBuckets e{}; + e.key_ = key; + e.value = value; + e.expires = expires; + e.account_id = (entity_choice == 1) ? entity_id : 0; + e.character_id = (entity_choice == 2) ? entity_id : 0; + e.npc_id = (entity_choice == 3) ? entity_id : 0; + e.bot_id = (entity_choice == 4) ? entity_id : 0; + e.zone_id = (entity_choice == 5) ? entity_id : 0; + e.instance_id = (entity_choice == 5) ? entity_id : 0; + + batch.emplace_back(e); + } + DataBucketsRepository::InsertMany(database, batch); + current_count += batch.size(); + } + } + else { + std::cout << "✅ Table already has " << current_count << " rows, proceeding with benchmark.\n"; + } + + auto populate_end = std::chrono::high_resolution_clock::now(); + std::chrono::duration populate_time = populate_end - populate_start; + std::cout << "✅ Populated table in " << populate_time.count() << " seconds.\n"; + + std::mt19937 rng(std::random_device{}()); + std::uniform_int_distribution id_dist(1, 1000); + + // 🚀 **Measure Insert Performance** + std::vector inserted_keys = {}; + auto insert_start = std::chrono::high_resolution_clock::now(); + for (size_t i = 0; i < OPERATIONS_PER_TEST; ++i) { + std::string key = test_key_prefix + std::to_string(current_count + i); + std::string value = "value_" + std::to_string(current_count + i); + int expires = static_cast(std::time(nullptr)) + 3600; + + DataBucketKey e{ + .key = key, + .value = value, + .expires = std::to_string(expires), + .account_id = 0, + .character_id = 0, + .npc_id = 0, + .bot_id = 0 + }; + + // randomly set account_id, character_id, npc_id, or bot_id + switch (i % 4) { + case 0: + e.account_id = id_dist(rng); + break; + case 1: + e.character_id = id_dist(rng); + break; + case 2: + e.npc_id = id_dist(rng); + break; + case 3: + e.bot_id = id_dist(rng); + break; + case 4: + int entity_choice = id_dist(rng); + e.zone_id = entity_choice; + e.instance_id = entity_choice; + break; + } + + DataBucket::SetData(e); + + inserted_keys.emplace_back(e); + } + auto insert_end = std::chrono::high_resolution_clock::now(); + std::chrono::duration insert_time = insert_end - insert_start; + std::cout << "✅ Completed " << Strings::Commify(OPERATIONS_PER_TEST) << " inserts in " << insert_time.count() + << " seconds. (Individual Insert Performance)\n"; + + // ✏️ **Measure Update Performance Using DataBucket** + auto update_start = std::chrono::high_resolution_clock::now(); + for (auto &key: inserted_keys) { + // 🔍 Retrieve existing bucket using scoped `GetData` + auto e = DataBucket::GetData(key); + if (e.id > 0) { + // create a new key object with the updated values + DataBucketKey bucket_entry_key{ + .key = e.key_, + .value = "some_new_value", + .expires = std::to_string(e.expires), + .account_id = e.account_id, + .character_id = e.character_id, + .npc_id = e.npc_id, + .bot_id = e.bot_id, + .zone_id = e.zone_id, + .instance_id = e.instance_id + }; + + // 🔄 Update using DataBucket class + DataBucket::SetData(bucket_entry_key); + } + } + auto update_end = std::chrono::high_resolution_clock::now(); + std::chrono::duration update_time = update_end - update_start; + std::cout << "✅ Completed " << Strings::Commify(OPERATIONS_PER_TEST) << " updates in " << update_time.count() + << " seconds. (Scoped Update Performance)\n"; + + + // 🔍 **Measure Cached Read Performance** + auto read_cached_start = std::chrono::high_resolution_clock::now(); + for (size_t i = 0; i < OPERATIONS_PER_TEST; ++i) { + std::string key = test_key_prefix + std::to_string(current_count + i); + DataBucketKey k{ + .key = key, + .account_id = 0, + .character_id = 0, + .npc_id = 0, + .bot_id = 0, + .zone_id = 0, + .instance_id = 0 + }; + + // randomly set account_id, character_id, npc_id, or bot_id + switch (i % 4) { + case 0: + k.account_id = id_dist(rng); + break; + case 1: + k.character_id = id_dist(rng); + break; + case 2: + k.npc_id = id_dist(rng); + break; + case 3: + k.bot_id = id_dist(rng); + break; + case 4: + int entity_choice = id_dist(rng); + k.zone_id = entity_choice; + k.instance_id = entity_choice; + } + + DataBucket::GetData(key); + } + auto read_cached_end = std::chrono::high_resolution_clock::now(); + std::chrono::duration read_cached_time = read_cached_end - read_cached_start; + std::cout << "✅ Completed " << Strings::Commify(OPERATIONS_PER_TEST) << " cached reads in " + << read_cached_time.count() << " seconds. (DataBucket::GetData)\n"; + + // 🔍 **Measure Client-Scoped Cache Miss Performance (Skips DB via CanCache)** + auto read_client_cache_miss_start = std::chrono::high_resolution_clock::now(); + for (size_t i = 0; i < OPERATIONS_PER_TEST; ++i) { + // generate key that doesn't exist + std::string key = "nonexistent_key_" + std::to_string(i); + + DataBucketKey k{ + .key = key, + .character_id = 999999999, // use scoped value + }; + + DataBucket::GetData(k); + } + auto read_client_cache_miss_end = std::chrono::high_resolution_clock::now(); + std::chrono::duration read_client_cache_miss_time = read_client_cache_miss_end - read_client_cache_miss_start; + std::cout << "✅ Completed " << Strings::Commify(OPERATIONS_PER_TEST) + << " scoped cache-miss reads (no DB) in " + << read_client_cache_miss_time.count() << " seconds. (Client Scoped, Cache Miss, No DB)\n"; + + // 🔍 **Measure Non-Cached Read Performance (Direct Query)** + auto read_uncached_start = std::chrono::high_resolution_clock::now(); + for (size_t i = 0; i < OPERATIONS_PER_TEST; ++i) { + std::string key = test_key_prefix + std::to_string(current_count + i); + DataBucketsRepository::GetWhere(database, "`key` = '" + key + "'"); + } + auto read_uncached_end = std::chrono::high_resolution_clock::now(); + std::chrono::duration read_uncached_time = read_uncached_end - read_uncached_start; + std::cout << "✅ Completed " << Strings::Commify(OPERATIONS_PER_TEST) << " non-cached reads in " + << read_uncached_time.count() << " seconds. (DataBucketsRepository::GetWhere)\n"; + + // 🗑️ **Measure Delete Performance** + auto delete_start = std::chrono::high_resolution_clock::now(); + for (size_t i = 0; i < OPERATIONS_PER_TEST; ++i) { + std::string key = test_key_prefix + std::to_string(current_count + i); + + DataBucketKey k{ + .key = key, + .account_id = 0, + .character_id = 0, + .npc_id = 0, + .bot_id = 0, + .zone_id = 0, + .instance_id = 0 + }; + + // randomly set account_id, character_id, npc_id, or bot_id + switch (i % 4) { + case 0: + k.account_id = id_dist(rng); + break; + case 1: + k.character_id = id_dist(rng); + break; + case 2: + k.npc_id = id_dist(rng); + break; + case 3: + k.bot_id = id_dist(rng); + break; + case 4: + int entity_choice = id_dist(rng); + k.zone_id = entity_choice; + k.instance_id = entity_choice; + } + + DataBucket::DeleteData(k); + } + auto delete_end = std::chrono::high_resolution_clock::now(); + std::chrono::duration delete_time = delete_end - delete_start; + std::cout << "✅ Completed " << Strings::Commify(OPERATIONS_PER_TEST) << " deletes in " << delete_time.count() + << " seconds.\n"; +} + +void ZoneCLI::BenchmarkDatabuckets(int argc, char **argv, argh::parser &cmd, std::string &description) +{ + description = "Benchmark individual reads/writes/deletes in data_buckets at different table sizes."; + + if (cmd[{"-h", "--help"}]) { + std::cout << "Usage: BenchmarkDatabuckets\n"; + return; + } + + if (std::getenv("DEBUG")) { + EQEmuLogSys::Instance()->SetDatabase(&database)->LoadLogDatabaseSettings(); + } + + auto start_time = std::chrono::high_resolution_clock::now(); + + std::vector benchmark_sizes = {10000, 100000, 1000000}; + + for (auto size: benchmark_sizes) { + RunBenchmarkCycle(size); + } + + // 🚀 **Total Benchmark Time** + auto end_time = std::chrono::high_resolution_clock::now(); + std::chrono::duration total_elapsed = end_time - start_time; + std::cout << "\n🚀 Total Benchmark Time: " << total_elapsed.count() << " seconds\n"; +} diff --git a/zone/cli/tests/_test_util.cpp b/zone/cli/tests/_test_util.cpp new file mode 100644 index 000000000..0bd118166 --- /dev/null +++ b/zone/cli/tests/_test_util.cpp @@ -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) { + EQEmuLogSys::Instance()->SilenceConsoleLogging(); + + EQEmuLogSys::Instance()->log_settings[Logs::ZoneState].log_to_console = std::getenv("DEBUG") ? 3 : 0; + EQEmuLogSys::Instance()->log_settings[Logs::Info].log_to_console = std::getenv("DEBUG") ? 3 : 0; + EQEmuLogSys::Instance()->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(); + + EQEmuLogSys::Instance()->EnableConsoleLogging(); +} diff --git a/zone/cli/tests/databuckets.cpp b/zone/cli/tests/databuckets.cpp new file mode 100644 index 000000000..28a3604d4 --- /dev/null +++ b/zone/cli/tests/databuckets.cpp @@ -0,0 +1,352 @@ +#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; + + EQEmuLogSys::Instance()->SilenceConsoleLogging(); + + // boot shell zone for testing + Zone::Bootup(ZoneID("qrg"), 0, false); + zone->StopShutdownTimer(); + + entity_list.Process(); + entity_list.MobProcess(); + + EQEmuLogSys::Instance()->EnableConsoleLogging(); + + Client* client = new Client(); + client->SetCharacterId(1); // Set a dummy character ID for testing + + EQEmuLogSys::Instance()->log_settings[Logs::MySQLQuery].is_category_enabled = std::getenv("DEBUG") ? 1 : 0; + EQEmuLogSys::Instance()->log_settings[Logs::MySQLQuery].log_to_console = std::getenv("DEBUG") ? 3 : 0; + + // 🧹 Delete all test keys before running tests + std::vector test_keys_to_clear = { + "basic_key", "expiring_key", "cache_key", "json_key", "non_existent_key", "simple_key", + "nested", "nested.test1", "nested.test2", "nested.test1.a", "nested.test2.a", + "exp_test", "cache_test", "full_json", "full_json.key2", "complex", "complex.nested.obj1", + "complex.nested.obj2", "plain_string", "json_array", "nested_partial", + "nested_override", "empty_json", "json_string", "deep_nested", + "nested_expire", "scoped_miss_test", "scoped_nested_miss.key", + "cache_miss_overwrite", "missed_nested_set", "account_client_test", "ac_nested.test", + "scoped_db_only_key" + }; + + DataBucketsRepository::DeleteWhere( + database, + fmt::format("`key` IN ('{}')", Strings::Join(test_keys_to_clear, "','")) + ); + DataBucket::ClearCache(); + + std::cout << "===========================================\n"; + std::cout << "⚙\uFE0F> Running DataBuckets Tests...\n"; + std::cout << "===========================================\n\n"; + + // 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); + + // ================================ + // 🧪 Scoped Cache-Miss Behavior Tests + // ================================ + + // Ensure a scoped key (character ID) that doesn't exist is not fetched from DB if not in cache + client->DeleteBucket("scoped_miss_test"); // Ensure not in DB + DataBucket::ClearCache(); // Clear all caches + std::string scoped_miss_value = client->GetBucket("scoped_miss_test"); + RunTest("Scoped Missing Key Returns Empty (Skips DB)", "", scoped_miss_value); + + // Ensure nested scoped key that isn't in cache returns empty immediately + client->DeleteBucket("scoped_nested_miss.key"); + DataBucket::ClearCache(); // Clear cache again + std::string scoped_nested_miss = client->GetBucket("scoped_nested_miss.key"); + RunTest("Nested Scoped Key Miss Returns Empty (Skips DB)", "", scoped_nested_miss); + + // Write to a key that was previously missed (0-id cached miss) + client->DeleteBucket("cache_miss_overwrite"); + DataBucket::ClearCache(); // Ensure clean slate + std::string missed_value = client->GetBucket("cache_miss_overwrite"); + RunTest("Initial Cache Miss Returns Empty", "", missed_value); + client->SetBucket("cache_miss_overwrite", "new_value"); + std::string new_val = client->GetBucket("cache_miss_overwrite"); + RunTest("Overwrite After Cache Miss Works", "new_value", new_val); + + // Write a nested key that previously missed + client->DeleteBucket("missed_nested_set.test"); + DataBucket::ClearCache(); + std::string initial = client->GetBucket("missed_nested_set.test"); + RunTest("Missed Nested Key Returns Empty", "", initial); + client->SetBucket("missed_nested_set.test", "set_value"); + std::string after_write = client->GetBucket("missed_nested_set.test"); + RunTest("Nested Set After Miss Works", "set_value", after_write); + + // ================================ + // 🧪 Scoped Cache Preload Tests (Account + Client) + // ================================ + + // Clear everything for a clean test + // Insert directly into the DB without touching cache + const std::string scoped_key = "scoped_db_only_key"; + client->DeleteBucket(scoped_key); + DataBucket::ClearCache(); + + // ✅ Scoped insert + DataBucketsRepository::InsertOne( + database, { + .key_ = scoped_key, + .value = "cached_value", + .character_id = client->CharacterID() + } + ); + + // Cold cache test — should return "" + // std::string cold_value = client->GetBucket(scoped_key); + // RunTest("Cold Cache Scoped Key Returns Empty (Due to Skip DB)", "", cold_value); + // + // // ✅ Reload cache + // client->LoadDataBucketsCache(); + // + // // Cache should now return the value + // std::string hot_value = client->GetBucket(scoped_key); + // RunTest("Post-BulkLoad Scoped Key Returns Value", "cached_value", hot_value); + // + // // Also test nested key after preload + // client->DeleteBucket("ac_nested.test"); + // client->SetBucket("ac_nested.test", "nested_val"); + // + // // Clear cache, then preload + // DataBucket::ClearCache(); + // client->LoadDataBucketsCache(); + // + // std::string nested_value = client->GetBucket("ac_nested.test"); + // RunTest("Post-BulkLoad Nested Scoped Key Returns Value", "nested_val", nested_value); + // + // // Remove and check that cache misses properly again + // client->DeleteBucket("ac_nested.test"); + // DataBucket::ClearCache(); + // std::string post_delete_check = client->GetBucket("ac_nested.test"); + // RunTest("Post-Delete Nested Scoped Key Returns Empty", "", post_delete_check); + + + std::cout << "\n===========================================\n"; + std::cout << "✅ All DataBucket Tests Completed!\n"; + std::cout << "===========================================\n"; +} diff --git a/zone/cli/tests/npc_handins.cpp b/zone/cli/tests/npc_handins.cpp new file mode 100644 index 000000000..c9eed484d --- /dev/null +++ b/zone/cli/tests/npc_handins.cpp @@ -0,0 +1,536 @@ +#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" +#include "../../common/json/json.hpp" + +extern Zone *zone; +using json = nlohmann::json; + +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 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 &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; + } + + EQEmuLogSys::Instance()->SilenceConsoleLogging(); + + Zone::Bootup(ZoneID("qrg"), 0, false); + zone->StopShutdownTimer(); + + entity_list.Process(); + entity_list.MobProcess(); + + 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); + if (npc_type) { + auto npc = new NPC( + npc_type, + nullptr, + glm::vec4(0, 0, 0, 0), + GravityBehavior::Water + ); + + entity_list.AddNPC(npc); + + std::vector test_cases = { + TestCase{ + .description = "Test basic cloth-cap hand-in", + .hand_in = { + .items = { + HandinEntry{.item_id = "1001", .count = 1}, + }, + }, + .required = { + .items = { + HandinEntry{.item_id = "1001", .count = 1}, + }, + }, + .returned = {}, + .handin_check_result = true, + }, + TestCase{ + .description = "Test basic cloth-cap hand-in failure", + .hand_in = { + .items = { + HandinEntry{.item_id = "9997", .count = 1}, + }, + }, + .required = { + .items = { + HandinEntry{.item_id = "1001", .count = 1}, + }, + }, + .returned = { + .items = { + HandinEntry{.item_id = "9997", .count = 1}, + }, + }, + .handin_check_result = false, + }, + TestCase{ + .description = "Test basic cloth-cap hand-in failure from handing in too many", + .hand_in = { + .items = { + HandinEntry{.item_id = "9997", .count = 1}, + HandinEntry{.item_id = "9997", .count = 1}, + }, + }, + .required = { + .items = { + HandinEntry{.item_id = "1001", .count = 1}, + }, + }, + .returned = { + .items = { + HandinEntry{.item_id = "9997", .count = 1}, + HandinEntry{.item_id = "9997", .count = 1}, + }, + }, + .handin_check_result = false, + }, + TestCase{ + .description = "Test handing in money", + .hand_in = { + .items = {}, + .money = {.platinum = 1}, + }, + .required = { + .items = {}, + .money = {.platinum = 1}, + }, + .returned = {}, + .handin_check_result = true, + }, + TestCase{ + .description = "Test handing in money, but not enough", + .hand_in = { + .items = {}, + .money = {.platinum = 1}, + }, + .required = { + .items = {}, + .money = {.platinum = 100}, + }, + .returned = { + .items = {}, + .money = {.platinum = 1}, + }, + .handin_check_result = false, + }, + TestCase{ + .description = "Test handing in money, but not enough of any type", + .hand_in = { + .items = {}, + .money = {.platinum = 1, .gold = 1, .silver = 1, .copper = 1}, + }, + .required = { + .items = {}, + .money = {.platinum = 100, .gold = 100, .silver = 100, .copper = 100}, + }, + .returned = { + .items = {}, + .money = {.platinum = 1, .gold = 1, .silver = 1, .copper = 1}, + }, + .handin_check_result = false, + }, + TestCase{ + .description = "Test handing in money of all types", + .hand_in = { + .items = {}, + .money = {.platinum = 1, .gold = 1, .silver = 1, .copper = 1}, + }, + .required = { + .items = {}, + .money = {.platinum = 1, .gold = 1, .silver = 1, .copper = 1}, + }, + .returned = {}, + .handin_check_result = true, + }, + TestCase{ + .description = "Test handing in platinum with items with success", + .hand_in = { + .items = { + HandinEntry{.item_id = "1001", .count = 1}, + }, + .money = {.platinum = 1}, + }, + .required = { + .items = { + HandinEntry{.item_id = "1001", .count = 1}, + }, + .money = {.platinum = 1}, + }, + .returned = {}, + .handin_check_result = true, + }, + TestCase{ + .description = "Test handing in platinum with items with failure", + .hand_in = { + .items = { + HandinEntry{.item_id = "1001", .count = 1}, + }, + .money = {.platinum = 1}, + }, + .required = { + .items = { + HandinEntry{.item_id = "1001", .count = 1}, + }, + .money = {.platinum = 100}, + }, + .returned = { + .items = { + HandinEntry{ + .item_id = "1001", .count = 0, + }, + }, + .money = {.platinum = 1}, + }, + .handin_check_result = false, + }, + TestCase{ + .description = "Test returning money and items", + .hand_in = { + .items = { + HandinEntry{.item_id = "1007", .count = 1}, + }, + .money = { + .platinum = 1, + .gold = 666, + .silver = 234, + .copper = 444, + }, + }, + .required = { + .items = { + HandinEntry{.item_id = "1001", .count = 1}, + }, + .money = {.platinum = 100}, + }, + .returned = { + .items = { + HandinEntry{.item_id = "1007", .count = 1}, + }, + .money = { + .platinum = 1, + .gold = 666, + .silver = 234, + .copper = 444, + }, + }, + .handin_check_result = false, + }, + TestCase{ + .description = "Test returning money", + .hand_in = { + .items = {}, + .money = { + .platinum = 1, + .gold = 666, + .silver = 234, + .copper = 444, + }, + }, + .required = { + .items = {}, + .money = {.platinum = 100}, + }, + .returned = { + .items = { + }, + .money = { + .platinum = 1, + .gold = 666, + .silver = 234, + .copper = 444, + }, + }, + .handin_check_result = false, + }, + TestCase{ + .description = "Test handing in many items of the same required item", + .hand_in = { + .items = { + HandinEntry{.item_id = "1007", .count = 1}, + HandinEntry{.item_id = "1007", .count = 1}, + HandinEntry{.item_id = "1007", .count = 1}, + HandinEntry{.item_id = "1007", .count = 1}, + }, + .money = {}, + }, + .required = { + .items = { + HandinEntry{.item_id = "1007", .count = 1}, + }, + .money = {}, + }, + .returned = { + .items = { + HandinEntry{.item_id = "1007", .count = 1}, + HandinEntry{.item_id = "1007", .count = 1}, + HandinEntry{.item_id = "1007", .count = 1}, + }, + .money = {}, + }, + .handin_check_result = true, + }, + TestCase{ + .description = "Test handing in item of a stack", + .hand_in = { + .items = { + HandinEntry{.item_id = "13005", .count = 20}, + }, + .money = {}, + }, + .required = { + .items = { + HandinEntry{.item_id = "13005", .count = 20}, + }, + .money = {}, + }, + .returned = { + .items = {}, + .money = {}, + }, + .handin_check_result = true, + }, + TestCase{ + .description = "Test handing in item of a stack but not enough", + .hand_in = { + .items = { + HandinEntry{.item_id = "13005", .count = 10}, + }, + .money = {}, + }, + .required = { + .items = { + HandinEntry{.item_id = "13005", .count = 20}, + }, + .money = {}, + }, + .returned = { + .items = { + HandinEntry{.item_id = "13005", .count = 10}, + }, + .money = {}, + }, + .handin_check_result = false, + }, + TestCase{ + .description = "Test handing in 4 non-stacking helmets when 4 are required", + .hand_in = { + .items = { + HandinEntry{.item_id = "29062", .count = 1}, + HandinEntry{.item_id = "29062", .count = 1}, + HandinEntry{.item_id = "29062", .count = 1}, + HandinEntry{.item_id = "29062", .count = 1}, + }, + .money = {}, + }, + .required = { + .items = { + HandinEntry{.item_id = "29062", .count = 4}, + }, + .money = {}, + }, + .returned = { + .items = { + }, + .money = {}, + }, + .handin_check_result = true, + }, + TestCase{ + .description = "Test handing in Soulfire that has 5 charges and have it count as 1 item", + .hand_in = { + .items = { + HandinEntry{.item_id = "5504", .count = 1}, + }, + .money = {}, + }, + .required = { + .items = { + HandinEntry{.item_id = "5504", .count = 1}, + }, + .money = {}, + }, + .returned = { + .items = { + }, + .money = {}, + }, + .handin_check_result = true, + }, + }; + + std::map hand_ins; + std::map required; + std::vector items; + + EQEmuLogSys::Instance()->EnableConsoleLogging(); + + // turn this on to see debugging output + EQEmuLogSys::Instance()->log_settings[Logs::NpcHandin].log_to_console = std::getenv("DEBUG") ? 3 : 0; + + for (auto &test: test_cases) { + hand_ins.clear(); + required.clear(); + items.clear(); + + 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()) { + inst->SetCharges(hand_in.count); + } + + if (inst->GetItem()->MaxCharges > 0) { + inst->SetCharges(inst->GetItem()->MaxCharges); + } + + hand_ins[hand_in.item_id] = inst->GetCharges(); + items.push_back(inst); + } + + // money + if (test.hand_in.money.platinum > 0) { + hand_ins["platinum"] = test.hand_in.money.platinum; + } + if (test.hand_in.money.gold > 0) { + hand_ins["gold"] = test.hand_in.money.gold; + } + if (test.hand_in.money.silver > 0) { + hand_ins["silver"] = test.hand_in.money.silver; + } + if (test.hand_in.money.copper > 0) { + hand_ins["copper"] = test.hand_in.money.copper; + } + + for (auto &req: test.required.items) { + required[req.item_id] = req.count; + } + + // money + if (test.required.money.platinum > 0) { + required["platinum"] = test.required.money.platinum; + } + if (test.required.money.gold > 0) { + required["gold"] = test.required.money.gold; + } + if (test.required.money.silver > 0) { + required["silver"] = test.required.money.silver; + } + if (test.required.money.copper > 0) { + required["copper"] = test.required.money.copper; + } + + auto result = npc->CheckHandin(c, hand_ins, required, items); + + RunTest(test.description, test.handin_check_result, result); + + auto returned = npc->ReturnHandinItems(c); + + std::map 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 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 (EQEmuLogSys::Instance()->log_settings[Logs::NpcHandin].log_to_console > 0) { + std::cout << std::endl; + } + } + } + + std::cout << "\n===========================================\n"; + std::cout << "✅ All NPC Hand-in Tests Completed!\n"; + std::cout << "===========================================\n"; +} diff --git a/zone/cli/tests/npc_handins_multiquest.cpp b/zone/cli/tests/npc_handins_multiquest.cpp new file mode 100644 index 000000000..1c560cdc9 --- /dev/null +++ b/zone/cli/tests/npc_handins_multiquest.cpp @@ -0,0 +1,174 @@ +#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::TestNpcHandinsMultiQuest(int argc, char **argv, argh::parser &cmd, std::string &description) +{ + if (cmd[{"-h", "--help"}]) { + return; + } + + uint32 break_length = 50; + int failed_count = 0; + + EQEmuLogSys::Instance()->SilenceConsoleLogging(); + + Zone::Bootup(ZoneID("qrg"), 0, false); + zone->StopShutdownTimer(); + + entity_list.Process(); + entity_list.MobProcess(); + + 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); + if (npc_type) { + auto npc = new NPC( + npc_type, + nullptr, + glm::vec4(0, 0, 0, 0), + GravityBehavior::Water + ); + + entity_list.AddNPC(npc); + npc->MultiQuestEnable(); + + 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 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 test_cases = { + TestCase{ + .description = "Journeyman's Boots", + .hand_in = { + .items = { + HandinEntry{.item_id = "12268", .count = 1}, + HandinEntry{.item_id = "7100", .count = 1}, + }, + .money = {.platinum = 325}, + }, + .required = { + .items = { + HandinEntry{.item_id = "12268", .count = 1}, + HandinEntry{.item_id = "7100", .count = 1}, + }, + .money = {.platinum = 325}, + }, + .returned = {}, + .handin_check_result = true, + }, + }; + + std::map hand_ins; + std::map required; + std::vector items; + + EQEmuLogSys::Instance()->EnableConsoleLogging(); + + // turn this on to see debugging output + EQEmuLogSys::Instance()->log_settings[Logs::NpcHandin].log_to_console = std::getenv("DEBUG") ? 3 : 0; + + for (auto &test: test_cases) { + required.clear(); + + for (auto &hand_in: test.hand_in.items) { + hand_ins.clear(); + items.clear(); + + auto item_id = Strings::ToInt(hand_in.item_id); + EQ::ItemInstance *inst = database.CreateItem(item_id); + if (inst->IsStackable()) { + inst->SetCharges(hand_in.count); + } + + if (inst->GetItem()->MaxCharges > 0) { + inst->SetCharges(inst->GetItem()->MaxCharges); + } + + hand_ins[hand_in.item_id] = inst->GetCharges(); + items.push_back(inst); + + npc->CheckHandin(c, hand_ins, required, items); + npc->ResetHandin(); + } + + // money + if (test.hand_in.money.platinum > 0) { + hand_ins["platinum"] = test.hand_in.money.platinum; + } + if (test.hand_in.money.gold > 0) { + hand_ins["gold"] = test.hand_in.money.gold; + } + if (test.hand_in.money.silver > 0) { + hand_ins["silver"] = test.hand_in.money.silver; + } + if (test.hand_in.money.copper > 0) { + hand_ins["copper"] = test.hand_in.money.copper; + } + + for (auto &req: test.required.items) { + required[req.item_id] = req.count; + } + + // money + if (test.required.money.platinum > 0) { + required["platinum"] = test.required.money.platinum; + } + if (test.required.money.gold > 0) { + required["gold"] = test.required.money.gold; + } + if (test.required.money.silver > 0) { + required["silver"] = test.required.money.silver; + } + if (test.required.money.copper > 0) { + required["copper"] = test.required.money.copper; + } + + auto result = npc->CheckHandin(c, hand_ins, required, items); + + RunTest(test.description, test.handin_check_result, result); + + auto returned = npc->ReturnHandinItems(c); + + npc->ResetHandin(); + + if (EQEmuLogSys::Instance()->log_settings[Logs::NpcHandin].log_to_console > 0) { + std::cout << std::endl; + } + } + } + + std::cout << "\n===========================================\n"; + std::cout << "✅ All NPC Hand-in Tests Completed (Multi-Quest)!\n"; + std::cout << "===========================================\n"; +} diff --git a/zone/cli/tests/zone_state.cpp b/zone/cli/tests/zone_state.cpp new file mode 100644 index 000000000..b527f5a4f --- /dev/null +++ b/zone/cli/tests/zone_state.cpp @@ -0,0 +1,1134 @@ +extern Zone *zone; + +#include +#include +#include "../../common/repositories/npc_types_repository.h" +#include "../../corpse.h" +#include "../../../common/repositories/respawn_times_repository.h" + +inline void ClearState() +{ + ZoneStateSpawnsRepository::DeleteWhere(database, "zone_id = 32 and instance_id = 0"); +} + +inline std::vector GetStateSpawns() +{ + return ZoneStateSpawnsRepository::GetWhere(database, "zone_id = 32 and instance_id = 0"); +} + +inline void PrintEntityCounts() +{ + std::cout << " NPC count: " << entity_list.GetNPCList().size() << std::endl; + std::cout << " Corpse count: " << entity_list.GetCorpseList().size() << std::endl; +} + +inline void PrintZoneNpcs() +{ + int npc_count = 0; + for (auto &npc: entity_list.GetNPCList()) { + std::cout << npc.second->GetNPCTypeID() << " " << npc.second->GetCleanName() << std::endl; + npc_count++; + } + + std::cout << "Total spawned NPCs: " << npc_count << std::endl; +} + +inline void SetupStateZone() +{ + SetupZone("soldungb"); + zone->Process(); + // depop the zone controller + auto controller = entity_list.GetNPCByNPCTypeID(ZONE_CONTROLLER_NPC_ID); + if (controller != nullptr) { + controller->Depop(); + } + + entity_list.MobProcess(); // process the depop +} + +inline int GetStateSpawnSpawn2Count() +{ + int count = 0; + for (auto &e: GetStateSpawns()) { + if (e.spawn2_id > 0) { + count++; + } + } + return count; +} + +inline uint32_t SeedLootTable() +{ + const std::string table_name = "zone_state_test"; + + auto entries = LoottableRepository::GetWhere(database, fmt::format("name = '{}'", table_name)); + if (!entries.empty()) { + zone->LoadLootTable(entries[0].id); + return entries[0].id; + } + + // seed the loot table + auto loot_table = LoottableRepository::NewEntity(); + loot_table.name = table_name; + auto inserted = LoottableRepository::InsertOne(database, loot_table); + + auto loot_drop = LootdropRepository::NewEntity(); + loot_drop.name = table_name; + auto inserted_loot_drop = LootdropRepository::InsertOne(database, loot_drop); + + auto loot_table_entries = LoottableEntriesRepository::NewEntity(); + loot_table_entries.lootdrop_id = inserted_loot_drop.id; + loot_table_entries.loottable_id = inserted.id; + LoottableEntriesRepository::InsertOne(database, loot_table_entries); + + auto loot_drop_entries = LootdropEntriesRepository::NewEntity(); + loot_drop_entries.lootdrop_id = inserted_loot_drop.id; + loot_drop_entries.item_id = 11621; // cloak of flames + loot_drop_entries.chance = 100; + LootdropEntriesRepository::InsertOne(database, loot_drop_entries); + + zone->LoadLootTable(inserted.id); + + return inserted.id; +} + +inline std::map GetVariablesDeserialized(const std::string &entity_variables) +{ + std::map deserialized_map; + + if (entity_variables.empty()) { + return deserialized_map; + } + + if (!Strings::IsValidJson(entity_variables)) { + LogZoneState("Invalid JSON data for entity variables"); + return deserialized_map; + } + + try { + std::stringstream ss; + { + ss << entity_variables; + cereal::JSONInputArchive ar(ss); + ar(deserialized_map); + } + } catch (const std::exception &e) { + LogZoneState("Failed to load entity variables [{}]", e.what()); + } + + return deserialized_map; +} + +// MatchState compares the NPC to the state +// it should print what doesn't match when it returns false +inline bool MatchState(NPC *n, const ZoneStateSpawnsRepository::ZoneStateSpawns &state) +{ + if (n->GetNPCTypeID() != state.npc_id) { + std::cout << "NPC ID mismatch: " << n->GetNPCTypeID() << " != " << state.npc_id << std::endl; + return false; + } + + if (n->GetX() != state.x) { + std::cout << "X mismatch: " << n->GetX() << " != " << state.x << std::endl; + return false; + } + + if (n->GetY() != state.y) { + std::cout << "Y mismatch: " << n->GetY() << " != " << state.y << std::endl; + return false; + } + + if (n->GetZ() != state.z) { + std::cout << "Z mismatch: " << n->GetZ() << " != " << state.z << std::endl; + return false; + } + + if (n->GetHeading() != state.heading) { + std::cout << "Heading mismatch: " << n->GetHeading() << " != " << state.heading << std::endl; + return false; + } + + if (n->GetHP() != state.hp) { + std::cout << "HP mismatch: " << n->GetHP() << " != " << state.hp << std::endl; + return false; + } + + if (n->GetMana() != state.mana) { + std::cout << "Mana mismatch: " << n->GetMana() << " != " << state.mana << std::endl; + return false; + } + + if (n->GetEndurance() != state.endurance) { + std::cout << "Endurance mismatch: " << n->GetEndurance() << " != " << state.endurance << std::endl; + return false; + } + + auto entity_variables = GetVariablesDeserialized(state.entity_variables); + + for (const auto &[key, value]: entity_variables) { + if (n->GetEntityVariable(key) != value) { + std::cout << "Variable mismatch: " << key << " " << n->GetEntityVariable(key) << " != " << value + << std::endl; + return false; + } + } + + return true; +} + +// MatchFreshlySpawnedState compares the NPC to the state +// it should print what doesn't match when it returns false +inline bool MatchFreshlySpawnedState(NPC *n, const ZoneStateSpawnsRepository::ZoneStateSpawns &state) +{ + auto npc_type = content_db.LoadNPCTypesData(n->GetNPCTypeID()); + if (!npc_type) { + RunTest("NPC type does not exist", false, true); + } + + if (n->GetX() != state.x) { + std::cout << "X mismatch: " << n->GetX() << " != " << state.x << std::endl; + return false; + } + + if (n->GetY() != state.y) { + std::cout << "Y mismatch: " << n->GetY() << " != " << state.y << std::endl; + return false; + } + +// if (n->GetZ() != state.z) { +// std::cout << "Z mismatch: " << n->GetZ() << " != " << state.z << std::endl; +// return false; +// } + + if (n->GetHeading() != state.heading) { + std::cout << "Heading mismatch: " << n->GetHeading() << " != " << state.heading << std::endl; + return false; + } + + if (n->GetHP() != npc_type->max_hp) { + std::cout << "HP mismatch: " << n->GetHP() << " != " << npc_type->max_hp << std::endl; + return false; + } + + if (n->GetLoottableID() != npc_type->loottable_id) { + std::cout << "Loot table mismatch: " << n->GetLoottableID() << " != " << npc_type->loottable_id << std::endl; + return false; + } + + // not loaded from database +// if (n->GetMana() != npc_type->Mana) { +// std::cout << "Mana mismatch: " << n->GetMana() << " != " << npc_type->Mana << std::endl; +// return false; +// } + + // endurance not saved + + auto entity_variables = GetVariablesDeserialized(state.entity_variables); + + for (const auto &[key, value]: entity_variables) { + if (n->GetEntityVariable(key) != value) { + std::cout << "Variable mismatch: " << key << " " << n->GetEntityVariable(key) << " != " << value + << std::endl; + return false; + } + } + + return true; +} + +inline bool MatchCorpseState(Corpse *c, const ZoneStateSpawnsRepository::ZoneStateSpawns &state) +{ + if (c->GetNPCTypeID() != state.npc_id) { + std::cout << "Corpse NPC ID mismatch: " << c->GetNPCTypeID() << " != " << state.npc_id << std::endl; + return false; + } + + if (c->GetX() != state.x) { + std::cout << "Corpse X mismatch: " << c->GetX() << " != " << state.x << std::endl; + return false; + } + + if (c->GetY() != state.y) { + std::cout << "Corpse Y mismatch: " << c->GetY() << " != " << state.y << std::endl; + return false; + } + +// if (c->GetZ() != state.z) { +// std::cout << "Corpse Z mismatch: " << c->GetZ() << " != " << state.z << std::endl; +// return false; +// } + + if (c->GetHeading() != state.heading) { + std::cout << "Corpse Heading mismatch: " << c->GetHeading() << " != " << state.heading << std::endl; + return false; + } + + if (c->GetDecayTime() != state.decay_in_seconds * 1000) { + std::cout << "Corpse Decay Time mismatch: " << c->GetDecayTime() << " != " << state.decay_in_seconds * 1000 + << std::endl; + return false; + } + + auto entity_variables = GetVariablesDeserialized(state.entity_variables); + + for (const auto &[key, value]: entity_variables) { + if (c->GetEntityVariable(key) != value) { + std::cout << "Corpse Variable mismatch: " << key << " " << c->GetEntityVariable(key) << " != " << value + << std::endl; + return false; + } + } + + return true; +} + +inline void TestSpawns() +{ + zone->Shutdown(); + ClearState(); + SetupStateZone(); + zone->Repop(true); + entity_list.MobProcess(); + +// PrintZoneNpcs(); + + RunTest("Spawns > Ensure no state spawns exist before shutdown", 0, (int) GetStateSpawns().size()); + + zone->Shutdown(); + + auto entries = GetStateSpawns().size(); + RunTest(fmt::format("Spawns > State exists after shutdown, entries ({})", entries), true, entries > 0); + + SetupStateZone(); + + entries = GetStateSpawns().size(); + RunTest(fmt::format("Spawns > State exists after bootup, entries ({})", entries), true, entries > 0); + + zone->Shutdown(); + SetupStateZone(); + + entries = GetStateSpawns().size(); + RunTest( + fmt::format("Spawns > State is the same after shutdown/bootup (2nd time), entries ({})", entries), + true, + entries > 0 + ); + + // need to compare the state spawns to the actual spawns + bool all_exist = true; + + for (auto &state_spawn: GetStateSpawns()) { + auto npc = entity_list.GetNPCByNPCTypeID(state_spawn.npc_id); + if (!npc) { + all_exist = false; + break; + } + } + + RunTest("Spawns > All state spawns (Spawn2) exist in entity list", true, all_exist); + RunTest( + fmt::format("Spawns > All state spawns (Spawn2) exist in entity list, count [{}]", GetStateSpawns().size()), + true, + GetStateSpawns().size() == entity_list.GetNPCList().size() + ); + RunTest( + fmt::format( + "Spawns > Same count of state spawns and entity list | state spawns [{}] entity list [{}]", + GetStateSpawns().size(), + entity_list.GetNPCList().size()), + true, + GetStateSpawns().size() == entity_list.GetNPCList().size() + ); + + // kill all NPC's + std::vector npcs_to_kill; + + // Collect NPCs first + for (const auto &e: entity_list.GetNPCList()) { + if (e.second) { + npcs_to_kill.push_back(e.second); + } + } + + // Now safely process them + for (auto *npc: npcs_to_kill) { + npc->SetQueuedToCorpse(); + npc->Death(npc, npc->GetHP() + 1, SPELL_UNKNOWN, EQ::skills::SkillHandtoHand); + } + + bool condition = (int) entity_list.GetNPCList().size() == 0 && (int) entity_list.GetCorpseList().size() == entries; + RunTest( + fmt::format("Spawns > All NPC's killed (0 NPCs) ([{}] Corpses)", entries), + true, + condition + ); + + std::vector spawn2_ids = {}; + + for (auto &e: GetStateSpawns()) { + if (e.spawn2_id > 0) { + spawn2_ids.push_back(e.spawn2_id); + } + } + + auto times = RespawnTimesRepository::GetWhere( + database, + fmt::format("id IN ({})", Strings::Join(spawn2_ids, ",")) + ); + + for (auto &e: times) { + e.duration = 1; + e.start = std::time(nullptr) + 1; + } + RespawnTimesRepository::ReplaceMany(database, times); + + zone->Shutdown(); + SetupStateZone(); + + condition = (int) entity_list.GetNPCList().size() == 0 && (int) entity_list.GetCorpseList().size() == entries; + if (!condition) { + PrintEntityCounts(); + } + RunTest( + fmt::format("Spawns > After restore (0 NPCs) ([{}] Corpses)", entries), + true, + condition + ); + + for (auto &e: entity_list.GetCorpseList()) { + auto c = e.second; + + for (auto &s: GetStateSpawns()) { + bool is_same_corpse = + c->GetNPCTypeID() == s.npc_id && + c->GetX() == s.x && + c->GetY() == s.y && + c->GetHeading() == s.heading; + + if (is_same_corpse) { + if (!MatchCorpseState(c, s)) { + RunTest("Spawns > Corpse state matches state", true, false); + } + } + } + } + + Timer::RollForward(5); + + zone->Process(); + + condition = (int) entity_list.GetNPCList().size() == entries && (int) entity_list.GetCorpseList().size() == entries; + if (!condition) { + PrintEntityCounts(); + } + RunTest( + fmt::format("Spawns > After respawn ([{}] NPCs) ([{}] Corpses)", entries, entries), + true, + condition + ); + + for (auto &c: entity_list.GetCorpseList()) { + c.second->DepopNPCCorpse(); + } + + entity_list.CorpseProcess(); + + condition = (int) entity_list.GetNPCList().size() == entries && (int) entity_list.GetCorpseList().size() == 0; + if (!condition) { + PrintEntityCounts(); + } + RunTest( + fmt::format("Spawns > After respawn ([{}] NPCs) (0 Corpses)", entries), + true, + condition + ); + + // lets set NPC's up with a predictable loottable for testing + uint32_t loottable_id = SeedLootTable(); + + for (auto &e: entity_list.GetNPCList()) { + auto n = e.second; + n->ClearLootItems(); + n->SetResumedFromZoneSuspend(false); + n->AddLootTable(loottable_id); + n->SetResumedFromZoneSuspend(true); + } + + RespawnTimesRepository::DeleteWhere(database, fmt::format("id IN ({})", Strings::Join(spawn2_ids, ","))); + + zone->Shutdown(); + SetupStateZone(); + + npcs_to_kill = {}; + + // kill only 10 NPCs + int i = 0; + + for (const auto &e: entity_list.GetNPCList()) { + if (e.second) { + npcs_to_kill.push_back(e.second); + i++; + } + if (i == 10) { + break; + } + } + + for (auto *npc: npcs_to_kill) { + if (!npc) { + continue; + } + npc->SetQueuedToCorpse(); + npc->Death(npc, npc->GetHP() + 1, SPELL_UNKNOWN, EQ::skills::SkillHandtoHand); + } + + condition = (int) entity_list.GetNPCList().size() == (entries - 10) && (int) entity_list.GetCorpseList().size() == 10; + if (!condition) { + PrintEntityCounts(); + } + RunTest( + fmt::format("Spawns > Kill 10 NPC's before save/restore ([{}] NPCs) (10 Corpses)", (entries - 10)), + true, + condition + ); + + zone->Shutdown(); + SetupStateZone(); + + condition = (int) entity_list.GetNPCList().size() == (entries - 10) && (int) entity_list.GetCorpseList().size() == 10; + if (!condition) { + PrintEntityCounts(); + } + RunTest( + fmt::format("Spawns > After restore ([{}] NPCs) (10 Corpses)", (entries - 10)), + true, + condition + ); + + // validate that all corpses and npc's have cloak of flames + bool test_failed = false; + for (auto &e: entity_list.GetCorpseList()) { + auto corpse = e.second; + if (!corpse) { + continue; + } + + bool has_item = corpse->HasItem(11621); + if (!has_item || corpse->CountItem(11621) != 1) { + std::cout << "Corpse does not have item" << std::endl; + std::cout << " -- CountItem(11621) " << corpse->CountItem(11621) << std::endl; + std::cout << " -- GetCleanName " << corpse->GetCleanName() << std::endl; + RunTest( + "Spawns > After restore > Corpse has item (cloak of flames) item count (1) (no dupes)", + true, + has_item + ); + } + } + + for (auto &e: entity_list.GetNPCList()) { + auto npc = e.second; + if (!npc) { + continue; + } + + bool has_item = npc->HasItem(11621); + if (!has_item && npc->CountItem(11621) == 1) { + std::cout << "NPC does not have item" << std::endl; + std::cout << " -- CountItem(11621) " << npc->CountItem(11621) << std::endl; + std::cout << " -- GetCleanName " << npc->GetCleanName() << std::endl; + RunTest( + "Spawns > After restore > NPC has item (cloak of flames) item count (1) (no dupes)", + true, + has_item + ); + } + } + + RunTest( + "Spawns > After restore > All npcs/corpses have item (cloak of flames) item count (1) and no dupes", + true, + true + ); + + // test to make sure the entity variables match what was on state + int npcs_matching_state = 0; + + for (auto &e: entity_list.GetNPCList()) { + auto npc = e.second; + for (auto &s: GetStateSpawns()) { + if (npc->GetSpawnGroupId() == s.spawngroup_id && npc->GetSpawn()->GetID() == s.spawn2_id) { + if (!MatchState(npc, s)) { + RunTest("Spawns > NPC state matches state", true, false); + } + npcs_matching_state++; + } + } + } + + RunTest( + fmt::format("Spawns > After restore > NPC(s) matching state | matching ({})", npcs_matching_state), + false, + false + ); + + // test to make sure the entity variables match what was on state + int corpses_matching_state = 0; + + for (auto &e: entity_list.GetCorpseList()) { + auto c = e.second; + + for (auto &s: GetStateSpawns()) { + bool is_same_corpse = + c->GetNPCTypeID() == s.npc_id && + c->GetX() == s.x && + c->GetY() == s.y && + c->GetHeading() == s.heading; + + if (is_same_corpse) { + if (!MatchCorpseState(c, s)) { + RunTest("Spawns > Corpse state matches state", true, false); + } + corpses_matching_state++; + } + } + } + + RunTest( + fmt::format("Spawns > After restore > Corpse(s) matching state | matching ({})", corpses_matching_state), + false, + false + ); + + int max_respawn = 0; + const auto& l = RespawnTimesRepository::All(database); + for (const auto& e : l) { + if (e.duration > max_respawn) { + max_respawn = e.duration; + } + } + + entity_list.MobProcess(); + + zone->Process(); + zone->ClearSpawnTimers(); + entity_list.MobProcess(); + + for (auto &e: entity_list.GetNPCList()) { + auto npc = e.second; + if (npc->GetSpawnGroupId() == 0) { + continue; + } + + npc->SetEntityVariable("previously_spawned", "true"); + } + + Timer::RollForward(max_respawn); // longest respawn time in zone + zone->Process(); + entity_list.MobProcess(); // processing depops + + condition = (int) entity_list.GetNPCList().size() == entries && (int) entity_list.GetCorpseList().size() == 10; + if (!condition) { + PrintEntityCounts(); + PrintZoneNpcs(); + } + RunTest( + fmt::format("Spawns > After respawn, ensure we have expected entity counts ([{}] NPCs) (10 Corpses)", entries), + true, + condition + ); + + entity_list.MobProcess(); // processing depops + + npcs_matching_state = 0; + int dropped_items = 0; + + for (auto &e: entity_list.GetNPCList()) { + auto npc = e.second; + + if (npc->GetEntityVariable("previously_spawned") != "true") { + for (auto &s: GetStateSpawns()) { + if (npc->GetSpawnGroupId() == s.spawngroup_id && npc->GetSpawn()->GetID() == s.spawn2_id && + s.npc_id == 0) { + if (!MatchFreshlySpawnedState(npc, s)) { + RunTest("Spawns > NPC state matches state", true, false); + } + + dropped_items += npc->GetLootList().size(); + npcs_matching_state++; + } + } + } + } + + RunTest( + fmt::format( + "Spawns > After restore, after respawn (10 killed) > ensure NPC(s) match state | matching ({})", + npcs_matching_state + ), + npcs_matching_state == 10, + true + ); + + RunTest( + fmt::format( + "Spawns > After restore, after respawn (10 killed) ensure NPC's have loot assigned | dropped items ({})", + dropped_items + ), + dropped_items > 0, + true + ); +} + +inline void TestZoneVariables() +{ + std::vector> test_variables = { + {"test_variable", "test_value"}, + {"test_variable2", "test_value2"} + }; + + // Set variables + for (const auto &[key, value]: test_variables) { + zone->SetVariable(key, value); + RunTest("Zone variables > (" + key + ") set", value, zone->GetVariable(key)); + } + + // Simulate shutdown and restart twice + for (int i = 1; i <= 2; ++i) { + zone->Shutdown(); + SetupStateZone(); + + for (const auto &[key, value]: test_variables) { + RunTest( + "Zone variables > (" + key + ") persists after shutdown/bootup (" + std::to_string(i) + ")", + value, + zone->GetVariable(key) + ); + } + } + + // Delete one variable + zone->DeleteVariable("test_variable"); + + RunTest("Zone variables > (test_variable) delete is empty", "", zone->GetVariable("test_variable")); + RunTest( + "Zone variables > (test_variable2) delete second one still exists", + "test_value2", + zone->GetVariable("test_variable2") + ); + + // Final shutdown and restart check + zone->Shutdown(); + SetupStateZone(); + + for (const auto &[key, value]: test_variables) { + std::string expected_value = (key == "test_variable") ? "" : value; + RunTest("Zone variables > (" + key + ") after shutdown/bootup", expected_value, zone->GetVariable(key)); + } +} + +inline void TestHpManaEnd() +{ + std::vector ids = {}; + + for (auto &npc: entity_list.GetNPCList()) { + ids.push_back(npc.second->GetNPCTypeID()); + } + + auto npc_types = NpcTypesRepository::GetWhere(database, fmt::format(" id IN ({})", Strings::Join(ids, ","))); + + // validate that hp / mana / end equal that of what's on the npc types row for all rows + // dont run tests in the loop, just collect the data + std::vector> hp_mismatch = {}; + std::vector> mana_mismatch = {}; + + for (auto &e: entity_list.GetNPCList()) { + auto npc = e.second; + + for (auto &npc_type: npc_types) { + if (npc->GetNPCTypeID() != npc_type.id) { + continue; + } + + // we need to make sure the values are not 0 because of auto scaling + if (npc->GetHP() != npc_type.hp && npc_type.hp > 0) { + hp_mismatch.emplace_back(npc->GetNPCTypeID(), true); + } + if (npc->GetMana() != npc_type.mana && npc_type.mana > 0) { + mana_mismatch.emplace_back(npc->GetNPCTypeID(), true); + } + } + } + + RunTest( + "HP/Mana/End Save/Restore > Ensure default HP state matches data in npc_types row", + 0, + (int) hp_mismatch.size() + ); + + RunTest( + "HP/Mana/End Save/Restore > Ensure default Mana state matches data in npc_types row", + 0, + (int) mana_mismatch.size() + ); + + // do damage to NPC's and make sure they restore to their original values + for (auto &e: entity_list.GetNPCList()) { + auto npc = e.second; + npc->SetHP(1); + npc->SetMana(1); + } + + zone->Shutdown(); + SetupStateZone(); + + hp_mismatch.clear(); + mana_mismatch.clear(); + + // compare state values versus what's on the entity list NPC object + for (auto &e: entity_list.GetNPCList()) { + auto npc = e.second; + for (auto &state: GetStateSpawns()) { + if (npc->GetNPCTypeID() != state.npc_id) { + continue; + } + + // we need to make sure the values are not 0 because of auto scaling + if (npc->GetHP() != state.hp && state.hp > 0) { + hp_mismatch.emplace_back(npc->GetNPCTypeID(), true); + } + if (npc->GetMana() != state.mana && state.mana > 0) { + mana_mismatch.emplace_back(npc->GetNPCTypeID(), true); + } + } + } + + RunTest( + "HP/Mana/End Save/Restore > Ensure restored HP state matches data on the NPC object", + 0, + (int) hp_mismatch.size() + ); + RunTest( + "HP/Mana/End Save/Restore > Ensure restored Mana state matches data on the NPC object", + 0, + (int) mana_mismatch.size() + ); +} + +inline void TestBuffs() +{ + for (auto &e: entity_list.GetNPCList()) { + auto npc = e.second; + if (npc->GetNPCTypeID() == 0) { + continue; + } + + npc->CastSpell(6824, npc->GetID(), (EQ::spells::CastingSlot) 0, 0, 0); + } + + zone->Shutdown(); + SetupStateZone(); + + // Check buffs + bool missing_buffs = false; + + for (auto &e: entity_list.GetNPCList()) { + auto npc = e.second; + if (npc->GetNPCTypeID() == 0) { + continue; + } + + if (!npc->FindBuff(6824)) { + missing_buffs = true; + break; + } + } + + RunTest("Buffs > Persist after shutdown/bootup", false, missing_buffs); +} + +inline void TestZLocationDrift() +{ + zone->Shutdown(); + ClearState(); + SetupStateZone(); + + auto b = GetStateSpawns(); + + for (int i = 0; i < 10; ++i) { + zone->Shutdown(); + SetupStateZone(); + } + + auto a = GetStateSpawns(); + + // compare entries_before x/y/z to entries_after x/y/z + bool locations_different = false; + + for (size_t i = 0; i < b.size(); ++i) { + if (b[i].x != a[i].x || b[i].y != a[i].y || b[i].z != a[i].z) { + locations_different = true; + + std::cout << "Location drift detected for NPC ID: " << b[i].npc_id << std::endl; + std::cout << "Location drift detected for NPC ID: " << a[i].npc_id << std::endl; + std::cout << "Before - X: " << b[i].x << ", Y: " << b[i].y << ", Z: " << b[i].z << std::endl; + std::cout << "After - X: " << a[i].x << ", Y: " << a[i].y << ", Z: " << a[i].z << std::endl; + break; + } + } + + RunTest( + "Z Location Drift > Ensure Z location does not drift after multiple shutdowns/bootups", + false, + locations_different + ); +} + +inline void TestLocationChange() +{ + // Test that state spawns are where we moved them to if we moved them, move them slightly in a predictable way so + // we can test for it after restore + for (auto &e: entity_list.GetNPCList()) { + auto npc = e.second; + if (!npc) { + continue; + } + + // set all to -870 -1394 106.58 (nagafen area) + npc->SetPosition(-870, -1394, 106); + } + + zone->Shutdown(); + SetupStateZone(); + + bool all_moved = true; + + for (auto &e: entity_list.GetNPCList()) { + for (auto &state: GetStateSpawns()) { + if (e.second->GetNPCTypeID() != state.npc_id) { + continue; + } + + auto n = e.second; + if (n->GetSpawnGroupId() != state.spawngroup_id && n->GetSpawn()->GetID() != state.spawn2_id) { + continue; + } + + // z gets auto adjusted to the ground, so we dont need to check it + if (n->GetX() != state.x || n->GetY() != state.y) { + std::cout << "NPC ID: " << n->GetNPCTypeID() << " X: " << n->GetX() << " Y: " << n->GetY() << std::endl; + std::cout << "State ID " << state.npc_id << " X: " << state.x << " Y: " << state.y << std::endl; + std::cout << "-----------------------------------\n"; + all_moved = false; + break; + } + else { +// std::cout << "NPC ID: " << e.second->GetNPCTypeID() << " X: " << e.second->GetX() << " Y: " << e.second->GetY() << std::endl; +// std::cout << "State ID " << state.npc_id << " X: " << state.x << " Y: " << state.y << std::endl; +// std::cout << "-----------------------------------\n"; + } + } + } + + RunTest("Location > State spawns are where we moved them to after restore", true, all_moved); +} + +inline void TestEntityVariables() +{ + std::map test_entity_variables = { + {"test_entity_variable", "test_entity_value"}, + {"test_entity_variable2", "test_entity_value2"} + }; + + // Set entity variables + for (const auto &[key, value]: test_entity_variables) { + for (auto &e: entity_list.GetNPCList()) { + e.second->SetEntityVariable(key, value); + } + } + + zone->Shutdown(); + SetupStateZone(); + + // Check entity variables + bool missing_entity_variables = false; + for (const auto &[key, value]: test_entity_variables) { + for (auto &e: entity_list.GetNPCList()) { + if (e.second->GetEntityVariable(key) != value) { + missing_entity_variables = true; + break; + } + } + } + + RunTest("Entity Variables > Persist after shutdown/bootup", false, missing_entity_variables); +} + +inline void TestLoot() +{ + uint32_t table_id = SeedLootTable(); + + for (auto &e: entity_list.GetNPCList()) { + e.second->ClearLootItems(); + e.second->SetResumedFromZoneSuspend(false); + e.second->AddLootTable(table_id); + e.second->SetResumedFromZoneSuspend(true); + } + + bool missing_loot = false; + + for (auto &e: entity_list.GetNPCList()) { + auto npc = e.second; + if (npc->GetNPCTypeID() == 0) { + continue; + } + + // cloak of flames + if (npc->CountItem(11621) == 0) { + missing_loot = true; + break; + } + } + + RunTest("Loot > Cloak of Flames added to all NPC's via Loottable before shutdown", false, missing_loot); + + zone->Shutdown(); + SetupStateZone(); + + missing_loot = false; + for (auto &e: entity_list.GetNPCList()) { + auto npc = e.second; + if (npc->GetNPCTypeID() == 0) { + continue; + } + + // cloak of flames + if (npc->CountItem(11621) == 0) { + missing_loot = true; + break; + } + } + + RunTest("Loot > Cloak of Flames added to all NPC's via Loottable after shutdown/bootup", false, missing_loot); + + // make sure no duplicates are added + bool duplicates = false; + + for (auto &e: entity_list.GetNPCList()) { + auto npc = e.second; + if (npc->GetNPCTypeID() == 0) { + continue; + } + + if (npc->CountItem(11621) > 1) { + duplicates = true; + break; + } + } + + RunTest("Loot > No duplicates added when adding item to NPC", false, duplicates); + + // kill all NPC's + std::vector npcs_to_kill; + + // Collect NPCs first + for (const auto &e: entity_list.GetNPCList()) { + if (e.second) { + npcs_to_kill.push_back(e.second); + } + } + + // Now safely process them + for (auto *npc: npcs_to_kill) { + npc->SetQueuedToCorpse(); + npc->Death(npc, npc->GetHP() + 1, SPELL_UNKNOWN, EQ::skills::SkillHandtoHand); + } + + // make sure all of the corpses have "Cloak of Flames" + bool missing_loot_corpse = false; + + for (auto &e: entity_list.GetCorpseList()) { + auto corpse = e.second; + if (corpse->GetNPCTypeID() == 0) { + continue; + } + + if (corpse->CountItem(11621) == 0) { + missing_loot_corpse = true; + break; + } + } + + RunTest( + "Loot > Cloak of Flames added to all Corpse's via Loottable before shutdown/bootup", + false, + missing_loot_corpse + ); + + zone->Shutdown(); + SetupStateZone(); + + missing_loot_corpse = false; + for (auto &e: entity_list.GetCorpseList()) { + auto corpse = e.second; + if (corpse->GetNPCTypeID() == 0) { + continue; + } + + if (corpse->CountItem(11621) == 0) { + missing_loot_corpse = true; + break; + } + } + + RunTest( + "Loot > Cloak of Flames added to all Corpse's via Loottable after shutdown/bootup", + false, + missing_loot_corpse + ); + + // make sure no duplicates are added + bool duplicates_corpse = false; + + for (auto &e: entity_list.GetCorpseList()) { + auto corpse = e.second; + if (corpse->GetNPCTypeID() == 0) { + continue; + } + + if (corpse->CountItem(11621) > 1) { + duplicates_corpse = true; + break; + } + } + + RunTest("Loot > No duplicates added when adding item to Corpse", false, duplicates_corpse); +} + +void ZoneCLI::TestZoneState(int argc, char **argv, argh::parser &cmd, std::string &description) +{ + if (cmd[{"-h", "--help"}]) { + return; + } + + ClearState(); // clean slate + SetupStateZone(); + zone->Repop(true); + + std::cout << "===========================================\n"; + std::cout << "⚙\uFE0F> Running Zone State Tests... (soldungb)\n"; + std::cout << "===========================================\n\n"; + + TestZoneVariables(); + TestHpManaEnd(); + TestBuffs(); + TestLocationChange(); + TestEntityVariables(); + TestLoot(); + TestSpawns(); + TestZLocationDrift(); + + std::cout << "\n===========================================\n"; + std::cout << "✅ All Zone State Tests Completed!\n"; + std::cout << "===========================================\n"; +} diff --git a/zone/client.cpp b/zone/client.cpp index d0a8d97b0..5ed9eddec 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -39,9 +39,8 @@ extern volatile bool RunLoops; #include "../common/strings.h" #include "../common/data_verification.h" #include "../common/profanity_manager.h" -#include "data_bucket.h" -#include "expedition.h" -#include "expedition_database.h" +#include "../common/data_bucket.h" +#include "dynamic_zone.h" #include "expedition_request.h" #include "position.h" #include "worldserver.h" @@ -51,6 +50,7 @@ extern volatile bool RunLoops; #include "water_map.h" #include "bot_command.h" #include "string_ids.h" +#include "dialogue_window.h" #include "guild_mgr.h" #include "quest_parser_collection.h" @@ -60,12 +60,15 @@ extern volatile bool RunLoops; #include "lua_parser.h" #include "../common/repositories/character_alternate_abilities_repository.h" +#include "../common/repositories/character_expedition_lockouts_repository.h" #include "../common/repositories/account_flags_repository.h" #include "../common/repositories/bug_reports_repository.h" #include "../common/repositories/char_recipe_list_repository.h" #include "../common/repositories/character_spells_repository.h" #include "../common/repositories/character_disciplines_repository.h" #include "../common/repositories/character_data_repository.h" +#include "../common/repositories/character_pet_name_repository.h" +#include "../common/repositories/completed_tasks_repository.h" #include "../common/repositories/discovered_items_repository.h" #include "../common/repositories/inventory_repository.h" #include "../common/repositories/keyring_repository.h" @@ -83,11 +86,11 @@ extern Zone* zone; extern volatile bool is_zone_loaded; extern WorldServer worldserver; extern uint32 numclients; -extern PetitionList petition_list; void UpdateWindowTitle(char* iNewTitle); -Client::Client(EQStreamInterface *ieqs) : Mob( +// client constructor purely for testing / mocking +Client::Client() : Mob( "No name", // in_name "", // in_lastname 0, // in_cur_hp @@ -145,49 +148,50 @@ Client::Client(EQStreamInterface *ieqs) : Mob( 0, // in_heroic_strikethrough false // in_keeps_sold_items ), - hpupdate_timer(2000), - camp_timer(29000), - process_timer(100), - consume_food_timer(CONSUMPTION_TIMER), - zoneinpacket_timer(1000), - linkdead_timer(RuleI(Zone, ClientLinkdeadMS)), - dead_timer(2000), - global_channel_timer(1000), - fishing_timer(8000), - endupkeep_timer(1000), - autosave_timer(RuleI(Character, AutosaveIntervalS) * 1000), - m_client_npc_aggro_scan_timer(RuleI(Aggro, ClientAggroCheckIdleInterval)), - m_client_zone_wide_full_position_update_timer(5 * 60 * 1000), - tribute_timer(Tribute_duration), - proximity_timer(ClientProximity_interval), - TaskPeriodic_Timer(RuleI(TaskSystem, PeriodicCheckTimer) * 1000), - charm_update_timer(6000), - rest_timer(1), - pick_lock_timer(1000), - charm_class_attacks_timer(3000), - charm_cast_timer(3500), - qglobal_purge_timer(30000), - TrackingTimer(2000), - RespawnFromHoverTimer(0), - merc_timer(RuleI(Mercs, UpkeepIntervalMS)), - ItemQuestTimer(500), - anon_toggle_timer(250), - afk_toggle_timer(250), - helm_toggle_timer(250), - aggro_meter_timer(AGGRO_METER_UPDATE_MS), - m_Proximity(FLT_MAX, FLT_MAX, FLT_MAX), //arbitrary large number - m_ZoneSummonLocation(-2.0f, -2.0f, -2.0f, -2.0f), - m_AutoAttackPosition(0.0f, 0.0f, 0.0f, 0.0f), - m_AutoAttackTargetLocation(0.0f, 0.0f, 0.0f), - last_region_type(RegionTypeUnsupported), - m_dirtyautohaters(false), - m_position_update_timer(10000), - consent_throttle_timer(2000), - tmSitting(0), - parcel_timer(RuleI(Parcel, ParcelDeliveryDelay)), - lazy_load_bank_check_timer(1000), - bandolier_throttle_timer(0) + hpupdate_timer(2000), + camp_timer(29000), + process_timer(100), + consume_food_timer(CONSUMPTION_TIMER), + zoneinpacket_timer(1000), + linkdead_timer(RuleI(Zone, ClientLinkdeadMS)), + dead_timer(2000), + global_channel_timer(1000), + fishing_timer(8000), + endupkeep_timer(1000), + autosave_timer(RuleI(Character, AutosaveIntervalS) * 1000), + m_client_npc_aggro_scan_timer(RuleI(Aggro, ClientAggroCheckIdleInterval)), + m_client_bulk_npc_pos_update_timer(60 * 1000), + tribute_timer(Tribute_duration), + proximity_timer(ClientProximity_interval), + TaskPeriodic_Timer(RuleI(TaskSystem, PeriodicCheckTimer) * 1000), + charm_update_timer(6000), + rest_timer(1), + pick_lock_timer(1000), + charm_class_attacks_timer(3000), + charm_cast_timer(3500), + qglobal_purge_timer(30000), + TrackingTimer(2000), + RespawnFromHoverTimer(0), + merc_timer(RuleI(Mercs, UpkeepIntervalMS)), + ItemQuestTimer(500), + anon_toggle_timer(250), + afk_toggle_timer(250), + helm_toggle_timer(250), + aggro_meter_timer(AGGRO_METER_UPDATE_MS), + m_Proximity(FLT_MAX, FLT_MAX, FLT_MAX), //arbitrary large number + m_ZoneSummonLocation(-2.0f, -2.0f, -2.0f, -2.0f), + m_AutoAttackPosition(0.0f, 0.0f, 0.0f, 0.0f), + m_AutoAttackTargetLocation(0.0f, 0.0f, 0.0f), + last_region_type(RegionTypeUnsupported), + m_dirtyautohaters(false), + m_position_update_timer(10000), + consent_throttle_timer(2000), + tmSitting(0), + parcel_timer(RuleI(Parcel, ParcelDeliveryDelay)), + lazy_load_bank_check_timer(1000), + bandolier_throttle_timer(0) { + eqs = nullptr; for (auto client_filter = FilterNone; client_filter < _FilterCount; client_filter = eqFilterType(client_filter + 1)) { SetFilter(client_filter, FilterShow); } @@ -199,9 +203,6 @@ Client::Client(EQStreamInterface *ieqs) : Mob( client_data_loaded = false; berserk = false; dead = false; - eqs = ieqs; - ip = eqs->GetRemoteIP(); - port = ntohs(eqs->GetRemotePort()); client_state = CLIENT_CONNECTING; SetTrader(false); Haste = 0; @@ -225,8 +226,8 @@ Client::Client(EQStreamInterface *ieqs) : Mob( last_reported_endurance_percent = 0; last_reported_mana_percent = 0; gm_hide_me = false; - AFK = false; - LFG = false; + m_is_afk = false; + LFG = false; LFGFromLevel = 0; LFGToLevel = 0; LFGMatchFilter = false; @@ -329,7 +330,318 @@ Client::Client(EQStreamInterface *ieqs) : Mob( adventure_stats_timer = nullptr; adventure_leaderboard_timer = nullptr; adv_data = nullptr; - adv_requested_theme = LDoNThemes::Unused; + adv_requested_theme = LDoNTheme::Unused; + adv_requested_id = 0; + adv_requested_member_count = 0; + + for(int i = 0; i < XTARGET_HARDCAP; ++i) + { + XTargets[i].Type = Auto; + XTargets[i].ID = 0; + XTargets[i].Name[0] = 0; + XTargets[i].dirty = false; + } + MaxXTargets = 5; + XTargetAutoAddHaters = true; + m_autohatermgr.SetOwner(this, nullptr, nullptr); + m_activeautohatermgr = &m_autohatermgr; + + initial_respawn_selection = 0; + alternate_currency_loaded = false; + + interrogateinv_flag = false; + + trapid = 0; + + for (int i = 0; i < InnateSkillMax; ++i) + m_pp.InnateSkills[i] = InnateDisabled; + + temp_pvp = false; + + moving = false; + + environment_damage_modifier = 0; + invulnerable_environment_damage = false; + + // rate limiter + m_list_task_timers_rate_limit.Start(1000); + + // gm + SetDisplayMobInfoWindow(true); + SetDevToolsEnabled(true); + + bot_owner_options[booDeathMarquee] = false; + bot_owner_options[booStatsUpdate] = false; + bot_owner_options[booSpawnMessageSay] = false; + bot_owner_options[booSpawnMessageTell] = true; + bot_owner_options[booSpawnMessageClassSpecific] = true; + bot_owner_options[booAutoDefend] = RuleB(Bots, AllowOwnerOptionAutoDefend); + bot_owner_options[booBuffCounter] = false; + bot_owner_options[booMonkWuMessage] = false; + + m_parcel_platinum = 0; + m_parcel_gold = 0; + m_parcel_silver = 0; + m_parcel_copper = 0; + m_parcel_count = 0; + m_parcel_enabled = true; + m_parcel_merchant_engaged = false; + m_parcels.clear(); + + m_buyer_id = 0; + + SetBotPulling(false); + SetBotPrecombat(false); + + AI_Init(); + +} + +Client::Client(EQStreamInterface *ieqs) : Mob( + "No name", // in_name + "", // in_lastname + 0, // in_cur_hp + 0, // in_max_hp + Gender::Male, // in_gender + Race::Doug, // in_race + Class::None, // in_class + BodyType::Humanoid, // in_bodytype + Deity::Unknown, // in_deity + 0, // in_level + 0, // in_npctype_id + 0.0f, // in_size + 0.7f, // in_runspeed + glm::vec4(), // position + 0, // in_light + 0xFF, // in_texture + 0xFF, // in_helmtexture + 0, // in_ac + 0, // in_atk + 0, // in_str + 0, // in_sta + 0, // in_dex + 0, // in_agi + 0, // in_int + 0, // in_wis + 0, // in_cha + 0, // in_haircolor + 0, // in_beardcolor + 0, // in_eyecolor1 + 0, // in_eyecolor2 + 0, // in_hairstyle + 0, // in_luclinface + 0, // in_beard + 0, // in_drakkin_heritage + 0, // in_drakkin_tattoo + 0, // in_drakkin_details + EQ::TintProfile(), // in_armor_tint + 0xff, // in_aa_title + 0, // in_see_invis + 0, // in_see_invis_undead + 0, // in_see_hide + 0, // in_see_improved_hide + 0, // in_hp_regen + 0, // in_mana_regen + 0, // in_qglobal + 0, // in_maxlevel + 0, // in_scalerate + 0, // in_armtexture + 0, // in_bracertexture + 0, // in_handtexture + 0, // in_legtexture + 0, // in_feettexture + 0, // in_usemodel + false, // in_always_aggros_foes + 0, // in_heroic_strikethrough + false // in_keeps_sold_items +), + hpupdate_timer(2000), + camp_timer(29000), + bot_camp_timer((RuleI(Bots, CampTimer) * 1000)), + process_timer(100), + consume_food_timer(CONSUMPTION_TIMER), + zoneinpacket_timer(1000), + linkdead_timer(RuleI(Zone, ClientLinkdeadMS)), + dead_timer(2000), + global_channel_timer(1000), + fishing_timer(8000), + endupkeep_timer(1000), + autosave_timer(RuleI(Character, AutosaveIntervalS) * 1000), + m_client_npc_aggro_scan_timer(RuleI(Aggro, ClientAggroCheckIdleInterval)), + m_client_bulk_npc_pos_update_timer(60 * 1000), + tribute_timer(Tribute_duration), + proximity_timer(ClientProximity_interval), + TaskPeriodic_Timer(RuleI(TaskSystem, PeriodicCheckTimer) * 1000), + charm_update_timer(6000), + rest_timer(1), + pick_lock_timer(1000), + charm_class_attacks_timer(3000), + charm_cast_timer(3500), + qglobal_purge_timer(30000), + TrackingTimer(2000), + RespawnFromHoverTimer(0), + merc_timer(RuleI(Mercs, UpkeepIntervalMS)), + ItemQuestTimer(500), + anon_toggle_timer(250), + afk_toggle_timer(250), + helm_toggle_timer(250), + aggro_meter_timer(AGGRO_METER_UPDATE_MS), + m_Proximity(FLT_MAX, FLT_MAX, FLT_MAX), //arbitrary large number + m_ZoneSummonLocation(-2.0f, -2.0f, -2.0f, -2.0f), + m_AutoAttackPosition(0.0f, 0.0f, 0.0f, 0.0f), + m_AutoAttackTargetLocation(0.0f, 0.0f, 0.0f), + last_region_type(RegionTypeUnsupported), + m_dirtyautohaters(false), + m_position_update_timer(10000), + consent_throttle_timer(2000), + tmSitting(0), + parcel_timer(RuleI(Parcel, ParcelDeliveryDelay)), + lazy_load_bank_check_timer(1000), + bandolier_throttle_timer(0) +{ + for (auto client_filter = FilterNone; client_filter < _FilterCount; client_filter = eqFilterType(client_filter + 1)) { + SetFilter(client_filter, FilterShow); + } + + cheat_manager.SetClient(this); + mMovementManager->AddClient(this); + character_id = 0; + conn_state = NoPacketsReceived; + client_data_loaded = false; + berserk = false; + dead = false; + eqs = ieqs; + ip = eqs->GetRemoteIP(); + port = ntohs(eqs->GetRemotePort()); + client_state = CLIENT_CONNECTING; + SetTrader(false); + Haste = 0; + SetCustomerID(0); + SetTraderID(0); + TrackingID = 0; + WID = 0; + account_id = 0; + admin = AccountStatus::Player; + lsaccountid = 0; + guild_id = GUILD_NONE; + guildrank = 0; + guild_tribute_opt_in = 0; + SetGuildListDirty(false); + GuildBanker = false; + memset(lskey, 0, sizeof(lskey)); + strcpy(account_name, ""); + tellsoff = false; + last_reported_mana = 0; + last_reported_endurance = 0; + last_reported_endurance_percent = 0; + last_reported_mana_percent = 0; + gm_hide_me = false; + m_is_afk = false; + LFG = false; + LFGFromLevel = 0; + LFGToLevel = 0; + LFGMatchFilter = false; + LFGComments[0] = '\0'; + LFP = false; + gmspeed = 0; + gminvul = false; + playeraction = 0; + SetTarget(0); + auto_attack = false; + auto_fire = false; + runmode = false; + linkdead_timer.Disable(); + zonesummon_id = 0; + zonesummon_ignorerestrictions = 0; + bZoning = false; + m_lock_save_position = false; + zone_mode = ZoneUnsolicited; + casting_spell_id = 0; + npcflag = false; + npclevel = 0; + fishing_timer.Disable(); + dead_timer.Disable(); + camp_timer.Disable(); + bot_camp_timer.Disable(); + autosave_timer.Disable(); + GetMercTimer()->Disable(); + instalog = false; + m_pp.autosplit = false; + // initialise haste variable + m_tradeskill_object = nullptr; + delaytimer = false; + PendingRezzXP = -1; + PendingRezzDBID = 0; + PendingRezzSpellID = 0; + numclients++; + // emuerror; + UpdateWindowTitle(nullptr); + horseId = 0; + tgb = false; + tribute_master_id = 0xFFFFFFFF; + tribute_timer.Disable(); + task_state = nullptr; + TotalSecondsPlayed = 0; + keyring.clear(); + bind_sight_target = nullptr; + p_raid_instance = nullptr; + mercid = 0; + mercSlot = 0; + InitializeMercInfo(); + SetMerc(0); + if (RuleI(World, PVPMinLevel) > 0 && level >= RuleI(World, PVPMinLevel) && m_pp.pvp == 0) SetPVP(true, false); + dynamiczone_removal_timer.Disable(); + + //for good measure: + memset(&m_pp, 0, sizeof(m_pp)); + memset(&m_epp, 0, sizeof(m_epp)); + PendingTranslocate = false; + PendingSacrifice = false; + sacrifice_caster_id = 0; + controlling_boat_id = 0; + controlled_mob_id = 0; + qGlobals = nullptr; + + if (!RuleB(Character, PerCharacterQglobalMaxLevel) && !RuleB(Character, PerCharacterBucketMaxLevel)) { + SetClientMaxLevel(0); + } else if (RuleB(Character, PerCharacterQglobalMaxLevel)) { + SetClientMaxLevel(GetCharMaxLevelFromQGlobal()); + } else if (RuleB(Character, PerCharacterBucketMaxLevel)) { + SetClientMaxLevel(GetCharMaxLevelFromBucket()); + } + + KarmaUpdateTimer = new Timer(RuleI(Chat, KarmaUpdateIntervalMS)); + GlobalChatLimiterTimer = new Timer(RuleI(Chat, IntervalDurationMS)); + AttemptedMessages = 0; + TotalKarma = 0; + m_ClientVersion = EQ::versions::ClientVersion::Unknown; + m_ClientVersionBit = 0; + AggroCount = 0; + ooc_regen = false; + AreaHPRegen = 1.0f; + AreaManaRegen = 1.0f; + AreaEndRegen = 1.0f; + XPRate = 100; + current_endurance = 0; + + CanUseReport = true; + aa_los_them_mob = nullptr; + los_status = false; + los_status_facing = false; + HideCorpseMode = HideCorpseNone; + PendingGuildInvitation = false; + + InitializeBuffSlots(); + + adventure_request_timer = nullptr; + adventure_create_timer = nullptr; + adventure_leave_timer = nullptr; + adventure_door_timer = nullptr; + adv_requested_data = nullptr; + adventure_stats_timer = nullptr; + adventure_leaderboard_timer = nullptr; + adv_data = nullptr; + adv_requested_theme = LDoNTheme::Unused; adv_requested_id = 0; adv_requested_member_count = 0; @@ -396,12 +708,16 @@ Client::Client(EQStreamInterface *ieqs) : Mob( } Client::~Client() { + entity_list.RemoveMobFromCloseLists(this); + m_close_mobs.clear(); + if (ClientVersion() == EQ::versions::ClientVersion::RoF2 && RuleB (Parcel, EnableParcelMerchants)) { DoParcelCancel(); } mMovementManager->RemoveClient(this); + DataBucket::DeleteCachedBuckets(DataBucketLoadType::Account, AccountID()); DataBucket::DeleteCachedBuckets(DataBucketLoadType::Client, CharacterID()); if (RuleB(Bots, Enabled)) { @@ -499,9 +815,11 @@ Client::~Client() { zone->RemoveAuth(GetName(), lskey); //let the stream factory know were done with this stream - eqs->Close(); - eqs->ReleaseFromUse(); - safe_delete(eqs); + if (eqs) { + eqs->Close(); + eqs->ReleaseFromUse(); + safe_delete(eqs); + } UninitializeBuffSlots(); } @@ -564,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(); @@ -650,6 +972,10 @@ bool Client::SaveAA() m_pp.aapoints_spent = aa_points_spent + m_epp.expended_aa; + if (v.empty()) { + return true; + } + return CharacterAlternateAbilitiesRepository::ReplaceMany(database, v); } @@ -669,6 +995,8 @@ bool Client::Save(uint8 iCommitNow) { if(!ClientDataLoaded()) return false; + BenchTimer timer; + /* Wrote current basics to PP for saves */ if (!m_lock_save_position) { m_pp.x = m_Position.x; @@ -775,6 +1103,12 @@ bool Client::Save(uint8 iCommitNow) { database.SaveCharacterEXPModifier(this); + if (RuleB(Bots, Enabled)) { + database.botdb.SaveBotSettings(this); + } + + LogInfo("Save for [{}] took [{}]", GetCleanName(), timer.elapsed()); + return true; } @@ -841,6 +1175,10 @@ void Client::QueuePacket(const EQApplicationPacket* app, bool ack_req, CLIENT_CO return; } + if (RuleB(Character, AutoIdleFilterPackets) && m_is_idle && IsFilteredAFKPacket(app)) { + return; + } + if (client_state != CLIENT_CONNECTED && required_state == CLIENT_CONNECTED) { AddPacket(app, ack_req); return; @@ -879,6 +1217,58 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s LogDebug("Client::ChannelMessageReceived() Channel:[{}] message:[{}]", chan_num, message); + if (RuleB(Chat, AlwaysCaptureCommandText)) { + if (message[0] == COMMAND_CHAR) { + if (command_dispatch(this, message, false) == -2) { + if (parse->PlayerHasQuestSub(EVENT_COMMAND)) { + int i = parse->EventPlayer(EVENT_COMMAND, this, message, 0); + if (i == 0 && !RuleB(Chat, SuppressCommandErrors)) { + Message(Chat::Red, "Command '%s' not recognized.", message); + } + } + else if (parse->PlayerHasQuestSub(EVENT_SAY)) { + int i = parse->EventPlayer(EVENT_SAY, this, message, 0); + if (i == 0 && !RuleB(Chat, SuppressCommandErrors)) { + Message(Chat::Red, "Command '%s' not recognized.", message); + } + } + else { + if (!RuleB(Chat, SuppressCommandErrors)) { + Message(Chat::Red, "Command '%s' not recognized.", message); + } + } + } + return; + } + + if (message[0] == BOT_COMMAND_CHAR) { + if (RuleB(Bots, Enabled)) { + if (bot_command_dispatch(this, message) == -2) { + if (parse->PlayerHasQuestSub(EVENT_BOT_COMMAND)) { + int i = parse->EventPlayer(EVENT_BOT_COMMAND, this, message, 0); + if (i == 0 && !RuleB(Chat, SuppressCommandErrors)) { + Message(Chat::Red, "Bot command '%s' not recognized.", message); + } + } + else if (parse->PlayerHasQuestSub(EVENT_SAY)) { + int i = parse->EventPlayer(EVENT_SAY, this, message, 0); + if (i == 0 && !RuleB(Chat, SuppressCommandErrors)) { + Message(Chat::Red, "Bot command '%s' not recognized.", message); + } + } + else { + if (!RuleB(Chat, SuppressCommandErrors)) { + Message(Chat::Red, "Bot command '%s' not recognized.", message); + } + } + } + } else { + Message(Chat::Red, "Bots are disabled on this server."); + } + return; + } + } + if (targetname == nullptr) { targetname = (!GetTarget()) ? "" : GetTarget()->GetName(); } @@ -928,28 +1318,21 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s } } - /* Logs Player Chat */ - if (RuleB(QueryServ, PlayerLogChat)) { - auto pack = new ServerPacket(ServerOP_Speech, sizeof(Server_Speech_Struct) + strlen(message) + 1); - Server_Speech_Struct* sem = (Server_Speech_Struct*) pack->pBuffer; + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::EventType::SPEECH)) { + PlayerEvent::PlayerSpeech e{}; + std::string msg = message; + if (!msg.empty() && msg.at(0) != '#' && msg.at(0) != '^') { + e.message = message; + e.min_status = Admin(); + e.type = chan_num; + e.to = targetname; + e.from = GetCleanName(); + if (chan_num == ChatChannel_Guild) { + e.guild_id = GuildID(); + } - if(chan_num == ChatChannel_Guild) - sem->guilddbid = GuildID(); - else - sem->guilddbid = 0; - - strcpy(sem->message, message); - sem->minstatus = Admin(); - sem->type = chan_num; - if(targetname != 0) - strcpy(sem->to, targetname); - - if(GetName() != 0) - strcpy(sem->from, GetName()); - - if(worldserver.Connected()) - worldserver.SendPacket(pack); - safe_delete(pack); + RecordPlayerEventLog(PlayerEvent::SPEECH, e); + } } // Garble the message based on drunkness @@ -1008,7 +1391,7 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s } case ChatChannel_Shout: { /* Shout */ Mob *sender = this; - if (GetPet() && GetTarget() == GetPet() && GetPet()->FindType(SE_VoiceGraft)) + if (GetPet() && GetTarget() == GetPet() && GetPet()->FindType(SpellEffect::VoiceGraft)) sender = GetPet(); entity_list.ChannelMessage(sender, chan_num, language, lang_skill, message); @@ -1046,7 +1429,7 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s else if(!RuleB(Chat, ServerWideAuction)) { Mob *sender = this; - if (GetPet() && GetTarget() == GetPet() && GetPet()->FindType(SE_VoiceGraft)) + if (GetPet() && GetTarget() == GetPet() && GetPet()->FindType(SpellEffect::VoiceGraft)) sender = GetPet(); entity_list.ChannelMessage(sender, chan_num, language, lang_skill, message); @@ -1093,7 +1476,7 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s { Mob *sender = this; - if (GetPet() && GetTarget() == GetPet() && GetPet()->FindType(SE_VoiceGraft)) + if (GetPet() && GetTarget() == GetPet() && GetPet()->FindType(SpellEffect::VoiceGraft)) sender = GetPet(); entity_list.ChannelMessage(sender, chan_num, language, lang_skill, message); @@ -1157,7 +1540,7 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s break; } case ChatChannel_Say: { /* Say */ - if (player_event_logs.IsEventEnabled(PlayerEvent::SAY)) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::SAY)) { std::string msg = message; if (!msg.empty() && msg.at(0) != '#' && msg.at(0) != '^') { auto e = PlayerEvent::SayEvent{ @@ -1223,7 +1606,7 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s } Mob* sender = this; - if (GetPet() && GetTarget() == GetPet() && GetPet()->FindType(SE_VoiceGraft)) { + if (GetPet() && GetTarget() == GetPet() && GetPet()->FindType(SpellEffect::VoiceGraft)) { sender = GetPet(); } @@ -1469,7 +1852,7 @@ bool Client::UpdateLDoNPoints(uint32 theme_id, int points) bool is_loss = false; switch (theme_id) { - case LDoNThemes::Unused: { // No theme, so distribute evenly across all + case LDoNTheme::Unused: { // No theme, so distribute evenly across all int split_points = (points / 5); int guk_points = (split_points + (points % 5)); @@ -1522,12 +1905,12 @@ bool Client::UpdateLDoNPoints(uint32 theme_id, int points) points -= split_points; if (split_points != 0) { // if anything left, recursively loop thru again - UpdateLDoNPoints(LDoNThemes::Unused, split_points); + UpdateLDoNPoints(LDoNTheme::Unused, split_points); } break; } - case LDoNThemes::GUK: { + case LDoNTheme::GUK: { if (points < 0) { if (m_pp.ldon_points_guk < (0 - points)) { return false; @@ -1539,7 +1922,7 @@ bool Client::UpdateLDoNPoints(uint32 theme_id, int points) m_pp.ldon_points_guk += points; break; } - case LDoNThemes::MIR: { + case LDoNTheme::MIR: { if (points < 0) { if (m_pp.ldon_points_mir < (0 - points)) { return false; @@ -1551,7 +1934,7 @@ bool Client::UpdateLDoNPoints(uint32 theme_id, int points) m_pp.ldon_points_mir += points; break; } - case LDoNThemes::MMC: { + case LDoNTheme::MMC: { if (points < 0) { if (m_pp.ldon_points_mmc < (0 - points)) { return false; @@ -1563,7 +1946,7 @@ bool Client::UpdateLDoNPoints(uint32 theme_id, int points) m_pp.ldon_points_mmc += points; break; } - case LDoNThemes::RUJ: { + case LDoNTheme::RUJ: { if (points < 0) { if (m_pp.ldon_points_ruj < (0 - points)) { return false; @@ -1575,7 +1958,7 @@ bool Client::UpdateLDoNPoints(uint32 theme_id, int points) m_pp.ldon_points_ruj += points; break; } - case LDoNThemes::TAK: { + case LDoNTheme::TAK: { if (points < 0) { if (m_pp.ldon_points_tak < (0 - points)) { return false; @@ -1623,23 +2006,23 @@ bool Client::UpdateLDoNPoints(uint32 theme_id, int points) void Client::SetLDoNPoints(uint32 theme_id, uint32 points) { switch (theme_id) { - case LDoNThemes::GUK: { + case LDoNTheme::GUK: { m_pp.ldon_points_guk = points; break; } - case LDoNThemes::MIR: { + case LDoNTheme::MIR: { m_pp.ldon_points_mir = points; break; } - case LDoNThemes::MMC: { + case LDoNTheme::MMC: { m_pp.ldon_points_mmc = points; break; } - case LDoNThemes::RUJ: { + case LDoNTheme::RUJ: { m_pp.ldon_points_ruj = points; break; } - case LDoNThemes::TAK: { + case LDoNTheme::TAK: { m_pp.ldon_points_tak = points; break; } @@ -1827,7 +2210,7 @@ void Client::FriendsWho(char *FriendsString) { Message(0, "Error: World server disconnected"); else { auto pack = - new ServerPacket(ServerOP_FriendsWho, sizeof(ServerFriendsWho_Struct) + strlen(FriendsString)); + new ServerPacket(ServerOP_FriendsWho, sizeof(ServerFriendsWho_Struct) + strlen(FriendsString)); ServerFriendsWho_Struct* FriendsWho = (ServerFriendsWho_Struct*) pack->pBuffer; FriendsWho->FromID = GetID(); strcpy(FriendsWho->FromName, GetName()); @@ -1849,7 +2232,7 @@ void Client::UpdateAdmin(bool from_database) { if (m_pp.gm) { LogInfo("[{}] - [{}] is a GM", __FUNCTION__ , GetName()); - petition_list.UpdateGMQueue(); + PetitionList::Instance()->UpdateGMQueue(); } UpdateWho(); @@ -2149,7 +2532,7 @@ void Client::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) Mob::FillSpawnStruct(ns, ForWho); // Populate client-specific spawn information - ns->spawn.afk = AFK; + ns->spawn.afk = m_is_afk; ns->spawn.lfg = LFG; // afk and lfg are cleared on zoning on live ns->spawn.anon = m_pp.anon; ns->spawn.gm = GetGM() ? 1 : 0; @@ -2208,7 +2591,7 @@ void Client::Stand() { } void Client::Sit() { - SetAppearance(eaSitting, false); + SetAppearance(eaSitting, false); } void Client::ChangeLastName(std::string last_name) { @@ -2230,40 +2613,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; } @@ -2479,6 +2881,7 @@ void Client::AddMoneyToPP(uint64 copper, bool update_client){ /* Add Amount of Platinum */ temporary_copper_two = temporary_copper / 1000; + m_external_handin_money_returned.platinum = temporary_copper_two; int32 new_value = m_pp.platinum + temporary_copper_two; if (new_value < 0) { @@ -2492,6 +2895,7 @@ void Client::AddMoneyToPP(uint64 copper, bool update_client){ /* Add Amount of Gold */ temporary_copper_two = temporary_copper / 100; new_value = m_pp.gold + temporary_copper_two; + m_external_handin_money_returned.gold = temporary_copper_two; if (new_value < 0) { m_pp.gold = 0; @@ -2504,6 +2908,7 @@ void Client::AddMoneyToPP(uint64 copper, bool update_client){ /* Add Amount of Silver */ temporary_copper_two = temporary_copper / 10; new_value = m_pp.silver + temporary_copper_two; + m_external_handin_money_returned.silver = temporary_copper_two; if (new_value < 0) { m_pp.silver = 0; @@ -2516,6 +2921,7 @@ void Client::AddMoneyToPP(uint64 copper, bool update_client){ /* Add Amount of Copper */ temporary_copper_two = temporary_copper; new_value = m_pp.copper + temporary_copper_two; + m_external_handin_money_returned.copper = temporary_copper_two; if (new_value < 0) { m_pp.copper = 0; @@ -2532,23 +2938,12 @@ void Client::AddMoneyToPP(uint64 copper, bool update_client){ SaveCurrency(); + m_external_handin_money_returned.return_source = "AddMoneyToPP"; + LogDebug("Client::AddMoneyToPP() [{}] should have: plat:[{}] gold:[{}] silver:[{}] copper:[{}]", GetName(), m_pp.platinum, m_pp.gold, m_pp.silver, m_pp.copper); } -void Client::EVENT_ITEM_ScriptStopReturn(){ - /* Set a timestamp in an entity variable for plugin check_handin.pl in return_items - This will stopgap players from items being returned if global_npc.pl has a catch all return_items - */ - struct timeval read_time; - char buffer[50]; - gettimeofday(&read_time, 0); - sprintf(buffer, "%li.%li \n", read_time.tv_sec, read_time.tv_usec); - SetEntityVariable("Stop_Return", buffer); -} - void Client::AddMoneyToPP(uint32 copper, uint32 silver, uint32 gold, uint32 platinum, bool update_client){ - EVENT_ITEM_ScriptStopReturn(); - int32 new_value = m_pp.platinum + platinum; if (new_value >= 0 && new_value > m_pp.platinum) { m_pp.platinum += platinum; @@ -2576,6 +2971,14 @@ void Client::AddMoneyToPP(uint32 copper, uint32 silver, uint32 gold, uint32 plat RecalcWeight(); SaveCurrency(); + m_external_handin_money_returned = ExternalHandinMoneyReturned{ + .copper = copper, + .silver = silver, + .gold = gold, + .platinum = platinum, + .return_source = "AddMoneyToPP" + }; + #if (EQDEBUG>=5) LogDebug("Client::AddMoneyToPP() [{}] should have: plat:[{}] gold:[{}] silver:[{}] copper:[{}]", GetName(), m_pp.platinum, m_pp.gold, m_pp.silver, m_pp.copper); @@ -2705,7 +3108,7 @@ bool Client::CheckIncreaseSkill(EQ::skills::SkillType skillid, Mob *against_who, { SetSkill(skillid, GetRawSkill(skillid) + 1); - if (player_event_logs.IsEventEnabled(PlayerEvent::SKILL_UP)) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::SKILL_UP)) { auto e = PlayerEvent::SkillUpEvent{ .skill_id = static_cast(skillid), .value = static_cast((skillval + 1)), @@ -2793,7 +3196,7 @@ bool Client::CanHaveSkill(EQ::skills::SkillType skill_id) const skill_id = EQ::skills::Skill2HPiercing; } - return skill_caps.GetSkillCap(GetClass(), skill_id, RuleI(Character, MaxLevel)).cap > 0; + return SkillCaps::Instance()->GetSkillCap(GetClass(), skill_id, RuleI(Character, MaxLevel)).cap > 0; } uint16 Client::MaxSkill(EQ::skills::SkillType skill_id, uint8 class_id, uint8 level) const @@ -2806,7 +3209,7 @@ uint16 Client::MaxSkill(EQ::skills::SkillType skill_id, uint8 class_id, uint8 le skill_id = EQ::skills::Skill2HPiercing; } - return skill_caps.GetSkillCap(class_id, skill_id, level).cap; + return SkillCaps::Instance()->GetSkillCap(class_id, skill_id, level).cap; } uint8 Client::GetSkillTrainLevel(EQ::skills::SkillType skill_id, uint8 class_id) @@ -2819,7 +3222,7 @@ uint8 Client::GetSkillTrainLevel(EQ::skills::SkillType skill_id, uint8 class_id) skill_id = EQ::skills::Skill2HPiercing; } - return skill_caps.GetSkillTrainLevel(class_id, skill_id, RuleI(Character, MaxLevel)); + return SkillCaps::Instance()->GetSkillTrainLevel(class_id, skill_id, RuleI(Character, MaxLevel)); } uint16 Client::GetMaxSkillAfterSpecializationRules(EQ::skills::SkillType skillid, uint16 maxSkill) @@ -4309,7 +4712,7 @@ void Client::KeyRingLoad() const auto &l = KeyringRepository::GetWhere( database, fmt::format( - "`char_id` = {} ORDER BY `item_id`", + "`char_id` = {} ORDER BY `item_id` ASC", character_id ) ); @@ -4318,21 +4721,15 @@ void Client::KeyRingLoad() return; } - - for (const auto &e : l) { + for (const auto& e : l) { keyring.emplace_back(e.item_id); } } -void Client::KeyRingAdd(uint32 item_id) +bool Client::KeyRingAdd(uint32 item_id) { - if (!item_id) { - return; - } - - const bool found = KeyRingCheck(item_id); - if (found) { - return; + if (!item_id || KeyRingCheck(item_id)) { + return false; } auto e = KeyringRepository::NewEntity(); @@ -4343,14 +4740,14 @@ void Client::KeyRingAdd(uint32 item_id) e = KeyringRepository::InsertOne(database, e); if (!e.id) { - return; + return false; } keyring.emplace_back(item_id); if (!RuleB(World, UseItemLinksForKeyRing)) { Message(Chat::LightBlue, "Added to keyring."); - return; + return true; } const std::string &item_link = database.CreateItemLink(item_id); @@ -4362,47 +4759,237 @@ void Client::KeyRingAdd(uint32 item_id) item_link ).c_str() ); + return true; } bool Client::KeyRingCheck(uint32 item_id) { - for (const auto &e : keyring) { - if (e == item_id) { - return true; + return std::find(keyring.begin(), keyring.end(), item_id) != keyring.end(); +} + +bool Client::KeyRingClear() +{ + keyring.clear(); + + return KeyringRepository::DeleteWhere( + database, + fmt::format( + "`char_id` = {}", + CharacterID() + ) + ); +} + +void Client::KeyRingList(Client* c) +{ + if (!c) { + return; + } + + std::string message = "Keys on Keyring:"; + if (c != this) { + message = fmt::format( + "Keys on Keyring for {}:", + GetCleanName() + ); + } + + c->Message(Chat::LightBlue, message.c_str()); + + const EQ::ItemData *item = nullptr; + + for (const uint32& e : keyring) { + item = database.GetItem(e); + if (item) { + const std::string& item_string = ( + RuleB(World, UseItemLinksForKeyRing) ? + database.CreateItemLink(e) : + item->Name + ); + + c->Message(Chat::LightBlue, item_string.c_str()); } } +} + +bool Client::KeyRingRemove(uint32 item_id) +{ + keyring.erase( + std::remove( + keyring.begin(), + keyring.end(), + item_id + ) + ); + + return KeyringRepository::DeleteWhere( + database, + fmt::format( + "`char_id` = {} AND `item_id` = {}", + CharacterID(), + 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; } -void Client::KeyRingList() -{ - Message(Chat::LightBlue, "Keys on Keyring:"); - - const EQ::ItemData *item = nullptr; - - for (const auto &e : keyring) { - item = database.GetItem(e); - if (item) { - const std::string &item_string = RuleB(World, UseItemLinksForKeyRing) ? database.CreateItemLink(e) : item->Name; - - Message(Chat::LightBlue, item_string.c_str()); - } - } -} - -bool Client::IsDiscovered(uint32 item_id) { - const auto& l = DiscoveredItemsRepository::GetWhere( - database, - fmt::format( - "item_id = {}", - item_id - ) - ); - if (l.empty()) { +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; + } + + DataBucketKey k = GetScopedBucketKeys(); + k.key = "PetNameChangesAllowed"; + + auto b = DataBucket::GetData(k); + if (!b.value.empty()) { + return true; + } + + return false; +} + +void Client::InvokeChangePetName(bool immediate) { + if (!IsPetNameChangeAllowed()) { + return; + } + + auto packet_op = immediate ? OP_InvokeChangePetNameImmediate : OP_InvokeChangePetName; + + auto outapp = new EQApplicationPacket(packet_op, 0); + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::GrantPetNameChange() { + DataBucketKey k = GetScopedBucketKeys(); + k.key = "PetNameChangesAllowed"; + k.value = "true"; + DataBucket::SetData(k); + + InvokeChangePetName(true); +} + +void Client::ClearPetNameChange() { + DataBucketKey k = GetScopedBucketKeys(); + k.key = "PetNameChangesAllowed"; + + DataBucket::DeleteData(k); +} + +bool Client::ChangePetName(std::string new_name) +{ + if (new_name.empty()) { + return false; + } + + if (!IsPetNameChangeAllowed()) { + return false; + } + + auto pet = GetPet(); + if (!pet) { + return false; + } + + if (pet->GetName() == new_name) { + return false; + } + + if (!database.CheckNameFilter(new_name) || database.IsNameUsed(new_name)) { + return false; + } + + CharacterPetNameRepository::ReplaceOne( + database, + CharacterPetNameRepository::CharacterPetName{ + .character_id = static_cast(CharacterID()), + .name = new_name + } + ); + + pet->TempName(new_name.c_str()); + + ClearPetNameChange(); + return true; +} + +bool Client::IsDiscovered(uint32 item_id) +{ + if ( + std::find( + zone->discovered_items.begin(), + zone->discovered_items.end(), + item_id + ) != zone->discovered_items.end() + ) { + return true; + } + + if ( + DiscoveredItemsRepository::GetWhere( + database, + fmt::format( + "`item_id` = {} LIMIT 1", + item_id + ) + ).empty() + ) { + return false; + } + + zone->discovered_items.emplace_back(item_id); + return true; } @@ -4416,7 +5003,7 @@ void Client::DiscoverItem(uint32 item_id) { auto d = DiscoveredItemsRepository::InsertOne(database, e); - if (player_event_logs.IsEventEnabled(PlayerEvent::DISCOVER_ITEM)) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::DISCOVER_ITEM)) { const auto* item = database.GetItem(item_id); auto e = PlayerEvent::DiscoverItemEvent{ @@ -5627,13 +6214,13 @@ bool Client::TryReward(uint32 claim_id) if (amt == 1) { query = StringFormat("DELETE FROM account_rewards " - "WHERE account_id = %i AND reward_id = %i", - AccountID(), claim_id); + "WHERE account_id = %i AND reward_id = %i", + AccountID(), claim_id); auto results = database.QueryDatabase(query); } else { query = StringFormat("UPDATE account_rewards SET amount = (amount-1) " - "WHERE account_id = %i AND reward_id = %i", - AccountID(), claim_id); + "WHERE account_id = %i AND reward_id = %i", + AccountID(), claim_id); auto results = database.QueryDatabase(query); } @@ -5676,15 +6263,15 @@ uint32 Client::GetLDoNPointsTheme(uint32 t) { switch(t) { - case LDoNThemes::GUK: + case LDoNTheme::GUK: return m_pp.ldon_points_guk; - case LDoNThemes::MIR: + case LDoNTheme::MIR: return m_pp.ldon_points_mir; - case LDoNThemes::MMC: + case LDoNTheme::MMC: return m_pp.ldon_points_mmc; - case LDoNThemes::RUJ: + case LDoNTheme::RUJ: return m_pp.ldon_points_ruj; - case LDoNThemes::TAK: + case LDoNTheme::TAK: return m_pp.ldon_points_tak; default: return 0; @@ -5695,15 +6282,15 @@ uint32 Client::GetLDoNWinsTheme(uint32 t) { switch(t) { - case LDoNThemes::GUK: + case LDoNTheme::GUK: return m_pp.ldon_wins_guk; - case LDoNThemes::MIR: + case LDoNTheme::MIR: return m_pp.ldon_wins_mir; - case LDoNThemes::MMC: + case LDoNTheme::MMC: return m_pp.ldon_wins_mmc; - case LDoNThemes::RUJ: + case LDoNTheme::RUJ: return m_pp.ldon_wins_ruj; - case LDoNThemes::TAK: + case LDoNTheme::TAK: return m_pp.ldon_wins_tak; default: return 0; @@ -5714,15 +6301,15 @@ uint32 Client::GetLDoNLossesTheme(uint32 t) { switch(t) { - case LDoNThemes::GUK: + case LDoNTheme::GUK: return m_pp.ldon_losses_guk; - case LDoNThemes::MIR: + case LDoNTheme::MIR: return m_pp.ldon_losses_mir; - case LDoNThemes::MMC: + case LDoNTheme::MMC: return m_pp.ldon_losses_mmc; - case LDoNThemes::RUJ: + case LDoNTheme::RUJ: return m_pp.ldon_losses_ruj; - case LDoNThemes::TAK: + case LDoNTheme::TAK: return m_pp.ldon_losses_tak; default: return 0; @@ -5731,35 +6318,35 @@ uint32 Client::GetLDoNLossesTheme(uint32 t) void Client::UpdateLDoNWinLoss(uint32 theme_id, bool win, bool remove) { switch (theme_id) { - case LDoNThemes::GUK: + case LDoNTheme::GUK: if (win) { m_pp.ldon_wins_guk += (remove ? -1 : 1); } else { m_pp.ldon_losses_guk += (remove ? -1 : 1); } break; - case LDoNThemes::MIR: + case LDoNTheme::MIR: if (win) { m_pp.ldon_wins_mir += (remove ? -1 : 1); } else { m_pp.ldon_losses_mir += (remove ? -1 : 1); } break; - case LDoNThemes::MMC: + case LDoNTheme::MMC: if (win) { m_pp.ldon_wins_mmc += (remove ? -1 : 1); } else { m_pp.ldon_losses_mmc += (remove ? -1 : 1); } break; - case LDoNThemes::RUJ: + case LDoNTheme::RUJ: if (win) { m_pp.ldon_wins_ruj += (remove ? -1 : 1); } else { m_pp.ldon_losses_ruj += (remove ? -1 : 1); } break; - case LDoNThemes::TAK: + case LDoNTheme::TAK: if (win) { m_pp.ldon_wins_tak += (remove ? -1 : 1); } else { @@ -5828,17 +6415,17 @@ void Client::SuspendMinion(int value) // TODO: These pet command states need to be synced ... // Will just fix them for now if (m_ClientVersionBit & EQ::versions::maskUFAndLater) { - SetPetCommandState(PET_BUTTON_SIT, 0); - SetPetCommandState(PET_BUTTON_STOP, 0); - SetPetCommandState(PET_BUTTON_REGROUP, 0); - SetPetCommandState(PET_BUTTON_FOLLOW, 1); - SetPetCommandState(PET_BUTTON_GUARD, 0); + SetPetCommandState(PetButton::Sit, PetButtonState::Off); + SetPetCommandState(PetButton::Stop, PetButtonState::Off); + SetPetCommandState(PetButton::Regroup, PetButtonState::Off); + SetPetCommandState(PetButton::Follow, PetButtonState::On); + SetPetCommandState(PetButton::Guard, PetButtonState::Off); // Taunt saved on client side for logging on with pet // In our db for when we zone. - SetPetCommandState(PET_BUTTON_HOLD, 0); - SetPetCommandState(PET_BUTTON_GHOLD, 0); - SetPetCommandState(PET_BUTTON_FOCUS, 0); - SetPetCommandState(PET_BUTTON_SPELLHOLD, 0); + SetPetCommandState(PetButton::Hold, PetButtonState::Off); + SetPetCommandState(PetButton::GreaterHold, PetButtonState::Off); + SetPetCommandState(PetButton::Focus, PetButtonState::Off); + SetPetCommandState(PetButton::SpellHold, PetButtonState::Off); } } else @@ -5871,7 +6458,7 @@ void Client::SuspendMinion(int value) { m_suspendedminion.SpellID = SpellID; - m_suspendedminion.HP = CurrentPet->GetHP();; + m_suspendedminion.HP = CurrentPet->GetHP(); m_suspendedminion.Mana = CurrentPet->GetMana(); m_suspendedminion.petpower = CurrentPet->GetPetPower(); @@ -6227,7 +6814,7 @@ void Client::NewAdventure(int id, int theme, const char *text, int member_count, void Client::ClearPendingAdventureData() { adv_requested_id = 0; - adv_requested_theme = LDoNThemes::Unused; + adv_requested_theme = LDoNTheme::Unused; safe_delete_array(adv_requested_data); adv_requested_member_count = 0; } @@ -6319,9 +6906,9 @@ void Client::CheckLDoNHail(NPC* n) auto pet = GetPet(); if (pet) { - if (pet->GetPetType() == petCharmed) { - pet->BuffFadeByEffect(SE_Charm); - } else if (pet->GetPetType() == petNPCFollow) { + if (pet->GetPetType() == PetType::Charmed) { + pet->BuffFadeByEffect(SpellEffect::Charm); + } else if (pet->GetPetType() == PetType::Follow) { pet->SetOwnerID(0); } else { pet->Depop(); @@ -6821,15 +7408,6 @@ int Client::AddAlternateCurrencyValue(uint32 currency_id, int amount, bool is_sc return 0; } - /* Added via Quest, rest of the logging methods may be done inline due to information available in that area of the code */ - if (is_scripted) { - /* QS: PlayerLogAlternateCurrencyTransactions :: Cursor to Item Storage */ - if (RuleB(QueryServ, PlayerLogAlternateCurrencyTransactions)){ - std::string event_desc = StringFormat("Added via Quest :: Cursor to Item :: alt_currency_id:%i amount:%i in zoneid:%i instid:%i", currency_id, GetZoneID(), GetInstanceID()); - QServ->PlayerLogEvent(Player_Log_Alternate_Currency_Transactions, CharacterID(), event_desc); - } - } - if (!amount) { return 0; } @@ -7867,7 +8445,7 @@ void Client::MerchantRejectMessage(Mob *merchant, int primaryfaction) merchant->SayString(zone->random.Int(WONT_SELL_DEEDS1, WONT_SELL_DEEDS6)); } else if (lowestvalue == fmod.race_mod) { // race biggest // Non-standard race (ex. illusioned to wolf) - if (GetRace() > PLAYER_RACE_COUNT) { + if (!IsPlayerRace(GetRace())) { messageid = zone->random.Int(1, 3); // these aren't sequential StringIDs :( switch (messageid) { case 1: @@ -8366,7 +8944,7 @@ int Client::GetQuiverHaste(int delay) for (int r = EQ::invslot::GENERAL_BEGIN; r <= EQ::invslot::GENERAL_END; r++) { pi = GetInv().GetItem(r); if (pi && pi->IsClassBag() && pi->GetItem()->BagType == EQ::item::BagTypeQuiver && - pi->GetItem()->BagWR > 0) + pi->GetItem()->BagWR > 0) break; if (r == EQ::invslot::GENERAL_END) // we will get here if we don't find a valid quiver @@ -8723,10 +9301,10 @@ void Client::ProcessAggroMeter() // probably should have PVP rules ... if (cur_tar && cur_tar != this) { - if (cur_tar->IsNPC() && !cur_tar->IsPetOwnerClient() && cur_tar->GetID() != m_aggrometer.get_target_id()) { + if (cur_tar->IsNPC() && !cur_tar->IsPetOwnerOfClientBot() && cur_tar->GetID() != m_aggrometer.get_target_id()) { m_aggrometer.set_target_id(cur_tar->GetID()); send_targetinfo = true; - } else if ((cur_tar->IsPetOwnerClient() || cur_tar->IsClient()) && cur_tar->GetTarget() && cur_tar->GetTarget()->GetID() != m_aggrometer.get_target_id()) { + } else if ((cur_tar->IsPetOwnerOfClientBot() || cur_tar->IsClient()) && cur_tar->GetTarget() && cur_tar->GetTarget()->GetID() != m_aggrometer.get_target_id()) { m_aggrometer.set_target_id(cur_tar->GetTarget()->GetID()); send_targetinfo = true; } @@ -8872,12 +9450,15 @@ void Client::ProcessAggroMeter() } } -void Client::SetPetCommandState(int button, int state) +void Client::SetPetCommandState(uint8 button, uint8 state) { auto app = new EQApplicationPacket(OP_PetCommandState, sizeof(PetCommandState_Struct)); - auto pcs = (PetCommandState_Struct *)app->pBuffer; - pcs->button_id = button; - pcs->state = state; + + auto s = (PetCommandState_Struct*) app->pBuffer; + + s->button_id = button; + s->state = state; + FastQueuePacket(&app); } @@ -9124,7 +9705,7 @@ void Client::SetPrimaryWeaponOrnamentation(uint32 model_id) auto l = InventoryRepository::GetWhere( database, fmt::format( - "`charid` = {} AND `slotid` = {}", + "`character_id` = {} AND `slot_id` = {}", character_id, EQ::invslot::slotPrimary ) @@ -9136,7 +9717,7 @@ void Client::SetPrimaryWeaponOrnamentation(uint32 model_id) auto e = l.front(); - e.ornamentidfile = model_id; + e.ornament_idfile = model_id; const int updated = InventoryRepository::UpdateOne(database, e); @@ -9157,7 +9738,7 @@ void Client::SetSecondaryWeaponOrnamentation(uint32 model_id) auto l = InventoryRepository::GetWhere( database, fmt::format( - "`charid` = {} AND `slotid` = {}", + "`character_id` = {} AND `slot_id` = {}", character_id, EQ::invslot::slotSecondary ) @@ -9169,7 +9750,7 @@ void Client::SetSecondaryWeaponOrnamentation(uint32 model_id) auto e = l.front(); - e.ornamentidfile = model_id; + e.ornament_idfile = model_id; const int updated = InventoryRepository::UpdateOne(database, e); @@ -9329,14 +9910,6 @@ void Client::ShowDevToolsMenu() std::string menu_search; std::string menu_show; std::string menu_reload_one; - std::string menu_reload_two; - std::string menu_reload_three; - std::string menu_reload_four; - std::string menu_reload_five; - std::string menu_reload_six; - std::string menu_reload_seven; - std::string menu_reload_eight; - std::string menu_reload_nine; std::string menu_toggle; std::string window_toggle; @@ -9361,45 +9934,7 @@ void Client::ShowDevToolsMenu() /** * Reload */ - menu_reload_one += Saylink::Silent("#reload aa", "AAs"); - menu_reload_one += " | " + Saylink::Silent("#reload alternate_currencies", "Alternate Currencies"); - menu_reload_one += " | " + Saylink::Silent("#reload base_data", "Base Data"); - menu_reload_one += " | " + Saylink::Silent("#reload blocked_spells", "Blocked Spells"); - - menu_reload_two += Saylink::Silent("#reload commands", "Commands"); - menu_reload_two += " | " + Saylink::Silent("#reload content_flags", "Content Flags"); - - menu_reload_three += Saylink::Silent("#reload data_buckets_cache", "Databuckets"); - menu_reload_three += " | " + Saylink::Silent("#reload doors", "Doors"); - menu_reload_three += " | " + Saylink::Silent("#reload factions", "Factions"); - menu_reload_three += " | " + Saylink::Silent("#reload ground_spawns", "Ground Spawns"); - - menu_reload_four += Saylink::Silent("#reload logs", "Level Based Experience Modifiers"); - menu_reload_four += " | " + Saylink::Silent("#reload logs", "Log Settings"); - menu_reload_four += " | " + Saylink::Silent("#reload Loot", "Loot"); - - menu_reload_five += Saylink::Silent("#reload merchants", "Merchants"); - menu_reload_five += " | " + Saylink::Silent("#reload npc_emotes", "NPC Emotes"); - menu_reload_five += " | " + Saylink::Silent("#reload npc_spells", "NPC Spells"); - menu_reload_five += " | " + Saylink::Silent("#reload objects", "Objects"); - menu_reload_five += " | " + Saylink::Silent("#reload opcodes", "Opcodes"); - - menu_reload_six += Saylink::Silent("#reload perl_export", "Perl Event Export Settings"); - menu_reload_six += " | " + Saylink::Silent("#reload quest", "Quests"); - - menu_reload_seven += Saylink::Silent("#reload rules", "Rules"); - menu_reload_seven += " | " + Saylink::Silent("#reload skill_caps", "Skill Caps"); - menu_reload_seven += " | " + Saylink::Silent("#reload static", "Static Zone Data"); - menu_reload_seven += " | " + Saylink::Silent("#reload tasks", "Tasks"); - - menu_reload_eight += Saylink::Silent("#reload titles", "Titles"); - menu_reload_eight += " | " + Saylink::Silent("#reload traps 1", "Traps"); - menu_reload_eight += " | " + Saylink::Silent("#reload variables", "Variables"); - menu_reload_eight += " | " + Saylink::Silent("#reload veteran_rewards", "Veteran Rewards"); - - menu_reload_nine += Saylink::Silent("#reload world", "World"); - menu_reload_nine += " | " + Saylink::Silent("#reload zone", "Zone"); - menu_reload_nine += " | " + Saylink::Silent("#reload zone_points", "Zone Points"); + menu_reload_one += Saylink::Silent("#reload", "Reload Menu (#reload)"); /** * Show window status @@ -9469,70 +10004,6 @@ void Client::ShowDevToolsMenu() ).c_str() ); - Message( - Chat::White, - fmt::format( - "Reload | {}", - menu_reload_two - ).c_str() - ); - - Message( - Chat::White, - fmt::format( - "Reload | {}", - menu_reload_three - ).c_str() - ); - - Message( - Chat::White, - fmt::format( - "Reload | {}", - menu_reload_four - ).c_str() - ); - - Message( - Chat::White, - fmt::format( - "Reload | {}", - menu_reload_five - ).c_str() - ); - - Message( - Chat::White, - fmt::format( - "Reload | {}", - menu_reload_six - ).c_str() - ); - - Message( - Chat::White, - fmt::format( - "Reload | {}", - menu_reload_seven - ).c_str() - ); - - Message( - Chat::White, - fmt::format( - "Reload | {}", - menu_reload_eight - ).c_str() - ); - - Message( - Chat::White, - fmt::format( - "Reload | {}", - menu_reload_nine - ).c_str() - ); - auto help_link = Saylink::Silent("#help"); Message( @@ -9549,8 +10020,8 @@ void Client::ShowDevToolsMenu() Chat::White, fmt::format( "Current Expansion | {} ({})", - content_service.GetCurrentExpansionName(), - content_service.GetCurrentExpansion() + WorldContentService::Instance()->GetCurrentExpansionName(), + WorldContentService::Instance()->GetCurrentExpansion() ).c_str() ); @@ -9625,13 +10096,13 @@ void Client::SendCrossZoneMessageString( return; } - SerializeBuffer argument_buffer; + SerializeBuffer argbuf; for (const auto& argument : arguments) { - argument_buffer.WriteString(argument); + argbuf.WriteString(argument); } - uint32_t args_size = static_cast(argument_buffer.size()); + uint32_t args_size = static_cast(argbuf.size()); uint32_t pack_size = sizeof(CZClientMessageString_Struct) + args_size; auto pack = std::make_unique(ServerOP_CZClientMessageString, pack_size); auto buf = reinterpret_cast(pack->pBuffer); @@ -9639,7 +10110,10 @@ void Client::SendCrossZoneMessageString( buf->chat_type = chat_type; strn0cpy(buf->client_name, character_name.c_str(), sizeof(buf->client_name)); buf->args_size = args_size; - memcpy(buf->args, argument_buffer.buffer(), argument_buffer.size()); + if (argbuf.size() > 0) + { + memcpy(buf->args, argbuf.buffer(), argbuf.size()); + } if (client) { @@ -9657,56 +10131,52 @@ void Client::SendDynamicZoneUpdates() SendDzCompassUpdate(); SetDynamicZoneMemberStatus(DynamicZoneMemberStatus::Online); - m_expedition_lockouts = ExpeditionDatabase::LoadCharacterLockouts(CharacterID()); + m_dz_lockouts = CharacterExpeditionLockoutsRepository::GetLockouts(database, CharacterID()); // expeditions are the only dz type that keep the window updated - auto expedition = GetExpedition(); - if (expedition) + if (DynamicZone* dz = GetExpedition()) { - expedition->GetDynamicZone()->SendClientWindowUpdate(this); + dz->SendClientWindowUpdate(this); // live synchronizes lockouts obtained during the active expedition to // members once they zone into the expedition's dynamic zone instance - if (expedition->GetDynamicZone()->IsCurrentZoneDzInstance()) + if (dz->IsCurrentZoneDz()) { - expedition->SyncCharacterLockouts(CharacterID(), m_expedition_lockouts); + dz->SyncCharacterLockouts(CharacterID(), m_dz_lockouts); } } - SendExpeditionLockoutTimers(); + SendDzLockoutTimers(); // ask world for any pending invite we saved from a previous zone - RequestPendingExpeditionInvite(); + RequestPendingDzInvite(); } -Expedition* Client::CreateExpedition(DynamicZone& dz, bool disable_messages) +DynamicZone* Client::CreateExpedition(DynamicZone& dz, bool silent) { - return Expedition::TryCreate(this, dz, disable_messages); + return DynamicZone::TryCreate(*this, dz, silent); } -Expedition* Client::CreateExpedition( - const std::string& zone_name, uint32 version, uint32 duration, const std::string& expedition_name, - uint32 min_players, uint32 max_players, bool disable_messages) +DynamicZone* Client::CreateExpedition(uint32 zone, uint32 version, uint32 duration, const std::string& name, uint32 min_players, uint32 max_players, bool silent) { - DynamicZone dz{ ZoneID(zone_name), version, duration, DynamicZoneType::Expedition }; - dz.SetName(expedition_name); + DynamicZone dz{ zone, version, duration, DynamicZoneType::Expedition }; + dz.SetName(name); dz.SetMinPlayers(min_players); dz.SetMaxPlayers(max_players); - return Expedition::TryCreate(this, dz, disable_messages); + return DynamicZone::TryCreate(*this, dz, silent); } -Expedition* Client::CreateExpeditionFromTemplate(uint32_t dz_template_id) +DynamicZone* Client::CreateExpeditionFromTemplate(uint32_t dz_template_id) { - Expedition* expedition = nullptr; auto it = zone->dz_template_cache.find(dz_template_id); if (it != zone->dz_template_cache.end()) { DynamicZone dz(DynamicZoneType::Expedition); dz.LoadTemplate(it->second); - expedition = Expedition::TryCreate(this, dz, false); + return DynamicZone::TryCreate(*this, dz, false); } - return expedition; + return nullptr; } void Client::CreateTaskDynamicZone(int task_id, DynamicZone& dz_request) @@ -9717,57 +10187,58 @@ void Client::CreateTaskDynamicZone(int task_id, DynamicZone& dz_request) } } -Expedition* Client::GetExpedition() const +DynamicZone* Client::GetExpedition() const { - if (zone && m_expedition_id) + if (zone) { - auto expedition_cache_iter = zone->expedition_cache.find(m_expedition_id); - if (expedition_cache_iter != zone->expedition_cache.end()) + for (uint32_t dz_id : m_dynamic_zone_ids) { - return expedition_cache_iter->second.get(); + auto it = zone->dynamic_zone_cache.find(dz_id); + if (it != zone->dynamic_zone_cache.end() && it->second->IsExpedition()) + { + return it->second.get(); + } } } return nullptr; } -void Client::AddExpeditionLockout(const ExpeditionLockoutTimer& lockout, bool update_db) +uint32_t Client::GetExpeditionID() const +{ + if (const DynamicZone* dz = GetExpedition()) + { + return dz->GetID(); + } + return 0; +} + +void Client::AddDzLockout(const DzLockout& lockout, bool update_db) { // todo: support for account based lockouts like live AoC expeditions // if client already has this lockout, we're replacing it with the new one - m_expedition_lockouts.erase(std::remove_if(m_expedition_lockouts.begin(), m_expedition_lockouts.end(), - [&](const ExpeditionLockoutTimer& existing_lockout) { - return existing_lockout.IsSameLockout(lockout); - } - ), m_expedition_lockouts.end()); + std::erase_if(m_dz_lockouts, [&](const DzLockout& l) { return l.IsSame(lockout); }); - m_expedition_lockouts.emplace_back(lockout); + m_dz_lockouts.push_back(lockout); if (update_db) // for quest api { - ExpeditionDatabase::InsertCharacterLockouts(CharacterID(), { lockout }); + CharacterExpeditionLockoutsRepository::InsertLockouts(database, CharacterID(), { lockout }); } - SendExpeditionLockoutTimers(); + SendDzLockoutTimers(); } -void Client::AddNewExpeditionLockout( - const std::string& expedition_name, const std::string& event_name, uint32_t seconds, std::string uuid) +void Client::AddDzLockout(const std::string& expedition, const std::string& event, uint32_t seconds, std::string uuid) { - auto lockout = ExpeditionLockoutTimer::CreateLockout(expedition_name, event_name, seconds, uuid); - AddExpeditionLockout(lockout, true); + auto lockout = DzLockout::Create(expedition, event, seconds, uuid); + AddDzLockout(lockout, true); } -void Client::AddExpeditionLockoutDuration( - const std::string& expedition_name, const std::string& event_name, int seconds, - const std::string& uuid, bool update_db) +void Client::AddDzLockoutDuration(const DzLockout& lockout, int seconds, const std::string& uuid, bool update_db) { - auto it = std::find_if(m_expedition_lockouts.begin(), m_expedition_lockouts.end(), - [&](const ExpeditionLockoutTimer& lockout) { - return lockout.IsSameLockout(expedition_name, event_name); - }); - - if (it != m_expedition_lockouts.end()) + auto it = std::ranges::find_if(m_dz_lockouts, [&](const DzLockout& l) { return l.IsSame(lockout); }); + if (it != m_dz_lockouts.end()) { it->AddLockoutTime(seconds); @@ -9778,98 +10249,86 @@ void Client::AddExpeditionLockoutDuration( if (update_db) { - ExpeditionDatabase::InsertCharacterLockouts(CharacterID(), { *it }); + CharacterExpeditionLockoutsRepository::InsertLockouts(database, CharacterID(), { *it }); } - SendExpeditionLockoutTimers(); + SendDzLockoutTimers(); } else if (seconds > 0) // missing lockouts inserted for reductions would be instantly expired { - auto lockout = ExpeditionLockoutTimer::CreateLockout(expedition_name, event_name, seconds, uuid); - AddExpeditionLockout(lockout, update_db); + AddDzLockout(lockout, update_db); } } -void Client::RemoveExpeditionLockout( - const std::string& expedition_name, const std::string& event_name, bool update_db) +void Client::RemoveDzLockout(const std::string& expedition, const std::string& event, bool update_db) { - m_expedition_lockouts.erase(std::remove_if(m_expedition_lockouts.begin(), m_expedition_lockouts.end(), - [&](const ExpeditionLockoutTimer& lockout) { - return lockout.IsSameLockout(expedition_name, event_name); - } - ), m_expedition_lockouts.end()); + std::erase_if(m_dz_lockouts, [&](const DzLockout& l) { return l.IsSame(expedition, event); }); if (update_db) // for quest api { - ExpeditionDatabase::DeleteCharacterLockout(CharacterID(), expedition_name, event_name); + CharacterExpeditionLockoutsRepository::DeleteWhere(database, fmt::format( + "character_id = {} AND expedition_name = '{}' AND event_name = '{}'", + CharacterID(), Strings::Escape(expedition), Strings::Escape(event))); } - SendExpeditionLockoutTimers(); + SendDzLockoutTimers(); } -void Client::RemoveAllExpeditionLockouts(const std::string& expedition_name, bool update_db) +void Client::RemoveDzLockouts(const std::string& expedition, bool update_db) { - if (expedition_name.empty()) + if (expedition.empty()) { if (update_db) { - ExpeditionDatabase::DeleteAllCharacterLockouts(CharacterID()); + CharacterExpeditionLockoutsRepository::DeleteWhere(database, fmt::format( + "character_id = {}", CharacterID())); } - m_expedition_lockouts.clear(); + m_dz_lockouts.clear(); } else { if (update_db) { - ExpeditionDatabase::DeleteAllCharacterLockouts(CharacterID(), expedition_name); + CharacterExpeditionLockoutsRepository::DeleteWhere(database, fmt::format( + "character_id = {} AND expedition_name = '{}'", CharacterID(), Strings::Escape(expedition))); } - - m_expedition_lockouts.erase(std::remove_if(m_expedition_lockouts.begin(), m_expedition_lockouts.end(), - [&](const ExpeditionLockoutTimer& lockout) { - return lockout.GetExpeditionName() == expedition_name; - } - ), m_expedition_lockouts.end()); + std::erase_if(m_dz_lockouts, [&](const DzLockout& l) { return l.DzName() == expedition; }); } - SendExpeditionLockoutTimers(); + SendDzLockoutTimers(); } -const ExpeditionLockoutTimer* Client::GetExpeditionLockout( - const std::string& expedition_name, const std::string& event_name, bool include_expired) const +const DzLockout* Client::GetDzLockout(const std::string& expedition, const std::string& event) const { - for (const auto& expedition_lockout : m_expedition_lockouts) + for (const auto& lockout : m_dz_lockouts) { - if ((include_expired || !expedition_lockout.IsExpired()) && - expedition_lockout.IsSameLockout(expedition_name, event_name)) + if (!lockout.IsExpired() && lockout.IsSame(expedition, event)) { - return &expedition_lockout; + return &lockout; } } return nullptr; } -std::vector Client::GetExpeditionLockouts( - const std::string& expedition_name, bool include_expired) +std::vector Client::GetDzLockouts(const std::string& expedition) { - std::vector lockouts; - for (const auto& lockout : m_expedition_lockouts) + std::vector lockouts; + for (const auto& lockout : m_dz_lockouts) { - if ((include_expired || !lockout.IsExpired()) && - lockout.GetExpeditionName() == expedition_name) + if (!lockout.IsExpired() && lockout.DzName() == expedition) { - lockouts.emplace_back(lockout); + lockouts.push_back(lockout); } } return lockouts; } -bool Client::HasExpeditionLockout( - const std::string& expedition_name, const std::string& event_name, bool include_expired) +bool Client::HasDzLockout(const std::string& expedition, const std::string& event) const { - return (GetExpeditionLockout(expedition_name, event_name, include_expired) != nullptr); + return GetDzLockout(expedition, event) != nullptr; } -void Client::SendExpeditionLockoutTimers() +void Client::SendDzLockoutTimers() { std::vector lockout_entries; @@ -9878,20 +10337,20 @@ void Client::SendExpeditionLockoutTimers() constexpr uint32_t rounding_seconds = 60; // erases expired lockouts while building lockout timer list - for (auto it = m_expedition_lockouts.begin(); it != m_expedition_lockouts.end();) + for (auto it = m_dz_lockouts.begin(); it != m_dz_lockouts.end();) { uint32_t seconds_remaining = it->GetSecondsRemaining(); if (seconds_remaining == 0) { - it = m_expedition_lockouts.erase(it); + it = m_dz_lockouts.erase(it); } else { - ExpeditionLockoutTimerEntry_Struct lockout; - strn0cpy(lockout.expedition_name, it->GetExpeditionName().c_str(), sizeof(lockout.expedition_name)); + ExpeditionLockoutTimerEntry_Struct lockout{}; + strn0cpy(lockout.expedition_name, it->DzName().c_str(), sizeof(lockout.expedition_name)); lockout.seconds_remaining = seconds_remaining + rounding_seconds; - lockout.event_type = it->IsReplayTimer() ? Expedition::REPLAY_TIMER_ID : Expedition::EVENT_TIMER_ID; - strn0cpy(lockout.event_name, it->GetEventName().c_str(), sizeof(lockout.event_name)); + lockout.event_type = it->IsReplay() ? DynamicZone::ReplayTimerID : DynamicZone::EventTimerID; + strn0cpy(lockout.event_name, it->Event().c_str(), sizeof(lockout.event_name)); lockout_entries.emplace_back(lockout); ++it; @@ -9911,38 +10370,31 @@ void Client::SendExpeditionLockoutTimers() QueuePacket(outapp.get()); } -void Client::RequestPendingExpeditionInvite() +void Client::RequestPendingDzInvite() const { - uint32_t packsize = sizeof(ServerExpeditionCharacterID_Struct); - auto pack = std::make_unique(ServerOP_ExpeditionRequestInvite, packsize); - auto packbuf = reinterpret_cast(pack->pBuffer); - packbuf->character_id = CharacterID(); - worldserver.SendPacket(pack.get()); + ServerPacket pack(ServerOP_DzRequestInvite, static_cast(sizeof(ServerCharacterID_Struct))); + auto packbuf = reinterpret_cast(pack.pBuffer); + packbuf->char_id = CharacterID(); + worldserver.SendPacket(&pack); } void Client::DzListTimers() { // only lists player's current replay timer lockouts, not all event lockouts bool found = false; - for (const auto& lockout : m_expedition_lockouts) + for (const auto& lockout : m_dz_lockouts) { - if (lockout.IsReplayTimer()) + if (lockout.IsReplay()) { found = true; - auto time_remaining = lockout.GetDaysHoursMinutesRemaining(); - MessageString( - Chat::Yellow, DZLIST_REPLAY_TIMER, - time_remaining.days.c_str(), - time_remaining.hours.c_str(), - time_remaining.mins.c_str(), - lockout.GetExpeditionName().c_str() - ); + auto time = lockout.GetTimeRemainingStrs(); + MessageString(Chat::Yellow, DZ_TIMER, time.days.c_str(), time.hours.c_str(), time.mins.c_str(), lockout.DzName().c_str()); } } if (!found) { - MessageString(Chat::Yellow, EXPEDITION_NO_TIMERS); + MessageString(Chat::Yellow, DZ_NO_TIMERS); } } @@ -10048,7 +10500,10 @@ std::unique_ptr Client::CreateCompassPacket( auto outapp = std::make_unique(OP_DzCompass, outsize); auto outbuf = reinterpret_cast(outapp->pBuffer); outbuf->count = count; - memcpy(outbuf->entries, compass_entries.data(), entries_size); + if (!compass_entries.empty()) + { + memcpy(outbuf->entries, compass_entries.data(), entries_size); + } return outapp; } @@ -10057,7 +10512,7 @@ void Client::GoToDzSafeReturnOrBind(const DynamicZone* dynamic_zone) { if (dynamic_zone) { - auto safereturn = dynamic_zone->GetSafeReturnLocation(); + const auto& safereturn = dynamic_zone->GetSafeReturnLocation(); if (safereturn.zone_id != 0) { LogDynamicZonesDetail("Sending [{}] to safereturn zone [{}]", CharacterID(), safereturn.zone_id); @@ -10114,7 +10569,7 @@ void Client::SetDynamicZoneMemberStatus(DynamicZoneMemberStatus status) for (auto& dz : GetDynamicZones()) { // the rule to disable this status is handled internally by the dz - if (status == DynamicZoneMemberStatus::Online && dz->IsCurrentZoneDzInstance()) + if (status == DynamicZoneMemberStatus::Online && dz->IsCurrentZoneDz()) { status = DynamicZoneMemberStatus::InDynamicZone; } @@ -10135,7 +10590,7 @@ void Client::MovePCDynamicZone(uint32 zone_id, int zone_version, bool msg_if_inv { if (msg_if_invalid) { - MessageString(Chat::Red, DYNAMICZONE_WAY_IS_BLOCKED); // unconfirmed message + MessageString(Chat::Red, DZ_WAY_IS_BLOCKED); // unconfirmed message } } else if (client_dzs.size() == 1) @@ -10203,7 +10658,7 @@ void Client::MovePCDynamicZone(const std::string& zone_name, int zone_version, b } void Client::Fling(float value, float target_x, float target_y, float target_z, bool ignore_los, bool clip_through_walls, bool calculate_speed) { - BuffFadeByEffect(SE_Levitate); + BuffFadeByEffect(SpellEffect::Levitate); if (CheckLosFN(target_x, target_y, target_z, 6.0f) || ignore_los) { auto p = new EQApplicationPacket(OP_Fling, sizeof(fling_struct)); auto* f = (fling_struct*) p->pBuffer; @@ -10268,7 +10723,7 @@ std::vector Client::GetLearnableDisciplines(uint8 min_level, uint8 max_leve continue; } - if (RuleB(Spells, UseCHAScribeHack) && spells[spell_id].effect_id[EFFECT_COUNT - 1] == SE_CHA) { + if (RuleB(Spells, UseCHAScribeHack) && spells[spell_id].effect_id[EFFECT_COUNT - 1] == SpellEffect::CHA) { continue; } @@ -10339,7 +10794,7 @@ std::vector Client::GetScribeableSpells(uint8 min_level, uint8 max_level) { continue; } - if (RuleB(Spells, UseCHAScribeHack) && spells[spell_id].effect_id[EFFECT_COUNT - 1] == SE_CHA) { + if (RuleB(Spells, UseCHAScribeHack) && spells[spell_id].effect_id[EFFECT_COUNT - 1] == SpellEffect::CHA) { continue; } @@ -10403,15 +10858,37 @@ void Client::SetAnon(uint8 anon_flag) { safe_delete(outapp); } -void Client::SetAFK(uint8 afk_flag) { - AFK = afk_flag; - auto outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct)); - SpawnAppearance_Struct* spawn_appearance = (SpawnAppearance_Struct*)outapp->pBuffer; - spawn_appearance->spawn_id = GetID(); - spawn_appearance->type = AppearanceType::AFK; - spawn_appearance->parameter = afk_flag; - entity_list.QueueClients(this, outapp); - safe_delete(outapp); +void Client::SetAFK(uint8 afk_flag) +{ + if (!afk_flag) { + ResetAFKTimer(); + } + + bool changed_afk_state = (m_is_afk && !afk_flag) || (!m_is_afk && afk_flag); + + if (!changed_afk_state) { + return; + } + + // set messaging based on the state + std::string you_are = "You are no longer AFK."; + if (!m_is_afk && afk_flag) { + you_are = "You are now AFK."; + } + + // set the state + m_is_afk = afk_flag; + + // inform of state change + Message(Chat::Yellow, you_are.c_str()); + + // send the spawn appearance packet to all clients + static EQApplicationPacket p(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct)); + auto *s = (SpawnAppearance_Struct *) p.pBuffer; + s->spawn_id = GetID(); + s->type = AppearanceType::AFK; + s->parameter = afk_flag; + entity_list.QueueClients(this, &p); } void Client::SendToInstance(std::string instance_type, std::string zone_short_name, uint32 instance_version, float x, float y, float z, float heading, std::string instance_identifier, uint32 duration) { @@ -10461,27 +10938,15 @@ void Client::SendToInstance(std::string instance_type, std::string zone_short_na MovePC(zone_id, instance_id, x, y, z, heading); } -int Client::CountItem(uint32 item_id) +uint32 Client::CountItem(uint32 item_id) { - int quantity = 0; + uint32 quantity = 0; EQ::ItemInstance *item = nullptr; - static const int16 slots[][2] = { - { EQ::invslot::POSSESSIONS_BEGIN, EQ::invslot::POSSESSIONS_END }, - { EQ::invbag::GENERAL_BAGS_BEGIN, EQ::invbag::GENERAL_BAGS_END }, - { EQ::invbag::CURSOR_BAG_BEGIN, EQ::invbag::CURSOR_BAG_END}, - { EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END }, - { EQ::invslot::GUILD_TRIBUTE_BEGIN, EQ::invslot::GUILD_TRIBUTE_END }, - { EQ::invbag::BANK_BAGS_BEGIN, EQ::invbag::BANK_BAGS_END }, - { EQ::invslot::SHARED_BANK_BEGIN, EQ::invslot::SHARED_BANK_END }, - { EQ::invbag::SHARED_BANK_BAGS_BEGIN, EQ::invbag::SHARED_BANK_BAGS_END }, - }; - const size_t slot_index_count = sizeof(slots) / sizeof(slots[0]); - for (int slot_index = 0; slot_index < slot_index_count; ++slot_index) { - for (int slot_id = slots[slot_index][0]; slot_id <= slots[slot_index][1]; ++slot_id) { - item = GetInv().GetItem(slot_id); - if (item && item->GetID() == item_id) { - quantity += (item->IsStackable() ? item->GetCharges() : 1); - } + + for (const int16& slot_id : GetInventorySlots()) { + item = GetInv().GetItem(slot_id); + if (item && item->GetID() == item_id) { + quantity += (item->IsStackable() ? item->GetCharges() : 1); } } @@ -10498,30 +10963,26 @@ void Client::ResetItemCooldown(uint32 item_id) int recast_type = item_d->RecastType; bool found_item = false; - static const int16 slots[][2] = { - { EQ::invslot::POSSESSIONS_BEGIN, EQ::invslot::POSSESSIONS_END }, - { EQ::invbag::GENERAL_BAGS_BEGIN, EQ::invbag::GENERAL_BAGS_END }, - { EQ::invbag::CURSOR_BAG_BEGIN, EQ::invbag::CURSOR_BAG_END}, - { EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END }, - { EQ::invbag::BANK_BAGS_BEGIN, EQ::invbag::BANK_BAGS_END }, - { EQ::invslot::SHARED_BANK_BEGIN, EQ::invslot::SHARED_BANK_END }, - { EQ::invbag::SHARED_BANK_BAGS_BEGIN, EQ::invbag::SHARED_BANK_BAGS_END }, - }; - const size_t slot_index_count = sizeof(slots) / sizeof(slots[0]); - for (int slot_index = 0; slot_index < slot_index_count; ++slot_index) { - for (int slot_id = slots[slot_index][0]; slot_id <= slots[slot_index][1]; ++slot_id) { - item = GetInv().GetItem(slot_id); - if (item) { - item_d = item->GetItem(); - if (item_d && item->GetID() == item_id || (item_d->RecastType != RECAST_TYPE_UNLINKED_ITEM && item_d->RecastType == recast_type)) { - item->SetRecastTimestamp(0); - DeleteItemRecastTimer(item_d->ID); - SendItemPacket(slot_id, item, ItemPacketCharmUpdate); - found_item = true; - } + for (const int16& slot_id : GetInventorySlots()) { + item = GetInv().GetItem(slot_id); + if (item) { + item_d = item->GetItem(); + if ( + item_d && + item->GetID() == item_id || + ( + item_d->RecastType != RECAST_TYPE_UNLINKED_ITEM && + item_d->RecastType == recast_type + ) + ) { + item->SetRecastTimestamp(0); + DeleteItemRecastTimer(item_d->ID); + SendItemPacket(slot_id, item, ItemPacketCharmUpdate); + found_item = true; } } } + if (!found_item) { DeleteItemRecastTimer(item_id); //We didn't find the item but we still want to remove the timer } @@ -10556,25 +11017,20 @@ void Client::SetItemCooldown(uint32 item_id, bool use_saved_timer, uint32 in_sec final_time = total_time - current_time; } - static const int16 slots[][2] = { - { EQ::invslot::POSSESSIONS_BEGIN, EQ::invslot::POSSESSIONS_END }, - { EQ::invbag::GENERAL_BAGS_BEGIN, EQ::invbag::GENERAL_BAGS_END }, - { EQ::invbag::CURSOR_BAG_BEGIN, EQ::invbag::CURSOR_BAG_END}, - { EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END }, - { EQ::invbag::BANK_BAGS_BEGIN, EQ::invbag::BANK_BAGS_END }, - { EQ::invslot::SHARED_BANK_BEGIN, EQ::invslot::SHARED_BANK_END }, - { EQ::invbag::SHARED_BANK_BAGS_BEGIN, EQ::invbag::SHARED_BANK_BAGS_END }, - }; - const size_t slot_index_count = sizeof(slots) / sizeof(slots[0]); - for (int slot_index = 0; slot_index < slot_index_count; ++slot_index) { - for (int slot_id = slots[slot_index][0]; slot_id <= slots[slot_index][1]; ++slot_id) { - item = GetInv().GetItem(slot_id); - if (item) { - item_d = item->GetItem(); - if (item_d && item->GetID() == item_id || (item_d->RecastType != RECAST_TYPE_UNLINKED_ITEM && item_d->RecastType == recast_type)) { - item->SetRecastTimestamp(total_time); - SendItemPacket(slot_id, item, ItemPacketCharmUpdate); - } + for (const int16& slot_id : GetInventorySlots()) { + item = GetInv().GetItem(slot_id); + if (item) { + item_d = item->GetItem(); + if ( + item_d && + item->GetID() == item_id || + ( + item_d->RecastType != RECAST_TYPE_UNLINKED_ITEM && + item_d->RecastType == recast_type + ) + ) { + item->SetRecastTimestamp(total_time); + SendItemPacket(slot_id, item, ItemPacketCharmUpdate); } } } @@ -10617,38 +11073,26 @@ uint32 Client::GetItemCooldown(uint32 item_id) void Client::RemoveItem(uint32 item_id, uint32 quantity) { + uint32 removed_count = 0; EQ::ItemInstance *item = nullptr; - static const int16 slots[][2] = { - { EQ::invslot::POSSESSIONS_BEGIN, EQ::invslot::POSSESSIONS_END }, - { EQ::invbag::GENERAL_BAGS_BEGIN, EQ::invbag::GENERAL_BAGS_END }, - { EQ::invbag::CURSOR_BAG_BEGIN, EQ::invbag::CURSOR_BAG_END}, - { EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END }, - { EQ::invslot::GUILD_TRIBUTE_BEGIN, EQ::invslot::GUILD_TRIBUTE_END }, - { EQ::invbag::BANK_BAGS_BEGIN, EQ::invbag::BANK_BAGS_END }, - { EQ::invslot::SHARED_BANK_BEGIN, EQ::invslot::SHARED_BANK_END }, - { EQ::invbag::SHARED_BANK_BAGS_BEGIN, EQ::invbag::SHARED_BANK_BAGS_END }, - }; - int16 removed_count = 0; - const size_t slot_index_count = sizeof(slots) / sizeof(slots[0]); - for (int slot_index = 0; slot_index < slot_index_count; ++slot_index) { - for (int slot_id = slots[slot_index][0]; slot_id <= slots[slot_index][1]; ++slot_id) { - if (removed_count == quantity) { - break; - } - item = GetInv().GetItem(slot_id); - if (item && item->GetID() == item_id) { - int16 charges = item->IsStackable() ? item->GetCharges() : 0; - int16 stack_size = std::max(charges, static_cast(1)); - if ((removed_count + stack_size) <= quantity) { - removed_count += stack_size; - DeleteItemInInventory(slot_id, charges, true); - } else { - int16 amount_left = (quantity - removed_count); - if (amount_left > 0 && stack_size >= amount_left) { - removed_count += amount_left; - DeleteItemInInventory(slot_id, amount_left, true); - } + for (const int16& slot_id : GetInventorySlots()) { + if (removed_count == quantity) { + break; + } + + item = GetInv().GetItem(slot_id); + if (item && item->GetID() == item_id) { + uint32 charges = item->IsStackable() ? item->GetCharges() : 0; + uint32 stack_size = std::max(charges, static_cast(1)); + if ((removed_count + stack_size) <= quantity) { + removed_count += stack_size; + DeleteItemInInventory(slot_id, charges, true); + } else { + uint32 amount_left = (quantity - removed_count); + if (amount_left > 0 && stack_size >= amount_left) { + removed_count += amount_left; + DeleteItemInInventory(slot_id, amount_left, true); } } } @@ -10992,7 +11436,7 @@ void Client::SummonBaggedItems(uint32 bag_item_id, const std::vector& return; } - // todo: maybe some common functions for SE_SummonItem and SE_SummonItemIntoBag + // todo: maybe some common functions for SpellEffect::SummonItem and SpellEffect::SummonItemIntoBag const EQ::ItemData* bag_item = database.GetItem(bag_item_id); if (!bag_item) @@ -11331,338 +11775,22 @@ void Client::ReconnectUCS() void Client::SendReloadCommandMessages() { SendChatLineBreak(); - auto aa_link = Saylink::Silent("#reload aa"); + for (auto &t: ServerReload::GetTypes()) { + std::string reload_slug = Strings::Slugify(ServerReload::GetNameClean(t), "_");; + auto reload_link = Saylink::Silent(fmt::format("#reload {}", reload_slug), "Local"); + auto reload_link_global = Saylink::Silent(fmt::format("#reload {} global", reload_slug), "Global"); - Message( - Chat::White, - fmt::format( - "Usage: {} - Reloads Alternate Advancement Data globally", - aa_link - ).c_str() - ); - - auto alternate_currencies_link = Saylink::Silent("#reload alternate_currencies"); - - Message( - Chat::White, - fmt::format( - "Usage: {} - Reloads Alternate Currencies globally", - alternate_currencies_link - ).c_str() - ); - - auto base_data_link = Saylink::Silent("#reload base_data"); - - Message( - Chat::White, - fmt::format( - "Usage: {} - Reloads Base Data globally", - base_data_link - ).c_str() - ); - - auto blocked_spells_link = Saylink::Silent("#reload blocked_spells"); - - Message( - Chat::White, - fmt::format( - "Usage: {} - Reloads Blocked Spells globally", - blocked_spells_link - ).c_str() - ); - - auto commands_link = Saylink::Silent("#reload commands"); - - Message( - Chat::White, - fmt::format( - "Usage: {} - Reloads Commands globally", - commands_link - ).c_str() - ); - - auto content_flags_link = Saylink::Silent("#reload content_flags"); - - Message( - Chat::White, - fmt::format( - "Usage: {} - Reloads Content Flags globally", - content_flags_link - ).c_str() - ); - - auto doors_link = Saylink::Silent("#reload doors"); - - Message( - Chat::White, - fmt::format( - "Usage: {} - Reloads Doors globally", - doors_link - ).c_str() - ); - - auto data_buckets_link = Saylink::Silent("#reload data_buckets_cache"); - - Message( - Chat::White, - fmt::format( - "Usage: {} - Reloads data buckets cache globally", - data_buckets_link - ).c_str() - ); - - auto dztemplates_link = Saylink::Silent("#reload dztemplates"); - - Message( - Chat::White, - fmt::format( - "Usage: {} - Reloads Dynamic Zone Templates globally", - dztemplates_link - ).c_str() - ); - - auto factions_link = Saylink::Silent("#reload factions"); - - Message( - Chat::White, - fmt::format( - "Usage: {} - Reloads Factions globally", - factions_link - ).c_str() - ); - - auto ground_spawns_link = Saylink::Silent("#reload ground_spawns"); - - Message( - Chat::White, - fmt::format( - "Usage: {} - Reloads Ground Spawns globally", - ground_spawns_link - ).c_str() - ); - - auto level_mods_link = Saylink::Silent("#reload level_mods"); - - Message( - Chat::White, - fmt::format( - "Usage: {} - Reloads Level Based Experience Modifiers globally", - level_mods_link - ).c_str() - ); - - auto logs_link = Saylink::Silent("#reload logs"); - - Message( - Chat::White, - fmt::format( - "Usage: {} - Reloads Log Settings globally", - logs_link - ).c_str() - ); - - auto loot_link = Saylink::Silent("#reload loot"); - - Message( - Chat::White, - fmt::format( - "Usage: {} - Reloads Loot globally", - loot_link - ).c_str() - ); - - auto merchants_link = Saylink::Silent("#reload merchants"); - - Message( - Chat::White, - fmt::format( - "Usage: {} - Reloads Merchants globally", - merchants_link - ).c_str() - ); - - auto npc_emotes_link = Saylink::Silent("#reload npc_emotes"); - - Message( - Chat::White, - fmt::format( - "Usage: {} - Reloads NPC Emotes globally", - npc_emotes_link - ).c_str() - ); - - auto npc_spells_link = Saylink::Silent("#reload npc_spells"); - Message( - Chat::White, - fmt::format( - "Usage: {} - Reloads NPC Spells globally", - npc_spells_link - ).c_str() - ); - - auto objects_link = Saylink::Silent("#reload objects"); - - Message( - Chat::White, - fmt::format( - "Usage: {} - Reloads Objects globally", - objects_link - ).c_str() - ); - - auto opcodes_link = Saylink::Silent("#reload opcodes"); - - Message( - Chat::White, - fmt::format( - "Usage: {} - Reloads Opcodes globally", - opcodes_link - ).c_str() - ); - - auto perl_export_link = Saylink::Silent("#reload perl_export"); - - Message( - Chat::White, - fmt::format( - "Usage: {} - Reloads Perl Event Export Settings globally", - perl_export_link - ).c_str() - ); - - auto quest_link_one = Saylink::Silent("#reload quest"); - auto quest_link_two = Saylink::Silent("#reload quest", "0"); - auto quest_link_three = Saylink::Silent("#reload quest 1", "1"); - - Message( - Chat::White, - fmt::format( - "Usage: {} [{}|{}] - Reloads Quests and Timers in your current zone if specified (0 = Do Not Reload Timers, 1 = Reload Timers)", - quest_link_one, - quest_link_two, - quest_link_three - ).c_str() - ); - - auto rules_link = Saylink::Silent("#reload rules"); - - Message( - Chat::White, - fmt::format( - "Usage: {} - Reloads Rules globally", - rules_link - ).c_str() - ); - - auto skill_caps_link = Saylink::Silent("#reload skill_caps"); - - Message( - Chat::White, - fmt::format( - "Usage: {} - Reloads Skill Caps globally", - skill_caps_link - ).c_str() - ); - - auto static_link = Saylink::Silent("#reload static"); - - Message( - Chat::White, - fmt::format( - "Usage: {} - Reloads Static Zone Data globally", - static_link - ).c_str() - ); - - auto tasks_link = Saylink::Silent("#reload tasks"); - - Message( - Chat::White, - fmt::format( - "Usage: {} [Task ID] - Reloads Tasks globally or by ID if specified", - tasks_link - ).c_str() - ); - - auto titles_link = Saylink::Silent("#reload titles"); - - Message( - Chat::White, - fmt::format( - "Usage: {} - Reloads Titles globally", - titles_link - ).c_str() - ); - - auto traps_link_one = Saylink::Silent("#reload traps"); - auto traps_link_two = Saylink::Silent("#reload traps", "0"); - auto traps_link_three = Saylink::Silent("#reload traps 1", "1"); - - Message( - Chat::White, - fmt::format( - "Usage: {} [{}|{}] - Reloads Traps in your current zone or globally if specified", - traps_link_one, - traps_link_two, - traps_link_three - ).c_str() - ); - - auto variables_link = Saylink::Silent("#reload variables"); - - Message( - Chat::White, - fmt::format( - "Usage: {} - Reloads Variables globally", - variables_link - ).c_str() - ); - - auto veteran_rewards_link = Saylink::Silent("#reload veteran_rewards"); - - Message( - Chat::White, - fmt::format( - "Usage: {} - Reloads Veteran Rewards globally", - veteran_rewards_link - ).c_str() - ); - - auto world_link_one = Saylink::Silent("#reload world"); - auto world_link_two = Saylink::Silent("#reload world", "0"); - auto world_link_three = Saylink::Silent("#reload world 1", "1"); - auto world_link_four = Saylink::Silent("#reload world 2", "2"); - - Message( - Chat::White, - fmt::format( - "Usage: {} [{}|{}|{}] - Reloads Quests and repops globally if specified (0 = No Repop, 1 = Repop, 2 = Force Repop)", - world_link_one, - world_link_two, - world_link_three, - world_link_four - ).c_str() - ); - - auto zone_link = Saylink::Silent("#reload zone"); - - Message( - Chat::White, - fmt::format( - "Usage: {} [Zone ID] [Version] - Reloads Zone configuration for your current zone, can load another Zone's configuration if specified", - zone_link - ).c_str() - ); - - auto zone_points_link = Saylink::Silent("#reload zone_points"); - - Message( - Chat::White, - fmt::format( - "Usage: {} - Reloads Zone Points globally", - zone_points_link - ).c_str() - ); + Message( + Chat::White, + fmt::format( + "Usage: [{}] [{}] #reload {} - Reloads {}", + reload_link, + reload_link_global, + reload_slug, + ServerReload::GetName(t) + ).c_str() + ); + } SendChatLineBreak(); } @@ -11986,7 +12114,7 @@ void Client::MaxSkills() auto current_skill_value = ( EQ::skills::IsSpecializedSkill(s.first) ? MAX_SPECIALIZED_SKILL : - skill_caps.GetSkillCap(GetClass(), s.first, GetLevel()).cap + SkillCaps::Instance()->GetSkillCap(GetClass(), s.first, GetLevel()).cap ); if (GetSkill(s.first) < current_skill_value) { @@ -12193,23 +12321,22 @@ void Client::PlayerTradeEventLog(Trade *t, Trade *t2) if (inst) { t_entries.emplace_back( PlayerEvent::TradeItemEntry{ - .slot = i, - .item_id = inst->GetItem()->ID, - .item_name = inst->GetItem()->Name, - .charges = static_cast(inst->GetCharges()), - .aug_1_item_id = inst->GetAugmentItemID(0), - .aug_1_item_name = inst->GetAugment(0) ? inst->GetAugment(0)->GetItem()->Name : "", - .aug_2_item_id = inst->GetAugmentItemID(1), - .aug_2_item_name = inst->GetAugment(1) ? inst->GetAugment(1)->GetItem()->Name : "", - .aug_3_item_id = inst->GetAugmentItemID(2), - .aug_3_item_name = inst->GetAugment(2) ? inst->GetAugment(2)->GetItem()->Name : "", - .aug_4_item_id = inst->GetAugmentItemID(3), - .aug_4_item_name = inst->GetAugment(3) ? inst->GetAugment(3)->GetItem()->Name : "", - .aug_5_item_id = inst->GetAugmentItemID(4), - .aug_5_item_name = inst->GetAugment(4) ? inst->GetAugment(4)->GetItem()->Name : "", - .aug_6_item_id = inst->GetAugmentItemID(5), - .aug_6_item_name = inst->GetAugment(5) ? inst->GetAugment(5)->GetItem()->Name : "", - .in_bag = false, + .slot = i, + .item_id = inst->GetItem()->ID, + .augment_1_id = inst->GetAugmentItemID(0), + .augment_1_name = inst->GetAugment(0) ? inst->GetAugment(0)->GetItem()->Name : "", + .augment_2_id = inst->GetAugmentItemID(1), + .augment_2_name = inst->GetAugment(1) ? inst->GetAugment(1)->GetItem()->Name : "", + .augment_3_id = inst->GetAugmentItemID(2), + .augment_3_name = inst->GetAugment(2) ? inst->GetAugment(2)->GetItem()->Name : "", + .augment_4_id = inst->GetAugmentItemID(3), + .augment_4_name = inst->GetAugment(3) ? inst->GetAugment(3)->GetItem()->Name : "", + .augment_5_id = inst->GetAugmentItemID(4), + .augment_5_name = inst->GetAugment(4) ? inst->GetAugment(4)->GetItem()->Name : "", + .augment_6_id = inst->GetAugmentItemID(5), + .augment_6_name = inst->GetAugment(5) ? inst->GetAugment(5)->GetItem()->Name : "",.item_name = inst->GetItem()->Name, + .charges = static_cast(inst->GetCharges()), + .in_bag = false, } ); @@ -12217,27 +12344,26 @@ void Client::PlayerTradeEventLog(Trade *t, Trade *t2) for (uint8 j = EQ::invbag::SLOT_BEGIN; j <= EQ::invbag::SLOT_END; j++) { inst = trader->GetInv().GetItem(i, j); if (inst) { - t_entries.emplace_back( - PlayerEvent::TradeItemEntry{ - .slot = j, - .item_id = inst->GetItem()->ID, - .item_name = inst->GetItem()->Name, - .charges = static_cast(inst->GetCharges()), - .aug_1_item_id = inst->GetAugmentItemID(0), - .aug_1_item_name = inst->GetAugment(0) ? inst->GetAugment(0)->GetItem()->Name : "", - .aug_2_item_id = inst->GetAugmentItemID(1), - .aug_2_item_name = inst->GetAugment(1) ? inst->GetAugment(1)->GetItem()->Name : "", - .aug_3_item_id = inst->GetAugmentItemID(2), - .aug_3_item_name = inst->GetAugment(2) ? inst->GetAugment(2)->GetItem()->Name : "", - .aug_4_item_id = inst->GetAugmentItemID(3), - .aug_4_item_name = inst->GetAugment(3) ? inst->GetAugment(3)->GetItem()->Name : "", - .aug_5_item_id = inst->GetAugmentItemID(4), - .aug_5_item_name = inst->GetAugment(4) ? inst->GetAugment(4)->GetItem()->Name : "", - .aug_6_item_id = inst->GetAugmentItemID(5), - .aug_6_item_name = inst->GetAugment(5) ? inst->GetAugment(5)->GetItem()->Name : "", - .in_bag = true, - } - ); + t_entries.emplace_back(PlayerEvent::TradeItemEntry{ + .slot = j, + .item_id = inst->GetItem()->ID, + .augment_1_id = inst->GetAugmentItemID(0), + .augment_1_name = inst->GetAugment(0) ? inst->GetAugment(0)->GetItem()->Name : "", + .augment_2_id = inst->GetAugmentItemID(1), + .augment_2_name = inst->GetAugment(1) ? inst->GetAugment(1)->GetItem()->Name : "", + .augment_3_id = inst->GetAugmentItemID(2), + .augment_3_name = inst->GetAugment(2) ? inst->GetAugment(2)->GetItem()->Name : "", + .augment_4_id = inst->GetAugmentItemID(3), + .augment_4_name = inst->GetAugment(3) ? inst->GetAugment(3)->GetItem()->Name : "", + .augment_5_id = inst->GetAugmentItemID(4), + .augment_5_name = inst->GetAugment(4) ? inst->GetAugment(4)->GetItem()->Name : "", + .augment_6_id = inst->GetAugmentItemID(5), + .augment_6_name = inst->GetAugment(5) ? inst->GetAugment(5)->GetItem()->Name : "", + .item_name = inst->GetItem()->Name, + .charges = static_cast(inst->GetCharges()), + .in_bag = true, + } + ); } } } @@ -12251,52 +12377,50 @@ void Client::PlayerTradeEventLog(Trade *t, Trade *t2) for (uint16 i = EQ::invslot::TRADE_BEGIN; i <= EQ::invslot::TRADE_END; i++) { const EQ::ItemInstance *inst = trader2->GetInv().GetItem(i); if (inst) { - t2_entries.emplace_back( - PlayerEvent::TradeItemEntry{ - .slot = i, - .item_id = inst->GetItem()->ID, - .item_name = inst->GetItem()->Name, - .charges = static_cast(inst->GetCharges()), - .aug_1_item_id = inst->GetAugmentItemID(0), - .aug_1_item_name = inst->GetAugment(0) ? inst->GetAugment(0)->GetItem()->Name : "", - .aug_2_item_id = inst->GetAugmentItemID(1), - .aug_2_item_name = inst->GetAugment(1) ? inst->GetAugment(1)->GetItem()->Name : "", - .aug_3_item_id = inst->GetAugmentItemID(2), - .aug_3_item_name = inst->GetAugment(2) ? inst->GetAugment(2)->GetItem()->Name : "", - .aug_4_item_id = inst->GetAugmentItemID(3), - .aug_4_item_name = inst->GetAugment(3) ? inst->GetAugment(3)->GetItem()->Name : "", - .aug_5_item_id = inst->GetAugmentItemID(4), - .aug_5_item_name = inst->GetAugment(4) ? inst->GetAugment(4)->GetItem()->Name : "", - .aug_6_item_id = inst->GetAugmentItemID(5), - .aug_6_item_name = inst->GetAugment(5) ? inst->GetAugment(5)->GetItem()->Name : "", - .in_bag = false, - } + t2_entries.emplace_back(PlayerEvent::TradeItemEntry{ + .slot = i, + .item_id = inst->GetItem()->ID, + .augment_1_id = inst->GetAugmentItemID(0), + .augment_1_name = inst->GetAugment(0) ? inst->GetAugment(0)->GetItem()->Name : "", + .augment_2_id = inst->GetAugmentItemID(1), + .augment_2_name = inst->GetAugment(1) ? inst->GetAugment(1)->GetItem()->Name : "", + .augment_3_id = inst->GetAugmentItemID(2), + .augment_3_name = inst->GetAugment(2) ? inst->GetAugment(2)->GetItem()->Name : "", + .augment_4_id = inst->GetAugmentItemID(3), + .augment_4_name = inst->GetAugment(3) ? inst->GetAugment(3)->GetItem()->Name : "", + .augment_5_id = inst->GetAugmentItemID(4), + .augment_5_name = inst->GetAugment(4) ? inst->GetAugment(4)->GetItem()->Name : "", + .augment_6_id = inst->GetAugmentItemID(5), + .augment_6_name = inst->GetAugment(5) ? inst->GetAugment(5)->GetItem()->Name : "", + .item_name = inst->GetItem()->Name, + .charges = static_cast(inst->GetCharges()), + .in_bag = false, + } ); if (inst->IsClassBag()) { for (uint8 j = EQ::invbag::SLOT_BEGIN; j <= EQ::invbag::SLOT_END; j++) { inst = trader2->GetInv().GetItem(i, j); if (inst) { - t2_entries.emplace_back( - PlayerEvent::TradeItemEntry{ - .slot = j, - .item_id = inst->GetItem()->ID, - .item_name = inst->GetItem()->Name, - .charges = static_cast(inst->GetCharges()), - .aug_1_item_id = inst->GetAugmentItemID(0), - .aug_1_item_name = inst->GetAugment(0) ? inst->GetAugment(0)->GetItem()->Name : "", - .aug_2_item_id = inst->GetAugmentItemID(1), - .aug_2_item_name = inst->GetAugment(1) ? inst->GetAugment(1)->GetItem()->Name : "", - .aug_3_item_id = inst->GetAugmentItemID(2), - .aug_3_item_name = inst->GetAugment(2) ? inst->GetAugment(2)->GetItem()->Name : "", - .aug_4_item_id = inst->GetAugmentItemID(3), - .aug_4_item_name = inst->GetAugment(3) ? inst->GetAugment(3)->GetItem()->Name : "", - .aug_5_item_id = inst->GetAugmentItemID(4), - .aug_5_item_name = inst->GetAugment(4) ? inst->GetAugment(4)->GetItem()->Name : "", - .aug_6_item_id = inst->GetAugmentItemID(5), - .aug_6_item_name = inst->GetAugment(5) ? inst->GetAugment(5)->GetItem()->Name : "", - .in_bag = true, - } + t2_entries.emplace_back(PlayerEvent::TradeItemEntry{ + .slot = j, + .item_id = inst->GetItem()->ID, + .augment_1_id = inst->GetAugmentItemID(0), + .augment_1_name = inst->GetAugment(0) ? inst->GetAugment(0)->GetItem()->Name : "", + .augment_2_id = inst->GetAugmentItemID(1), + .augment_2_name = inst->GetAugment(1) ? inst->GetAugment(1)->GetItem()->Name : "", + .augment_3_id = inst->GetAugmentItemID(2), + .augment_3_name = inst->GetAugment(2) ? inst->GetAugment(2)->GetItem()->Name : "", + .augment_4_id = inst->GetAugmentItemID(3), + .augment_4_name = inst->GetAugment(3) ? inst->GetAugment(3)->GetItem()->Name : "", + .augment_5_id = inst->GetAugmentItemID(4), + .augment_5_name = inst->GetAugment(4) ? inst->GetAugment(4)->GetItem()->Name : "", + .augment_6_id = inst->GetAugmentItemID(5), + .augment_6_name = inst->GetAugment(5) ? inst->GetAugment(5)->GetItem()->Name : "", + .item_name = inst->GetItem()->Name, + .charges = static_cast(inst->GetCharges()), + .in_bag = true, + } ); } } @@ -12317,251 +12441,10 @@ void Client::PlayerTradeEventLog(Trade *t, Trade *t2) }; RecordPlayerEventLogWithClient(trader, PlayerEvent::TRADE, e); + //Not sure the usefulness of sending the same data twice?? RecordPlayerEventLogWithClient(trader2, PlayerEvent::TRADE, e); } -void Client::NPCHandinEventLog(Trade* t, NPC* n) -{ - Client* c = t->GetOwner()->CastToClient(); - - std::vector hi = {}; - std::vector ri = {}; - PlayerEvent::HandinMoney hm{}; - PlayerEvent::HandinMoney rm{}; - - if ( - c->EntityVariableExists("HANDIN_ITEMS") && - c->EntityVariableExists("HANDIN_MONEY") && - c->EntityVariableExists("RETURN_ITEMS") && - c->EntityVariableExists("RETURN_MONEY") - ) { - const std::string& handin_items = c->GetEntityVariable("HANDIN_ITEMS"); - const std::string& return_items = c->GetEntityVariable("RETURN_ITEMS"); - const std::string& handin_money = c->GetEntityVariable("HANDIN_MONEY"); - const std::string& return_money = c->GetEntityVariable("RETURN_MONEY"); - - // Handin Items - if (!handin_items.empty()) { - if (Strings::Contains(handin_items, ",")) { - const auto handin_data = Strings::Split(handin_items, ","); - for (const auto& h : handin_data) { - const auto item_data = Strings::Split(h, "|"); - if ( - item_data.size() == 3 && - Strings::IsNumber(item_data[0]) && - Strings::IsNumber(item_data[1]) && - Strings::IsNumber(item_data[2]) - ) { - const uint32 item_id = Strings::ToUnsignedInt(item_data[0]); - if (item_id != 0) { - const auto* item = database.GetItem(item_id); - - if (item) { - hi.emplace_back( - PlayerEvent::HandinEntry{ - .item_id = item_id, - .item_name = item->Name, - .charges = static_cast(Strings::ToUnsignedInt(item_data[1])), - .attuned = Strings::ToInt(item_data[2]) ? true : false - } - ); - } - } - } - } - } else if (Strings::Contains(handin_items, "|")) { - const auto item_data = Strings::Split(handin_items, "|"); - if ( - item_data.size() == 3 && - Strings::IsNumber(item_data[0]) && - Strings::IsNumber(item_data[1]) && - Strings::IsNumber(item_data[2]) - ) { - const uint32 item_id = Strings::ToUnsignedInt(item_data[0]); - const auto* item = database.GetItem(item_id); - - if (item) { - hi.emplace_back( - PlayerEvent::HandinEntry{ - .item_id = item_id, - .item_name = item->Name, - .charges = static_cast(Strings::ToUnsignedInt(item_data[1])), - .attuned = Strings::ToInt(item_data[2]) ? true : false - } - ); - } - } - } - } - - // Handin Money - if (!handin_money.empty()) { - const auto hms = Strings::Split(handin_money, "|"); - - hm.copper = Strings::ToUnsignedInt(hms[0]); - hm.silver = Strings::ToUnsignedInt(hms[1]); - hm.gold = Strings::ToUnsignedInt(hms[2]); - hm.platinum = Strings::ToUnsignedInt(hms[3]); - } - - // Return Items - if (!return_items.empty()) { - if (Strings::Contains(return_items, ",")) { - const auto return_data = Strings::Split(return_items, ","); - for (const auto& r : return_data) { - const auto item_data = Strings::Split(r, "|"); - if ( - item_data.size() == 3 && - Strings::IsNumber(item_data[0]) && - Strings::IsNumber(item_data[1]) && - Strings::IsNumber(item_data[2]) - ) { - const uint32 item_id = Strings::ToUnsignedInt(item_data[0]); - const auto* item = database.GetItem(item_id); - - if (item) { - ri.emplace_back( - PlayerEvent::HandinEntry{ - .item_id = item_id, - .item_name = item->Name, - .charges = static_cast(Strings::ToUnsignedInt(item_data[1])), - .attuned = Strings::ToInt(item_data[2]) ? true : false - } - ); - } - } - } - } else if (Strings::Contains(return_items, "|")) { - const auto item_data = Strings::Split(return_items, "|"); - if ( - item_data.size() == 3 && - Strings::IsNumber(item_data[0]) && - Strings::IsNumber(item_data[1]) && - Strings::IsNumber(item_data[2]) - ) { - const uint32 item_id = Strings::ToUnsignedInt(item_data[0]); - const auto* item = database.GetItem(item_id); - - if (item) { - ri.emplace_back( - PlayerEvent::HandinEntry{ - .item_id = item_id, - .item_name = item->Name, - .charges = static_cast(Strings::ToUnsignedInt(item_data[1])), - .attuned = Strings::ToInt(item_data[2]) ? true : false - } - ); - } - } - } - } - - // Return Money - if (!return_money.empty()) { - const auto rms = Strings::Split(return_money, "|"); - rm.copper = static_cast(Strings::ToUnsignedInt(rms[0])); - rm.silver = static_cast(Strings::ToUnsignedInt(rms[1])); - rm.gold = static_cast(Strings::ToUnsignedInt(rms[2])); - rm.platinum = static_cast(Strings::ToUnsignedInt(rms[3])); - } - - c->DeleteEntityVariable("HANDIN_ITEMS"); - c->DeleteEntityVariable("HANDIN_MONEY"); - c->DeleteEntityVariable("RETURN_ITEMS"); - c->DeleteEntityVariable("RETURN_MONEY"); - - const bool handed_in_money = hm.platinum > 0 || hm.gold > 0 || hm.silver > 0 || hm.copper > 0; - - const bool event_has_data_to_record = ( - !hi.empty() || handed_in_money - ); - - if (player_event_logs.IsEventEnabled(PlayerEvent::NPC_HANDIN) && event_has_data_to_record) { - auto e = PlayerEvent::HandinEvent{ - .npc_id = n->GetNPCTypeID(), - .npc_name = n->GetCleanName(), - .handin_items = hi, - .handin_money = hm, - .return_items = ri, - .return_money = rm, - .is_quest_handin = true - }; - - RecordPlayerEventLogWithClient(c, PlayerEvent::NPC_HANDIN, e); - } - - return; - } - - uint8 item_count = 0; - - hm.platinum = t->pp; - hm.gold = t->gp; - hm.silver = t->sp; - hm.copper = t->cp; - - for (uint16 i = EQ::invslot::TRADE_BEGIN; i <= EQ::invslot::TRADE_NPC_END; i++) { - if (c->GetInv().GetItem(i)) { - item_count++; - } - } - - hi.reserve(item_count); - - if (item_count > 0) { - for (uint16 i = EQ::invslot::TRADE_BEGIN; i <= EQ::invslot::TRADE_NPC_END; i++) { - const EQ::ItemInstance* inst = c->GetInv().GetItem(i); - if (inst) { - hi.emplace_back( - PlayerEvent::HandinEntry{ - .item_id = inst->GetItem()->ID, - .item_name = inst->GetItem()->Name, - .charges = static_cast(inst->GetCharges()), - .attuned = inst->IsAttuned() - } - ); - - if (inst->IsClassBag()) { - for (uint8 j = EQ::invbag::SLOT_BEGIN; j <= EQ::invbag::SLOT_END; j++) { - inst = c->GetInv().GetItem(i, j); - if (inst) { - hi.emplace_back( - PlayerEvent::HandinEntry{ - .item_id = inst->GetItem()->ID, - .item_name = inst->GetItem()->Name, - .charges = static_cast(inst->GetCharges()), - .attuned = inst->IsAttuned() - } - ); - } - } - } - } - } - } - - const bool handed_in_money = hm.platinum > 0 || hm.gold > 0 || hm.silver > 0 || hm.copper > 0; - - ri = hi; - rm = hm; - - const bool event_has_data_to_record = !hi.empty() || handed_in_money; - - if (player_event_logs.IsEventEnabled(PlayerEvent::NPC_HANDIN) && event_has_data_to_record) { - auto e = PlayerEvent::HandinEvent{ - .npc_id = n->GetNPCTypeID(), - .npc_name = n->GetCleanName(), - .handin_items = hi, - .handin_money = hm, - .return_items = ri, - .return_money = rm, - .is_quest_handin = false - }; - - RecordPlayerEventLogWithClient(c, PlayerEvent::NPC_HANDIN, e); - } -} - void Client::ShowSpells(Client* c, ShowSpellType show_spell_type) { std::string spell_string; @@ -12820,144 +12703,34 @@ uint16 Client::GetSkill(EQ::skills::SkillType skill_id) const void Client::RemoveItemBySerialNumber(uint32 serial_number, uint32 quantity) { EQ::ItemInstance *item = nullptr; - static const int16 slots[][2] = { - { EQ::invslot::POSSESSIONS_BEGIN, EQ::invslot::POSSESSIONS_END }, - { EQ::invbag::GENERAL_BAGS_BEGIN, EQ::invbag::GENERAL_BAGS_END }, - { EQ::invbag::CURSOR_BAG_BEGIN, EQ::invbag::CURSOR_BAG_END}, - { EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END }, - { EQ::invslot::GUILD_TRIBUTE_BEGIN, EQ::invslot::GUILD_TRIBUTE_END }, - { EQ::invbag::BANK_BAGS_BEGIN, EQ::invbag::BANK_BAGS_END }, - { EQ::invslot::SHARED_BANK_BEGIN, EQ::invslot::SHARED_BANK_END }, - { EQ::invbag::SHARED_BANK_BAGS_BEGIN, EQ::invbag::SHARED_BANK_BAGS_END }, - }; - int16 removed_count = 0; - const size_t slot_index_count = sizeof(slots) / sizeof(slots[0]); - for (int slot_index = 0; slot_index < slot_index_count; ++slot_index) { - for (int slot_id = slots[slot_index][0]; slot_id <= slots[slot_index][1]; ++slot_id) { - if (removed_count == quantity) { - break; - } - item = GetInv().GetItem(slot_id); - if (item && item->GetSerialNumber() == serial_number) { - int16 charges = item->IsStackable() ? item->GetCharges() : 0; - int16 stack_size = std::max(charges, static_cast(1)); - if ((removed_count + stack_size) <= quantity) { - removed_count += stack_size; - DeleteItemInInventory(slot_id, charges, true); - } else { - int16 amount_left = (quantity - removed_count); - if (amount_left > 0 && stack_size >= amount_left) { - removed_count += amount_left; - DeleteItemInInventory(slot_id, amount_left, true); - } + uint32 removed_count = 0; + + const auto& slot_ids = GetInventorySlots(); + + for (const int16& slot_id : slot_ids) { + if (removed_count == quantity) { + break; + } + + item = GetInv().GetItem(slot_id); + if (item && item->GetSerialNumber() == serial_number) { + uint32 charges = item->IsStackable() ? item->GetCharges() : 0; + uint32 stack_size = std::max(charges, static_cast(1)); + if ((removed_count + stack_size) <= quantity) { + removed_count += stack_size; + DeleteItemInInventory(slot_id, charges, true); + } else { + uint32 amount_left = (quantity - removed_count); + if (amount_left > 0 && stack_size >= amount_left) { + removed_count += amount_left; + DeleteItemInInventory(slot_id, amount_left, true); } } } } } -void Client::AddMoneyToPPWithOverflow(uint64 copper, bool update_client) -{ - //I noticed in the ROF2 client that the client auto updates the currency values using overflow - //Therefore, I created this method to ensure that the db matches and clients don't see 10 pp 5 gp - //becoming 9pp 15 gold with the current AddMoneyToPP method. - - auto add_pp = copper / 1000; - auto add_gp = (copper - add_pp * 1000) / 100; - auto add_sp = (copper - add_pp * 1000 - add_gp * 100) / 10; - auto add_cp = copper - add_pp * 1000 - add_gp * 100 - add_sp * 10; - - m_pp.copper += add_cp; - if (m_pp.copper >= 10) { - m_pp.silver += m_pp.copper / 10; - m_pp.copper = m_pp.copper % 10; - } - - m_pp.silver += add_sp; - if (m_pp.silver >= 10) { - m_pp.gold += m_pp.silver / 10; - m_pp.silver = m_pp.silver % 10; - } - - m_pp.gold += add_gp; - if (m_pp.gold >= 10) { - m_pp.platinum += m_pp.gold / 10; - m_pp.gold = m_pp.gold % 10; - } - - m_pp.platinum += add_pp; - - if (update_client) { - SendMoneyUpdate(); - } - - RecalcWeight(); - SaveCurrency(); - - LogDebug("Client::AddMoneyToPPWithOverflow() [{}] should have: plat:[{}] gold:[{}] silver:[{}] copper:[{}]", - GetName(), - m_pp.platinum, - m_pp.gold, - m_pp.silver, - m_pp.copper - ); -} - -bool Client::TakeMoneyFromPPWithOverFlow(uint64 copper, bool update_client) -{ - int32 remove_pp = copper / 1000; - int32 remove_gp = (copper - remove_pp * 1000) / 100; - int32 remove_sp = (copper - remove_pp * 1000 - remove_gp * 100) / 10; - int32 remove_cp = copper - remove_pp * 1000 - remove_gp * 100 - remove_sp * 10; - - uint64 current_money = GetCarriedMoney(); - - if (copper > current_money) { - return false; //client does not have enough money on them - } - - m_pp.copper -= remove_cp; - if (m_pp.copper < 0) { - m_pp.silver -= 1; - m_pp.copper = m_pp.copper + 10; - if (m_pp.copper >= 10) { - m_pp.silver += m_pp.copper / 10; - m_pp.copper = m_pp.copper % 10; - } - } - - m_pp.silver -= remove_sp; - if (m_pp.silver < 0) { - m_pp.gold -= 1; - m_pp.silver = m_pp.silver + 10; - if (m_pp.silver >= 10) { - m_pp.gold += m_pp.silver / 10; - m_pp.silver = m_pp.silver % 10; - } - } - - m_pp.gold -= remove_gp; - if (m_pp.gold < 0) { - m_pp.platinum -= 1; - m_pp.gold = m_pp.gold + 10; - if (m_pp.gold >= 10) { - m_pp.platinum += m_pp.gold / 10; - m_pp.gold = m_pp.gold % 10; - } - } - - m_pp.platinum -= remove_pp; - - if (update_client) { - SendMoneyUpdate(); - } - - SaveCurrency(); - RecalcWeight(); - return true; -} - void Client::SendTopLevelInventory() { EQ::ItemInstance* inst = nullptr; @@ -12981,50 +12754,87 @@ void Client::SendTopLevelInventory() } } -// On a normal basis we limit mob movement updates based on distance -// This ensures we send a periodic full zone update to a client that has started moving after 5 or so minutes -// -// For very large zones we will also force a full update based on distance -// -// We ignore a small distance around us so that we don't interrupt already pathing deltas as those npcs will appear -// to full stop when they are actually still pathing -void Client::CheckSendBulkClientPositionUpdate() +void Client::CheckSendBulkNpcPositions(bool force) { float distance_moved = DistanceNoZ(m_last_position_before_bulk_update, GetPosition()); - bool moved_far_enough_before_bulk_update = distance_moved >= zone->GetNpcPositionUpdateDistance(); + float update_range = RuleI(Range, MobCloseScanDistance); + bool moved_far_enough_before_bulk_update = distance_moved >= update_range; bool is_ready_to_update = ( - m_client_zone_wide_full_position_update_timer.Check() || moved_far_enough_before_bulk_update + m_client_bulk_npc_pos_update_timer.Check() || moved_far_enough_before_bulk_update ); - if (IsMoving() && is_ready_to_update) { - LogDebug("[[{}]] Client Zone Wide Position Update NPCs", GetCleanName()); - + int updated_count = 0; + int skipped_count = 0; + if (is_ready_to_update || force) { auto &mob_movement_manager = MobMovementManager::Get(); - auto &mob_list = entity_list.GetMobList(); - for (auto &it : mob_list) { - Mob *entity = it.second; - if (!entity->IsNPC()) { + for (auto &e: entity_list.GetMobList()) { + Mob *mob = e.second; + if (!mob->IsNPC()) { continue; } int animation_speed = 0; - if (entity->IsMoving()) { - if (entity->IsRunning()) { - animation_speed = (entity->IsFeared() ? entity->GetFearSpeed() : entity->GetRunspeed()); + if (mob->IsMoving()) { + if (mob->IsRunning()) { + animation_speed = (mob->IsFeared() ? mob->GetFearSpeed() : mob->GetRunspeed()); } else { - animation_speed = entity->GetWalkspeed(); + animation_speed = mob->GetWalkspeed(); } } - mob_movement_manager.SendCommandToClients(entity, 0.0, 0.0, 0.0, 0.0, animation_speed, ClientRangeAny, this); + // if we have seen this mob before, and it hasn't moved, skip it + if (m_last_seen_mob_position.contains(mob->GetID())) { + if (m_last_seen_mob_position[mob->GetID()] == mob->GetPosition()) { + LogPositionUpdateDetail( + "Mob [{}] has already been sent to client [{}] at this position, skipping", + mob->GetCleanName(), + GetCleanName() + ); + skipped_count++; + continue; + } + } + + mob_movement_manager.SendCommandToClients( + mob, + 0.0, + 0.0, + 0.0, + 0.0, + animation_speed, + ClientRangeAny, + this + ); + + updated_count++; } + if (force) { + static EQApplicationPacket p(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); + auto *s = (PlayerPositionUpdateServer_Struct *) p.pBuffer; + for (auto &e: entity_list.GetCorpseList()) { + Corpse *c = e.second; + MakeSpawnUpdate(s); + QueuePacket(&p, false); + } + } + + LogPositionUpdate( + "[{}] Sent [{}] bulk updated NPC positions, skipped [{}] distance_moved [{}] update_range [{}]", + GetCleanName(), + updated_count, + skipped_count, + distance_moved, + update_range + ); + m_last_position_before_bulk_update = GetPosition(); } } + const uint16 scan_npc_aggro_timer_idle = RuleI(Aggro, ClientAggroCheckIdleInterval); const uint16 scan_npc_aggro_timer_moving = RuleI(Aggro, ClientAggroCheckMovingInterval); @@ -13056,8 +12866,8 @@ void Client::ClientToNpcAggroProcess() { if (zone->CanDoCombat() && !GetFeigned() && m_client_npc_aggro_scan_timer.Check()) { int npc_scan_count = 0; - for (auto &close_mob: GetCloseMobList()) { - Mob *mob = close_mob.second; + for (auto& close_mob : GetCloseMobList()) { + Mob* mob = close_mob.second; if (!mob) { continue; } @@ -13075,3 +12885,400 @@ void Client::ClientToNpcAggroProcess() LogAggro("Checking Reverse Aggro (client->npc) scanned_npcs ([{}])", npc_scan_count); } } + +const std::vector& Client::GetInventorySlots() +{ + static const std::vector> slots = { + {EQ::invslot::POSSESSIONS_BEGIN, EQ::invslot::POSSESSIONS_END}, + {EQ::invbag::GENERAL_BAGS_BEGIN, EQ::invbag::GENERAL_BAGS_END}, + {EQ::invbag::CURSOR_BAG_BEGIN, EQ::invbag::CURSOR_BAG_END}, + {EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END}, + {EQ::invbag::BANK_BAGS_BEGIN, EQ::invbag::BANK_BAGS_END}, + {EQ::invslot::SHARED_BANK_BEGIN, EQ::invslot::SHARED_BANK_END}, + {EQ::invbag::SHARED_BANK_BAGS_BEGIN, EQ::invbag::SHARED_BANK_BAGS_END}, + }; + + static std::vector slot_ids; + + if (slot_ids.empty()) { + for (const auto &[begin, end]: slots) { + for (int16 slot_id = begin; slot_id <= end; ++slot_id) { + slot_ids.emplace_back(slot_id); + } + } + } + + return slot_ids; +} + +void Client::ShowZoneShardMenu() +{ + auto z = GetZone(GetZoneID()); + if (z && !z->shard_at_player_count) { + return; + } + + auto results = CharacterDataRepository::GetInstanceZonePlayerCounts(database, GetZoneID()); + LogZoning("Zone sharding results count [{}]", results.size()); + + if (results.empty()) { + Message(Chat::White, "No zone shards found."); + return; + } + + if (!results.empty()) { + Message(Chat::White, "Available Zone Shards:"); + } + + int number = 1; + for (auto &e: results) { + std::string teleport_link = Saylink::Silent( + fmt::format("#zoneshard {} {}", e.zone_id, (e.instance_id == 0 ? -1 : e.instance_id)), + "Teleport" + ); + + std::string yours; + if (e.zone_id == GetZoneID() && e.instance_id == GetInstanceID()) { + teleport_link = "Teleport"; + yours = " (Yours)"; + } + + Message( + Chat::White, fmt::format( + " --> [{}] #{} {} ({}) [{}/{}] players {}", + teleport_link, + number, + z->long_name, + e.instance_id, + e.player_count, + z->shard_at_player_count, + yours + ).c_str() + ); + number++; + } +} + +std::string Client::SendBotCommandHelpWindow(const BotCommandHelpParams& params) { + unsigned string_length = 0; + unsigned current_place = 0; + uint16 max_length = RuleI(Command, MaxHelpLineLength); // Line length before splitting + const std::string& header_color = "indian_red"; + const std::string& description_color = "light_grey"; + const std::string& description_color_secondary = "dark_orange"; + const std::string& example_color = "goldenrod"; + const std::string& example_color_secondary = "slate_blue"; + const std::string& option_color = "light_grey"; + const std::string& option_color_secondary = "slate_blue"; + const std::string& filler_line_color = "dark_grey"; + + std::string filler_line = "--------------------------------------------------------------------"; + std::string filler_dia = DialogueWindow::TableRow(DialogueWindow::TableCell(fmt::format("{}", DialogueWindow::ColorMessage(filler_line_color, filler_line)))); + std::string break_line = DialogueWindow::Break(); + std::string popup_text; + + if (!params.description.empty()) { + popup_text += GetCommandHelpHeader("[Description]", header_color); + popup_text += SplitCommandHelpText(params.description, description_color, max_length); + } + + if (!params.notes.empty()) { + popup_text += break_line + break_line; + popup_text += GetCommandHelpHeader("[Notes]", header_color); + popup_text += SplitCommandHelpText(params.notes, description_color_secondary, max_length); + } + + if (!params.example_format.empty()) { + popup_text += filler_dia; + popup_text += GetCommandHelpHeader("[Examples]", header_color); + popup_text += SplitCommandHelpText(params.example_format, example_color, max_length); + } + + if (!params.examples_one.empty()) { + popup_text += break_line + break_line; + popup_text += SplitCommandHelpText(params.examples_one, example_color, max_length, example_color_secondary); + } + + if (!params.examples_two.empty()) { + popup_text += SplitCommandHelpText(params.examples_two, example_color, max_length, example_color_secondary); + } + + if (!params.examples_three.empty()) { + popup_text += SplitCommandHelpText(params.examples_three, example_color, max_length, example_color_secondary); + } + + if (!params.options.empty()) { + popup_text += filler_dia; + popup_text += GetCommandHelpHeader("[Options]", header_color); + popup_text += SplitCommandHelpText(params.options, option_color, max_length); + } + + if (!params.options_one.empty()) { + popup_text += break_line + break_line; + popup_text += SplitCommandHelpText(params.options_one, option_color_secondary, max_length); + } + + if (!params.options_two.empty()) { + popup_text += SplitCommandHelpText(params.options_two, option_color_secondary, max_length); + } + + if (!params.options_three.empty()) { + popup_text += SplitCommandHelpText(params.options_three, option_color_secondary, max_length); + } + + if (!params.actionables.empty()) { + popup_text += filler_dia; + popup_text += GetCommandHelpHeader("[Actionables]", header_color); + popup_text += SplitCommandHelpText(params.actionables, description_color, max_length); + } + + popup_text = DialogueWindow::Table(popup_text); + + return popup_text; +} + +std::string Client::GetCommandHelpHeader(std::string msg, std::string color) { + std::string return_text = DialogueWindow::TableRow( + DialogueWindow::TableCell( + fmt::format( + "{}", + DialogueWindow::ColorMessage(color, msg) + ) + ) + ); + + return return_text; +} + +std::string Client::SplitCommandHelpText(std::vector msg, std::string color, uint16 max_length, std::string secondary_color) { + std::string return_text; + + for (int i = 0; i < msg.size(); i++) { + std::vector msg_split; + int string_length = msg[i].length() + 1; + int end_count = 0; + int new_count = 0; + int split_count = 0; + + for (int x = 0; x < string_length; x = end_count) { + end_count = std::min(int(string_length), (int(x) + std::min(int(string_length), int(max_length)))); + + if ((string_length - (x + 1)) > max_length) { + for (int y = end_count; y >= x; --y) { + if (msg[i][y] == ' ') { + split_count = y - x; + msg_split.emplace_back(msg[i].substr(x, split_count)); + end_count = y + 1; + + break; + } + + if (y == x) { + msg_split.emplace_back(msg[i].substr(x, max_length)); + + break; + } + } + } + else { + msg_split.emplace_back(msg[i].substr(x, (string_length - 1) - x)); + + break; + } + } + + for (const auto& s : msg_split) { + return_text += DialogueWindow::TableRow( + DialogueWindow::TableCell(DialogueWindow::ColorMessage(((!secondary_color.empty() && i== 0) ? secondary_color : color), s)) + ); + } + } + + return return_text; +} + +void Client::SetAAEXPPercentage(uint8 percentage) +{ + const uint32 before_percentage = m_epp.perAA; + + if (before_percentage > 0 && percentage == 0) { + MessageString(Chat::White, AA_OFF); + } + else if (before_percentage == 0 && percentage > 0) { + MessageString(Chat::White, AA_ON); + } + + m_epp.perAA = EQ::Clamp(static_cast(percentage), 0, 100); + + SendAlternateAdvancementStats(); + SendAlternateAdvancementTable(); +} + +void Client::BroadcastPositionUpdate() +{ + EQApplicationPacket outapp(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); + PlayerPositionUpdateServer_Struct *spu = (PlayerPositionUpdateServer_Struct *) outapp.pBuffer; + + memset(spu, 0x00, sizeof(PlayerPositionUpdateServer_Struct)); + spu->spawn_id = GetID(); + spu->x_pos = FloatToEQ19(GetX()); + spu->y_pos = FloatToEQ19(GetY()); + spu->z_pos = FloatToEQ19(GetZ()); + spu->heading = FloatToEQ12(GetHeading()); + spu->delta_x = FloatToEQ13(0); + spu->delta_y = FloatToEQ13(0); + spu->delta_z = FloatToEQ13(0); + spu->delta_heading = FloatToEQ10(0); + spu->animation = 0; + + entity_list.QueueCloseClients(this, &outapp, true, zone->GetClientUpdateRange()); + + Group *g = GetGroup(); + if (g) { + for (auto &m: g->members) { + if (m && m->IsClient() && m != this) { + m->CastToClient()->QueuePacket(&outapp); + } + } + } +} + +std::string Client::GetAccountBucket(std::string bucket_name) +{ + DataBucketKey k = {}; + k.account_id = AccountID(); + k.key = bucket_name; + + return DataBucket::GetData(k).value; +} + +void Client::SetAccountBucket(std::string bucket_name, std::string bucket_value, std::string expiration) +{ + DataBucketKey k = {}; + k.account_id = AccountID(); + k.key = bucket_name; + k.expires = expiration; + k.value = bucket_value; + + DataBucket::SetData(k); +} + +void Client::DeleteAccountBucket(std::string bucket_name) +{ + DataBucketKey k = {}; + k.account_id = AccountID(); + k.key = bucket_name; + + DataBucket::DeleteData(k); +} + +std::string Client::GetAccountBucketExpires(std::string bucket_name) +{ + DataBucketKey k = {}; + k.account_id = AccountID(); + k.key = bucket_name; + + return DataBucket::GetDataExpires(k); +} + +std::string Client::GetAccountBucketRemaining(std::string bucket_name) +{ + DataBucketKey k = {}; + k.account_id = AccountID(); + k.key = bucket_name; + + return DataBucket::GetDataRemaining(k); +} + +std::string Client::GetBandolierName(uint8 bandolier_slot) +{ + if (!EQ::ValueWithin(bandolier_slot, 0, 3)) { + return std::string(); + } + + return GetPP().bandoliers[bandolier_slot].Name; +} + +uint32 Client::GetBandolierItemIcon(uint8 bandolier_slot, uint8 slot_id) +{ + if ( + !EQ::ValueWithin(bandolier_slot, 0, 3) || + !EQ::ValueWithin(slot_id, 0, 3) + ) { + return 0; + } + + return GetPP().bandoliers[bandolier_slot].Items[slot_id].Icon; +} + +uint32 Client::GetBandolierItemID(uint8 bandolier_slot, uint8 slot_id) +{ + if ( + !EQ::ValueWithin(bandolier_slot, 0, 3) || + !EQ::ValueWithin(slot_id, 0, 3) + ) { + return 0; + } + + return GetPP().bandoliers[bandolier_slot].Items[slot_id].ID; +} + +std::string Client::GetBandolierItemName(uint8 bandolier_slot, uint8 slot_id) +{ + if ( + !EQ::ValueWithin(bandolier_slot, 0, 3) || + !EQ::ValueWithin(slot_id, 0, 3) + ) { + return std::string(); + } + + return GetPP().bandoliers[bandolier_slot].Items[slot_id].Name; +} + +void Client::SendMerchantEnd() +{ + SetMerchantSessionEntityID(0); + + if (ClientVersion() == EQ::versions::ClientVersion::RoF2 && RuleB(Parcel, EnableParcelMerchants)) { + DoParcelCancel(); + SetEngagedWithParcelMerchant(false); + } + + EQApplicationPacket empty(OP_ShopEndConfirm); + QueuePacket(&empty); +} + +void Client::CheckItemDiscoverability(uint32 item_id) +{ + if (!RuleB(Character, EnableDiscoveredItems) || IsDiscovered(item_id)) { + return; + } + + if (GetGM()) { + const std::string& item_link = database.CreateItemLink(item_id); + Message( + Chat::White, + fmt::format( + "Your GM flag prevents {} from being added to discovered items.", + item_link + ).c_str() + ); + return; + } + + DiscoverItem(item_id); +} + +bool Client::UncompleteTask(int task_id) +{ + CompletedTasksRepository::DeleteWhere( + database, + fmt::format( + "charid = {} AND taskid = {}", + CharacterID(), + task_id + ) + ); + + return task_state->UncompleteTask(task_id); +} diff --git a/zone/client.h b/zone/client.h index 5acc388b3..d5e8f3ef4 100644 --- a/zone/client.h +++ b/zone/client.h @@ -21,8 +21,7 @@ class Client; class EQApplicationPacket; class DynamicZone; -class Expedition; -class ExpeditionLockoutTimer; +class DzLockout; class ExpeditionRequest; class Group; class NPC; @@ -32,6 +31,7 @@ class Seperator; class ServerPacket; struct DynamicZoneLocation; enum WaterRegionType : int; +enum class DynamicZoneMemberStatus; namespace EQ { @@ -72,6 +72,10 @@ namespace EQ #include "../common/repositories/trader_repository.h" #include "../common/guild_base.h" #include "../common/repositories/buyer_buy_lines_repository.h" +#include "../common/repositories/character_evolving_items_repository.h" +#include "../common/repositories/player_titlesets_repository.h" + +#include "bot_structs.h" #ifdef _WINDOWS // since windows defines these within windef.h (which windows.h include) @@ -94,7 +98,6 @@ namespace EQ #define MAX_SPECIALIZED_SKILL 50 extern Zone* zone; -extern TaskManager *task_manager; class CLIENTPACKET { @@ -196,6 +199,19 @@ struct RespawnOption float heading; }; +struct BotCommandHelpParams { + std::vector description = {}; + std::vector notes = {}; + std::vector example_format = {}; + std::vector examples_one = {}; + std::vector examples_two = {}; + std::vector examples_three = {}; + std::vector actionables = {}; + std::vector options = {}; + std::vector options_one = {}; + std::vector options_two = {}; + std::vector options_three = {}; +}; // do not ask what all these mean because I have no idea! // named from the client's CEverQuest::GetInnateDesc, they're missing some @@ -232,6 +248,13 @@ struct ClientReward uint32 amount; }; +struct ExpeditionInvite +{ + uint32_t dz_id; + std::string inviter_name; + std::string swap_name; +}; + class Client : public Mob { public: @@ -239,6 +262,7 @@ public: #include "client_packet.h" Client(EQStreamInterface * ieqs); + Client(); // mocking / testing ~Client(); void ReconnectUCS(); @@ -294,7 +318,7 @@ public: void SendBazaarDone(uint32 trader_id); void SendBulkBazaarTraders(); void SendBulkBazaarBuyers(); - void DoBazaarInspect(const BazaarInspect_Struct &in); + void DoBazaarInspect(BazaarInspect_Struct &in); void SendBazaarDeliveryCosts(); static std::string DetermineMoneyString(uint64 copper); @@ -302,10 +326,22 @@ public: void TraderStartTrader(const EQApplicationPacket *app); // void TraderPriceUpdate(const EQApplicationPacket *app); uint8 WithCustomer(uint16 NewCustomer); + std::vector GetKeyRing() { return keyring; } void KeyRingLoad(); - void KeyRingAdd(uint32 item_id); + bool KeyRingAdd(uint32 item_id); bool KeyRingCheck(uint32 item_id); - void KeyRingList(); + bool KeyRingClear(); + bool KeyRingRemove(uint32 item_id); + void KeyRingList(Client* c = nullptr); + bool IsNameChangeAllowed(); + void InvokeChangeNameWindow(bool immediate = true); + bool ClearNameChange(); + void GrantNameChange(); + bool IsPetNameChangeAllowed(); + void GrantPetNameChange(); + void ClearPetNameChange(); + void InvokeChangePetName(bool immediate = true); + bool ChangePetName(std::string new_name); bool IsClient() const override { return true; } bool IsOfClientBot() const override { return true; } bool IsOfClientBotMerc() const override { return true; } @@ -333,7 +369,6 @@ public: const char *message9 = nullptr); void Tell_StringID(uint32 string_id, const char *who, const char *message); void SendColoredText(uint32 color, std::string message); - void SendBazaarResults(uint32 trader_id, uint32 in_class, uint32 in_race, uint32 item_stat, uint32 item_slot, uint32 item_type, char item_name[64], uint32 min_price, uint32 max_price); void SendTraderItem(uint32 item_id,uint16 quantity, TraderRepository::Trader &trader); void DoBazaarSearch(BazaarSearchCriteria_Struct search_criteria); uint16 FindTraderItem(int32 SerialNumber,uint16 Quantity); @@ -374,6 +409,7 @@ public: void LoadParcels(); std::map GetParcels() { return m_parcels; } int32 FindNextFreeParcelSlot(uint32 char_id); + int32 FindNextFreeParcelSlotUsingMemory(); void SendParcelIconStatus(); void SendBecomeTraderToWorld(Client *trader, BazaarTraderBarterActions action); @@ -412,6 +448,8 @@ public: int64 ValidateBuyLineCost(std::map& 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(); } @@ -446,6 +484,9 @@ public: virtual bool Save() { return Save(0); } bool Save(uint8 iCommitNow); // 0 = delayed, 1=async now, 2=sync now + inline void SaveCharacterData() { + database.SaveCharacterData(this, &m_pp, &m_epp); + }; /* New PP Save Functions */ bool SaveCurrency(){ return database.SaveCharacterCurrency(this->CharacterID(), &m_pp); } @@ -460,24 +501,36 @@ public: void Kick(const std::string &reason); void WorldKick(); inline uint8 GetAnon() const { return m_pp.anon; } - inline uint8 GetAFK() const { return AFK; } + inline uint8 GetAFK() const { return m_is_afk; } void SetAnon(uint8 anon_flag); + inline Client* ResetAFKTimer() { + if (!RuleB(Character, EnableAutoAFK)) { + return this; + } + + m_afk_reset = true; + m_last_moved = std::chrono::steady_clock::now(); + return this; + }; void SetAFK(uint8 afk_flag); + inline bool IsIdle() { return m_is_idle; } inline PlayerProfile_Struct& GetPP() { return m_pp; } inline ExtendedProfile_Struct& GetEPP() { return m_epp; } inline EQ::InventoryProfile& GetInv() { return m_inv; } inline const EQ::InventoryProfile& GetInv() const { return m_inv; } + const std::vector& GetInventorySlots(); inline PetInfo* GetPetInfo(int pet_info_type) { return pet_info_type == PetInfoType::Suspended ? &m_suspendedminion : &m_petinfo; } inline InspectMessage_Struct& GetInspectMessage() { return m_inspect_message; } inline const InspectMessage_Struct& GetInspectMessage() const { return m_inspect_message; } void ReloadExpansionProfileSetting(); - void SetPetCommandState(int button, int state); + void SetPetCommandState(uint8 button, uint8 state); 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(); @@ -691,6 +744,8 @@ public: void SetAAEXPModifier(uint32 zone_id, float aa_modifier, int16 instance_version = -1); void SetEXPModifier(uint32 zone_id, float exp_modifier, int16 instance_version = -1); + void SetAAEXPPercentage(uint8 percentage); + bool UpdateLDoNPoints(uint32 theme_id, int points); void SetLDoNPoints(uint32 theme_id, uint32 points); void SetPVPPoints(uint32 Points) { m_pp.PVPCurrentPoints = Points; } @@ -707,13 +762,13 @@ public: void SendCrystalCounts(); uint64 GetExperienceForKill(Mob *against); - void AddEXP(ExpSource exp_source, uint64 in_add_exp, uint8 conlevel = 0xFF, bool resexp = false); + void AddEXP(ExpSource exp_source, uint64 in_add_exp, uint8 conlevel = 0xFF, bool resexp = false, NPC* npc = nullptr); uint64 CalcEXP(uint8 conlevel = 0xFF, bool ignore_mods = false); void CalculateNormalizedAAExp(uint64 &add_aaxp, uint8 conlevel, bool resexp); void CalculateStandardAAExp(uint64 &add_aaxp, uint8 conlevel, bool resexp); void CalculateLeadershipExp(uint64 &add_exp, uint8 conlevel); void CalculateExp(uint64 in_add_exp, uint64 &add_exp, uint64 &add_aaxp, uint8 conlevel, bool resexp); - void SetEXP(ExpSource exp_source, uint64 set_exp, uint64 set_aaxp, bool resexp = false); + void SetEXP(ExpSource exp_source, uint64 set_exp, uint64 set_aaxp, bool resexp = false, NPC* npc = nullptr); void AddLevelBasedExp(ExpSource exp_source, uint8 exp_percentage, uint8 max_level = 0, bool ignore_mods = false); void SetLeadershipEXP(uint64 group_exp, uint64 raid_exp); void AddLeadershipEXP(uint64 group_exp, uint64 raid_exp); @@ -855,11 +910,9 @@ public: void QuestReadBook(const char* text, uint8 type); void SendMoneyUpdate(); bool TakeMoneyFromPP(uint64 copper, bool update_client = false); - bool TakeMoneyFromPPWithOverFlow(uint64 copper, bool update_client); bool TakePlatinum(uint32 platinum, bool update_client = false); void AddMoneyToPP(uint64 copper, bool update_client = false); void AddMoneyToPP(uint32 copper, uint32 silver, uint32 gold, uint32 platinum, bool update_client = false); - void AddMoneyToPPWithOverflow(uint64 copper, bool update_client); void AddPlatinum(uint32 platinu, bool update_client = false); bool HasMoney(uint64 copper); uint64 GetCarriedMoney(); @@ -1078,7 +1131,6 @@ public: // Item methods void UseAugmentContainer(int container_slot); - void EVENT_ITEM_ScriptStopReturn(); uint32 NukeItem(uint32 itemnum, uint8 where_to_check = (invWhereWorn | invWherePersonal | invWhereBank | invWhereSharedBank | invWhereTrading | invWhereCursor)); void SetTint(int16 slot_id, uint32 color); @@ -1093,7 +1145,7 @@ public: bool PushItemOnCursor(const EQ::ItemInstance& inst, bool client_update = false); void SendCursorBuffer(); void DeleteItemInInventory(int16 slot_id, int16 quantity = 0, bool client_update = false, bool update_db = true); - int CountItem(uint32 item_id); + uint32 CountItem(uint32 item_id); void ResetItemCooldown(uint32 item_id); void SetItemCooldown(uint32 item_id, bool use_saved_timer = false, uint32 in_seconds = 1); uint32 GetItemCooldown(uint32 item_id); @@ -1101,7 +1153,6 @@ public: void RemoveItemBySerialNumber(uint32 serial_number, uint32 quantity = 1); bool SwapItem(MoveItem_Struct* move_in); void SwapItemResync(MoveItem_Struct* move_slots); - void QSSwapItemAuditor(MoveItem_Struct* move_in, bool postaction_call = false); void PutLootInInventory(int16 slot_id, const EQ::ItemInstance &inst, LootItem** bag_item_data = 0); bool AutoPutLootInInventory(EQ::ItemInstance& inst, bool try_worn = false, bool try_cursor = true, LootItem** bag_item_data = 0); bool SummonItem(uint32 item_id, int16 charges = -1, uint32 aug1 = 0, uint32 aug2 = 0, uint32 aug3 = 0, uint32 aug4 = 0, uint32 aug5 = 0, uint32 aug6 = 0, bool attuned = false, uint16 to_slot = EQ::invslot::slotCursor, uint32 ornament_icon = 0, uint32 ornament_idfile = 0, uint32 ornament_hero_model = 0); @@ -1110,7 +1161,6 @@ public: void SetStats(uint8 type,int16 set_val); void IncStats(uint8 type,int16 increase_val); void DropItem(int16 slot_id, bool recurse = true); - void DropItemQS(EQ::ItemInstance* inst, bool pickup); bool HasItemOnCorpse(uint32 item_id); bool IsAugmentRestricted(uint8 item_type, uint32 augment_restriction); @@ -1127,6 +1177,12 @@ public: void SetTrader(bool status) { trader = status; } uint16 GetTraderID() { return trader_id; } void SetTraderID(uint16 id) { trader_id = id; } + void SetTraderCount(uint32 no) { m_trader_count = no; } + uint32 GetTraderCount() { return m_trader_count; } + void IncrementTraderCount() { m_trader_count += 1; } + void DecrementTraderCount() { m_trader_count > 0 ? m_trader_count -= 1 : m_trader_count = 0; } + void SetTraderTransactionDate() { m_trader_transaction_date = time(nullptr); } + time_t GetTraderTransactionDate() { return m_trader_transaction_date; } eqFilterMode GetFilter(eqFilterType filter_id) const { return ClientFilters[filter_id]; } void SetFilter(eqFilterType filter_id, eqFilterMode filter_mode) { ClientFilters[filter_id] = filter_mode; } @@ -1162,7 +1218,7 @@ public: void Escape(); //keep or quest function void DisenchantSummonedBags(bool client_update = true); void RemoveNoRent(bool client_update = true); - void RemoveDuplicateLore(bool client_update = true); + void RemoveDuplicateLore(); void MoveSlotNotAllowed(bool client_update = true); virtual bool RangedAttack(Mob* other, bool CanDoubleAttack = false); virtual void ThrowingAttack(Mob* other, bool CanDoubleAttack = false); @@ -1210,9 +1266,10 @@ public: void ResetAllCastbarCooldowns(); void ResetCastbarCooldownBySpellID(uint32 spell_id); - bool CheckTitle(int titleset); - void EnableTitle(int titleset); - void RemoveTitle(int titleset); + bool CheckTitle(int title_set); + void EnableTitle(int title_set, bool insert = true); + const std::vector& GetTitles() { return m_player_title_sets; }; + void RemoveTitle(int title_set); void EnteringMessages(Client* client); void SendRules(); @@ -1250,7 +1307,36 @@ public: PendingTranslocate_Struct PendingTranslocateData; void SendOPTranslocateConfirm(Mob *Caster, uint16 SpellID); + // Help Window + std::string SendBotCommandHelpWindow(const BotCommandHelpParams& params); + std::string GetCommandHelpHeader(std::string msg, std::string color); + std::string SplitCommandHelpText(std::vector msg, std::string color, uint16 max_length, std::string secondary_color = ""); + void SendSpellTypePrompts(bool commanded_types = false, bool client_only_types = false); + // Task System Methods + inline void LoadClientSharedCompletedTasks() + { + std::string query = fmt::format(R"( + SELECT + cst.task_id + FROM completed_shared_task_members cstm + JOIN completed_shared_tasks cst ON cstm.shared_task_id = cst.id + WHERE cstm.character_id = {} + GROUP BY cst.task_id; + )", CharacterID()); + + auto results = database.QueryDatabase(query); + if (!results.Success()) { + return; + } + + m_completed_shared_tasks.clear(); + + for (auto row = results.begin(); row != results.end(); ++row) { + m_completed_shared_tasks.push_back(std::stoi(row[0])); + } + }; + inline std::vector GetCompletedSharedTasks() const { return m_completed_shared_tasks; }; void LoadClientTaskState(); void RemoveClientTaskState(); void SendTaskActivityComplete(int task_id, int activity_id, int task_index, TaskType task_type, int task_incomplete=1); @@ -1273,7 +1359,7 @@ public: } inline bool SaveTaskState() { - return task_manager != nullptr && task_manager->SaveClientState(this, task_state); + return TaskManager::Instance()->SaveClientState(this, task_state); } inline bool IsTaskStateLoaded() { return task_state != nullptr; } inline bool IsTaskActive(int task_id) { return task_state != nullptr && task_state->IsTaskActive(task_id); } @@ -1347,14 +1433,14 @@ public: } inline void TaskSetSelector(Mob* mob, int task_set_id, bool ignore_cooldown) { - if (task_manager && task_state) { - task_manager->TaskSetSelector(this, mob, task_set_id, ignore_cooldown); + if (task_state) { + TaskManager::Instance()->TaskSetSelector(this, mob, task_set_id, ignore_cooldown); } } inline void TaskQuestSetSelector(Mob* mob, const std::vector& tasks, bool ignore_cooldown) { - if (task_manager && task_state) { - task_manager->TaskQuestSetSelector(this, mob, tasks, ignore_cooldown); + if (task_state) { + TaskManager::Instance()->TaskQuestSetSelector(this, mob, tasks, ignore_cooldown); } } inline void EnableTask(int task_count, int *task_list) @@ -1415,13 +1501,17 @@ public: { return task_state ? task_state->CompleteTask(this, task_id) : false; } + bool UncompleteTask(int task_id); inline void FailTask(int task_id) { if (task_state) { task_state->FailTask(this, task_id); }} inline int TaskTimeLeft(int task_id) { return (task_state ? task_state->TaskTimeLeft(task_id) : 0); } inline int EnabledTaskCount(int task_set_id) { return (task_state ? task_state->EnabledTaskCount(task_set_id) : -1); } - inline bool IsTaskCompleted(int task_id) { return (task_state ? task_state->IsTaskCompleted(task_id) : false); } + inline bool IsTaskCompleted(int task_id) + { + return (task_state ? task_state->IsTaskCompleted(task_id, this) : false); + } inline bool AreTasksCompleted(std::vector task_ids) { return (task_state ? task_state->AreTasksCompleted(task_ids) : false); @@ -1534,32 +1624,24 @@ public: Client* client, const std::string& client_name, uint16_t chat_type, uint32_t string_id, const std::initializer_list& arguments = {}); - void AddExpeditionLockout(const ExpeditionLockoutTimer& lockout, bool update_db = false); - void AddExpeditionLockoutDuration(const std::string& expedition_name, - const std::string& event_Name, int seconds, const std::string& uuid = {}, bool update_db = false); - void AddNewExpeditionLockout(const std::string& expedition_name, - const std::string& event_name, uint32_t duration, std::string uuid = {}); - Expedition* CreateExpedition(DynamicZone& dz, bool disable_messages = false); - Expedition* CreateExpedition(const std::string& zone_name, - uint32 version, uint32 duration, const std::string& expedition_name, - uint32 min_players, uint32 max_players, bool disable_messages = false); - Expedition* CreateExpeditionFromTemplate(uint32_t dz_template_id); - Expedition* GetExpedition() const; - uint32 GetExpeditionID() const { return m_expedition_id; } - const ExpeditionLockoutTimer* GetExpeditionLockout( - const std::string& expedition_name, const std::string& event_name, bool include_expired = false) const; - const std::vector& GetExpeditionLockouts() const { return m_expedition_lockouts; }; - std::vector GetExpeditionLockouts(const std::string& expedition_name, bool include_expired = false); - uint32 GetPendingExpeditionInviteID() const { return m_pending_expedition_invite.expedition_id; } - bool HasExpeditionLockout(const std::string& expedition_name, const std::string& event_name, bool include_expired = false); - bool IsInExpedition() const { return m_expedition_id != 0; } - void RemoveAllExpeditionLockouts(const std::string& expedition_name, bool update_db = false); - void RemoveExpeditionLockout(const std::string& expedition_name, - const std::string& event_name, bool update_db = false); - void RequestPendingExpeditionInvite(); - void SendExpeditionLockoutTimers(); - void SetExpeditionID(uint32 expedition_id) { m_expedition_id = expedition_id; }; - void SetPendingExpeditionInvite(ExpeditionInvite&& invite) { m_pending_expedition_invite = invite; } + void AddDzLockout(const DzLockout& lockout, bool update_db = false); + void AddDzLockout(const std::string& expedition, const std::string& event, uint32_t duration, std::string uuid = {}); + void AddDzLockoutDuration(const DzLockout& lockout, int seconds, const std::string& uuid = {}, bool update_db = false); + DynamicZone* CreateExpedition(DynamicZone& dz, bool silent = false); + DynamicZone* CreateExpedition(uint32 zone_id, uint32 version, uint32 duration, const std::string& name, uint32 min_players, uint32 max_players, bool silent = false); + DynamicZone* CreateExpeditionFromTemplate(uint32_t dz_template_id); + DynamicZone* GetExpedition() const; + uint32 GetExpeditionID() const; + const DzLockout* GetDzLockout(const std::string& expedition, const std::string& event) const; + const std::vector& GetDzLockouts() const { return m_dz_lockouts; }; + std::vector GetDzLockouts(const std::string& expedition); + uint32 GetPendingDzInviteID() const { return m_dz_invite.dz_id; } + void SetPendingDzInvite(const ExpeditionInvite& invite) { m_dz_invite = invite; } + void RequestPendingDzInvite() const; + bool HasDzLockout(const std::string& expedition, const std::string& event) const; + void RemoveDzLockouts(const std::string& expedition, bool update_db = false); + void RemoveDzLockout(const std::string& expedition, const std::string& event, bool update_db = false); + void SendDzLockoutTimers(); void DzListTimers(); void SetDzRemovalTimer(bool enable_timer); void SendDzCompassUpdate(); @@ -1795,8 +1877,35 @@ public: uint32 trapid; //ID of trap player has triggered. This is cleared when the player leaves the trap's radius, or it despawns. + void SendMerchantEnd(); + + void CheckItemDiscoverability(uint32 item_id); + Raid *p_raid_instance; + inline uint32 GetPotionBeltItemIcon(uint8 slot_id) + { + return EQ::ValueWithin( + slot_id, + 0, + EQ::profile::POTION_BELT_SIZE - 1 + ) ? m_pp.potionbelt.Items[slot_id].Icon : 0; + }; + + inline uint32 GetPotionBeltItemID(uint8 slot_id) + { + return EQ::ValueWithin(slot_id, 0, EQ::profile::POTION_BELT_SIZE - 1) ? m_pp.potionbelt.Items[slot_id].ID : 0; + }; + + inline std::string GetPotionBeltItemName(uint8 slot_id) + { + return EQ::ValueWithin( + slot_id, + 0, + EQ::profile::POTION_BELT_SIZE - 1 + ) ? m_pp.potionbelt.Items[slot_id].Name : 0; + }; + void ShowDevToolsMenu(); CheatManager cheat_manager; @@ -1809,6 +1918,51 @@ public: void RecordKilledNPCEvent(NPC *n); uint32 GetEXPForLevel(uint16 check_level); + + // Evolving Item Info + void ProcessEvolvingItem(const uint64 exp, const Mob* mob); + void SendEvolvingPacket(int8 action, const CharacterEvolvingItemsRepository::CharacterEvolvingItems &item); + void DoEvolveItemToggle(const EQApplicationPacket* app); + void DoEvolveItemDisplayFinalResult(const EQApplicationPacket* app); + bool DoEvolveCheckProgression(EQ::ItemInstance &inst); + void SendEvolveXPWindowDetails(const EQApplicationPacket* app); + void DoEvolveTransferXP(const EQApplicationPacket* app); + void SendEvolveXPTransferWindow(); + void SendEvolveTransferResults(const EQ::ItemInstance &inst_from, const EQ::ItemInstance &inst_to, const EQ::ItemInstance &inst_from_new, const EQ::ItemInstance &inst_to_new, const uint32 compatibility, const uint32 max_transfer_level); + + // Account buckets + std::string GetAccountBucket(std::string bucket_name); + void SetAccountBucket(std::string bucket_name, std::string bucket_value, std::string expiration = ""); + void DeleteAccountBucket(std::string bucket_name); + std::string GetAccountBucketExpires(std::string bucket_name); + std::string GetAccountBucketRemaining(std::string bucket_name); + + std::string GetBandolierName(uint8 bandolier_slot); + uint32 GetBandolierItemIcon(uint8 bandolier_slot, uint8 slot_id); + uint32 GetBandolierItemID(uint8 bandolier_slot, uint8 slot_id); + std::string GetBandolierItemName(uint8 bandolier_slot, uint8 slot_id); + + // External handin tracking + // this is used to prevent things like quest::givecash and AddMoneyToPP + // from double giving money back to players in scripts when return_items + // also gives money back to players + struct ExternalHandinMoneyReturned { + uint64 copper; + uint64 silver; + uint64 gold; + uint64 platinum; + std::string return_source; + }; +private: + ExternalHandinMoneyReturned m_external_handin_money_returned = {}; + std::vector m_external_handin_items_returned = {}; +public: + ExternalHandinMoneyReturned GetExternalHandinMoneyReturned() { return m_external_handin_money_returned; } + std::vector GetExternalHandinItemsReturned() { return m_external_handin_items_returned; } + + // used only for testing + inline void SetCharacterId(uint32_t id) { character_id = id; } + protected: friend class Mob; void CalcEdibleBonuses(StatBonuses* newbon); @@ -1913,7 +2067,7 @@ private: bool GuildBanker; uint16 duel_target; bool duelaccepted; - std::list keyring; + std::vector keyring; bool tellsoff; // GM /toggle bool gm_hide_me; bool LFG; @@ -1922,7 +2076,8 @@ private: uint8 LFGToLevel; bool LFGMatchFilter; char LFGComments[64]; - bool AFK; + bool m_is_afk = false; + bool m_is_manual_afk = false; bool auto_attack; bool auto_fire; bool runmode; @@ -1942,9 +2097,12 @@ private: uint16 trader_id; uint16 customer_id; uint32 account_creation; - uint8 firstlogon; + bool first_login; + bool ingame; uint32 mercid; // current merc uint8 mercSlot; // selected merc slot + time_t m_trader_transaction_date; + uint32 m_trader_count{}; uint32 m_buyer_id; uint32 m_barter_time; int32 m_parcel_platinum; @@ -1967,6 +2125,8 @@ private: uint16 m_door_tool_entity_id; uint16 m_object_tool_entity_id; + + public: uint16 GetDoorToolEntityId() const; void SetDoorToolEntityId(uint16 door_tool_entity_id); @@ -2022,6 +2182,7 @@ private: PTimerList p_timers; //persistent timers Timer hpupdate_timer; Timer camp_timer; + Timer bot_camp_timer; Timer process_timer; Timer consume_food_timer; Timer zoneinpacket_timer; @@ -2064,12 +2225,18 @@ private: Timer m_client_npc_aggro_scan_timer; void CheckClientToNpcAggroTimer(); void ClientToNpcAggroProcess(); + void BroadcastPositionUpdate(); // bulk position updates glm::vec4 m_last_position_before_bulk_update; - Timer m_client_zone_wide_full_position_update_timer; + Timer m_client_bulk_npc_pos_update_timer; Timer m_position_update_timer; - void CheckSendBulkClientPositionUpdate(); + void CheckSendBulkNpcPositions(bool force = false); + + // afk + bool m_is_idle = false; + bool m_afk_reset = false; // used to trigger next-tic afk reset + std::chrono::steady_clock::time_point m_last_moved = std::chrono::steady_clock::now(); void BulkSendInventoryItems(); @@ -2113,6 +2280,7 @@ private: bool m_exp_enabled; std::vector m_exp_modifiers; + std::vector m_player_title_sets; //Anti Spam Stuff Timer *KarmaUpdateTimer; @@ -2179,13 +2347,14 @@ private: uint8 client_max_level; - uint32 m_expedition_id = 0; - ExpeditionInvite m_pending_expedition_invite { 0 }; - std::vector m_expedition_lockouts; + ExpeditionInvite m_dz_invite = {}; + std::vector m_dz_lockouts; glm::vec3 m_quest_compass; bool m_has_quest_compass = false; std::vector m_dynamic_zone_ids; + std::vector m_completed_shared_tasks; + public: enum BotOwnerOption : size_t { booDeathMarquee, @@ -2205,6 +2374,8 @@ public: bool GetBotPulling() { return m_bot_pulling; } void SetBotPulling(bool flag = true) { m_bot_pulling = flag; } + uint32 GetAssistee() { return bot_assistee; } + void SetAssistee(uint32 id = 0) { bot_assistee = id; } bool GetBotPrecombat() { return m_bot_precombat; } void SetBotPrecombat(bool flag = true) { m_bot_precombat = flag; } @@ -2219,15 +2390,37 @@ public: void CampAllBots(uint8 class_id = Class::None); void SpawnRaidBotsOnConnect(Raid* raid); + void LoadDefaultBotSettings(); + int GetDefaultBotSettings(uint8 setting_type, uint16 bot_setting); + int GetBotSetting(uint8 setting_type, uint16 bot_setting); + void SetBotSetting(uint8 setting_type, uint16 bot_setting, uint32 setting_value); + + uint16 GetDefaultSpellTypeDelay(uint16 spell_type); + uint8 GetDefaultSpellTypeMinThreshold(uint16 spell_type); + uint8 GetDefaultSpellTypeMaxThreshold(uint16 spell_type); + inline uint16 GetSpellTypeDelay(uint16 spell_type) const { return m_bot_spell_settings[spell_type].delay; } + inline void SetSpellTypeDelay(uint16 spell_type, uint16 delay_value) { m_bot_spell_settings[spell_type].delay = delay_value; } + inline uint8 GetSpellTypeMinThreshold(uint16 spell_type) const { return m_bot_spell_settings[spell_type].min_threshold; } + inline void SetSpellTypeMinThreshold(uint16 spell_type, uint8 threshold_value) { m_bot_spell_settings[spell_type].min_threshold = threshold_value; } + inline uint8 GetSpellTypeMaxThreshold(uint16 spell_type) const { return m_bot_spell_settings[spell_type].max_threshold; } + inline void SetSpellTypeMaxThreshold(uint16 spell_type, uint8 threshold_value) { m_bot_spell_settings[spell_type].max_threshold = threshold_value; } + inline bool SpellTypeRecastCheck(uint16 spellType) { return !m_bot_spell_settings[spellType].recast_timer.GetRemainingTime(); } + void SetSpellTypeRecastTimer(uint16 spell_type, uint32 recast_time) { m_bot_spell_settings[spell_type].recast_timer.Start(recast_time); } + + void SetIllusionBlock(bool value) { _illusion_block = value; } + bool GetIllusionBlock() const override { return _illusion_block; } + private: bool bot_owner_options[_booCount]; bool m_bot_pulling; bool m_bot_precombat; + uint32 bot_assistee; + std::vector m_bot_spell_settings; + bool _illusion_block; bool CanTradeFVNoDropItem(); void SendMobPositions(); void PlayerTradeEventLog(Trade *t, Trade *t2); - void NPCHandinEventLog(Trade* t, NPC* n); // full and partial mail key cache std::string m_mail_key_full; @@ -2235,6 +2428,11 @@ private: public: const std::string &GetMailKeyFull() const; const std::string &GetMailKey() const; + void ShowZoneShardMenu(); + void Handle_OP_ChangePetName(const EQApplicationPacket *app); + bool IsFilteredAFKPacket(const EQApplicationPacket *p); + void CheckAutoIdleAFK(PlayerPositionUpdateClient_Struct *p); + void SyncWorldPositionsToClient(bool ignore_idle = false); }; #endif diff --git a/zone/client_bot.cpp b/zone/client_bot.cpp index 1f3f809dd..b5621840f 100644 --- a/zone/client_bot.cpp +++ b/zone/client_bot.cpp @@ -1,6 +1,8 @@ #include "bot.h" #include "client.h" +#define NO_BOT_LIMIT -1; + bool Client::GetBotOption(BotOwnerOption boo) const { if (boo < _booCount) { return bot_owner_options[boo]; @@ -15,51 +17,57 @@ void Client::SetBotOption(BotOwnerOption boo, bool flag) { } } -uint32 Client::GetBotCreationLimit(uint8 class_id) -{ +uint32 Client::GetBotCreationLimit(uint8 class_id) { uint32 bot_creation_limit = RuleI(Bots, CreationLimit); + if (GetGM() && Admin() >= RuleI(Bots, MinStatusToBypassCreateLimit)) { + return RuleI(Bots, MinStatusBypassCreateLimit); + } + const auto bucket_name = fmt::format( "bot_creation_limit{}", - ( - class_id && IsPlayerClass(class_id) ? - fmt::format( - "_{}", - Strings::ToLower(GetClassIDName(class_id)) - ) : + class_id && IsPlayerClass(class_id) ? + fmt::format("_{}", Strings::ToLower(GetClassIDName(class_id))) : "" - ) ); auto bucket_value = GetBucket(bucket_name); + if (!bucket_value.empty() && Strings::IsNumber(bucket_value)) { - bot_creation_limit = Strings::ToUnsignedInt(bucket_value); + bot_creation_limit = Strings::ToInt(bucket_value); + } + + if (class_id && bucket_value.empty()) { + bot_creation_limit = NO_BOT_LIMIT; } return bot_creation_limit; } -int Client::GetBotRequiredLevel(uint8 class_id) -{ +int Client::GetBotRequiredLevel(uint8 class_id) { int bot_character_level = RuleI(Bots, BotCharacterLevel); + if (GetGM() && Admin() >= RuleI(Bots, MinStatusToBypassBotLevelRequirement)) { + return 0; + } + const auto bucket_name = fmt::format( "bot_required_level{}", - ( - class_id && IsPlayerClass(class_id) ? - fmt::format( - "_{}", - Strings::ToLower(GetClassIDName(class_id)) - ) : + class_id && IsPlayerClass(class_id) ? + fmt::format("_{}", Strings::ToLower(GetClassIDName(class_id))) : "" - ) ); auto bucket_value = GetBucket(bucket_name); + if (!bucket_value.empty() && Strings::IsNumber(bucket_value)) { bot_character_level = Strings::ToInt(bucket_value); } + if (class_id && bucket_value.empty()) { + bot_character_level = NO_BOT_LIMIT; + } + return bot_character_level; } @@ -67,22 +75,25 @@ int Client::GetBotSpawnLimit(uint8 class_id) { int bot_spawn_limit = RuleI(Bots, SpawnLimit); + if (GetGM() && Admin() >= RuleI(Bots, MinStatusToBypassSpawnLimit)) { + return RuleI(Bots, MinStatusBypassSpawnLimit); + } + const auto bucket_name = fmt::format( "bot_spawn_limit{}", - ( - class_id && IsPlayerClass(class_id) ? - fmt::format( - "_{}", - Strings::ToLower(GetClassIDName(class_id)) - ) : + class_id && IsPlayerClass(class_id) ? + fmt::format("_{}", Strings::ToLower(GetClassIDName(class_id))) : "" - ) ); auto bucket_value = GetBucket(bucket_name); + if (!bucket_value.empty() && Strings::IsNumber(bucket_value)) { bot_spawn_limit = Strings::ToInt(bucket_value); - return bot_spawn_limit; + } + + if (class_id && bucket_value.empty()) { + return NO_BOT_LIMIT; } if (RuleB(Bots, QuestableSpawnLimit)) { @@ -93,19 +104,63 @@ int Client::GetBotSpawnLimit(uint8 class_id) ); auto results = database.QueryDatabase(query); // use 'database' for non-bot table calls - if (!results.Success() || !results.RowCount()) { - return bot_spawn_limit; + + if (results.Success() && results.RowCount()) { + auto row = results.begin(); + bot_spawn_limit = Strings::ToInt(row[0]); + } + } + + if (!class_id) { + const auto &zones_list = Strings::Split(RuleS(Bots, ZonesWithSpawnLimits), ","); + + if (!zones_list.empty()) { + auto it = std::find(zones_list.begin(), zones_list.end(), std::to_string(zone->GetZoneID())); + + if (it != zones_list.end()) { + const auto &zones_limits_list = Strings::Split(RuleS(Bots, ZoneSpawnLimits), ","); + + 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()); + } + } + } } - auto row = results.begin(); - bot_spawn_limit = Strings::ToInt(row[0]); + const auto &zones_forced_list = Strings::Split(RuleS(Bots, ZonesWithForcedSpawnLimits), ","); + + if (!zones_forced_list.empty()) { + auto it = std::find(zones_forced_list.begin(), zones_forced_list.end(), std::to_string(zone->GetZoneID())); + + if (it != zones_forced_list.end()) { + const auto &zones_forced_limits_list = Strings::Split(RuleS(Bots, ZoneForcedSpawnLimits), ","); + + 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()); + } + } + } + } } return bot_spawn_limit; } -void Client::SetBotCreationLimit(uint32 new_creation_limit, uint8 class_id) -{ +void Client::SetBotCreationLimit(uint32 new_creation_limit, uint8 class_id) { const auto bucket_name = fmt::format( "bot_creation_limit{}", ( @@ -121,8 +176,7 @@ void Client::SetBotCreationLimit(uint32 new_creation_limit, uint8 class_id) SetBucket(bucket_name, std::to_string(new_creation_limit)); } -void Client::SetBotRequiredLevel(int new_required_level, uint8 class_id) -{ +void Client::SetBotRequiredLevel(int new_required_level, uint8 class_id) { const auto bucket_name = fmt::format( "bot_required_level{}", ( @@ -138,8 +192,7 @@ void Client::SetBotRequiredLevel(int new_required_level, uint8 class_id) SetBucket(bucket_name, std::to_string(new_required_level)); } -void Client::SetBotSpawnLimit(int new_spawn_limit, uint8 class_id) -{ +void Client::SetBotSpawnLimit(int new_spawn_limit, uint8 class_id) { const auto bucket_name = fmt::format( "bot_spawn_limit{}", ( @@ -155,7 +208,223 @@ void Client::SetBotSpawnLimit(int new_spawn_limit, uint8 class_id) SetBucket(bucket_name, std::to_string(new_spawn_limit)); } -void Client::CampAllBots(uint8 class_id) -{ +void Client::CampAllBots(uint8 class_id) { Bot::BotOrderCampAll(this, class_id); } + +void Client::LoadDefaultBotSettings() { + m_bot_spell_settings.clear(); + + /* No longer used, left as example. + SetBotSetting(BotSettingCategories::BaseSetting, BotBaseSettings::IllusionBlock, GetDefaultBotSettings(BotSettingCategories::BaseSetting, BotBaseSettings::IllusionBlock)); + LogBotSettingsDetail("{} says, 'Setting default {} [{}] to [{}]'", GetCleanName(), CastToBot()->GetBotSettingCategoryName(BotBaseSettings::IllusionBlock), BotBaseSettings::IllusionBlock, GetDefaultBotSettings(BotSettingCategories::BaseSetting, BotBaseSettings::IllusionBlock)); + */ + + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) { + BotSpellSettings t; + + t.spell_type = i; + t.short_name = Bot::GetSpellTypeShortNameByID(i); + t.name = Bot::GetSpellTypeNameByID(i); + t.delay = GetDefaultSpellTypeDelay(i); + t.min_threshold = GetDefaultSpellTypeMinThreshold(i); + t.max_threshold = GetDefaultSpellTypeMaxThreshold(i); + t.recast_timer.Start(); + + m_bot_spell_settings.push_back(t); + + LogBotSettingsDetail("{} says, 'Setting defaults for {} ({}) [#{}]'", GetCleanName(), t.name, t.short_name, t.spell_type); + LogBotSettingsDetail("{} says, 'Delay = [{}ms] | MinThreshold = [{}\%] | MaxThreshold = [{}\%]'", GetCleanName(), + GetDefaultSpellTypeDelay(i), + GetDefaultSpellTypeMinThreshold(i), GetDefaultSpellTypeMaxThreshold(i)); + } +} + +int Client::GetDefaultBotSettings(uint8 setting_type, uint16 bot_setting) { + switch (setting_type) { + case BotSettingCategories::BaseSetting: + return false; // only setting supported currently is illusion block + case BotSettingCategories::SpellDelay: + return GetDefaultSpellTypeDelay(bot_setting); + case BotSettingCategories::SpellMinThreshold: + return GetDefaultSpellTypeMinThreshold(bot_setting); + case BotSettingCategories::SpellMaxThreshold: + return GetDefaultSpellTypeMaxThreshold(bot_setting); + default: + return 0; // default return for any unsupported setting type + } +} + +int Client::GetBotSetting(uint8 setting_type, uint16 bot_setting) { + switch (setting_type) { + case BotSettingCategories::BaseSetting: + return 0; // unused currently + case BotSettingCategories::SpellDelay: + return GetSpellTypeDelay(bot_setting); + case BotSettingCategories::SpellMinThreshold: + return GetSpellTypeMinThreshold(bot_setting); + case BotSettingCategories::SpellMaxThreshold: + return GetSpellTypeMaxThreshold(bot_setting); + default: + return 0; // default return for any unsupported setting type + } +} + +void Client::SetBotSetting(uint8 setting_type, uint16 bot_setting, uint32 setting_value) { + switch (setting_type) { + case BotSettingCategories::BaseSetting: + break; // unused currently + case BotSettingCategories::SpellDelay: + SetSpellTypeDelay(bot_setting, setting_value); + break; + case BotSettingCategories::SpellMinThreshold: + SetSpellTypeMinThreshold(bot_setting, setting_value); + break; + case BotSettingCategories::SpellMaxThreshold: + SetSpellTypeMaxThreshold(bot_setting, setting_value); + break; + } +} + +void Client::SendSpellTypePrompts(bool commanded_types, bool client_only_types) { + if (client_only_types) { + Message( + Chat::Yellow, + fmt::format( + "You can view client spell types by {} or {}.", + Saylink::Silent( + fmt::format("^spelltypeids client"), "ID" + ), + Saylink::Silent( + fmt::format("^spelltypenames client"), "Shortname" + ) + ).c_str() + ); + } + else { + Message( + Chat::Yellow, + fmt::format( + "You can view spell types by {}, {}, {} or by {}, {}, {}.", + Saylink::Silent( + fmt::format("^spelltypeids 0-19"), "ID 0-19" + ), + Saylink::Silent( + fmt::format("^spelltypeids 20-39"), "20-39" + ), + Saylink::Silent( + fmt::format("^spelltypeids 40+"), "40+" + ), + Saylink::Silent( + fmt::format("^spelltypenames 0-19"), "Shortname 0-19" + ), + Saylink::Silent( + fmt::format("^spelltypenames 20-39"), "20-39" + ), + Saylink::Silent( + fmt::format("^spelltypenames 40+"), "40+" + ) + ).c_str() + ); + } + + if (commanded_types) { + Message( + Chat::Yellow, + fmt::format( + "You can view commanded spell types by {} or {}.", + Saylink::Silent( + fmt::format("^spelltypeids commanded"), "ID" + ), + Saylink::Silent( + fmt::format("^spelltypenames commanded"), "Shortname" + ) + ).c_str() + ); + } + + return; +} + +uint16 Client::GetDefaultSpellTypeDelay(uint16 spell_type) { + switch (spell_type) { + case BotSpellTypes::VeryFastHeals: + case BotSpellTypes::PetVeryFastHeals: + return 1500; + case BotSpellTypes::FastHeals: + case BotSpellTypes::PetFastHeals: + return 2500; + case BotSpellTypes::GroupHeals: + case BotSpellTypes::RegularHeal: + case BotSpellTypes::PetRegularHeals: + return 4000; + case BotSpellTypes::CompleteHeal: + case BotSpellTypes::GroupCompleteHeals: + case BotSpellTypes::PetCompleteHeals: + return 8000; + case BotSpellTypes::GroupHoTHeals: + case BotSpellTypes::HoTHeals: + case BotSpellTypes::PetHoTHeals: + return 22000; + case BotSpellTypes::Cure: + return 2000; + case BotSpellTypes::GroupCures: + return 3000; + case BotSpellTypes::PetCures: + return 5000; + default: + return 100; + } +} + +uint8 Client::GetDefaultSpellTypeMinThreshold(uint16 spell_type) { + switch (spell_type) { + default: + return 0; + } +} + +uint8 Client::GetDefaultSpellTypeMaxThreshold(uint16 spell_type) { + uint8 client_class = GetClass(); + + switch (spell_type) { + case BotSpellTypes::VeryFastHeals: + case BotSpellTypes::PetVeryFastHeals: + return 25; + case BotSpellTypes::FastHeals: + case BotSpellTypes::PetFastHeals: + return 40; + case BotSpellTypes::GroupHeals: + case BotSpellTypes::RegularHeal: + case BotSpellTypes::PetRegularHeals: + return 60; + case BotSpellTypes::CompleteHeal: + case BotSpellTypes::GroupCompleteHeals: + case BotSpellTypes::PetCompleteHeals: + if (client_class == Class::Necromancer || client_class == Class::Shaman) { + return 55; + } + else { + return 80; + } + case BotSpellTypes::GroupHoTHeals: + case BotSpellTypes::HoTHeals: + case BotSpellTypes::PetHoTHeals: + if (client_class == Class::Necromancer || client_class == Class::Shaman) { + return 70; + } + else { + return 90; + } + case BotSpellTypes::Buff: + case BotSpellTypes::Cure: + case BotSpellTypes::GroupCures: + case BotSpellTypes::PetCures: + case BotSpellTypes::PetBuffs: + case BotSpellTypes::PetDamageShields: + case BotSpellTypes::PetResistBuffs: + case BotSpellTypes::ResistBuffs: + default: + return 100; + } +} diff --git a/zone/client_evolving_items.cpp b/zone/client_evolving_items.cpp new file mode 100644 index 000000000..77c29a45e --- /dev/null +++ b/zone/client_evolving_items.cpp @@ -0,0 +1,576 @@ +#include "../common/evolving_items.h" + +#include "../common/events/player_event_logs.h" +#include "../common/global_define.h" + +#include "client.h" +#include "string_ids.h" +#include "worldserver.h" + +extern WorldServer worldserver; +extern QueryServ* QServ; +const std::string SUB_TYPE_DELIMITER = "."; + +void Client::DoEvolveItemToggle(const EQApplicationPacket *app) +{ + const auto in = reinterpret_cast(app->pBuffer); + auto item = CharacterEvolvingItemsRepository::FindOne(database, in->unique_id); + + LogEvolveItemDetail( + "Character ID [{}] requested to set evolve item with unique id [{}] to status [{}]", + CharacterID(), + in->unique_id, + in->activated + ); + + if (!item.id) { + LogEvolveItemDetail( + "Character ID [{}] toggle evolve item unique id [{}] failed", CharacterID(), in->unique_id); + return; + } + + item.activated = in->activated; + const auto inst = GetInv().GetItem(GetInv().HasItem(item.item_id)); + inst->SetEvolveActivated(item.activated ? true : false); + + CharacterEvolvingItemsRepository::ReplaceOne(database, item); + + SendEvolvingPacket(EvolvingItems::Actions::UPDATE_ITEMS, item); +} + +void Client::SendEvolvingPacket(const int8 action, const CharacterEvolvingItemsRepository::CharacterEvolvingItems &item) +{ + auto out = std::make_unique(OP_EvolveItem, sizeof(EvolveItemToggle)); + const auto data = reinterpret_cast(out->pBuffer); + + LogEvolveItemDetail( + "Character ID [{}] requested info for evolving item with unique id [{}] status [{}] " + "percentage [{}]", + CharacterID(), + item.id, + item.activated, + item.progression + ); + + data->action = action; + data->unique_id = item.id; + data->percentage = item.progression; + data->activated = item.activated; + + QueuePacket(out.get()); + + LogEvolveItem( + "Sent evolve item with unique id [{}] status [{}] percentage [{}] to Character ID " + "[{}]", + data->unique_id, + data->activated, + data->percentage, + CharacterID() + ); +} + +void Client::ProcessEvolvingItem(const uint64 exp, const Mob *mob) +{ + std::vector queue{}; + + for (auto &[key, inst]: GetInv().GetWorn()) { + LogEvolveItemDetail( + "CharacterID [{}] found equipped item ID [{}]", CharacterID(), inst->GetID()); + if (!inst->IsEvolving() || !inst->GetEvolveActivated()) { + LogEvolveItemDetail( + "CharacterID [{}], item ID [{}] not an evolving item.", CharacterID(), inst->GetID() + ); + continue; + } + + if (inst->GetTimers().contains("evolve") && !inst->GetTimers().at("evolve").Check(false)) { + LogEvolveItemDetail( + "CharacterID [{}], item ID [{}] timer not yet expired. [{}] secs remaining.", + CharacterID(), + inst->GetID(), + inst->GetTimers().at("evolve").GetRemainingTime() / 1000); + continue; + } + + if (!EvolvingItemsManager::Instance()->GetEvolvingItemsCache().contains(inst->GetID())) { + LogEvolveItem( + "Character ID {} has an evolving item that is not found in the db. Please check your " + "items_evolving_details table for item id {}", + CharacterID(), + inst->GetID() + ); + continue; + } + + auto const type = EvolvingItemsManager::Instance()->GetEvolvingItemsCache().at(inst->GetID()).type; + auto const sub_type = EvolvingItemsManager::Instance()->GetEvolvingItemsCache().at(inst->GetID()).sub_type; + + LogEvolveItemDetail( + "CharacterID [{}] item id [{}] type {} sub_type {} is Evolving. Continue processing...", + CharacterID(), + inst->GetID(), + type, + sub_type + ); + + auto sub_types = Strings::Split(sub_type, SUB_TYPE_DELIMITER); + auto has_sub_type = [&](uint32_t type) { + return Strings::Contains(sub_types, std::to_string(type)); + }; + + switch (type) { + case EvolvingItems::Types::AMOUNT_OF_EXP: { + LogEvolveItemDetail("Type [{}] Processing sub_type", type); + + // Determine the evolve amount based on sub_type conditions + int evolve_amount = 0; + + if (has_sub_type(EvolvingItems::SubTypes::ALL_EXP) || + (has_sub_type(EvolvingItems::SubTypes::GROUP_EXP) && IsGrouped())) { + evolve_amount = exp * RuleR(EvolvingItems, PercentOfGroupExperience) / 100; + } + else if (has_sub_type(EvolvingItems::SubTypes::ALL_EXP) || + (has_sub_type(EvolvingItems::SubTypes::RAID_EXP) && IsRaidGrouped())) { + evolve_amount = exp * RuleR(EvolvingItems, PercentOfRaidExperience) / 100; + } + else if (has_sub_type(EvolvingItems::SubTypes::ALL_EXP) || + has_sub_type(EvolvingItems::SubTypes::SOLO_EXP)) { + evolve_amount = exp * RuleR(EvolvingItems, PercentOfSoloExperience) / 100; + } + + inst->SetEvolveAddToCurrentAmount(evolve_amount); + inst->CalculateEvolveProgression(); + + auto e = CharacterEvolvingItemsRepository::SetCurrentAmountAndProgression( + database, inst->GetEvolveUniqueID(), inst->GetEvolveCurrentAmount(), inst->GetEvolveProgression() + ); + if (!e.id) { + break; + } + + SendEvolvingPacket(EvolvingItems::Actions::UPDATE_ITEMS, e); + + LogEvolveItem( + "Processing Complete for item id [{1}] Type 1 Amount of EXP - SubType [{0}] - " + "Assigned [{2}] of exp to [{1}]", + sub_type, + inst->GetID(), + exp * 0.001 + ); + + if (inst->GetEvolveProgression() >= 100) { + queue.push_back(inst); + } + + break; + } + case EvolvingItems::Types::SPECIFIC_MOB_RACE: { + LogEvolveItemDetail("Type [{}] Processing sub type", type); + if (mob && has_sub_type(mob->GetRace())) { + LogEvolveItemDetail("Sub_Type [{}] Processing Item", sub_type); + inst->SetEvolveAddToCurrentAmount(1); + inst->CalculateEvolveProgression(); + + auto e = CharacterEvolvingItemsRepository::SetCurrentAmountAndProgression( + database, + inst->GetEvolveUniqueID(), + inst->GetEvolveCurrentAmount(), + inst->GetEvolveProgression() + ); + if (!e.id) { + break; + } + + SendEvolvingPacket(EvolvingItems::Actions::UPDATE_ITEMS, e); + + LogEvolveItem( + "Processing Complete for item id [{1}] Type 3 Specific Mob Race - SubType " + "[{0}] " + "- Increased count by 1 for [{1}]", + sub_type, + inst->GetID() + ); + } + + if (inst->GetEvolveProgression() >= 100) { + queue.push_back(inst); + } + + break; + } + case EvolvingItems::Types::SPECIFIC_ZONE_ID: { + LogEvolveItemDetail("Type [{}] Processing sub type", type); + if (mob && has_sub_type(mob->GetZoneID())) { + LogEvolveItemDetail("Sub_Type [{}] Processing Item", sub_type); + inst->SetEvolveAddToCurrentAmount(1); + inst->CalculateEvolveProgression(); + + auto e = CharacterEvolvingItemsRepository::SetCurrentAmountAndProgression( + database, + inst->GetEvolveUniqueID(), + inst->GetEvolveCurrentAmount(), + inst->GetEvolveProgression() + ); + if (!e.id) { + break; + } + + SendEvolvingPacket(EvolvingItems::Actions::UPDATE_ITEMS, e); + + LogEvolveItem( + "Processing Complete for item id [{1}] Type 4 Specific Zone ID - SubType " + "[{0}] " + "- Increased count by 1 for [{1}]", + sub_type, + inst->GetID() + ); + } + + if (inst->GetEvolveProgression() >= 100) { + queue.push_back(inst); + } + + break; + } + case EvolvingItems::Types::NUMBER_OF_KILLS: { + LogEvolveItemDetail("Type [{}] Processing sub type", type); + if (mob) { + if (mob->GetLevel() >= Strings::ToUnsignedInt(sub_types.front()) || + Strings::ToUnsignedInt(sub_types.front()) == 0 + ) { + LogEvolveItemDetail("Sub_Type [{}] Processing Item", sub_type); + inst->SetEvolveAddToCurrentAmount(1); + inst->CalculateEvolveProgression(); + + auto e = CharacterEvolvingItemsRepository::SetCurrentAmountAndProgression( + database, + inst->GetEvolveUniqueID(), + inst->GetEvolveCurrentAmount(), + inst->GetEvolveProgression() + ); + if (!e.id) { + break; + } + + SendEvolvingPacket(EvolvingItems::Actions::UPDATE_ITEMS, e); + + LogEvolveItem( + "Processing Complete for item id [{1}] Type 4 Specific Zone ID - SubType " + "[{0}] " + "- Increased count by 1 for [{1}]", + sub_type, + inst->GetID() + ); + } + } + + if (inst->GetEvolveProgression() >= 100) { + queue.push_back(inst); + } + + break; + } + default: { + } + } + } + + if (!queue.empty()) { + for (auto const &i: queue) { + DoEvolveCheckProgression(*i); + } + } +} + +void Client::DoEvolveItemDisplayFinalResult(const EQApplicationPacket *app) +{ + const auto in = reinterpret_cast(app->pBuffer); + + const uint32 item_id = static_cast(in->unique_id & 0xFFFFFFFF); + if (item_id == 0) { + LogEvolveItem("Error - Item ID of final evolve item is blank."); + return; + } + + std::unique_ptr const inst(database.CreateItem(item_id)); + if (!inst) { + return; + } + + LogEvolveItemDetail( + "Character ID [{}] requested to view final evolve item id [{}] for evolve item id [{}]", + CharacterID(), + item_id, + EvolvingItemsManager::Instance()->GetFirstItemInLoreGroupByItemID(item_id) + ); + + inst->SetEvolveProgression(100); + + LogEvolveItemDetail( + "Sending final result for item id [{}] to Character ID [{}]", item_id, CharacterID() + ); + SendItemPacket(0, inst.get(), ItemPacketViewLink); +} + +bool Client::DoEvolveCheckProgression(EQ::ItemInstance &inst) +{ + if (!inst) { + return false; + } + + if (inst.GetEvolveProgression() < 100 || inst.GetEvolveLvl() == inst.GetMaxEvolveLvl()) { + return false; + } + + const auto new_item_id = EvolvingItemsManager::Instance()->GetNextEvolveItemID(inst); + if (!new_item_id) { + return false; + } + + std::unique_ptr const new_inst(database.CreateItem(new_item_id)); + + if (!new_inst) { + return false; + } + + if (RuleB(EvolvingItems, EnableParcelMerchants) && + !RuleB(EvolvingItems, DestroyAugmentsOnEvolve) && + inst.IsAugmented() + ) { + auto const augs = inst.GetAugmentIDs(); + std::vector parcels; + int32 next_slot = FindNextFreeParcelSlotUsingMemory(); + for (auto const &item_id: augs) { + if (!item_id) { + continue; + } + + if (next_slot == INVALID_INDEX) { + break; + } + + 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 = next_slot; + p.sent_date = time(nullptr); + p.item_id = item_id; + p.quantity = 1; + + if (PlayerEventLogs::Instance()->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); + m_parcels.emplace(p.slot_id, p); + next_slot = FindNextFreeParcelSlotUsingMemory(); + } + + CharacterParcelsRepository::InsertMany(database, parcels); + SendParcelStatus(); + SendParcelIconStatus(); + } + + CheckItemDiscoverability(new_inst->GetID()); + + PlayerEvent::EvolveItem e{}; + + RemoveItemBySerialNumber(inst.GetSerialNumber()); + EvolvingItemsManager::Instance()->LoadPlayerEvent(inst, e); + e.status = "Evolved Item due to obtaining progression - Old Evolve Item removed from inventory."; + RecordPlayerEventLog(PlayerEvent::EVOLVE_ITEM, e); + + PushItemOnCursor(*new_inst, true); + EvolvingItemsManager::Instance()->LoadPlayerEvent(*new_inst, e); + e.status = "Evolved Item due to obtaining progression - New Evolve Item placed in inventory."; + RecordPlayerEventLog(PlayerEvent::EVOLVE_ITEM, e); + + MessageString(Chat::Yellow, EVOLVE_ITEM_EVOLVED, inst.GetItem()->Name); + + LogEvolveItem( + "Evolved item id [{}] into item id [{}] for Character ID [{}]", + inst.GetID(), + new_inst->GetID(), + CharacterID()); + + return true; +} + +void Client::SendEvolveXPTransferWindow() +{ + auto out = std::make_unique(OP_EvolveItem, sizeof(EvolveItemToggle)); + const auto data = reinterpret_cast(out->pBuffer); + + data->action = 1; + + QueuePacket(out.get()); +} + +void Client::SendEvolveXPWindowDetails(const EQApplicationPacket *app) +{ + const auto in = reinterpret_cast(app->pBuffer); + + const auto item_1_slot = + GetInv().HasEvolvingItem(in->item1_unique_id, 1, invWherePersonal | invWhereWorn | invWhereCursor); + const auto item_2_slot = + GetInv().HasEvolvingItem(in->item2_unique_id, 1, invWherePersonal | invWhereWorn | invWhereCursor); + + if (item_1_slot == INVALID_INDEX || item_2_slot == INVALID_INDEX) { + return; + } + + const auto inst_from = GetInv().GetItem(item_1_slot); + const auto inst_to = GetInv().GetItem(item_2_slot); + + if (!inst_from || !inst_to) { + return; + } + + const auto results = EvolvingItemsManager::Instance()->DetermineTransferResults(*inst_from, *inst_to); + + if (!results.item_from_id || !results.item_to_id) { + SendEvolveTransferResults(*inst_from, *inst_to, *inst_from, *inst_to, 0, 0); + return; + } + + std::unique_ptr const inst_from_new(database.CreateItem(results.item_from_id)); + std::unique_ptr const inst_to_new(database.CreateItem(results.item_to_id)); + if (!inst_from_new || !inst_to_new) { + SendEvolveTransferResults(*inst_from, *inst_to, *inst_from, *inst_to, 0, 0); + return; + } + + inst_from_new->SetEvolveCurrentAmount(results.item_from_current_amount); + inst_from_new->CalculateEvolveProgression(); + inst_to_new->SetEvolveCurrentAmount(results.item_to_current_amount); + inst_to_new->CalculateEvolveProgression(); + + SendEvolveTransferResults( + *inst_from, *inst_to, *inst_from_new, *inst_to_new, results.compatibility, results.max_transfer_level); +} + +void Client::DoEvolveTransferXP(const EQApplicationPacket *app) +{ + const auto in = reinterpret_cast(app->pBuffer); + + const auto item_1_slot = + GetInv().HasEvolvingItem(in->item1_unique_id, 1, invWherePersonal | invWhereWorn | invWhereCursor); + const auto item_2_slot = + GetInv().HasEvolvingItem(in->item2_unique_id, 1, invWherePersonal | invWhereWorn | invWhereCursor); + + if (item_1_slot == INVALID_INDEX || item_2_slot == INVALID_INDEX) { + return; + } + + const auto inst_from = GetInv().GetItem(item_1_slot); + const auto inst_to = GetInv().GetItem(item_2_slot); + + if (!inst_from || !inst_to) { + Message(Chat::Red, "Transfer Failed. Incompatible Items."); + LogEvolveItem("Transfer Failed for Character ID [{}]", CharacterID()); + return; + } + + const auto results = EvolvingItemsManager::Instance()->DetermineTransferResults(*inst_from, *inst_to); + + if (!results.item_from_id || !results.item_to_id) { + Message(Chat::Red, "Transfer Failed. Incompatible Items."); + LogEvolveItem("Transfer Failed for Character ID [{}]", CharacterID()); + return; + } + + std::unique_ptr const inst_from_new(database.CreateItem(results.item_from_id)); + std::unique_ptr const inst_to_new(database.CreateItem(results.item_to_id)); + + if (!inst_from_new || !inst_to_new) { + Message(Chat::Red, "Transfer Failed. Incompatible Items."); + LogEvolveItem("Transfer Failed for Character ID [{}]", CharacterID()); + return; + } + + inst_from_new->SetEvolveCurrentAmount(results.item_from_current_amount); + inst_from_new->CalculateEvolveProgression(); + inst_to_new->SetEvolveCurrentAmount(results.item_to_current_amount); + inst_to_new->CalculateEvolveProgression(); + + PlayerEvent::EvolveItem e{}; + + RemoveItemBySerialNumber(inst_from->GetSerialNumber()); + EvolvingItemsManager::Instance()->LoadPlayerEvent(*inst_from, e); + e.status = "Transfer XP - Original FROM Evolve Item removed from inventory."; + RecordPlayerEventLog(PlayerEvent::EVOLVE_ITEM, e); + + PushItemOnCursor(*inst_from_new, true); + EvolvingItemsManager::Instance()->LoadPlayerEvent(*inst_from_new, e); + e.status = "Transfer XP - Updated FROM item placed in inventory."; + RecordPlayerEventLog(PlayerEvent::EVOLVE_ITEM, e); + + RemoveItemBySerialNumber(inst_to->GetSerialNumber()); + EvolvingItemsManager::Instance()->LoadPlayerEvent(*inst_to, e); + e.status = "Transfer XP - Original TO Evolve Item removed from inventory."; + RecordPlayerEventLog(PlayerEvent::EVOLVE_ITEM, e); + + PushItemOnCursor(*inst_to_new, true); + EvolvingItemsManager::Instance()->LoadPlayerEvent(*inst_to_new, e); + e.status = "Transfer XP - Updated TO Evolve item placed in inventory."; + RecordPlayerEventLog(PlayerEvent::EVOLVE_ITEM, e); + + LogEvolveItem( + "Evolve Transfer XP resulted in evolved item id [{}] into item id [{}] for Character ID " + "[{}]", + inst_to->GetID(), + inst_to_new->GetID(), + CharacterID() + ); +} + +void Client::SendEvolveTransferResults( + const EQ::ItemInstance &inst_from, + const EQ::ItemInstance &inst_to, + const EQ::ItemInstance &inst_from_new, + const EQ::ItemInstance &inst_to_new, + const uint32 compatibility, + const uint32 max_transfer_level) +{ + std::stringstream ss; + cereal::BinaryOutputArchive ar(ss); + + EvolveXPWindowSend e{}; + e.action = EvolvingItems::Actions::TRANSFER_WINDOW_DETAILS; + e.compatibility = compatibility; + e.item1_unique_id = inst_from.GetEvolveUniqueID(); + e.item2_unique_id = inst_to.GetEvolveUniqueID(); + e.max_transfer_level = max_transfer_level; + e.item1_present = 1; + e.item2_present = 1; + e.serialize_item_1 = inst_from_new.Serialize(0); + e.serialize_item_2 = inst_to_new.Serialize(0); + + { + ar(e); + } + + uint32 packet_size = sizeof(EvolveItemMessaging) + ss.str().length(); + + std::unique_ptr out(new EQApplicationPacket(OP_EvolveItem, packet_size)); + const auto data = reinterpret_cast(out->pBuffer); + + data->action = EvolvingItems::Actions::TRANSFER_WINDOW_DETAILS; + memcpy(data->serialized_data, ss.str().data(), ss.str().length()); + + QueuePacket(out.get()); + + ss.str(""); + ss.clear(); +} diff --git a/zone/client_mods.cpp b/zone/client_mods.cpp index 0387b795b..6c7026185 100644 --- a/zone/client_mods.cpp +++ b/zone/client_mods.cpp @@ -693,7 +693,7 @@ int64 Client::CalcManaRegen(bool bCombat) } regen += aabonuses.ManaRegen; - // add in + 1 bonus for SE_CompleteHeal, but we don't do anything for it yet? + // add in + 1 bonus for SpellEffect::CompleteHeal, but we don't do anything for it yet? int item_bonus = itembonuses.ManaRegen; // this is capped already item_bonus += itembonuses.heroic_mana_regen; @@ -812,7 +812,7 @@ int32 Client::CalcSTR() int32 Client::CalcSTA() { - int32 val = m_pp.STA + itembonuses.STA + spellbonuses.STA + CalcAlcoholPhysicalEffect();; + int32 val = m_pp.STA + itembonuses.STA + spellbonuses.STA + CalcAlcoholPhysicalEffect(); int32 mod = aabonuses.STA; STA = val + mod; if (STA < 1) { @@ -827,7 +827,7 @@ int32 Client::CalcSTA() int32 Client::CalcAGI() { - int32 val = m_pp.AGI + itembonuses.AGI + spellbonuses.AGI - CalcAlcoholPhysicalEffect();; + int32 val = m_pp.AGI + itembonuses.AGI + spellbonuses.AGI - CalcAlcoholPhysicalEffect(); int32 mod = aabonuses.AGI; int32 str = GetSTR(); //Encumbered penalty @@ -852,7 +852,7 @@ int32 Client::CalcAGI() int32 Client::CalcDEX() { - int32 val = m_pp.DEX + itembonuses.DEX + spellbonuses.DEX - CalcAlcoholPhysicalEffect();; + int32 val = m_pp.DEX + itembonuses.DEX + spellbonuses.DEX - CalcAlcoholPhysicalEffect(); int32 mod = aabonuses.DEX; DEX = val + mod; if (DEX < 1) { @@ -1011,52 +1011,52 @@ int32 Client::CalcMR() { //racial bases switch (GetBaseRace()) { - case HUMAN: + case Race::Human: MR = 25; break; - case BARBARIAN: + case Race::Barbarian: MR = 25; break; - case ERUDITE: + case Race::Erudite: MR = 30; break; - case WOOD_ELF: + case Race::WoodElf: MR = 25; break; - case HIGH_ELF: + case Race::HighElf: MR = 25; break; - case DARK_ELF: + case Race::DarkElf: MR = 25; break; - case HALF_ELF: + case Race::HalfElf: MR = 25; break; - case DWARF: + case Race::Dwarf: MR = 30; break; - case TROLL: + case Race::Troll: MR = 25; break; - case OGRE: + case Race::Ogre: MR = 25; break; - case HALFLING: + case Race::Halfling: MR = 25; break; - case GNOME: + case Race::Gnome: MR = 25; break; - case IKSAR: + case Race::Iksar: MR = 25; break; - case VAHSHIR: + case Race::VahShir: MR = 25; break; - case FROGLOK: + case Race::Froglok2: MR = 30; break; - case DRAKKIN: + case Race::Drakkin: { MR = 25; if (GetDrakkinHeritage() == 2) @@ -1085,52 +1085,52 @@ int32 Client::CalcFR() { //racial bases switch (GetBaseRace()) { - case HUMAN: + case Race::Human: FR = 25; break; - case BARBARIAN: + case Race::Barbarian: FR = 25; break; - case ERUDITE: + case Race::Erudite: FR = 25; break; - case WOOD_ELF: + case Race::WoodElf: FR = 25; break; - case HIGH_ELF: + case Race::HighElf: FR = 25; break; - case DARK_ELF: + case Race::DarkElf: FR = 25; break; - case HALF_ELF: + case Race::HalfElf: FR = 25; break; - case DWARF: + case Race::Dwarf: FR = 25; break; - case TROLL: + case Race::Troll: FR = 5; break; - case OGRE: + case Race::Ogre: FR = 25; break; - case HALFLING: + case Race::Halfling: FR = 25; break; - case GNOME: + case Race::Gnome: FR = 25; break; - case IKSAR: + case Race::Iksar: FR = 30; break; - case VAHSHIR: + case Race::VahShir: FR = 25; break; - case FROGLOK: + case Race::Froglok2: FR = 25; break; - case DRAKKIN: + case Race::Drakkin: { FR = 25; if (GetDrakkinHeritage() == 0) @@ -1171,52 +1171,52 @@ int32 Client::CalcDR() { //racial bases switch (GetBaseRace()) { - case HUMAN: + case Race::Human: DR = 15; break; - case BARBARIAN: + case Race::Barbarian: DR = 15; break; - case ERUDITE: + case Race::Erudite: DR = 10; break; - case WOOD_ELF: + case Race::WoodElf: DR = 15; break; - case HIGH_ELF: + case Race::HighElf: DR = 15; break; - case DARK_ELF: + case Race::DarkElf: DR = 15; break; - case HALF_ELF: + case Race::HalfElf: DR = 15; break; - case DWARF: + case Race::Dwarf: DR = 15; break; - case TROLL: + case Race::Troll: DR = 15; break; - case OGRE: + case Race::Ogre: DR = 15; break; - case HALFLING: + case Race::Halfling: DR = 20; break; - case GNOME: + case Race::Gnome: DR = 15; break; - case IKSAR: + case Race::Iksar: DR = 15; break; - case VAHSHIR: + case Race::VahShir: DR = 15; break; - case FROGLOK: + case Race::Froglok2: DR = 15; break; - case DRAKKIN: + case Race::Drakkin: { DR = 15; if (GetDrakkinHeritage() == 1) @@ -1263,52 +1263,52 @@ int32 Client::CalcPR() { //racial bases switch (GetBaseRace()) { - case HUMAN: + case Race::Human: PR = 15; break; - case BARBARIAN: + case Race::Barbarian: PR = 15; break; - case ERUDITE: + case Race::Erudite: PR = 15; break; - case WOOD_ELF: + case Race::WoodElf: PR = 15; break; - case HIGH_ELF: + case Race::HighElf: PR = 15; break; - case DARK_ELF: + case Race::DarkElf: PR = 15; break; - case HALF_ELF: + case Race::HalfElf: PR = 15; break; - case DWARF: + case Race::Dwarf: PR = 20; break; - case TROLL: + case Race::Troll: PR = 15; break; - case OGRE: + case Race::Ogre: PR = 15; break; - case HALFLING: + case Race::Halfling: PR = 20; break; - case GNOME: + case Race::Gnome: PR = 15; break; - case IKSAR: + case Race::Iksar: PR = 15; break; - case VAHSHIR: + case Race::VahShir: PR = 15; break; - case FROGLOK: + case Race::Froglok2: PR = 30; break; - case DRAKKIN: + case Race::Drakkin: { PR = 15; if (GetDrakkinHeritage() == 3) @@ -1355,52 +1355,52 @@ int32 Client::CalcCR() { //racial bases switch (GetBaseRace()) { - case HUMAN: + case Race::Human: CR = 25; break; - case BARBARIAN: + case Race::Barbarian: CR = 35; break; - case ERUDITE: + case Race::Erudite: CR = 25; break; - case WOOD_ELF: + case Race::WoodElf: CR = 25; break; - case HIGH_ELF: + case Race::HighElf: CR = 25; break; - case DARK_ELF: + case Race::DarkElf: CR = 25; break; - case HALF_ELF: + case Race::HalfElf: CR = 25; break; - case DWARF: + case Race::Dwarf: CR = 25; break; - case TROLL: + case Race::Troll: CR = 25; break; - case OGRE: + case Race::Ogre: CR = 25; break; - case HALFLING: + case Race::Halfling: CR = 25; break; - case GNOME: + case Race::Gnome: CR = 25; break; - case IKSAR: + case Race::Iksar: CR = 15; break; - case VAHSHIR: + case Race::VahShir: CR = 25; break; - case FROGLOK: + case Race::Froglok2: CR = 25; break; - case DRAKKIN: + case Race::Drakkin: { CR = 25; if (GetDrakkinHeritage() == 4) @@ -1482,24 +1482,24 @@ uint32 Mob::GetInstrumentMod(uint16 spell_id) Bard Spell Effects Mod uses the highest bonus from either of these for each instrument - SPA 179 SE_AllInstrumentMod is used for instrument spellbonus.______Mod. This applies to ALL instrument mods (Puretones Discipline) - SPA 260 SE_AddSingingMod is used for instrument spellbonus.______Mod. This applies to indiviual instrument mods. (Instrument mastery AA) + SPA 179 SpellEffect::AllInstrumentMod is used for instrument spellbonus.______Mod. This applies to ALL instrument mods (Puretones Discipline) + SPA 260 SpellEffect::AddSingingMod is used for instrument spellbonus.______Mod. This applies to indiviual instrument mods. (Instrument mastery AA) -Example usage: From AA a value of 4 = 40% - SPA 118 SE_Amplification is a stackable singing mod, on live it exists as both spell and AA bonus (stackable) + SPA 118 SpellEffect::Amplification is a stackable singing mod, on live it exists as both spell and AA bonus (stackable) - Live Behavior: Amplifcation can be modified by singing mods and amplification itself, thus on the second cast of Amplification you will recieve the mod from the first cast, this continues until you reach the song mod cap. - SPA 261 SE_SongModCap raises song focus cap (No longer used on live) - SPA 270 SE_BardSongRange increase range of beneficial bard songs (Sionachie's Crescendo) + SPA 261 SpellEffect::SongModCap raises song focus cap (No longer used on live) + SPA 270 SpellEffect::BardSongRange increase range of beneficial bard songs (Sionachie's Crescendo) - SPA 413 SE_FcBaseEffects focus effect that replaced item instrument mods + SPA 413 SpellEffect::FcBaseEffects focus effect that replaced item instrument mods Issues 10-15-21: Bonuses are not applied, unless song is stopped and restarted due to pulse keeping it continues. -> Need to recode songs to recast when duration ends. Formula Live Bards: - mod = (10 + (aabonus.____Mod [SPA 260 AA Instrument Mastery]) + (SE_FcBaseEffect[SPA 413])/10 + (spellbonus.______Mod [SPA 179 Puretone Disc]) + (Amplication [SPA 118])/10 + mod = (10 + (aabonus.____Mod [SPA 260 AA Instrument Mastery]) + (SpellEffect::FcBaseEffect[SPA 413])/10 + (spellbonus.______Mod [SPA 179 Puretone Disc]) + (Amplication [SPA 118])/10 TODO: Spell Table Fields that need to be implemented Field 225 //float base_effects_focus_slope; // -- BASE_EFFECTS_FOCUS_SLOPE @@ -1567,7 +1567,7 @@ uint32 Mob::GetInstrumentMod(uint16 spell_id) else effectmod = spellbonuses.singingMod; if (IsBardSong(spell_id)) - effectmod += aabonuses.singingMod + (spellbonuses.Amplification + itembonuses.Amplification + aabonuses.Amplification); //SPA 118 SE_Amplification + effectmod += aabonuses.singingMod + (spellbonuses.Amplification + itembonuses.Amplification + aabonuses.Amplification); //SPA 118 SpellEffect::Amplification break; default: effectmod = 10; @@ -1584,7 +1584,7 @@ uint32 Mob::GetInstrumentMod(uint16 spell_id) if (effectmodcap) { - effectmodcap += aabonuses.songModCap + spellbonuses.songModCap + itembonuses.songModCap; //SPA 261 SE_SongModCap (not used on live) + effectmodcap += aabonuses.songModCap + spellbonuses.songModCap + itembonuses.songModCap; //SPA 261 SpellEffect::SongModCap (not used on live) //Incase a negative modifier is used. if (effectmodcap <= 0) { diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 14ee222f3..a059bef65 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -42,9 +42,9 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "../common/data_verification.h" #include "../common/rdtsc.h" -#include "data_bucket.h" +#include "../common/data_bucket.h" +#include "dynamic_zone.h" #include "event_codes.h" -#include "expedition.h" #include "guild_mgr.h" #include "merc.h" #include "petitions.h" @@ -66,21 +66,22 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "../common/repositories/character_corpses_repository.h" #include "../common/repositories/guild_tributes_repository.h" #include "../common/repositories/buyer_buy_lines_repository.h" +#include "../common/repositories/character_pet_name_repository.h" +#include "../common/repositories/tradeskill_recipe_entries_repository.h" #include "../common/events/player_event_logs.h" #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; extern volatile bool is_zone_loaded; extern WorldServer worldserver; -extern PetitionList petition_list; extern EntityList entity_list; typedef void (Client::*ClientPacketProc)(const EQApplicationPacket *app); - //Use a map for connecting opcodes since it dosent get used a lot and is sparse std::map ConnectingOpcodes; //Use a static array for connected, for speed @@ -161,6 +162,7 @@ void MapOpcodes() ConnectedOpcodes[OP_CancelTrade] = &Client::Handle_OP_CancelTrade; ConnectedOpcodes[OP_CastSpell] = &Client::Handle_OP_CastSpell; ConnectedOpcodes[OP_ChannelMessage] = &Client::Handle_OP_ChannelMessage; + ConnectedOpcodes[OP_ChangePetName] = &Client::Handle_OP_ChangePetName; ConnectedOpcodes[OP_ClearBlockedBuffs] = &Client::Handle_OP_ClearBlockedBuffs; ConnectedOpcodes[OP_ClearNPCMarks] = &Client::Handle_OP_ClearNPCMarks; ConnectedOpcodes[OP_ClearSurname] = &Client::Handle_OP_ClearSurname; @@ -208,6 +210,7 @@ void MapOpcodes() ConnectedOpcodes[OP_Emote] = &Client::Handle_OP_Emote; ConnectedOpcodes[OP_EndLootRequest] = &Client::Handle_OP_EndLootRequest; ConnectedOpcodes[OP_EnvDamage] = &Client::Handle_OP_EnvDamage; + ConnectedOpcodes[OP_EvolveItem] = &Client::Handle_OP_EvolveItem; ConnectedOpcodes[OP_FaceChange] = &Client::Handle_OP_FaceChange; ConnectedOpcodes[OP_FeignDeath] = &Client::Handle_OP_FeignDeath; ConnectedOpcodes[OP_FindPersonRequest] = &Client::Handle_OP_FindPersonRequest; @@ -283,6 +286,7 @@ void MapOpcodes() ConnectedOpcodes[OP_ItemLinkResponse] = &Client::Handle_OP_ItemLinkResponse; ConnectedOpcodes[OP_ItemName] = &Client::Handle_OP_ItemName; ConnectedOpcodes[OP_ItemPreview] = &Client::Handle_OP_ItemPreview; + ConnectedOpcodes[OP_ItemPreviewRequest] = &Client::Handle_OP_ItemPreviewRequest; ConnectedOpcodes[OP_ItemVerifyRequest] = &Client::Handle_OP_ItemVerifyRequest; ConnectedOpcodes[OP_ItemViewUnknown] = &Client::Handle_OP_Ignore; ConnectedOpcodes[OP_Jump] = &Client::Handle_OP_Jump; @@ -339,6 +343,7 @@ void MapOpcodes() ConnectedOpcodes[OP_PlayerStateAdd] = &Client::Handle_OP_PlayerStateAdd; ConnectedOpcodes[OP_PlayerStateRemove] = &Client::Handle_OP_PlayerStateRemove; ConnectedOpcodes[OP_PickPocket] = &Client::Handle_OP_PickPocket; + ConnectedOpcodes[OP_PickZone] = &Client::Handle_OP_PickZone; ConnectedOpcodes[OP_PopupResponse] = &Client::Handle_OP_PopupResponse; ConnectedOpcodes[OP_PotionBelt] = &Client::Handle_OP_PotionBelt; ConnectedOpcodes[OP_PurchaseLeadershipAA] = &Client::Handle_OP_PurchaseLeadershipAA; @@ -404,6 +409,7 @@ void MapOpcodes() ConnectedOpcodes[OP_TradeRequestAck] = &Client::Handle_OP_TradeRequestAck; ConnectedOpcodes[OP_TraderShop] = &Client::Handle_OP_TraderShop; ConnectedOpcodes[OP_TradeSkillCombine] = &Client::Handle_OP_TradeSkillCombine; + ConnectedOpcodes[OP_TradeSkillRecipeInspect] = &Client::Handle_OP_TradeSkillRecipeInspect; ConnectedOpcodes[OP_Translocate] = &Client::Handle_OP_Translocate; ConnectedOpcodes[OP_TributeItem] = &Client::Handle_OP_TributeItem; ConnectedOpcodes[OP_TributeMoney] = &Client::Handle_OP_TributeMoney; @@ -412,7 +418,7 @@ void MapOpcodes() ConnectedOpcodes[OP_TributeUpdate] = &Client::Handle_OP_TributeUpdate; ConnectedOpcodes[OP_VetClaimRequest] = &Client::Handle_OP_VetClaimRequest; ConnectedOpcodes[OP_VoiceMacroIn] = &Client::Handle_OP_VoiceMacroIn; - ConnectedOpcodes[OP_UpdateAura] = &Client::Handle_OP_UpdateAura;; + ConnectedOpcodes[OP_UpdateAura] = &Client::Handle_OP_UpdateAura; ConnectedOpcodes[OP_WearChange] = &Client::Handle_OP_WearChange; ConnectedOpcodes[OP_WhoAllRequest] = &Client::Handle_OP_WhoAllRequest; ConnectedOpcodes[OP_WorldUnknown001] = &Client::Handle_OP_Ignore; @@ -457,7 +463,7 @@ int Client::HandlePacket(const EQApplicationPacket *app) OpcodeManager::EmuToName(app->GetOpcode()), o->EmuToEQ(app->GetOpcode()) == 0 ? app->GetProtocolOpcode() : o->EmuToEQ(app->GetOpcode()), app->Size(), - (LogSys.IsLogEnabled(Logs::Detail, Logs::PacketClientServer) ? DumpPacketToString(app) : "") + (EQEmuLogSys::Instance()->IsLogEnabled(Logs::Detail, Logs::PacketClientServer) ? DumpPacketToString(app) : "") ); EmuOpcode opcode = app->GetOpcode(); @@ -524,7 +530,6 @@ int Client::HandlePacket(const EQApplicationPacket *app) // Finish client connecting state void Client::CompleteConnect() { - UpdateWho(); client_state = CLIENT_CONNECTED; SendAllPackets(); @@ -666,48 +671,52 @@ void Client::CompleteConnect() for (int x1 = 0; x1 < EFFECT_COUNT; x1++) { switch (spell.effect_id[x1]) { - case SE_Illusion: { + case SpellEffect::Illusion: { + if (GetIllusionBlock()) { + break; + } + if (buffs[j1].persistant_buff) { Mob *caster = entity_list.GetMobID(buffs[j1].casterid); ApplySpellEffectIllusion(spell.id, caster, j1, spell.base_value[x1], spell.limit_value[x1], spell.max_value[x1]); } break; } - case SE_SummonHorse: { + case SpellEffect::SummonHorse: { if (RuleB(Character, PreventMountsFromZoning) || !zone->CanCastOutdoor()) { - BuffFadeByEffect(SE_SummonHorse); + BuffFadeByEffect(SpellEffect::SummonHorse); } else { SummonHorse(buffs[j1].spellid); } break; } - case SE_Silence: + case SpellEffect::Silence: { Silence(true); break; } - case SE_Amnesia: + case SpellEffect::Amnesia: { Amnesia(true); break; } - case SE_DivineAura: + case SpellEffect::DivineAura: { invulnerable = true; break; } - case SE_Invisibility2: - case SE_Invisibility: + case SpellEffect::Invisibility2: + case SpellEffect::Invisibility: { SendAppearancePacket(AppearanceType::Invisibility, Invisibility::Invisible); break; } - case SE_Levitate: + case SpellEffect::Levitate: { if (!zone->CanLevitate()) { if (!GetGM()) { SendAppearancePacket(AppearanceType::FlyMode, 0); - BuffFadeByEffect(SE_Levitate); + BuffFadeByEffect(SpellEffect::Levitate); Message(Chat::Red, "You can't levitate in this zone."); break; } @@ -728,18 +737,18 @@ void Client::CompleteConnect() break; } - case SE_AddMeleeProc: - case SE_WeaponProc: + case SpellEffect::AddMeleeProc: + case SpellEffect::WeaponProc: { AddProcToWeapon(GetProcID(buffs[j1].spellid, x1), false, 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid, buffs[j1].casterlevel, GetSpellProcLimitTimer(buffs[j1].spellid, ProcType::MELEE_PROC)); break; } - case SE_DefensiveProc: + case SpellEffect::DefensiveProc: { AddDefensiveProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid, GetSpellProcLimitTimer(buffs[j1].spellid, ProcType::DEFENSIVE_PROC)); break; } - case SE_RangedProc: + case SpellEffect::RangedProc: { AddRangedProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid, GetSpellProcLimitTimer(buffs[j1].spellid, ProcType::RANGED_PROC)); break; @@ -786,6 +795,11 @@ void Client::CompleteConnect() parse->EventPlayer(EVENT_ENTER_ZONE, this, "", 0); } + if (parse->ZoneHasQuestSub(EVENT_ENTER_ZONE)) { + std::vector args = { this }; + parse->EventZone(EVENT_ENTER_ZONE, zone, "", 0, &args); + } + DeleteEntityVariable(SEE_BUFFS_FLAG); // the way that the client deals with positions during the initial spawn struct @@ -797,28 +811,50 @@ void Client::CompleteConnect() m_last_position_before_bulk_update = GetPosition(); /* This sub event is for if a player logs in for the first time since entering world. */ - if (firstlogon == 1) { + if (ingame) { + auto e = CharacterDataRepository::FindOne( + database, + CharacterID() + ); + + bool is_first_login = e.first_login == 0; + RecordPlayerEventLog(PlayerEvent::WENT_ONLINE, PlayerEvent::EmptyEvent{}); if (parse->PlayerHasQuestSub(EVENT_CONNECT)) { - parse->EventPlayer(EVENT_CONNECT, this, "", 0); + const std::string& export_string = fmt::format( + "{} {} {}", + e.last_login, + time(nullptr) - e.last_login, + is_first_login ? 1 : 0 + ); + parse->EventPlayer(EVENT_CONNECT, this, export_string, 0); } - /* QS: PlayerLogConnectDisconnect */ - if (RuleB(QueryServ, PlayerLogConnectDisconnect)) { - std::string event_desc = StringFormat("Connect :: Logged into zoneid:%i instid:%i", GetZoneID(), GetInstanceID()); - QServ->PlayerLogEvent(Player_Log_Connect_State, CharacterID(), event_desc); - } - - /** - * Update last login since this doesn't get updated until a late save later so we can update online status - */ - database.QueryDatabase( - StringFormat( - "UPDATE `character_data` SET `last_login` = UNIX_TIMESTAMP() WHERE id = %u", + if (is_first_login) { + e.first_login = time(nullptr); + TraderRepository::DeleteWhere(database, fmt::format("`char_id` = '{}'", CharacterID())); + BuyerRepository::DeleteBuyer(database, CharacterID()); + LogTradingDetail( + "Removed trader abd buyer entries for Character ID {} on first logon to ensure table consistency.", CharacterID() - ) - ); + ); + } + + e.last_login = time(nullptr); + + const int updated = CharacterDataRepository::UpdateOne(database, e); + if (!updated) { + LogError("Failed to update login time for character_id [{}]", CharacterID()); + } + + if (IsPetNameChangeAllowed() && !RuleB(Pets, AlwaysAllowPetRename)) { + InvokeChangePetName(false); + } + + if (IsNameChangeAllowed() && !RuleB(Character, AlwaysAllowNameChange)) { + InvokeChangeNameWindow(false); + } } if(ClientVersion() == EQ::versions::ClientVersion::RoF2 && RuleB(Parcel, EnableParcelMerchants)) { @@ -859,9 +895,9 @@ void Client::CompleteConnect() entity_list.SendFindableNPCList(this); if (IsInAGuild()) { - if (firstlogon == 1) { + if (ingame) { guild_mgr.UpdateDbMemberOnline(CharacterID(), true); - guild_mgr.SendToWorldSendGuildMembersList(GuildID()); + SendGuildMembersList(); } guild_mgr.SendGuildMemberUpdateToWorld(GetName(), GuildID(), zone->GetZoneID(), time(nullptr)); @@ -902,35 +938,34 @@ 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); CastToClient()->FastQueuePacket(&outapp); } - if (ClientVersion() >= EQ::versions::ClientVersion::RoF) { - SendBulkBazaarTraders(); - } - // TODO: load these states // We at least will set them to the correct state for now if (m_ClientVersionBit & EQ::versions::maskUFAndLater && GetPet()) { - SetPetCommandState(PET_BUTTON_SIT, 0); - SetPetCommandState(PET_BUTTON_STOP, 0); - SetPetCommandState(PET_BUTTON_REGROUP, 0); - SetPetCommandState(PET_BUTTON_FOLLOW, 1); - SetPetCommandState(PET_BUTTON_GUARD, 0); + SetPetCommandState(PetButton::Sit, PetButtonState::Off); + SetPetCommandState(PetButton::Stop, PetButtonState::Off); + SetPetCommandState(PetButton::Regroup, PetButtonState::Off); + SetPetCommandState(PetButton::Follow, PetButtonState::On); + SetPetCommandState(PetButton::Guard, PetButtonState::Off); // Taunt saved on client side for logging on with pet // In our db for when we zone. - SetPetCommandState(PET_BUTTON_HOLD, 0); - SetPetCommandState(PET_BUTTON_GHOLD, 0); - SetPetCommandState(PET_BUTTON_FOCUS, 0); - SetPetCommandState(PET_BUTTON_SPELLHOLD, 0); + SetPetCommandState(PetButton::Hold, PetButtonState::Off); + SetPetCommandState(PetButton::GreaterHold, PetButtonState::Off); + SetPetCommandState(PetButton::Focus, PetButtonState::Off); + SetPetCommandState(PetButton::SpellHold, PetButtonState::Off); } database.LoadAuras(this); // this ends up spawning them so probably safer to load this later (here) @@ -946,6 +981,11 @@ void Client::CompleteConnect() ShowDevToolsMenu(); } + auto z = GetZone(GetZoneID(), GetInstanceVersion()); + if (z && z->shard_at_player_count > 0 && !RuleB(Zone, ZoneShardQuestMenuOnly)) { + ShowZoneShardMenu(); + } + // shared tasks memberlist if (RuleB(TaskSystem, EnableTaskSystem) && GetTaskState()->HasActiveSharedTask()) { @@ -969,6 +1009,16 @@ void Client::CompleteConnect() RecordStats(); AutoGrantAAPoints(); + // set initial position for mob tracking + m_last_seen_mob_position.reserve(entity_list.GetMobList().size()); + for (auto& mob : entity_list.GetMobList()) { + if (!mob.second->IsNPC()) { + continue; + } + + m_last_seen_mob_position[mob.second->GetID()] = mob.second->GetPosition(); + } + // enforce some rules.. if (!CanEnterZone()) { LogInfo("Kicking character [{}] from zone, not allowed here (missing requirements)", GetCleanName()); @@ -1281,24 +1331,36 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) /* Load Character Data */ query = fmt::format( - "SELECT `lfp`, `lfg`, `xtargets`, `firstlogon`, `guild_id`, `rank`, `exp_enabled`, `tribute_enable`, `extra_haste` FROM `character_data` LEFT JOIN `guild_members` ON `id` = `char_id` WHERE `id` = {}", + "SELECT `lfp`, `lfg`, `xtargets`, `first_login`, `guild_id`, `rank`, `exp_enabled`, `tribute_enable`, `extra_haste`, `illusion_block`, `ingame` FROM `character_data` LEFT JOIN `guild_members` ON `id` = `char_id` WHERE `id` = {}", cid ); auto results = database.QueryDatabase(query); for (auto row : results) { if (row[4] && Strings::ToInt(row[4]) > 0) { - guild_id = Strings::ToInt(row[4]); - guildrank = row[5] ? Strings::ToInt(row[5]) : GUILD_RANK_NONE; + guild_id = Strings::ToInt(row[4]); + guildrank = row[5] ? Strings::ToInt(row[5]) : GUILD_RANK_NONE; guild_tribute_opt_in = row[7] ? Strings::ToBool(row[7]) : 0; } SetEXPEnabled(Strings::ToBool(row[6])); SetExtraHaste(Strings::ToInt(row[8]), false); + SetIllusionBlock(Strings::ToBool(row[9])); - if (LFP) { LFP = Strings::ToInt(row[0]); } - if (LFG) { LFG = Strings::ToInt(row[1]); } - if (row[3]) - firstlogon = Strings::ToInt(row[3]); + if (LFP) { + LFP = Strings::ToInt(row[0]); + } + + if (LFG) { + LFG = Strings::ToInt(row[1]); + } + + if (row[3]) { + first_login = Strings::ToUnsignedInt(row[3]); + } + + if (row[10]) { + ingame = Strings::ToBool(row[10]); + } } if (RuleB(Character, SharedBankPlat)) @@ -1308,7 +1370,7 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) // set to full support in case they're a gm with items in disabled expansion slots...but, have their gm flag off... // item loss will occur when they use the 'empty' slots, if this is not done m_inv.SetGMInventory(true); - loaditems = database.GetInventory(cid, &m_inv); /* Load Character Inventory */ + loaditems = database.GetInventory(this); /* Load Character Inventory */ database.LoadCharacterBandolier(cid, &m_pp); /* Load Character Bandolier */ database.LoadCharacterBindPoint(cid, &m_pp); /* Load Character Bind */ database.LoadCharacterMaterialColor(cid, &m_pp); /* Load Character Material */ @@ -1323,6 +1385,7 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) database.LoadCharacterLeadershipAbilities(cid, &m_pp); /* Load Character Leadership AA's */ database.LoadCharacterTribute(this); /* Load CharacterTribute */ database.LoadCharacterEXPModifier(this); /* Load Character EXP Modifier */ + database.LoadCharacterTitleSets(this); /* Load Character Title Sets */ // this pattern is strange // this is remnants of the old way of doing things @@ -1413,7 +1476,8 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) drakkin_details = m_pp.drakkin_details; // Load Data Buckets - DataBucket::GetDataBuckets(this); + ClearDataBucketCache(); + LoadDataBucketsCache(); // Max Level for Character:PerCharacterQglobalMaxLevel and Character:PerCharacterBucketMaxLevel uint8 client_max_level = 0; @@ -1451,23 +1515,23 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) switch (race) { - case OGRE: + case Race::Ogre: size = 9; break; - case TROLL: + case Race::Troll: size = 8; break; - case VAHSHIR: case BARBARIAN: + case Race::VahShir: case Race::Barbarian: size = 7; break; - case HUMAN: case HIGH_ELF: case ERUDITE: case IKSAR: case DRAKKIN: + case Race::Human: case Race::HighElf: case Race::Erudite: case Race::Iksar: case Race::Drakkin: size = 6; break; - case HALF_ELF: + case Race::HalfElf: size = 5.5; break; - case WOOD_ELF: case DARK_ELF: case FROGLOK: + case Race::WoodElf: case Race::DarkElf: case Race::Froglok2: size = 5; break; - case DWARF: + case Race::Dwarf: size = 4; break; - case HALFLING: + case Race::Halfling: size = 3.5; break; - case GNOME: + case Race::Gnome: size = 3; break; default: size = 0; @@ -1487,6 +1551,11 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) LogError("Error loading AA points for [{}]", GetName()); } + if (RuleB(Bots, Enabled)) { + LoadDefaultBotSettings(); + database.botdb.LoadBotSettings(this); + } + if (SPDAT_RECORDS > 0) { for (uint32 z = 0; z < EQ::spells::SPELL_GEM_COUNT; z++) { if (m_pp.mem_spells[z] >= (uint32)SPDAT_RECORDS) @@ -1583,7 +1652,6 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) LFG = false; } - /* Load Bots */ if (RuleB(Bots, Enabled)) { database.botdb.LoadOwnerOptions(this); // TODO: mod below function for loading spawned botgroups @@ -1639,18 +1707,13 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) m_pp.abilitySlotRefresh = 0; } -#ifdef _EQDEBUG - printf("Dumping inventory on load:\n"); - m_inv.dumpEntireInventory(); -#endif - /* Reset to max so they dont drown on zone in if its underwater */ m_pp.air_remaining = 60; /* Check for PVP Zone status*/ if (zone->IsPVPZone()) m_pp.pvp = 1; /* Time entitled on Account: Move to account */ - m_pp.timeentitledonaccount = database.GetTotalTimeEntitledOnAccount(AccountID()) / 1440; + m_pp.timeentitledonaccount = CharacterDataRepository::GetTotalTimePlayed(database, AccountID()) / 1440; /* Reset rest timer if the durations have been lowered in the database */ if ((m_pp.RestTimer > RuleI(Character, RestRegenTimeToActivate)) && (m_pp.RestTimer > RuleI(Character, RestRegenRaidTimeToActivate))) m_pp.RestTimer = 0; @@ -1686,7 +1749,7 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) // Taunt persists when zoning on newer clients, overwrite default. if (m_ClientVersionBit & EQ::versions::maskUFAndLater) { - if (!firstlogon) { + if (!ingame) { pet->SetTaunting(m_petinfo.taunting); } } @@ -1762,8 +1825,6 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) m_dynamic_zone_ids.emplace_back(entry.dynamic_zone_id); } - m_expedition_id = ExpeditionsRepository::GetIDByMemberID(database, CharacterID()); - auto dz = zone->GetDynamicZone(); if (dz && dz->GetSafeReturnLocation().zone_id != 0) { @@ -1862,8 +1923,9 @@ void Client::Handle_OP_AAAction(const EQApplicationPacket *app) PurchaseAlternateAdvancementRank(action->ability); } else if (action->action == aaActionDisableEXP) { //Turn Off AA Exp - if (m_epp.perAA > 0) + if (m_epp.perAA > 0) { MessageString(Chat::White, AA_OFF); + } m_epp.perAA = 0; SendAlternateAdvancementStats(); @@ -2023,38 +2085,38 @@ void Client::Handle_OP_AdventureMerchantPurchase(const EQApplicationPacket *app) return; } - if (item->LDoNTheme <= LDoNThemeBits::TAKBit) { + if (item->LDoNTheme <= LDoNTheme::TAKBit) { uint32 ldon_theme; - if (item->LDoNTheme & LDoNThemeBits::TAKBit) { + if (item->LDoNTheme & LDoNTheme::TAKBit) { if (m_pp.ldon_points_tak < item_cost) { cannot_afford = true; - ldon_theme = LDoNThemes::TAK; + ldon_theme = LDoNTheme::TAK; } - } else if (item->LDoNTheme & LDoNThemeBits::RUJBit) { + } else if (item->LDoNTheme & LDoNTheme::RUJBit) { if (m_pp.ldon_points_ruj < item_cost) { cannot_afford = true; - ldon_theme = LDoNThemes::RUJ; + ldon_theme = LDoNTheme::RUJ; } - } else if (item->LDoNTheme & LDoNThemeBits::MMCBit) { + } else if (item->LDoNTheme & LDoNTheme::MMCBit) { if (m_pp.ldon_points_mmc < item_cost) { cannot_afford = true; - ldon_theme = LDoNThemes::MMC; + ldon_theme = LDoNTheme::MMC; } - } else if (item->LDoNTheme & LDoNThemeBits::MIRBit) { + } else if (item->LDoNTheme & LDoNTheme::MIRBit) { if (m_pp.ldon_points_mir < item_cost) { cannot_afford = true; - ldon_theme = LDoNThemes::MIR; + ldon_theme = LDoNTheme::MIR; } - } else if (item->LDoNTheme & LDoNThemeBits::GUKBit) { + } else if (item->LDoNTheme & LDoNTheme::GUKBit) { if (m_pp.ldon_points_guk < item_cost) { cannot_afford = true; - ldon_theme = LDoNThemes::GUK; + ldon_theme = LDoNTheme::GUK; } } merchant_type = fmt::format( "{} Point{}", - EQ::constants::GetLDoNThemeName(ldon_theme), + LDoNTheme::GetName(ldon_theme), item_cost != 1 ? "s" : "" ); } @@ -2129,20 +2191,7 @@ void Client::Handle_OP_AdventureMerchantPurchase(const EQApplicationPacket *app) if (item->MaxCharges != 0) charges = item->MaxCharges; - if (RuleB(Character, EnableDiscoveredItems) && !IsDiscovered(item->ID)) { - if (!GetGM()) { - DiscoverItem(item->ID); - } else { - const std::string& item_link = database.CreateItemLink(item->ID); - Message( - Chat::White, - fmt::format( - "Your GM flag prevents {} from being added to discovered items.", - item_link - ).c_str() - ); - } - } + CheckItemDiscoverability(item->ID); EQ::ItemInstance *inst = database.CreateItem(item, charges); if (!AutoPutLootInInventory(*inst, true, true)) @@ -2198,19 +2247,19 @@ void Client::Handle_OP_AdventureMerchantRequest(const EQApplicationPacket *app) item = database.GetItem(ml.item); if (item) { - uint32 theme = LDoNThemes::Unused; - if (item->LDoNTheme > LDoNThemeBits::TAKBit) { - theme = LDoNThemes::Unused; - } else if (item->LDoNTheme & LDoNThemeBits::TAKBit) { - theme = LDoNThemes::TAK; - } else if (item->LDoNTheme & LDoNThemeBits::RUJBit) { - theme = LDoNThemes::RUJ; - } else if (item->LDoNTheme & LDoNThemeBits::MMCBit) { - theme = LDoNThemes::MMC; - } else if (item->LDoNTheme & LDoNThemeBits::MIRBit) { - theme = LDoNThemes::MIR; - } else if (item->LDoNTheme & LDoNThemeBits::GUKBit) { - theme = LDoNThemes::GUK; + uint32 theme = LDoNTheme::Unused; + if (item->LDoNTheme > LDoNTheme::TAKBit) { + theme = LDoNTheme::Unused; + } else if (item->LDoNTheme & LDoNTheme::TAKBit) { + theme = LDoNTheme::TAK; + } else if (item->LDoNTheme & LDoNTheme::RUJBit) { + theme = LDoNTheme::RUJ; + } else if (item->LDoNTheme & LDoNTheme::MMCBit) { + theme = LDoNTheme::MMC; + } else if (item->LDoNTheme & LDoNTheme::MIRBit) { + theme = LDoNTheme::MIR; + } else if (item->LDoNTheme & LDoNTheme::GUKBit) { + theme = LDoNTheme::GUK; } ss << "^" << item->Name << "|"; ss << item->ID << "|"; @@ -2653,12 +2702,6 @@ void Client::Handle_OP_AltCurrencyPurchase(const EQApplicationPacket *app) return; } - /* QS: PlayerLogAlternateCurrencyTransactions :: Merchant Purchase */ - if (RuleB(QueryServ, PlayerLogAlternateCurrencyTransactions)) { - std::string event_desc = StringFormat("Merchant Purchase :: Spent alt_currency_id:%i cost:%i for itemid:%i in zoneid:%i instid:%i", alt_cur_id, cost, item->ID, GetZoneID(), GetInstanceID()); - QServ->PlayerLogEvent(Player_Log_Alternate_Currency_Transactions, CharacterID(), event_desc); - } - if (parse->PlayerHasQuestSub(EVENT_ALT_CURRENCY_MERCHANT_BUY)) { const auto& export_string = fmt::format( "{} {} {} {} {}", @@ -2678,7 +2721,7 @@ void Client::Handle_OP_AltCurrencyPurchase(const EQApplicationPacket *app) charges = item->MaxCharges; } - if (player_event_logs.IsEventEnabled(PlayerEvent::MERCHANT_PURCHASE)) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::MERCHANT_PURCHASE)) { auto e = PlayerEvent::MerchantPurchaseEvent{ .npc_id = tar->GetNPCTypeID(), .merchant_name = tar->GetCleanName(), @@ -2695,20 +2738,7 @@ void Client::Handle_OP_AltCurrencyPurchase(const EQApplicationPacket *app) RecordPlayerEventLog(PlayerEvent::MERCHANT_PURCHASE, e); } - if (RuleB(Character, EnableDiscoveredItems) && !IsDiscovered(item->ID)) { - if (!GetGM()) { - DiscoverItem(item->ID); - } else { - const std::string& item_link = database.CreateItemLink(item->ID); - Message( - Chat::White, - fmt::format( - "Your GM flag prevents {} from being added to discovered items.", - item_link - ).c_str() - ); - } - } + CheckItemDiscoverability(item->ID); EQ::ItemInstance *inst = database.CreateItem(item, charges); if (!AutoPutLootInInventory(*inst, true, true)) @@ -2744,12 +2774,6 @@ void Client::Handle_OP_AltCurrencyReclaim(const EQApplicationPacket *app) uint32 removed = NukeItem(item_id, invWhereWorn | invWherePersonal | invWhereCursor); if (removed > 0) { AddAlternateCurrencyValue(reclaim->currency_id, removed); - - /* QS: PlayerLogAlternateCurrencyTransactions :: Item to Currency */ - if (RuleB(QueryServ, PlayerLogAlternateCurrencyTransactions)) { - std::string event_desc = StringFormat("Reclaim :: Item to Currency :: alt_currency_id:%i amount:%i to currency tab in zoneid:%i instid:%i", reclaim->currency_id, removed, GetZoneID(), GetInstanceID()); - QServ->PlayerLogEvent(Player_Log_Alternate_Currency_Transactions, CharacterID(), event_desc); - } } } /* Cursor to Item storage */ @@ -2768,11 +2792,6 @@ void Client::Handle_OP_AltCurrencyReclaim(const EQApplicationPacket *app) SummonItem(item_id, reclaim->count, 0, 0, 0, 0, 0, 0, false, EQ::invslot::slotCursor); AddAlternateCurrencyValue(reclaim->currency_id, -((int)reclaim->count)); } - /* QS: PlayerLogAlternateCurrencyTransactions :: Cursor to Item Storage */ - if (RuleB(QueryServ, PlayerLogAlternateCurrencyTransactions)) { - std::string event_desc = StringFormat("Reclaim :: Cursor to Item :: alt_currency_id:%i amount:-%i in zoneid:%i instid:%i", reclaim->currency_id, reclaim->count, GetZoneID(), GetInstanceID()); - QServ->PlayerLogEvent(Player_Log_Alternate_Currency_Transactions, CharacterID(), event_desc); - } } } @@ -2871,12 +2890,6 @@ void Client::Handle_OP_AltCurrencySell(const EQApplicationPacket *app) sell->cost = cost; - /* QS: PlayerLogAlternateCurrencyTransactions :: Sold to Merchant*/ - if (RuleB(QueryServ, PlayerLogAlternateCurrencyTransactions)) { - std::string event_desc = StringFormat("Sold to Merchant :: itemid:%u npcid:%u alt_currency_id:%u cost:%u in zoneid:%u instid:%i", item->ID, npc_id, alt_cur_id, cost, GetZoneID(), GetInstanceID()); - QServ->PlayerLogEvent(Player_Log_Alternate_Currency_Transactions, CharacterID(), event_desc); - } - if (parse->PlayerHasQuestSub(EVENT_ALT_CURRENCY_MERCHANT_SELL)) { const auto& export_string = fmt::format( "{} {} {} {} {}", @@ -2893,7 +2906,7 @@ void Client::Handle_OP_AltCurrencySell(const EQApplicationPacket *app) FastQueuePacket(&outapp); uint64 new_balance = AddAlternateCurrencyValue(alt_cur_id, cost); - if (player_event_logs.IsEventEnabled(PlayerEvent::MERCHANT_SELL)) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::MERCHANT_SELL)) { auto e = PlayerEvent::MerchantSellEvent{ .npc_id = tar->GetNPCTypeID(), .merchant_name = tar->GetCleanName(), @@ -3232,11 +3245,11 @@ void Client::Handle_OP_AugmentItem(const EQApplicationPacket *app) if (!new_aug) { // Shouldn't get the OP code without the augment on the user's cursor, but maybe it's h4x. LogError("AugmentItem OpCode with 'Insert' or 'Swap' action received, but no augment on client's cursor"); Message(Chat::Red, "Error: No augment found on cursor for inserting."); - return; + break; } else { if (!RuleB(Inventory, AllowMultipleOfSameAugment) && tobe_auged->ContainsAugmentByID(new_aug->GetID())) { Message(Chat::Red, "Error: Cannot put multiple of the same augment in an item."); - return; + break; } if ( @@ -3251,30 +3264,14 @@ void Client::Handle_OP_AugmentItem(const EQApplicationPacket *app) args.push_back(old_aug); if (parse->ItemHasQuestSub(tobe_auged, EVENT_UNAUGMENT_ITEM)) { - parse->EventItem( - EVENT_UNAUGMENT_ITEM, - this, - tobe_auged, - nullptr, - "", - in_augment->augment_index, - &args - ); + parse->EventItem(EVENT_UNAUGMENT_ITEM, this, tobe_auged, nullptr, "", in_augment->augment_index, &args); } args.assign(1, tobe_auged); args.push_back(false); if (parse->ItemHasQuestSub(old_aug, EVENT_AUGMENT_REMOVE)) { - parse->EventItem( - EVENT_AUGMENT_REMOVE, - this, - old_aug, - nullptr, - "", - in_augment->augment_index, - &args - ); + parse->EventItem(EVENT_AUGMENT_REMOVE, this, old_aug, nullptr, "", in_augment->augment_index, &args); } if (parse->PlayerHasQuestSub(EVENT_AUGMENT_REMOVE_CLIENT)) { @@ -3306,29 +3303,13 @@ void Client::Handle_OP_AugmentItem(const EQApplicationPacket *app) args.push_back(aug); if (parse->ItemHasQuestSub(tobe_auged, EVENT_AUGMENT_ITEM)) { - parse->EventItem( - EVENT_AUGMENT_ITEM, - this, - tobe_auged, - nullptr, - "", - in_augment->augment_index, - &args - ); + parse->EventItem(EVENT_AUGMENT_ITEM, this, tobe_auged, nullptr, "", in_augment->augment_index, &args); } args.assign(1, tobe_auged); if (parse->ItemHasQuestSub(aug, EVENT_AUGMENT_INSERT)) { - parse->EventItem( - EVENT_AUGMENT_INSERT, - this, - aug, - nullptr, - "", - in_augment->augment_index, - &args - ); + parse->EventItem(EVENT_AUGMENT_INSERT, this, aug, nullptr, "", in_augment->augment_index, &args); } args.push_back(aug); @@ -3352,7 +3333,7 @@ void Client::Handle_OP_AugmentItem(const EQApplicationPacket *app) in_augment->augment_index ).c_str() ); - return; + break; } item_one_to_push = tobe_auged->Clone(); @@ -3398,30 +3379,14 @@ void Client::Handle_OP_AugmentItem(const EQApplicationPacket *app) args.push_back(aug); if (parse->ItemHasQuestSub(tobe_auged, EVENT_UNAUGMENT_ITEM)) { - parse->EventItem( - EVENT_UNAUGMENT_ITEM, - this, - tobe_auged, - nullptr, - "", - in_augment->augment_index, - &args - ); + parse->EventItem(EVENT_UNAUGMENT_ITEM, this, tobe_auged, nullptr, "", in_augment->augment_index, &args); } args.assign(1, tobe_auged); args.push_back(false); if (parse->ItemHasQuestSub(aug, EVENT_AUGMENT_REMOVE)) { - parse->EventItem( - EVENT_AUGMENT_REMOVE, - this, - aug, - nullptr, - "", - in_augment->augment_index, - &args - ); + parse->EventItem(EVENT_AUGMENT_REMOVE, this, aug, nullptr, "", in_augment->augment_index, &args); } args.push_back(aug); @@ -3440,7 +3405,7 @@ void Client::Handle_OP_AugmentItem(const EQApplicationPacket *app) } } else { Message(Chat::Red, "Error: Could not find augmentation to remove at index %i. Aborting.", in_augment->augment_index); - return; + break; } old_aug = tobe_auged->RemoveAugment(in_augment->augment_index); @@ -3473,7 +3438,7 @@ void Client::Handle_OP_AugmentItem(const EQApplicationPacket *app) if (!PutItemInInventory(EQ::invslot::slotCursor, *item_two_to_push, true)) { LogError("Problem returning augment to player's cursor after safe removal"); Message(Chat::Yellow, "Error: Failed to return augment after removal from item!"); - return; + break; } } break; @@ -3486,30 +3451,14 @@ void Client::Handle_OP_AugmentItem(const EQApplicationPacket *app) args.push_back(aug); if (parse->ItemHasQuestSub(tobe_auged, EVENT_UNAUGMENT_ITEM)) { - parse->EventItem( - EVENT_UNAUGMENT_ITEM, - this, - tobe_auged, - nullptr, - "", - in_augment->augment_index, - &args - ); + parse->EventItem(EVENT_UNAUGMENT_ITEM, this, tobe_auged, nullptr, "", in_augment->augment_index, &args); } args.assign(1, tobe_auged); args.push_back(true); if (parse->ItemHasQuestSub(aug, EVENT_AUGMENT_REMOVE)) { - parse->EventItem( - EVENT_AUGMENT_REMOVE, - this, - aug, - nullptr, - "", - in_augment->augment_index, - &args - ); + parse->EventItem(EVENT_AUGMENT_REMOVE, this, aug, nullptr, "", in_augment->augment_index, &args); } args.push_back(aug); @@ -3534,7 +3483,7 @@ void Client::Handle_OP_AugmentItem(const EQApplicationPacket *app) in_augment->augment_index ).c_str() ); - return; + break; } tobe_auged->DeleteAugment(in_augment->augment_index); @@ -3555,6 +3504,7 @@ void Client::Handle_OP_AugmentItem(const EQApplicationPacket *app) if (material != EQ::textures::materialInvalid) { SendWearChange(material); } + break; default: // Unknown LogInventory( @@ -3568,9 +3518,12 @@ void Client::Handle_OP_AugmentItem(const EQApplicationPacket *app) ); break; } + safe_delete(item_one_to_push); + safe_delete(item_two_to_push); } else { Object::HandleAugmentation(this, in_augment, m_tradeskill_object); // Delegate to tradeskill object to perform combine } + return; } @@ -3985,6 +3938,10 @@ void Client::Handle_OP_BazaarSearch(const EQApplicationPacket *app) SendBazaarWelcome(); break; } + case FirstOpenSearch: { + SendBulkBazaarTraders(); + break; + } default: { LogError("Malformed BazaarSearch_Struct packet received, ignoring\n"); } @@ -4302,7 +4259,7 @@ void Client::Handle_OP_BuffRemoveRequest(const EQApplicationPacket *app) uint16 SpellID = m->GetSpellIDFromSlot(brrs->SlotID); - if (SpellID && (GetGM() || ((IsBeneficialSpell(SpellID) || IsEffectInSpell(SpellID, SE_BindSight)) && !spells[SpellID].no_remove))) { + if (SpellID && (GetGM() || ((IsBeneficialSpell(SpellID) || IsEffectInSpell(SpellID, SpellEffect::BindSight)) && !spells[SpellID].no_remove))) { m->BuffFadeBySlot(brrs->SlotID, true); } } @@ -4336,10 +4293,23 @@ void Client::Handle_OP_Camp(const EQApplicationPacket *app) if (GetGM()) { - OnDisconnect(true); + if (RuleB(Character, EnableHackedFastCampForGM)) + { + camp_timer.Start(100, true); + } + else { + OnDisconnect(true); + } + return; } + camp_timer.Start(29000, true); + + if (RuleB(Bots, Enabled)) { + bot_camp_timer.Start((RuleI(Bots, CampTimer) * 1000), true); + } + return; } @@ -4417,6 +4387,14 @@ void Client::Handle_OP_CastSpell(const EQApplicationPacket *app) m_TargetRing = glm::vec3(castspell->x_pos, castspell->y_pos, castspell->z_pos); + if (castspell->spell_id && IsValidSpell(castspell->spell_id)) { + bool is_non_combat_zone = !zone->CanDoCombat() || zone->BuffTimersSuspended(); + bool is_excluded_reset = is_non_combat_zone && IsBardSong(castspell->spell_id); + if (!is_excluded_reset) { + ResetAFKTimer(); + } + } + LogSpells("OP CastSpell: slot [{}] spell [{}] target [{}] inv [{}]", castspell->slot, castspell->spell_id, castspell->target_id, (unsigned long)castspell->inventoryslot); CastingSlot slot = static_cast(castspell->slot); @@ -4479,14 +4457,7 @@ void Client::Handle_OP_CastSpell(const EQApplicationPacket *app) int i = 0; if (parse->ItemHasQuestSub(p_inst, EVENT_ITEM_CLICK_CAST)) { - i = parse->EventItem( - EVENT_ITEM_CLICK_CAST, - this, - p_inst, - nullptr, - "", - castspell->inventoryslot - ); + i = parse->EventItem(EVENT_ITEM_CLICK_CAST, this, p_inst, nullptr, "", castspell->inventoryslot); } if (parse->PlayerHasQuestSub(EVENT_ITEM_CLICK_CAST_CLIENT)) { @@ -4518,14 +4489,7 @@ void Client::Handle_OP_CastSpell(const EQApplicationPacket *app) int i = 0; if (parse->ItemHasQuestSub(p_inst, EVENT_ITEM_CLICK_CAST)) { - i = parse->EventItem( - EVENT_ITEM_CLICK_CAST, - this, - p_inst, - nullptr, - "", - castspell->inventoryslot - ); + i = parse->EventItem(EVENT_ITEM_CLICK_CAST, this, p_inst, nullptr, "", castspell->inventoryslot); } if (parse->PlayerHasQuestSub(EVENT_ITEM_CLICK_CAST_CLIENT)) { @@ -4614,6 +4578,12 @@ void Client::Handle_OP_ChannelMessage(const EQApplicationPacket *app) return; } + // reject automatic AFK messages from resetting /afk + std::string message = cm->message; + if (!Strings::Contains(message, "Sorry, I am A.F.K.")) { + ResetAFKTimer(); + } + if (IsAIControlled() && !GetGM()) { Message(Chat::Red, "You try to speak but can't move your mouth!"); return; @@ -4628,6 +4598,27 @@ void Client::Handle_OP_ChannelMessage(const EQApplicationPacket *app) return; } +void Client::Handle_OP_ChangePetName(const EQApplicationPacket *app) { + if (app->size != sizeof(ChangePetName_Struct)) { + LogError("Got OP_ChangePetName of incorrect size. Expected [{}], got [{}].", sizeof(ChangePetName_Struct), app->size); + return; + } + + auto p = (ChangePetName_Struct *) app->pBuffer; + if (!IsPetNameChangeAllowed()) { + p->response_code = ChangeNameResponse::Ineligible; + QueuePacket(app); + return; + } + + p->response_code = ChangeNameResponse::Denied; + if (ChangePetName(p->new_pet_name)) { + p->response_code = ChangeNameResponse::Accepted; + } + + QueuePacket(app); +} + void Client::Handle_OP_ClearBlockedBuffs(const EQApplicationPacket *app) { if (!RuleB(Spells, EnableBlockedBuffs)) @@ -4723,6 +4714,12 @@ void Client::Handle_OP_ClickDoor(const EQApplicationPacket *app) quest_return = parse->EventPlayer(EVENT_CLICK_DOOR, this, std::to_string(cd->doorid), 0, &args); } + if (parse->ZoneHasQuestSub(EVENT_CLICK_DOOR)) { + std::vector args = { currentdoor, this }; + + quest_return = parse->EventZone(EVENT_CLICK_DOOR, zone, std::to_string(cd->doorid), 0, &args); + } + if (quest_return == 0) { currentdoor->HandleClick(this, 0); } @@ -4755,6 +4752,11 @@ void Client::Handle_OP_ClickObject(const EQApplicationPacket *app) parse->EventPlayer(EVENT_CLICK_OBJECT, this, std::to_string(click_object->drop_id), GetID(), &args); } + if (parse->ZoneHasQuestSub(EVENT_CLICK_OBJECT)) { + std::vector args = { object, this }; + parse->EventZone(EVENT_CLICK_OBJECT, zone, std::to_string(click_object->drop_id), GetID(), &args); + } + if (IsDevToolsEnabled()) { SetObjectToolEntityId(entity->GetID()); ObjectManipulation::CommandHeader(this); @@ -4871,11 +4873,10 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) { auto boat_delta = glm::vec4(ppu->delta_x, ppu->delta_y, ppu->delta_z, EQ10toFloat(ppu->delta_heading)); cmob->SetDelta(boat_delta); - auto outapp = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); - PlayerPositionUpdateServer_Struct *ppus = (PlayerPositionUpdateServer_Struct *) outapp->pBuffer; + static EQApplicationPacket outapp(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); + auto *ppus = (PlayerPositionUpdateServer_Struct *) outapp.pBuffer; cmob->MakeSpawnUpdate(ppus); - entity_list.QueueCloseClients(cmob, outapp, true, 300, this, false); - safe_delete(outapp); + entity_list.QueueCloseClients(cmob, &outapp, true, 300, this, false); /* Update the boat's position on the server, without sending an update */ cmob->GMMove(ppu->x_pos, ppu->y_pos, ppu->z_pos, EQ12toFloat(ppu->heading)); @@ -4982,9 +4983,6 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) { m_Proximity = glm::vec3(cx, cy, cz); } - /* Update internal state */ - m_Delta = glm::vec4(ppu->delta_x, ppu->delta_y, ppu->delta_z, EQ10toFloat(ppu->delta_heading)); - if (RuleB(Skills, TrackingAutoRefreshSkillUps) && IsTracking() && ((m_Position.x != cx) || (m_Position.y != cy))) { if (zone->random.Real(0, 100) < 70)//should be good CheckIncreaseSkill(EQ::skills::SkillTracking, nullptr, -20); @@ -5018,22 +5016,28 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) { rewind_timer.Start(30000, true); } + glm::vec4 prevDelta = m_Delta; // SetMoving clears m_Delta SetMoving(!(cy == m_Position.y && cx == m_Position.x)); + if (RuleB(Character, EnableAutoAFK)) { + CheckAutoIdleAFK(ppu); + } + CheckClientToNpcAggroTimer(); if (m_mob_check_moving_timer.Check()) { CheckScanCloseMobsMovingTimer(); } - CheckSendBulkClientPositionUpdate(); - - int32 new_animation = ppu->animation; + CheckSendBulkNpcPositions(); /* Update internal server position from what the client has sent */ - m_Position.x = cx; - m_Position.y = cy; - m_Position.z = cz; + glm::vec4 prevPosition = m_Position; + m_Position = glm::vec4(cx, cy, cz, new_heading); + m_Delta = glm::vec4(ppu->delta_x, ppu->delta_y, ppu->delta_z, EQ10toFloat(ppu->delta_heading)); + int32 prevAnimation = ppu->animation; + animation = ppu->animation; + bool positionUpdated = m_Position != prevPosition || m_Delta != prevDelta || m_Delta != glm::vec4(0.0f) || prevAnimation != animation; /* Visual Debugging */ if (RuleB(Character, OPClientUpdateVisualDebug)) { @@ -5043,35 +5047,28 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) { } /* Only feed real time updates when client is moving */ - if (IsMoving() || new_heading != m_Position.w || new_animation != animation) { - - animation = ppu->animation; - m_Position.w = new_heading; - + if (positionUpdated) { /* Broadcast update to other clients */ - auto outapp = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); - PlayerPositionUpdateServer_Struct *position_update = (PlayerPositionUpdateServer_Struct *) outapp->pBuffer; + static EQApplicationPacket outapp(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); + PlayerPositionUpdateServer_Struct *position_update = (PlayerPositionUpdateServer_Struct *) outapp.pBuffer; MakeSpawnUpdate(position_update); if (gm_hide_me) { - entity_list.QueueClientsStatus(this, outapp, true, Admin(), AccountStatus::Max); + entity_list.QueueClientsStatus(this, &outapp, true, Admin(), AccountStatus::Max); } else { - entity_list.QueueCloseClients(this, outapp, true, RuleI(Range, ClientPositionUpdates), nullptr, true); + entity_list.QueueCloseClients(this, &outapp, true, RuleI(Range, ClientPositionUpdates), nullptr, true); } - /* Always send position updates to group - send when beyond normal ClientPositionUpdate range */ Group *group = GetGroup(); Raid *raid = GetRaid(); if (raid) { - raid->QueueClients(this, outapp, true, true, (RuleI(Range, ClientPositionUpdates) * -1)); + raid->QueueClients(this, &outapp, true, true, (RuleI(Range, ClientPositionUpdates) * -1)); } else if (group) { - group->QueueClients(this, outapp, true, true, (RuleI(Range, ClientPositionUpdates) * -1)); + group->QueueClients(this, &outapp, true, true, (RuleI(Range, ClientPositionUpdates) * -1)); } - - safe_delete(outapp); } if (zone->watermap) { @@ -5081,14 +5078,13 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) { // Dismount horses when entering water if (GetHorseId() && RuleB(Character, DismountWater)) { SetHorseId(0); - BuffFadeByEffect(SE_SummonHorse); + BuffFadeByEffect(SpellEffect::SummonHorse); } } CheckRegionTypeChanges(); } CheckVirtualZoneLines(); - } void Client::Handle_OP_CombatAbility(const EQApplicationPacket *app) @@ -5546,7 +5542,7 @@ void Client::Handle_OP_CrashDump(const EQApplicationPacket *app) void Client::Handle_OP_CreateObject(const EQApplicationPacket *app) { - if (LogSys.log_settings[Logs::Inventory].is_category_enabled) + if (EQEmuLogSys::Instance()->log_settings[Logs::Inventory].is_category_enabled) LogInventory("Handle_OP_CreateObject() [psize: [{}]] [{}]", app->size, DumpPacketToString(app).c_str()); DropItem(EQ::invslot::slotCursor); @@ -5720,12 +5716,19 @@ void Client::Handle_OP_DeleteItem(const EQApplicationPacket *app) SetIntoxication(GetIntoxication()+IntoxicationIncrease); - if (player_event_logs.IsEventEnabled(PlayerEvent::ITEM_DESTROY) && inst->GetItem()) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::ITEM_DESTROY) && inst->GetItem()) { auto e = PlayerEvent::DestroyItemEvent{ - .item_id = inst->GetItem()->ID, - .item_name = inst->GetItem()->Name, - .charges = inst->GetCharges(), - .reason = "Client deleted", + .item_id = inst->GetItem()->ID, + .item_name = inst->GetItem()->Name, + .charges = inst->GetCharges(), + .augment_1_id = inst->GetAugmentItemID(0), + .augment_2_id = inst->GetAugmentItemID(1), + .augment_3_id = inst->GetAugmentItemID(2), + .augment_4_id = inst->GetAugmentItemID(3), + .augment_5_id = inst->GetAugmentItemID(4), + .augment_6_id = inst->GetAugmentItemID(5), + .attuned = inst->IsAttuned(), + .reason = "Client deleted" }; RecordPlayerEventLog(PlayerEvent::ITEM_DESTROY, e); @@ -6123,8 +6126,8 @@ void Client::Handle_OP_DzAddPlayer(const EQApplicationPacket *app) } else { - // the only /dz command that sends an error message if no active expedition - Message(Chat::System, DZ_YOU_NOT_ASSIGNED); + // message string 8271 (not in emu clients) is the only /dz command that sends an error if no active expedition + Message(Chat::System, "You could not use this command because you are not currently assigned to a dynamic zone."); } } @@ -6157,14 +6160,14 @@ void Client::Handle_OP_DzChooseZoneReply(const EQApplicationPacket *app) void Client::Handle_OP_DzExpeditionInviteResponse(const EQApplicationPacket *app) { - auto expedition = Expedition::FindCachedExpeditionByID(m_pending_expedition_invite.expedition_id); - std::string swap_remove_name = m_pending_expedition_invite.swap_remove_name; - m_pending_expedition_invite = { 0 }; // clear before re-validating + auto expedition = DynamicZone::FindDynamicZoneByID(m_dz_invite.dz_id); + std::string swap_name = m_dz_invite.swap_name; + m_dz_invite = {}; // clear before re-validating if (expedition) { auto dzmsg = reinterpret_cast(app->pBuffer); - expedition->DzInviteResponse(this, dzmsg->accepted, swap_remove_name); + expedition->DzInviteResponse(this, dzmsg->accepted, swap_name); } } @@ -6839,6 +6842,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; @@ -6953,7 +6971,7 @@ void Client::Handle_OP_GMSearchCorpse(const EQApplicationPacket *app) DialogueWindow::TableCell( fmt::format( "{} ({})", - zone_store.GetZoneLongName(e.zone_id, true), + ZoneStore::Instance()->GetZoneLongName(e.zone_id, true), e.zone_id ) ) + @@ -7653,292 +7671,303 @@ void Client::Handle_OP_GroupUpdate(const EQApplicationPacket *app) void Client::Handle_OP_GuildBank(const EQApplicationPacket *app) { - if (!GuildBanks) + if (!GuildBanks) { + GuildBankAck(); return; + } - if (zone->GetZoneID() != Zones::GUILDHALL) - { + if (zone->GetZoneID() != Zones::GUILDHALL) { Message(Chat::Red, "The Guild Bank is not available in this zone."); - + GuildBankAck(); return; } if (app->size < sizeof(uint32)) { LogError("Wrong size: OP_GuildBank, size=[{}], expected [{}]", app->size, sizeof(uint32)); DumpPacket(app); + GuildBankAck(); return; } - char *Buffer = (char *)app->pBuffer; - - uint32 Action = VARSTRUCT_DECODE_TYPE(uint32, Buffer); + char *Buffer = (char *) app->pBuffer; + uint32 Action = VARSTRUCT_DECODE_TYPE(uint32, Buffer); uint32 sentAction = Action; - if (!IsInAGuild()) - { + if (!IsInAGuild()) { Message(Chat::Red, "You must be in a Guild to use the Guild Bank."); - - if (Action == GuildBankDeposit) + if (Action == GuildBankDeposit) { GuildBankDepositAck(true, sentAction); - else + } + else { GuildBankAck(); + } return; } if (!IsGuildBanker()) { - if ((Action != GuildBankDeposit) && (Action != GuildBankViewItem) && (Action != GuildBankWithdraw)) + if (Action != GuildBankDeposit && Action != GuildBankViewItem && Action != GuildBankWithdraw) { LogError("Suspected hacking attempt on guild bank from [{}]", GetName()); - GuildBankAck(); - return; } } - switch (Action) - { - case GuildBankPromote: - { - if (GuildBanks->IsAreaFull(GuildID(), GuildBankMainArea)) - { - MessageString(Chat::Red, GUILD_BANK_FULL); - - GuildBankDepositAck(true, sentAction); - - return; - } - - GuildBankPromote_Struct *gbps = (GuildBankPromote_Struct*)app->pBuffer; - - int Slot = GuildBanks->Promote(GuildID(), gbps->Slot); - - if (Slot >= 0) - { - EQ::ItemInstance* inst = GuildBanks->GetItem(GuildID(), GuildBankMainArea, Slot, 1); - - if (inst) - { - MessageString(Chat::LightGray, GUILD_BANK_TRANSFERRED, inst->GetItem()->Name); - safe_delete(inst); - } - } - else - Message(Chat::Red, "Unexpected error while moving item into Guild Bank."); - - GuildBankAck(); - - break; - } - - case GuildBankViewItem: - { - GuildBankViewItem_Struct *gbvis = (GuildBankViewItem_Struct*)app->pBuffer; - - EQ::ItemInstance* inst = GuildBanks->GetItem(GuildID(), gbvis->Area, gbvis->SlotID, 1); - - if (!inst) - break; - - SendItemPacket(0, inst, ItemPacketViewLink); - - safe_delete(inst); - - break; - } - - case GuildBankDeposit: // Deposit Item - { - EQ::ItemInstance *CursorItemInst = GetInv().GetItem(EQ::invslot::slotCursor); - - bool Allowed = true; - - if (!CursorItemInst) - { - Message(Chat::Red, "No Item on the cursor."); - - GuildBankDepositAck(true, sentAction); - - return; - } - - const EQ::ItemData* CursorItem = CursorItemInst->GetItem(); - - if (GuildBanks->IsAreaFull(GuildID(), GuildBankDepositArea)) - { - MessageString(Chat::Red, GUILD_BANK_FULL); - GuildBankDepositAck(true, sentAction); - if (ClientVersion() >= EQ::versions::ClientVersion::RoF) { - GetInv().PopItem(EQ::invslot::slotCursor); - PushItemOnCursor(CursorItem, true); + switch (Action) { + case GuildBankPromote: { + if (GuildBanks->IsAreaFull(GuildID(), GuildBankMainArea)) { + MessageString(Chat::Red, GUILD_BANK_FULL); + GuildBankAck(); + return; } - return; - } + auto gbps = (GuildBankPromote_Struct *) app->pBuffer; + int slot_id = GuildBanks->Promote(GuildID(), gbps->Slot, this); - if (!CursorItem->NoDrop || CursorItemInst->IsAttuned()) - { - Allowed = false; - } - else if (CursorItemInst->IsNoneEmptyContainer()) - { - Allowed = false; - } - else if (CursorItemInst->IsAugmented()) - { - Allowed = false; - } - else if (CursorItem->NoRent == 0) - { - Allowed = false; - } - else if (CursorItem->LoreFlag && GuildBanks->HasItem(GuildID(), CursorItem->ID)) - { - Allowed = false; - } + if (slot_id >= 0) { + auto inst = GuildBanks->GetItem(GuildID(), GuildBankMainArea, slot_id, 1); + if (inst) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::GUILD_BANK_MOVE_TO_BANK_AREA)) { + PlayerEvent::GuildBankTransaction log{}; + log.char_id = CharacterID(); + log.guild_id = GuildID(); + log.item_id = inst->GetID(); + log.quantity = 1; + if (inst->GetCharges() > 0 || inst->IsStackable() || inst->GetItem()->MaxCharges > 0) { + log.quantity = inst->GetCharges(); + } - if (!Allowed) - { - MessageString(Chat::Red, GUILD_BANK_CANNOT_DEPOSIT); - GuildBankDepositAck(true, sentAction); + if (inst->IsAugmented()) { + auto augs = inst->GetAugmentIDs(); + log.aug_slot_one = augs.at(0); + log.aug_slot_two = augs.at(1); + log.aug_slot_three = augs.at(2); + log.aug_slot_four = augs.at(3); + log.aug_slot_five = augs.at(4); + log.aug_slot_six = augs.at(5); + } - if (ClientVersion() >= EQ::versions::ClientVersion::RoF) { - GetInv().PopItem(EQ::invslot::slotCursor); - PushItemOnCursor(CursorItem, true); + RecordPlayerEventLog(PlayerEvent::GUILD_BANK_MOVE_TO_BANK_AREA, log); + } + + MessageString(Chat::LightGray, GUILD_BANK_TRANSFERRED, inst->GetItem()->Name); + } + } + else { + Message(Chat::Red, "Unexpected error while moving item into Guild Bank."); } - return; - } - - if (GuildBanks->AddItem(GuildID(), GuildBankDepositArea, CursorItem->ID, CursorItemInst->GetCharges(), GetName(), GuildBankBankerOnly, "")) - { - GuildBankDepositAck(false, sentAction); - - DeleteItemInInventory(EQ::invslot::slotCursor, 0, false); - } - - break; - } - - case GuildBankPermissions: - { - GuildBankPermissions_Struct *gbps = (GuildBankPermissions_Struct*)app->pBuffer; - - if (gbps->Permissions == 1) - GuildBanks->SetPermissions(GuildID(), gbps->SlotID, gbps->Permissions, gbps->MemberName); - else - GuildBanks->SetPermissions(GuildID(), gbps->SlotID, gbps->Permissions, ""); - - GuildBankAck(); - break; - } - - case GuildBankWithdraw: - { - if (GetInv()[EQ::invslot::slotCursor]) - { - MessageString(Chat::Red, GUILD_BANK_EMPTY_HANDS); GuildBankAck(); + break; + } + + case GuildBankViewItem: { + auto gbvis = (GuildBankViewItem_Struct *) app->pBuffer; + auto inst = GuildBanks->GetItem(GuildID(), gbvis->Area, gbvis->SlotID, 1); + + if (!inst) { + break; + } + + SendItemPacket(0, inst.get(), ItemPacketViewLink); + break; + } + + case GuildBankDeposit: // Deposit Item + { + const auto cursor_item_inst = GetInv().GetItem(EQ::invslot::slotCursor); + bool allowed = true; + + if (!cursor_item_inst) { + Message(Chat::Red, "No Item on the cursor."); + GuildBankDepositAck(true, sentAction); + return; + } + + const auto cursor_item = cursor_item_inst->GetItem(); + if (GuildBanks->IsAreaFull(GuildID(), GuildBankDepositArea)) { + MessageString(Chat::Red, GUILD_BANK_FULL); + GuildBankDepositAck(true, sentAction); + if (ClientVersion() >= EQ::versions::ClientVersion::RoF) { + GetInv().PopItem(EQ::invslot::slotCursor); + PushItemOnCursor(*cursor_item_inst, true); + } + + return; + } + + if (!cursor_item->NoDrop || + cursor_item_inst->IsAttuned() || + cursor_item_inst->IsNoneEmptyContainer() || + cursor_item->NoRent == 0 || + (cursor_item->LoreFlag && GuildBanks->HasItem(GuildID(), cursor_item->ID)) + ) { + allowed = false; + } + + if (!allowed) { + MessageString(Chat::Red, GUILD_BANK_CANNOT_DEPOSIT); + GuildBankDepositAck(true, sentAction); + + if (ClientVersion() >= EQ::versions::ClientVersion::RoF) { + GetInv().PopItem(EQ::invslot::slotCursor); + PushItemOnCursor(*cursor_item_inst, true); + } + return; + } + + auto item = GuildBankRepository::NewEntity(); + item.guild_id = GuildID(); + item.area = GuildBankDepositArea; + item.item_id = cursor_item->ID; + item.quantity = 1; + if (cursor_item_inst->GetCharges() > 0 || cursor_item_inst->IsStackable() || cursor_item->MaxCharges > 0) { + item.quantity = cursor_item_inst->GetCharges(); + } + + item.donator = GetCleanName(); + item.permissions = GuildBankBankerOnly; + if (cursor_item_inst->IsAugmented()) { + auto const augs = cursor_item_inst->GetAugmentIDs(); + item.augment_one_id = augs.at(0); + item.augment_two_id = augs.at(1); + item.augment_three_id = augs.at(2); + item.augment_four_id = augs.at(3); + item.augment_five_id = augs.at(4); + item.augment_six_id = augs.at(5); + } + + if (GuildBanks->AddItem(item, this)) { + GuildBankDepositAck(false, sentAction); + DeleteItemInInventory(EQ::invslot::slotCursor, 0, false); + + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::GUILD_BANK_DEPOSIT)) { + PlayerEvent::GuildBankTransaction log{}; + log.char_id = CharacterID(); + log.guild_id = GuildID(); + log.item_id = item.item_id; + log.quantity = item.quantity; + log.aug_slot_one = item.augment_one_id; + log.aug_slot_two = item.augment_two_id; + log.aug_slot_three = item.augment_three_id; + log.aug_slot_four = item.augment_four_id; + log.aug_slot_five = item.augment_five_id; + log.aug_slot_six = item.augment_six_id; + + RecordPlayerEventLog(PlayerEvent::GUILD_BANK_DEPOSIT, log); + } + } break; } - GuildBankWithdrawItem_Struct *gbwis = (GuildBankWithdrawItem_Struct*)app->pBuffer; + case GuildBankPermissions: { + auto gbps = (GuildBankPermissions_Struct *) app->pBuffer; - EQ::ItemInstance* inst = GuildBanks->GetItem(GuildID(), gbwis->Area, gbwis->SlotID, gbwis->Quantity); + if (gbps->Permissions == 1) { + GuildBanks->SetPermissions(GuildID(), gbps->SlotID, gbps->Permissions, gbps->MemberName, this); + } + else { + GuildBanks->SetPermissions(GuildID(), gbps->SlotID, gbps->Permissions, "", this); + } - if (!inst) - { GuildBankAck(); - break; } - if (!guild_mgr.CheckPermission(GuildID(), GuildRank(), GUILD_ACTION_BANK_WITHDRAW_ITEMS)) - { - Message(Chat::Red, "You do not have permission to withdraw."); + case GuildBankWithdraw: { + if (GetInv()[EQ::invslot::slotCursor]) { + MessageString(Chat::Red, GUILD_BANK_EMPTY_HANDS); + GuildBankAck(); + break; + } + + auto gbwis = (GuildBankWithdrawItem_Struct *) app->pBuffer; + auto inst = GuildBanks->GetItem(GuildID(), gbwis->Area, gbwis->SlotID, gbwis->Quantity); + + if (!inst) { + GuildBankAck(); + break; + } + + if (!guild_mgr.CheckPermission(GuildID(), GuildRank(), GUILD_ACTION_BANK_WITHDRAW_ITEMS)) { + Message(Chat::Red, "You do not have permission to withdraw."); + GuildBankAck(); + break; + } + + if (!IsGuildBanker()) { + LogError("Suspected attempted hack on the guild bank from [{}]", GetName()); + GuildBankAck(); + break; + } + + if (CheckLoreConflict(inst->GetItem())) { + MessageString(Chat::Red, DUP_LORE); + GuildBankAck(); + break; + } + + gbwis->Quantity = 1; + if (inst->GetCharges() > 0 || inst->IsStackable() || inst->GetItem()->MaxCharges > 0) { + gbwis->Quantity = inst->GetCharges(); + } + + PushItemOnCursor(*inst.get()); + SendItemPacket(EQ::invslot::slotCursor, inst.get(), ItemPacketLimbo); + GuildBanks->DeleteItem(GuildID(), gbwis->Area, gbwis->SlotID, gbwis->Quantity, this); + + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::GUILD_BANK_WITHDRAWAL)) { + PlayerEvent::GuildBankTransaction log{}; + log.char_id = CharacterID(); + log.guild_id = GuildID(); + log.item_id = inst->GetID(); + log.quantity = gbwis->Quantity; + if (inst->IsAugmented()) { + auto augs = inst->GetAugmentIDs(); + log.aug_slot_one = augs.at(0); + log.aug_slot_two = augs.at(1); + log.aug_slot_three = augs.at(2); + log.aug_slot_four = augs.at(3); + log.aug_slot_five = augs.at(4); + log.aug_slot_six = augs.at(5); + } + + RecordPlayerEventLog(PlayerEvent::GUILD_BANK_WITHDRAWAL, log); + } + + else { + Message(Chat::Red, "Unable to withdraw 0 quantity of %s", inst->GetItem()->Name); + } + + GuildBankAck(); + break; + } + case GuildBankSplitStacks: { + if (GuildBanks->IsAreaFull(GuildID(), GuildBankMainArea)) { + MessageString(Chat::Red, GUILD_BANK_FULL); + } + else { + auto gbwis = (GuildBankWithdrawItem_Struct *) app->pBuffer; + GuildBanks->SplitStack(GuildID(), gbwis->SlotID, gbwis->Quantity, this); + } + + GuildBankAck(); + break; + } + case GuildBankMergeStacks: { + auto gbwis = (GuildBankWithdrawItem_Struct *) app->pBuffer; + GuildBanks->MergeStacks(GuildID(), gbwis->SlotID, this); GuildBankAck(); - safe_delete(inst); break; } - if (!IsGuildBanker() && !GuildBanks->AllowedToWithdraw(GuildID(), gbwis->Area, gbwis->SlotID, GetName())) - { - LogError("Suspected attempted hack on the guild bank from [{}]", GetName()); - - GuildBankAck(); - - safe_delete(inst); - - break; + default: { + Message(Chat::Red, "Unexpected GuildBank action."); + LogError("Received unexpected guild bank action code [{}] from [{}]", Action, GetName()); } - - if (CheckLoreConflict(inst->GetItem())) - { - MessageString(Chat::Red, DUP_LORE); - - GuildBankAck(); - - safe_delete(inst); - - break; - } - - if (gbwis->Quantity > 0) - { - PushItemOnCursor(*inst); - - SendItemPacket(EQ::invslot::slotCursor, inst, ItemPacketLimbo); - - GuildBanks->DeleteItem(GuildID(), gbwis->Area, gbwis->SlotID, gbwis->Quantity); - } - else - { - Message(Chat::Red, "Unable to withdraw 0 quantity of %s", inst->GetItem()->Name); - } - - safe_delete(inst); - - GuildBankAck(); - - break; - } - - case GuildBankSplitStacks: - { - if (GuildBanks->IsAreaFull(GuildID(), GuildBankMainArea)) - MessageString(Chat::Red, GUILD_BANK_FULL); - else - { - GuildBankWithdrawItem_Struct *gbwis = (GuildBankWithdrawItem_Struct*)app->pBuffer; - - GuildBanks->SplitStack(GuildID(), gbwis->SlotID, gbwis->Quantity); - } - - GuildBankAck(); - - break; - } - - case GuildBankMergeStacks: - { - GuildBankWithdrawItem_Struct *gbwis = (GuildBankWithdrawItem_Struct*)app->pBuffer; - - GuildBanks->MergeStacks(GuildID(), gbwis->SlotID); - - GuildBankAck(); - - break; - } - - default: - { - Message(Chat::Red, "Unexpected GuildBank action."); - - LogError("Received unexpected guild bank action code [{}] from [{}]", Action, GetName()); - } } } @@ -7959,7 +7988,7 @@ void Client::Handle_OP_GuildCreate(const EQApplicationPacket *app) if ((Admin() < RuleI(Guild, PlayerCreationRequiredStatus)) || (GetLevel() < RuleI(Guild, PlayerCreationRequiredLevel)) || - (database.GetTotalTimeEntitledOnAccount(AccountID()) < (unsigned int)RuleI(Guild, PlayerCreationRequiredTime))) + (CharacterDataRepository::GetTotalTimePlayed(database, AccountID()) < (unsigned int)RuleI(Guild, PlayerCreationRequiredTime))) { Message(Chat::Red, "Your status, level or time playing on this account are insufficient to use this feature."); return; @@ -8013,8 +8042,9 @@ 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(); SendGuildSpawnAppearance(); @@ -8181,7 +8211,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; @@ -9370,6 +9400,30 @@ void Client::Handle_OP_ItemPreview(const EQApplicationPacket *app) return; } +void Client::Handle_OP_ItemPreviewRequest(const EQApplicationPacket* app) +{ + VERIFY_PACKET_LENGTH(OP_ItemPreviewRequest, app, ItemPreview_Struct); + auto ips = (ItemPreview_Struct*) app->pBuffer; + const EQ::ItemData* item = database.GetItem(ips->itemid); + + if (item) { + EQ::ItemInstance* inst = database.CreateItem(item); + if (inst) { + std::string packet = inst->Serialize(-1); + auto outapp = new EQApplicationPacket(OP_ItemPreviewRequest, packet.length()); + memcpy(outapp->pBuffer, packet.c_str(), packet.length()); + +#if EQDEBUG >= 9 + DumpPacket(outapp); +#endif + + QueuePacket(outapp); + safe_delete(outapp); + safe_delete(inst); + } + } +} + void Client::Handle_OP_ItemVerifyRequest(const EQApplicationPacket *app) { using EQ::spells::CastingSlot; @@ -9591,14 +9645,7 @@ void Client::Handle_OP_ItemVerifyRequest(const EQApplicationPacket *app) int i = 0; if (parse->ItemHasQuestSub(p_inst, EVENT_ITEM_CLICK_CAST)) { - i = parse->EventItem( - EVENT_ITEM_CLICK_CAST, - this, - p_inst, - nullptr, - "", - slot_id - ); + i = parse->EventItem(EVENT_ITEM_CLICK_CAST, this, p_inst, nullptr, "", slot_id); } if (parse->PlayerHasQuestSub(EVENT_ITEM_CLICK_CAST_CLIENT)) { @@ -9662,14 +9709,7 @@ void Client::Handle_OP_ItemVerifyRequest(const EQApplicationPacket *app) int i = 0; if (parse->ItemHasQuestSub(p_inst, EVENT_ITEM_CLICK_CAST)) { - i = parse->EventItem( - EVENT_ITEM_CLICK_CAST, - this, - clickaug, - nullptr, - "", - slot_id - ); + i = parse->EventItem(EVENT_ITEM_CLICK_CAST, this, clickaug, nullptr, "", slot_id); } if (parse->PlayerHasQuestSub(EVENT_ITEM_CLICK_CAST_CLIENT)) { @@ -9742,7 +9782,7 @@ void Client::Handle_OP_Jump(const EQApplicationPacket *app) void Client::Handle_OP_KeyRing(const EQApplicationPacket *app) { - KeyRingList(); + KeyRingList(this); } void Client::Handle_OP_KickPlayers(const EQApplicationPacket *app) @@ -10769,8 +10809,7 @@ void Client::Handle_OP_MoveCoin(const EQApplicationPacket *app) void Client::Handle_OP_MoveItem(const EQApplicationPacket *app) { - if (!CharacterID()) - { + if (!CharacterID()) { return; } @@ -10779,60 +10818,52 @@ void Client::Handle_OP_MoveItem(const EQApplicationPacket *app) return; } - MoveItem_Struct* mi = (MoveItem_Struct*)app->pBuffer; - if (spellend_timer.Enabled() && casting_spell_id && !IsBardSong(casting_spell_id)) - { - if (mi->from_slot != mi->to_slot && (mi->from_slot <= EQ::invslot::GENERAL_END || mi->from_slot > 39) && IsValidSlot(mi->from_slot) && IsValidSlot(mi->to_slot)) - { - const EQ::ItemInstance *itm_from = GetInv().GetItem(mi->from_slot); - const EQ::ItemInstance *itm_to = GetInv().GetItem(mi->to_slot); - auto message = fmt::format("Player issued a move item from {}(item id {}) to {}(item id {}) while casting {}.", + ResetAFKTimer(); + + BenchTimer bench; + + MoveItem_Struct* mi = (MoveItem_Struct*) app->pBuffer; + if (spellend_timer.Enabled() && casting_spell_id && !IsBardSong(casting_spell_id)) { + if (mi->from_slot != mi->to_slot && (mi->from_slot <= EQ::invslot::GENERAL_END || mi->from_slot > 39) && + IsValidSlot(mi->from_slot) && IsValidSlot(mi->to_slot)) { + const EQ::ItemInstance* itm_from = GetInv().GetItem(mi->from_slot); + const EQ::ItemInstance* itm_to = GetInv().GetItem(mi->to_slot); + auto message = fmt::format( + "Player issued a move item from {}(item id {}) to {}(item id {}) while casting {}.", mi->from_slot, itm_from ? itm_from->GetID() : 0, mi->to_slot, itm_to ? itm_to->GetID() : 0, - casting_spell_id); - RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message}); + casting_spell_id + ); + RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{ .message = message }); Kick("Inventory desync"); // Kick client to prevent client and server from getting out-of-sync inventory slots return; } } - // Illegal bagslot usage checks. Currently, user only receives a message if this check is triggered. - bool mi_hack = false; - - if (mi->from_slot >= EQ::invbag::GENERAL_BAGS_BEGIN && mi->from_slot <= EQ::invbag::CURSOR_BAG_END) { - if (mi->from_slot >= EQ::invbag::CURSOR_BAG_BEGIN) { mi_hack = true; } - else { - int16 from_parent = m_inv.CalcSlotId(mi->from_slot); - if (!m_inv[from_parent]) { mi_hack = true; } - else if (!m_inv[from_parent]->IsClassBag()) { mi_hack = true; } - else if (m_inv.CalcBagIdx(mi->from_slot) >= m_inv[from_parent]->GetItem()->BagSlots) { mi_hack = true; } - } - } - - if (mi->to_slot >= EQ::invbag::GENERAL_BAGS_BEGIN && mi->to_slot <= EQ::invbag::CURSOR_BAG_END) { - if (mi->to_slot >= EQ::invbag::CURSOR_BAG_BEGIN) { mi_hack = true; } - else { - int16 to_parent = m_inv.CalcSlotId(mi->to_slot); - if (!m_inv[to_parent]) { mi_hack = true; } - else if (!m_inv[to_parent]->IsClassBag()) { mi_hack = true; } - else if (m_inv.CalcBagIdx(mi->to_slot) >= m_inv[to_parent]->GetItem()->BagSlots) { mi_hack = true; } - } - } - - if (mi_hack) { Message(Chat::Yellow, "Caution: Illegal use of inaccessible bag slots!"); } + database.TransactionBegin(); if (!SwapItem(mi) && IsValidSlot(mi->from_slot) && IsValidSlot(mi->to_slot)) { SwapItemResync(mi); bool error = false; InterrogateInventory(this, false, true, false, error, false); - if (error) + if (error) { InterrogateInventory(this, true, false, true, error); + } } - return; + for (int slot : {mi->to_slot, mi->from_slot}) { + auto item = GetInv().GetItem(slot); + if (item && item->IsEvolving()) { + CharacterEvolvingItemsRepository::UpdateOne(database, item->GetEvolvingDetails()); + } + } + + database.TransactionCommit(); + + LogInventory("OP_MoveItem took [{}] ms", bench.elapsedMilliseconds()); } void Client::Handle_OP_MoveMultipleItems(const EQApplicationPacket *app) @@ -10899,6 +10930,7 @@ void Client::Handle_OP_MoveMultipleItems(const EQApplicationPacket *app) InterrogateInventory(this, true, false, true, error); } } + safe_delete(mi); } // This is the swap. // Client behavior is just to move stacks without combining them @@ -11034,13 +11066,14 @@ void Client::Handle_OP_PDeletePetition(const EQApplicationPacket *app) LogError("Wrong size: OP_PDeletePetition, size=[{}], expected [{}]", app->size, 2); return; } - if (petition_list.DeletePetitionByCharName((char*)app->pBuffer)) + if (PetitionList::Instance()->DeletePetitionByCharName((char*)app->pBuffer)) MessageString(Chat::White, PETITION_DELETED); else MessageString(Chat::White, PETITION_NO_DELETE); return; } + void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) { if (app->size != sizeof(PetCommand_Struct)) { @@ -11093,10 +11126,17 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) case PET_ATTACK: { if (!target) break; + + if (RuleB(Pets, PetsRequireLoS) && !DoLosChecks(target)) { + mypet->SayString(this, NOT_LEGAL_TARGET); + break; + } + if (target->IsMezzed()) { MessageString(Chat::NPCQuestSay, CANNOT_WAKE, mypet->GetCleanName(), target->GetCleanName()); break; } + if (mypet->IsFeared()) break; //prevent pet from attacking stuff while feared @@ -11151,8 +11191,15 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) if (mypet->IsFeared()) break; //prevent pet from attacking stuff while feared - if (!GetTarget()) + if (!GetTarget()) { break; + } + + if (RuleB(Pets, PetsRequireLoS) && !DoLosChecks(GetTarget())) { + mypet->SayString(this, NOT_LEGAL_TARGET); + break; + } + if (GetTarget()->IsMezzed()) { MessageString(Chat::NPCQuestSay, CANNOT_WAKE, mypet->GetCleanName(), GetTarget()->GetCleanName()); break; @@ -11217,7 +11264,7 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) // eqlive ignores this command // we could just remove the charm // and continue - mypet->BuffFadeByEffect(SE_Charm); + mypet->BuffFadeByEffect(SpellEffect::Charm); break; } else { @@ -11684,7 +11731,6 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) break; } } - void Client::Handle_OP_Petition(const EQApplicationPacket *app) { if (app->size <= 1) @@ -11698,7 +11744,7 @@ void Client::Handle_OP_Petition(const EQApplicationPacket *app) }*/ else { - if (petition_list.FindPetitionByAccountName(AccountName())) + if (PetitionList::Instance()->FindPetitionByAccountName(AccountName())) { Message(Chat::White, "You already have a petition in the queue, you must wait for it to be answered or use /deletepetition to delete it."); return; @@ -11714,10 +11760,10 @@ void Client::Handle_OP_Petition(const EQApplicationPacket *app) pet->SetPetitionText((char*)app->pBuffer); pet->SetZone(zone->GetZoneID()); pet->SetUrgency(0); - petition_list.AddPetition(pet); + PetitionList::Instance()->AddPetition(pet); database.InsertPetitionToDB(pet); - petition_list.UpdateGMQueue(); - petition_list.UpdateZoneListQueue(); + PetitionList::Instance()->UpdateGMQueue(); + PetitionList::Instance()->UpdateZoneListQueue(); worldserver.SendEmoteMessage( 0, 0, @@ -11748,16 +11794,16 @@ void Client::Handle_OP_PetitionCheckIn(const EQApplicationPacket *app) } Petition_Struct* inpet = (Petition_Struct*)app->pBuffer; - Petition* pet = petition_list.GetPetitionByID(inpet->petnumber); + Petition* pet = PetitionList::Instance()->GetPetitionByID(inpet->petnumber); //if (inpet->urgency != pet->GetUrgency()) pet->SetUrgency(inpet->urgency); pet->SetLastGM(GetName()); pet->SetGMText(inpet->gmtext); pet->SetCheckedOut(false); - petition_list.UpdatePetition(pet); - petition_list.UpdateGMQueue(); - petition_list.UpdateZoneListQueue(); + PetitionList::Instance()->UpdatePetition(pet); + PetitionList::Instance()->UpdateGMQueue(); + PetitionList::Instance()->UpdateZoneListQueue(); return; } @@ -11771,14 +11817,14 @@ void Client::Handle_OP_PetitionCheckout(const EQApplicationPacket *app) Message(Chat::Red, "Error: World server disconnected"); else { uint32 getpetnum = *((uint32*)app->pBuffer); - Petition* getpet = petition_list.GetPetitionByID(getpetnum); + Petition* getpet = PetitionList::Instance()->GetPetitionByID(getpetnum); if (getpet != 0) { getpet->AddCheckout(); getpet->SetCheckedOut(true); getpet->SendPetitionToPlayer(CastToClient()); - petition_list.UpdatePetition(getpet); - petition_list.UpdateGMQueue(); - petition_list.UpdateZoneListQueue(); + PetitionList::Instance()->UpdatePetition(getpet); + PetitionList::Instance()->UpdateGMQueue(); + PetitionList::Instance()->UpdateZoneListQueue(); } } return; @@ -11798,16 +11844,16 @@ void Client::Handle_OP_PetitionDelete(const EQApplicationPacket *app) pet->senttime = 0; strcpy(pet->accountid, ""); strcpy(pet->gmsenttoo, ""); - pet->quetotal = petition_list.GetTotalPetitions(); + pet->quetotal = PetitionList::Instance()->GetTotalPetitions(); strcpy(pet->charname, ""); FastQueuePacket(&outapp); - if (petition_list.DeletePetition(pet->petnumber) == -1) + if (PetitionList::Instance()->DeletePetition(pet->petnumber) == -1) std::cout << "Something is borked with: " << pet->petnumber << std::endl; - petition_list.ClearPetitions(); - petition_list.UpdateGMQueue(); - petition_list.ReadDatabase(); - petition_list.UpdateZoneListQueue(); + PetitionList::Instance()->ClearPetitions(); + PetitionList::Instance()->UpdateGMQueue(); + PetitionList::Instance()->ReadDatabase(); + PetitionList::Instance()->UpdateZoneListQueue(); return; } @@ -11842,12 +11888,12 @@ void Client::Handle_OP_PetitionUnCheckout(const EQApplicationPacket *app) Message(Chat::Red, "Error: World server disconnected"); else { uint32 getpetnum = *((uint32*)app->pBuffer); - Petition* getpet = petition_list.GetPetitionByID(getpetnum); + Petition* getpet = PetitionList::Instance()->GetPetitionByID(getpetnum); if (getpet != 0) { getpet->SetCheckedOut(false); - petition_list.UpdatePetition(getpet); - petition_list.UpdateGMQueue(); - petition_list.UpdateZoneListQueue(); + PetitionList::Instance()->UpdatePetition(getpet); + PetitionList::Instance()->UpdateGMQueue(); + PetitionList::Instance()->UpdateZoneListQueue(); } } return; @@ -11930,6 +11976,17 @@ void Client::Handle_OP_PickPocket(const EQApplicationPacket *app) SendPickPocketResponse(victim, 0, PickPocketFailed); } +void Client::Handle_OP_PickZone(const EQApplicationPacket *app) +{ + if (app->size != sizeof(PickZone_Struct)) { + LogDebug("Size mismatch in OP_PickZone expected [{}] got [{}]", sizeof(PickZone_Struct), app->size); + DumpPacket(app); + return; + } + + // handle +} + void Client::Handle_OP_PopupResponse(const EQApplicationPacket *app) { @@ -11994,6 +12051,11 @@ void Client::Handle_OP_PopupResponse(const EQApplicationPacket *app) parse->EventPlayer(EVENT_POPUP_RESPONSE, this, std::to_string(popup_response->popupid), 0); } + if (parse->ZoneHasQuestSub(EVENT_POPUP_RESPONSE)) { + std::vector args = { this }; + parse->EventZone(EVENT_POPUP_RESPONSE, zone, std::to_string(popup_response->popupid), 0, &args); + } + auto t = GetTarget(); if (t) { parse->EventBotMercNPC(EVENT_POPUP_RESPONSE, t, this, [&]() { return std::to_string(popup_response->popupid); }); @@ -13506,10 +13568,11 @@ void Client::Handle_OP_RequestDuel(const EQApplicationPacket *app) void Client::Handle_OP_RequestTitles(const EQApplicationPacket *app) { - EQApplicationPacket *outapp = title_manager.MakeTitlesPacket(this); + auto outapp = title_manager.MakeTitlesPacket(this); - if (outapp != nullptr) + if (outapp) { FastQueuePacket(&outapp); + } } void Client::Handle_OP_RespawnWindow(const EQApplicationPacket *app) @@ -13556,7 +13619,7 @@ void Client::Handle_OP_RezzAnswer(const EQApplicationPacket *app) OPRezzAnswer(r->action, r->spellid, r->zone_id, r->instance_id, r->x, r->y, r->z); if (r->action == ResurrectionActions::Accept) { - if (player_event_logs.IsEventEnabled(PlayerEvent::REZ_ACCEPTED)) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::REZ_ACCEPTED)) { auto e = PlayerEvent::ResurrectAcceptEvent{ .resurrecter_name = r->rezzer_name, .spell_name = spells[r->spellid].name, @@ -13924,7 +13987,7 @@ void Client::Handle_OP_Shielding(const EQApplicationPacket *app) Recast is 3 minutes. For custom use cases, Mob::ShieldAbility can be used in quests with all parameters being altered. This functional - is also used for SPA 201 SE_PetShield, which functions in a simalar manner with pet shielding owner. + is also used for SPA 201 SpellEffect::PetShield, which functions in a simalar manner with pet shielding owner. Note: If either the shielder or the shield target die all variables are reset on both. @@ -13971,14 +14034,7 @@ void Client::Handle_OP_Shielding(const EQApplicationPacket *app) void Client::Handle_OP_ShopEnd(const EQApplicationPacket *app) { - if (ClientVersion() == EQ::versions::ClientVersion::RoF2 && RuleB(Parcel, EnableParcelMerchants)) { - DoParcelCancel(); - SetEngagedWithParcelMerchant(false); - } - - EQApplicationPacket empty(OP_ShopEndConfirm); - QueuePacket(&empty); - return; + SendMerchantEnd(); } void Client::Handle_OP_ShopPlayerBuy(const EQApplicationPacket *app) @@ -13988,8 +14044,7 @@ void Client::Handle_OP_ShopPlayerBuy(const EQApplicationPacket *app) sizeof(Merchant_Sell_Struct), app->size); return; } - RDTSC_Timer t1; - t1.start(); + Merchant_Sell_Struct* mp = (Merchant_Sell_Struct*)app->pBuffer; #if EQDEBUG >= 5 LogDebug("[{}], purchase item", GetName()); @@ -14000,14 +14055,16 @@ void Client::Handle_OP_ShopPlayerBuy(const EQApplicationPacket *app) bool tmpmer_used = false; Mob* tmp = entity_list.GetMob(mp->npcid); - if (tmp == 0 || !tmp->IsNPC() || tmp->GetClass() != Class::Merchant) - return; - - if (mp->quantity < 1) return; - - //you have to be somewhat close to them to be properly using them - if (DistanceSquared(m_Position, tmp->GetPosition()) > USE_NPC_RANGE2) + if ( + tmp == 0 || + !tmp->IsNPC() || + tmp->GetClass() != Class::Merchant || + mp->quantity < 1 || + DistanceSquared(m_Position, tmp->GetPosition()) > USE_NPC_RANGE2 + ) { + SendMerchantEnd(); return; + } merchantid = tmp->CastToNPC()->MerchantType; @@ -14041,36 +14098,34 @@ void Client::Handle_OP_ShopPlayerBuy(const EQApplicationPacket *app) } } } + item = database.GetItem(item_id); + if (!item) { //error finding item, client didnt get the update packet for whatever reason, roleplay a tad - Message(Chat::Yellow, "%s tells you 'Sorry, that item is for display purposes only.' as they take the item off the shelf.", tmp->GetCleanName()); - auto delitempacket = new EQApplicationPacket(OP_ShopDelItem, sizeof(Merchant_DelItem_Struct)); - Merchant_DelItem_Struct* delitem = (Merchant_DelItem_Struct*)delitempacket->pBuffer; - delitem->itemslot = mp->itemslot; - delitem->npcid = mp->npcid; - delitem->playerid = mp->playerid; - delitempacket->priority = 6; - entity_list.QueueCloseClients(tmp, delitempacket); //que for anyone that could be using the merchant so they see the update - safe_delete(delitempacket); + MessageString(Chat::White, ALREADY_SOLD); + entity_list.SendMerchantInventory(tmp, mp->itemslot, true); + SendMerchantEnd(); return; } - if (CheckLoreConflict(item)) - { - Message(Chat::Yellow, "You can only have one of a lore item."); + + if (CheckLoreConflict(item)) { + MessageString(Chat::White, DUPE_LORE_MERCHANT,tmp->GetCleanName(),item->Name); return; } - if (tmpmer_used && (mp->quantity > prevcharges || item->MaxCharges > 1)) - { - if (prevcharges > item->MaxCharges && item->MaxCharges > 1) + + if (tmpmer_used && (mp->quantity > prevcharges || item->MaxCharges > 1)) { + if (prevcharges > item->MaxCharges && item->MaxCharges > 1) { mp->quantity = item->MaxCharges; - else + } else { mp->quantity = prevcharges; + } } // Item's stackable, but the quantity they want to buy exceeds the max stackable quantity. - if (item->Stackable && mp->quantity > item->StackSize) + if (item->Stackable && mp->quantity > item->StackSize) { mp->quantity = item->StackSize; + } auto outapp = new EQApplicationPacket(OP_ShopPlayerBuy, sizeof(Merchant_Sell_Struct)); Merchant_Sell_Struct* mpo = (Merchant_Sell_Struct*)outapp->pBuffer; @@ -14081,10 +14136,11 @@ void Client::Handle_OP_ShopPlayerBuy(const EQApplicationPacket *app) int16 freeslotid = INVALID_INDEX; int16 charges = 0; - if (item->Stackable || tmpmer_used) + if (item->Stackable || tmpmer_used) { charges = mp->quantity; - else if ( item->MaxCharges >= 1) + } else if ( item->MaxCharges >= 1) { charges = item->MaxCharges; + } EQ::ItemInstance* inst = database.CreateItem(item, charges); @@ -14099,23 +14155,24 @@ void Client::Handle_OP_ShopPlayerBuy(const EQApplicationPacket *app) single_price *= Client::CalcPriceMod(tmp, false); } - if (item->MaxCharges > 1) + if (item->MaxCharges > 1) { mpo->price = single_price; - else + } else { mpo->price = single_price * mp->quantity; + } - if (mpo->price < 0) - { + if (mpo->price < 0) { + MessageString(Chat::White, ALREADY_SOLD); safe_delete(outapp); safe_delete(inst); + SendMerchantEnd(); return; } // this area needs some work..two inventory insertion check failure points // below do not return player's money..is this the intended behavior? - if (!TakeMoneyFromPP(mpo->price)) - { + if (!TakeMoneyFromPP(mpo->price)) { auto message = fmt::format( "Vendor Cheat attempted to buy qty [{}] of item_id [{}] item_name[{}] that cost [{}] copper but only has platinum [{}] gold [{}] silver [{}] copper [{}]", mpo->quantity, @@ -14135,8 +14192,10 @@ void Client::Handle_OP_ShopPlayerBuy(const EQApplicationPacket *app) } bool stacked = TryStacking(inst); - if (!stacked) + + if (!stacked) { freeslotid = m_inv.FindFreeSlot(false, true, item->Size); + } // shouldn't we be reimbursing if these two fail? @@ -14150,8 +14209,7 @@ void Client::Handle_OP_ShopPlayerBuy(const EQApplicationPacket *app) } } - if (!stacked && freeslotid == INVALID_INDEX) - { + if (!stacked && freeslotid == INVALID_INDEX) { Message(Chat::Red, "You do not have room for any more items."); safe_delete(outapp); safe_delete(inst); @@ -14162,11 +14220,12 @@ void Client::Handle_OP_ShopPlayerBuy(const EQApplicationPacket *app) if (!stacked && inst) { PutItemInInventory(freeslotid, *inst); SendItemPacket(freeslotid, inst, ItemPacketTrade); - } - else if (!stacked) { + } else if (!stacked) { LogError("OP_ShopPlayerBuy: item->ItemClass Unknown! Type: [{}]", item->ItemClass); } + QueuePacket(outapp); + if (inst && tmpmer_used) { int32 new_charges = prevcharges - mp->quantity; zone->SaveTempItem(merchantid, tmp->GetNPCTypeID(), item_id, new_charges); @@ -14179,8 +14238,7 @@ void Client::Handle_OP_ShopPlayerBuy(const EQApplicationPacket *app) delitempacket->priority = 6; entity_list.QueueClients(tmp, delitempacket); //que for anyone that could be using the merchant so they see the update safe_delete(delitempacket); - } - else { + } else { // Update the charges/quantity in the merchant window inst->SetCharges(new_charges); inst->SetPrice(single_price); @@ -14190,10 +14248,8 @@ void Client::Handle_OP_ShopPlayerBuy(const EQApplicationPacket *app) SendItemPacket(mp->itemslot, inst, ItemPacketMerchant); } } - safe_delete(inst); - safe_delete(outapp); - if (player_event_logs.IsEventEnabled(PlayerEvent::MERCHANT_PURCHASE)) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::MERCHANT_PURCHASE)) { auto e = PlayerEvent::MerchantPurchaseEvent{ .npc_id = tmp->GetNPCTypeID(), .merchant_name = tmp->GetCleanName(), @@ -14210,61 +14266,6 @@ void Client::Handle_OP_ShopPlayerBuy(const EQApplicationPacket *app) RecordPlayerEventLog(PlayerEvent::MERCHANT_PURCHASE, e); } - // start QS code - // stacking purchases not supported at this time - entire process will need some work to catch them properly - if (RuleB(QueryServ, PlayerLogMerchantTransactions)) { - auto qspack = - new ServerPacket(ServerOP_QSPlayerLogMerchantTransactions, - sizeof(QSMerchantLogTransaction_Struct) + sizeof(QSTransactionItems_Struct)); - QSMerchantLogTransaction_Struct* qsaudit = (QSMerchantLogTransaction_Struct*)qspack->pBuffer; - - qsaudit->zone_id = zone->GetZoneID(); - qsaudit->merchant_id = tmp->CastToNPC()->MerchantType; - qsaudit->merchant_money.platinum = 0; - qsaudit->merchant_money.gold = 0; - qsaudit->merchant_money.silver = 0; - qsaudit->merchant_money.copper = 0; - qsaudit->merchant_count = 1; - qsaudit->char_id = character_id; - qsaudit->char_money.platinum = (mpo->price / 1000); - qsaudit->char_money.gold = (mpo->price / 100) % 10; - qsaudit->char_money.silver = (mpo->price / 10) % 10; - qsaudit->char_money.copper = mpo->price % 10; - qsaudit->char_count = 0; - - qsaudit->items[0].char_slot = freeslotid == INVALID_INDEX ? 0 : freeslotid; - qsaudit->items[0].item_id = item->ID; - qsaudit->items[0].charges = mpo->quantity; - - const EQ::ItemInstance* audit_inst = m_inv[freeslotid]; - - if (audit_inst) { - qsaudit->items[0].aug_1 = audit_inst->GetAugmentItemID(0); - qsaudit->items[0].aug_2 = audit_inst->GetAugmentItemID(1); - qsaudit->items[0].aug_3 = audit_inst->GetAugmentItemID(2); - qsaudit->items[0].aug_4 = audit_inst->GetAugmentItemID(3); - qsaudit->items[0].aug_5 = audit_inst->GetAugmentItemID(4); - } - else { - qsaudit->items[0].aug_1 = 0; - qsaudit->items[0].aug_2 = 0; - qsaudit->items[0].aug_3 = 0; - qsaudit->items[0].aug_4 = 0; - qsaudit->items[0].aug_5 = 0; - - if (freeslotid != INVALID_INDEX) { - LogError("Handle_OP_ShopPlayerBuy: QS Audit could not locate merchant ([{}]) purchased item in player ([{}]) inventory slot ([{}])", - qsaudit->merchant_id, qsaudit->char_id, freeslotid); - } - } - - audit_inst = nullptr; - - if (worldserver.Connected()) { worldserver.SendPacket(qspack); } - safe_delete(qspack); - } - // end QS code - if (parse->PlayerHasQuestSub(EVENT_MERCHANT_BUY)) { const auto& export_string = fmt::format( "{} {} {} {} {}", @@ -14278,42 +14279,12 @@ void Client::Handle_OP_ShopPlayerBuy(const EQApplicationPacket *app) parse->EventPlayer(EVENT_MERCHANT_BUY, this, export_string, 0); } - if (player_event_logs.IsEventEnabled(PlayerEvent::MERCHANT_PURCHASE)) { - auto e = PlayerEvent::MerchantPurchaseEvent{ - .npc_id = tmp->GetNPCTypeID(), - .merchant_name = tmp->GetCleanName(), - .merchant_type = tmp->CastToNPC()->MerchantType, - .item_id = item->ID, - .item_name = item->Name, - .charges = static_cast(mpo->quantity), - .cost = mpo->price, - .alternate_currency_id = 0, - .player_money_balance = GetCarriedMoney(), - .player_currency_balance = 0, - }; - RecordPlayerEventLog(PlayerEvent::MERCHANT_PURCHASE, e); - } + CheckItemDiscoverability(item_id); - - if (RuleB(Character, EnableDiscoveredItems) && !IsDiscovered(item_id)) { - if (!GetGM()) { - DiscoverItem(item_id); - } else { - const std::string& item_link = database.CreateItemLink(item_id); - Message( - Chat::White, - fmt::format( - "Your GM flag prevents {} from being added to discovered items.", - item_link - ).c_str() - ); - } - } - - t1.stop(); - std::cout << "At 1: " << t1.getDuration() << std::endl; - return; + safe_delete(inst); + safe_delete(outapp); } + void Client::Handle_OP_ShopPlayerSell(const EQApplicationPacket *app) { if (app->size != sizeof(Merchant_Purchase_Struct)) { @@ -14450,41 +14421,6 @@ void Client::Handle_OP_ShopPlayerSell(const EQApplicationPacket *app) } } - // start QS code - if (RuleB(QueryServ, PlayerLogMerchantTransactions)) { - auto qspack = - new ServerPacket(ServerOP_QSPlayerLogMerchantTransactions, - sizeof(QSMerchantLogTransaction_Struct) + sizeof(QSTransactionItems_Struct)); - QSMerchantLogTransaction_Struct* qsaudit = (QSMerchantLogTransaction_Struct*)qspack->pBuffer; - - qsaudit->zone_id = zone->GetZoneID(); - qsaudit->merchant_id = vendor->CastToNPC()->MerchantType; - qsaudit->merchant_money.platinum = (price / 1000); - qsaudit->merchant_money.gold = (price / 100) % 10; - qsaudit->merchant_money.silver = (price / 10) % 10; - qsaudit->merchant_money.copper = price % 10; - qsaudit->merchant_count = 0; - qsaudit->char_id = character_id; - qsaudit->char_money.platinum = 0; - qsaudit->char_money.gold = 0; - qsaudit->char_money.silver = 0; - qsaudit->char_money.copper = 0; - qsaudit->char_count = 1; - - qsaudit->items[0].char_slot = mp->itemslot; - qsaudit->items[0].item_id = itemid; - qsaudit->items[0].charges = charges; - qsaudit->items[0].aug_1 = m_inv[mp->itemslot]->GetAugmentItemID(1); - qsaudit->items[0].aug_2 = m_inv[mp->itemslot]->GetAugmentItemID(2); - qsaudit->items[0].aug_3 = m_inv[mp->itemslot]->GetAugmentItemID(3); - qsaudit->items[0].aug_4 = m_inv[mp->itemslot]->GetAugmentItemID(4); - qsaudit->items[0].aug_5 = m_inv[mp->itemslot]->GetAugmentItemID(5); - - if (worldserver.Connected()) { worldserver.SendPacket(qspack); } - safe_delete(qspack); - } - // end QS code - if (parse->PlayerHasQuestSub(EVENT_MERCHANT_SELL)) { const auto& export_string = fmt::format( "{} {} {} {} {}", @@ -14498,7 +14434,7 @@ void Client::Handle_OP_ShopPlayerSell(const EQApplicationPacket *app) parse->EventPlayer(EVENT_MERCHANT_SELL, this, export_string, 0); } - if (player_event_logs.IsEventEnabled(PlayerEvent::MERCHANT_SELL)) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::MERCHANT_SELL)) { auto e = PlayerEvent::MerchantSellEvent{ .npc_id = vendor->GetNPCTypeID(), .merchant_name = vendor->GetCleanName(), @@ -14652,6 +14588,8 @@ void Client::Handle_OP_ShopRequest(const EQApplicationPacket *app) if (action == MerchantActions::Open) { BulkSendMerchantInventory(merchant_id, tmp->GetNPCTypeID()); + SetMerchantSessionEntityID(tmp->GetID()); + if ((tabs_to_display & Parcel) == Parcel) { SendBulkParcels(); } @@ -14763,6 +14701,7 @@ void Client::Handle_OP_SpawnAppearance(const EQApplicationPacket *app) SetFeigned(false); BindWound(this, false, true); camp_timer.Disable(); + bot_camp_timer.Disable(); } else if (sa->parameter == Animation::Sitting) { SetAppearance(eaSitting); @@ -14826,7 +14765,9 @@ void Client::Handle_OP_SpawnAppearance(const EQApplicationPacket *app) } else if (sa->type == AppearanceType::AFK) { if (afk_toggle_timer.Check()) { - AFK = (sa->parameter == 1); + m_is_afk = (sa->parameter == 1); + m_is_manual_afk = (sa->parameter == 1); + ResetAFKTimer(); entity_list.QueueClients(this, app, true); } } @@ -15220,7 +15161,7 @@ void Client::Handle_OP_TargetMouse(const EQApplicationPacket *app) GetTarget()->IsTargeted(1); return; } - else if (GetTarget()->IsPetOwnerClient()) + else if (GetTarget()->IsPetOwnerOfClientBot()) { GetTarget()->IsTargeted(1); return; @@ -15421,48 +15362,8 @@ void Client::Handle_OP_TradeAcceptClick(const EQApplicationPacket *app) else { other->PlayerTradeEventLog(other->trade, trade); - // start QS code - if (RuleB(QueryServ, PlayerLogTrades)) { - PlayerLogTrade_Struct event_entry; - std::list event_details; - - memset(&event_entry, 0, sizeof(PlayerLogTrade_Struct)); - - // Perform actual trade - FinishTrade(other, true, &event_entry, &event_details); - other->FinishTrade(this, false, &event_entry, &event_details); - - event_entry._detail_count = event_details.size(); - - auto qs_pack = new ServerPacket( - ServerOP_QSPlayerLogTrades, - sizeof(PlayerLogTrade_Struct) + - (sizeof(PlayerLogTradeItemsEntry_Struct) * event_entry._detail_count)); - PlayerLogTrade_Struct* qs_buf = (PlayerLogTrade_Struct*)qs_pack->pBuffer; - - memcpy(qs_buf, &event_entry, sizeof(PlayerLogTrade_Struct)); - - int offset = 0; - - for (auto iter = event_details.begin(); iter != event_details.end(); - ++iter, ++offset) { - PlayerLogTradeItemsEntry_Struct* detail = reinterpret_cast(*iter); - qs_buf->item_entries[offset] = *detail; - safe_delete(detail); - } - - event_details.clear(); - - if (worldserver.Connected()) - worldserver.SendPacket(qs_pack); - - safe_delete(qs_pack); - // end QS code - } - else { - FinishTrade(other); - other->FinishTrade(this); - } + FinishTrade(other); + other->FinishTrade(this); other->trade->Reset(); trade->Reset(); @@ -15479,43 +15380,7 @@ void Client::Handle_OP_TradeAcceptClick(const EQApplicationPacket *app) QueuePacket(outapp); safe_delete(outapp); if (with->IsNPC()) { - // Audit trade to database for player trade stream - if (RuleB(QueryServ, PlayerLogHandins)) { - QSPlayerLogHandin_Struct event_entry; - std::list event_details; - - memset(&event_entry, 0, sizeof(QSPlayerLogHandin_Struct)); - - FinishTrade(with->CastToNPC(), false, &event_entry, &event_details); - - event_entry._detail_count = event_details.size(); - - auto qs_pack = - new ServerPacket(ServerOP_QSPlayerLogHandins, - sizeof(QSPlayerLogHandin_Struct) + - (sizeof(QSHandinItems_Struct) * event_entry._detail_count)); - QSPlayerLogHandin_Struct* qs_buf = (QSPlayerLogHandin_Struct*)qs_pack->pBuffer; - - memcpy(qs_buf, &event_entry, sizeof(QSPlayerLogHandin_Struct)); - - int offset = 0; - - for (auto iter = event_details.begin(); iter != event_details.end(); ++iter, ++offset) { - QSHandinItems_Struct* detail = reinterpret_cast(*iter); - qs_buf->items[offset] = *detail; - safe_delete(detail); - } - - event_details.clear(); - - if (worldserver.Connected()) - worldserver.SendPacket(qs_pack); - - safe_delete(qs_pack); - } - else { - FinishTrade(with->CastToNPC()); - } + FinishTrade(with->CastToNPC()); } // TODO: Log Bot trades else if (with->IsBot()) @@ -15603,6 +15468,26 @@ void Client::Handle_OP_TraderBuy(const EQApplicationPacket *app) // Client has elected to buy an item from a Trader // auto in = (TraderBuy_Struct *) app->pBuffer; + + if (RuleB(Bazaar, UseAlternateBazaarSearch) && in->trader_id >= TraderRepository::TRADER_CONVERT_ID) { + auto trader = TraderRepository::GetTraderByInstanceAndSerialnumber( + database, + in->trader_id - TraderRepository::TRADER_CONVERT_ID, + in->serial_number + ); + + if (!trader.trader_id) { + LogTrading("Unable to convert trader id for {} and serial number {}. Trader Buy aborted.", + in->trader_id - TraderRepository::TRADER_CONVERT_ID, + in->serial_number + ); + return; + } + + in->trader_id = trader.trader_id; + strn0cpy(in->seller_name, trader.trader_name.c_str(), sizeof(in->seller_name)); + } + auto trader = entity_list.GetClientByID(in->trader_id); switch (in->method) { @@ -15668,7 +15553,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; @@ -15701,6 +15586,14 @@ void Client::Handle_OP_TradeRequest(const EQApplicationPacket *app) // Pass trade request on to recipient if (tradee && tradee->IsClient()) { + // if we are idling we need to sync client positions otherwise clients will not be aware of each other + if (m_is_idle) { + SyncWorldPositionsToClient(true); + } + if (tradee->CastToClient()->IsIdle()) { + tradee->CastToClient()->SyncWorldPositionsToClient(true); + } + tradee->CastToClient()->QueuePacket(app); } else if (tradee && (tradee->IsNPC() || tradee->IsBot())) { @@ -15730,6 +15623,14 @@ void Client::Handle_OP_TradeRequestAck(const EQApplicationPacket *app) Mob* tradee = entity_list.GetMob(msg->to_mob_id); if (tradee && tradee->IsClient()) { + // if we are idling we need to sync client positions otherwise clients will not be aware of each other + if (m_is_idle) { + SyncWorldPositionsToClient(true); + } + if (tradee->CastToClient()->IsIdle()) { + tradee->CastToClient()->SyncWorldPositionsToClient(true); + } + trade->Start(msg->to_mob_id); tradee->CastToClient()->QueuePacket(app); } @@ -15749,7 +15650,9 @@ void Client::Handle_OP_TraderShop(const EQApplicationPacket *app) switch (in->Code) { case ClickTrader: { LogTrading("Handle_OP_TraderShop case ClickTrader [{}]", in->Code); - auto outapp = std::make_unique(OP_TraderShop, sizeof(TraderClick_Struct)); + auto outapp = + std::make_unique(OP_TraderShop, static_cast(sizeof(TraderClick_Struct)) + ); auto data = (TraderClick_Struct *) outapp->pBuffer; auto trader_client = entity_list.GetClientByID(in->TraderID); @@ -15830,6 +15733,40 @@ void Client::Handle_OP_TradeSkillCombine(const EQApplicationPacket *app) return; } +void Client::Handle_OP_TradeSkillRecipeInspect(const EQApplicationPacket* app) +{ + if (app->size != sizeof(TradeSkillRecipeInspect_Struct)) { + LogError( + "Invalid size for TradeSkillRecipeInspect_Struct: Expected: [{}] Got: [{}]", + sizeof(TradeSkillRecipeInspect_Struct), + app->size + ); + return; + } + + auto s = (TradeSkillRecipeInspect_Struct*) app->pBuffer; + + const auto& v = TradeskillRecipeEntriesRepository::GetWhere( + content_db, + fmt::format( + "`recipe_id` = {} AND `componentcount` = 0 AND `successcount` > 0 ORDER BY `id` ASC LIMIT 1", + s->recipe_id + ) + ); + + if (v.empty()) { + return; + } + + const uint32 item_id = v.front().item_id; + + auto inst = database.CreateItem(item_id); + if (inst) { + SendItemPacket(0, inst, ItemPacketViewLink); + safe_delete(inst); + } +} + void Client::Handle_OP_Translocate(const EQApplicationPacket *app) { if (app->size != sizeof(Translocate_Struct)) { @@ -16728,13 +16665,12 @@ bool Client::CanTradeFVNoDropItem() void Client::SendMobPositions() { - auto p = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); - auto *s = (PlayerPositionUpdateServer_Struct *) p->pBuffer; + static EQApplicationPacket p(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); + auto *s = (PlayerPositionUpdateServer_Struct *) p.pBuffer; for (auto &m: entity_list.GetMobList()) { m.second->MakeSpawnUpdate(s); - QueuePacket(p, false); + QueuePacket(&p, false); } - safe_delete(p); } struct RecordKillCheck { @@ -16744,6 +16680,11 @@ struct RecordKillCheck { void Client::RecordKilledNPCEvent(NPC *n) { + if (!n) { + LogError("NPC passed was invalid."); + return; + } + bool is_named = Strings::Contains(n->GetName(), "#") && !n->IsRaidTarget(); std::vector checks = { @@ -16753,7 +16694,7 @@ void Client::RecordKilledNPCEvent(NPC *n) }; for (auto &c: checks) { - if (c.check && player_event_logs.IsEventEnabled(c.event)) { + if (c.check && PlayerEventLogs::Instance()->IsEventEnabled(c.event)) { auto e = PlayerEvent::KilledNPCEvent{ .npc_id = n->GetNPCTypeID(), .npc_name = n->GetCleanName(), @@ -16877,6 +16818,7 @@ void Client::RecordStats() r.endurance_regen = GetEnduranceRegen() - GetSpellBonuses().EnduranceRegen; r.shielding = GetShielding() - GetSpellBonuses().MeleeMitigation; r.spell_damage = GetSpellDmg() - GetSpellBonuses().SpellDmg; + r.heal_amount = GetHealAmt() - GetSpellBonuses().HealAmt; r.spell_shielding = GetSpellShield() - GetSpellBonuses().SpellShield; r.strikethrough = GetStrikeThrough() - GetSpellBonuses().StrikeThrough; r.stun_resist = GetStunResist() - GetSpellBonuses().StunResist; @@ -17146,12 +17088,16 @@ void Client::Handle_OP_GuildTributeDonateItem(const EQApplicationPacket *app) SendGuildTributeDonateItemReply(in, favor); - if(player_event_logs.IsEventEnabled(PlayerEvent::GUILD_TRIBUTE_DONATE_ITEM)) { - auto e = PlayerEvent::GuildTributeDonateItem { - .item_id = inst->GetID(), - .guild_favor = favor + if(inst && PlayerEventLogs::Instance()->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), + .augment_3_id = inst->GetAugmentItemID(2), + .augment_4_id = inst->GetAugmentItemID(3), + .augment_5_id = inst->GetAugmentItemID(4), + .augment_6_id = inst->GetAugmentItemID(5), + .guild_favor = favor }; - RecordPlayerEventLog(PlayerEvent::GUILD_TRIBUTE_DONATE_ITEM, e); } @@ -17191,7 +17137,7 @@ void Client::Handle_OP_GuildTributeDonatePlat(const EQApplicationPacket *app) TakePlatinum(quantity, false); SendGuildTributeDonatePlatReply(in, favor); - if(player_event_logs.IsEventEnabled(PlayerEvent::GUILD_TRIBUTE_DONATE_PLAT)) { + if(PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::GUILD_TRIBUTE_DONATE_PLAT)) { auto e = PlayerEvent::GuildTributeDonatePlat { .plat = quantity, .guild_favor = favor @@ -17238,3 +17184,187 @@ void Client::Handle_OP_ShopRetrieveParcel(const EQApplicationPacket *app) auto parcel_in = (ParcelRetrieve_Struct *)app->pBuffer; DoParcelRetrieve(*parcel_in); } + +void Client::Handle_OP_EvolveItem(const EQApplicationPacket *app) +{ + if (app->size != sizeof(EvolveItemToggle)) { + LogError( + "Received Handle_OP_EvolveItem packet. Expected size {}, received size {}.", + sizeof(EvolveItemToggle), + app->size + ); + return; + } + + auto in = reinterpret_cast(app->pBuffer); + + switch (in->action) { + case EvolvingItems::Actions::UPDATE_ITEMS: { + DoEvolveItemToggle(app); + break; + } + case EvolvingItems::Actions::FINAL_RESULT: { + DoEvolveItemDisplayFinalResult(app); + break; + } + case EvolvingItems::Actions::TRANSFER_XP: { + DoEvolveTransferXP(app); + break; + } + case EvolvingItems::Actions::TRANSFER_WINDOW_DETAILS: { + SendEvolveXPWindowDetails(app); + } + default: { + } + } +} + +bool Client::IsFilteredAFKPacket(const EQApplicationPacket *p) +{ + if (p->GetOpcode() == OP_ClientUpdate) { + return true; + } + + return false; +} + +void Client::CheckAutoIdleAFK(PlayerPositionUpdateClient_Struct *p) +{ + if (!RuleB(Character, EnableAutoAFK)) { + return; + } + + bool is_non_combat_zone = !zone->CanDoCombat() || zone->BuffTimersSuspended(); + + int seconds_before_afk = + is_non_combat_zone ? + RuleI(Character, SecondsBeforeAFKNonCombatZone) : + RuleI(Character, SecondsBeforeAFKCombatZone); + + int seconds_before_idle = + is_non_combat_zone ? + RuleI(Character, SecondsBeforeIdleNonCombatZone) : + RuleI(Character, SecondsBeforeIdleCombatZone); + + // seconds_before_idle can't be greater than seconds_before_afk + if (seconds_before_idle > seconds_before_afk) { + seconds_before_idle = seconds_before_afk; + } + + bool has_moved = + m_Position.x != p->x_pos || + m_Position.y != p->y_pos || + m_Position.z != p->z_pos || + m_Position.w != EQ12toFloat(p->heading); + + bool triggered_reset = m_afk_reset; + bool was_idle = m_is_idle; + bool is_idle_or_afk = m_is_idle || m_is_afk; + + if (!has_moved && (!m_is_idle || !m_is_afk)) { + auto now = std::chrono::steady_clock::now(); + auto since_last_moved = now - m_last_moved; + + if (!m_is_manual_afk && !m_is_afk && since_last_moved > std::chrono::seconds(seconds_before_afk)) { + bool is_client_excluded_from_afk = (IsBuyer() || IsTrader() || GetGM()); + if (is_client_excluded_from_afk) { + return; + } + + LogInfo( + "Client [{}] has been AFK for [{}] seconds", + GetCleanName(), + std::chrono::duration_cast(since_last_moved).count() + ); + SetAFK(true); + return; + } + else if (!m_is_idle && since_last_moved > std::chrono::seconds(seconds_before_idle)) { + bool is_client_excluded_from_idle = GetGM() && !is_non_combat_zone; + if (is_client_excluded_from_idle) { + return; + } + + LogInfo( + "Client [{}] has been idle for [{}] seconds", + GetCleanName(), + std::chrono::duration_cast(since_last_moved).count() + ); + m_is_idle = true; + Message(Chat::Yellow, "You are now idle. Updates will be sent to you less frequently."); + return; + } + } + + // if we triggered a reset, but didn't move, we are still idling but not AFK + if (triggered_reset && was_idle) { + m_is_idle = true; + } + + // if we moved or triggered reset through other actions, we are no longer AFK. + // we could trigger resetting AFK status through actions like message, cast, attack etc but still by idle until we move + if (!m_is_manual_afk && (has_moved || triggered_reset) && m_is_afk) { + LogInfo("AFK [{}] is no longer idle, syncing positions", GetCleanName()); + SetAFK(false); + ResetAFKTimer(); + } + + // we could be not AFK and idle at the same time + if (has_moved && m_is_idle) { + LogInfo("Idle [{}] is no longer idle, syncing positions", GetCleanName()); + m_is_idle = false; + Message(Chat::Yellow, "You are no longer idle."); + SyncWorldPositionsToClient(); + ResetAFKTimer(); + } + + m_afk_reset = false; +} + +void Client::SyncWorldPositionsToClient(bool ignore_idle) +{ + // if we are idle currently, we need to force updates (which bypasses idle status) and reset idle status + bool reset_idle = false; + if (ignore_idle && m_is_idle) { + m_is_idle = false; + reset_idle = true; + } + + LogInfo("Syncing positions for client [{}]", GetCleanName()); + CheckSendBulkNpcPositions(true); + + static EQApplicationPacket cu(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); + + for (auto &e: entity_list.GetClientList()) { + auto c = e.second; + + // skip if not in range + if (Distance(c->GetPosition(), GetPosition()) > RuleI(Range, ClientPositionUpdates)) { + continue; + } + + // skip self + if (c == this) { + continue; + } + + auto *spu = (PlayerPositionUpdateServer_Struct *) cu.pBuffer; + + memset(spu, 0x00, sizeof(PlayerPositionUpdateServer_Struct)); + spu->spawn_id = c->GetID(); + spu->x_pos = FloatToEQ19(c->GetX()); + spu->y_pos = FloatToEQ19(c->GetY()); + spu->z_pos = FloatToEQ19(c->GetZ()); + spu->heading = FloatToEQ12(c->GetHeading()); + spu->delta_x = FloatToEQ13(0); + spu->delta_y = FloatToEQ13(0); + spu->delta_z = FloatToEQ13(0); + spu->delta_heading = FloatToEQ10(0); + spu->animation = 0; + QueuePacket(&cu); + } + + if (ignore_idle && reset_idle) { + m_is_idle = false; + } +} diff --git a/zone/client_packet.h b/zone/client_packet.h index 57a4bfcf4..0840f5edc 100644 --- a/zone/client_packet.h +++ b/zone/client_packet.h @@ -114,6 +114,7 @@ void Handle_OP_Emote(const EQApplicationPacket *app); void Handle_OP_EndLootRequest(const EQApplicationPacket *app); void Handle_OP_EnvDamage(const EQApplicationPacket *app); + void Handle_OP_EvolveItem(const EQApplicationPacket *app); void Handle_OP_FaceChange(const EQApplicationPacket *app); void Handle_OP_FeignDeath(const EQApplicationPacket *app); void Handle_OP_FindPersonRequest(const EQApplicationPacket *app); @@ -188,6 +189,7 @@ void Handle_OP_ItemLinkResponse(const EQApplicationPacket *app); void Handle_OP_ItemName(const EQApplicationPacket *app); void Handle_OP_ItemPreview(const EQApplicationPacket *app); + void Handle_OP_ItemPreviewRequest(const EQApplicationPacket *app); void Handle_OP_ItemVerifyRequest(const EQApplicationPacket *app); void Handle_OP_Jump(const EQApplicationPacket *app); void Handle_OP_KeyRing(const EQApplicationPacket *app); @@ -242,6 +244,7 @@ void Handle_OP_PlayerStateAdd(const EQApplicationPacket *app); void Handle_OP_PlayerStateRemove(const EQApplicationPacket *app); void Handle_OP_PickPocket(const EQApplicationPacket *app); + void Handle_OP_PickZone(const EQApplicationPacket *app); void Handle_OP_PopupResponse(const EQApplicationPacket *app); void Handle_OP_PotionBelt(const EQApplicationPacket *app); void Handle_OP_PurchaseLeadershipAA(const EQApplicationPacket *app); @@ -307,6 +310,7 @@ void Handle_OP_TradeRequestAck(const EQApplicationPacket *app); void Handle_OP_TraderShop(const EQApplicationPacket *app); void Handle_OP_TradeSkillCombine(const EQApplicationPacket *app); + void Handle_OP_TradeSkillRecipeInspect(const EQApplicationPacket* app); void Handle_OP_Translocate(const EQApplicationPacket *app); void Handle_OP_TributeItem(const EQApplicationPacket *app); void Handle_OP_TributeMoney(const EQApplicationPacket *app); diff --git a/zone/client_process.cpp b/zone/client_process.cpp index c2e20fe5b..e14985763 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -39,8 +39,8 @@ #include "../common/skills.h" #include "../common/spdat.h" #include "../common/strings.h" +#include "dynamic_zone.h" #include "event_codes.h" -#include "expedition.h" #include "guild_mgr.h" #include "map.h" #include "petitions.h" @@ -57,7 +57,6 @@ extern QueryServ* QServ; extern Zone* zone; extern volatile bool is_zone_loaded; extern WorldServer worldserver; -extern PetitionList petition_list; extern EntityList entity_list; bool Client::Process() { @@ -122,7 +121,7 @@ bool Client::Process() { /* I haven't naturally updated my position in 10 seconds, updating manually */ if (!IsMoving() && m_position_update_timer.Check()) { - SentPositionPacket(0.0f, 0.0f, 0.0f, 0.0f, 0); + BroadcastPositionUpdate(); } if (mana_timer.Check()) @@ -179,7 +178,7 @@ bool Client::Process() { } if (IsInAGuild()) { guild_mgr.UpdateDbMemberOnline(CharacterID(), false); - guild_mgr.SendToWorldSendGuildMembersList(GuildID()); + guild_mgr.SendGuildMemberUpdateToWorld(GetName(), GuildID(), 0, time(nullptr)); } SetDynamicZoneMemberStatus(DynamicZoneMemberStatus::Offline); @@ -193,6 +192,12 @@ bool Client::Process() { return false; //delete client } + if (RuleB(Bots, Enabled)) { + if (bot_camp_timer.Check()) { + CampAllBots(); + } + } + if (camp_timer.Check()) { Raid *myraid = entity_list.GetRaidByClient(this); if (myraid) { @@ -202,7 +207,7 @@ bool Client::Process() { Save(); if (IsInAGuild()) { guild_mgr.UpdateDbMemberOnline(CharacterID(), false); - guild_mgr.SendToWorldSendGuildMembersList(GuildID()); + guild_mgr.SendGuildMemberUpdateToWorld(GetName(), GuildID(), 0, time(nullptr)); } if (GetMerc()) @@ -211,6 +216,8 @@ bool Client::Process() { GetMerc()->Depop(); } instalog = true; + + camp_timer.Disable(); } if (IsStunned() && stunned_timer.Check()) @@ -294,24 +301,30 @@ bool Client::Process() { } } - if (m_lazy_load_bank && m_lazy_load_sent_bank_slots <= EQ::invslot::SHARED_BANK_END) { - const EQ::ItemInstance *inst = nullptr; + int lazy_load_bank_slots = 0; + for (int i = 0; i < 5000; i++) { + if (m_lazy_load_bank && m_lazy_load_sent_bank_slots <= EQ::invslot::SHARED_BANK_END) { + const EQ::ItemInstance *inst = nullptr; - // Jump the gaps - if (m_lazy_load_sent_bank_slots < EQ::invslot::BANK_BEGIN) { - m_lazy_load_sent_bank_slots = EQ::invslot::BANK_BEGIN; - } - else if (m_lazy_load_sent_bank_slots > EQ::invslot::BANK_END && - m_lazy_load_sent_bank_slots < EQ::invslot::SHARED_BANK_BEGIN) { - m_lazy_load_sent_bank_slots = EQ::invslot::SHARED_BANK_BEGIN; - } - else { - m_lazy_load_sent_bank_slots++; - } + // Jump the gaps + if (m_lazy_load_sent_bank_slots < EQ::invslot::BANK_BEGIN) { + m_lazy_load_sent_bank_slots = EQ::invslot::BANK_BEGIN; + } + else if (m_lazy_load_sent_bank_slots > EQ::invslot::BANK_END && + m_lazy_load_sent_bank_slots < EQ::invslot::SHARED_BANK_BEGIN) { + m_lazy_load_sent_bank_slots = EQ::invslot::SHARED_BANK_BEGIN; + } + else { + m_lazy_load_sent_bank_slots++; + } - inst = m_inv[m_lazy_load_sent_bank_slots]; - if (inst) { - SendItemPacket(m_lazy_load_sent_bank_slots, inst, ItemPacketType::ItemPacketTrade); + inst = m_inv[m_lazy_load_sent_bank_slots]; + if (inst) { + SendItemPacket(m_lazy_load_sent_bank_slots, inst, ItemPacketType::ItemPacketTrade); + lazy_load_bank_slots++; + } + } else { + break; } } } @@ -522,6 +535,10 @@ bool Client::Process() { DoEnduranceRegen(); BuffProcess(); + if (auto_attack) { + ResetAFKTimer(); + } + if (tribute_timer.Check()) { ToggleTribute(true); //re-activate the tribute. } @@ -544,6 +561,10 @@ bool Client::Process() { { ItemTimerCheck(); } + + if (m_clear_wearchange_cache_timer.Check()) { + m_last_seen_wearchange.clear(); + } } } @@ -578,7 +599,7 @@ bool Client::Process() { } if (IsInAGuild()) { guild_mgr.UpdateDbMemberOnline(CharacterID(), false); - guild_mgr.SendToWorldSendGuildMembersList(GuildID()); + guild_mgr.SendGuildMemberUpdateToWorld(GetName(), GuildID(), 0, time(nullptr)); } return false; @@ -691,12 +712,6 @@ void Client::OnDisconnect(bool hard_disconnect) { if (r) { r->MemberZoned(this); } - - /* QS: PlayerLogConnectDisconnect */ - if (RuleB(QueryServ, PlayerLogConnectDisconnect)) { - std::string event_desc = StringFormat("Disconnect :: in zoneid:%i instid:%i", GetZoneID(), GetInstanceID()); - QServ->PlayerLogEvent(Player_Log_Connect_State, CharacterID(), event_desc); - } } if (!bZoning) { @@ -719,7 +734,7 @@ void Client::OnDisconnect(bool hard_disconnect) { o->trade->Reset(); } - database.SetFirstLogon(CharacterID(), 0); //We change firstlogon status regardless of if a player logs out to zone or not, because we only want to trigger it on their first login from world. + database.SetIngame(CharacterID(), 0); //We change ingame status regardless of if a player logs out to zone or not, because we only want to trigger it on their first login from world. /* Remove from all proximities */ ClearAllProximities(); @@ -764,7 +779,7 @@ void Client::BulkSendInventoryItems() RemoveNoRent(false); } - RemoveDuplicateLore(false); + RemoveDuplicateLore(); MoveSlotNotAllowed(false); EQ::OutBuffer ob; @@ -773,62 +788,64 @@ void Client::BulkSendInventoryItems() // Possessions items for (int16 slot_id = EQ::invslot::POSSESSIONS_BEGIN; slot_id <= EQ::invslot::POSSESSIONS_END; slot_id++) { const EQ::ItemInstance* inst = m_inv[slot_id]; - if (!inst) + if (!inst) { continue; + } inst->Serialize(ob, slot_id); - if (ob.tellp() == last_pos) + if (ob.tellp() == last_pos) { LogInventory("Serialization failed on item slot [{}] during BulkSendInventoryItems. Item skipped", slot_id); + } last_pos = ob.tellp(); } - if (!RuleB(Inventory, LazyLoadBank)) { - // Bank items - for (int16 slot_id = EQ::invslot::BANK_BEGIN; slot_id <= EQ::invslot::BANK_END; slot_id++) { - const EQ::ItemInstance* inst = m_inv[slot_id]; - if (!inst) - continue; + if (!RuleB(Inventory, LazyLoadBank)) { + // Bank items + for (int16 slot_id = EQ::invslot::BANK_BEGIN; slot_id <= EQ::invslot::BANK_END; slot_id++) { + const EQ::ItemInstance* inst = m_inv[slot_id]; + if (!inst) { + continue; + } - inst->Serialize(ob, slot_id); + inst->Serialize(ob, slot_id); - if (ob.tellp() == last_pos) - LogInventory("Serialization failed on item slot [{}] during BulkSendInventoryItems. Item skipped", slot_id); + if (ob.tellp() == last_pos) { + LogInventory("Serialization failed on item slot [{}] during BulkSendInventoryItems. Item skipped", slot_id); + } - last_pos = ob.tellp(); - } + last_pos = ob.tellp(); + } - // SharedBank items - for (int16 slot_id = EQ::invslot::SHARED_BANK_BEGIN; slot_id <= EQ::invslot::SHARED_BANK_END; slot_id++) { - const EQ::ItemInstance* inst = m_inv[slot_id]; - if (!inst) - continue; + // SharedBank items + for (int16 slot_id = EQ::invslot::SHARED_BANK_BEGIN; slot_id <= EQ::invslot::SHARED_BANK_END; slot_id++) { + const EQ::ItemInstance* inst = m_inv[slot_id]; + if (!inst) { + continue; + } - inst->Serialize(ob, slot_id); + inst->Serialize(ob, slot_id); - if (ob.tellp() == last_pos) - LogInventory("Serialization failed on item slot [{}] during BulkSendInventoryItems. Item skipped", slot_id); + if (ob.tellp() == last_pos) { + LogInventory("Serialization failed on item slot [{}] during BulkSendInventoryItems. Item skipped", slot_id); + } - last_pos = ob.tellp(); - } - } + last_pos = ob.tellp(); + } + } - auto outapp = new EQApplicationPacket(OP_CharInventory); - outapp->size = ob.size(); - outapp->pBuffer = ob.detach(); - QueuePacket(outapp); - safe_delete(outapp); + auto outapp = new EQApplicationPacket(OP_CharInventory); + + outapp->size = ob.size(); + outapp->pBuffer = ob.detach(); + + QueuePacket(outapp); + safe_delete(outapp); } void Client::BulkSendMerchantInventory(int merchant_id, int npcid) { const EQ::ItemData* handy_item = nullptr; - - uint32 merchant_slots = 80; //The max number of items passed in the transaction. - if (m_ClientVersionBit & EQ::versions::maskRoFAndLater) { // RoF+ can send 200 items - merchant_slots = 200; - } - const EQ::ItemData *item = nullptr; auto merchant_list = zone->merchanttable[merchant_id]; auto npc = entity_list.GetMobByNpcTypeID(npcid); @@ -840,6 +857,8 @@ void Client::BulkSendMerchantInventory(int merchant_id, int npcid) { } } + const int16 merchant_slots = (m_ClientVersionBit & EQ::versions::maskRoFAndLater) ? EQ::invtype::MERCHANT_SIZE : 80; + auto temporary_merchant_list = zone->tmpmerchanttable[npcid]; uint32 slot_id = 1; uint8 handy_chance = 0; @@ -1552,7 +1571,7 @@ void Client::OPMoveCoin(const EQApplicationPacket* app) if (from_bucket == &m_pp.platinum_shared) amount_to_add = 0 - amount_to_take; - database.SetSharedPlatinum(AccountID(),amount_to_add); + database.AddSharedPlatinum(AccountID(),amount_to_add); } } else{ @@ -1629,7 +1648,7 @@ void Client::OPGMTraining(const EQApplicationPacket *app) //#pragma GCC push_options //#pragma GCC optimize ("O0") for (int sk = EQ::skills::Skill1HBlunt; sk <= EQ::skills::HIGHEST_SKILL; ++sk) { - if (sk == EQ::skills::SkillTinkering && GetRace() != GNOME) { + if (sk == EQ::skills::SkillTinkering && GetRace() != Race::Gnome) { gmtrain->skills[sk] = 0; //Non gnomes can't tinker! } else { gmtrain->skills[sk] = GetMaxSkillAfterSpecializationRules((EQ::skills::SkillType)sk, MaxSkill((EQ::skills::SkillType)sk, GetClass(), RuleI(Character, MaxLevel))); diff --git a/zone/command.cpp b/zone/command.cpp index 513cd0218..a31760828 100644 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -19,10 +19,9 @@ #include "../common/file.h" #include "../common/repositories/dynamic_zones_repository.h" -#include "data_bucket.h" +#include "../common/data_bucket.h" #include "command.h" #include "dynamic_zone.h" -#include "expedition.h" #include "queryserv.h" #include "quest_parser_collection.h" #include "titles.h" @@ -35,7 +34,6 @@ extern QueryServ* QServ; extern WorldServer worldserver; -extern TaskManager *task_manager; extern FastMath g_Math; void CatchSignal(int sig_num); @@ -126,6 +124,7 @@ int command_init(void) command_add("enablerecipe", "[Recipe ID] - Enables a Recipe", AccountStatus::QuestTroupe, command_enablerecipe) || command_add("entityvariable", "[clear|delete|set|view] - Modify entity variables for yourself or your target", AccountStatus::GMAdmin, command_entityvariable) || command_add("exptoggle", "[Toggle] - Toggle your or your target's experience gain.", AccountStatus::QuestTroupe, command_exptoggle) || + command_add("evolve", "Evolving Item manipulation commands. Use argument help for more info.", AccountStatus::Steward, command_evolvingitems) || command_add("faction", "[Find (criteria | all ) | Review (criteria | all) | Reset (id)] - Resets Player's Faction", AccountStatus::QuestTroupe, command_faction) || command_add("factionassociation", "[factionid] [amount] - triggers a faction hits via association", AccountStatus::GMLeadAdmin, command_faction_association) || command_add("feature", "Change your or your target's feature's temporarily", AccountStatus::QuestTroupe, command_feature) || @@ -147,6 +146,7 @@ int command_init(void) command_add("help", "[Search Criteria] - List available commands and their description, specify partial command as argument to search", AccountStatus::Player, command_help) || command_add("hotfix", "[hotfix_name] - Reloads shared memory into a hotfix, equiv to load_shared_memory followed by apply_shared_memory", AccountStatus::GMImpossible, command_hotfix) || command_add("hp", "Refresh your HP bar from the server.", AccountStatus::Player, command_hp) || + command_add("illusionblock", "Controls whether or not illusion effects will land on you when cast by other players or bots", AccountStatus::Guide, command_illusion_block) || command_add("instance", "Modify Instances", AccountStatus::GMMgmt, command_instance) || command_add("interrogateinv", "use [help] argument for available options", AccountStatus::Player, command_interrogateinv) || command_add("interrupt", "[Message ID] [Color] - Interrupt your casting. Arguments are optional.", AccountStatus::Guide, command_interrupt) || @@ -243,7 +243,9 @@ int command_init(void) command_add("zone", "[Zone ID|Zone Short Name] [X] [Y] [Z] - Teleport to specified Zone by ID or Short Name (coordinates are optional)", AccountStatus::Guide, command_zone) || 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("zoneshutdown", "[shortname] - Shut down a zone server", AccountStatus::GMLeadAdmin, command_zoneshutdown) || + command_add("zoneshard", "[zone] [instance_id] - Teleport explicitly to a zone shard", AccountStatus::Player, command_zone_shard) || + command_add("zoneshutdown", "[instance|zone] [Instance ID|Zone ID|Zone Short Name] - Shut down a zone server by Instance ID, Zone ID, or Zone Short Name", AccountStatus::GMLeadAdmin, command_zoneshutdown) || + command_add("zonevariable", "[clear|delete|set|view] - Modify zone variables for your current zone", AccountStatus::GMAdmin, command_zonevariable) || command_add("zsave", " Saves zheader to the database", AccountStatus::QuestTroupe, command_zsave) ) { command_deinit(); @@ -492,17 +494,6 @@ int command_realdispatch(Client *c, std::string message, bool ignore_status) } } - /* QS: Player_Log_Issued_Commands */ - if (RuleB(QueryServ, PlayerLogIssuedCommandes)) { - auto event_desc = fmt::format( - "Issued command :: '{}' in Zone ID: {} Instance ID: {}", - message, - c->GetZoneID(), - c->GetInstanceID() - ); - QServ->PlayerLogEvent(Player_Log_Issued_Commands, c->CharacterID(), event_desc); - } - if (current_command->admin >= COMMANDS_LOGGING_MIN_STATUS) { LogCommands( "[{}] ([{}]) used command: [{}] (target=[{}])", @@ -522,7 +513,15 @@ int command_realdispatch(Client *c, std::string message, bool ignore_status) parse->EventPlayer(EVENT_GM_COMMAND, c, message, 0); } - if (player_event_logs.IsEventEnabled(PlayerEvent::GM_COMMAND) && message != "#help") { + bool log_command = true; + for (auto &cmd: Strings::Split(RuleS(Logging, PlayerEventsIgnoreGMCommands), ",")) { + if (Strings::Contains(command, Strings::ToLower(cmd))) { + log_command = false; + break; + } + } + + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::GM_COMMAND) && log_command) { auto e = PlayerEvent::GMCommandEvent{ .message = message, .target = c->GetTarget() ? c->GetTarget()->GetName() : "NONE" @@ -783,157 +782,3 @@ void command_bot(Client *c, const Seperator *sep) c->Message(Chat::Red, "Bots are disabled on this server."); } } - -#include "gm_commands/acceptrules.cpp" -#include "gm_commands/advnpcspawn.cpp" -#include "gm_commands/aggrozone.cpp" -#include "gm_commands/ai.cpp" -#include "gm_commands/appearance.cpp" -#include "gm_commands/appearanceeffects.cpp" -#include "gm_commands/attack.cpp" -#include "gm_commands/augmentitem.cpp" -#include "gm_commands/ban.cpp" -#include "gm_commands/bugs.cpp" -#include "gm_commands/camerashake.cpp" -#include "gm_commands/castspell.cpp" -#include "gm_commands/chat.cpp" -#include "gm_commands/clearxtargets.cpp" -#include "gm_commands/copycharacter.cpp" -#include "gm_commands/corpse.cpp" -#include "gm_commands/corpsefix.cpp" -#include "gm_commands/countitem.cpp" -#include "gm_commands/damage.cpp" -#include "gm_commands/databuckets.cpp" -#include "gm_commands/dbspawn2.cpp" -#include "gm_commands/delacct.cpp" -#include "gm_commands/delpetition.cpp" -#include "gm_commands/depop.cpp" -#include "gm_commands/depopzone.cpp" -#include "gm_commands/devtools.cpp" -#include "gm_commands/disablerecipe.cpp" -#include "gm_commands/disarmtrap.cpp" -#include "gm_commands/doanim.cpp" -#include "gm_commands/door.cpp" -#include "gm_commands/door_manipulation.cpp" -#include "gm_commands/dye.cpp" -#include "gm_commands/dz.cpp" -#include "gm_commands/dzkickplayers.cpp" -#include "gm_commands/editmassrespawn.cpp" -#include "gm_commands/emote.cpp" -#include "gm_commands/emptyinventory.cpp" -#include "gm_commands/enablerecipe.cpp" -#include "gm_commands/entityvariable.cpp" -#include "gm_commands/exptoggle.cpp" -#include "gm_commands/faction.cpp" -#include "gm_commands/feature.cpp" -#include "gm_commands/find.cpp" -#include "gm_commands/fish.cpp" -#include "gm_commands/fixmob.cpp" -#include "gm_commands/flagedit.cpp" -#include "gm_commands/fleeinfo.cpp" -#include "gm_commands/forage.cpp" -#include "gm_commands/gearup.cpp" -#include "gm_commands/giveitem.cpp" -#include "gm_commands/givemoney.cpp" -#include "gm_commands/gmzone.cpp" -#include "gm_commands/goto.cpp" -#include "gm_commands/grantaa.cpp" -#include "gm_commands/grid.cpp" -#include "gm_commands/guild.cpp" -#include "gm_commands/hp.cpp" -#include "gm_commands/instance.cpp" -#include "gm_commands/interrogateinv.cpp" -#include "gm_commands/interrupt.cpp" -#include "gm_commands/invsnapshot.cpp" -#include "gm_commands/ipban.cpp" -#include "gm_commands/kick.cpp" -#include "gm_commands/kill.cpp" -#include "gm_commands/killallnpcs.cpp" -#include "gm_commands/list.cpp" -#include "gm_commands/lootsim.cpp" -#include "gm_commands/loc.cpp" -#include "gm_commands/logs.cpp" -#include "gm_commands/makepet.cpp" -#include "gm_commands/memspell.cpp" -#include "gm_commands/merchantshop.cpp" -#include "gm_commands/modifynpcstat.cpp" -#include "gm_commands/movechar.cpp" -#include "gm_commands/movement.cpp" -#include "gm_commands/myskills.cpp" -#include "gm_commands/mysql.cpp" -#include "gm_commands/mystats.cpp" -#include "gm_commands/npccast.cpp" -#include "gm_commands/npcedit.cpp" -#include "gm_commands/npceditmass.cpp" -#include "gm_commands/npcemote.cpp" -#include "gm_commands/npcloot.cpp" -#include "gm_commands/npcsay.cpp" -#include "gm_commands/npcshout.cpp" -#include "gm_commands/npcspawn.cpp" -#include "gm_commands/npctypespawn.cpp" -#include "gm_commands/nudge.cpp" -#include "gm_commands/nukebuffs.cpp" -#include "gm_commands/nukeitem.cpp" -#include "gm_commands/object.cpp" -#include "gm_commands/object_manipulation.cpp" -#include "gm_commands/parcels.cpp" -#include "gm_commands/path.cpp" -#include "gm_commands/peqzone.cpp" -#include "gm_commands/petitems.cpp" -#include "gm_commands/petname.cpp" -#include "gm_commands/picklock.cpp" -#include "gm_commands/profanity.cpp" -#include "gm_commands/push.cpp" -#include "gm_commands/raidloot.cpp" -#include "gm_commands/randomfeatures.cpp" -#include "gm_commands/refreshgroup.cpp" -#include "gm_commands/reload.cpp" -#include "gm_commands/removeitem.cpp" -#include "gm_commands/repop.cpp" -#include "gm_commands/resetaa.cpp" -#include "gm_commands/resetaa_timer.cpp" -#include "gm_commands/resetdisc_timer.cpp" -#include "gm_commands/revoke.cpp" -#include "gm_commands/roambox.cpp" -#include "gm_commands/rules.cpp" -#include "gm_commands/save.cpp" -#include "gm_commands/scale.cpp" -#include "gm_commands/scribespell.cpp" -#include "gm_commands/scribespells.cpp" -#include "gm_commands/sendzonespawns.cpp" -#include "gm_commands/sensetrap.cpp" -#include "gm_commands/serverrules.cpp" -#include "gm_commands/set.cpp" -#include "gm_commands/show.cpp" -#include "gm_commands/shutdown.cpp" -#include "gm_commands/spawn.cpp" -#include "gm_commands/spawneditmass.cpp" -#include "gm_commands/spawnfix.cpp" -#include "gm_commands/faction_association.cpp" -#include "gm_commands/stun.cpp" -#include "gm_commands/summon.cpp" -#include "gm_commands/summonburiedplayercorpse.cpp" -#include "gm_commands/summonitem.cpp" -#include "gm_commands/suspend.cpp" -#include "gm_commands/suspendmulti.cpp" -#include "gm_commands/takeplatinum.cpp" -#include "gm_commands/task.cpp" -#include "gm_commands/traindisc.cpp" -#include "gm_commands/tune.cpp" -#include "gm_commands/undye.cpp" -#include "gm_commands/unmemspell.cpp" -#include "gm_commands/unmemspells.cpp" -#include "gm_commands/unscribespell.cpp" -#include "gm_commands/unscribespells.cpp" -#include "gm_commands/untraindisc.cpp" -#include "gm_commands/untraindiscs.cpp" -#include "gm_commands/wc.cpp" -#include "gm_commands/worldshutdown.cpp" -#include "gm_commands/worldwide.cpp" -#include "gm_commands/wp.cpp" -#include "gm_commands/wpadd.cpp" -#include "gm_commands/zone.cpp" -#include "gm_commands/zonebootup.cpp" -#include "gm_commands/zoneshutdown.cpp" -#include "gm_commands/zone_instance.cpp" -#include "gm_commands/zsave.cpp" diff --git a/zone/command.h b/zone/command.h index d59fdf9a5..0159d4d65 100644 --- a/zone/command.h +++ b/zone/command.h @@ -38,6 +38,7 @@ void SendShowInventorySubCommands(Client *c); void SendFixMobSubCommands(Client *c); void SendDataBucketsSubCommands(Client *c); void SendParcelsSubCommands(Client *c); +void SendEvolvingItemsSubCommands(Client *c); // Commands void command_acceptrules(Client *c, const Seperator *sep); @@ -80,6 +81,7 @@ void command_emptyinventory(Client *c, const Seperator *sep); void command_enablerecipe(Client *c, const Seperator *sep); void command_entityvariable(Client *c, const Seperator *sep); void command_exptoggle(Client *c, const Seperator *sep); +void command_evolvingitems(Client *c, const Seperator *sep); void command_faction(Client *c, const Seperator *sep); void command_faction_association(Client *c, const Seperator *sep); void command_feature(Client *c, const Seperator *sep); @@ -100,6 +102,7 @@ void command_guild(Client *c, const Seperator *sep); void command_help(Client *c, const Seperator *sep); void command_hotfix(Client *c, const Seperator *sep); void command_hp(Client *c, const Seperator *sep); +void command_illusion_block(Client* c, const Seperator* sep); void command_instance(Client *c, const Seperator *sep); void command_interrogateinv(Client *c, const Seperator *sep); void command_interrupt(Client *c, const Seperator *sep); @@ -108,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); @@ -136,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); @@ -144,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); @@ -195,10 +195,10 @@ void command_wpadd(Client *c, const Seperator *sep); void command_worldwide(Client *c, const Seperator *sep); void command_zone(Client *c, const Seperator *sep); 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_zonevariable(Client *c, const Seperator *sep); void command_zsave(Client *c, const Seperator *sep); #include "bot.h" diff --git a/zone/common.h b/zone/common.h index 98d0eda6d..6a11f7255 100644 --- a/zone/common.h +++ b/zone/common.h @@ -3,6 +3,9 @@ #include "../common/types.h" #include "../common/spdat.h" +#include "../common/emu_constants.h" + +#include #define HIGHEST_RESIST 9 //Max resist type value #define MAX_SPELL_PROJECTILE 10 //Max amount of spell projectiles that can be active by a single mob. @@ -43,55 +46,6 @@ namespace Archetype { //Maximum distance from a zone point if zone was specified #define ZONEPOINT_ZONE_RANGE 40000.0f -// Defines based on the RoF2 Client -#define PET_HEALTHREPORT 0 // 0x00 - /pet health or Pet Window -#define PET_LEADER 1 // 0x01 - /pet leader or Pet Window -#define PET_ATTACK 2 // 0x02 - /pet attack or Pet Window -#define PET_QATTACK 3 // 0x03 - /pet qattack or Pet Window -#define PET_FOLLOWME 4 // 0x04 - /pet follow or Pet Window -#define PET_GUARDHERE 5 // 0x05 - /pet guard or Pet Window -#define PET_SIT 6 // 0x06 - /pet sit or Pet Window -#define PET_SITDOWN 7 // 0x07 - /pet sit on -#define PET_STANDUP 8 // 0x08 - /pet sit off -#define PET_STOP 9 // 0x09 - /pet stop or Pet Window - Not implemented -#define PET_STOP_ON 10 // 0x0a - /pet stop on - Not implemented -#define PET_STOP_OFF 11 // 0x0b - /pet stop off - Not implemented -#define PET_TAUNT 12 // 0x0c - /pet taunt or Pet Window -#define PET_TAUNT_ON 13 // 0x0d - /pet taunt on -#define PET_TAUNT_OFF 14 // 0x0e - /pet taunt off -#define PET_HOLD 15 // 0x0f - /pet hold or Pet Window, won't add to hate list unless attacking -#define PET_HOLD_ON 16 // 0x10 - /pet hold on -#define PET_HOLD_OFF 17 // 0x11 - /pet hold off -#define PET_GHOLD 18 // 0x12 - /pet ghold, will never add to hate list unless told to -#define PET_GHOLD_ON 19 // 0x13 - /pet ghold on -#define PET_GHOLD_OFF 20 // 0x14 - /pet ghold off -#define PET_SPELLHOLD 21 // 0x15 - /pet no cast or /pet spellhold or Pet Window -#define PET_SPELLHOLD_ON 22 // 0x16 - /pet spellhold on -#define PET_SPELLHOLD_OFF 23 // 0x17 - /pet spellhold off -#define PET_FOCUS 24 // 0x18 - /pet focus or Pet Window -#define PET_FOCUS_ON 25 // 0x19 - /pet focus on -#define PET_FOCUS_OFF 26 // 0x1a - /pet focus off -#define PET_FEIGN 27 // 0x1b - /pet feign -#define PET_BACKOFF 28 // 0x1c - /pet back off -#define PET_GETLOST 29 // 0x1d - /pet get lost -#define PET_GUARDME 30 // 0x1e - Same as /pet follow, but different message in older clients - define not from client /pet target in modern clients but doesn't send packet -#define PET_REGROUP 31 // 0x1f - /pet regroup, acts like classic hold. Stops attack and moves back to guard/you but doesn't clear hate list -#define PET_REGROUP_ON 32 // 0x20 - /pet regroup on, turns on regroup -#define PET_REGROUP_OFF 33 // 0x21 - /pet regroup off, turns off regroup -#define PET_MAXCOMMANDS PET_REGROUP_OFF + 1 - -// can change the state of these buttons with a packet -#define PET_BUTTON_SIT 0 -#define PET_BUTTON_STOP 1 -#define PET_BUTTON_REGROUP 2 -#define PET_BUTTON_FOLLOW 3 -#define PET_BUTTON_GUARD 4 -#define PET_BUTTON_TAUNT 5 -#define PET_BUTTON_HOLD 6 -#define PET_BUTTON_GHOLD 7 -#define PET_BUTTON_FOCUS 8 -#define PET_BUTTON_SPELLHOLD 9 - #define AURA_HARDCAP 2 #define WEAPON_STANCE_TYPE_MAX 2 @@ -99,50 +53,50 @@ namespace Archetype { #define SHIELD_ABILITY_RECAST_TIME 180 typedef enum { //focus types - focusSpellHaste = 1, //@Fc, SPA: 127, SE_IncreaseSpellHaste, On Caster, cast time mod pct, base: pct - focusSpellDuration, //@Fc, SPA: 128, SE_IncreaseSpellDuration, On Caster, spell duration mod pct, base: pct - focusRange, //@Fc, SPA: 129, SE_IncreaseRange, On Caster, spell range mod pct, base: pct - focusReagentCost, //@Fc, SPA: 131, SE_ReduceReagentCost, On Caster, do not consume reagent pct chance, base: min pct, limit: max pct - focusManaCost, //@Fc, SPA: 132, SE_ReduceManaCost, On Caster, reduce mana cost by pct, base: min pct, limt: max pct - focusImprovedHeal, //@Fc, SPA: 125, SE_ImprovedHeal, On Caster, spell healing mod pct, base: min pct, limit: max pct - focusImprovedDamage, //@Fc, SPA: 124, SE_ImprovedDamage, On Caster, spell damage mod pct, base: min pct, limit: max pct - focusImprovedDamage2, //@Fc, SPA: 461, SE_ImprovedDamage2, On Caster, spell damage mod pct, base: min pct, limit: max pct - focusFcDamagePctCrit, //@Fc, SPA: 302, SE_FcDamagePctCrit, On Caster, spell damage mod pct, base: min pct, limit: max pct - focusPetPower, //@Fc, SPA: 167, SE_PetPowerIncrease, On Caster, pet power mod, base: value - focusResistRate, //@Fc, SPA: 126, SE_SpellResistReduction, On Caster, casted spell resist mod pct, base: min pct, limit: max pct - focusSpellHateMod, //@Fc, SPA: 130, SE_SpellHateMod, On Caster, spell hate mod pct, base: min pct, limit: max pct - focusTriggerOnCast, //@Fc, SPA: 339, SE_TriggerOnCast, On Caster, cast on spell use, base: chance pct limit: spellid - focusSpellVulnerability, //@Fc, SPA: 296, SE_FcSpellVulnerability, On Target, spell damage taken mod pct, base: min pct, limit: max pct - focusFcSpellDamagePctIncomingPC, //@Fc, SPA: 483, SE_Fc_Spell_Damage_Pct_IncomingPC, On Target, spell damage taken mod pct, base: min pct, limit: max pct - focusTwincast, //@Fc, SPA: 399, SE_FcTwincast, On Caster, chance cast spell twice, base: chance pct - focusSympatheticProc, //@Fc, SPA: 383, SE_SympatheticProc, On Caster, cast on spell use, base: variable proc chance on cast time, limit: spellid - focusFcDamageAmt, //@Fc, SPA: 286, SE_FcDamageAmt, On Caster, spell damage mod flat amt, base: amt - focusFcDamageAmt2, //@Fc, SPA: 462, SE_FcDamageAmt2, On Caster, spell damage mod flat amt, base: amt - focusFcDamageAmtCrit, //@Fc, SPA: 303, SE_FFcDamageAmtCrit, On Caster, spell damage mod flat amt, base: amt - focusSpellDurByTic, //@Fc, SPA: 287, SE_SpellDurationIncByTic, On Caster, spell buff duration mod, base: tics - focusSwarmPetDuration, //@Fc, SPA: 398, SE_SwarmPetDuration, On Caster, swarm pet duration mod, base: milliseconds - focusReduceRecastTime, //@Fc, SPA: 310, SE_ReduceReuseTimer, On Caster, disc reuse time mod, base: milliseconds - focusBlockNextSpell, //@Fc, SPA: 335, SE_BlockNextSpellFocus, On Caster, chance to block next spell, base: chance - focusFcHealPctIncoming, //@Fc, SPA: 393, SE_FcHealPctIncoming, On Target, heal received mod pct, base: pct - focusFcDamageAmtIncoming, //@Fc, SPA: 297, SE_FcDamageAmtIncoming, On Target, damage taken flat amt, base: amt - focusFcSpellDamageAmtIncomingPC, //@Fc, SPA: 484, SE_Fc_Spell_Damage_Amt_IncomingPC, On Target, damage taken flat amt, base: amt - focusFcCastSpellOnLand, //@Fc, SPA: 481, SE_Fc_Cast_Spell_On_Land, On Target, cast spell if hit by spell, base: chance pct, limit: spellid - focusFcHealAmtIncoming, //@Fc, SPA: 394, SE_FcHealAmtIncoming, On Target, heal received mod flat amt, base: amt - focusFcBaseEffects, //@Fc, SPA: 413, SE_FcBaseEffects, On Caster, base spell effectiveness mod pct, base: pct - focusIncreaseNumHits, //@Fc, SPA: 421, SE_FcIncreaseNumHits, On Caster, numhits mod flat amt, base: amt - focusFcLimitUse, //@Fc, SPA: 420, SE_FcLimitUse, On Caster, numhits mod pct, base: pct - focusFcMute, //@Fc, SPA: 357, SE_FcMute, On Caster, prevents spell casting, base: chance pct - focusFcTimerRefresh, //@Fc, SPA: 389, SE_FcTimerRefresh, On Caster, reset spell recast timer, base: 1 - focusFcTimerLockout, //@Fc, SPA: 390, SE_FcTimerLockout, On Caster, set a spell to be on recast timer, base: recast duration milliseconds - focusFcStunTimeMod, //@Fc, SPA: 133, SE_FcStunTimeMod, On Caster, stun time mod pct, base: chance pct - focusFcResistIncoming, //@Fc, SPA: 510, SE_Fc_Resist_Incoming, On Target, resist modifier, base: amt - focusFcAmplifyMod, //@Fc, SPA: 507, SE_Fc_Amplify_Mod, On Caster, damage-heal-dot mod pct, base: pct - focusFcAmplifyAmt, //@Fc, SPA: 508, SE_Fc_Amplify_Amt, On Caster, damage-heal-dot mod flat amt, base: amt - focusFcCastTimeMod2, //@Fc, SPA: 500, SE_Fc_CastTimeMod2, On Caster, cast time mod pct, base: pct - focusFcCastTimeAmt, //@Fc, SPA: 501, SE_Fc_CastTimeAmt, On Caster, cast time mod flat amt, base: milliseconds - focusFcHealPctCritIncoming, //@Fc, SPA: 395, SE_FcHealPctCritIncoming, On Target, spell healing mod pct, base: pct - focusFcHealAmt, //@Fc, SPA: 392, SE_FcHealAmt, On Caster, spell healing mod flat amt, base: amt - focusFcHealAmtCrit, //@Fc, SPA: 396, SE_FcHealAmtCrit, On Caster, spell healing mod flat amt, base: amt + focusSpellHaste = 1, //@Fc, SPA: 127, SpellEffect::IncreaseSpellHaste, On Caster, cast time mod pct, base: pct + focusSpellDuration, //@Fc, SPA: 128, SpellEffect::IncreaseSpellDuration, On Caster, spell duration mod pct, base: pct + focusRange, //@Fc, SPA: 129, SpellEffect::IncreaseRange, On Caster, spell range mod pct, base: pct + focusReagentCost, //@Fc, SPA: 131, SpellEffect::ReduceReagentCost, On Caster, do not consume reagent pct chance, base: min pct, limit: max pct + focusManaCost, //@Fc, SPA: 132, SpellEffect::ReduceManaCost, On Caster, reduce mana cost by pct, base: min pct, limt: max pct + focusImprovedHeal, //@Fc, SPA: 125, SpellEffect::ImprovedHeal, On Caster, spell healing mod pct, base: min pct, limit: max pct + focusImprovedDamage, //@Fc, SPA: 124, SpellEffect::ImprovedDamage, On Caster, spell damage mod pct, base: min pct, limit: max pct + focusImprovedDamage2, //@Fc, SPA: 461, SpellEffect::ImprovedDamage2, On Caster, spell damage mod pct, base: min pct, limit: max pct + focusFcDamagePctCrit, //@Fc, SPA: 302, SpellEffect::FcDamagePctCrit, On Caster, spell damage mod pct, base: min pct, limit: max pct + focusPetPower, //@Fc, SPA: 167, SpellEffect::PetPowerIncrease, On Caster, pet power mod, base: value + focusResistRate, //@Fc, SPA: 126, SpellEffect::SpellResistReduction, On Caster, casted spell resist mod pct, base: min pct, limit: max pct + focusSpellHateMod, //@Fc, SPA: 130, SpellEffect::SpellHateMod, On Caster, spell hate mod pct, base: min pct, limit: max pct + focusTriggerOnCast, //@Fc, SPA: 339, SpellEffect::TriggerOnCast, On Caster, cast on spell use, base: chance pct limit: spellid + focusSpellVulnerability, //@Fc, SPA: 296, SpellEffect::FcSpellVulnerability, On Target, spell damage taken mod pct, base: min pct, limit: max pct + focusFcSpellDamagePctIncomingPC, //@Fc, SPA: 483, SpellEffect::Fc_Spell_Damage_Pct_IncomingPC, On Target, spell damage taken mod pct, base: min pct, limit: max pct + focusTwincast, //@Fc, SPA: 399, SpellEffect::FcTwincast, On Caster, chance cast spell twice, base: chance pct + focusSympatheticProc, //@Fc, SPA: 383, SpellEffect::SympatheticProc, On Caster, cast on spell use, base: variable proc chance on cast time, limit: spellid + focusFcDamageAmt, //@Fc, SPA: 286, SpellEffect::FcDamageAmt, On Caster, spell damage mod flat amt, base: amt + focusFcDamageAmt2, //@Fc, SPA: 462, SpellEffect::FcDamageAmt2, On Caster, spell damage mod flat amt, base: amt + focusFcDamageAmtCrit, //@Fc, SPA: 303, SpellEffect::FFcDamageAmtCrit, On Caster, spell damage mod flat amt, base: amt + focusSpellDurByTic, //@Fc, SPA: 287, SpellEffect::SpellDurationIncByTic, On Caster, spell buff duration mod, base: tics + focusSwarmPetDuration, //@Fc, SPA: 398, SpellEffect::SwarmPetDuration, On Caster, swarm pet duration mod, base: milliseconds + focusReduceRecastTime, //@Fc, SPA: 310, SpellEffect::ReduceReuseTimer, On Caster, disc reuse time mod, base: milliseconds + focusBlockNextSpell, //@Fc, SPA: 335, SpellEffect::BlockNextSpellFocus, On Caster, chance to block next spell, base: chance + focusFcHealPctIncoming, //@Fc, SPA: 393, SpellEffect::FcHealPctIncoming, On Target, heal received mod pct, base: pct + focusFcDamageAmtIncoming, //@Fc, SPA: 297, SpellEffect::FcDamageAmtIncoming, On Target, damage taken flat amt, base: amt + focusFcSpellDamageAmtIncomingPC, //@Fc, SPA: 484, SpellEffect::Fc_Spell_Damage_Amt_IncomingPC, On Target, damage taken flat amt, base: amt + focusFcCastSpellOnLand, //@Fc, SPA: 481, SpellEffect::Fc_Cast_Spell_On_Land, On Target, cast spell if hit by spell, base: chance pct, limit: spellid + focusFcHealAmtIncoming, //@Fc, SPA: 394, SpellEffect::FcHealAmtIncoming, On Target, heal received mod flat amt, base: amt + focusFcBaseEffects, //@Fc, SPA: 413, SpellEffect::FcBaseEffects, On Caster, base spell effectiveness mod pct, base: pct + focusIncreaseNumHits, //@Fc, SPA: 421, SpellEffect::FcIncreaseNumHits, On Caster, numhits mod flat amt, base: amt + focusFcLimitUse, //@Fc, SPA: 420, SpellEffect::FcLimitUse, On Caster, numhits mod pct, base: pct + focusFcMute, //@Fc, SPA: 357, SpellEffect::FcMute, On Caster, prevents spell casting, base: chance pct + focusFcTimerRefresh, //@Fc, SPA: 389, SpellEffect::FcTimerRefresh, On Caster, reset spell recast timer, base: 1 + focusFcTimerLockout, //@Fc, SPA: 390, SpellEffect::FcTimerLockout, On Caster, set a spell to be on recast timer, base: recast duration milliseconds + focusFcStunTimeMod, //@Fc, SPA: 133, SpellEffect::FcStunTimeMod, On Caster, stun time mod pct, base: chance pct + focusFcResistIncoming, //@Fc, SPA: 510, SpellEffect::Fc_Resist_Incoming, On Target, resist modifier, base: amt + focusFcAmplifyMod, //@Fc, SPA: 507, SpellEffect::Fc_Amplify_Mod, On Caster, damage-heal-dot mod pct, base: pct + focusFcAmplifyAmt, //@Fc, SPA: 508, SpellEffect::Fc_Amplify_Amt, On Caster, damage-heal-dot mod flat amt, base: amt + focusFcCastTimeMod2, //@Fc, SPA: 500, SpellEffect::Fc_CastTimeMod2, On Caster, cast time mod pct, base: pct + focusFcCastTimeAmt, //@Fc, SPA: 501, SpellEffect::Fc_CastTimeAmt, On Caster, cast time mod flat amt, base: milliseconds + focusFcHealPctCritIncoming, //@Fc, SPA: 395, SpellEffect::FcHealPctCritIncoming, On Target, spell healing mod pct, base: pct + focusFcHealAmt, //@Fc, SPA: 392, SpellEffect::FcHealAmt, On Caster, spell healing mod flat amt, base: amt + focusFcHealAmtCrit, //@Fc, SPA: 396, SpellEffect::FcHealAmtCrit, On Caster, spell healing mod flat amt, base: amt } focusType; //Any new FocusType needs to be added to the Mob::IsFocus function #define HIGHEST_FOCUS focusFcHealAmtCrit //Should always be last focusType in enum @@ -272,6 +226,46 @@ struct Buffs_Struct { bool persistant_buff; bool client; //True if the caster is a client bool UpdateClient; + + // cereal + template + void serialize(Archive &ar) + { + + std::string caster_name_str(caster_name); + if (Archive::is_saving::value) { + caster_name_str = std::string(caster_name); + } + + ar( + CEREAL_NVP(spellid), + CEREAL_NVP(casterlevel), + CEREAL_NVP(casterid), + CEREAL_NVP(caster_name_str), + CEREAL_NVP(ticsremaining), + CEREAL_NVP(counters), + CEREAL_NVP(hit_number), + CEREAL_NVP(melee_rune), + CEREAL_NVP(magic_rune), + CEREAL_NVP(dot_rune), + CEREAL_NVP(caston_x), + CEREAL_NVP(caston_y), + CEREAL_NVP(caston_z), + CEREAL_NVP(ExtraDIChance), + CEREAL_NVP(RootBreakChance), + CEREAL_NVP(instrument_mod), + CEREAL_NVP(virus_spread_time), + CEREAL_NVP(persistant_buff), + CEREAL_NVP(client), + CEREAL_NVP(UpdateClient) + ); + + // Copy back into caster_name after deserialization + if (Archive::is_loading::value) { + strncpy(caster_name, caster_name_str.c_str(), sizeof(caster_name)); + caster_name[sizeof(caster_name) - 1] = '\0'; // Ensure null termination + } + } }; struct StatBonuses { @@ -575,7 +569,7 @@ struct StatBonuses { uint8 TradeSkillMastery; // Allow number of tradeskills to exceed 200 skill. int16 NoBreakAESneak; // Percent value int16 FeignedCastOnChance; // Percent Value - bool PetCommands[PET_MAXCOMMANDS]; // SPA 267 + bool PetCommands[PetCommand::Max]; // SPA 267 int FeignedMinionChance; // SPA 281 base1 = chance, just like normal FD int GrantForage; // affects max skill of forage as well as granting non-forage classes forage int aura_slots; @@ -739,16 +733,6 @@ enum { GridRandomPath }; -typedef enum { - petFamiliar, //only listens to /pet get lost - petAnimation, //does not listen to any commands - petOther, - petCharmed, - petNPCFollow, - petTargetLock, //remain active as long something is on the hatelist. Don't listen to any commands - petNone = 0xFF // not a pet -} PetType; - typedef enum { SingleTarget, // causes effect to spell_target AETarget, // causes effect in aerange of target + target @@ -862,13 +846,6 @@ struct DamageHitInfo { EQ::skills::SkillType skill; }; -struct ExpeditionInvite -{ - uint32_t expedition_id; - std::string inviter_name; - std::string swap_remove_name; -}; - struct DataBucketCache { uint64_t bucket_id; @@ -877,6 +854,66 @@ struct DataBucketCache uint32_t bucket_expires; }; +// Defines based on the RoF2 Client +#define PET_HEALTHREPORT 0 // 0x00 - /pet health or Pet Window +#define PET_LEADER 1 // 0x01 - /pet leader or Pet Window +#define PET_ATTACK 2 // 0x02 - /pet attack or Pet Window +#define PET_QATTACK 3 // 0x03 - /pet qattack or Pet Window +#define PET_FOLLOWME 4 // 0x04 - /pet follow or Pet Window +#define PET_GUARDHERE 5 // 0x05 - /pet guard or Pet Window +#define PET_SIT 6 // 0x06 - /pet sit or Pet Window +#define PET_SITDOWN 7 // 0x07 - /pet sit on +#define PET_STANDUP 8 // 0x08 - /pet sit off +#define PET_STOP 9 // 0x09 - /pet stop or Pet Window - Not implemented +#define PET_STOP_ON 10 // 0x0a - /pet stop on - Not implemented +#define PET_STOP_OFF 11 // 0x0b - /pet stop off - Not implemented +#define PET_TAUNT 12 // 0x0c - /pet taunt or Pet Window +#define PET_TAUNT_ON 13 // 0x0d - /pet taunt on +#define PET_TAUNT_OFF 14 // 0x0e - /pet taunt off +#define PET_HOLD 15 // 0x0f - /pet hold or Pet Window, won't add to hate list unless attacking +#define PET_HOLD_ON 16 // 0x10 - /pet hold on +#define PET_HOLD_OFF 17 // 0x11 - /pet hold off +#define PET_GHOLD 18 // 0x12 - /pet ghold, will never add to hate list unless told to +#define PET_GHOLD_ON 19 // 0x13 - /pet ghold on +#define PET_GHOLD_OFF 20 // 0x14 - /pet ghold off +#define PET_SPELLHOLD 21 // 0x15 - /pet no cast or /pet spellhold or Pet Window +#define PET_SPELLHOLD_ON 22 // 0x16 - /pet spellhold on +#define PET_SPELLHOLD_OFF 23 // 0x17 - /pet spellhold off +#define PET_FOCUS 24 // 0x18 - /pet focus or Pet Window +#define PET_FOCUS_ON 25 // 0x19 - /pet focus on +#define PET_FOCUS_OFF 26 // 0x1a - /pet focus off +#define PET_FEIGN 27 // 0x1b - /pet feign +#define PET_BACKOFF 28 // 0x1c - /pet back off +#define PET_GETLOST 29 // 0x1d - /pet get lost +#define PET_GUARDME 30 // 0x1e - Same as /pet follow, but different message in older clients - define not from client /pet target in modern clients but doesn't send packet +#define PET_REGROUP 31 // 0x1f - /pet regroup, acts like classic hold. Stops attack and moves back to guard/you but doesn't clear hate list +#define PET_REGROUP_ON 32 // 0x20 - /pet regroup on, turns on regroup +#define PET_REGROUP_OFF 33 // 0x21 - /pet regroup off, turns off regroup +#define PET_MAXCOMMANDS PET_REGROUP_OFF + 1 + +// can change the state of these buttons with a packet +#define PET_BUTTON_SIT 0 +#define PET_BUTTON_STOP 1 +#define PET_BUTTON_REGROUP 2 +#define PET_BUTTON_FOLLOW 3 +#define PET_BUTTON_GUARD 4 +#define PET_BUTTON_TAUNT 5 +#define PET_BUTTON_HOLD 6 +#define PET_BUTTON_GHOLD 7 +#define PET_BUTTON_FOCUS 8 +#define PET_BUTTON_SPELLHOLD 9 + +enum eStandingPetOrder { SPO_Follow, SPO_Sit, SPO_Guard, SPO_FeignDeath }; + +typedef enum { + petFamiliar, //only listens to /pet get lost + petAnimation, //does not listen to any commands + petOther, + petCharmed, + petNPCFollow, + petTargetLock, //remain active as long something is on the hatelist. Don't listen to any commands + petNone = 0xFF // not a pet +} PetTypeOld; #endif diff --git a/zone/corpse.cpp b/zone/corpse.cpp index e54dd2e63..9bd13d63b 100644 --- a/zone/corpse.cpp +++ b/zone/corpse.cpp @@ -16,8 +16,8 @@ #include "../common/say_link.h" #include "corpse.h" +#include "dynamic_zone.h" #include "entity.h" -#include "expedition.h" #include "groups.h" #include "mob.h" #include "raids.h" @@ -31,12 +31,16 @@ #include "../common/repositories/character_corpses_repository.h" #include "../common/repositories/character_corpse_items_repository.h" #include +#include "queryserv.h" +#include "../common/json/json.hpp" +using json = nlohmann::json; extern EntityList entity_list; -extern Zone *zone; +extern Zone *zone; extern WorldServer worldserver; extern npcDecayTimes_Struct npcCorpseDecayTimes[100]; +extern QueryServ *QServ; void Corpse::SendEndLootErrorPacket(Client *client) { @@ -288,7 +292,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); @@ -366,8 +370,8 @@ Corpse::Corpse(Client *c, int32 rez_exp, KilledByTypes in_killed_by) : Mob( if (iter != removed_list.end()) { std::stringstream ss(""); - ss << "DELETE FROM `inventory` WHERE `charid` = " << c->CharacterID(); - ss << " AND `slotid` IN (" << (*iter); + ss << "DELETE FROM `inventory` WHERE `character_id` = " << c->CharacterID(); + ss << " AND `slot_id` IN (" << (*iter); ++iter; while (iter != removed_list.end()) { @@ -581,7 +585,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(); @@ -672,6 +676,21 @@ bool Corpse::Save() ce.drakkin_tattoo = drakkin_tattoo; ce.drakkin_details = drakkin_details; + { + json j; + for (const auto& kv : m_EntityVariables) { + j[kv.first] = kv.second; + } + + if (!j.empty()) { + ce.entity_variables = j.dump(); + } else { + ce.entity_variables = "{}"; + } + + LogCorpses("Corpse entity_variables: %s", ce.entity_variables.c_str()); + } + for (auto &item: m_item_list) { CharacterCorpseItemEntry e; @@ -845,7 +864,7 @@ LootItem *Corpse::GetItem(uint16 lootslot, LootItem **bag_item_data) // convert above code to for loop for (const auto &item: m_item_list) { - if (item->equip_slot >= bagstart && item->equip_slot < bagstart + 10) { + if (item->equip_slot >= bagstart && item->equip_slot < bagstart + EQ::invbag::SLOT_COUNT) { bag_item_data[item->equip_slot - bagstart] = item; } } @@ -1472,7 +1491,7 @@ void Corpse::LootCorpseItem(Client *c, const EQApplicationPacket *app) const EQ::ItemData *item = nullptr; EQ::ItemInstance *inst = nullptr; - LootItem *item_data = nullptr, *bag_item_data[10] = {}; + LootItem *item_data = nullptr, *bag_item_data[EQ::invbag::SLOT_COUNT] = {}; memset(bag_item_data, 0, sizeof(bag_item_data)); if (GetPlayerKillItem() > 1) { @@ -1567,13 +1586,34 @@ void Corpse::LootCorpseItem(Client *c, const EQApplicationPacket *app) } } - if (player_event_logs.IsEventEnabled(PlayerEvent::LOOT_ITEM) && !IsPlayerCorpse()) { + if (parse->ZoneHasQuestSub(EVENT_LOOT_ZONE)) { + const auto &export_string = fmt::format( + "{} {} {} {}", + inst->GetItem()->ID, + inst->GetCharges(), + EntityList::RemoveNumbers(corpse_name), + GetID() + ); + + std::vector args = {inst, this, c}; + if (parse->EventZone(EVENT_LOOT_ZONE, zone, export_string, 0, &args) != 0) { + prevent_loot = true; + } + } + + if (inst && PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::LOOT_ITEM) && !IsPlayerCorpse()) { auto e = PlayerEvent::LootItemEvent{ - .item_id = inst->GetItem()->ID, - .item_name = inst->GetItem()->Name, - .charges = inst->GetCharges(), - .npc_id = GetNPCTypeID(), - .corpse_name = EntityList::RemoveNumbers(corpse_name) + .item_id = inst->GetItem()->ID, + .item_name = inst->GetItem()->Name, + .charges = inst->GetCharges(), + .augment_1_id = inst->GetAugmentItemID(0), + .augment_2_id = inst->GetAugmentItemID(1), + .augment_3_id = inst->GetAugmentItemID(2), + .augment_4_id = inst->GetAugmentItemID(3), + .augment_5_id = inst->GetAugmentItemID(4), + .augment_6_id = inst->GetAugmentItemID(5), + .npc_id = GetNPCTypeID(), + .corpse_name = EntityList::RemoveNumbers(corpse_name) }; RecordPlayerEventLogWithClient(c, PlayerEvent::LOOT_ITEM, e); @@ -1615,21 +1655,8 @@ void Corpse::LootCorpseItem(Client *c, const EQApplicationPacket *app) // safe to ACK now c->QueuePacket(app); - if (!IsPlayerCorpse()) { - if (RuleB(Character, EnableDiscoveredItems) && c && !c->IsDiscovered(inst->GetItem()->ID)) { - if (!c->GetGM()) { - c->DiscoverItem(inst->GetItem()->ID); - } else { - const std::string& item_link = database.CreateItemLink(inst->GetItem()->ID); - c->Message( - Chat::White, - fmt::format( - "Your GM flag prevents {} from being added to discovered items.", - item_link - ).c_str() - ); - } - } + if (!IsPlayerCorpse() && c) { + c->CheckItemDiscoverability(inst->GetID()); } if (zone->adv_data) { @@ -1871,9 +1898,10 @@ bool Corpse::HasItem(uint32 item_id) return false; } -uint16 Corpse::CountItem(uint32 item_id) +uint32 Corpse::CountItem(uint32 item_id) { - uint16 item_count = 0; + uint32 item_count = 0; + if (!database.GetItem(item_id)) { return item_count; } @@ -1893,6 +1921,7 @@ uint16 Corpse::CountItem(uint32 item_id) item_count += i->charges > 0 ? i->charges : 1; } } + return item_count; } @@ -2431,9 +2460,36 @@ Corpse *Corpse::LoadCharacterCorpse( c->m_become_npc = false; c->m_consented_guild_id = cc.guild_consent_id; + try { + if (Strings::IsValidJson(cc.entity_variables)) { + json j = json::parse(cc.entity_variables); + for (auto& el : j.items()) { + c->SetEntityVariable(el.key(), el.value().get()); + } + } + } catch (const std::exception& ex) { + LogError("Failed to parse entity_variables JSON for corpse ID %u: %s", cc.id, ex.what()); + } + c->IsRezzed(cc.is_rezzed); c->UpdateEquipmentLight(); return c; } + +void Corpse::SyncEntityVariablesToCorpseDB() +{ + if (!m_is_player_corpse || m_corpse_db_id == 0) { + return; + } + + json j; + for (const auto& [key, value] : m_EntityVariables) { + j[key] = value; + } + + std::string serialized = j.dump(); + + CharacterCorpsesRepository::UpdateEntityVariables(database, m_corpse_db_id, serialized); +} diff --git a/zone/corpse.h b/zone/corpse.h index 28fc8c91a..65c461950 100644 --- a/zone/corpse.h +++ b/zone/corpse.h @@ -196,10 +196,11 @@ public: /* Corpse: Loot */ void QueryLoot(Client *to); bool HasItem(uint32 item_id); - uint16 CountItem(uint32 item_id); + uint32 CountItem(uint32 item_id); uint32 GetItemIDBySlot(uint16 loot_slot); uint16 GetFirstLootSlotByItemID(uint32 item_id); std::vector GetLootList(); + inline const LootItems &GetLootItems() { return m_item_list; } void LootCorpseItem(Client *c, const EQApplicationPacket *app); void EndLoot(Client *c, const EQApplicationPacket *app); void MakeLootRequestPackets(Client *c, const EQApplicationPacket *app); @@ -243,6 +244,8 @@ public: const glm::vec4 &position ); + void SyncEntityVariablesToCorpseDB(); + protected: void MoveItemToCorpse(Client *client, EQ::ItemInstance *inst, int16 equipSlot, std::list &removedList); diff --git a/zone/data_bucket.cpp b/zone/data_bucket.cpp deleted file mode 100644 index d022183bc..000000000 --- a/zone/data_bucket.cpp +++ /dev/null @@ -1,669 +0,0 @@ -#include "data_bucket.h" -#include "entity.h" -#include "zonedb.h" -#include "mob.h" -#include "worldserver.h" -#include -#include - -extern WorldServer worldserver; - -std::vector g_data_bucket_cache = {}; - -void DataBucket::SetData(const std::string &bucket_key, const std::string &bucket_value, std::string expires_time) -{ - auto k = DataBucketKey{ - .key = bucket_key, - .value = bucket_value, - .expires = expires_time, - .character_id = 0, - .npc_id = 0, - .bot_id = 0 - }; - - DataBucket::SetData(k); -} - -void DataBucket::SetData(const DataBucketKey &k) -{ - auto b = DataBucketsRepository::NewEntity(); - auto r = GetData(k, true); - // if we have an entry, use it - if (r.id > 0) { - b = r; - } - - // add scoping to bucket - if (k.character_id > 0) { - b.character_id = k.character_id; - } - else if (k.npc_id > 0) { - b.npc_id = k.npc_id; - } - else if (k.bot_id > 0) { - b.bot_id = k.bot_id; - } - - const uint64 bucket_id = b.id; - int64 expires_time_unix = 0; - - if (!k.expires.empty()) { - expires_time_unix = static_cast(std::time(nullptr)) + Strings::ToInt(k.expires); - if (isalpha(k.expires[0]) || isalpha(k.expires[k.expires.length() - 1])) { - expires_time_unix = static_cast(std::time(nullptr)) + Strings::TimeToSeconds(k.expires); - } - } - - b.expires = expires_time_unix; - b.value = k.value; - - if (bucket_id) { - // loop cache and update cache value and timestamp - for (auto &ce: g_data_bucket_cache) { - if (CheckBucketMatch(ce.e, k)) { - ce.e = b; - ce.updated_time = GetCurrentTimeUNIX(); - ce.update_action = DataBucketCacheUpdateAction::Upsert; - SendDataBucketCacheUpdate(ce); - break; - } - } - - DataBucketsRepository::UpdateOne(database, b); - } - else { - b.key_ = k.key; - b = DataBucketsRepository::InsertOne(database, b); - if (!ExistsInCache(b)) { - // add data bucket and timestamp to cache - auto ce = DataBucketCacheEntry{ - .e = b, - .updated_time = DataBucket::GetCurrentTimeUNIX(), - .update_action = DataBucketCacheUpdateAction::Upsert - }; - - g_data_bucket_cache.emplace_back(ce); - - SendDataBucketCacheUpdate(ce); - - DeleteFromMissesCache(b); - } - } -} - -std::string DataBucket::GetData(const std::string &bucket_key) -{ - DataBucketKey k = {}; - k.key = bucket_key; - return GetData(k).value; -} - -// GetData fetches bucket data from the database or cache if it exists -// if the bucket doesn't exist, it will be added to the cache as a miss -// if ignore_misses_cache is true, the bucket will not be added to the cache as a miss -// the only place we should be ignoring the misses cache is on the initial read during SetData -DataBucketsRepository::DataBuckets DataBucket::GetData(const DataBucketKey &k, bool ignore_misses_cache) -{ - LogDataBuckets( - "Getting bucket key [{}] bot_id [{}] character_id [{}] npc_id [{}]", - k.key, - k.bot_id, - k.character_id, - k.npc_id - ); - - for (const auto &ce: g_data_bucket_cache) { - if (CheckBucketMatch(ce.e, k)) { - if (ce.e.expires > 0 && ce.e.expires < std::time(nullptr)) { - LogDataBuckets("Attempted to read expired key [{}] removing from cache", ce.e.key_); - DeleteData(k); - return DataBucketsRepository::NewEntity(); - } - - // this is a bucket miss, return empty entity - // we still cache bucket misses, so we don't have to hit the database - if (ce.e.id == 0) { - return DataBucketsRepository::NewEntity(); - } - - LogDataBuckets("Returning key [{}] value [{}] from cache", ce.e.key_, ce.e.value); - return ce.e; - } - } - - auto r = DataBucketsRepository::GetWhere( - database, - fmt::format( - "{} `key` = '{}' LIMIT 1", - DataBucket::GetScopedDbFilters(k), - k.key - ) - ); - - if (r.empty()) { - - // if we're ignoring the misses cache, don't add to the cache - // the only place this is ignored is during the initial read of SetData - if (!ignore_misses_cache) { - size_t size_before = g_data_bucket_cache.size(); - - // cache bucket misses, so we don't have to hit the database - // when scripts try to read a bucket that doesn't exist - g_data_bucket_cache.emplace_back( - DataBucketCacheEntry{ - .e = DataBucketsRepository::DataBuckets{ - .id = 0, - .key_ = k.key, - .value = "", - .expires = 0, - .character_id = k.character_id, - .npc_id = k.npc_id, - .bot_id = k.bot_id - }, - .updated_time = DataBucket::GetCurrentTimeUNIX() - } - ); - - LogDataBuckets( - "Key [{}] not found in database, adding to cache as a miss character_id [{}] npc_id [{}] bot_id [{}] cache size before [{}] after [{}]", - k.key, - k.character_id, - k.npc_id, - k.bot_id, - size_before, - g_data_bucket_cache.size() - ); - } - - return {}; - } - - // if the entry has expired, delete it - if (r[0].expires > 0 && r[0].expires < (long long) std::time(nullptr)) { - DeleteData(k); - return {}; - } - - bool has_cache = false; - for (auto &ce: g_data_bucket_cache) { - if (ce.e.id == r[0].id) { - has_cache = true; - break; - } - } - - if (!has_cache) { - // add data bucket and timestamp to cache - g_data_bucket_cache.emplace_back( - DataBucketCacheEntry{ - .e = r[0], - .updated_time = DataBucket::GetCurrentTimeUNIX() - } - ); - } - - return r[0]; -} - -std::string DataBucket::GetDataExpires(const std::string &bucket_key) -{ - DataBucketKey k = {}; - k.key = bucket_key; - - return GetDataExpires(k); -} - -std::string DataBucket::GetDataRemaining(const std::string &bucket_key) -{ - DataBucketKey k = {}; - k.key = bucket_key; - return GetDataRemaining(k); -} - -bool DataBucket::DeleteData(const std::string &bucket_key) -{ - DataBucketKey k = {}; - k.key = bucket_key; - return DeleteData(k); -} - -// GetDataBuckets bulk loads all data buckets for a mob -bool DataBucket::GetDataBuckets(Mob *mob) -{ - DataBucketLoadType::Type t; - const uint32 id = mob->GetMobTypeIdentifier(); - - if (!id) { - return false; - } - - if (mob->IsBot()) { - t = DataBucketLoadType::Bot; - } - else if (mob->IsClient()) { - t = DataBucketLoadType::Client; - } - else if (mob->IsNPC()) { - t = DataBucketLoadType::NPC; - } - - BulkLoadEntities(t, {id}); - - return true; -} - -bool DataBucket::DeleteData(const DataBucketKey &k) -{ - size_t size_before = g_data_bucket_cache.size(); - - // delete from cache where contents match - g_data_bucket_cache.erase( - std::remove_if( - g_data_bucket_cache.begin(), - g_data_bucket_cache.end(), - [&](DataBucketCacheEntry &ce) { - bool match = CheckBucketMatch(ce.e, k); - if (match) { - ce.update_action = DataBucketCacheUpdateAction::Delete; - SendDataBucketCacheUpdate(ce); - } - - return match; - } - ), - g_data_bucket_cache.end() - ); - - LogDataBuckets( - "Deleting bucket key [{}] bot_id [{}] character_id [{}] npc_id [{}] cache size before [{}] after [{}]", - k.key, - k.bot_id, - k.character_id, - k.npc_id, - size_before, - g_data_bucket_cache.size() - ); - - return DataBucketsRepository::DeleteWhere( - database, - fmt::format( - "{} `key` = '{}'", - DataBucket::GetScopedDbFilters(k), - k.key - ) - ); -} - -std::string DataBucket::GetDataExpires(const DataBucketKey &k) -{ - LogDataBuckets( - "Getting bucket expiration key [{}] bot_id [{}] character_id [{}] npc_id [{}]", - k.key, - k.bot_id, - k.character_id, - k.npc_id - ); - - auto r = GetData(k); - if (r.id == 0) { - return {}; - } - - return std::to_string(r.expires); -} - -std::string DataBucket::GetDataRemaining(const DataBucketKey &k) -{ - LogDataBuckets( - "Getting bucket remaining key [{}] bot_id [{}] character_id [{}] npc_id [{}]", - k.key, - k.bot_id, - k.character_id, - k.npc_id - ); - - auto r = GetData(k); - if (r.id == 0) { - return "0"; - } - - return fmt::format("{}", r.expires - (long long) std::time(nullptr)); -} - -std::string DataBucket::GetScopedDbFilters(const DataBucketKey &k) -{ - std::vector query = {}; - if (k.character_id > 0) { - query.emplace_back(fmt::format("character_id = {}", k.character_id)); - } - else { - query.emplace_back("character_id = 0"); - } - - if (k.npc_id > 0) { - query.emplace_back(fmt::format("npc_id = {}", k.npc_id)); - } - else { - query.emplace_back("npc_id = 0"); - } - - if (k.bot_id > 0) { - query.emplace_back(fmt::format("bot_id = {}", k.bot_id)); - } - else { - query.emplace_back("bot_id = 0"); - } - - return fmt::format( - "{} {}", - Strings::Join(query, " AND "), - !query.empty() ? "AND" : "" - ); -} - -bool DataBucket::CheckBucketMatch(const DataBucketsRepository::DataBuckets &dbe, const DataBucketKey &k) -{ - return ( - dbe.key_ == k.key && - dbe.bot_id == k.bot_id && - dbe.character_id == k.character_id && - dbe.npc_id == k.npc_id - ); -} - -void DataBucket::BulkLoadEntities(DataBucketLoadType::Type t, std::vector ids) -{ - if (ids.empty()) { - return; - } - - if (ids.size() == 1) { - bool has_cache = false; - for (const auto &ce: g_data_bucket_cache) { - if (t == DataBucketLoadType::Bot) { - has_cache = ce.e.bot_id == ids[0]; - } - else if (t == DataBucketLoadType::Client) { - has_cache = ce.e.character_id == ids[0]; - } - else if (t == DataBucketLoadType::NPC) { - has_cache = ce.e.npc_id == ids[0]; - } - } - - if (has_cache) { - LogDataBucketsDetail("LoadType [{}] ID [{}] has cache", DataBucketLoadType::Name[t], ids[0]); - return; - } - } - - std::string column; - - switch (t) { - case DataBucketLoadType::Bot: - column = "bot_id"; - break; - case DataBucketLoadType::Client: - column = "character_id"; - break; - case DataBucketLoadType::NPC: - column = "npc_id"; - break; - default: - LogError("Incorrect LoadType [{}]", static_cast(t)); - break; - } - - const auto &l = DataBucketsRepository::GetWhere( - database, - fmt::format( - "{} IN ({}) AND (`expires` > {} OR `expires` = 0)", - column, - Strings::Join(ids, ", "), - (long long) std::time(nullptr) - ) - ); - - if (l.empty()) { - return; - } - - LogDataBucketsDetail("cache size before [{}] l size [{}]", g_data_bucket_cache.size(), l.size()); - - uint32 added_count = 0; - - for (const auto &e: l) { - if (!ExistsInCache(e)) { - added_count++; - } - } - - for (const auto &e: l) { - if (!ExistsInCache(e)) { - LogDataBucketsDetail("bucket id [{}] bucket key [{}] bucket value [{}]", e.id, e.key_, e.value); - - g_data_bucket_cache.emplace_back( - DataBucketCacheEntry{ - .e = e, - .updated_time = GetCurrentTimeUNIX() - } - ); - } - } - - LogDataBucketsDetail("cache size after [{}]", g_data_bucket_cache.size()); - - LogDataBuckets( - "Bulk Loaded ids [{}] column [{}] new cache size is [{}]", - ids.size(), - column, - g_data_bucket_cache.size() - ); -} - -void DataBucket::DeleteCachedBuckets(DataBucketLoadType::Type t, uint32 id) -{ - size_t size_before = g_data_bucket_cache.size(); - - g_data_bucket_cache.erase( - std::remove_if( - g_data_bucket_cache.begin(), - g_data_bucket_cache.end(), - [&](DataBucketCacheEntry &ce) { - return ( - (t == DataBucketLoadType::Bot && ce.e.bot_id == id) || - (t == DataBucketLoadType::Client && ce.e.character_id == id) || - (t == DataBucketLoadType::NPC && ce.e.npc_id == id) - ); - } - ), - g_data_bucket_cache.end() - ); - - LogDataBuckets( - "LoadType [{}] id [{}] cache size before [{}] after [{}]", - DataBucketLoadType::Name[t], - id, - size_before, - g_data_bucket_cache.size() - ); -} - -int64_t DataBucket::GetCurrentTimeUNIX() -{ - return std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch() - ).count(); -} - -bool DataBucket::ExistsInCache(const DataBucketsRepository::DataBuckets &e) -{ - for (const auto &ce: g_data_bucket_cache) { - if (ce.e.id == e.id) { - return true; - } - } - - return false; -} - -bool DataBucket::SendDataBucketCacheUpdate(const DataBucketCacheEntry &e) -{ - if (!e.e.id) { - return false; - } - - EQ::Net::DynamicPacket p; - p.PutSerialize(0, e); - - auto pack_size = sizeof(ServerDataBucketCacheUpdate_Struct) + p.Length(); - auto pack = new ServerPacket(ServerOP_DataBucketCacheUpdate, static_cast(pack_size)); - auto buf = reinterpret_cast(pack->pBuffer); - - buf->cereal_size = static_cast(p.Length()); - - memcpy(buf->cereal_data, p.Data(), p.Length()); - - worldserver.SendPacket(pack); - - return true; -} - -void DataBucket::HandleWorldMessage(ServerPacket *p) -{ - DataBucketCacheEntry n; - auto s = (ServerDataBucketCacheUpdate_Struct *) p->pBuffer; - EQ::Util::MemoryStreamReader ss(s->cereal_data, s->cereal_size); - cereal::BinaryInputArchive archive(ss); - archive(n); - - LogDataBucketsDetail( - "Received cache packet for id [{}] key [{}] value [{}] action [{}]", - n.e.id, - n.e.key_, - n.e.value, - static_cast(n.update_action) - ); - - // delete - if (n.update_action == DataBucketCacheUpdateAction::Delete) { - DeleteFromMissesCache(n.e); - - g_data_bucket_cache.erase( - std::remove_if( - g_data_bucket_cache.begin(), - g_data_bucket_cache.end(), - [&](DataBucketCacheEntry &ce) { - bool match = n.e.id > 0 && ce.e.id == n.e.id; - if (match) { - LogDataBuckets( - "[delete] cache key [{}] id [{}] cache_size before [{}] after [{}]", - ce.e.key_, - ce.e.id, - g_data_bucket_cache.size(), - g_data_bucket_cache.size() - 1 - ); - } - return match; - } - ), - g_data_bucket_cache.end() - ); - - return; - } - - // update - bool has_key = false; - for (auto &ce: g_data_bucket_cache) { - // update cache - if (ce.e.id == n.e.id) { - // reject old updates - int64 time_delta = ce.updated_time - n.updated_time; - if (ce.updated_time >= n.updated_time) { - LogDataBuckets( - "Attempted to update older cache key [{}] rejecting old time [{}] new time [{}] delta [{}] cache_size [{}]", - ce.e.key_, - ce.updated_time, - n.updated_time, - time_delta, - g_data_bucket_cache.size() - ); - return; - } - - DeleteFromMissesCache(n.e); - - LogDataBuckets( - "[update] cache id [{}] key [{}] value [{}] old time [{}] new time [{}] delta [{}] cache_size [{}]", - ce.e.id, - ce.e.key_, - n.e.value, - ce.updated_time, - n.updated_time, - time_delta, - g_data_bucket_cache.size() - ); - ce.e = n.e; - ce.updated_time = n.updated_time; - has_key = true; - break; - } - } - - // create - if (!has_key) { - DeleteFromMissesCache(n.e); - - size_t size_before = g_data_bucket_cache.size(); - - g_data_bucket_cache.emplace_back( - DataBucketCacheEntry{ - .e = n.e, - .updated_time = GetCurrentTimeUNIX() - } - ); - - LogDataBuckets( - "[create] Adding new cache id [{}] key [{}] value [{}] cache size before [{}] after [{}]", - n.e.id, - n.e.key_, - n.e.value, - size_before, - g_data_bucket_cache.size() - ); - } -} - -void DataBucket::DeleteFromMissesCache(DataBucketsRepository::DataBuckets e) -{ - // delete from cache where there might have been a written bucket miss to the cache - // this is to prevent the cache from growing too large - size_t size_before = g_data_bucket_cache.size(); - - g_data_bucket_cache.erase( - std::remove_if( - g_data_bucket_cache.begin(), - g_data_bucket_cache.end(), - [&](DataBucketCacheEntry &ce) { - return ce.e.id == 0 && ce.e.key_ == e.key_ && - ce.e.character_id == e.character_id && - ce.e.npc_id == e.npc_id && - ce.e.bot_id == e.bot_id; - } - ), - g_data_bucket_cache.end() - ); - LogDataBucketsDetail( - "Deleted bucket misses from cache where key [{}] size before [{}] after [{}]", - e.key_, - size_before, - g_data_bucket_cache.size() - ); -} - -void DataBucket::ClearCache() -{ - g_data_bucket_cache.clear(); - LogInfo("Cleared data buckets cache"); -} diff --git a/zone/dialogue_window.cpp b/zone/dialogue_window.cpp index a02e90723..326d706d4 100644 --- a/zone/dialogue_window.cpp +++ b/zone/dialogue_window.cpp @@ -1,5 +1,3 @@ -#include - #include "dialogue_window.h" void DialogueWindow::Render(Client *c, std::string markdown) @@ -529,12 +527,19 @@ std::string DialogueWindow::CenterMessage(std::string message) return std::string(); } - auto cleaned_message = message; + std::string cleaned_message; + cleaned_message.reserve(message.size()); - std::regex tags("<[^>]*>"); - - if (std::regex_search(cleaned_message, tags)) { - std::regex_replace(cleaned_message, tags, cleaned_message); + // Strip HTML-like tags + bool in_tag = false; + for (char c : message) { + if (c == '<') { + in_tag = true; + } else if (c == '>' && in_tag) { + in_tag = false; + } else if (!in_tag) { + cleaned_message += c; + } } auto message_len = cleaned_message.length(); diff --git a/zone/doors.cpp b/zone/doors.cpp index 3ec9c86af..858d63a3e 100644 --- a/zone/doors.cpp +++ b/zone/doors.cpp @@ -30,10 +30,14 @@ #include "string_ids.h" #include "worldserver.h" #include "zonedb.h" +#include "../common/evolving_items.h" #include "../common/repositories/criteria/content_filter_criteria.h" #include +#include +#include + #define OPEN_DOOR 0x02 #define CLOSE_DOOR 0x03 #define OPEN_INVDOOR 0x03 @@ -610,6 +614,10 @@ void Doors::HandleClick(Client *sender, uint8 trigger) } } } + + if (GetOpenType() == 40 && sender->GetZoneID() == Zones::CORATHUS) { + sender->SendEvolveXPTransferWindow(); + } } void Doors::Open(Mob *sender, bool alt_mode) @@ -844,11 +852,13 @@ void Doors::CreateDatabaseEntry() const auto& l = DoorsRepository::GetWhere( content_db, fmt::format( - "zone = '{}' AND doorid = {}", + "zone = '{}' AND (version = {} OR version = -1) AND doorid = {}", zone->GetShortName(), + zone->GetInstanceVersion(), GetDoorID() ) ); + if (!l.empty()) { auto e = l[0]; @@ -965,3 +975,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; +} diff --git a/zone/doors.h b/zone/doors.h index 3c1ec3704..f600a280b 100644 --- a/zone/doors.h +++ b/zone/doors.h @@ -76,6 +76,9 @@ 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; } private: bool GetIsDoorBlacklisted(); diff --git a/zone/dynamic_zone.cpp b/zone/dynamic_zone.cpp index 65087f9ac..e6e993bc2 100644 --- a/zone/dynamic_zone.cpp +++ b/zone/dynamic_zone.cpp @@ -20,14 +20,20 @@ #include "dynamic_zone.h" #include "client.h" -#include "expedition.h" +#include "expedition_request.h" #include "string_ids.h" #include "worldserver.h" +#include "../common/repositories/character_expedition_lockouts_repository.h" +#include "../common/repositories/dynamic_zone_lockouts_repository.h" +#include extern WorldServer worldserver; -DynamicZone::DynamicZone( - uint32_t zone_id, uint32_t version, uint32_t duration, DynamicZoneType type) +// various expeditions use these strings when locking +constexpr char LockClose[] = "Your expedition is nearing its close. You cannot bring any additional people into your expedition at this time."; +constexpr char LockBegin[] = "The trial has begun. You cannot bring any additional people into your expedition at this time."; + +DynamicZone::DynamicZone(uint32_t zone_id, uint32_t version, uint32_t duration, DynamicZoneType type) { m_zone_id = zone_id; m_zone_version = version; @@ -45,72 +51,79 @@ bool DynamicZone::SendServerPacket(ServerPacket* packet) return worldserver.SendPacket(packet); } -uint16_t DynamicZone::GetCurrentInstanceID() +uint16_t DynamicZone::GetCurrentInstanceID() const { return zone ? static_cast(zone->GetInstanceID()) : 0; } -uint16_t DynamicZone::GetCurrentZoneID() +uint16_t DynamicZone::GetCurrentZoneID() const { return zone ? static_cast(zone->GetZoneID()) : 0; } -DynamicZone* DynamicZone::CreateNew(DynamicZone& dz_request, const std::vector& members) +DynamicZone* DynamicZone::TryCreate(Client& client, DynamicZone& dzinfo, bool silent) { - if (!zone || dz_request.GetID() != 0) + // only expedition types are currently created in zone + if (!zone || dzinfo.GetID() != 0 || !dzinfo.IsExpedition()) { return nullptr; } + // request parses leader, members list, and lockouts while validating + ExpeditionRequest request(dzinfo, client, silent); + if (!request.Validate()) + { + LogExpeditions("[{}] request by [{}] denied", dzinfo.GetName(), client.GetName()); + return nullptr; + } + + dzinfo.SetLeader({ request.GetLeaderID(), request.GetLeaderName(), DynamicZoneMemberStatus::Online }); + // this creates a new dz instance and saves it to both db and cache - uint32_t dz_id = dz_request.Create(); + uint32_t dz_id = dzinfo.Create(); if (dz_id == 0) { - LogDynamicZones("Failed to create dynamic zone for zone [{}]", dz_request.GetZoneID()); + // live uses this message when trying to enter an instance that isn't ready + // for now we can use it as a client error message if instance creation fails + client.MessageString(Chat::Red, DZ_PREVENT_ENTERING); + LogDynamicZones("Failed to create dynamic zone for zone [{}]", dzinfo.GetZoneID()); return nullptr; } - auto dz = std::make_unique(dz_request); - if (!members.empty()) - { - dz->SaveMembers(members); - } + auto [it, ok] = zone->dynamic_zone_cache.try_emplace(dz_id, std::make_unique(dzinfo)); - LogDynamicZones("Created new dz [{}] for zone [{}]", dz_id, dz_request.GetZoneID()); + DynamicZone* dz = it->second.get(); + dz->SaveMembers(request.GetMembers()); + dz->SaveLockouts(request.GetLockouts()); + + dz->SendLeaderMessage(request.GetLeaderClient(), Chat::System, DZ_AVAILABLE, { dz->GetName() }); + if (dz->GetMemberCount() < request.GetMembers().size()) + { + dz->SendLeaderMessage(request.GetLeaderClient(), Chat::System, fmt::format(DzNotAllAdded, + request.IsRaid() ? "raid" : "group", "expedition", dz->GetMaxPlayers(), request.GetMembers().size())); + } // world must be notified before we request async member updates - auto pack = dz->CreateServerDzCreatePacket(zone->GetZoneID(), zone->GetInstanceID()); + auto pack = dz->CreateServerPacket(zone->GetZoneID(), zone->GetInstanceID()); worldserver.SendPacket(pack.get()); - auto inserted = zone->dynamic_zone_cache.emplace(dz_id, std::move(dz)); + dz->UpdateMembers(); - // expeditions invoke their own updates after installing client update callbacks - if (inserted.first->second->GetType() != DynamicZoneType::Expedition) - { - inserted.first->second->DoAsyncZoneMemberUpdates(); - } + LogDynamicZones("Created new dz [{}] for zone [{}]", dz_id, dz->GetZoneID()); - return inserted.first->second.get(); + return dz; } void DynamicZone::CacheNewDynamicZone(ServerPacket* pack) { - auto buf = reinterpret_cast(pack->pBuffer); + auto buf = reinterpret_cast(pack->pBuffer); // caching new dz created in world or another zone (has member statuses set by world) - auto dz = std::make_unique(); - dz->LoadSerializedDzPacket(buf->cereal_data, buf->cereal_size); + auto [it, ok] = zone->dynamic_zone_cache.try_emplace(buf->dz_id, std::make_unique()); + it->second->Unserialize({ buf->cereal_data, buf->cereal_size }); + it->second->UpdateMembers(); - uint32_t dz_id = dz->GetID(); - auto inserted = zone->dynamic_zone_cache.emplace(dz_id, std::move(dz)); - - // expeditions invoke their own updates after installing client update callbacks - if (inserted.first->second->GetType() != DynamicZoneType::Expedition) - { - inserted.first->second->DoAsyncZoneMemberUpdates(); - } - - LogDynamicZones("Cached new dynamic zone [{}]", dz_id); + LogDynamicZones("Cached new dynamic zone [{}]", buf->dz_id); } void DynamicZone::CacheAllFromDatabase() @@ -122,18 +135,19 @@ void DynamicZone::CacheAllFromDatabase() BenchTimer bench; - auto dynamic_zones = DynamicZonesRepository::AllWithInstanceNotExpired(database); - auto dynamic_zone_members = DynamicZoneMembersRepository::GetAllWithNames(database); + auto dzs = DynamicZonesRepository::AllWithInstanceNotExpired(database); + auto members = DynamicZoneMembersRepository::AllWithNames(database); + auto lockouts = DynamicZoneLockoutsRepository::All(database); zone->dynamic_zone_cache.clear(); - zone->dynamic_zone_cache.reserve(dynamic_zones.size()); + zone->dynamic_zone_cache.reserve(dzs.size()); - for (auto& entry : dynamic_zones) + for (auto& entry : dzs) { uint32_t dz_id = entry.id; auto dz = std::make_unique(std::move(entry)); - for (auto& member : dynamic_zone_members) + for (auto& member : members) { if (member.dynamic_zone_id == dz_id) { @@ -141,32 +155,73 @@ void DynamicZone::CacheAllFromDatabase() } } + for (auto& lockout : lockouts) + { + if (lockout.dynamic_zone_id == dz->GetID()) + { + dz->m_lockouts.emplace_back(dz->GetName(), std::move(lockout)); + } + } + 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()); } -DynamicZone* DynamicZone::FindDynamicZoneByID(uint32_t dz_id) +void DynamicZone::RequestMemberStatuses() { - if (!zone) - { - return nullptr; - } + ServerPacket pack(ServerOP_DzGetBulkMemberStatuses, sizeof(ServerDzCerealData_Struct)); + auto buf = reinterpret_cast(pack.pBuffer); + buf->zone_id = static_cast(zone->GetZoneID()); + buf->inst_id = static_cast(zone->GetInstanceID()); - auto dz = zone->dynamic_zone_cache.find(dz_id); - if (dz != zone->dynamic_zone_cache.end()) - { - return dz->second.get(); - } + worldserver.SendPacket(&pack); +} +template +DynamicZone* FindDynamicZone(T pred) +{ + if (zone) + { + for (const auto& [id_, dz] : zone->dynamic_zone_cache) + { + if (pred(*dz.get())) + { + return dz.get(); + } + } + } return nullptr; } -void DynamicZone::RegisterOnClientAddRemove(std::function on_client_addremove) +DynamicZone* DynamicZone::FindDynamicZoneByID(uint32_t dz_id, DynamicZoneType type) { - m_on_client_addremove = std::move(on_client_addremove); + if (zone) + { + auto it = zone->dynamic_zone_cache.find(dz_id); + if (it != zone->dynamic_zone_cache.end() && (type == DynamicZoneType::None || it->second->GetType() == type)) + { + return it->second.get(); + } + } + return nullptr; +} + +DynamicZone* DynamicZone::FindExpeditionByCharacter(uint32_t char_id) +{ + return FindDynamicZone([&](const DynamicZone& dz) { return dz.IsExpedition() && dz.HasMember(char_id); }); +} + +DynamicZone* DynamicZone::FindExpeditionByZone(uint32_t zone_id, uint32_t instance_id) +{ + return FindDynamicZone([&](const DynamicZone& dz) { return dz.IsExpedition() && dz.IsSameDz(zone_id, instance_id); }); } void DynamicZone::StartAllClientRemovalTimers() @@ -180,9 +235,9 @@ void DynamicZone::StartAllClientRemovalTimers() } } -bool DynamicZone::IsCurrentZoneDzInstance() const +bool DynamicZone::IsCurrentZoneDz() const { - return (zone && zone->GetInstanceID() != 0 && zone->GetInstanceID() == GetInstanceID()); + return zone && zone->GetInstanceID() != 0 && zone->GetInstanceID() == GetInstanceID(); } void DynamicZone::SetSecondsRemaining(uint32_t seconds_remaining) @@ -205,19 +260,410 @@ void DynamicZone::SetUpdatedDuration(uint32_t new_duration) LogDynamicZones("Updated dz [{}] zone [{}]:[{}] seconds remaining: [{}]", m_id, m_zone_id, m_instance_id, GetSecondsRemaining()); - if (zone && IsCurrentZoneDzInstance()) + if (zone && IsCurrentZoneDz()) { zone->SetInstanceTimer(GetSecondsRemaining()); } } +void DynamicZone::SendClientInvite(Client* client, const std::string& inviter, const std::string& swap_name) +{ + if (!client) + { + return; + } + + LogExpeditions("Invite [{}] to [{}] by [{}] swap [{}]", client->GetName(), GetID(), inviter, swap_name); + + client->SetPendingDzInvite({ GetID(), inviter, swap_name }); + client->MessageString(Chat::System, DZ_INVITED, GetLeaderName().c_str(), GetName().c_str()); + + // live (as of March 11 2020 patch) warns for lockouts added during current + // expedition that client would receive upon entering (sent in invite packet) + std::string events; + for (const auto& lockout : m_lockouts) + { + // live doesn't issue a warning for the dz's replay timer + if (!lockout.IsReplay() && !lockout.IsExpired() && lockout.IsUUID(GetUUID()) && + !client->HasDzLockout(GetName(), lockout.Event())) + { + auto time = lockout.GetTimeRemainingStrs(); + events += fmt::format("\n{} - {}D:{}H:{}M", lockout.Event(), time.days, time.hours, time.mins); + } + } + + if (!events.empty()) + { + client->SendColoredText(Chat::System, fmt::format( + "Warning! You will be given replay timers for the following events if you enter {}:{}", GetName(), events)); + } + + auto outapp = CreateInvitePacket(inviter, swap_name).release(); + client->FastQueuePacket(&outapp); +} + +bool DynamicZone::ConfirmLeaderCommand(Client* client) +{ + if (!client) + { + return false; + } + + if (!GetLeader().IsValid()) + { + client->MessageString(Chat::Red, DZ_NO_LEADER_INFO); // unconfirmed message + return false; + } + + if (GetLeaderID() != client->CharacterID()) + { + client->MessageString(Chat::System, DZ_NOT_LEADER, GetLeaderName().c_str()); + return false; + } + + return true; +} + +void DynamicZone::SendLeaderMessage(Client* leader, uint16_t type, const std::string& msg) +{ + Client::SendCrossZoneMessage(leader, GetLeaderName(), type, msg); +} + +void DynamicZone::SendLeaderMessage(Client* leader, uint16_t type, uint32_t str_id, std::initializer_list args) +{ + Client::SendCrossZoneMessageString(leader, GetLeaderName(), type, str_id, args); +} + +bool DynamicZone::ProcessAddConflicts(Client* leader, Client* client, bool swapping) +{ + if (!client) // a null leader client is handled by SendLeaderMessage fallback + { + return true; + } + + bool has_conflict = false; + + if (IsCurrentZoneDz()) + { + SendLeaderMessage(leader, Chat::Red, DZADD_LEAVE_ZONE, { client->GetName() }); + has_conflict = true; + } + + auto dz_id = client->GetExpeditionID(); + if (dz_id) + { + int string_id = dz_id == GetID() ? DZADD_ALREADY_PART : DZADD_ALREADY_OTHER; + SendLeaderMessage(leader, Chat::Red, string_id, { client->GetName() }); + has_conflict = true; + } + + // check any extra event lockouts for this expedition that the client has and expedition doesn't + auto lockouts = client->GetDzLockouts(GetName()); + for (const auto& lockout : lockouts) + { + // client with a replay lockout is allowed only if the replay timer was from this expedition + if (lockout.IsReplay() && lockout.UUID() != m_uuid) + { + has_conflict = true; + + auto time = lockout.GetTimeRemainingStrs(); + SendLeaderMessage(leader, Chat::Red, DZADD_REPLAY_TIMER, { client->GetName(), time.days, time.hours, time.mins }); + } + else if (!lockout.IsReplay() && !HasLockout(lockout.Event())) + { + has_conflict = true; + + auto time = lockout.GetTimeRemainingStrs(); + SendLeaderMessage(leader, Chat::Red, DZADD_EVENT_TIMER, { client->GetName(), lockout.Event(), time.days, time.hours, time.mins }); + } + } + + // member swapping integrity is handled by invite response + if (!swapping) + { + auto member_count = GetDatabaseMemberCount(); + if (member_count == 0) + { + has_conflict = true; + } + else if (member_count >= GetMaxPlayers()) + { + SendLeaderMessage(leader, Chat::Red, DZADD_EXCEED_MAX, { fmt::format_int(GetMaxPlayers()).str() }); + has_conflict = true; + } + } + + auto invite_id = client->GetPendingDzInviteID(); + if (invite_id) + { + int string_id = invite_id == GetID() ? DZADD_PENDING : DZADD_PENDING_OTHER; + SendLeaderMessage(leader, Chat::Red, string_id, { client->GetName() }); + has_conflict = true; + } + + return has_conflict; +} + +void DynamicZone::TryAddClient(Client* client, const std::string& inviter, const std::string& swap_name, Client* leader) +{ + if (!client) + { + return; + } + + LogExpeditions("Adding [{}] to [{}] by [{}] swap [{}]", client->GetName(), GetID(), inviter, swap_name); + + // null leader client handled by ProcessAddConflicts/SendLeaderMessage fallbacks + if (!leader) + { + leader = entity_list.GetClientByName(inviter.c_str()); + } + + bool has_conflicts = ProcessAddConflicts(leader, client, !swap_name.empty()); + if (!has_conflicts) + { + // live uses the original unsanitized input string in invite messages + uint32_t string_id = swap_name.empty() ? DZADD_INVITE : DZSWAP_INVITE; + SendLeaderMessage(leader, Chat::Yellow, string_id, { client->GetName() }); + SendClientInvite(client, inviter, swap_name); + } + else if (swap_name.empty()) // swap command doesn't result in this message + { + SendLeaderMessage(leader, Chat::Red, DZADD_INVITE_FAIL, { client->GetName() }); + } +} + +void DynamicZone::DzAddPlayer(Client* client, const std::string& add_name, const std::string& swap_name) +{ + if (!client || !ConfirmLeaderCommand(client)) + { + return; + } + + bool invite_failed = false; + + if (IsLocked()) + { + client->MessageString(Chat::Red, DZADD_NOT_ALLOWING); + invite_failed = true; + } + else if (add_name.empty()) + { + client->MessageString(Chat::Red, DZADD_NOT_ONLINE, add_name.c_str()); + invite_failed = true; + } + else + { + auto member_data = GetMemberData(add_name); + if (member_data.IsValid()) + { + // live prioritizes offline message before already a member message + if (member_data.status == DynamicZoneMemberStatus::Offline) + { + client->MessageString(Chat::Red, DZADD_NOT_ONLINE, add_name.c_str()); + } + else + { + client->MessageString(Chat::Red, DZADD_ALREADY_PART, add_name.c_str()); + } + invite_failed = true; + } + } + + if (invite_failed) + { + client->MessageString(Chat::Red, DZADD_INVITE_FAIL, FormatName(add_name).c_str()); + return; + } + + if (Client* add_client = entity_list.GetClientByName(add_name.c_str())) + { + // client is online in this zone + TryAddClient(add_client, client->GetName(), swap_name, client); + } + else + { + // forward to world to check if client is online and perform cross-zone invite + SendWorldPlayerInvite(client->GetName(), swap_name, FormatName(add_name)); + } +} + +void DynamicZone::DzAddPlayerContinue(std::string inviter, std::string add_name, std::string swap_name) +{ + // continuing expedition invite from leader in another zone + if (Client* add_client = entity_list.GetClientByName(add_name.c_str())) + { + TryAddClient(add_client, inviter, swap_name); + } +} + +void DynamicZone::DzInviteResponse(Client* client, bool accepted, const std::string& swap_name) +{ + if (!client) + { + return; + } + + LogExpeditions("[{}] accepted invite [{}] swap [{}]", client->GetName(), accepted, swap_name); + + // a null leader client is handled by SendLeaderMessage fallbacks + // note current leader receives invite reply messages (if leader changed) + Client* leader = entity_list.GetClientByCharID(GetLeaderID()); + + if (!accepted) + { + SendLeaderMessage(leader, Chat::Red, DZ_INVITE_DECLINED, { client->GetName() }); + return; + } + + bool is_swap = !swap_name.empty(); + bool has_conflicts = IsLocked(); + + if (IsLocked()) + { + SendLeaderMessage(leader, Chat::Red, DZADD_NOT_ALLOWING); + } + else + { + has_conflicts = ProcessAddConflicts(leader, client, is_swap); + } + + // error if swapping and character was already removed before the accept + if (is_swap) + { + auto swap_member = GetMemberData(swap_name); + if (!swap_member.IsValid() || !HasDatabaseMember(swap_member.id)) + { + has_conflicts = true; + } + } + + if (has_conflicts) + { + SendLeaderMessage(leader, Chat::Red, DZ_INVITE_ERROR, { client->GetName() }); + } + else + { + SendLeaderMessage(leader, Chat::Yellow, DZ_INVITE_ACCEPTED, { client->GetName() }); + + // replay timers are optionally added to new members on join with fresh expire time + if (m_add_replay) + { + auto it = std::ranges::find_if(m_lockouts, [&](const auto& l) { return l.IsReplay(); }); + if (it != m_lockouts.end() && it->IsUUID(GetUUID()) && !client->HasDzLockout(GetName(), DzLockout::ReplayTimer)) + { + DzLockout replay_timer = *it; // copy + replay_timer.Reset(); + client->AddDzLockout(replay_timer, true); + } + } + + DynamicZoneMember add_member(client->CharacterID(), client->GetName(), DynamicZoneMemberStatus::Online); + + bool success = is_swap ? SwapMember(add_member, swap_name) : AddMember(add_member); + if (success) + { + SendLeaderMessage(leader, Chat::Yellow, DZ_ADDED, { client->GetName(), GetName() }); + } + } +} + +void DynamicZone::DzMakeLeader(Client* client, std::string leader_name) +{ + if (!client || !ConfirmLeaderCommand(client)) + { + return; + } + + if (leader_name.empty()) + { + client->MessageString(Chat::Red, DZ_LEADER_OFFLINE, leader_name.c_str()); + return; + } + + // leader can only be changed by world + SendWorldMakeLeaderRequest(client->CharacterID(), FormatName(leader_name)); +} + +void DynamicZone::DzRemovePlayer(Client* client, std::string name) +{ + if (!client || !ConfirmLeaderCommand(client)) + { + return; + } + + // live only seems to enforce min_players for requesting expeditions, no need to check here + // note: on live members removed when inside a dz instance remain "temporary" + // members for kick timer duration and still receive lockouts across zones (unimplemented) + bool removed = RemoveMember(name); + if (!removed) + { + client->MessageString(Chat::Red, DZ_NOT_MEMBER, FormatName(name).c_str()); + } + else + { + client->MessageString(Chat::Yellow, DZ_REMOVED, FormatName(name).c_str(), m_name.c_str()); + } +} + +void DynamicZone::DzQuit(Client* client) +{ + if (client) + { + RemoveMember(client->GetName()); + } +} + +void DynamicZone::DzSwapPlayer(Client* client, std::string rem_name, std::string add_name) +{ + if (!client || !ConfirmLeaderCommand(client)) + { + return; + } + + if (rem_name.empty() || !HasMember(rem_name)) + { + client->MessageString(Chat::Red, DZSWAP_CANNOT_REMOVE, FormatName(rem_name).c_str()); + return; + } + + DzAddPlayer(client, add_name, rem_name); +} + +void DynamicZone::DzPlayerList(Client* client) +{ + if (client) + { + client->MessageString(Chat::Yellow, DZ_LEADER, GetLeaderName().c_str()); + + std::vector names; + for (const auto& member : m_members) + { + names.push_back(member.name); + } + + client->MessageString(Chat::Yellow, DZ_MEMBERS, fmt::format("{}", fmt::join(names, ", ")).c_str()); + } +} + +void DynamicZone::DzKickPlayers(Client* client) +{ + if (!client || !ConfirmLeaderCommand(client)) + { + return; + } + + RemoveAllMembers(); + client->MessageString(Chat::Red, DZ_REMOVED, "Everyone", m_name.c_str()); +} + void DynamicZone::HandleWorldMessage(ServerPacket* pack) { switch (pack->opcode) { case ServerOP_DzCreated: { - auto buf = reinterpret_cast(pack->pBuffer); + auto buf = reinterpret_cast(pack->pBuffer); if (zone && !zone->IsZone(buf->origin_zone_id, buf->origin_instance_id)) { DynamicZone::CacheNewDynamicZone(pack); @@ -232,24 +678,55 @@ void DynamicZone::HandleWorldMessage(ServerPacket* pack) auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id); if (zone && dz) { - dz->SendUpdatesToZoneMembers(true, true); // members silently removed - - // manually handle expeditions to remove any references before the dz is deleted - if (dz->GetType() == DynamicZoneType::Expedition) - { - auto expedition = Expedition::FindCachedExpeditionByDynamicZoneID(dz->GetID()); - if (expedition) - { - LogExpeditionsDetail("Deleting expedition [{}] from zone cache", expedition->GetID()); - zone->expedition_cache.erase(expedition->GetID()); - } - } - LogDynamicZonesDetail("Deleting dynamic zone [{}] from zone cache", buf->dz_id); + dz->SendUpdatesToZoneMembers(true, true); // members silently removed zone->dynamic_zone_cache.erase(buf->dz_id); } break; } + case ServerOP_DzAddPlayer: + { + auto buf = reinterpret_cast(pack->pBuffer); + if (buf->is_char_online) + { + if (auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id)) + { + dz->DzAddPlayerContinue(buf->requester_name, buf->target_name, buf->remove_name); + } + } + else if (Client* leader = entity_list.GetClientByName(buf->requester_name)) + { + std::string target_name = FormatName(buf->target_name); + leader->MessageString(Chat::Red, DZADD_NOT_ONLINE, target_name.c_str()); + leader->MessageString(Chat::Red, DZADD_INVITE_FAIL, target_name.c_str()); + } + break; + } + case ServerOP_DzMakeLeader: + { + auto buf = reinterpret_cast(pack->pBuffer); + auto old_leader = entity_list.GetClientByCharID(buf->requester_id); + + // success flag is set by world to indicate new leader set to an online member + if (old_leader && buf->is_success) + { + old_leader->MessageString(Chat::Yellow, DZ_LEADER_NAME, buf->new_leader_name); + } + else if (old_leader) + { + uint32_t str_id = buf->is_online ? DZ_NOT_MEMBER : DZ_LEADER_OFFLINE; + old_leader->MessageString(Chat::Red, str_id, buf->new_leader_name); + } + + if (buf->is_success && !RuleB(Expedition, AlwaysNotifyNewLeaderOnChange)) + { + if (auto new_leader = entity_list.GetClientByName(buf->new_leader_name)) + { + new_leader->MessageString(Chat::Yellow, DZ_LEADER_YOU); + } + } + break; + } case ServerOP_DzAddRemoveMember: { auto buf = reinterpret_cast(pack->pBuffer); @@ -387,6 +864,34 @@ void DynamicZone::HandleWorldMessage(ServerPacket* pack) } break; } + case ServerOP_DzGetBulkMemberStatuses: + { + if (zone) + { + std::vector>> dzs; + dzs.reserve(zone->dynamic_zone_cache.size()); + + auto buf = reinterpret_cast(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(pack->pBuffer); @@ -435,6 +940,73 @@ void DynamicZone::HandleWorldMessage(ServerPacket* pack) } break; } + case ServerOP_DzLock: + { + auto buf = reinterpret_cast(pack->pBuffer); + if (zone && !zone->IsZone(buf->sender_zone_id, buf->sender_instance_id)) + { + if (auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id)) + { + dz->SetLocked(buf->lock, false, static_cast(buf->lock_msg), buf->color); + } + } + break; + } + case ServerOP_DzReplayOnJoin: + { + auto buf = reinterpret_cast(pack->pBuffer); + if (zone && !zone->IsZone(buf->sender_zone_id, buf->sender_instance_id)) + { + if (auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id)) + { + dz->SetReplayOnJoin(buf->enabled); + } + } + break; + } + case ServerOP_DzLockout: + case ServerOP_DzLockoutDuration: + { + auto buf = reinterpret_cast(pack->pBuffer); + if (zone && !zone->IsZone(buf->sender_zone_id, buf->sender_instance_id)) + { + if (DynamicZone* dz = DynamicZone::FindDynamicZoneByID(buf->dz_id)) + { + DzLockout lockout{ dz->GetUUID(), dz->GetName(), buf->event_name, buf->expire_time, buf->duration }; + if (pack->opcode == ServerOP_DzLockout) + { + dz->HandleLockoutUpdate(lockout, buf->remove, buf->members_only); + } + else if (pack->opcode == ServerOP_DzLockoutDuration) + { + dz->HandleLockoutDuration(lockout, buf->seconds, buf->members_only, false); + } + } + } + break; + } + case ServerOP_DzCharacterLockout: + { + auto buf = reinterpret_cast(pack->pBuffer); + if (Client* client = entity_list.GetClientByCharID(buf->char_id)) + { + if (!buf->remove) + { + client->AddDzLockout(DzLockout{ buf->uuid, buf->expedition, buf->event, buf->expire_time, buf->duration }); + } + else if (buf->event[0] != '\0') + { + client->RemoveDzLockout(buf->expedition, buf->event); + } + else + { + client->RemoveDzLockouts(buf->expedition); + } + } + break; + } + default: + break; } } @@ -484,19 +1056,17 @@ std::unique_ptr DynamicZone::CreateMemberListPacket(bool cl return outapp; } -std::unique_ptr DynamicZone::CreateMemberListNamePacket( - const std::string& name, bool remove_name) +std::unique_ptr DynamicZone::CreateMemberNamePacket(const std::string& name, bool remove) { constexpr uint32_t outsize = sizeof(DynamicZoneMemberListName_Struct); auto outapp = std::make_unique(OP_DzMemberListName, outsize); auto buf = reinterpret_cast(outapp->pBuffer); - buf->add_name = !remove_name; + buf->add_name = !remove; strn0cpy(buf->name, name.c_str(), sizeof(buf->name)); return outapp; } -std::unique_ptr DynamicZone::CreateMemberListStatusPacket( - const std::string& name, DynamicZoneMemberStatus status) +std::unique_ptr DynamicZone::CreateMemberStatusPacket(const std::string& name, DynamicZoneMemberStatus status) { // member list status uses member list struct with a single entry constexpr uint32_t outsize = sizeof(DynamicZoneMemberList_Struct) + sizeof(DynamicZoneMemberEntry_Struct); @@ -557,26 +1127,25 @@ void DynamicZone::SendLeaderNameToZoneMembers() if (member.id == m_leader.id && RuleB(Expedition, AlwaysNotifyNewLeaderOnChange)) { - member_client->MessageString(Chat::Yellow, DZMAKELEADER_YOU); + member_client->MessageString(Chat::Yellow, DZ_LEADER_YOU); } } } } -void DynamicZone::SendMembersExpireWarning(uint32_t minutes_remaining) +void DynamicZone::SendMembersExpireWarning(uint32_t minutes) { // expeditions warn members in all zones not just the dz - auto outapp = CreateExpireWarningPacket(minutes_remaining); + auto outapp = CreateExpireWarningPacket(minutes); for (const auto& member : GetMembers()) { - Client* member_client = entity_list.GetClientByCharID(member.id); - if (member_client) + Client* client = entity_list.GetClientByCharID(member.id); + if (client) { - member_client->QueuePacket(outapp.get()); + client->QueuePacket(outapp.get()); // live doesn't actually send the chat message with it - member_client->MessageString(Chat::Yellow, EXPEDITION_MIN_REMAIN, - fmt::format_int(minutes_remaining).c_str()); + client->MessageString(Chat::Yellow, DZ_MINUTES_REMAIN, fmt::format_int(minutes).c_str()); } } } @@ -595,30 +1164,30 @@ void DynamicZone::SendMemberListToZoneMembers() } } -void DynamicZone::SendMemberListNameToZoneMembers(const std::string& char_name, bool remove) +void DynamicZone::SendMemberNameToZoneMembers(const std::string& char_name, bool remove) { - auto outapp_member_name = CreateMemberListNamePacket(char_name, remove); + auto outapp = CreateMemberNamePacket(char_name, remove); for (const auto& member : m_members) { Client* member_client = entity_list.GetClientByCharID(member.id); if (member_client) { - member_client->QueuePacket(outapp_member_name.get()); + member_client->QueuePacket(outapp.get()); } } } -void DynamicZone::SendMemberListStatusToZoneMembers(const DynamicZoneMember& update_member) +void DynamicZone::SendMemberStatusToZoneMembers(const DynamicZoneMember& update) { - auto outapp_member_status = CreateMemberListStatusPacket(update_member.name, update_member.status); + auto outapp = CreateMemberStatusPacket(update.name, update.status); for (const auto& member : m_members) { Client* member_client = entity_list.GetClientByCharID(member.id); if (member_client) { - member_client->QueuePacket(outapp_member_status.get()); + member_client->QueuePacket(outapp.get()); } } } @@ -676,10 +1245,9 @@ void DynamicZone::SendUpdatesToZoneMembers(bool removing_all, bool silent) client->QueuePacket(outapp_members.get()); } - // callback to the dz system so it can perform any messages or set client data - if (m_on_client_addremove) + if (m_type == DynamicZoneType::Expedition && removing_all && !silent) { - m_on_client_addremove(client, removing_all, silent); + client->MessageString(Chat::Yellow, DZ_REMOVED, client->GetCleanName(), GetName().c_str()); } } } @@ -706,11 +1274,7 @@ void DynamicZone::ProcessMemberAddRemove(const DynamicZoneMember& member, bool r { // sending clear info also clears member list for removed members client->QueuePacket(CreateInfoPacket(removed).get()); - } - - if (m_on_client_addremove) - { - m_on_client_addremove(client, removed, false); + client->MessageString(Chat::Yellow, removed ? DZ_REMOVED : DZ_ADDED, client->GetCleanName(), GetName().c_str()); } } @@ -720,18 +1284,18 @@ void DynamicZone::ProcessMemberAddRemove(const DynamicZoneMember& member, bool r if (!removed) { SendMemberListToZoneMembers(); } else { - SendMemberListNameToZoneMembers(member.name, true); + SendMemberNameToZoneMembers(member.name, true); } } } -void DynamicZone::ProcessRemoveAllMembers(bool silent) +void DynamicZone::ProcessRemoveAllMembers() { - SendUpdatesToZoneMembers(true, silent); - DynamicZoneBase::ProcessRemoveAllMembers(silent); + SendUpdatesToZoneMembers(true, false); + DynamicZoneBase::ProcessRemoveAllMembers(); } -void DynamicZone::DoAsyncZoneMemberUpdates() +void DynamicZone::UpdateMembers() { // gets member statuses from world and performs zone member updates on reply // if we've already received member statuses we can just update immediately @@ -750,16 +1314,16 @@ void DynamicZone::DoAsyncZoneMemberUpdates() worldserver.SendPacket(pack.get()); } -bool DynamicZone::ProcessMemberStatusChange(uint32_t member_id, DynamicZoneMemberStatus status) +bool DynamicZone::ProcessMemberStatusChange(uint32_t character_id, DynamicZoneMemberStatus status) { - bool changed = DynamicZoneBase::ProcessMemberStatusChange(member_id, status); + bool changed = DynamicZoneBase::ProcessMemberStatusChange(character_id, status); if (changed && m_type == DynamicZoneType::Expedition) { - auto member = GetMemberData(member_id); + auto member = GetMemberData(character_id); if (member.IsValid()) { - SendMemberListStatusToZoneMembers(member); + SendMemberStatusToZoneMembers(member); } } @@ -795,10 +1359,22 @@ bool DynamicZone::CanClientLootCorpse(Client* client, uint32_t npc_type_id, uint // expeditions may prevent looting based on client's lockouts if (GetType() == DynamicZoneType::Expedition) { - auto expedition = Expedition::FindCachedExpeditionByZoneInstance(zone->GetZoneID(), zone->GetInstanceID()); - if (expedition && !expedition->CanClientLootCorpse(client, npc_type_id, entity_id)) + // entity id takes priority, falls back to checking by npc type if not set + std::string event = GetLootEvent(entity_id, DzLootEvent::Type::Entity); + if (event.empty()) { - return false; + event = GetLootEvent(npc_type_id, DzLootEvent::Type::NpcType); + } + + if (!event.empty()) + { + auto lockout = client->GetDzLockout(GetName(), event); + if (!lockout || lockout->UUID() != m_uuid) + { + // client lockout not received in this expedition, prevent looting + LogExpeditions("Character [{}] denied looting npc [{}] for [{}]", client->CharacterID(), npc_type_id, event); + return false; + } } } @@ -824,3 +1400,277 @@ void DynamicZone::MovePCInto(Client* client, bool world_verify) const worldserver.SendPacket(&pack); } } + +void DynamicZone::SetLocked(bool lock, bool update_db, DzLockMsg lock_msg, uint32_t color) +{ + DynamicZoneBase::SetLocked(lock, update_db, lock_msg, color); + + if (m_is_locked && lock_msg != DzLockMsg::None && IsCurrentZoneDz()) + { + auto msg = lock_msg == DzLockMsg::Close ? LockClose : LockBegin; + for (const auto& it : entity_list.GetClientList()) + { + if (it.second) + { + it.second->Message(color, msg); + } + } + } +} + +void DynamicZone::SaveLockouts(const std::vector& lockouts) +{ + m_lockouts = lockouts; + DynamicZoneLockoutsRepository::InsertLockouts(database, m_id, m_lockouts); +} + +static void SendWorldCharacterLockout(uint32_t char_id, const DzLockout& lockout, bool remove) +{ + uint32_t pack_size = sizeof(ServerDzCharacterLockout_Struct); + ServerPacket pack(ServerOP_DzCharacterLockout, pack_size); + auto buf = reinterpret_cast(pack.pBuffer); + buf->remove = remove; + buf->char_id = char_id; + buf->expire_time = lockout.GetExpireTime(); + buf->duration = lockout.GetDuration(); + strn0cpy(buf->uuid, lockout.UUID().c_str(), sizeof(buf->uuid)); + strn0cpy(buf->expedition, lockout.DzName().c_str(), sizeof(buf->expedition)); + strn0cpy(buf->event, lockout.Event().c_str(), sizeof(buf->event)); + worldserver.SendPacket(&pack); +} + +void DynamicZone::AddCharacterLockout( + uint32_t char_id, const std::string& expedition, const std::string& event, uint32_t seconds, const std::string& uuid) +{ + if (char_id) + { + auto lockout = DzLockout::Create(expedition, event, seconds, uuid); + CharacterExpeditionLockoutsRepository::InsertLockouts(database, char_id, { lockout }); + SendWorldCharacterLockout(char_id, lockout, false); + } +} + +void DynamicZone::AddCharacterLockout( + const std::string& char_name, const std::string& expedition, const std::string& event, uint32_t seconds, const std::string& uuid) +{ + if (!char_name.empty()) + { + uint32_t char_id = database.GetCharacterID(char_name); + AddCharacterLockout(char_id, expedition, event, seconds, uuid); + } +} + +bool DynamicZone::HasCharacterLockout(uint32_t char_id, const std::string& expedition, const std::string& event) +{ + auto lockouts = GetCharacterLockouts(char_id); + return std::any_of(lockouts.begin(), lockouts.end(), + [&](const auto& l) { return !l.IsExpired() && l.IsSame(expedition, event); }); +} + +bool DynamicZone::HasCharacterLockout(const std::string& char_name, const std::string& expedition, const std::string& event) +{ + if (!char_name.empty()) + { + return HasCharacterLockout(database.GetCharacterID(char_name), expedition, event); + } + return false; +} + +void DynamicZone::RemoveCharacterLockouts(uint32_t char_id, const std::string& expedition, const std::string& event) +{ + if (char_id) + { + std::string where = fmt::format("character_id = {}", char_id); + if (!event.empty()) + { + where += fmt::format(" AND expedition_name = '{}' AND event_name = '{}'", Strings::Escape(expedition), Strings::Escape(event)); + } + else if (!expedition.empty()) + { + where += fmt::format(" AND expedition_name = '{}'", Strings::Escape(expedition)); + } + CharacterExpeditionLockoutsRepository::DeleteWhere(database, where); + + DzLockout lockout{{}, expedition, event, 0, 0}; + SendWorldCharacterLockout(char_id, lockout, true); + } +} + +void DynamicZone::RemoveCharacterLockouts(const std::string& char_name, const std::string& expedition, const std::string& event) +{ + if (!char_name.empty()) + { + uint32_t char_id = database.GetCharacterID(char_name); + RemoveCharacterLockouts(char_id, expedition, event); + } +} + +std::vector DynamicZone::GetCharacterLockouts(uint32_t char_id) +{ + std::vector lockouts; + if (char_id == 0) + { + return lockouts; + } + + auto client = entity_list.GetClientByCharID(char_id); + if (client) + { + lockouts = client->GetDzLockouts(); + } + else + { + lockouts = CharacterExpeditionLockoutsRepository::GetLockouts(database, char_id); + } + + return lockouts; +} + +void DynamicZone::AddClientsLockout(const DzLockout& lockout) +{ + std::vector char_ids; + for (const auto& it : entity_list.GetClientList()) + { + char_ids.push_back(it.second->CharacterID()); + it.second->AddDzLockout(lockout); + } + + if (!char_ids.empty()) + { + CharacterExpeditionLockoutsRepository::InsertLockout(database, char_ids, lockout); + } +} + +void DynamicZone::HandleLockoutUpdate(const DzLockout& lockout, bool remove, bool members_only) +{ + DynamicZoneBase::HandleLockoutUpdate(lockout, remove, members_only); + + std::vector char_ids; + for (const auto& it : entity_list.GetClientList()) + { + Client* client = it.second; + if (std::ranges::any_of(m_members, [&](const auto& m) { return m.id == client->CharacterID(); })) + { + if (!remove) + { + client->AddDzLockout(lockout); + } + else + { + client->RemoveDzLockout(GetName(), lockout.Event()); + } + } + else if (!remove && IsCurrentZoneDz()) // non-member client inside the dz + { + // all clients inside the dz instance receive added lockouts to avoid exploits + // where members quit the expedition but haven't been kicked from zone yet + char_ids.push_back(client->CharacterID()); + client->AddDzLockout(lockout); + } + } + + if (!char_ids.empty()) + { + CharacterExpeditionLockoutsRepository::InsertLockout(database, char_ids, lockout); + } +} + +void DynamicZone::HandleLockoutDuration(const DzLockout& lockout, int seconds, bool members_only, bool insert_db) +{ + DynamicZoneBase::HandleLockoutDuration(lockout, seconds, members_only, insert_db); + + std::vector char_ids; + for (const auto& it : entity_list.GetClientList()) + { + Client* client = it.second; + if (std::ranges::any_of(m_members, [&](const auto& m) { return m.id == client->CharacterID(); })) + { + client->AddDzLockoutDuration(lockout, seconds, m_uuid); + } + else if (IsCurrentZoneDz()) // non-member client inside the dz + { + char_ids.push_back(client->CharacterID()); + client->AddDzLockoutDuration(lockout, seconds, m_uuid); + } + } + + if (!char_ids.empty()) // always update db for non-members in dz (call may be from another zone) + { + int secs = static_cast(seconds * RuleR(Expedition, LockoutDurationMultiplier)); + CharacterExpeditionLockoutsRepository::AddLockoutDuration(database, char_ids, lockout, secs); + } +} + +void DynamicZone::SetLootEvent(uint32_t id, const std::string& event, DzLootEvent::Type type) +{ + if (id != 0 && IsCurrentZoneDz()) + { + LogExpeditions("Setting loot event [{}] for id [{}] type [{}]", event, id, static_cast(type)); + auto it = std::ranges::find_if(m_loot_events, [&](const auto& le) { return le.id == id && le.type == type; }); + if (it != m_loot_events.end()) + { + it->event = event; + } + else + { + m_loot_events.push_back({id, event, type}); + } + } +} + +std::string DynamicZone::GetLootEvent(uint32_t id, DzLootEvent::Type type) const +{ + std::string event_name; + if (id != 0 && IsCurrentZoneDz()) + { + auto it = std::ranges::find_if(m_loot_events, [&](const auto& le) { return le.id == id && le.type == type; }); + if (it != m_loot_events.end()) + { + event_name = it->event; + } + } + return event_name; +} + +std::unique_ptr DynamicZone::CreateInvitePacket(const std::string& inviter, const std::string& swap_name) +{ + uint32_t outsize = sizeof(ExpeditionInvite_Struct); + auto outapp = std::make_unique(OP_DzExpeditionInvite, outsize); + auto outbuf = reinterpret_cast(outapp->pBuffer); + strn0cpy(outbuf->inviter_name, inviter.c_str(), sizeof(outbuf->inviter_name)); + strn0cpy(outbuf->expedition_name, GetName().c_str(), sizeof(outbuf->expedition_name)); + strn0cpy(outbuf->swap_name, swap_name.c_str(), sizeof(outbuf->swap_name)); + outbuf->swapping = !swap_name.empty(); + outbuf->dz_zone_id = GetZoneID(); + outbuf->dz_instance_id = GetInstanceID(); + return outapp; +} + +void DynamicZone::SendWorldPendingInvite(const ExpeditionInvite& invite, const std::string& add_name) +{ + LogExpeditions("[{}] saving invite from [{}] to [{}]", add_name, invite.inviter_name, invite.dz_id); + SendWorldPlayerInvite(invite.inviter_name, invite.swap_name, add_name, true); +} + +void DynamicZone::SendWorldPlayerInvite(const std::string& inviter, const std::string& swap_name, const std::string& add_name, bool pending) +{ + auto opcode = pending ? ServerOP_DzSaveInvite : ServerOP_DzAddPlayer; + ServerPacket pack(opcode, static_cast(sizeof(ServerDzCommand_Struct))); + auto buf = reinterpret_cast(pack.pBuffer); + buf->dz_id = GetID(); + buf->is_char_online = false; + strn0cpy(buf->requester_name, inviter.c_str(), sizeof(buf->requester_name)); + strn0cpy(buf->target_name, add_name.c_str(), sizeof(buf->target_name)); + strn0cpy(buf->remove_name, swap_name.c_str(), sizeof(buf->remove_name)); + worldserver.SendPacket(&pack); +} + +void DynamicZone::SendWorldMakeLeaderRequest(uint32_t char_id, const std::string& leader_name) +{ + ServerPacket pack(ServerOP_DzMakeLeader, static_cast(sizeof(ServerDzCommandMakeLeader_Struct))); + auto buf = reinterpret_cast(pack.pBuffer); + buf->dz_id = GetID(); + buf->requester_id = char_id; + strn0cpy(buf->new_leader_name, leader_name.c_str(), sizeof(buf->new_leader_name)); + worldserver.SendPacket(&pack); +} diff --git a/zone/dynamic_zone.h b/zone/dynamic_zone.h index 45d4e93ea..82d82263f 100644 --- a/zone/dynamic_zone.h +++ b/zone/dynamic_zone.h @@ -31,6 +31,15 @@ class Client; class Database; class EQApplicationPacket; class ServerPacket; +struct ExpeditionInvite; + +struct DzLootEvent +{ + enum class Type { NpcType = 0, Entity }; + uint32_t id = 0; + std::string event; + Type type = Type::NpcType; +}; class DynamicZone : public DynamicZoneBase { @@ -40,53 +49,93 @@ public: DynamicZone() = default; DynamicZone(uint32_t zone_id, uint32_t version, uint32_t duration, DynamicZoneType type); + static constexpr int32_t EventTimerID = 1; + static constexpr int32_t ReplayTimerID = -1; + static void CacheAllFromDatabase(); static void CacheNewDynamicZone(ServerPacket* pack); - static DynamicZone* CreateNew(DynamicZone& dz_details, const std::vector& members); - static DynamicZone* FindDynamicZoneByID(uint32_t dz_id); + static DynamicZone* TryCreate(Client& client, DynamicZone& dzinfo, bool silent = false); + static DynamicZone* FindDynamicZoneByID(uint32_t dz_id, DynamicZoneType type = DynamicZoneType::None); + static DynamicZone* FindExpeditionByCharacter(uint32_t char_id); + static DynamicZone* FindExpeditionByZone(uint32_t zone_id, uint32_t instance_id); static void HandleWorldMessage(ServerPacket* pack); + static void AddClientsLockout(const DzLockout& lockout); + static void AddCharacterLockout(uint32_t char_id, const std::string& expedition, const std::string& event, uint32_t seconds, const std::string& uuid = {}); + static void AddCharacterLockout(const std::string& char_name, const std::string& expedition, const std::string& event, uint32_t seconds, const std::string& uuid = {}); + static bool HasCharacterLockout(uint32_t char_id, const std::string& expedition, const std::string& event); + static bool HasCharacterLockout(const std::string& char_name, const std::string& expedition, const std::string& event); + static void RemoveCharacterLockouts(uint32_t char_id, const std::string& expedition = {}, const std::string& event = {}); + static void RemoveCharacterLockouts(const std::string& char_name, const std::string& expedition = {}, const std::string& event = {}); + static std::vector GetCharacterLockouts(uint32_t char_id); + + void DzAddPlayer(Client* client, const std::string& add_name, const std::string& swap_name = {}); + void DzAddPlayerContinue(std::string inviter, std::string add_name, std::string swap_name = {}); + void DzInviteResponse(Client* client, bool accepted, const std::string& swap_name); + void DzMakeLeader(Client* client, std::string leader_name); + void DzPlayerList(Client* client); + void DzRemovePlayer(Client* client, std::string name); + void DzSwapPlayer(Client* client, std::string rem_name, std::string add_name); + void DzQuit(Client* client); + void DzKickPlayers(Client* client); + void SendWorldMakeLeaderRequest(uint32_t char_id, const std::string& leader_name); + void SendWorldPendingInvite(const ExpeditionInvite& invite, const std::string& add_name); + void SetSecondsRemaining(uint32_t seconds_remaining) override; - void DoAsyncZoneMemberUpdates(); bool CanClientLootCorpse(Client* client, uint32_t npc_type_id, uint32_t entity_id); - bool IsCurrentZoneDzInstance() const; + bool IsCurrentZoneDz() const; void MovePCInto(Client* client, bool world_verify = false) const; - void RegisterOnClientAddRemove(std::function on_client_addremove); void SendClientWindowUpdate(Client* client); void SendLeaderNameToZoneMembers(); void SendMemberListToZoneMembers(); - void SendMemberListNameToZoneMembers(const std::string& char_name, bool remove); - void SendMemberListStatusToZoneMembers(const DynamicZoneMember& member); - void SendRemoveAllMembersToZoneMembers(bool silent) { ProcessRemoveAllMembers(silent); } + 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); - std::unique_ptr CreateExpireWarningPacket(uint32_t minutes_remaining); - std::unique_ptr CreateInfoPacket(bool clear = false); - std::unique_ptr CreateLeaderNamePacket(); - std::unique_ptr CreateMemberListPacket(bool clear = false); - std::unique_ptr CreateMemberListNamePacket(const std::string& name, bool remove_name); - std::unique_ptr CreateMemberListStatusPacket(const std::string& name, DynamicZoneMemberStatus status); - -protected: - uint16_t GetCurrentInstanceID() override; - uint16_t GetCurrentZoneID() override; - Database& GetDatabase() override; - void ProcessCompassChange(const DynamicZoneLocation& location) override; - void ProcessMemberAddRemove(const DynamicZoneMember& member, bool removed) override; - bool ProcessMemberStatusChange(uint32_t member_id, DynamicZoneMemberStatus status) override; - void ProcessRemoveAllMembers(bool silent = false) override; - void ProcessSetSwitchID(int dz_switch_id) override; - bool SendServerPacket(ServerPacket* packet) override; + 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; + Database& GetDatabase() override; + void HandleLockoutDuration(const DzLockout& lockout, int seconds, bool members_only, bool insert_db) override; + void HandleLockoutUpdate(const DzLockout& lockout, bool remove, bool members_only) override; + void ProcessCompassChange(const DynamicZoneLocation& location) override; + void ProcessMemberAddRemove(const DynamicZoneMember& member, bool removed) override; + bool ProcessMemberStatusChange(uint32_t character_id, DynamicZoneMemberStatus status) override; + void ProcessRemoveAllMembers() override; + void ProcessSetSwitchID(int dz_switch_id) override; + bool SendServerPacket(ServerPacket* packet) override; + + bool ConfirmLeaderCommand(Client* client); + bool ProcessAddConflicts(Client* leader, Client* client, bool swapping); void ProcessLeaderChanged(uint32_t new_leader_id); + void SaveLockouts(const std::vector& lockouts); + void SendClientInvite(Client* client, const std::string& inviter, const std::string& swap_name); void SendCompassUpdateToZoneMembers(); + void SendLeaderMessage(Client* leader, uint16_t type, const std::string& msg); + void SendLeaderMessage(Client* leader, uint16_t type, uint32_t str_id, std::initializer_list args = {}); void SendMembersExpireWarning(uint32_t minutes); void SendUpdatesToZoneMembers(bool removing_all = false, bool silent = true); + 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::function m_on_client_addremove; + std::unique_ptr CreateExpireWarningPacket(uint32_t minutes_remaining); + std::unique_ptr CreateInfoPacket(bool clear = false); + std::unique_ptr CreateInvitePacket(const std::string& inviter, const std::string& swap_name); + std::unique_ptr CreateLeaderNamePacket(); + std::unique_ptr CreateMemberListPacket(bool clear = false); + std::unique_ptr CreateMemberNamePacket(const std::string& name, bool remove); + std::unique_ptr CreateMemberStatusPacket(const std::string& name, DynamicZoneMemberStatus status); + + std::vector m_loot_events; // only valid inside dz zone }; #endif diff --git a/zone/effects.cpp b/zone/effects.cpp index 63e349f1b..3efda3d5e 100644 --- a/zone/effects.cpp +++ b/zone/effects.cpp @@ -263,7 +263,7 @@ int64 Mob::GetActDoTDamage(uint16 spell_id, int64 value, Mob* target, bool from_ chance += itembonuses.CriticalDoTChance + spellbonuses.CriticalDoTChance + aabonuses.CriticalDoTChance; if (spellbonuses.CriticalDotDecay) - chance += GetDecayEffectValue(spell_id, SE_CriticalDotDecay); + chance += GetDecayEffectValue(spell_id, SpellEffect::CriticalDotDecay); if (spells[spell_id].override_crit_chance > 0 && chance > spells[spell_id].override_crit_chance) chance = spells[spell_id].override_crit_chance; @@ -429,14 +429,14 @@ int64 Mob::GetActSpellHealing(uint16 spell_id, int64 value, Mob* target, bool fr critical_chance += itembonuses.CriticalHealChance + spellbonuses.CriticalHealChance + aabonuses.CriticalHealChance; if (spellbonuses.CriticalHealDecay) { - critical_chance += GetDecayEffectValue(spell_id, SE_CriticalHealDecay); + critical_chance += GetDecayEffectValue(spell_id, SpellEffect::CriticalHealDecay); } } else { critical_chance = itembonuses.CriticalHealOverTime + spellbonuses.CriticalHealOverTime + aabonuses.CriticalHealOverTime; if (spellbonuses.CriticalRegenDecay) { - critical_chance += GetDecayEffectValue(spell_id, SE_CriticalRegenDecay); + critical_chance += GetDecayEffectValue(spell_id, SpellEffect::CriticalRegenDecay); } } @@ -596,7 +596,7 @@ int32 Mob::GetActSpellCost(uint16 spell_id, int32 cost) if (buffs[buffSlot].spellid == 0 || buffs[buffSlot].spellid >= SPDAT_RECORDS) continue; - if(IsEffectInSpell(buffs[buffSlot].spellid, SE_ReduceManaCost)) { + if(IsEffectInSpell(buffs[buffSlot].spellid, SpellEffect::ReduceManaCost)) { if(CalcFocusEffect(focusManaCost, buffs[buffSlot].spellid, spell_id) == 100) cost = 1; } @@ -1105,12 +1105,12 @@ void EntityList::AESpell( IsTargetableAESpell(spell_id) && is_detrimental_spell && !is_npc && - !IsEffectInSpell(spell_id, SE_Lull) && - !IsEffectInSpell(spell_id, SE_Mez) + !IsEffectInSpell(spell_id, SpellEffect::Lull) && + !IsEffectInSpell(spell_id, SpellEffect::Mez) ) { max_targets_allowed = RuleI(Spells, TargetedAOEMaxTargets); } else if ( - IsPBAENukeSpell(spell_id) && + IsPBAESpell(spell_id) && IsDetrimentalSpell && !is_npc ) { @@ -1125,8 +1125,8 @@ void EntityList::AESpell( RuleI(Range, MobCloseScanDistance), distance ); - - for (auto& it: caster_mob->GetCloseMobList(distance)) { + auto list = caster_mob->GetCloseMobList(distance); + for (auto& it: list) { current_mob = it.second; if (!current_mob) { continue; @@ -1142,7 +1142,7 @@ void EntityList::AESpell( continue; } - if (spells[spell_id].target_type == ST_TargetAENoPlayersPets && current_mob->IsPetOwnerClient()) { + if (spells[spell_id].target_type == ST_TargetAENoPlayersPets && current_mob->IsPetOwnerOfClientBot()) { continue; } diff --git a/zone/embparser.cpp b/zone/embparser.cpp index 55cf1a4ce..ad9708d52 100644 --- a/zone/embparser.cpp +++ b/zone/embparser.cpp @@ -59,6 +59,7 @@ void perl_register_bot(); void perl_register_buff(); void perl_register_merc(); void perl_register_database(); +void perl_register_zone(); #endif // EMBPERL_XS_CLASSES #endif // EMBPERL_XS @@ -206,6 +207,9 @@ const char* QuestEventSubroutines[_LargestEventID] = { "EVENT_AA_LOSS", "EVENT_SPELL_BLOCKED", "EVENT_READ_ITEM", + "EVENT_PET_COMMAND", + "EVENT_CHARM_START", + "EVENT_CHARM_END", // Add new events before these or Lua crashes "EVENT_SPELL_EFFECT_BOT", @@ -221,6 +225,8 @@ PerlembParser::PerlembParser() : perl(nullptr) global_bot_quest_status_ = questUnloaded; merc_quest_status_ = questUnloaded; global_merc_quest_status_ = questUnloaded; + zone_quest_status_ = questUnloaded; + global_zone_quest_status_ = questUnloaded; } PerlembParser::~PerlembParser() @@ -264,6 +270,8 @@ void PerlembParser::ReloadQuests() global_bot_quest_status_ = questUnloaded; merc_quest_status_ = questUnloaded; global_merc_quest_status_ = questUnloaded; + zone_quest_status_ = questUnloaded; + global_zone_quest_status_ = questUnloaded; item_quest_status_.clear(); spell_quest_status_.clear(); @@ -277,6 +285,7 @@ int PerlembParser::EventCommon( EQ::ItemInstance* inst, const SPDat_Spell_Struct* spell, Mob* mob, + Zone* zone, uint32 extra_data, bool is_global, std::vector* extra_pointers @@ -286,52 +295,22 @@ int PerlembParser::EventCommon( return 0; } - bool is_player_quest = false; - bool is_global_player_quest = false; - bool is_global_npc_quest = false; - bool is_bot_quest = false; - bool is_global_bot_quest = false; - bool is_merc_quest = false; - bool is_global_merc_quest = false; - bool is_item_quest = false; - bool is_spell_quest = false; - - std::string package_name; - - GetQuestTypes( - is_player_quest, - is_global_player_quest, - is_bot_quest, - is_global_bot_quest, - is_merc_quest, - is_global_merc_quest, - is_global_npc_quest, - is_item_quest, - is_spell_quest, + QuestType quest_type = GetQuestTypes( event_id, npc_mob, inst, mob, + zone, is_global ); - GetQuestPackageName( - is_player_quest, - is_global_player_quest, - is_bot_quest, - is_global_bot_quest, - is_merc_quest, - is_global_merc_quest, - is_global_npc_quest, - is_item_quest, - is_spell_quest, - package_name, + std::string package_name = GetQuestPackageName( + quest_type, event_id, object_id, data, npc_mob, - inst, - is_global + inst ); const std::string& sub_name = QuestEventSubroutines[event_id]; @@ -347,15 +326,7 @@ int PerlembParser::EventCommon( /* Check for QGlobal export event enable */ if (parse->perl_event_export_settings[event_id].qglobals) { ExportQGlobals( - is_player_quest, - is_global_player_quest, - is_bot_quest, - is_global_bot_quest, - is_merc_quest, - is_global_merc_quest, - is_global_npc_quest, - is_item_quest, - is_spell_quest, + quest_type, package_name, npc_mob, mob, @@ -366,15 +337,7 @@ int PerlembParser::EventCommon( /* Check for Mob export event enable */ if (parse->perl_event_export_settings[event_id].mob) { ExportMobVariables( - is_player_quest, - is_global_player_quest, - is_bot_quest, - is_global_bot_quest, - is_merc_quest, - is_global_merc_quest, - is_global_npc_quest, - is_item_quest, - is_spell_quest, + quest_type, package_name, mob, npc_mob @@ -396,19 +359,24 @@ int PerlembParser::EventCommon( ExportEventVariables(package_name, event_id, object_id, data, npc_mob, inst, mob, extra_data, extra_pointers); } - if (is_player_quest || is_global_player_quest) { - return SendCommands(package_name.c_str(), QuestEventSubroutines[event_id], 0, mob, mob, nullptr, nullptr); - } else if (is_bot_quest || is_global_bot_quest || is_merc_quest || is_global_merc_quest) { - return SendCommands(package_name.c_str(), QuestEventSubroutines[event_id], 0, npc_mob, mob, nullptr, nullptr); - } else if (is_item_quest) { - return SendCommands(package_name.c_str(), QuestEventSubroutines[event_id], 0, mob, mob, inst, nullptr); - } else if (is_spell_quest) { + if (quest_type == QuestType::Player || quest_type == QuestType::PlayerGlobal) { + return SendCommands(package_name.c_str(), QuestEventSubroutines[event_id], 0, mob, mob, nullptr, nullptr, nullptr); + } else if ( + quest_type == QuestType::Bot || + quest_type == QuestType::BotGlobal || + quest_type == QuestType::Merc || + quest_type == QuestType::MercGlobal + ) { + return SendCommands(package_name.c_str(), QuestEventSubroutines[event_id], 0, npc_mob, mob, nullptr, nullptr, nullptr); + } else if (quest_type == QuestType::Item || quest_type == QuestType::ItemGlobal) { + return SendCommands(package_name.c_str(), QuestEventSubroutines[event_id], 0, mob, mob, inst, nullptr, nullptr); + } else if (quest_type == QuestType::Spell || quest_type == QuestType::SpellGlobal) { if (mob) { - return SendCommands(package_name.c_str(), QuestEventSubroutines[event_id], 0, mob, mob, nullptr, spell); + return SendCommands(package_name.c_str(), QuestEventSubroutines[event_id], 0, mob, mob, nullptr, spell, nullptr); } else { - return SendCommands(package_name.c_str(), QuestEventSubroutines[event_id], 0, npc_mob, mob, nullptr, spell); + return SendCommands(package_name.c_str(), QuestEventSubroutines[event_id], 0, npc_mob, mob, nullptr, spell, nullptr); } - } else { + } else if (quest_type == QuestType::NPC || quest_type == QuestType::NPCGlobal) { return SendCommands( package_name.c_str(), QuestEventSubroutines[event_id], @@ -416,8 +384,20 @@ int PerlembParser::EventCommon( npc_mob, mob, nullptr, + nullptr, nullptr ); + } else if (quest_type == QuestType::Zone || quest_type == QuestType::ZoneGlobal) { + return SendCommands( + package_name.c_str(), + QuestEventSubroutines[event_id], + 0, + nullptr, + nullptr, + nullptr, + nullptr, + zone + ); } } @@ -438,6 +418,7 @@ int PerlembParser::EventNPC( nullptr, nullptr, mob, + nullptr, extra_data, false, extra_pointers @@ -461,6 +442,7 @@ int PerlembParser::EventGlobalNPC( nullptr, nullptr, mob, + nullptr, extra_data, true, extra_pointers @@ -483,6 +465,7 @@ int PerlembParser::EventPlayer( nullptr, nullptr, client, + nullptr, extra_data, false, extra_pointers @@ -505,6 +488,7 @@ int PerlembParser::EventGlobalPlayer( nullptr, nullptr, client, + nullptr, extra_data, true, extra_pointers @@ -533,6 +517,7 @@ int PerlembParser::EventItem( inst, nullptr, client, + nullptr, extra_data, false, extra_pointers @@ -557,6 +542,7 @@ int PerlembParser::EventSpell( nullptr, &spells[spell_id], client, + nullptr, extra_data, false, extra_pointers @@ -1005,7 +991,8 @@ int PerlembParser::SendCommands( Mob* other, Mob* mob, EQ::ItemInstance* inst, - const SPDat_Spell_Struct* spell + const SPDat_Spell_Struct* spell, + Zone* zone ) { if (!perl) { @@ -1013,12 +1000,22 @@ int PerlembParser::SendCommands( } int ret_value = 0; + RunningQuest q; + + q.owner = other; + q.questitem = inst; + q.questspell = spell; + if (mob && mob->IsClient()) { - quest_manager.StartQuest(other, mob->CastToClient(), inst, spell); - } else { - quest_manager.StartQuest(other); + q.initiator = mob->CastToClient(); } + if (zone) { + q.zone = zone; + } + + quest_manager.StartQuest(q); + try { perl->eval(fmt::format("package {};", prefix).c_str()); @@ -1032,7 +1029,8 @@ int PerlembParser::SendCommands( "merc", "npc", "questitem", - "spell" + "spell", + "zone" }; for (const auto& suffix : suffixes) { @@ -1057,21 +1055,23 @@ int PerlembParser::SendCommands( sv_setsv(client, _empty_sv); } - if (other->IsBot()) { - Bot* b = quest_manager.GetBot(); - buf = fmt::format("{}::bot", prefix); - SV* bot = get_sv(buf.c_str(), true); - sv_setref_pv(bot, "Bot", b); - } else if (other->IsMerc()) { - Merc* m = quest_manager.GetMerc(); - buf = fmt::format("{}::merc", prefix); - SV* merc = get_sv(buf.c_str(), true); - sv_setref_pv(merc, "Merc", m); - } else if (other->IsNPC()) { - NPC* n = quest_manager.GetNPC(); - buf = fmt::format("{}::npc", prefix); - SV* npc = get_sv(buf.c_str(), true); - sv_setref_pv(npc, "NPC", n); + if (other) { + if (other->IsBot()) { + Bot* b = quest_manager.GetBot(); + buf = fmt::format("{}::bot", prefix); + SV* bot = get_sv(buf.c_str(), true); + sv_setref_pv(bot, "Bot", b); + } else if (other->IsMerc()) { + Merc* m = quest_manager.GetMerc(); + buf = fmt::format("{}::merc", prefix); + SV* merc = get_sv(buf.c_str(), true); + sv_setref_pv(merc, "Merc", m); + } else if (other->IsNPC()) { + NPC* n = quest_manager.GetNPC(); + buf = fmt::format("{}::npc", prefix); + SV* npc = get_sv(buf.c_str(), true); + sv_setref_pv(npc, "NPC", n); + } } //only export QuestItem if it's an inst quest @@ -1109,7 +1109,8 @@ int PerlembParser::SendCommands( "merc", "npc", "questitem", - "spell" + "spell", + "zone" }; for (const auto& suffix : suffixes) { @@ -1187,23 +1188,16 @@ void PerlembParser::MapFunctions() perl_register_buff(); perl_register_merc(); perl_register_database(); + perl_register_zone(); #endif // EMBPERL_XS_CLASSES } -void PerlembParser::GetQuestTypes( - bool& is_player_quest, - bool& is_global_player_quest, - bool& is_bot_quest, - bool& is_global_bot_quest, - bool& is_merc_quest, - bool& is_global_merc_quest, - bool& is_global_npc_quest, - bool& is_item_quest, - bool& is_spell_quest, +QuestType PerlembParser::GetQuestTypes( QuestEventID event_id, Mob* npc_mob, EQ::ItemInstance* inst, Mob* mob, + Zone* zone, bool is_global ) { @@ -1217,100 +1211,74 @@ void PerlembParser::GetQuestTypes( event_id == EVENT_SPELL_FADE || event_id == EVENT_SPELL_EFFECT_TRANSLOCATE_COMPLETE ) { - is_spell_quest = true; + return is_global ? QuestType::SpellGlobal : QuestType::Spell; } else { if (npc_mob) { if (!inst) { - if (is_global) { - if (npc_mob->IsBot()) { - is_global_bot_quest = true; - } else if (npc_mob->IsMerc()) { - is_global_merc_quest = true; - } - } else { - if (npc_mob->IsBot()) { - is_bot_quest = true; - } else if (npc_mob->IsMerc()) { - is_merc_quest = true; - } + if (npc_mob->IsBot()) { + return is_global ? QuestType::BotGlobal : QuestType::Bot; + } else if (npc_mob->IsMerc()) { + return is_global ? QuestType::MercGlobal : QuestType::Merc; + } else if (npc_mob->IsNPC()) { + return is_global ? QuestType::NPCGlobal : QuestType::NPC; } } else { - is_item_quest = true; + return is_global ? QuestType::ItemGlobal : QuestType::Item; } } else if (!npc_mob && mob) { if (!inst) { - if (is_global) { - if (mob->IsClient()) { - is_global_player_quest = true; - } - } else { - if (mob->IsClient()) { - is_player_quest = true; - } + if (mob->IsClient()) { + return is_global ? QuestType::PlayerGlobal : QuestType::Player; } } else { - is_item_quest = true; + return is_global ? QuestType::ItemGlobal : QuestType::Item; } + } else if (zone) { + return is_global ? QuestType::ZoneGlobal : QuestType::Zone; } } } -void PerlembParser::GetQuestPackageName( - bool& is_player_quest, - bool& is_global_player_quest, - bool& is_bot_quest, - bool& is_global_bot_quest, - bool& is_merc_quest, - bool& is_global_merc_quest, - bool& is_global_npc_quest, - bool& is_item_quest, - bool& is_spell_quest, - std::string& package_name, +std::string PerlembParser::GetQuestPackageName( + QuestType quest_type, QuestEventID event_id, uint32 object_id, const char* data, Mob* npc_mob, - EQ::ItemInstance* inst, - bool is_global + EQ::ItemInstance* inst ) { - if ( - !is_player_quest && - !is_global_player_quest && - !is_bot_quest && - !is_global_bot_quest && - !is_merc_quest && - !is_global_merc_quest && - !is_item_quest && - !is_spell_quest - ) { - if (is_global) { - is_global_npc_quest = true; - package_name = "qst_global_npc"; - } else { - package_name = fmt::format("qst_npc_{}", npc_mob->GetNPCTypeID()); - } - } else if (is_item_quest) { + if (quest_type == QuestType::NPC) { + return fmt::format("qst_npc_{}", npc_mob->GetNPCTypeID()); + } else if (quest_type == QuestType::NPCGlobal) { + return "qst_global_npc"; + } else if (quest_type == QuestType::Item || quest_type == QuestType::ItemGlobal) { if (!inst) { - return; + return ""; } - package_name = fmt::format("qst_item_{}", inst->GetID()); - } else if (is_player_quest) { - package_name = "qst_player"; - } else if (is_global_player_quest) { - package_name = "qst_global_player"; - } else if (is_bot_quest) { - package_name = "qst_bot"; - } else if (is_global_bot_quest) { - package_name = "qst_global_bot"; - } else if (is_merc_quest) { - package_name = "qst_merc"; - } else if (is_global_merc_quest) { - package_name = "qst_global_merc"; - } else { - package_name = fmt::format("qst_spell_{}", object_id); + return fmt::format("qst_item_{}", inst->GetID()); + } else if (quest_type == QuestType::Player) { + return "qst_player"; + } else if (quest_type == QuestType::PlayerGlobal) { + return "qst_global_player"; + } else if (quest_type == QuestType::Bot) { + return "qst_bot"; + } else if (quest_type == QuestType::BotGlobal) { + return "qst_global_bot"; + } else if (quest_type == QuestType::Merc) { + return "qst_merc"; + } else if (quest_type == QuestType::MercGlobal) { + return "qst_global_merc"; + } else if (quest_type == QuestType::Spell || quest_type == QuestType::SpellGlobal) { + return fmt::format("qst_spell_{}", object_id); + } else if (quest_type == QuestType::Zone) { + return "qst_zone"; + } else if (quest_type == QuestType::ZoneGlobal) { + return "qst_global_zone"; } + + return ""; } void PerlembParser::ExportCharID(const std::string& package_name, int& char_id, Mob* npc_mob, Mob* mob) @@ -1329,15 +1297,7 @@ void PerlembParser::ExportCharID(const std::string& package_name, int& char_id, } void PerlembParser::ExportQGlobals( - bool is_player_quest, - bool is_global_player_quest, - bool is_bot_quest, - bool is_global_bot_quest, - bool is_merc_quest, - bool is_global_merc_quest, - bool is_global_npc_quest, - bool is_item_quest, - bool is_spell_quest, + QuestType quest_type, std::string& package_name, Mob* npc_mob, Mob* mob, @@ -1345,16 +1305,7 @@ void PerlembParser::ExportQGlobals( ) { //NPC quest - if ( - !is_player_quest && - !is_global_player_quest && - !is_bot_quest && - !is_global_bot_quest && - !is_merc_quest && - !is_global_merc_quest && - !is_item_quest && - !is_spell_quest - ) { + if (quest_type == QuestType::NPC || quest_type == QuestType::NPCGlobal) { //only export for npcs that are global enabled. if (npc_mob && npc_mob->GetQglobal()) { std::map globhash; @@ -1483,15 +1434,7 @@ void PerlembParser::ExportQGlobals( } void PerlembParser::ExportMobVariables( - bool is_player_quest, - bool is_global_player_quest, - bool is_bot_quest, - bool is_global_bot_quest, - bool is_merc_quest, - bool is_global_merc_quest, - bool is_global_npc_quest, - bool is_item_quest, - bool is_spell_quest, + QuestType quest_type, std::string& package_name, Mob* mob, Mob* npc_mob @@ -1509,15 +1452,7 @@ void PerlembParser::ExportMobVariables( ExportVar(package_name.c_str(), "bot_owner_char_id", mob->CastToBot()->GetBotOwnerCharacterID()); } - if ( - !is_player_quest && - !is_global_player_quest && - !is_bot_quest && - !is_global_bot_quest && - !is_merc_quest && - !is_global_merc_quest && - !is_item_quest - ) { + if (quest_type == QuestType::NPC || quest_type == QuestType::NPCGlobal) { if (mob && mob->IsClient() && npc_mob && npc_mob->IsNPC()) { Client* c = mob->CastToClient(); @@ -1541,16 +1476,7 @@ void PerlembParser::ExportMobVariables( ExportVar(package_name.c_str(), "userid", mob->GetID()); } - if ( - !is_player_quest && - !is_global_player_quest && - !is_bot_quest && - !is_global_bot_quest && - !is_merc_quest && - !is_global_merc_quest && - !is_item_quest && - !is_spell_quest - ) { + if (quest_type == QuestType::NPC || quest_type == QuestType::NPCGlobal) { if (npc_mob->IsNPC()) { ExportVar(package_name.c_str(), "mname", npc_mob->GetName()); ExportVar(package_name.c_str(), "mobid", npc_mob->GetID()); @@ -1579,6 +1505,7 @@ void PerlembParser::ExportZoneVariables(std::string& package_name) ExportVar(package_name.c_str(), "instanceversion", zone->GetInstanceVersion()); TimeOfDay_Struct eqTime{ }; zone->zone_time.GetCurrentEQTimeOfDay(time(0), &eqTime); + ExportVar(package_name.c_str(), "zone", "Zone", zone); ExportVar(package_name.c_str(), "zonehour", eqTime.hour - 1); ExportVar(package_name.c_str(), "zoneid", zone->GetZoneID()); ExportVar(package_name.c_str(), "zoneln", zone->GetLongName()); @@ -1755,10 +1682,14 @@ void PerlembParser::ExportEventVariables( ExportVar(package_name.c_str(), "doorid", data); ExportVar(package_name.c_str(), "version", zone->GetInstanceVersion()); - if (extra_pointers && extra_pointers->size() == 1) { + if (extra_pointers && extra_pointers->size() >= 1) { ExportVar(package_name.c_str(), "door", "Doors", std::any_cast(extra_pointers->at(0))); } + if (extra_pointers && extra_pointers->size() == 2) { + ExportVar(package_name.c_str(), "player", "Client", std::any_cast(extra_pointers->at(1))); + } + break; } @@ -1779,10 +1710,14 @@ void PerlembParser::ExportEventVariables( ); } - if (extra_pointers && extra_pointers->size() == 2) { + if (extra_pointers && extra_pointers->size() >= 2) { ExportVar(package_name.c_str(), "corpse", "Corpse", std::any_cast(extra_pointers->at(1))); } + if (extra_pointers && extra_pointers->size() == 3) { + ExportVar(package_name.c_str(), "player", "Client", std::any_cast(extra_pointers->at(2))); + } + break; } @@ -1849,7 +1784,7 @@ void PerlembParser::ExportEventVariables( ExportVar(package_name.c_str(), "picked_up_id", data); ExportVar(package_name.c_str(), "picked_up_entity_id", extra_data); - if (extra_pointers && extra_pointers->size() == 1) { + if (extra_pointers && extra_pointers->size() >= 1) { ExportVar( package_name.c_str(), "item", @@ -1858,6 +1793,10 @@ void PerlembParser::ExportEventVariables( ); } + if (extra_pointers && extra_pointers->size() == 2) { + ExportVar(package_name.c_str(), "player", "Client", std::any_cast(extra_pointers->at(1))); + } + break; } @@ -1870,6 +1809,11 @@ void PerlembParser::ExportEventVariables( case EVENT_POPUP_RESPONSE: { ExportVar(package_name.c_str(), "popupid", data); + + if (extra_pointers && extra_pointers->size() == 1) { + ExportVar(package_name.c_str(), "player", "Client", std::any_cast(extra_pointers->at(0))); + } + break; } @@ -2033,10 +1977,14 @@ void PerlembParser::ExportEventVariables( ExportVar(package_name.c_str(), "objectid", data); ExportVar(package_name.c_str(), "clicker_id", extra_data); - if (extra_pointers && extra_pointers->size() == 1) { + if (extra_pointers && extra_pointers->size() >= 1) { ExportVar(package_name.c_str(), "object", "Object", std::any_cast(extra_pointers->at(0))); } + if (extra_pointers && extra_pointers->size() == 2) { + ExportVar(package_name.c_str(), "player", "Client", std::any_cast(extra_pointers->at(1))); + } + break; } @@ -2112,6 +2060,13 @@ void PerlembParser::ExportEventVariables( ExportVar(package_name.c_str(), "killed_npc_id", !killed->IsMerc() && killed->IsNPC() ? killed->GetNPCTypeID() : 0); } } + + if (extra_pointers && extra_pointers->size() == 3) { + Mob* killer = std::any_cast(extra_pointers->at(2)); + if (killer) { + ExportVar(package_name.c_str(), "killer", "Mob", killer); + } + } break; } @@ -2139,10 +2094,21 @@ void PerlembParser::ExportEventVariables( } case EVENT_SPAWN_ZONE: { - ExportVar(package_name.c_str(), "spawned_entity_id", mob->GetID()); - ExportVar(package_name.c_str(), "spawned_bot_id", mob->IsBot() ? mob->CastToBot()->GetBotID() : 0); - ExportVar(package_name.c_str(), "spawned_npc_id", mob->IsNPC() ? mob->GetNPCTypeID() : 0); - ExportVar(package_name.c_str(), "spawned", "Mob", mob); + if (mob) { + ExportVar(package_name.c_str(), "spawned", "Mob", mob); + ExportVar(package_name.c_str(), "spawned_bot_id", mob->IsBot() ? mob->CastToBot()->GetBotID() : 0); + ExportVar(package_name.c_str(), "spawned_entity_id", mob->GetID()); + ExportVar(package_name.c_str(), "spawned_npc_id", mob->IsNPC() ? mob->GetNPCTypeID() : 0); + } + + if (extra_pointers && extra_pointers->size() == 1) { + NPC* spawn_npc = std::any_cast(extra_pointers->at(0)); + ExportVar(package_name.c_str(), "spawned", "NPC", spawn_npc); + ExportVar(package_name.c_str(), "spawned_bot_id", spawn_npc->IsBot() ? spawn_npc->CastToBot()->GetBotID() : 0); + ExportVar(package_name.c_str(), "spawned_entity_id", spawn_npc->GetID()); + ExportVar(package_name.c_str(), "spawned_npc_id", spawn_npc->IsNPC() ? spawn_npc->GetNPCTypeID() : 0); + } + break; } @@ -2383,6 +2349,7 @@ void PerlembParser::ExportEventVariables( } case EVENT_DESPAWN: { + ExportVar(package_name.c_str(), "despawned", "Mob", npc_mob); ExportVar(package_name.c_str(), "despawned_entity_id", npc_mob->GetID()); ExportVar(package_name.c_str(), "despawned_bot_id", npc_mob->IsBot() ? npc_mob->CastToBot()->GetBotID() : 0); ExportVar(package_name.c_str(), "despawned_merc_id", npc_mob->IsMerc() ? npc_mob->CastToMerc()->GetMercenaryID() : 0); @@ -2391,9 +2358,21 @@ void PerlembParser::ExportEventVariables( } case EVENT_DESPAWN_ZONE: { - ExportVar(package_name.c_str(), "despawned_entity_id", mob->GetID()); - ExportVar(package_name.c_str(), "despawned_bot_id", mob->IsBot() ? mob->CastToBot()->GetBotID() : 0); - ExportVar(package_name.c_str(), "despawned_npc_id", mob->IsNPC() ? mob->GetNPCTypeID() : 0); + if (mob) { + ExportVar(package_name.c_str(), "despawned", "Mob", mob); + ExportVar(package_name.c_str(), "despawned_bot_id", mob->IsBot() ? mob->CastToBot()->GetBotID() : 0); + ExportVar(package_name.c_str(), "despawned_entity_id", mob->GetID()); + ExportVar(package_name.c_str(), "despawned_npc_id", mob->IsNPC() ? mob->GetNPCTypeID() : 0); + } + + if (extra_pointers && extra_pointers->size() == 1) { + NPC* spawn_npc = std::any_cast(extra_pointers->at(0)); + ExportVar(package_name.c_str(), "despawned", "NPC", spawn_npc); + ExportVar(package_name.c_str(), "despawned_bot_id", spawn_npc->IsBot() ? spawn_npc->CastToBot()->GetBotID() : 0); + ExportVar(package_name.c_str(), "despawned_entity_id", spawn_npc->GetID()); + ExportVar(package_name.c_str(), "despawned_npc_id", spawn_npc->IsNPC() ? spawn_npc->GetNPCTypeID() : 0); + } + break; } @@ -2540,6 +2519,28 @@ void PerlembParser::ExportEventVariables( break; } + case EVENT_CONNECT: { + Seperator sep(data); + ExportVar(package_name.c_str(), "last_login", sep.arg[0]); + ExportVar(package_name.c_str(), "seconds_since_last_login", sep.arg[1]); + ExportVar(package_name.c_str(), "is_first_login", sep.arg[2]); + break; + } + + case EVENT_ENTER_ZONE: { + if (extra_pointers && extra_pointers->size() == 1) { + ExportVar(package_name.c_str(), "player", "Client", std::any_cast(extra_pointers->at(0))); + } + + break; + } + + case EVENT_PET_COMMAND: { + ExportVar(package_name.c_str(), "pet_command", extra_data); + ExportVar(package_name.c_str(), "pet_command_name", data); + break; + } + default: { break; } @@ -2637,6 +2638,7 @@ int PerlembParser::EventBot( nullptr, nullptr, mob, + nullptr, extra_data, false, extra_pointers @@ -2660,6 +2662,7 @@ int PerlembParser::EventGlobalBot( nullptr, nullptr, mob, + nullptr, extra_data, true, extra_pointers @@ -2757,6 +2760,7 @@ int PerlembParser::EventMerc( nullptr, nullptr, mob, + nullptr, extra_data, false, extra_pointers @@ -2780,6 +2784,127 @@ int PerlembParser::EventGlobalMerc( nullptr, nullptr, mob, + nullptr, + extra_data, + true, + extra_pointers + ); +} + +void PerlembParser::LoadZoneScript(std::string filename) +{ + if (!perl || zone_quest_status_ != questUnloaded) { + return; + } + + try { + perl->eval_file("qst_zone", filename.c_str()); + } catch (std::string e) { + AddError( + fmt::format( + "Error Compiling Zone Quest File [{}] Error [{}]", + filename, + e + ) + ); + + zone_quest_status_ = questFailedToLoad; + return; + } + + zone_quest_status_ = questLoaded; +} + +void PerlembParser::LoadGlobalZoneScript(std::string filename) +{ + if (!perl || global_zone_quest_status_ != questUnloaded) { + return; + } + + try { + perl->eval_file("qst_global_zone", filename.c_str()); + } catch (std::string e) { + AddError( + fmt::format( + "Error Compiling Global Zone Quest File [{}] Error [{}]", + filename, + e + ) + ); + + global_zone_quest_status_ = questFailedToLoad; + return; + } + + global_zone_quest_status_ = questLoaded; +} + +bool PerlembParser::ZoneHasQuestSub(QuestEventID event_id) +{ + if ( + !perl || + zone_quest_status_ != questLoaded || + event_id >= _LargestEventID + ) { + return false; + } + + return perl->SubExists("qst_zone", QuestEventSubroutines[event_id]); +} + +bool PerlembParser::GlobalZoneHasQuestSub(QuestEventID event_id) +{ + if ( + !perl || + global_zone_quest_status_ != questLoaded || + event_id >= _LargestEventID + ) { + return false; + } + + return perl->SubExists("qst_global_zone", QuestEventSubroutines[event_id]); +} + +int PerlembParser::EventZone( + QuestEventID event_id, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector* extra_pointers +) +{ + return EventCommon( + event_id, + 0, + data.c_str(), + nullptr, + nullptr, + nullptr, + nullptr, + zone, + extra_data, + false, + extra_pointers + ); +} + +int PerlembParser::EventGlobalZone( + QuestEventID event_id, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector* extra_pointers +) +{ + return EventCommon( + event_id, + 0, + data.c_str(), + nullptr, + nullptr, + nullptr, + nullptr, + zone, extra_data, true, extra_pointers diff --git a/zone/embparser.h b/zone/embparser.h index dd4ffe8d8..5817ce15b 100644 --- a/zone/embparser.h +++ b/zone/embparser.h @@ -41,6 +41,23 @@ typedef enum { questFailedToLoad } PerlQuestStatus; +enum class QuestType { + Bot, + BotGlobal, + Item, + ItemGlobal, + Merc, + MercGlobal, + NPC, + NPCGlobal, + Player, + PlayerGlobal, + Spell, + SpellGlobal, + Zone, + ZoneGlobal +}; + class PerlembParser : public QuestInterface { public: PerlembParser(); @@ -136,6 +153,22 @@ public: std::vector* extra_pointers ); + virtual int EventZone( + QuestEventID event_id, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector* extra_pointers + ); + + virtual int EventGlobalZone( + QuestEventID event_id, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector* extra_pointers + ); + virtual bool HasQuestSub(uint32 npc_id, QuestEventID event_id); virtual bool HasGlobalQuestSub(QuestEventID event_id); virtual bool PlayerHasQuestSub(QuestEventID event_id); @@ -146,6 +179,8 @@ public: virtual bool GlobalBotHasQuestSub(QuestEventID event_id); virtual bool MercHasQuestSub(QuestEventID event_id); virtual bool GlobalMercHasQuestSub(QuestEventID event_id); + virtual bool ZoneHasQuestSub(QuestEventID event_id); + virtual bool GlobalZoneHasQuestSub(QuestEventID event_id); virtual void LoadNPCScript(std::string filename, int npc_id); virtual void LoadGlobalNPCScript(std::string filename); @@ -157,6 +192,8 @@ public: virtual void LoadGlobalBotScript(std::string filename); virtual void LoadMercScript(std::string filename); virtual void LoadGlobalMercScript(std::string filename); + virtual void LoadZoneScript(std::string filename); + virtual void LoadGlobalZoneScript(std::string filename); virtual void AddVar(std::string name, std::string val); virtual std::string GetVar(std::string name); @@ -182,6 +219,7 @@ private: EQ::ItemInstance* inst, const SPDat_Spell_Struct* spell, Mob* mob, + Zone* zone, uint32 extra_data, bool is_global, std::vector* extra_pointers @@ -194,59 +232,34 @@ private: Mob* other, Mob* mob, EQ::ItemInstance* inst, - const SPDat_Spell_Struct* spell + const SPDat_Spell_Struct* spell, + Zone* zone ); void MapFunctions(); - void GetQuestTypes( - bool& is_player_quest, - bool& is_global_player_quest, - bool& is_bot_quest, - bool& is_global_bot_quest, - bool& is_merc_quest, - bool& is_global_merc_quest, - bool& is_global_npc_quest, - bool& is_item_quest, - bool& is_spell_quest, + QuestType GetQuestTypes( QuestEventID event, Mob* npc_mob, EQ::ItemInstance* inst, Mob* mob, + Zone* zone, bool is_global ); - void GetQuestPackageName( - bool& is_player_quest, - bool& is_global_player_quest, - bool& is_bot_quest, - bool& is_global_bot_quest, - bool& is_merc_quest, - bool& is_global_merc_quest, - bool& is_global_npc_quest, - bool& is_item_quest, - bool& is_spell_quest, - std::string& package_name, + std::string GetQuestPackageName( + QuestType quest_type, QuestEventID event, uint32 object_id, const char* data, Mob* npc_mob, - EQ::ItemInstance* inst, - bool is_global + EQ::ItemInstance* inst ); void ExportCharID(const std::string& package_name, int& char_id, Mob* npc_mob, Mob* mob); void ExportQGlobals( - bool is_player_quest, - bool is_global_player_quest, - bool is_bot_quest, - bool is_global_bot_quest, - bool is_merc_quest, - bool is_global_merc_quest, - bool is_global_npc_quest, - bool is_item_quest, - bool is_spell_quest, + QuestType quest_type, std::string& package_name, Mob* npc_mob, Mob* mob, @@ -254,15 +267,7 @@ private: ); void ExportMobVariables( - bool is_player_quest, - bool is_global_player_quest, - bool is_bot_quest, - bool is_global_bot_quest, - bool is_merc_quest, - bool is_global_merc_quest, - bool is_global_npc_quest, - bool is_item_quest, - bool is_spell_quest, + QuestType quest_type, std::string& package_name, Mob* mob, Mob* npc_mob @@ -295,6 +300,8 @@ private: PerlQuestStatus global_bot_quest_status_; PerlQuestStatus merc_quest_status_; PerlQuestStatus global_merc_quest_status_; + PerlQuestStatus zone_quest_status_; + PerlQuestStatus global_zone_quest_status_; SV* _empty_sv; diff --git a/zone/embparser_api.cpp b/zone/embparser_api.cpp index d18796a4e..feedab7fd 100644 --- a/zone/embparser_api.cpp +++ b/zone/embparser_api.cpp @@ -27,13 +27,13 @@ #include "../common/misc_functions.h" #include "dialogue_window.h" +#include "dynamic_zone.h" #include "embperl.h" #include "entity.h" -#include "expedition.h" #include "queryserv.h" #include "questmgr.h" #include "zone.h" -#include "data_bucket.h" +#include "../common/data_bucket.h" #include "../common/events/player_event_logs.h" #include "worldserver.h" @@ -278,25 +278,25 @@ int Perl__getinventoryslotid(std::string identifier) else if (identifier == "generalbags.begin") result = EQ::invbag::GENERAL_BAGS_BEGIN; else if (identifier == "generalbags.end") result = EQ::invbag::GENERAL_BAGS_END; else if (identifier == "general1bag.begin") result = EQ::invbag::GENERAL_BAGS_BEGIN; - else if (identifier == "general1bag.end") result = EQ::invbag::GENERAL_BAGS_BEGIN + 9; - else if (identifier == "general2bag.begin") result = EQ::invbag::GENERAL_BAGS_BEGIN + 10; - else if (identifier == "general2bag.end") result = EQ::invbag::GENERAL_BAGS_BEGIN + 19; - else if (identifier == "general3bag.begin") result = EQ::invbag::GENERAL_BAGS_BEGIN + 20; - else if (identifier == "general3bag.end") result = EQ::invbag::GENERAL_BAGS_BEGIN + 29; - else if (identifier == "general4bag.begin") result = EQ::invbag::GENERAL_BAGS_BEGIN + 30; - else if (identifier == "general4bag.end") result = EQ::invbag::GENERAL_BAGS_BEGIN + 39; - else if (identifier == "general5bag.begin") result = EQ::invbag::GENERAL_BAGS_BEGIN + 40; - else if (identifier == "general5bag.end") result = EQ::invbag::GENERAL_BAGS_BEGIN + 49; - else if (identifier == "general6bag.begin") result = EQ::invbag::GENERAL_BAGS_BEGIN + 50; - else if (identifier == "general6bag.end") result = EQ::invbag::GENERAL_BAGS_BEGIN + 59; - else if (identifier == "general7bag.begin") result = EQ::invbag::GENERAL_BAGS_BEGIN + 60; - else if (identifier == "general7bag.end") result = EQ::invbag::GENERAL_BAGS_BEGIN + 69; - else if (identifier == "general8bag.begin") result = EQ::invbag::GENERAL_BAGS_BEGIN + 70; - else if (identifier == "general8bag.end") result = EQ::invbag::GENERAL_BAGS_BEGIN + 79; - else if (identifier == "general9bag.begin") result = EQ::invbag::GENERAL_BAGS_BEGIN + 80; - else if (identifier == "general9bag.end") result = EQ::invbag::GENERAL_BAGS_BEGIN + 89; - else if (identifier == "general10bag.begin") result = EQ::invbag::GENERAL_BAGS_BEGIN + 90; - else if (identifier == "general10bag.end") result = EQ::invbag::GENERAL_BAGS_BEGIN + 99; + else if (identifier == "general1bag.end") result = EQ::invbag::GENERAL_BAGS_BEGIN + (EQ::invbag::SLOT_COUNT - 1); + else if (identifier == "general2bag.begin") result = EQ::invbag::GENERAL_BAGS_BEGIN + EQ::invbag::SLOT_COUNT; + else if (identifier == "general2bag.end") result = EQ::invbag::GENERAL_BAGS_BEGIN + ((EQ::invbag::SLOT_COUNT * 2) - 1); + else if (identifier == "general3bag.begin") result = EQ::invbag::GENERAL_BAGS_BEGIN + (EQ::invbag::SLOT_COUNT * 2); + else if (identifier == "general3bag.end") result = EQ::invbag::GENERAL_BAGS_BEGIN + ((EQ::invbag::SLOT_COUNT * 3) - 1); + else if (identifier == "general4bag.begin") result = EQ::invbag::GENERAL_BAGS_BEGIN + (EQ::invbag::SLOT_COUNT * 3); + else if (identifier == "general4bag.end") result = EQ::invbag::GENERAL_BAGS_BEGIN + ((EQ::invbag::SLOT_COUNT * 4) - 1); + else if (identifier == "general5bag.begin") result = EQ::invbag::GENERAL_BAGS_BEGIN + (EQ::invbag::SLOT_COUNT * 4); + else if (identifier == "general5bag.end") result = EQ::invbag::GENERAL_BAGS_BEGIN + ((EQ::invbag::SLOT_COUNT * 5) - 1); + else if (identifier == "general6bag.begin") result = EQ::invbag::GENERAL_BAGS_BEGIN + (EQ::invbag::SLOT_COUNT * 5); + else if (identifier == "general6bag.end") result = EQ::invbag::GENERAL_BAGS_BEGIN + ((EQ::invbag::SLOT_COUNT * 6) - 1); + else if (identifier == "general7bag.begin") result = EQ::invbag::GENERAL_BAGS_BEGIN + (EQ::invbag::SLOT_COUNT * 6); + else if (identifier == "general7bag.end") result = EQ::invbag::GENERAL_BAGS_BEGIN + ((EQ::invbag::SLOT_COUNT * 7) - 1); + else if (identifier == "general8bag.begin") result = EQ::invbag::GENERAL_BAGS_BEGIN + (EQ::invbag::SLOT_COUNT * 7); + else if (identifier == "general8bag.end") result = EQ::invbag::GENERAL_BAGS_BEGIN + ((EQ::invbag::SLOT_COUNT * 8) - 1); + else if (identifier == "general9bag.begin") result = EQ::invbag::GENERAL_BAGS_BEGIN + (EQ::invbag::SLOT_COUNT * 8); + else if (identifier == "general9bag.end") result = EQ::invbag::GENERAL_BAGS_BEGIN + ((EQ::invbag::SLOT_COUNT * 9) - 1); + else if (identifier == "general10bag.begin") result = EQ::invbag::GENERAL_BAGS_BEGIN + (EQ::invbag::SLOT_COUNT * 9); + else if (identifier == "general10bag.end") result = EQ::invbag::GENERAL_BAGS_BEGIN + ((EQ::invbag::SLOT_COUNT * 10) - 1); else if (identifier == "cursorbag.begin") result = EQ::invbag::CURSOR_BAG_BEGIN; else if (identifier == "cursorbag.end") result = EQ::invbag::CURSOR_BAG_END; else if (identifier == "bank.begin") result = EQ::invslot::BANK_BEGIN; @@ -754,7 +754,7 @@ void Perl__setsky(uint8 new_sky) void Perl__setguild(uint32_t guild_id, uint8_t guild_rank_id) { - quest_manager.setguild(guild_id, guild_rank_id); + quest_manager.SetGuild(guild_id, guild_rank_id); } void Perl__createguild(const char* guild_name, const char* leader_name) @@ -1271,6 +1271,16 @@ void Perl__failtask(int task_id) quest_manager.failtask(task_id); } +bool Perl__completetask(int task_id) +{ + return quest_manager.completetask(task_id); +} + +bool Perl__uncompletetask(int task_id) +{ + return quest_manager.uncompletetask(task_id); +} + int Perl__tasktimeleft(int task_id) { return quest_manager.tasktimeleft(task_id); @@ -1446,7 +1456,7 @@ int Perl__collectitems(uint32_t item_id, bool remove_item) return quest_manager.collectitems(item_id, remove_item); } -int Perl__countitem(uint32_t item_id) +uint32 Perl__countitem(uint32_t item_id) { return quest_manager.countitem(item_id); } @@ -2430,11 +2440,6 @@ void Perl__qs_send_query(std::string query) QServ->SendQuery(std::move(query)); } -void Perl__qs_player_event(int char_id, std::string message) -{ - QServ->PlayerLogEvent(Player_Log_Quest, char_id, message); -} - void Perl__log(int category, const char* message) { if (category < Logs::None || category >= Logs::MaxCategoryID) @@ -2505,324 +2510,324 @@ bool Perl__delete_data(std::string bucket_key) bool Perl__IsClassicEnabled() { - return content_service.IsClassicEnabled(); + return WorldContentService::Instance()->IsClassicEnabled(); } bool Perl__IsTheRuinsOfKunarkEnabled() { - return content_service.IsTheRuinsOfKunarkEnabled(); + return WorldContentService::Instance()->IsTheRuinsOfKunarkEnabled(); } bool Perl__IsTheScarsOfVeliousEnabled() { - return content_service.IsTheScarsOfVeliousEnabled(); + return WorldContentService::Instance()->IsTheScarsOfVeliousEnabled(); } bool Perl__IsTheShadowsOfLuclinEnabled() { - return content_service.IsTheShadowsOfLuclinEnabled(); + return WorldContentService::Instance()->IsTheShadowsOfLuclinEnabled(); } bool Perl__IsThePlanesOfPowerEnabled() { - return content_service.IsThePlanesOfPowerEnabled(); + return WorldContentService::Instance()->IsThePlanesOfPowerEnabled(); } bool Perl__IsTheLegacyOfYkeshaEnabled() { - return content_service.IsTheLegacyOfYkeshaEnabled(); + return WorldContentService::Instance()->IsTheLegacyOfYkeshaEnabled(); } bool Perl__IsLostDungeonsOfNorrathEnabled() { - return content_service.IsLostDungeonsOfNorrathEnabled(); + return WorldContentService::Instance()->IsLostDungeonsOfNorrathEnabled(); } bool Perl__IsGatesOfDiscordEnabled() { - return content_service.IsGatesOfDiscordEnabled(); + return WorldContentService::Instance()->IsGatesOfDiscordEnabled(); } bool Perl__IsOmensOfWarEnabled() { - return content_service.IsOmensOfWarEnabled(); + return WorldContentService::Instance()->IsOmensOfWarEnabled(); } bool Perl__IsDragonsOfNorrathEnabled() { - return content_service.IsDragonsOfNorrathEnabled(); + return WorldContentService::Instance()->IsDragonsOfNorrathEnabled(); } bool Perl__IsDepthsOfDarkhollowEnabled() { - return content_service.IsDepthsOfDarkhollowEnabled(); + return WorldContentService::Instance()->IsDepthsOfDarkhollowEnabled(); } bool Perl__IsProphecyOfRoEnabled() { - return content_service.IsProphecyOfRoEnabled(); + return WorldContentService::Instance()->IsProphecyOfRoEnabled(); } bool Perl__IsTheSerpentsSpineEnabled() { - return content_service.IsTheSerpentsSpineEnabled(); + return WorldContentService::Instance()->IsTheSerpentsSpineEnabled(); } bool Perl__IsTheBuriedSeaEnabled() { - return content_service.IsTheBuriedSeaEnabled(); + return WorldContentService::Instance()->IsTheBuriedSeaEnabled(); } bool Perl__IsSecretsOfFaydwerEnabled() { - return content_service.IsSecretsOfFaydwerEnabled(); + return WorldContentService::Instance()->IsSecretsOfFaydwerEnabled(); } bool Perl__IsSeedsOfDestructionEnabled() { - return content_service.IsSeedsOfDestructionEnabled(); + return WorldContentService::Instance()->IsSeedsOfDestructionEnabled(); } bool Perl__IsUnderfootEnabled() { - return content_service.IsUnderfootEnabled(); + return WorldContentService::Instance()->IsUnderfootEnabled(); } bool Perl__IsHouseOfThuleEnabled() { - return content_service.IsHouseOfThuleEnabled(); + return WorldContentService::Instance()->IsHouseOfThuleEnabled(); } bool Perl__IsVeilOfAlarisEnabled() { - return content_service.IsVeilOfAlarisEnabled(); + return WorldContentService::Instance()->IsVeilOfAlarisEnabled(); } bool Perl__IsRainOfFearEnabled() { - return content_service.IsRainOfFearEnabled(); + return WorldContentService::Instance()->IsRainOfFearEnabled(); } bool Perl__IsCallOfTheForsakenEnabled() { - return content_service.IsCallOfTheForsakenEnabled(); + return WorldContentService::Instance()->IsCallOfTheForsakenEnabled(); } bool Perl__IsTheDarkenedSeaEnabled() { - return content_service.IsTheDarkenedSeaEnabled(); + return WorldContentService::Instance()->IsTheDarkenedSeaEnabled(); } bool Perl__IsTheBrokenMirrorEnabled() { - return content_service.IsTheBrokenMirrorEnabled(); + return WorldContentService::Instance()->IsTheBrokenMirrorEnabled(); } bool Perl__IsEmpiresOfKunarkEnabled() { - return content_service.IsEmpiresOfKunarkEnabled(); + return WorldContentService::Instance()->IsEmpiresOfKunarkEnabled(); } bool Perl__IsRingOfScaleEnabled() { - return content_service.IsRingOfScaleEnabled(); + return WorldContentService::Instance()->IsRingOfScaleEnabled(); } bool Perl__IsTheBurningLandsEnabled() { - return content_service.IsTheBurningLandsEnabled(); + return WorldContentService::Instance()->IsTheBurningLandsEnabled(); } bool Perl__IsTormentOfVeliousEnabled() { - return content_service.IsTormentOfVeliousEnabled(); + return WorldContentService::Instance()->IsTormentOfVeliousEnabled(); } bool Perl__IsCurrentExpansionClassic() { - return content_service.IsCurrentExpansionClassic(); + return WorldContentService::Instance()->IsCurrentExpansionClassic(); } bool Perl__IsCurrentExpansionTheRuinsOfKunark() { - return content_service.IsCurrentExpansionTheRuinsOfKunark(); + return WorldContentService::Instance()->IsCurrentExpansionTheRuinsOfKunark(); } bool Perl__IsCurrentExpansionTheScarsOfVelious() { - return content_service.IsCurrentExpansionTheScarsOfVelious(); + return WorldContentService::Instance()->IsCurrentExpansionTheScarsOfVelious(); } bool Perl__IsCurrentExpansionTheShadowsOfLuclin() { - return content_service.IsCurrentExpansionTheShadowsOfLuclin(); + return WorldContentService::Instance()->IsCurrentExpansionTheShadowsOfLuclin(); } bool Perl__IsCurrentExpansionThePlanesOfPower() { - return content_service.IsCurrentExpansionThePlanesOfPower(); + return WorldContentService::Instance()->IsCurrentExpansionThePlanesOfPower(); } bool Perl__IsCurrentExpansionTheLegacyOfYkesha() { - return content_service.IsCurrentExpansionTheLegacyOfYkesha(); + return WorldContentService::Instance()->IsCurrentExpansionTheLegacyOfYkesha(); } bool Perl__IsCurrentExpansionLostDungeonsOfNorrath() { - return content_service.IsCurrentExpansionLostDungeonsOfNorrath(); + return WorldContentService::Instance()->IsCurrentExpansionLostDungeonsOfNorrath(); } bool Perl__IsCurrentExpansionGatesOfDiscord() { - return content_service.IsCurrentExpansionGatesOfDiscord(); + return WorldContentService::Instance()->IsCurrentExpansionGatesOfDiscord(); } bool Perl__IsCurrentExpansionOmensOfWar() { - return content_service.IsCurrentExpansionOmensOfWar(); + return WorldContentService::Instance()->IsCurrentExpansionOmensOfWar(); } bool Perl__IsCurrentExpansionDragonsOfNorrath() { - return content_service.IsCurrentExpansionDragonsOfNorrath(); + return WorldContentService::Instance()->IsCurrentExpansionDragonsOfNorrath(); } bool Perl__IsCurrentExpansionDepthsOfDarkhollow() { - return content_service.IsCurrentExpansionDepthsOfDarkhollow(); + return WorldContentService::Instance()->IsCurrentExpansionDepthsOfDarkhollow(); } bool Perl__IsCurrentExpansionProphecyOfRo() { - return content_service.IsCurrentExpansionProphecyOfRo(); + return WorldContentService::Instance()->IsCurrentExpansionProphecyOfRo(); } bool Perl__IsCurrentExpansionTheSerpentsSpine() { - return content_service.IsCurrentExpansionTheSerpentsSpine(); + return WorldContentService::Instance()->IsCurrentExpansionTheSerpentsSpine(); } bool Perl__IsCurrentExpansionTheBuriedSea() { - return content_service.IsCurrentExpansionTheBuriedSea(); + return WorldContentService::Instance()->IsCurrentExpansionTheBuriedSea(); } bool Perl__IsCurrentExpansionSecretsOfFaydwer() { - return content_service.IsCurrentExpansionSecretsOfFaydwer(); + return WorldContentService::Instance()->IsCurrentExpansionSecretsOfFaydwer(); } bool Perl__IsCurrentExpansionSeedsOfDestruction() { - return content_service.IsCurrentExpansionSeedsOfDestruction(); + return WorldContentService::Instance()->IsCurrentExpansionSeedsOfDestruction(); } bool Perl__IsCurrentExpansionUnderfoot() { - return content_service.IsCurrentExpansionUnderfoot(); + return WorldContentService::Instance()->IsCurrentExpansionUnderfoot(); } bool Perl__IsCurrentExpansionHouseOfThule() { - return content_service.IsCurrentExpansionHouseOfThule(); + return WorldContentService::Instance()->IsCurrentExpansionHouseOfThule(); } bool Perl__IsCurrentExpansionVeilOfAlaris() { - return content_service.IsCurrentExpansionVeilOfAlaris(); + return WorldContentService::Instance()->IsCurrentExpansionVeilOfAlaris(); } bool Perl__IsCurrentExpansionRainOfFear() { - return content_service.IsCurrentExpansionRainOfFear(); + return WorldContentService::Instance()->IsCurrentExpansionRainOfFear(); } bool Perl__IsCurrentExpansionCallOfTheForsaken() { - return content_service.IsCurrentExpansionCallOfTheForsaken(); + return WorldContentService::Instance()->IsCurrentExpansionCallOfTheForsaken(); } bool Perl__IsCurrentExpansionTheDarkenedSea() { - return content_service.IsCurrentExpansionTheDarkenedSea(); + return WorldContentService::Instance()->IsCurrentExpansionTheDarkenedSea(); } bool Perl__IsCurrentExpansionTheBrokenMirror() { - return content_service.IsCurrentExpansionTheBrokenMirror(); + return WorldContentService::Instance()->IsCurrentExpansionTheBrokenMirror(); } bool Perl__IsCurrentExpansionEmpiresOfKunark() { - return content_service.IsCurrentExpansionEmpiresOfKunark(); + return WorldContentService::Instance()->IsCurrentExpansionEmpiresOfKunark(); } bool Perl__IsCurrentExpansionRingOfScale() { - return content_service.IsCurrentExpansionRingOfScale(); + return WorldContentService::Instance()->IsCurrentExpansionRingOfScale(); } bool Perl__IsCurrentExpansionTheBurningLands() { - return content_service.IsCurrentExpansionTheBurningLands(); + return WorldContentService::Instance()->IsCurrentExpansionTheBurningLands(); } bool Perl__IsCurrentExpansionTormentOfVelious() { - return content_service.IsCurrentExpansionTormentOfVelious(); + return WorldContentService::Instance()->IsCurrentExpansionTormentOfVelious(); } bool Perl__IsContentFlagEnabled(std::string flag_name) { - return content_service.IsContentFlagEnabled(flag_name); + return WorldContentService::Instance()->IsContentFlagEnabled(flag_name); } void Perl__SetContentFlag(std::string flag_name, bool enabled) { - content_service.SetContentFlag(flag_name, enabled); + WorldContentService::Instance()->SetContentFlag(flag_name, enabled); zone->ReloadContentFlags(); } -Expedition* Perl__get_expedition() +DynamicZone* Perl__get_expedition() { if (zone && zone->GetInstanceID() != 0) { - return Expedition::FindCachedExpeditionByZoneInstance(zone->GetZoneID(), zone->GetInstanceID()); + return DynamicZone::FindExpeditionByZone(zone->GetZoneID(), zone->GetInstanceID()); } return nullptr; } -Expedition* Perl__get_expedition_by_char_id(uint32 char_id) +DynamicZone* Perl__get_expedition_by_char_id(uint32 char_id) { - return Expedition::FindCachedExpeditionByCharacterID(char_id); + return DynamicZone::FindExpeditionByCharacter(char_id); } -Expedition* Perl__get_expedition_by_dz_id(uint32 dz_id) +DynamicZone* Perl__get_expedition_by_dz_id(uint32 dz_id) { - return Expedition::FindCachedExpeditionByDynamicZoneID(dz_id); + return DynamicZone::FindDynamicZoneByID(dz_id, DynamicZoneType::Expedition); } -Expedition* Perl__get_expedition_by_zone_instance(uint32 zone_id, uint32 instance_id) +DynamicZone* Perl__get_expedition_by_zone_instance(uint32 zone_id, uint32 instance_id) { - return Expedition::FindCachedExpeditionByZoneInstance(zone_id, instance_id); + return DynamicZone::FindExpeditionByZone(zone_id, instance_id); } perl::reference Perl__get_expedition_lockout_by_char_id(uint32 char_id, std::string expedition_name, std::string event_name) { perl::hash table; - auto lockouts = Expedition::GetExpeditionLockoutsByCharacterID(char_id); + auto lockouts = DynamicZone::GetCharacterLockouts(char_id); - auto it = std::find_if(lockouts.begin(), lockouts.end(), [&](const ExpeditionLockoutTimer& lockout) { - return lockout.IsSameLockout(expedition_name, event_name); + auto it = std::find_if(lockouts.begin(), lockouts.end(), [&](const DzLockout& lockout) { + return lockout.IsSame(expedition_name, event_name); }); if (it != lockouts.end()) { table["remaining"] = it->GetSecondsRemaining(); - table["uuid"] = it->GetExpeditionUUID(); + table["uuid"] = it->UUID(); } return perl::reference(table); @@ -2832,23 +2837,23 @@ perl::reference Perl__get_expedition_lockouts_by_char_id(uint32 char_id) { perl::hash table; - auto lockouts = Expedition::GetExpeditionLockoutsByCharacterID(char_id); + auto lockouts = DynamicZone::GetCharacterLockouts(char_id); for (const auto& lockout : lockouts) { - if (!table.exists(lockout.GetExpeditionName())) + if (!table.exists(lockout.DzName())) { - table[lockout.GetExpeditionName()] = perl::reference(perl::hash()); + table[lockout.DzName()] = perl::reference(perl::hash()); } - perl::hash expedition_table = table[lockout.GetExpeditionName()]; - if (!expedition_table.exists(lockout.GetEventName())) + perl::hash expedition_table = table[lockout.DzName()]; + if (!expedition_table.exists(lockout.Event())) { - expedition_table[lockout.GetEventName()] = perl::reference(perl::hash()); + expedition_table[lockout.Event()] = perl::reference(perl::hash()); } - perl::hash event_table = expedition_table[lockout.GetEventName()]; + perl::hash event_table = expedition_table[lockout.Event()]; event_table["remaining"] = lockout.GetSecondsRemaining(); - event_table["uuid"] = lockout.GetExpeditionUUID(); + event_table["uuid"] = lockout.UUID(); } return perl::reference(table); @@ -2858,18 +2863,18 @@ perl::reference Perl__get_expedition_lockouts_by_char_id(uint32 char_id, std::st { perl::hash table; - auto lockouts = Expedition::GetExpeditionLockoutsByCharacterID(char_id); + auto lockouts = DynamicZone::GetCharacterLockouts(char_id); for (const auto& lockout : lockouts) { - if (lockout.GetExpeditionName() == expedition_name) + if (lockout.DzName() == expedition_name) { - if (!table.exists(lockout.GetEventName())) + if (!table.exists(lockout.Event())) { - table[lockout.GetEventName()] = perl::reference(perl::hash()); + table[lockout.Event()] = perl::reference(perl::hash()); } - perl::hash event_table = table[lockout.GetEventName()]; + perl::hash event_table = table[lockout.Event()]; event_table["remaining"] = lockout.GetSecondsRemaining(); - event_table["uuid"] = lockout.GetExpeditionUUID(); + event_table["uuid"] = lockout.UUID(); } } @@ -2878,39 +2883,39 @@ perl::reference Perl__get_expedition_lockouts_by_char_id(uint32 char_id, std::st void Perl__add_expedition_lockout_all_clients(std::string expedition_name, std::string event_name, uint32 seconds) { - auto lockout = ExpeditionLockoutTimer::CreateLockout(expedition_name, event_name, seconds); - Expedition::AddLockoutClients(lockout); + auto lockout = DzLockout::Create(expedition_name, event_name, seconds); + DynamicZone::AddClientsLockout(lockout); } void Perl__add_expedition_lockout_all_clients(std::string expedition_name, std::string event_name, uint32 seconds, std::string uuid) { - auto lockout = ExpeditionLockoutTimer::CreateLockout(expedition_name, event_name, seconds, uuid); - Expedition::AddLockoutClients(lockout); + auto lockout = DzLockout::Create(expedition_name, event_name, seconds, uuid); + DynamicZone::AddClientsLockout(lockout); } void Perl__add_expedition_lockout_by_char_id(uint32 char_id, std::string expedition_name, std::string event_name, uint32 seconds) { - Expedition::AddLockoutByCharacterID(char_id, expedition_name, event_name, seconds); + DynamicZone::AddCharacterLockout(char_id, expedition_name, event_name, seconds); } void Perl__add_expedition_lockout_by_char_id(uint32 char_id, std::string expedition_name, std::string event_name, uint32 seconds, std::string uuid) { - Expedition::AddLockoutByCharacterID(char_id, expedition_name, event_name, seconds, uuid); + DynamicZone::AddCharacterLockout(char_id, expedition_name, event_name, seconds, uuid); } void Perl__remove_expedition_lockout_by_char_id(uint32 char_id, std::string expedition_name, std::string event_name) { - Expedition::RemoveLockoutsByCharacterID(char_id, expedition_name, event_name); + DynamicZone::RemoveCharacterLockouts(char_id, expedition_name, event_name); } void Perl__remove_all_expedition_lockouts_by_char_id(uint32 char_id) { - Expedition::RemoveLockoutsByCharacterID(char_id); + DynamicZone::RemoveCharacterLockouts(char_id); } void Perl__remove_all_expedition_lockouts_by_char_id(uint32 char_id, std::string expedition_name) { - Expedition::RemoveLockoutsByCharacterID(char_id, expedition_name); + DynamicZone::RemoveCharacterLockouts(char_id, expedition_name); } EQ::ItemInstance* Perl__createitem(uint32 item_id) @@ -5120,637 +5125,627 @@ void Perl__send_player_handin_event() float Perl__GetZoneSafeX(uint32 zone_id) { - return zone_store.GetZoneSafeCoordinates(zone_id).x; + return ZoneStore::Instance()->GetZoneSafeCoordinates(zone_id).x; } float Perl__GetZoneSafeX(uint32 zone_id, int version) { - return zone_store.GetZoneSafeCoordinates(zone_id, version).x; + return ZoneStore::Instance()->GetZoneSafeCoordinates(zone_id, version).x; } float Perl__GetZoneSafeY(uint32 zone_id) { - return zone_store.GetZoneSafeCoordinates(zone_id).y; + return ZoneStore::Instance()->GetZoneSafeCoordinates(zone_id).y; } float Perl__GetZoneSafeY(uint32 zone_id, int version) { - return zone_store.GetZoneSafeCoordinates(zone_id, version).y; + return ZoneStore::Instance()->GetZoneSafeCoordinates(zone_id, version).y; } float Perl__GetZoneSafeZ(uint32 zone_id) { - return zone_store.GetZoneSafeCoordinates(zone_id).z; + return ZoneStore::Instance()->GetZoneSafeCoordinates(zone_id).z; } float Perl__GetZoneSafeZ(uint32 zone_id, int version) { - return zone_store.GetZoneSafeCoordinates(zone_id, version).z; + return ZoneStore::Instance()->GetZoneSafeCoordinates(zone_id, version).z; } float Perl__GetZoneSafeHeading(uint32 zone_id) { - return zone_store.GetZoneSafeCoordinates(zone_id).w; + return ZoneStore::Instance()->GetZoneSafeCoordinates(zone_id).w; } float Perl__GetZoneSafeHeading(uint32 zone_id, int version) { - return zone_store.GetZoneSafeCoordinates(zone_id, version).w; + return ZoneStore::Instance()->GetZoneSafeCoordinates(zone_id, version).w; } float Perl__GetZoneGraveyardID(uint32 zone_id) { - return zone_store.GetZoneGraveyardID(zone_id); + return ZoneStore::Instance()->GetZoneGraveyardID(zone_id); } float Perl__GetZoneGraveyardID(uint32 zone_id, int version) { - return zone_store.GetZoneGraveyardID(zone_id, version); + return ZoneStore::Instance()->GetZoneGraveyardID(zone_id, version); } uint8 Perl__GetZoneMinimumLevel(uint32 zone_id) { - return zone_store.GetZoneMinimumLevel(zone_id); + return ZoneStore::Instance()->GetZoneMinimumLevel(zone_id); } uint8 Perl__GetZoneMinimumLevel(uint32 zone_id, int version) { - return zone_store.GetZoneMinimumLevel(zone_id, version); + return ZoneStore::Instance()->GetZoneMinimumLevel(zone_id, version); } uint8 Perl__GetZoneMaximumLevel(uint32 zone_id) { - return zone_store.GetZoneMaximumLevel(zone_id); + return ZoneStore::Instance()->GetZoneMaximumLevel(zone_id); } uint8 Perl__GetZoneMaximumLevel(uint32 zone_id, int version) { - return zone_store.GetZoneMaximumLevel(zone_id, version); + return ZoneStore::Instance()->GetZoneMaximumLevel(zone_id, version); } uint8 Perl__GetZoneMinimumStatus(uint32 zone_id) { - return zone_store.GetZoneMinimumStatus(zone_id); + return ZoneStore::Instance()->GetZoneMinimumStatus(zone_id); } uint8 Perl__GetZoneMinimumStatus(uint32 zone_id, int version) { - return zone_store.GetZoneMinimumStatus(zone_id, version); + return ZoneStore::Instance()->GetZoneMinimumStatus(zone_id, version); } int Perl__GetZoneTimeZone(uint32 zone_id) { - return zone_store.GetZoneTimeZone(zone_id); + return ZoneStore::Instance()->GetZoneTimeZone(zone_id); } int Perl__GetZoneTimeZone(uint32 zone_id, int version) { - return zone_store.GetZoneTimeZone(zone_id, version); + return ZoneStore::Instance()->GetZoneTimeZone(zone_id, version); } int Perl__GetZoneMaximumPlayers(uint32 zone_id) { - return zone_store.GetZoneMaximumPlayers(zone_id); + return ZoneStore::Instance()->GetZoneMaximumPlayers(zone_id); } int Perl__GetZoneMaximumPlayers(uint32 zone_id, int version) { - return zone_store.GetZoneMaximumPlayers(zone_id, version); + return ZoneStore::Instance()->GetZoneMaximumPlayers(zone_id, version); } uint32 Perl__GetZoneRuleSet(uint32 zone_id) { - return zone_store.GetZoneRuleSet(zone_id); + return ZoneStore::Instance()->GetZoneRuleSet(zone_id); } uint32 Perl__GetZoneRuleSet(uint32 zone_id, int version) { - return zone_store.GetZoneRuleSet(zone_id, version); + return ZoneStore::Instance()->GetZoneRuleSet(zone_id, version); } std::string Perl__GetZoneNote(uint32 zone_id) { - return zone_store.GetZoneNote(zone_id); + return ZoneStore::Instance()->GetZoneNote(zone_id); } std::string Perl__GetZoneNote(uint32 zone_id, int version) { - return zone_store.GetZoneNote(zone_id, version); + return ZoneStore::Instance()->GetZoneNote(zone_id, version); } float Perl__GetZoneUnderworld(uint32 zone_id) { - return zone_store.GetZoneUnderworld(zone_id); + return ZoneStore::Instance()->GetZoneUnderworld(zone_id); } float Perl__GetZoneUnderworld(uint32 zone_id, int version) { - return zone_store.GetZoneUnderworld(zone_id, version); + return ZoneStore::Instance()->GetZoneUnderworld(zone_id, version); } float Perl__GetZoneMinimumClip(uint32 zone_id) { - return zone_store.GetZoneMinimumClip(zone_id); + return ZoneStore::Instance()->GetZoneMinimumClip(zone_id); } float Perl__GetZoneMinimumClip(uint32 zone_id, int version) { - return zone_store.GetZoneMinimumClip(zone_id, version); + return ZoneStore::Instance()->GetZoneMinimumClip(zone_id, version); } float Perl__GetZoneMaximumClip(uint32 zone_id) { - return zone_store.GetZoneMaximumClip(zone_id); + return ZoneStore::Instance()->GetZoneMaximumClip(zone_id); } float Perl__GetZoneMaximumClip(uint32 zone_id, int version) { - return zone_store.GetZoneMaximumClip(zone_id, version); + return ZoneStore::Instance()->GetZoneMaximumClip(zone_id, version); } float Perl__GetZoneFogMinimumClip(uint32 zone_id) { - return zone_store.GetZoneFogMinimumClip(zone_id); + return ZoneStore::Instance()->GetZoneFogMinimumClip(zone_id); } float Perl__GetZoneFogMinimumClip(uint32 zone_id, uint8 slot) { - return zone_store.GetZoneFogMinimumClip(zone_id, slot); + return ZoneStore::Instance()->GetZoneFogMinimumClip(zone_id, slot); } float Perl__GetZoneFogMinimumClip(uint32 zone_id, uint8 slot, int version) { - return zone_store.GetZoneFogMinimumClip(zone_id, slot, version); + return ZoneStore::Instance()->GetZoneFogMinimumClip(zone_id, slot, version); } float Perl__GetZoneFogMaximumClip(uint32 zone_id) { - return zone_store.GetZoneFogMaximumClip(zone_id); + return ZoneStore::Instance()->GetZoneFogMaximumClip(zone_id); } float Perl__GetZoneFogMaximumClip(uint32 zone_id, uint8 slot) { - return zone_store.GetZoneFogMaximumClip(zone_id, slot); + return ZoneStore::Instance()->GetZoneFogMaximumClip(zone_id, slot); } float Perl__GetZoneFogMaximumClip(uint32 zone_id, uint8 slot, int version) { - return zone_store.GetZoneFogMaximumClip(zone_id, slot, version); + return ZoneStore::Instance()->GetZoneFogMaximumClip(zone_id, slot, version); } uint8 Perl__GetZoneFogRed(uint32 zone_id) { - return zone_store.GetZoneFogRed(zone_id); + return ZoneStore::Instance()->GetZoneFogRed(zone_id); } uint8 Perl__GetZoneFogRed(uint32 zone_id, uint8 slot) { - return zone_store.GetZoneFogRed(zone_id, slot); + return ZoneStore::Instance()->GetZoneFogRed(zone_id, slot); } uint8 Perl__GetZoneFogRed(uint32 zone_id, uint8 slot, int version) { - return zone_store.GetZoneFogRed(zone_id, slot, version); + return ZoneStore::Instance()->GetZoneFogRed(zone_id, slot, version); } uint8 Perl__GetZoneFogGreen(uint32 zone_id) { - return zone_store.GetZoneFogGreen(zone_id); + return ZoneStore::Instance()->GetZoneFogGreen(zone_id); } uint8 Perl__GetZoneFogGreen(uint32 zone_id, uint8 slot) { - return zone_store.GetZoneFogGreen(zone_id, slot); + return ZoneStore::Instance()->GetZoneFogGreen(zone_id, slot); } uint8 Perl__GetZoneFogGreen(uint32 zone_id, uint8 slot, int version) { - return zone_store.GetZoneFogGreen(zone_id, slot, version); + return ZoneStore::Instance()->GetZoneFogGreen(zone_id, slot, version); } uint8 Perl__GetZoneFogBlue(uint32 zone_id) { - return zone_store.GetZoneFogBlue(zone_id); + return ZoneStore::Instance()->GetZoneFogBlue(zone_id); } uint8 Perl__GetZoneFogBlue(uint32 zone_id, uint8 slot) { - return zone_store.GetZoneFogBlue(zone_id, slot); + return ZoneStore::Instance()->GetZoneFogBlue(zone_id, slot); } uint8 Perl__GetZoneFogBlue(uint32 zone_id, uint8 slot, int version) { - return zone_store.GetZoneFogBlue(zone_id, slot, version); + return ZoneStore::Instance()->GetZoneFogBlue(zone_id, slot, version); } uint8 Perl__GetZoneSky(uint32 zone_id) { - return zone_store.GetZoneSky(zone_id); + return ZoneStore::Instance()->GetZoneSky(zone_id); } uint8 Perl__GetZoneSky(uint32 zone_id, int version) { - return zone_store.GetZoneSky(zone_id, version); + return ZoneStore::Instance()->GetZoneSky(zone_id, version); } uint8 Perl__GetZoneZType(uint32 zone_id) { - return zone_store.GetZoneZType(zone_id); + return ZoneStore::Instance()->GetZoneZType(zone_id); } uint8 Perl__GetZoneZType(uint32 zone_id, int version) { - return zone_store.GetZoneZType(zone_id, version); + return ZoneStore::Instance()->GetZoneZType(zone_id, version); } float Perl__GetZoneExperienceMultiplier(uint32 zone_id) { - return zone_store.GetZoneExperienceMultiplier(zone_id); + return ZoneStore::Instance()->GetZoneExperienceMultiplier(zone_id); } float Perl__GetZoneExperienceMultiplier(uint32 zone_id, int version) { - return zone_store.GetZoneExperienceMultiplier(zone_id, version); + return ZoneStore::Instance()->GetZoneExperienceMultiplier(zone_id, version); } float Perl__GetZoneWalkSpeed(uint32 zone_id) { - return zone_store.GetZoneWalkSpeed(zone_id); + return ZoneStore::Instance()->GetZoneWalkSpeed(zone_id); } float Perl__GetZoneWalkSpeed(uint32 zone_id, int version) { - return zone_store.GetZoneWalkSpeed(zone_id, version); + return ZoneStore::Instance()->GetZoneWalkSpeed(zone_id, version); } uint8 Perl__GetZoneTimeType(uint32 zone_id) { - return zone_store.GetZoneTimeType(zone_id); + return ZoneStore::Instance()->GetZoneTimeType(zone_id); } uint8 Perl__GetZoneTimeType(uint32 zone_id, int version) { - return zone_store.GetZoneTimeType(zone_id, version); + return ZoneStore::Instance()->GetZoneTimeType(zone_id, version); } float Perl__GetZoneFogDensity(uint32 zone_id) { - return zone_store.GetZoneFogDensity(zone_id); + return ZoneStore::Instance()->GetZoneFogDensity(zone_id); } float Perl__GetZoneFogDensity(uint32 zone_id, int version) { - return zone_store.GetZoneFogDensity(zone_id, version); + return ZoneStore::Instance()->GetZoneFogDensity(zone_id, version); } std::string Perl__GetZoneFlagNeeded(uint32 zone_id) { - return zone_store.GetZoneFlagNeeded(zone_id); + return ZoneStore::Instance()->GetZoneFlagNeeded(zone_id); } std::string Perl__GetZoneFlagNeeded(uint32 zone_id, int version) { - return zone_store.GetZoneFlagNeeded(zone_id, version); + return ZoneStore::Instance()->GetZoneFlagNeeded(zone_id, version); } int8 Perl__GetZoneCanBind(uint32 zone_id) { - return zone_store.GetZoneCanBind(zone_id); + return ZoneStore::Instance()->GetZoneCanBind(zone_id); } int8 Perl__GetZoneCanBind(uint32 zone_id, int version) { - return zone_store.GetZoneCanBind(zone_id, version); + return ZoneStore::Instance()->GetZoneCanBind(zone_id, version); } int8 Perl__GetZoneCanCombat(uint32 zone_id) { - return zone_store.GetZoneCanCombat(zone_id); + return ZoneStore::Instance()->GetZoneCanCombat(zone_id); } int8 Perl__GetZoneCanCombat(uint32 zone_id, int version) { - return zone_store.GetZoneCanCombat(zone_id, version); + return ZoneStore::Instance()->GetZoneCanCombat(zone_id, version); } int8 Perl__GetZoneCanLevitate(uint32 zone_id) { - return zone_store.GetZoneCanLevitate(zone_id); + return ZoneStore::Instance()->GetZoneCanLevitate(zone_id); } int8 Perl__GetZoneCanLevitate(uint32 zone_id, int version) { - return zone_store.GetZoneCanLevitate(zone_id, version); + return ZoneStore::Instance()->GetZoneCanLevitate(zone_id, version); } int8 Perl__GetZoneCastOutdoor(uint32 zone_id) { - return zone_store.GetZoneCastOutdoor(zone_id); + return ZoneStore::Instance()->GetZoneCastOutdoor(zone_id); } int8 Perl__GetZoneCastOutdoor(uint32 zone_id, int version) { - return zone_store.GetZoneCastOutdoor(zone_id, version); + return ZoneStore::Instance()->GetZoneCastOutdoor(zone_id, version); } uint8 Perl__GetZoneHotzone(uint32 zone_id) { - return zone_store.GetZoneHotzone(zone_id); + return ZoneStore::Instance()->GetZoneHotzone(zone_id); } uint8 Perl__GetZoneHotzone(uint32 zone_id, int version) { - return zone_store.GetZoneHotzone(zone_id, version); + return ZoneStore::Instance()->GetZoneHotzone(zone_id, version); } uint8 Perl__GetZoneInstanceType(uint32 zone_id) { - return zone_store.GetZoneInstanceType(zone_id); + return ZoneStore::Instance()->GetZoneInstanceType(zone_id); } uint8 Perl__GetZoneInstanceType(uint32 zone_id, int version) { - return zone_store.GetZoneInstanceType(zone_id, version); + return ZoneStore::Instance()->GetZoneInstanceType(zone_id, version); } uint64 Perl__GetZoneShutdownDelay(uint32 zone_id) { - return zone_store.GetZoneShutdownDelay(zone_id); + return ZoneStore::Instance()->GetZoneShutdownDelay(zone_id); } uint64 Perl__GetZoneShutdownDelay(uint32 zone_id, int version) { - return zone_store.GetZoneShutdownDelay(zone_id, version); + return ZoneStore::Instance()->GetZoneShutdownDelay(zone_id, version); } int8 Perl__GetZonePEQZone(uint32 zone_id) { - return zone_store.GetZonePEQZone(zone_id); + return ZoneStore::Instance()->GetZonePEQZone(zone_id); } int8 Perl__GetZonePEQZone(uint32 zone_id, int version) { - return zone_store.GetZonePEQZone(zone_id, version); + return ZoneStore::Instance()->GetZonePEQZone(zone_id, version); } int8 Perl__GetZoneExpansion(uint32 zone_id) { - return zone_store.GetZoneExpansion(zone_id); + return ZoneStore::Instance()->GetZoneExpansion(zone_id); } int8 Perl__GetZoneExpansion(uint32 zone_id, int version) { - return zone_store.GetZoneExpansion(zone_id, version); + return ZoneStore::Instance()->GetZoneExpansion(zone_id, version); } int8 Perl__GetZoneBypassExpansionCheck(uint32 zone_id) { - return zone_store.GetZoneBypassExpansionCheck(zone_id); + return ZoneStore::Instance()->GetZoneBypassExpansionCheck(zone_id); } int8 Perl__GetZoneBypassExpansionCheck(uint32 zone_id, int version) { - return zone_store.GetZoneBypassExpansionCheck(zone_id, version); + return ZoneStore::Instance()->GetZoneBypassExpansionCheck(zone_id, version); } uint8 Perl__GetZoneSuspendBuffs(uint32 zone_id) { - return zone_store.GetZoneSuspendBuffs(zone_id); + return ZoneStore::Instance()->GetZoneSuspendBuffs(zone_id); } uint8 Perl__GetZoneSuspendBuffs(uint32 zone_id, int version) { - return zone_store.GetZoneSuspendBuffs(zone_id, version); + return ZoneStore::Instance()->GetZoneSuspendBuffs(zone_id, version); } int Perl__GetZoneRainChance(uint32 zone_id) { - return zone_store.GetZoneRainChance(zone_id); + return ZoneStore::Instance()->GetZoneRainChance(zone_id); } int Perl__GetZoneRainChance(uint32 zone_id, uint8 slot) { - return zone_store.GetZoneRainChance(zone_id, slot); + return ZoneStore::Instance()->GetZoneRainChance(zone_id, slot); } int Perl__GetZoneRainChance(uint32 zone_id, uint8 slot, int version) { - return zone_store.GetZoneRainChance(zone_id, slot, version); + return ZoneStore::Instance()->GetZoneRainChance(zone_id, slot, version); } int Perl__GetZoneRainDuration(uint32 zone_id) { - return zone_store.GetZoneRainDuration(zone_id); + return ZoneStore::Instance()->GetZoneRainDuration(zone_id); } int Perl__GetZoneRainDuration(uint32 zone_id, uint8 slot) { - return zone_store.GetZoneRainDuration(zone_id, slot); + return ZoneStore::Instance()->GetZoneRainDuration(zone_id, slot); } int Perl__GetZoneRainDuration(uint32 zone_id, uint8 slot, int version) { - return zone_store.GetZoneRainDuration(zone_id, slot, version); + return ZoneStore::Instance()->GetZoneRainDuration(zone_id, slot, version); } int Perl__GetZoneSnowChance(uint32 zone_id) { - return zone_store.GetZoneSnowChance(zone_id); + return ZoneStore::Instance()->GetZoneSnowChance(zone_id); } int Perl__GetZoneSnowChance(uint32 zone_id, uint8 slot) { - return zone_store.GetZoneSnowChance(zone_id, slot); + return ZoneStore::Instance()->GetZoneSnowChance(zone_id, slot); } int Perl__GetZoneSnowChance(uint32 zone_id, uint8 slot, int version) { - return zone_store.GetZoneSnowChance(zone_id, slot, version); + return ZoneStore::Instance()->GetZoneSnowChance(zone_id, slot, version); } int Perl__GetZoneSnowDuration(uint32 zone_id) { - return zone_store.GetZoneSnowDuration(zone_id); + return ZoneStore::Instance()->GetZoneSnowDuration(zone_id); } int Perl__GetZoneSnowDuration(uint32 zone_id, uint8 slot) { - return zone_store.GetZoneSnowDuration(zone_id, slot); + return ZoneStore::Instance()->GetZoneSnowDuration(zone_id, slot); } int Perl__GetZoneSnowDuration(uint32 zone_id, uint8 slot, int version) { - return zone_store.GetZoneSnowDuration(zone_id, slot, version); + return ZoneStore::Instance()->GetZoneSnowDuration(zone_id, slot, version); } float Perl__GetZoneGravity(uint32 zone_id) { - return zone_store.GetZoneGravity(zone_id); + return ZoneStore::Instance()->GetZoneGravity(zone_id); } float Perl__GetZoneGravity(uint32 zone_id, int version) { - return zone_store.GetZoneGravity(zone_id, version); + return ZoneStore::Instance()->GetZoneGravity(zone_id, version); } int Perl__GetZoneType(uint32 zone_id) { - return zone_store.GetZoneType(zone_id); + return ZoneStore::Instance()->GetZoneType(zone_id); } int Perl__GetZoneType(uint32 zone_id, int version) { - return zone_store.GetZoneType(zone_id, version); + return ZoneStore::Instance()->GetZoneType(zone_id, version); } int8 Perl__GetZoneSkyLock(uint32 zone_id) { - return zone_store.GetZoneSkyLock(zone_id); + return ZoneStore::Instance()->GetZoneSkyLock(zone_id); } int8 Perl__GetZoneSkyLock(uint32 zone_id, int version) { - return zone_store.GetZoneSkyLock(zone_id, version); + return ZoneStore::Instance()->GetZoneSkyLock(zone_id, version); } int Perl__GetZoneFastRegenHP(uint32 zone_id) { - return zone_store.GetZoneFastRegenHP(zone_id); + return ZoneStore::Instance()->GetZoneFastRegenHP(zone_id); } int Perl__GetZoneFastRegenHP(uint32 zone_id, int version) { - return zone_store.GetZoneFastRegenHP(zone_id, version); + return ZoneStore::Instance()->GetZoneFastRegenHP(zone_id, version); } int Perl__GetZoneFastRegenMana(uint32 zone_id) { - return zone_store.GetZoneFastRegenMana(zone_id); + return ZoneStore::Instance()->GetZoneFastRegenMana(zone_id); } int Perl__GetZoneFastRegenMana(uint32 zone_id, int version) { - return zone_store.GetZoneFastRegenMana(zone_id, version); + return ZoneStore::Instance()->GetZoneFastRegenMana(zone_id, version); } int Perl__GetZoneFastRegenEndurance(uint32 zone_id) { - return zone_store.GetZoneFastRegenEndurance(zone_id); + return ZoneStore::Instance()->GetZoneFastRegenEndurance(zone_id); } int Perl__GetZoneFastRegenEndurance(uint32 zone_id, int version) { - return zone_store.GetZoneFastRegenEndurance(zone_id, version); + return ZoneStore::Instance()->GetZoneFastRegenEndurance(zone_id, version); } int Perl__GetZoneNPCMaximumAggroDistance(uint32 zone_id) { - return zone_store.GetZoneNPCMaximumAggroDistance(zone_id); + return ZoneStore::Instance()->GetZoneNPCMaximumAggroDistance(zone_id); } int Perl__GetZoneNPCMaximumAggroDistance(uint32 zone_id, int version) { - return zone_store.GetZoneNPCMaximumAggroDistance(zone_id, version); -} - -uint32 Perl__GetZoneMaximumMovementUpdateRange(uint32 zone_id) -{ - return zone_store.GetZoneMaximumMovementUpdateRange(zone_id); -} - -uint32 Perl__GetZoneMaximumMovementUpdateRange(uint32 zone_id, int version) -{ - return zone_store.GetZoneMaximumMovementUpdateRange(zone_id, version); + return ZoneStore::Instance()->GetZoneNPCMaximumAggroDistance(zone_id, version); } int8 Perl__GetZoneMinimumExpansion(uint32 zone_id) { - return zone_store.GetZoneMinimumExpansion(zone_id); + return ZoneStore::Instance()->GetZoneMinimumExpansion(zone_id); } int8 Perl__GetZoneMinimumExpansion(uint32 zone_id, int version) { - return zone_store.GetZoneMinimumExpansion(zone_id, version); + return ZoneStore::Instance()->GetZoneMinimumExpansion(zone_id, version); } int8 Perl__GetZoneMaximumExpansion(uint32 zone_id) { - return zone_store.GetZoneMaximumExpansion(zone_id); + return ZoneStore::Instance()->GetZoneMaximumExpansion(zone_id); } int8 Perl__GetZoneMaximumExpansion(uint32 zone_id, int version) { - return zone_store.GetZoneMaximumExpansion(zone_id, version); + return ZoneStore::Instance()->GetZoneMaximumExpansion(zone_id, version); } std::string Perl__GetZoneContentFlags(uint32 zone_id) { - return zone_store.GetZoneContentFlags(zone_id); + return ZoneStore::Instance()->GetZoneContentFlags(zone_id); } std::string Perl__GetZoneContentFlags(uint32 zone_id, int version) { - return zone_store.GetZoneContentFlags(zone_id, version); + return ZoneStore::Instance()->GetZoneContentFlags(zone_id, version); } std::string Perl__GetZoneContentFlagsDisabled(uint32 zone_id) { - return zone_store.GetZoneContentFlagsDisabled(zone_id); + return ZoneStore::Instance()->GetZoneContentFlagsDisabled(zone_id); } std::string Perl__GetZoneContentFlagsDisabled(uint32 zone_id, int version) { - return zone_store.GetZoneContentFlagsDisabled(zone_id, version); + return ZoneStore::Instance()->GetZoneContentFlagsDisabled(zone_id, version); } int Perl__GetZoneUnderworldTeleportIndex(uint32 zone_id) { - return zone_store.GetZoneUnderworldTeleportIndex(zone_id); + return ZoneStore::Instance()->GetZoneUnderworldTeleportIndex(zone_id); } int Perl__GetZoneUnderworldTeleportIndex(uint32 zone_id, int version) { - return zone_store.GetZoneUnderworldTeleportIndex(zone_id, version); + return ZoneStore::Instance()->GetZoneUnderworldTeleportIndex(zone_id, version); } int Perl__GetZoneLavaDamage(uint32 zone_id) { - return zone_store.GetZoneLavaDamage(zone_id); + return ZoneStore::Instance()->GetZoneLavaDamage(zone_id); } int Perl__GetZoneLavaDamage(uint32 zone_id, int version) { - return zone_store.GetZoneLavaDamage(zone_id, version); + return ZoneStore::Instance()->GetZoneLavaDamage(zone_id, version); } int Perl__GetZoneMinimumLavaDamage(uint32 zone_id) { - return zone_store.GetZoneMinimumLavaDamage(zone_id); + return ZoneStore::Instance()->GetZoneMinimumLavaDamage(zone_id); } int Perl__GetZoneMinimumLavaDamage(uint32 zone_id, int version) { - return zone_store.GetZoneMinimumLavaDamage(zone_id, version); + return ZoneStore::Instance()->GetZoneMinimumLavaDamage(zone_id, version); } uint8 Perl__GetZoneIdleWhenEmpty(uint32 zone_id) { - return zone_store.GetZoneIdleWhenEmpty(zone_id); + return ZoneStore::Instance()->GetZoneIdleWhenEmpty(zone_id); } uint8 Perl__GetZoneIdleWhenEmpty(uint32 zone_id, int version) { - return zone_store.GetZoneIdleWhenEmpty(zone_id, version); + return ZoneStore::Instance()->GetZoneIdleWhenEmpty(zone_id, version); } uint32 Perl__GetZoneSecondsBeforeIdle(uint32 zone_id) { - return zone_store.GetZoneSecondsBeforeIdle(zone_id); + return ZoneStore::Instance()->GetZoneSecondsBeforeIdle(zone_id); } uint32 Perl__GetZoneSecondsBeforeIdle(uint32 zone_id, int version) { - return zone_store.GetZoneSecondsBeforeIdle(zone_id, version); + return ZoneStore::Instance()->GetZoneSecondsBeforeIdle(zone_id, version); } void Perl__send_channel_message(uint8 channel_number, uint32 guild_id, uint8 language_id, uint8 language_skill, const char* message) @@ -5875,12 +5870,12 @@ bool Perl__SetAutoLoginCharacterNameByAccountID(uint32 account_id, std::string c uint32 Perl__GetZoneIDByLongName(std::string zone_long_name) { - return zone_store.GetZoneIDByLongName(zone_long_name); + return ZoneStore::Instance()->GetZoneIDByLongName(zone_long_name); } std::string Perl__GetZoneShortNameByLongName(std::string zone_long_name) { - return zone_store.GetZoneShortNameByLongName(zone_long_name); + return ZoneStore::Instance()->GetZoneShortNameByLongName(zone_long_name); } bool Perl__send_parcel(perl::reference table_ref) @@ -5988,6 +5983,72 @@ void Perl__SpawnGrid(uint32 npc_id, float x, float y, float z, float heading, fl quest_manager.SpawnGrid(npc_id, glm::vec4(x, y, z, heading), spacing, spawn_count); } +bool Perl__handin(perl::reference handin_ref) +{ + perl::hash handin = handin_ref; + + std::map handin_map; + + for (auto e: handin) { + if (!e.first) { + continue; + } + + if (Strings::EqualFold(e.first, "0")) { + continue; + } + + const uint32 count = static_cast(handin.at(e.first)); + handin_map[e.first] = count; + } + + return quest_manager.handin(handin_map); +} + +perl::array Perl__get_paused_timers(Mob* m) +{ + perl::array a; + + const auto& l = quest_manager.GetPausedTimers(m); + + if (!l.empty()) { + a.reserve(l.size()); + + for (const auto& v : l) { + a.push_back(v); + } + } + + return a; +} + +perl::array Perl__get_timers(Mob* m) +{ + perl::array a; + + const auto& l = quest_manager.GetTimers(m); + + if (!l.empty()) { + a.reserve(l.size()); + + for (const auto& v: l) { + a.push_back(v); + } + } + + return a; +} + +std::string Perl__get_pet_command_name(uint8 pet_command) +{ + return PetCommand::GetName(pet_command); +} + +std::string Perl__get_pet_type_name(uint8 pet_type) +{ + return PetType::GetName(pet_type); +} + void perl_register_quest() { perl::interpreter perl(PERL_GET_THX); @@ -6112,8 +6173,6 @@ void perl_register_quest() package.add("GetZoneMaximumExpansion", (int8(*)(uint32, int))&Perl__GetZoneMaximumExpansion); package.add("GetZoneMaximumLevel", (uint8(*)(uint32))&Perl__GetZoneMaximumLevel); package.add("GetZoneMaximumLevel", (uint8(*)(uint32, int))&Perl__GetZoneMaximumLevel); - package.add("GetZoneMaximumMovementUpdateRange", (uint32(*)(uint32))&Perl__GetZoneMaximumMovementUpdateRange); - package.add("GetZoneMaximumMovementUpdateRange", (uint32(*)(uint32, int))&Perl__GetZoneMaximumMovementUpdateRange); package.add("GetZoneMaximumPlayers", (int(*)(uint32))&Perl__GetZoneMaximumPlayers); package.add("GetZoneMaximumPlayers", (int(*)(uint32, int))&Perl__GetZoneMaximumPlayers); package.add("GetZoneMinimumClip", (float(*)(uint32))&Perl__GetZoneMinimumClip); @@ -6623,6 +6682,8 @@ void perl_register_quest() package.add("faction", (void(*)(int, int, int))&Perl__faction); package.add("factionvalue", &Perl__FactionValue); package.add("failtask", &Perl__failtask); + package.add("completetask", &Perl__completetask); + package.add("uncompletetask", &Perl__uncompletetask); package.add("firsttaskinset", &Perl__firsttaskinset); package.add("follow", (void(*)(int))&Perl__follow); package.add("follow", (void(*)(int, int))&Perl__follow); @@ -6677,6 +6738,9 @@ void perl_register_quest() package.add("getguildidbycharid", &Perl__getguildidbycharid); package.add("getgroupidbycharid", &Perl__getgroupidbycharid); package.add("getinventoryslotname", &Perl__getinventoryslotname); + package.add("get_paused_timers", &Perl__get_paused_timers); + package.add("get_pet_command_name", &Perl__get_pet_command_name); + package.add("get_pet_type_name", &Perl__get_pet_type_name); package.add("getraididbycharid", &Perl__getraididbycharid); package.add("get_race_bitmask", &Perl__get_race_bitmask); package.add("get_recipe_component_item_ids", &Perl__GetRecipeComponentItemIDs); @@ -6696,6 +6760,7 @@ void perl_register_quest() package.add("getspellstat", (int(*)(uint32, std::string))&Perl__getspellstat); package.add("getspellstat", (int(*)(uint32, std::string, uint8))&Perl__getspellstat); package.add("getskillname", &Perl__getskillname); + package.add("get_timers", &Perl__get_timers); package.add("getlevel", &Perl__getlevel); package.add("getplayerburiedcorpsecount", &Perl__getplayerburiedcorpsecount); package.add("getplayercorpsecount", &Perl__getplayercorpsecount); @@ -6715,6 +6780,7 @@ void perl_register_quest() package.add("gmsay", (void(*)(const char*, int, bool))&Perl__gmsay); package.add("gmsay", (void(*)(const char*, int, bool, int))&Perl__gmsay); package.add("gmsay", (void(*)(const char*, int, bool, int, int))&Perl__gmsay); + package.add("handin", &Perl__handin); package.add("has_zone_flag", &Perl__has_zone_flag); package.add("hasrecipelearned", &Perl__hasrecipelearned); package.add("hastimer", &Perl__hastimer); @@ -6780,7 +6846,6 @@ void perl_register_quest() package.add("popuptablerow", &Perl__popuptablerow); package.add("processmobswhilezoneempty", &Perl__processmobswhilezoneempty); package.add("pvp", &Perl__pvp); - package.add("qs_player_event", &Perl__qs_player_event); package.add("qs_send_query", &Perl__qs_send_query); package.add("rain", &Perl__rain); package.add("rebind", (void(*)(int, float, float, float))&Perl__rebind); diff --git a/zone/embperl.cpp b/zone/embperl.cpp index 21a82f111..b48c714e3 100644 --- a/zone/embperl.cpp +++ b/zone/embperl.cpp @@ -137,25 +137,28 @@ void Embperl::DoInit() catch (std::string& e) { LogQuests("Warning [{}]: [{}]", Config->PluginPlFile, e); } - try { - //should probably read the directory in c, instead, so that - //I can echo filenames as I do it, but c'mon... I'm lazy and this 1 line reads in all the plugins - const std::string& perl_command = ( - "if(opendir(D,'" + - path.GetPluginsPath() + - "')) { " - " my @d = readdir(D);" - " closedir(D);" - " foreach(@d){ " - " main::eval_file('plugin','" + - path.GetPluginsPath() + - "/'.$_)if/\\.pl$/;" - " }" - "}"); - eval_pv(perl_command.c_str(), FALSE); - } - catch (std::string& e) { - LogQuests("Warning [{}]", e); + + for (auto & dir : PathManager::Instance()->GetPluginPaths()) { + try { + //should probably read the directory in c, instead, so that + //I can echo filenames as I do it, but c'mon... I'm lazy and this 1 line reads in all the plugins + const std::string& perl_command = ( + "if(opendir(D,'" + + dir + + "')) { " + " my @d = readdir(D);" + " closedir(D);" + " foreach(@d){ " + " main::eval_file('plugin','" + + dir + + "/'.$_)if/\\.pl$/;" + " }" + "}"); + eval_pv(perl_command.c_str(), FALSE); + } + catch (std::string& e) { + LogQuests("Warning [{}]", e); + } } #endif //EMBPERL_PLUGIN } diff --git a/zone/entity.cpp b/zone/entity.cpp index 4a9a232ca..37fa9b4ad 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -55,9 +55,6 @@ extern Zone *zone; extern volatile bool is_zone_loaded; extern WorldServer worldserver; extern uint32 numclients; -extern PetitionList petition_list; - -extern char errorname[32]; Entity::Entity() { @@ -505,6 +502,9 @@ void EntityList::MobProcess() zone->GetSecondsBeforeIdle(), zone->GetSecondsBeforeIdle() != 1 ? "s" : "" ); + + CheckToClearTraderAndBuyerTables(); + mob_settle_timer->Disable(); } @@ -741,6 +741,11 @@ void EntityList::AddNPC(NPC *npc, bool send_spawn_packet, bool dont_queue) npc->DispatchZoneControllerEvent(EVENT_SPAWN_ZONE, npc, "", 0, nullptr); } + if (parse->ZoneHasQuestSub(EVENT_SPAWN_ZONE)) { + std::vector args = { npc }; + parse->EventZone(EVENT_SPAWN_ZONE, zone, "", 0, &args); + } + if (zone->HasMap() && zone->HasWaterMap()) { npc->SetSpawnedInWater(false); if (zone->watermap->InLiquid(npc->GetPosition())) { @@ -1384,7 +1389,7 @@ void EntityList::SendZoneSpawnsBulk(Client *client) bool is_delayed_packet = ( DistanceSquared(client_position, spawn_position) > distance_max || - (spawn->IsClient() && (spawn->GetRace() == MINOR_ILL_OBJ || spawn->GetRace() == TREE)) + (spawn->IsClient() && (spawn->GetRace() == Race::MinorIllusion || spawn->GetRace() == Race::Tree)) ); if (is_delayed_packet) { @@ -1408,7 +1413,7 @@ void EntityList::SendZoneSpawnsBulk(Client *client) * * Illusion races on PCs don't work as a mass spawn * But they will work as an add_spawn AFTER CLIENT_CONNECTED. - * if (spawn->IsClient() && (race == MINOR_ILL_OBJ || race == TREE)) { + * if (spawn->IsClient() && (race == Race::MinorIllusion || race == Race::Tree)) { * app = new EQApplicationPacket; * spawn->CreateSpawnPacket(app); * client->QueuePacket(app, true, Client::CLIENT_CONNECTED); @@ -1717,15 +1722,6 @@ void EntityList::QueueClientsByXTarget(Mob *sender, const EQApplicationPacket *a } } -/** - * @param sender - * @param app - * @param ignore_sender - * @param distance - * @param skipped_mob - * @param is_ack_required - * @param filter - */ void EntityList::QueueCloseClients( Mob *sender, const EQApplicationPacket *app, @@ -1742,7 +1738,7 @@ void EntityList::QueueCloseClients( } if (distance <= 0) { - distance = 600; + distance = zone->GetClientUpdateRange(); } float distance_squared = distance * distance; @@ -1817,25 +1813,13 @@ void EntityList::DuelMessage(Mob *winner, Mob *loser, bool flee) if (parse->PlayerHasQuestSub(EVENT_DUEL_WIN)) { std::vector args = { winner, loser }; - parse->EventPlayer( - EVENT_DUEL_WIN, - winner->CastToClient(), - loser->GetName(), - loser->CastToClient()->CharacterID(), - &args - ); + parse->EventPlayer(EVENT_DUEL_WIN, winner->CastToClient(), loser->GetName(), loser->CastToClient()->CharacterID(), &args); } if (parse->PlayerHasQuestSub(EVENT_DUEL_LOSE)) { std::vector args = { winner, loser }; - parse->EventPlayer( - EVENT_DUEL_LOSE, - loser->CastToClient(), - winner->GetName(), - winner->CastToClient()->CharacterID(), - &args - ); + parse->EventPlayer(EVENT_DUEL_LOSE, loser->CastToClient(), winner->GetName(), winner->CastToClient()->CharacterID(), &args); } } @@ -1912,13 +1896,13 @@ Bot* EntityList::GetRandomBot(const glm::vec3& location, float distance, Bot* ex for (const auto& b : bot_list) { if ( - b != exclude_bot && + b.second != exclude_bot && ( is_whole_zone || - DistanceSquared(static_cast(b->GetPosition()), location) <= distance_squared - ) + DistanceSquared(static_cast(b.second->GetPosition()), location) <= distance_squared + ) ) { - bots_in_range.push_back(b); + bots_in_range.push_back(b.second); } } @@ -2245,44 +2229,37 @@ Raid* EntityList::GetRaidByClient(Client* client) } Raid* EntityList::GetRaidByBotName(const char* name) { - std::list rm; - auto GetMembersWithNames = [&rm](Raid const* r) -> std::list { - for (const auto& m : r->members) { - if (strlen(m.member_name) > 0) - rm.push_back(m); - } - return rm; - }; - for (const auto& r : raid_list) { - for (const auto& m : GetMembersWithNames(r)) { - if (strcmp(m.member_name, name) == 0) { + for (const auto& m : r->members) { + if (m.is_bot && strcmp(m.member_name, name) == 0) { return r; } } } + return nullptr; } -Raid* EntityList::GetRaidByBot(const Bot* bot) +Raid* EntityList::GetRaidByBot(Bot* bot) { - std::list rm; - auto GetMembersWhoAreBots = [&rm](Raid* r) -> std::list { - for (auto const& m : r->members) { - if (m.is_bot) { - rm.push_back(m); - } - } - return rm; - }; - - for (const auto& r : raid_list) { - for (const auto& m : GetMembersWhoAreBots(r)) { - if (m.member->CastToBot() == bot) { - return r; - } - } + if (bot->p_raid_instance) { + return bot->p_raid_instance; } + + std::list::iterator iterator; + iterator = raid_list.begin(); + + while (iterator != raid_list.end()) { + for (const auto& member : (*iterator)->members) { + if (member.member && member.is_bot && member.member->CastToBot() == bot) { + bot->p_raid_instance = *iterator; + return *iterator; + } + } + + ++iterator; + } + return nullptr; } @@ -2360,30 +2337,18 @@ void EntityList::QueueClientsGuild(const EQApplicationPacket *app, uint32 guild_ } } -void EntityList::QueueClientsGuildBankItemUpdate(const GuildBankItemUpdate_Struct *gbius, uint32 GuildID) +void EntityList::QueueClientsGuildBankItemUpdate(GuildBankItemUpdate_Struct *gbius, uint32 guild_id) { - auto outapp = new EQApplicationPacket(OP_GuildBank, sizeof(GuildBankItemUpdate_Struct)); + auto outapp = std::make_unique(OP_GuildBank, sizeof(GuildBankItemUpdate_Struct)); + auto data = reinterpret_cast(outapp->pBuffer); - GuildBankItemUpdate_Struct *outgbius = (GuildBankItemUpdate_Struct*)outapp->pBuffer; + memcpy(data, gbius, sizeof(GuildBankItemUpdate_Struct)); - memcpy(outgbius, gbius, sizeof(GuildBankItemUpdate_Struct)); - - const EQ::ItemData *Item = database.GetItem(gbius->ItemID); - - auto it = client_list.begin(); - while (it != client_list.end()) { - Client *client = it->second; - - if (client->IsInGuild(GuildID)) { - if (Item && (gbius->Permissions == GuildBankPublicIfUsable)) - outgbius->Useable = Item->IsEquipable(client->GetBaseRace(), client->GetBaseClass()); - - client->QueuePacket(outapp); + for (auto const &[key, client]: client_list) { + if (client->IsInGuild(guild_id)) { + client->QueuePacket(outapp.get()); } - - ++it; } - safe_delete(outapp); } void EntityList::MessageStatus(uint32 to_guild_id, int to_minstatus, uint32 type, const char *message, ...) @@ -2890,6 +2855,8 @@ bool EntityList::RemoveMobFromCloseLists(Mob *mob) ); it->second->m_close_mobs.erase(entity_id); + it->second->m_last_seen_mob_position.erase(entity_id); + ++it; } @@ -2943,17 +2910,22 @@ void EntityList::RemoveAuraFromMobs(Mob *aura) // All of the above makes a tremendous impact on the bottom line of cpu cycle performance because we run an order of magnitude // less checks by focusing our hot path logic down to a very small subset of relevant entities instead of looping an entire // entity list (zone wide) + +BenchTimer g_scan_bench_timer; + void EntityList::ScanCloseMobs(Mob *scanning_mob) { if (!scanning_mob) { return; } - if (scanning_mob->GetID() <= 0) { + if (scanning_mob->GetID() <= 0 || scanning_mob->IsZoneController()) { return; } - float scan_range = RuleI(Range, MobCloseScanDistance) * RuleI(Range, MobCloseScanDistance); + g_scan_bench_timer.reset(); + + float scan_range = RuleI(Range, MobCloseScanDistance); // Reserve memory in m_close_mobs to avoid frequent re-allocations if not already reserved. // Assuming mob_list.size() as an upper bound for reservation. @@ -2965,11 +2937,12 @@ void EntityList::ScanCloseMobs(Mob *scanning_mob) for (auto &e : mob_list) { auto mob = e.second; - if (mob->GetID() <= 0) { + + if (mob && (mob->GetID() <= 0 || mob->IsZoneController())) { continue; } - float distance = DistanceSquared(scanning_mob->GetPosition(), mob->GetPosition()); + float distance = Distance(scanning_mob->GetPosition(), mob->GetPosition()); if (distance <= scan_range || mob->GetAggroRange() >= scan_range) { // add mob to scanning_mob's close list and vice versa // check if the mob is already in the close mobs list before inserting @@ -2981,10 +2954,11 @@ void EntityList::ScanCloseMobs(Mob *scanning_mob) } LogAIScanClose( - "[{}] Scanning close list > list_size [{}] moving [{}]", + "[{}] Scanning close list > list_size [{}] moving [{}] elapsed [{}] us", scanning_mob->GetCleanName(), scanning_mob->m_close_mobs.size(), - scanning_mob->IsMoving() ? "true" : "false" + scanning_mob->IsMoving() ? "true" : "false", + g_scan_bench_timer.elapsedMicroseconds() ); } @@ -3174,20 +3148,30 @@ 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); + } + + // Depop below will eventually remove this npc from the entity list + // but that can happen AFTER we've already tried to spawn its replacement. + // So go ahead and remove it from the limits so it doesn't count. + if (npc_limit_list.count(pnpc->GetID())) { + npc_limit_list.erase(pnpc->GetID()); + } pnpc->WipeHateList(); - pnpc->Depop(StartSpawnTimer); } } @@ -3465,7 +3449,7 @@ void EntityList::SendPetitionToAdmins(Petition *pet) strcpy(pcus->accountid, pet->GetAccountName()); strcpy(pcus->charname, pet->GetCharName()); } - pcus->quetotal = petition_list.GetTotalPetitions(); + pcus->quetotal = PetitionList::Instance()->GetTotalPetitions(); auto it = client_list.begin(); while (it != client_list.end()) { if (it->second->CastToClient()->Admin() >= AccountStatus::QuestTroupe) { @@ -3490,7 +3474,7 @@ void EntityList::ClearClientPetitionQueue() strcpy(pet->accountid, ""); strcpy(pet->gmsenttoo, ""); strcpy(pet->charname, ""); - pet->quetotal = petition_list.GetTotalPetitions(); + pet->quetotal = PetitionList::Instance()->GetTotalPetitions(); auto it = client_list.begin(); while (it != client_list.end()) { if (it->second->CastToClient()->Admin() >= AccountStatus::GMAdmin) { @@ -4193,10 +4177,6 @@ void EntityList::ProcessProximitySay(const char *message, Client *c, uint8 langu void EntityList::SaveAllClientsTaskState() { - if (!task_manager) { - return; - } - auto it = client_list.begin(); while (it != client_list.end()) { Client *client = it->second; @@ -4210,9 +4190,6 @@ void EntityList::SaveAllClientsTaskState() void EntityList::ReloadAllClientsTaskState(int task_id) { - if (!task_manager) - return; - auto it = client_list.begin(); while (it != client_list.end()) { Client *client = it->second; @@ -4223,7 +4200,7 @@ void EntityList::ReloadAllClientsTaskState(int task_id) Log(Logs::General, Logs::Tasks, "[CLIENTLOAD] Reloading Task State For Client %s", client->GetName()); client->RemoveClientTaskState(); client->LoadClientTaskState(); - task_manager->SendActiveTasksToClient(client); + TaskManager::Instance()->SendActiveTasksToClient(client); } } ++it; @@ -5149,9 +5126,10 @@ void EntityList::GetClientList(std::list &c_list) void EntityList::GetBotList(std::list &b_list) { b_list.clear(); - - for (const auto& b : bot_list) { - b_list.push_back(b); + auto it = bot_list.begin(); + while (it != bot_list.end()) { + b_list.push_back(it->second); + ++it; } } @@ -5163,14 +5141,13 @@ std::vector EntityList::GetBotListByCharacterID(uint32 character_id, uint return client_bot_list; } - for (const auto& b : bot_list) { - if ( - b->GetOwner() && - b->GetBotOwnerCharacterID() == character_id && - (!class_id || b->GetClass() == class_id) - ) { - client_bot_list.push_back(b); + auto it = bot_list.begin(); + + while (it != bot_list.end()) { + if (it->second->GetOwner() && it->second->GetBotOwnerCharacterID() == character_id && (!class_id || it->second->GetClass() == class_id)) { + client_bot_list.push_back(it->second); } + ++it; } return client_bot_list; @@ -5184,14 +5161,13 @@ std::vector EntityList::GetBotListByClientName(std::string client_name, u return client_bot_list; } - for (const auto& b : bot_list) { - if ( - b->GetOwner() && - Strings::ToLower(b->GetOwner()->GetCleanName()) == Strings::ToLower(client_name) && - (!class_id || b->GetClass() == class_id) - ) { - client_bot_list.push_back(b); + auto it = bot_list.begin(); + + while (it != bot_list.end()) { + if (it->second->GetOwner() && Strings::ToLower(it->second->GetOwner()->GetCleanName()) == Strings::ToLower(client_name) && (!class_id || it->second->GetClass() == class_id)) { + client_bot_list.push_back(it->second); } + ++it; } return client_bot_list; @@ -5332,15 +5308,12 @@ void EntityList::SendFindableNPCList(Client *c) return; } - auto outapp = new EQApplicationPacket(OP_SendFindableNPCs, sizeof(FindableNPC_Struct)); - - FindableNPC_Struct *fnpcs = (FindableNPC_Struct *)outapp->pBuffer; - - fnpcs->Unknown109 = 0x16; - fnpcs->Unknown110 = 0x06; - fnpcs->Unknown111 = 0x24; - - fnpcs->Action = 0; + static EQApplicationPacket p(OP_SendFindableNPCs, sizeof(FindableNPC_Struct)); + auto b = (FindableNPC_Struct*) p.pBuffer; + b->Unknown109 = 0x16; + b->Unknown110 = 0x06; + b->Unknown111 = 0x24; + b->Action = 0; auto it = npc_list.begin(); while (it != npc_list.end()) { @@ -5348,50 +5321,47 @@ void EntityList::SendFindableNPCList(Client *c) NPC *n = it->second; if (n->IsFindable()) { - fnpcs->EntityID = n->GetID(); - strn0cpy(fnpcs->Name, n->GetCleanName(), sizeof(fnpcs->Name)); - strn0cpy(fnpcs->LastName, n->GetLastName(), sizeof(fnpcs->LastName)); - fnpcs->Race = n->GetRace(); - fnpcs->Class = n->GetClass(); + b->EntityID = n->GetID(); + strn0cpy(b->Name, n->GetCleanName(), sizeof(b->Name)); + strn0cpy(b->LastName, n->GetLastName(), sizeof(b->LastName)); + b->Race = n->GetRace(); + b->Class = n->GetClass(); - c->QueuePacket(outapp); + c->QueuePacket(&p); } } ++it; } - safe_delete(outapp); } void EntityList::UpdateFindableNPCState(NPC *n, bool Remove) { - if (!n || !n->IsFindable()) + if (!n || !n->IsFindable()) { return; + } - auto outapp = new EQApplicationPacket(OP_SendFindableNPCs, sizeof(FindableNPC_Struct)); + static EQApplicationPacket p(OP_SendFindableNPCs, sizeof(FindableNPC_Struct)); + auto b = (FindableNPC_Struct *) p.pBuffer; + b->Unknown109 = 0x16; + b->Unknown110 = 0x06; + b->Unknown111 = 0x24; - FindableNPC_Struct *fnpcs = (FindableNPC_Struct *)outapp->pBuffer; - - fnpcs->Unknown109 = 0x16; - fnpcs->Unknown110 = 0x06; - fnpcs->Unknown111 = 0x24; - - fnpcs->Action = Remove ? 1: 0; - fnpcs->EntityID = n->GetID(); - strn0cpy(fnpcs->Name, n->GetCleanName(), sizeof(fnpcs->Name)); - strn0cpy(fnpcs->LastName, n->GetLastName(), sizeof(fnpcs->LastName)); - fnpcs->Race = n->GetRace(); - fnpcs->Class = n->GetClass(); + b->Action = Remove ? 1 : 0; + b->EntityID = n->GetID(); + strn0cpy(b->Name, n->GetCleanName(), sizeof(b->Name)); + strn0cpy(b->LastName, n->GetLastName(), sizeof(b->LastName)); + b->Race = n->GetRace(); + b->Class = n->GetClass(); auto it = client_list.begin(); while (it != client_list.end()) { Client *c = it->second; - if (c && (c->ClientVersion() >= EQ::versions::ClientVersion::SoD)) - c->QueuePacket(outapp); + if (c && (c->ClientVersion() >= EQ::versions::ClientVersion::SoD)) { + c->QueuePacket(&p); + } ++it; } - - safe_delete(outapp); } void EntityList::HideCorpses(Client *c, uint8 CurrentMode, uint8 NewMode) @@ -5544,7 +5514,7 @@ void EntityList::ExpeditionWarning(uint32 minutes_left) auto it = client_list.begin(); while (it != client_list.end()) { - it->second->MessageString(Chat::Yellow, EXPEDITION_MIN_REMAIN, itoa((int)minutes_left)); + it->second->MessageString(Chat::Yellow, DZ_MINUTES_REMAIN, itoa((int)minutes_left)); it->second->QueuePacket(outapp); ++it; } @@ -5569,7 +5539,7 @@ Mob *EntityList::GetClosestMobByBodyType(Mob *sender, uint8 BodyType, bool skip_ continue; // Do not detect client pets - if (skip_client_pets && CurrentMob->IsPet() && CurrentMob->IsPetOwnerClient()) + if (skip_client_pets && CurrentMob->IsPet() && CurrentMob->IsPetOwnerOfClientBot()) continue; CurrentDistance = ((CurrentMob->GetY() - sender->GetY()) * (CurrentMob->GetY() - sender->GetY())) + @@ -5771,10 +5741,6 @@ void EntityList::ReloadMerchants() { * then we return the full list * * See comments @EntityList::ScanCloseMobs for system explanation - * - * @param mob - * @param distance - * @return */ std::unordered_map &EntityList::GetCloseMobList(Mob *mob, float distance) { @@ -5980,3 +5946,83 @@ std::vector EntityList::GetExcludedNPCsByIDs(std::vector npc_ids) return v; } + +void EntityList::SendMerchantEnd(Mob* merchant) +{ + for (const auto& e : client_list) { + Client* c = e.second; + + if (!c) { + continue; + } + + if (c->GetMerchantSessionEntityID() == merchant->GetID()) { + c->SendMerchantEnd(); + } + } +} + +void EntityList::SendMerchantInventory(Mob* m, int32 slot_id, bool is_delete) +{ + if (!m || !m->IsNPC()) { + return; + } + + for (const auto& e : client_list) { + Client* c = e.second; + + if (!c) { + continue; + } + + if (c->GetMerchantSessionEntityID() == m->GetID()) { + if (!is_delete) { + c->BulkSendMerchantInventory(m->CastToNPC()->MerchantType, m->GetNPCTypeID()); + } else { + auto app = new EQApplicationPacket(OP_ShopDelItem, sizeof(Merchant_DelItem_Struct)); + auto d = (Merchant_DelItem_Struct*)app->pBuffer; + + d->itemslot = slot_id; + d->npcid = m->GetID(); + d->playerid = c->GetID(); + + app->priority = 6; + + c->QueuePacket(app); + safe_delete(app); + } + } + } + + 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); + } +} + +void EntityList::CheckToClearTraderAndBuyerTables() +{ + if (zone->GetZoneID() == Zones::BAZAAR) { + TraderRepository::DeleteWhere( + database, + fmt::format( + "`char_zone_id` = {} AND `char_zone_instance_id` = {}", zone->GetZoneID(), zone->GetInstanceID() + ) + ); + BuyerRepository::DeleteBuyers(database, zone->GetZoneID(), zone->GetInstanceID()); + + LogTradingDetail( + "Removed trader and buyer entries for Zone ID [{}] and Instance ID [{}]", + zone->GetZoneID(), + zone->GetInstanceID() + ); + } +} diff --git a/zone/entity.h b/zone/entity.h index 33313f14e..766cd7eda 100644 --- a/zone/entity.h +++ b/zone/entity.h @@ -200,7 +200,7 @@ public: Raid *GetRaidByClient(Client* client); Raid *GetRaidByID(uint32 id); Raid* GetRaidByBotName(const char* name); - Raid* GetRaidByBot(const Bot* bot); + Raid* GetRaidByBot(Bot* bot); Raid* GetRaidByName(const char* name); Corpse *GetCorpseByOwner(Client* client); @@ -429,7 +429,7 @@ public: void QueueClients(Mob* sender, const EQApplicationPacket* app, bool ignore_sender=false, bool ackreq = true); void QueueClientsStatus(Mob* sender, const EQApplicationPacket* app, bool ignore_sender = false, uint8 minstatus = AccountStatus::Player, uint8 maxstatus = AccountStatus::Player); void QueueClientsGuild(const EQApplicationPacket* app, uint32 guildeqid = 0); - void QueueClientsGuildBankItemUpdate(const GuildBankItemUpdate_Struct *gbius, uint32 GuildID); + void QueueClientsGuildBankItemUpdate(GuildBankItemUpdate_Struct *gbius, uint32 GuildID); void QueueClientsByTarget(Mob* sender, const EQApplicationPacket* app, bool iSendToSender = true, Mob* SkipThisMob = 0, bool ackreq = true, bool HoTT = true, uint32 ClientVersionBits = 0xFFFFFFFF, bool inspect_buffs = false, bool clear_target_window = false); void QueueClientsByXTarget(Mob* sender, const EQApplicationPacket* app, bool iSendToSender = true, EQ::versions::ClientVersionBitmask client_version_bits = EQ::versions::ClientVersionBitmask::maskAllClients); @@ -547,7 +547,7 @@ public: inline const std::unordered_map &GetNPCList() { return npc_list; } inline const std::unordered_map &GetMercList() { return merc_list; } inline const std::unordered_map &GetClientList() { return client_list; } - inline const std::list &GetBotList() { return bot_list; } + inline const std::unordered_map &GetBotList() { return bot_list; } std::vector GetBotListByCharacterID(uint32 character_id, uint8 class_id = Class::None); std::vector GetBotListByClientName(std::string client_name, uint8 class_id = Class::None); void SignalAllBotsByOwnerCharacterID(uint32 character_id, int signal_id); @@ -578,6 +578,11 @@ public: int MovePlayerCorpsesToGraveyard(bool force_move_from_instance = false); + void SendMerchantEnd(Mob* merchant); + void SendMerchantInventory(Mob* m, int32 slot_id = -1, bool is_delete = false); + void RestoreCorpse(NPC* npc, uint32_t decay_time); + void CheckToClearTraderAndBuyerTables(); + protected: friend class Zone; void Depop(bool StartSpawnTimer = false); @@ -623,19 +628,16 @@ private: bool RemoveBot(uint16 entityID); Mob* GetMobByBotID(uint32 botID); Bot* GetBotByBotID(uint32 botID); - Bot* GetBotByBotName(std::string_view botName); + Bot* GetBotByBotName(std::string botName); Client* GetBotOwnerByBotEntityID(uint32 entity_id); Client* GetBotOwnerByBotID(const uint32 bot_id); - std::list GetBotsByBotOwnerCharacterID(uint32 botOwnerCharacterID); + std::vector GetBotsByBotOwnerCharacterID(uint32 botOwnerCharacterID); - bool Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, float iRange, uint32 iSpellTypes); // TODO: Evaluate this closesly in hopes to eliminate void ShowSpawnWindow(Client* client, int Distance, bool NamedOnly); // TODO: Implement ShowSpawnWindow in the bot class but it needs entity list stuff - void ScanCloseClientMobs(std::unordered_map& close_mobs, Mob* scanning_mob); - void GetBotList(std::list &b_list); private: - std::list bot_list; + std::unordered_map bot_list; }; class BulkZoneSpawnPacket { diff --git a/zone/event_codes.h b/zone/event_codes.h index aeee13393..ff345abff 100644 --- a/zone/event_codes.h +++ b/zone/event_codes.h @@ -145,6 +145,9 @@ typedef enum { EVENT_AA_LOSS, EVENT_SPELL_BLOCKED, EVENT_READ_ITEM, + EVENT_PET_COMMAND, + EVENT_CHARM_START, + EVENT_CHARM_END, // Add new events before these or Lua crashes EVENT_SPELL_EFFECT_BOT, diff --git a/zone/exp.cpp b/zone/exp.cpp index be6b8af2b..e3016f8b2 100644 --- a/zone/exp.cpp +++ b/zone/exp.cpp @@ -22,7 +22,7 @@ #include "../common/strings.h" #include "client.h" -#include "data_bucket.h" +#include "../common/data_bucket.h" #include "groups.h" #include "mob.h" #include "raids.h" @@ -130,7 +130,7 @@ uint64 Client::CalcEXP(uint8 consider_level, bool ignore_modifiers) { if ( GetClass() == Class::Warrior || GetClass() == Class::Rogue || - GetBaseRace() == HALFLING + GetBaseRace() == Race::Halfling ) { total_modifier *= 1.05; } @@ -291,7 +291,7 @@ void Client::CalculateStandardAAExp(uint64 &add_aaxp, uint8 conlevel, bool resex // Shouldn't race not affect AA XP? if (RuleB(Character, UseRaceClassExpBonuses)) { - if (GetBaseRace() == HALFLING) { + if (GetBaseRace() == Race::Halfling) { aatotalmod *= 1.05; } @@ -439,7 +439,7 @@ void Client::CalculateExp(uint64 in_add_exp, uint64 &add_exp, uint64 &add_aaxp, if (RuleB(Character, UseRaceClassExpBonuses)) { - if (GetBaseRace() == HALFLING) { + if (GetBaseRace() == Race::Halfling) { totalmod *= 1.05; } @@ -497,14 +497,11 @@ void Client::CalculateExp(uint64 in_add_exp, uint64 &add_exp, uint64 &add_aaxp, add_exp = GetEXP() + add_exp; } -void Client::AddEXP(ExpSource exp_source, uint64 in_add_exp, uint8 conlevel, bool resexp) { +void Client::AddEXP(ExpSource exp_source, uint64 in_add_exp, uint8 conlevel, bool resexp, NPC* npc) { if (!IsEXPEnabled()) { return; } - - EVENT_ITEM_ScriptStopReturn(); - uint64 exp = 0; uint64 aaexp = 0; @@ -574,10 +571,10 @@ void Client::AddEXP(ExpSource exp_source, uint64 in_add_exp, uint8 conlevel, boo } // Now update our character's normal and AA xp - SetEXP(exp_source, exp, aaexp, resexp); + SetEXP(exp_source, exp, aaexp, resexp, npc); } -void Client::SetEXP(ExpSource exp_source, uint64 set_exp, uint64 set_aaxp, bool isrezzexp) { +void Client::SetEXP(ExpSource exp_source, uint64 set_exp, uint64 set_aaxp, bool isrezzexp, NPC* npc) { uint64 current_exp = GetEXP(); uint64 current_aa_exp = GetAAXP(); uint64 total_current_exp = current_exp + current_aa_exp; @@ -677,6 +674,7 @@ void Client::SetEXP(ExpSource exp_source, uint64 set_exp, uint64 set_aaxp, bool } } } + ProcessEvolvingItem(exp_gained, npc); } else if(total_add_exp < total_current_exp) { //only loss message if you lose exp, no message if you gained/lost nothing. uint64 exp_lost = current_exp - set_exp; float exp_percent = (float)((float)exp_lost / (float)(GetEXPForLevel(GetLevel() + 1) - GetEXPForLevel(GetLevel())))*(float)100; @@ -924,7 +922,7 @@ void Client::SetLevel(uint8 set_level, bool command) parse->EventPlayer(EVENT_LEVEL_UP, this, std::to_string(levels_gained), 0); } - if (player_event_logs.IsEventEnabled(PlayerEvent::LEVEL_GAIN)) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::LEVEL_GAIN)) { auto e = PlayerEvent::LevelGainedEvent{ .from_level = m_pp.level, .to_level = set_level, @@ -933,17 +931,6 @@ void Client::SetLevel(uint8 set_level, bool command) RecordPlayerEventLog(PlayerEvent::LEVEL_GAIN, e); } - - if (RuleB(QueryServ, PlayerLogLevels)) { - const auto event_desc = fmt::format( - "Leveled UP :: to Level:{} from Level:{} in zoneid:{} instid:{}", - set_level, - m_pp.level, - GetZoneID(), - GetInstanceID() - ); - QServ->PlayerLogEvent(Player_Log_Levels, CharacterID(), event_desc); - } } else if (set_level < m_pp.level) { int levels_lost = (m_pp.level - set_level); @@ -951,7 +938,7 @@ void Client::SetLevel(uint8 set_level, bool command) parse->EventPlayer(EVENT_LEVEL_DOWN, this, std::to_string(levels_lost), 0); } - if (player_event_logs.IsEventEnabled(PlayerEvent::LEVEL_LOSS)) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::LEVEL_LOSS)) { auto e = PlayerEvent::LevelLostEvent{ .from_level = m_pp.level, .to_level = set_level, @@ -960,17 +947,6 @@ void Client::SetLevel(uint8 set_level, bool command) RecordPlayerEventLog(PlayerEvent::LEVEL_LOSS, e); } - - if (RuleB(QueryServ, PlayerLogLevels)) { - const auto event_desc = fmt::format( - "Leveled DOWN :: to Level:{} from Level:{} in zoneid:{} instid:{}", - set_level, - m_pp.level, - GetZoneID(), - GetInstanceID() - ); - QServ->PlayerLogEvent(Player_Log_Levels, CharacterID(), event_desc); - } } m_pp.level = set_level; @@ -1081,13 +1057,13 @@ uint32 Client::GetEXPForLevel(uint16 check_level) if(RuleB(Character,UseOldRaceExpPenalties)) { float racemod = 1.0; - if(GetBaseRace() == TROLL || GetBaseRace() == IKSAR) { + if(GetBaseRace() == Race::Troll || GetBaseRace() == Race::Iksar) { racemod = 1.2; - } else if(GetBaseRace() == OGRE) { + } else if(GetBaseRace() == Race::Ogre) { racemod = 1.15; - } else if(GetBaseRace() == BARBARIAN) { + } else if(GetBaseRace() == Race::Barbarian) { racemod = 1.05; - } else if(GetBaseRace() == HALFLING) { + } else if(GetBaseRace() == Race::Halfling) { racemod = 0.95; } @@ -1205,7 +1181,7 @@ void Group::SplitExp(ExpSource exp_source, const uint64 exp, Mob* other) { if (diff >= max_diff) { const uint64 tmp = (m->GetLevel() + 3) * (m->GetLevel() + 3) * 75 * 35 / 10; const uint64 tmp2 = group_experience / member_count; - m->CastToClient()->AddEXP(exp_source, tmp < tmp2 ? tmp : tmp2, consider_level); + m->CastToClient()->AddEXP(exp_source, tmp < tmp2 ? tmp : tmp2, consider_level, false, other->CastToNPC()); } } } @@ -1256,7 +1232,7 @@ void Raid::SplitExp(ExpSource exp_source, const uint64 exp, Mob* other) { if (diff >= max_diff) { const uint64 tmp = (m.member->GetLevel() + 3) * (m.member->GetLevel() + 3) * 75 * 35 / 10; const uint64 tmp2 = (raid_experience / member_modifier) + 1; - m.member->AddEXP(exp_source, tmp < tmp2 ? tmp : tmp2, consider_level); + m.member->AddEXP(exp_source, tmp < tmp2 ? tmp : tmp2, consider_level, false, other->CastToNPC()); } } } diff --git a/zone/expedition.cpp b/zone/expedition.cpp deleted file mode 100644 index 436601126..000000000 --- a/zone/expedition.cpp +++ /dev/null @@ -1,1502 +0,0 @@ -/** - * EQEmulator: Everquest Server Emulator - * Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY except by those people which sell it, which - * are required to give you total support for your newly bought product; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR - * A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#include "expedition.h" -#include "expedition_database.h" -#include "expedition_request.h" -#include "client.h" -#include "string_ids.h" -#include "worldserver.h" -#include "zonedb.h" -#include "../common/repositories/expedition_lockouts_repository.h" - -extern WorldServer worldserver; -extern Zone* zone; - -// message string 8271 (not in emu clients) -const char* const DZ_YOU_NOT_ASSIGNED = "You could not use this command because you are not currently assigned to a dynamic zone."; -// lockout warnings were added to live in March 11 2020 patch -const char* const DZADD_INVITE_WARNING = "Warning! You will be given replay timers for the following events if you enter %s:"; -const char* const DZADD_INVITE_WARNING_TIMER = "%s - %sD:%sH:%sM"; -// various expeditions re-use these strings when locking -constexpr char LOCK_CLOSE[] = "Your expedition is nearing its close. You cannot bring any additional people into your expedition at this time."; -constexpr char LOCK_BEGIN[] = "The trial has begun. You cannot bring any additional people into your expedition at this time."; - -const int32_t Expedition::REPLAY_TIMER_ID = -1; -const int32_t Expedition::EVENT_TIMER_ID = 1; - -Expedition::Expedition(DynamicZone* dz) : - m_dynamic_zone(dz) -{ - assert(m_dynamic_zone != nullptr); -} - -Expedition::Expedition(DynamicZone* dz, uint32_t id, uint32_t dz_id) : - m_dynamic_zone(dz), - m_id(id), - m_dynamic_zone_id(dz_id) -{ - assert(m_dynamic_zone != nullptr); // dz must remain valid for lifetime of expedition -} - -void Expedition::LoadRepositoryResult(const ExpeditionsRepository::Expeditions& entry) -{ - m_id = entry.id; - m_dynamic_zone_id = entry.dynamic_zone_id; - m_add_replay_on_join = entry.add_replay_on_join; - m_is_locked = entry.is_locked; -} - -void Expedition::RegisterDynamicZoneCallbacks() -{ - uint32_t expedition_id = GetID(); - - GetDynamicZone()->RegisterOnClientAddRemove( - [expedition_id](Client* client, bool removed, bool silent) { - auto expedition = Expedition::FindCachedExpeditionByID(expedition_id); - if (expedition) { - expedition->OnClientAddRemove(client, removed, silent); - } - }); -} - -Expedition* Expedition::TryCreate(Client* requester, DynamicZone& dz_request, bool disable_messages) -{ - if (!requester || !zone) - { - return nullptr; - } - - ExpeditionRequest request{ dz_request, disable_messages }; - - // request parses leader, members list, and lockouts while validating - if (!request.Validate(requester)) - { - LogExpeditionsDetail("[{}] request by [{}] denied", request.GetExpeditionName(), requester->GetName()); - return nullptr; - } - - dz_request.SetLeader({ request.GetLeaderID(), request.GetLeaderName(), DynamicZoneMemberStatus::Online }); - - auto dz = DynamicZone::CreateNew(dz_request, request.GetMembers()); - if (!dz) - { - // live uses this message when trying to enter an instance that isn't ready - // we can use it as the client error message if instance creation fails - requester->MessageString(Chat::Red, DZ_PREVENT_ENTERING); - LogExpeditions("Failed to create a dynamic zone instance for expedition"); - return nullptr; - } - - // unique expedition ids are created from database via auto-increment column - auto expedition_id = ExpeditionDatabase::InsertExpedition(dz->GetID()); - - if (expedition_id) - { - auto expedition = std::make_unique(dz, expedition_id, dz->GetID()); - - LogExpeditions( - "Created [{}] [{}] dz: [{}] instance id: [{}] leader: [{}] minplayers: [{}] maxplayers: [{}]", - expedition->GetID(), - expedition->GetName(), - dz->GetID(), - dz->GetInstanceID(), - expedition->GetLeaderName(), - dz->GetMinPlayers(), - dz->GetMaxPlayers() - ); - - expedition->SaveLockouts(request); - expedition->RegisterDynamicZoneCallbacks(); - - auto inserted = zone->expedition_cache.emplace(expedition_id, std::move(expedition)); - - inserted.first->second->SendWorldExpeditionUpdate(ServerOP_ExpeditionCreate); // cache in other zones - inserted.first->second->SendLeaderMessage(request.GetLeaderClient(), - Chat::System, EXPEDITION_AVAILABLE, { request.GetExpeditionName() }); - - if (!request.GetNotAllAddedMessage().empty()) - { - Client::SendCrossZoneMessage(request.GetLeaderClient(), request.GetLeaderName(), - Chat::System, request.GetNotAllAddedMessage()); - } - - dz->DoAsyncZoneMemberUpdates(); - - return inserted.first->second.get(); - } - - return nullptr; -} - -void Expedition::CacheExpeditions( - std::vector&& expedition_entries) -{ - if (!zone) - { - return; - } - - // bulk load expedition internal lockouts before caching - std::vector expedition_ids; - for (const auto& entry : expedition_entries) - { - expedition_ids.emplace_back(entry.id); - } - - // todo: lockouts need be moved to dynamic zone - auto expedition_lockouts = ExpeditionLockoutsRepository::GetWithTimestamp(database, expedition_ids); - - for (auto& entry : expedition_entries) - { - // the expedition's dynamic zone and members should already be in cache - auto dynamic_zone = DynamicZone::FindDynamicZoneByID(entry.dynamic_zone_id); - if (!dynamic_zone) - { - LogExpeditions("[Warning] Expedition [{}] dz [{}] not found during caching", entry.id, entry.dynamic_zone_id); - continue; - } - - auto expedition = std::make_unique(dynamic_zone); - expedition->LoadRepositoryResult(entry); - expedition->RegisterDynamicZoneCallbacks(); - - for (auto& lockout_entry : expedition_lockouts) - { - if (lockout_entry.expedition_id == expedition->GetID()) - { - ExpeditionLockoutTimer lockout{ - std::move(lockout_entry.from_expedition_uuid), - expedition->GetName(), - std::move(lockout_entry.event_name), - static_cast(lockout_entry.expire_time), - static_cast(lockout_entry.duration) - }; - - std::string event_name = lockout.GetEventName(); // copy for key since we're moving it - expedition->m_lockouts.emplace(std::move(event_name), std::move(lockout)); - } - } - - zone->expedition_cache.emplace(entry.id, std::move(expedition)); - dynamic_zone->DoAsyncZoneMemberUpdates(); - } -} - -void Expedition::CacheFromDatabase(uint32_t expedition_id) -{ - if (zone && expedition_id != 0) - { - BenchTimer benchmark; - - auto expedition = ExpeditionsRepository::GetWhere(database, fmt::format("id = {}", expedition_id)); - CacheExpeditions({ std::move(expedition) }); - - LogExpeditions("Caching new expedition [{}] took [{}s]", expedition_id, benchmark.elapsed()); - } -} - -bool Expedition::CacheAllFromDatabase() -{ - if (!zone) - { - return false; - } - - BenchTimer benchmark; - - auto expeditions = ExpeditionsRepository::All(database); - zone->expedition_cache.clear(); - zone->expedition_cache.reserve(expeditions.size()); - - CacheExpeditions(std::move(expeditions)); - - LogInfo("Loaded [{}] expedition(s)", Strings::Commify(zone->expedition_cache.size())); - - LogExpeditions("Caching [{}] expedition(s) took [{}s]", zone->expedition_cache.size(), benchmark.elapsed()); - - return true; -} - -void Expedition::SaveLockouts(ExpeditionRequest& request) -{ - m_lockouts = request.GetLockouts(); - ExpeditionDatabase::InsertLockouts(m_id, m_lockouts); -} - -Expedition* Expedition::FindCachedExpeditionByCharacterID(uint32_t character_id) -{ - if (zone) - { - for (const auto& expedition : zone->expedition_cache) - { - auto expedition_dz = expedition.second->GetDynamicZone(); - if (expedition_dz && expedition_dz->HasMember(character_id)) - { - return expedition.second.get(); - } - } - } - return nullptr; -} - -Expedition* Expedition::FindCachedExpeditionByCharacterName(const std::string& char_name) -{ - if (zone) - { - for (const auto& expedition : zone->expedition_cache) - { - auto expedition_dz = expedition.second->GetDynamicZone(); - if (expedition_dz && expedition_dz->HasMember(char_name)) - { - return expedition.second.get(); - } - } - } - return nullptr; -} - -Expedition* Expedition::FindCachedExpeditionByDynamicZoneID(uint32_t dz_id) -{ - if (zone && dz_id != 0) - { - for (const auto& cached_expedition : zone->expedition_cache) - { - if (cached_expedition.second->GetDynamicZoneID() == dz_id) - { - return cached_expedition.second.get(); - } - } - } - return nullptr; -} - -Expedition* Expedition::FindCachedExpeditionByID(uint32_t expedition_id) -{ - if (zone && expedition_id) - { - auto expedition_cache_iter = zone->expedition_cache.find(expedition_id); - if (expedition_cache_iter != zone->expedition_cache.end()) - { - return expedition_cache_iter->second.get(); - } - } - return nullptr; -} - -Expedition* Expedition::FindCachedExpeditionByZoneInstance(uint32_t zone_id, uint32_t instance_id) -{ - if (zone && zone_id != 0 && instance_id != 0) - { - for (const auto& cached_expedition : zone->expedition_cache) - { - auto expedition_dz = cached_expedition.second->GetDynamicZone(); - if (expedition_dz && expedition_dz->IsSameDz(zone_id, instance_id)) - { - return cached_expedition.second.get(); - } - } - } - return nullptr; -} - -bool Expedition::HasLockout(const std::string& event_name) -{ - return (m_lockouts.find(event_name) != m_lockouts.end()); -} - -bool Expedition::HasReplayLockout() -{ - return HasLockout(DZ_REPLAY_TIMER_NAME); -} - -void Expedition::SetReplayLockoutOnMemberJoin(bool add_on_join, bool update_db) -{ - m_add_replay_on_join = add_on_join; - - if (update_db) - { - ExpeditionDatabase::UpdateReplayLockoutOnJoin(m_id, add_on_join); - SendWorldSettingChanged(ServerOP_ExpeditionReplayOnJoin, m_add_replay_on_join); - } -} - -void Expedition::AddReplayLockout(uint32_t seconds) -{ - AddLockout(DZ_REPLAY_TIMER_NAME, seconds); -} - -void Expedition::AddLockout(const std::string& event_name, uint32_t seconds) -{ - auto lockout = ExpeditionLockoutTimer::CreateLockout(GetName(), - event_name, seconds, GetDynamicZone()->GetUUID()); - AddLockout(lockout); -} - -void Expedition::AddLockout(const ExpeditionLockoutTimer& lockout, bool members_only) -{ - if (!members_only) - { - ExpeditionDatabase::InsertLockout(m_id, lockout); - } - ExpeditionDatabase::InsertMembersLockout(GetDynamicZone()->GetMembers(), lockout); - - ProcessLockoutUpdate(lockout, false, members_only); - SendWorldLockoutUpdate(lockout, false, members_only); -} - -void Expedition::AddLockoutDuration(const std::string& event_name, int seconds, bool members_only) -{ - // lockout timers use unsigned durations to define intent but we may need - // to insert a new lockout while still supporting timer reductions - auto lockout = ExpeditionLockoutTimer::CreateLockout(GetName(), - event_name, std::max(0, seconds), GetDynamicZone()->GetUUID()); - - if (!members_only) - { - auto it = m_lockouts.find(event_name); - if (it != m_lockouts.end()) - { - it->second.AddLockoutTime(seconds); - ExpeditionDatabase::InsertLockout(m_id, it->second); // replaces current one - } - else - { - ExpeditionDatabase::InsertLockout(m_id, lockout); - } - } - - // processing lockout duration applies multiplier again in client methods, - // update database with modified value now but pass original on - int modified_seconds = static_cast(seconds * RuleR(Expedition, LockoutDurationMultiplier)); - ExpeditionDatabase::AddLockoutDuration(GetDynamicZone()->GetMembers(), lockout, modified_seconds); - - ProcessLockoutDuration(lockout, seconds, members_only); - SendWorldLockoutDuration(lockout, seconds, members_only); -} - -void Expedition::AddReplayLockoutDuration(int seconds, bool members_only) -{ - AddLockoutDuration(DZ_REPLAY_TIMER_NAME, seconds, members_only); -} - -void Expedition::UpdateLockoutDuration( - const std::string& event_name, uint32_t seconds, bool members_only) -{ - // some live expeditions update existing lockout timers during progression - auto it = m_lockouts.find(event_name); - if (it != m_lockouts.end()) - { - seconds = static_cast(seconds * RuleR(Expedition, LockoutDurationMultiplier)); - - uint64_t expire_time = it->second.GetStartTime() + seconds; - AddLockout({ GetDynamicZone()->GetUUID(), GetName(), event_name, expire_time, seconds }, members_only); - } -} - -void Expedition::RemoveLockout(const std::string& event_name) -{ - ExpeditionDatabase::DeleteLockout(m_id, event_name); - ExpeditionDatabase::DeleteMembersLockout(GetDynamicZone()->GetMembers(), GetName(), event_name); - - ExpeditionLockoutTimer lockout{GetDynamicZone()->GetUUID(), GetName(), event_name, 0, 0}; - ProcessLockoutUpdate(lockout, true); - SendWorldLockoutUpdate(lockout, true); -} - -void Expedition::SendClientExpeditionInvite( - Client* client, const std::string& inviter_name, const std::string& swap_remove_name) -{ - if (!client) { - return; - } - - LogExpeditionsDetail( - "Sending expedition [{}] invite to player [{}] inviter [{}] swap name [{}]", - m_id, client->GetName(), inviter_name, swap_remove_name - ); - - client->SetPendingExpeditionInvite({ m_id, inviter_name, swap_remove_name }); - - client->MessageString(Chat::System, EXPEDITION_ASKED_TO_JOIN, - GetLeaderName().c_str(), GetName().c_str()); - - // live (as of March 11 2020 patch) sends warnings for lockouts added - // during current expedition that client would receive on entering dz - bool warned = false; - for (const auto& lockout_iter : m_lockouts) - { - // live doesn't issue a warning for the dz's replay timer - const ExpeditionLockoutTimer& lockout = lockout_iter.second; - if (!lockout.IsReplayTimer() && !lockout.IsExpired() && - lockout.IsFromExpedition(GetDynamicZone()->GetUUID()) && - !client->HasExpeditionLockout(GetName(), lockout.GetEventName())) - { - if (!warned) - { - client->Message(Chat::System, DZADD_INVITE_WARNING, GetName().c_str()); - warned = true; - } - - auto time_remaining = lockout.GetDaysHoursMinutesRemaining(); - client->Message( - Chat::System, DZADD_INVITE_WARNING_TIMER, - lockout.GetEventName().c_str(), - time_remaining.days.c_str(), - time_remaining.hours.c_str(), - time_remaining.mins.c_str() - ); - } - } - - auto outapp = CreateInvitePacket(inviter_name, swap_remove_name); - client->QueuePacket(outapp.get()); -} - -void Expedition::SendLeaderMessage( - Client* leader_client, uint16_t chat_type, uint32_t string_id, const std::initializer_list& args) -{ - Client::SendCrossZoneMessageString(leader_client, GetLeaderName(), chat_type, string_id, args); -} - -bool Expedition::ProcessAddConflicts(Client* leader_client, Client* add_client, bool swapping) -{ - if (!add_client) // a null leader_client is handled by SendLeaderMessage fallback - { - return true; - } - - bool has_conflict = false; - - if (GetDynamicZone()->IsCurrentZoneDzInstance()) - { - SendLeaderMessage(leader_client, Chat::Red, DZADD_LEAVE_ZONE_FIRST, { add_client->GetName() }); - has_conflict = true; - } - - auto expedition_id = add_client->GetExpeditionID(); - if (expedition_id) - { - auto string_id = (expedition_id == GetID()) ? DZADD_ALREADY_PART : DZADD_ALREADY_ASSIGNED; - SendLeaderMessage(leader_client, Chat::Red, string_id, { add_client->GetName() }); - has_conflict = true; - } - - // check any extra event lockouts for this expedition that the client has and expedition doesn't - auto client_lockouts = add_client->GetExpeditionLockouts(GetName()); - for (const auto& client_lockout : client_lockouts) - { - if (client_lockout.IsReplayTimer()) - { - // client with a replay lockout is allowed only if the replay timer was from this expedition - if (client_lockout.GetExpeditionUUID() != GetDynamicZone()->GetUUID()) - { - has_conflict = true; - - auto time_remaining = client_lockout.GetDaysHoursMinutesRemaining(); - SendLeaderMessage(leader_client, Chat::Red, DZADD_REPLAY_TIMER, { - add_client->GetName(), - time_remaining.days, - time_remaining.hours, - time_remaining.mins - }); - } - } - else - { - bool is_missing_lockout = (m_lockouts.find(client_lockout.GetEventName()) == m_lockouts.end()); - if (is_missing_lockout) - { - has_conflict = true; - - auto time_remaining = client_lockout.GetDaysHoursMinutesRemaining(); - SendLeaderMessage(leader_client, Chat::Red, DZADD_EVENT_TIMER, { - add_client->GetName(), - client_lockout.GetEventName(), - time_remaining.days, - time_remaining.hours, - time_remaining.mins, - client_lockout.GetEventName() - }); - } - } - } - - // member swapping integrity is handled by invite response - if (!swapping) - { - auto member_count = GetDynamicZone()->GetDatabaseMemberCount(); - if (member_count == 0) - { - has_conflict = true; - } - else if (member_count >= GetDynamicZone()->GetMaxPlayers()) - { - SendLeaderMessage(leader_client, Chat::Red, DZADD_EXCEED_MAX, { - fmt::format_int(GetDynamicZone()->GetMaxPlayers()).str() }); - has_conflict = true; - } - } - - auto invite_id = add_client->GetPendingExpeditionInviteID(); - if (invite_id) - { - auto string_id = (invite_id == GetID()) ? DZADD_PENDING : DZADD_PENDING_OTHER; - SendLeaderMessage(leader_client, Chat::Red, string_id, { add_client->GetName() }); - has_conflict = true; - } - - return has_conflict; -} - -void Expedition::DzInviteResponse(Client* add_client, bool accepted, const std::string& swap_remove_name) -{ - if (!add_client) - { - return; - } - - LogExpeditionsDetail("Invite response by [{}] accepted [{}] swap_name [{}]", - add_client->GetName(), accepted, swap_remove_name); - - // a null leader_client is handled by SendLeaderMessage fallbacks - // note current leader receives invite reply messages (if leader changed) - Client* leader_client = entity_list.GetClientByCharID(GetLeaderID()); - - if (!accepted) - { - SendLeaderMessage(leader_client, Chat::Red, EXPEDITION_INVITE_DECLINED, { add_client->GetName() }); - return; - } - - bool was_swap_invite = !swap_remove_name.empty(); - bool has_conflicts = m_is_locked; - - if (m_is_locked) - { - SendLeaderMessage(leader_client, Chat::Red, DZADD_NOT_ALLOWING); - } - else - { - has_conflicts = ProcessAddConflicts(leader_client, add_client, was_swap_invite); - } - - // error if swapping and character was already removed before the accept - if (was_swap_invite) - { - auto swap_member = GetDynamicZone()->GetMemberData(swap_remove_name); - if (!swap_member.IsValid() || !GetDynamicZone()->HasDatabaseMember(swap_member.id)) - { - has_conflicts = true; - } - } - - if (has_conflicts) - { - SendLeaderMessage(leader_client, Chat::Red, EXPEDITION_INVITE_ERROR, { add_client->GetName() }); - } - else - { - SendLeaderMessage(leader_client, Chat::Yellow, EXPEDITION_INVITE_ACCEPTED, { add_client->GetName() }); - - // replay timers are optionally added to new members on join with fresh expire time - if (m_add_replay_on_join) - { - auto replay_lockout = m_lockouts.find(DZ_REPLAY_TIMER_NAME); - if (replay_lockout != m_lockouts.end() && - replay_lockout->second.IsFromExpedition(GetDynamicZone()->GetUUID()) && - !add_client->HasExpeditionLockout(GetName(), DZ_REPLAY_TIMER_NAME)) - { - ExpeditionLockoutTimer replay_timer = replay_lockout->second; // copy - replay_timer.Reset(); - add_client->AddExpeditionLockout(replay_timer, true); - } - } - - auto status = DynamicZoneMemberStatus::Online; - DynamicZoneMember add_member{ add_client->CharacterID(), add_client->GetName(), status }; - - bool success = false; - if (was_swap_invite) { - success = GetDynamicZone()->SwapMember(add_member, swap_remove_name); - } else { - success = GetDynamicZone()->AddMember(add_member); - } - - if (success) - { - SendLeaderMessage(leader_client, Chat::Yellow, EXPEDITION_MEMBER_ADDED, - { add_client->GetName(), GetName().c_str() }); - } - } -} - -bool Expedition::ConfirmLeaderCommand(Client* requester) -{ - if (!requester) - { - return false; - } - - if (!GetLeader().IsValid()) - { - requester->MessageString(Chat::Red, UNABLE_RETRIEVE_LEADER); // unconfirmed message - return false; - } - - if (GetLeaderID() != requester->CharacterID()) - { - requester->MessageString(Chat::System, EXPEDITION_NOT_LEADER, GetLeaderName().c_str()); - return false; - } - - return true; -} - -void Expedition::TryAddClient( - Client* add_client, const std::string& inviter_name, const std::string& swap_remove_name, - Client* leader_client) -{ - if (!add_client) - { - return; - } - - LogExpeditionsDetail( - "Add player request for expedition [{}] by inviter [{}] add name [{}] swap name [{}]", - m_id, inviter_name, add_client->GetName(), swap_remove_name - ); - - // null leader client handled by ProcessAddConflicts/SendLeaderMessage fallbacks - if (!leader_client) - { - leader_client = entity_list.GetClientByName(inviter_name.c_str()); - } - - bool has_conflicts = ProcessAddConflicts(leader_client, add_client, !swap_remove_name.empty()); - if (!has_conflicts) - { - // live uses the original unsanitized input string in invite messages - uint32_t string_id = swap_remove_name.empty() ? DZADD_INVITE : DZSWAP_INVITE; - SendLeaderMessage(leader_client, Chat::Yellow, string_id, { add_client->GetName() }); - SendClientExpeditionInvite(add_client, inviter_name.c_str(), swap_remove_name); - } - else if (swap_remove_name.empty()) // swap command doesn't result in this message - { - SendLeaderMessage(leader_client, Chat::Red, DZADD_INVITE_FAIL, { add_client->GetName() }); - } -} - -void Expedition::DzAddPlayer( - Client* requester, const std::string& add_char_name, const std::string& swap_remove_name) -{ - if (!requester || !ConfirmLeaderCommand(requester)) - { - return; - } - - bool invite_failed = false; - - if (m_is_locked) - { - requester->MessageString(Chat::Red, DZADD_NOT_ALLOWING); - invite_failed = true; - } - else if (add_char_name.empty()) - { - requester->MessageString(Chat::Red, DZADD_NOT_ONLINE, add_char_name.c_str()); - invite_failed = true; - } - else - { - auto member_data = GetDynamicZone()->GetMemberData(add_char_name); - if (member_data.IsValid()) - { - // live prioritizes offline message before already a member message - if (member_data.status == DynamicZoneMemberStatus::Offline) - { - requester->MessageString(Chat::Red, DZADD_NOT_ONLINE, add_char_name.c_str()); - } - else - { - requester->MessageString(Chat::Red, DZADD_ALREADY_PART, add_char_name.c_str()); - } - invite_failed = true; - } - } - - if (invite_failed) - { - requester->MessageString(Chat::Red, DZADD_INVITE_FAIL, FormatName(add_char_name).c_str()); - return; - } - - Client* add_client = entity_list.GetClientByName(add_char_name.c_str()); - if (add_client) - { - // client is online in this zone - TryAddClient(add_client, requester->GetName(), swap_remove_name, requester); - } - else - { - // forward to world to check if client is online and perform cross-zone invite - SendWorldAddPlayerInvite(requester->GetName(), swap_remove_name, FormatName(add_char_name)); - } -} - -void Expedition::DzAddPlayerContinue( - std::string inviter_name, std::string add_name, std::string swap_remove_name) -{ - // continuing expedition invite from leader in another zone - Client* add_client = entity_list.GetClientByName(add_name.c_str()); - if (add_client) - { - TryAddClient(add_client, inviter_name, swap_remove_name); - } -} - -void Expedition::DzMakeLeader(Client* requester, std::string new_leader_name) -{ - if (!requester || !ConfirmLeaderCommand(requester)) - { - return; - } - - if (new_leader_name.empty()) - { - requester->MessageString(Chat::Red, DZMAKELEADER_NOT_ONLINE, new_leader_name.c_str()); - return; - } - - // leader can only be changed by world - SendWorldMakeLeaderRequest(requester->CharacterID(), FormatName(new_leader_name)); -} - -void Expedition::DzRemovePlayer(Client* requester, std::string char_name) -{ - if (!requester || !ConfirmLeaderCommand(requester)) - { - return; - } - - // live only seems to enforce min_players for requesting expeditions, no need to check here - // note: on live members removed when inside a dz instance remain "temporary" - // members for kick timer duration and still receive lockouts across zones (unimplemented) - bool removed = GetDynamicZone()->RemoveMember(char_name); - if (!removed) - { - requester->MessageString(Chat::Red, EXPEDITION_NOT_MEMBER, FormatName(char_name).c_str()); - } - else - { - requester->MessageString(Chat::Yellow, EXPEDITION_REMOVED, FormatName(char_name).c_str(), GetName().c_str()); - } -} - -void Expedition::DzQuit(Client* requester) -{ - if (requester) - { - GetDynamicZone()->RemoveMember(requester->GetName()); - } -} - -void Expedition::DzSwapPlayer( - Client* requester, std::string remove_char_name, std::string add_char_name) -{ - if (!requester || !ConfirmLeaderCommand(requester)) - { - return; - } - - if (remove_char_name.empty() || !GetDynamicZone()->HasMember(remove_char_name)) - { - requester->MessageString(Chat::Red, DZSWAP_CANNOT_REMOVE, FormatName(remove_char_name).c_str()); - return; - } - - DzAddPlayer(requester, add_char_name, remove_char_name); -} - -void Expedition::DzPlayerList(Client* requester) -{ - if (requester) - { - requester->MessageString(Chat::Yellow, EXPEDITION_LEADER, GetLeaderName().c_str()); - - std::string member_names; - for (const auto& member : GetDynamicZone()->GetMembers()) - { - fmt::format_to(std::back_inserter(member_names), "{}, ", member.name); - } - - if (member_names.size() > 1) - { - member_names.erase(member_names.length() - 2); // trailing comma and space - } - - requester->MessageString(Chat::Yellow, EXPEDITION_MEMBERS, member_names.c_str()); - } -} - -void Expedition::DzKickPlayers(Client* requester) -{ - if (!requester || !ConfirmLeaderCommand(requester)) - { - return; - } - - GetDynamicZone()->RemoveAllMembers(); - requester->MessageString(Chat::Red, EXPEDITION_REMOVED, "Everyone", GetName().c_str()); -} - -void Expedition::SetLocked( - bool lock_expedition, ExpeditionLockMessage lock_msg, bool update_db, uint32_t msg_color) -{ - m_is_locked = lock_expedition; - - if (m_is_locked && lock_msg != ExpeditionLockMessage::None && GetDynamicZone()->IsCurrentZoneDzInstance()) - { - auto msg = (lock_msg == ExpeditionLockMessage::Close) ? LOCK_CLOSE : LOCK_BEGIN; - for (const auto& client_iter : entity_list.GetClientList()) - { - if (client_iter.second) - { - client_iter.second->Message(msg_color, msg); - } - } - } - - if (update_db) - { - ExpeditionDatabase::UpdateLockState(m_id, lock_expedition); - SendWorldSettingChanged(ServerOP_ExpeditionLockState, m_is_locked); - } -} - -void Expedition::OnClientAddRemove(Client* client, bool removed, bool silent) -{ - if (client) - { - client->SetExpeditionID(removed ? 0 : GetID()); - - if (!silent) - { - auto string_id = removed ? EXPEDITION_REMOVED : EXPEDITION_MEMBER_ADDED; - client->MessageString(Chat::Yellow, string_id, client->GetCleanName(), GetName().c_str()); - } - } -} - -void Expedition::ProcessLockoutDuration( - const ExpeditionLockoutTimer& lockout, int seconds, bool members_only) -{ - if (!members_only) - { - auto it = m_lockouts.find(lockout.GetEventName()); - if (it != m_lockouts.end()) - { - it->second.AddLockoutTime(seconds); - } - else - { - m_lockouts[lockout.GetEventName()] = lockout; - } - } - - for (const auto& member : GetDynamicZone()->GetMembers()) - { - Client* member_client = entity_list.GetClientByCharID(member.id); - if (member_client) - { - member_client->AddExpeditionLockoutDuration(GetName(), - lockout.GetEventName(), seconds, GetDynamicZone()->GetUUID()); - } - } - - if (GetDynamicZone()->IsCurrentZoneDzInstance()) - { - AddLockoutDurationClients(lockout, seconds, GetID()); - } -} - -void Expedition::AddLockoutDurationClients( - const ExpeditionLockoutTimer& lockout, int seconds, uint32_t exclude_id) -{ - std::vector lockout_clients; - for (const auto& client_iter : entity_list.GetClientList()) - { - Client* client = client_iter.second; - if (client && (exclude_id == 0 || client->GetExpeditionID() != exclude_id)) - { - lockout_clients.emplace_back(client->CharacterID(), client->GetName()); - client->AddExpeditionLockoutDuration(GetName(), - lockout.GetEventName(), seconds, GetDynamicZone()->GetUUID()); - } - } - - if (!lockout_clients.empty()) - { - int modified_seconds = static_cast(seconds * RuleR(Expedition, LockoutDurationMultiplier)); - ExpeditionDatabase::AddLockoutDuration(lockout_clients, lockout, modified_seconds); - } -} - -void Expedition::ProcessLockoutUpdate( - const ExpeditionLockoutTimer& lockout, bool remove, bool members_only) -{ - if (!members_only) - { - if (!remove) - { - m_lockouts[lockout.GetEventName()] = lockout; - } - else - { - m_lockouts.erase(lockout.GetEventName()); - } - } - - for (const auto& member : GetDynamicZone()->GetMembers()) - { - Client* member_client = entity_list.GetClientByCharID(member.id); - if (member_client) - { - if (!remove) - { - member_client->AddExpeditionLockout(lockout); - } - else - { - member_client->RemoveExpeditionLockout(GetName(), lockout.GetEventName()); - } - } - } - - // if this is the expedition's dz instance, all clients inside the zone need - // to receive added lockouts. this is done on live to avoid exploits where - // members leave the expedition but haven't been kicked from zone yet - if (!remove && GetDynamicZone()->IsCurrentZoneDzInstance()) - { - AddLockoutClients(lockout, GetID()); - } -} - -void Expedition::AddLockoutClients( - const ExpeditionLockoutTimer& lockout, uint32_t exclude_expedition_id) -{ - std::vector lockout_clients; - for (const auto& client_iter : entity_list.GetClientList()) - { - Client* client = client_iter.second; - if (client && (exclude_expedition_id == 0 || client->GetExpeditionID() != exclude_expedition_id)) - { - lockout_clients.emplace_back(client->CharacterID(), client->GetName()); - client->AddExpeditionLockout(lockout); - } - } - - if (!lockout_clients.empty()) - { - ExpeditionDatabase::InsertMembersLockout(lockout_clients, lockout); - } -} - -void Expedition::SendWorldPendingInvite(const ExpeditionInvite& invite, const std::string& add_name) -{ - LogExpeditions( - "Character [{}] saving pending invite from [{}] to expedition [{}] in world", - add_name, invite.inviter_name, invite.expedition_id - ); - - SendWorldAddPlayerInvite(invite.inviter_name, invite.swap_remove_name, add_name, true); -} - -std::unique_ptr Expedition::CreateInvitePacket( - const std::string& inviter_name, const std::string& swap_remove_name) -{ - uint32_t outsize = sizeof(ExpeditionInvite_Struct); - auto outapp = std::make_unique(OP_DzExpeditionInvite, outsize); - auto outbuf = reinterpret_cast(outapp->pBuffer); - strn0cpy(outbuf->inviter_name, inviter_name.c_str(), sizeof(outbuf->inviter_name)); - strn0cpy(outbuf->expedition_name, GetName().c_str(), sizeof(outbuf->expedition_name)); - strn0cpy(outbuf->swap_name, swap_remove_name.c_str(), sizeof(outbuf->swap_name)); - outbuf->swapping = !swap_remove_name.empty(); - outbuf->dz_zone_id = GetDynamicZone()->GetZoneID(); - outbuf->dz_instance_id = GetDynamicZone()->GetInstanceID(); - return outapp; -} - -void Expedition::SendWorldExpeditionUpdate(uint16_t server_opcode) -{ - uint32_t pack_size = sizeof(ServerExpeditionID_Struct); - auto pack = std::make_unique(server_opcode, pack_size); - auto buf = reinterpret_cast(pack->pBuffer); - buf->expedition_id = GetID(); - buf->sender_zone_id = zone ? zone->GetZoneID() : 0; - buf->sender_instance_id = zone ? zone->GetInstanceID() : 0; - worldserver.SendPacket(pack.get()); -} - -void Expedition::SendWorldAddPlayerInvite( - const std::string& inviter_name, const std::string& swap_remove_name, const std::string& add_name, bool pending) -{ - auto server_opcode = pending ? ServerOP_ExpeditionSaveInvite : ServerOP_ExpeditionDzAddPlayer; - uint32_t pack_size = sizeof(ServerDzCommand_Struct); - auto pack = std::make_unique(server_opcode, pack_size); - auto buf = reinterpret_cast(pack->pBuffer); - buf->expedition_id = GetID(); - buf->is_char_online = false; - strn0cpy(buf->requester_name, inviter_name.c_str(), sizeof(buf->requester_name)); - strn0cpy(buf->target_name, add_name.c_str(), sizeof(buf->target_name)); - strn0cpy(buf->remove_name, swap_remove_name.c_str(), sizeof(buf->remove_name)); - worldserver.SendPacket(pack.get()); -} - -void Expedition::SendWorldLockoutDuration( - const ExpeditionLockoutTimer& lockout, int seconds, bool members_only) -{ - uint32_t pack_size = sizeof(ServerExpeditionLockout_Struct); - auto pack = std::make_unique(ServerOP_ExpeditionLockoutDuration, pack_size); - auto buf = reinterpret_cast(pack->pBuffer); - buf->expedition_id = GetID(); - buf->expire_time = lockout.GetExpireTime(); - buf->duration = lockout.GetDuration(); - buf->sender_zone_id = zone ? zone->GetZoneID() : 0; - buf->sender_instance_id = zone ? zone->GetInstanceID() : 0; - buf->members_only = members_only; - buf->seconds_adjust = seconds; - strn0cpy(buf->event_name, lockout.GetEventName().c_str(), sizeof(buf->event_name)); - worldserver.SendPacket(pack.get()); -} - -void Expedition::SendWorldLockoutUpdate( - const ExpeditionLockoutTimer& lockout, bool remove, bool members_only) -{ - uint32_t pack_size = sizeof(ServerExpeditionLockout_Struct); - auto pack = std::make_unique(ServerOP_ExpeditionLockout, pack_size); - auto buf = reinterpret_cast(pack->pBuffer); - buf->expedition_id = GetID(); - buf->expire_time = lockout.GetExpireTime(); - buf->duration = lockout.GetDuration(); - buf->sender_zone_id = zone ? zone->GetZoneID() : 0; - buf->sender_instance_id = zone ? zone->GetInstanceID() : 0; - buf->remove = remove; - buf->members_only = members_only; - strn0cpy(buf->event_name, lockout.GetEventName().c_str(), sizeof(buf->event_name)); - worldserver.SendPacket(pack.get()); -} - -void Expedition::SendWorldMakeLeaderRequest(uint32_t requester_id, const std::string& new_leader_name) -{ - uint32_t pack_size = sizeof(ServerDzCommandMakeLeader_Struct); - auto pack = std::make_unique(ServerOP_ExpeditionDzMakeLeader, pack_size); - auto buf = reinterpret_cast(pack->pBuffer); - buf->dz_id = GetDynamicZone()->GetID(); - buf->requester_id = requester_id; - strn0cpy(buf->new_leader_name, new_leader_name.c_str(), sizeof(buf->new_leader_name)); - worldserver.SendPacket(pack.get()); -} - -void Expedition::SendWorldSettingChanged(uint16_t server_opcode, bool setting_value) -{ - uint32_t pack_size = sizeof(ServerExpeditionSetting_Struct); - auto pack = std::make_unique(server_opcode, pack_size); - auto buf = reinterpret_cast(pack->pBuffer); - buf->expedition_id = GetID(); - buf->sender_zone_id = zone ? zone->GetZoneID() : 0; - buf->sender_instance_id = zone ? zone->GetInstanceID() : 0; - buf->enabled = setting_value; - worldserver.SendPacket(pack.get()); -} - -void Expedition::SendWorldCharacterLockout( - uint32_t character_id, const ExpeditionLockoutTimer& lockout, bool remove) -{ - uint32_t pack_size = sizeof(ServerExpeditionCharacterLockout_Struct); - auto pack = std::make_unique(ServerOP_ExpeditionCharacterLockout, pack_size); - auto buf = reinterpret_cast(pack->pBuffer); - buf->remove = remove; - buf->character_id = character_id; - buf->expire_time = lockout.GetExpireTime(); - buf->duration = lockout.GetDuration(); - strn0cpy(buf->uuid, lockout.GetExpeditionUUID().c_str(), sizeof(buf->uuid)); - strn0cpy(buf->expedition_name, lockout.GetExpeditionName().c_str(), sizeof(buf->expedition_name)); - strn0cpy(buf->event_name, lockout.GetEventName().c_str(), sizeof(buf->event_name)); - worldserver.SendPacket(pack.get()); -} - -void Expedition::AddLockoutByCharacterID( - uint32_t character_id, const std::string& expedition_name, const std::string& event_name, - uint32_t seconds, const std::string& uuid) -{ - if (character_id) - { - auto lockout = ExpeditionLockoutTimer::CreateLockout(expedition_name, event_name, seconds, uuid); - ExpeditionDatabase::InsertCharacterLockouts(character_id, { lockout }); - SendWorldCharacterLockout(character_id, lockout, false); - } -} - -void Expedition::AddLockoutByCharacterName( - const std::string& character_name, const std::string& expedition_name, const std::string& event_name, - uint32_t seconds, const std::string& uuid) -{ - if (!character_name.empty()) - { - uint32_t character_id = database.GetCharacterID(character_name); - AddLockoutByCharacterID(character_id, expedition_name, event_name, seconds, uuid); - } -} - -bool Expedition::HasLockoutByCharacterID( - uint32_t character_id, const std::string& expedition_name, const std::string& event_name) -{ - auto lockouts = Expedition::GetExpeditionLockoutsByCharacterID(character_id); - return std::any_of(lockouts.begin(), lockouts.end(), [&](const ExpeditionLockoutTimer& lockout) { - return !lockout.IsExpired() && lockout.IsSameLockout(expedition_name, event_name); - }); -} - -bool Expedition::HasLockoutByCharacterName( - const std::string& character_name, const std::string& expedition_name, const std::string& event_name) -{ - if (!character_name.empty()) - { - uint32_t character_id = database.GetCharacterID(character_name); - return HasLockoutByCharacterID(character_id, expedition_name, event_name); - } - return false; -} - -void Expedition::RemoveLockoutsByCharacterID( - uint32_t character_id, const std::string& expedition_name, const std::string& event_name) -{ - if (character_id) - { - if (!event_name.empty()) - { - ExpeditionDatabase::DeleteCharacterLockout(character_id, expedition_name, event_name); - } - else if (!expedition_name.empty()) - { - ExpeditionDatabase::DeleteAllCharacterLockouts(character_id, expedition_name); - } - else - { - ExpeditionDatabase::DeleteAllCharacterLockouts(character_id); - } - - ExpeditionLockoutTimer lockout{{}, expedition_name, event_name, 0, 0}; - SendWorldCharacterLockout(character_id, lockout, true); - } -} - -void Expedition::RemoveLockoutsByCharacterName( - const std::string& character_name, const std::string& expedition_name, const std::string& event_name) -{ - if (!character_name.empty()) - { - uint32_t character_id = database.GetCharacterID(character_name); - RemoveLockoutsByCharacterID(character_id, expedition_name, event_name); - } -} - -void Expedition::HandleWorldMessage(ServerPacket* pack) -{ - switch (pack->opcode) - { - case ServerOP_ExpeditionCreate: - { - auto buf = reinterpret_cast(pack->pBuffer); - if (zone && !zone->IsZone(buf->sender_zone_id, buf->sender_instance_id)) - { - Expedition::CacheFromDatabase(buf->expedition_id); - } - break; - } - case ServerOP_ExpeditionLockout: - case ServerOP_ExpeditionLockoutDuration: - { - auto buf = reinterpret_cast(pack->pBuffer); - if (zone && !zone->IsZone(buf->sender_zone_id, buf->sender_instance_id)) - { - auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); - if (expedition) - { - ExpeditionLockoutTimer lockout{ expedition->GetDynamicZone()->GetUUID(), expedition->GetName(), - buf->event_name, buf->expire_time, buf->duration }; - - if (pack->opcode == ServerOP_ExpeditionLockout) - { - expedition->ProcessLockoutUpdate(lockout, buf->remove, buf->members_only); - } - else if (pack->opcode == ServerOP_ExpeditionLockoutDuration) - { - expedition->ProcessLockoutDuration(lockout, buf->seconds_adjust, buf->members_only); - } - } - } - break; - } - case ServerOP_ExpeditionLockState: - { - auto buf = reinterpret_cast(pack->pBuffer); - if (zone && !zone->IsZone(buf->sender_zone_id, buf->sender_instance_id)) - { - auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); - if (expedition) - { - expedition->SetLocked(buf->enabled, static_cast(buf->lock_msg)); - } - } - break; - } - case ServerOP_ExpeditionReplayOnJoin: - { - auto buf = reinterpret_cast(pack->pBuffer); - if (zone && !zone->IsZone(buf->sender_zone_id, buf->sender_instance_id)) - { - auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); - if (expedition) - { - expedition->SetReplayLockoutOnMemberJoin(buf->enabled); - } - } - break; - } - case ServerOP_ExpeditionDzAddPlayer: - { - auto buf = reinterpret_cast(pack->pBuffer); - if (buf->is_char_online) - { - auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); - if (expedition) - { - expedition->DzAddPlayerContinue(buf->requester_name, buf->target_name, buf->remove_name); - } - } - else - { - Client* leader = entity_list.GetClientByName(buf->requester_name); - if (leader) - { - std::string target_name = FormatName(buf->target_name); - leader->MessageString(Chat::Red, DZADD_NOT_ONLINE, target_name.c_str()); - leader->MessageString(Chat::Red, DZADD_INVITE_FAIL, target_name.c_str()); - } - } - break; - } - case ServerOP_ExpeditionDzMakeLeader: - { - auto buf = reinterpret_cast(pack->pBuffer); - auto old_leader_client = entity_list.GetClientByCharID(buf->requester_id); - auto new_leader_client = entity_list.GetClientByName(buf->new_leader_name); - - if (old_leader_client) - { - // success flag is set by world to indicate new leader set to an online member - if (buf->is_success) { - old_leader_client->MessageString(Chat::Yellow, DZMAKELEADER_NAME, buf->new_leader_name); - } else if (!buf->is_online) { - old_leader_client->MessageString(Chat::Red, DZMAKELEADER_NOT_ONLINE, buf->new_leader_name); - } else { - old_leader_client->MessageString(Chat::Red, EXPEDITION_NOT_MEMBER, buf->new_leader_name); - } - } - - if (buf->is_success && new_leader_client && !RuleB(Expedition, AlwaysNotifyNewLeaderOnChange)) - { - new_leader_client->MessageString(Chat::Yellow, DZMAKELEADER_YOU); - } - break; - } - case ServerOP_ExpeditionCharacterLockout: - { - auto buf = reinterpret_cast(pack->pBuffer); - Client* client = entity_list.GetClientByCharID(buf->character_id); - if (client) - { - if (!buf->remove) - { - client->AddExpeditionLockout(ExpeditionLockoutTimer{ - buf->uuid, buf->expedition_name, buf->event_name, buf->expire_time, buf->duration - }); - } - else if (buf->event_name[0] != '\0') - { - client->RemoveExpeditionLockout(buf->expedition_name, buf->event_name); - } - else - { - client->RemoveAllExpeditionLockouts(buf->expedition_name); - } - } - break; - } - } -} - -bool Expedition::CanClientLootCorpse(Client* client, uint32_t npc_type_id, uint32_t spawn_id) -{ - if (client && GetDynamicZone()->IsCurrentZoneDzInstance()) - { - // entity id takes priority, falls back to checking by npc type if not set - std::string event_name = GetLootEventBySpawnID(spawn_id); - if (event_name.empty()) - { - event_name = GetLootEventByNPCTypeID(npc_type_id); - } - - if (!event_name.empty()) - { - auto client_lockout = client->GetExpeditionLockout(GetName(), event_name); - if (!client_lockout || client_lockout->GetExpeditionUUID() != GetDynamicZone()->GetUUID()) - { - // client lockout not received in this expedition, prevent looting - LogExpeditions( - "Character [{}] denied looting npc [{}] spawn [{}] for lockout event [{}]", - client->CharacterID(), npc_type_id, spawn_id, event_name - ); - return false; - } - } - } - - return true; -} - -void Expedition::SetLootEventByNPCTypeID(uint32_t npc_type_id, const std::string& event_name) -{ - if (npc_type_id && GetDynamicZone()->IsCurrentZoneDzInstance()) - { - LogExpeditions("Setting loot event [{}] for npc type id [{}]", event_name, npc_type_id); - m_npc_loot_events[npc_type_id] = event_name; - } -} - -void Expedition::SetLootEventBySpawnID(uint32_t spawn_id, const std::string& event_name) -{ - if (spawn_id && GetDynamicZone()->IsCurrentZoneDzInstance()) - { - LogExpeditions("Setting loot event [{}] for entity id [{}]", event_name, spawn_id); - m_spawn_loot_events[spawn_id] = event_name; - } -} - -std::string Expedition::GetLootEventByNPCTypeID(uint32_t npc_type_id) -{ - std::string event_name; - - if (npc_type_id && GetDynamicZone()->IsCurrentZoneDzInstance()) - { - auto it = m_npc_loot_events.find(npc_type_id); - if (it != m_npc_loot_events.end()) - { - event_name = it->second; - } - } - - return event_name; -} - -std::string Expedition::GetLootEventBySpawnID(uint32_t spawn_id) -{ - std::string event_name; - - if (spawn_id && GetDynamicZone()->IsCurrentZoneDzInstance()) - { - auto it = m_spawn_loot_events.find(spawn_id); - if (it != m_spawn_loot_events.end()) - { - event_name = it->second; - } - } - - return event_name; -} - -std::vector Expedition::GetExpeditionLockoutsByCharacterID(uint32_t character_id) -{ - std::vector lockouts; - if (character_id == 0) - { - return lockouts; - } - - auto client = entity_list.GetClientByCharID(character_id); - if (client) - { - lockouts = client->GetExpeditionLockouts(); - } - else - { - lockouts = ExpeditionDatabase::LoadCharacterLockouts(character_id); - } - - return lockouts; -} - -void Expedition::SyncCharacterLockouts( - uint32_t character_id, std::vector& client_lockouts) -{ - // adds missing event lockouts to client for this expedition and updates - // client timers that are both shorter and from another expedition - BenchTimer benchmark; - - bool modified = false; - - for (const auto& lockout_iter : m_lockouts) - { - const ExpeditionLockoutTimer& lockout = lockout_iter.second; - if (lockout.IsReplayTimer() || lockout.IsExpired() || - lockout.GetExpeditionUUID() != GetDynamicZone()->GetUUID()) - { - continue; - } - - auto client_lockout_iter = std::find_if(client_lockouts.begin(), client_lockouts.end(), - [&](const ExpeditionLockoutTimer& client_lockout) { - return client_lockout.IsSameLockout(lockout); - }); - - if (client_lockout_iter == client_lockouts.end()) - { - modified = true; - client_lockouts.emplace_back(lockout); // insert missing - } - else if (client_lockout_iter->GetSecondsRemaining() < lockout.GetSecondsRemaining() && - client_lockout_iter->GetExpeditionUUID() != GetDynamicZone()->GetUUID()) - { - // only update lockout timer not uuid so loot event apis still work - modified = true; - client_lockout_iter->SetDuration(lockout.GetDuration()); - client_lockout_iter->SetExpireTime(lockout.GetExpireTime()); - } - } - - if (modified) - { - ExpeditionDatabase::InsertCharacterLockouts(character_id, client_lockouts); - } - - LogExpeditionsDetail("Syncing character lockouts with expedition took [{}] s", benchmark.elapsed()); -} diff --git a/zone/expedition.h b/zone/expedition.h deleted file mode 100644 index 1379b724d..000000000 --- a/zone/expedition.h +++ /dev/null @@ -1,169 +0,0 @@ -/** - * EQEmulator: Everquest Server Emulator - * Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY except by those people which sell it, which - * are required to give you total support for your newly bought product; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR - * A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#ifndef EXPEDITION_H -#define EXPEDITION_H - -#include "dynamic_zone.h" -#include "../common/expedition_lockout_timer.h" -#include "../common/repositories/expeditions_repository.h" -#include -#include -#include -#include -#include -#include - -class Client; -class EQApplicationPacket; -struct ExpeditionInvite; -class ExpeditionRequest; -class ServerPacket; - -extern const char* const DZ_YOU_NOT_ASSIGNED; - -enum class ExpeditionLockMessage : uint8_t -{ - None = 0, - Close, - Begin -}; - -class Expedition -{ -public: - Expedition() = delete; - Expedition(DynamicZone* dz); - Expedition(DynamicZone* dz, uint32_t id, uint32_t dz_id); - - static Expedition* TryCreate(Client* requester, DynamicZone& dynamiczone, bool disable_messages); - - static void CacheFromDatabase(uint32_t expedition_id); - static bool CacheAllFromDatabase(); - static Expedition* FindCachedExpeditionByCharacterID(uint32_t character_id); - static Expedition* FindCachedExpeditionByCharacterName(const std::string& char_name); - static Expedition* FindCachedExpeditionByDynamicZoneID(uint32_t dz_id); - static Expedition* FindCachedExpeditionByID(uint32_t expedition_id); - static Expedition* FindCachedExpeditionByZoneInstance(uint32_t zone_id, uint32_t instance_id); - static std::vector GetExpeditionLockoutsByCharacterID(uint32_t character_id); - static void HandleWorldMessage(ServerPacket* pack); - static void AddLockoutByCharacterID(uint32_t character_id, const std::string& expedition_name, - const std::string& event_name, uint32_t seconds, const std::string& uuid = {}); - static void AddLockoutByCharacterName(const std::string& character_name, const std::string& expedition_name, - const std::string& event_name, uint32_t seconds, const std::string& uuid = {}); - static bool HasLockoutByCharacterID(uint32_t character_id, - const std::string& expedition_name, const std::string& event_name); - static bool HasLockoutByCharacterName(const std::string& character_name, - const std::string& expedition_name, const std::string& event_name); - static void RemoveLockoutsByCharacterID(uint32_t character_id, - const std::string& expedition_name = {}, const std::string& event_name = {}); - static void RemoveLockoutsByCharacterName(const std::string& character_name, - const std::string& expedition_name = {}, const std::string& event_name = {}); - static void AddLockoutClients(const ExpeditionLockoutTimer& lockout, uint32_t exclude_id = 0); - - uint32_t GetID() const { return m_id; } - uint32_t GetDynamicZoneID() const { return m_dynamic_zone_id; } - DynamicZone* GetDynamicZone() const { return m_dynamic_zone; } - const DynamicZoneMember& GetLeader() { return GetDynamicZone()->GetLeader(); } - uint32_t GetLeaderID() { return GetDynamicZone()->GetLeaderID(); } - const std::string& GetLeaderName() { return GetDynamicZone()->GetLeaderName(); } - const std::unordered_map& GetLockouts() const { return m_lockouts; } - const std::string& GetName() { return GetDynamicZone()->GetName(); } - void RegisterDynamicZoneCallbacks(); - - bool IsLocked() const { return m_is_locked; } - void SetLocked(bool lock_expedition, ExpeditionLockMessage lock_msg, - bool update_db = false, uint32_t msg_color = Chat::Yellow); - - void AddLockout(const std::string& event_name, uint32_t seconds); - void AddLockoutDuration(const std::string& event_name, int seconds, bool members_only = true); - void AddReplayLockout(uint32_t seconds); - void AddReplayLockoutDuration(int seconds, bool members_only = true); - bool HasLockout(const std::string& event_name); - bool HasReplayLockout(); - void RemoveLockout(const std::string& event_name); - void SetReplayLockoutOnMemberJoin(bool add_on_join, bool update_db = false); - void SyncCharacterLockouts(uint32_t character_id, std::vector& client_lockouts); - void UpdateLockoutDuration(const std::string& event_name, uint32_t seconds, bool members_only = true); - - bool CanClientLootCorpse(Client* client, uint32_t npc_type_id, uint32_t spawn_id); - std::string GetLootEventByNPCTypeID(uint32_t npc_id); - std::string GetLootEventBySpawnID(uint32_t spawn_id); - void SetLootEventByNPCTypeID(uint32_t npc_type_id, const std::string& event_name); - void SetLootEventBySpawnID(uint32_t spawn_id, const std::string& event_name); - - void SendWorldMakeLeaderRequest(uint32_t requester_id, const std::string& new_leader_name); - void SendWorldPendingInvite(const ExpeditionInvite& invite, const std::string& add_name); - - void DzAddPlayer(Client* requester, const std::string& add_char_name, const std::string& swap_remove_name = {}); - void DzAddPlayerContinue(std::string leader_name, std::string add_char_name, std::string swap_remove_name = {}); - void DzInviteResponse(Client* add_client, bool accepted, const std::string& swap_remove_name); - void DzMakeLeader(Client* requester, std::string new_leader_name); - void DzPlayerList(Client* requester); - void DzRemovePlayer(Client* requester, std::string remove_char_name); - void DzSwapPlayer(Client* requester, std::string remove_char_name, std::string add_char_name); - void DzQuit(Client* requester); - void DzKickPlayers(Client* requester); - - static const int32_t REPLAY_TIMER_ID; - static const int32_t EVENT_TIMER_ID; - -private: - static void CacheExpeditions(std::vector&& expeditions); - static void SendWorldCharacterLockout(uint32_t character_id, const ExpeditionLockoutTimer& lockout, bool remove); - - void AddLockout(const ExpeditionLockoutTimer& lockout, bool members_only = false); - void AddLockoutDurationClients(const ExpeditionLockoutTimer& lockout, int seconds, uint32_t exclude_id = 0); - bool ConfirmLeaderCommand(Client* requester); - void OnClientAddRemove(Client* client, bool removed, bool silent); - void LoadRepositoryResult(const ExpeditionsRepository::Expeditions& entry); - bool ProcessAddConflicts(Client* leader_client, Client* add_client, bool swapping); - void ProcessLockoutDuration(const ExpeditionLockoutTimer& lockout, int seconds, bool members_only = false); - void ProcessLockoutUpdate(const ExpeditionLockoutTimer& lockout, bool remove, bool members_only = false); - void SaveLockouts(ExpeditionRequest& request); - void SendClientExpeditionInvite( - Client* client, const std::string& inviter_name, const std::string& swap_remove_name); - void SendLeaderMessage(Client* leader_client, uint16_t chat_type, uint32_t string_id, - const std::initializer_list& args = {}); - void SendWorldExpeditionUpdate(uint16_t server_opcode); - void SendWorldAddPlayerInvite(const std::string& inviter_name, const std::string& swap_remove_name, - const std::string& add_name, bool pending = false); - void SendWorldLockoutDuration( - const ExpeditionLockoutTimer& lockout, int seconds, bool members_only = false); - void SendWorldLockoutUpdate( - const ExpeditionLockoutTimer& lockout, bool remove, bool members_only = false); - void SendWorldSettingChanged(uint16_t server_opcode, bool setting_value); - void SetDynamicZone(DynamicZone&& dz); - void TryAddClient(Client* add_client, const std::string& inviter_name, - const std::string& swap_remove_name, Client* leader_client = nullptr); - - std::unique_ptr CreateInvitePacket(const std::string& inviter_name, const std::string& swap_remove_name); - - uint32_t m_id = 0; - uint32_t m_dynamic_zone_id = 0; - bool m_is_locked = false; - bool m_add_replay_on_join = true; - DynamicZone* m_dynamic_zone = nullptr; // should never be null, will exist for lifetime of expedition - std::unordered_map m_lockouts; - std::unordered_map m_npc_loot_events; // only valid inside dz zone - std::unordered_map m_spawn_loot_events; // only valid inside dz zone -}; - -#endif diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp deleted file mode 100644 index 22d60a19d..000000000 --- a/zone/expedition_database.cpp +++ /dev/null @@ -1,409 +0,0 @@ -/** - * EQEmulator: Everquest Server Emulator - * Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY except by those people which sell it, which - * are required to give you total support for your newly bought product; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR - * A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#include "expedition_database.h" -#include "expedition.h" -#include "zonedb.h" -#include - -uint32_t ExpeditionDatabase::InsertExpedition(uint32_t dz_id) -{ - LogExpeditionsDetail("Inserting new expedition dz [{}]", dz_id); - - std::string query = fmt::format(SQL( - INSERT INTO expeditions - (dynamic_zone_id) - VALUES - ({}); - ), dz_id); - - auto results = database.QueryDatabase(query); - if (!results.Success()) - { - LogExpeditions("Failed to obtain an expedition id for dz [{}]", dz_id); - return 0; - } - - return results.LastInsertedID(); -} - -std::vector ExpeditionDatabase::LoadCharacterLockouts(uint32_t character_id) -{ - LogExpeditionsDetail("Loading character [{}] lockouts", character_id); - - std::vector lockouts; - - auto query = fmt::format(SQL( - SELECT - from_expedition_uuid, - expedition_name, - event_name, - UNIX_TIMESTAMP(expire_time), - duration - FROM character_expedition_lockouts - WHERE character_id = {} AND expire_time > NOW(); - ), character_id); - - auto results = database.QueryDatabase(query); - if (results.Success()) - { - for (auto row = results.begin(); row != results.end(); ++row) - { - lockouts.emplace_back( - row[0], // expedition_uuid - row[1], // expedition_name - row[2], // event_name - strtoull(row[3], nullptr, 10), // expire_time - static_cast(strtoul(row[4], nullptr, 10)) // duration - ); - } - } - - return lockouts; -} - -std::vector ExpeditionDatabase::LoadCharacterLockouts( - uint32_t character_id, const std::string& expedition_name) -{ - LogExpeditionsDetail("Loading character [{}] lockouts for [{}]", character_id, expedition_name); - - std::vector lockouts; - - auto query = fmt::format(SQL( - SELECT - from_expedition_uuid, - event_name, - UNIX_TIMESTAMP(expire_time), - duration - FROM character_expedition_lockouts - WHERE - character_id = {} - AND expire_time > NOW() - AND expedition_name = '{}'; - ), character_id, Strings::Escape(expedition_name)); - - auto results = database.QueryDatabase(query); - if (results.Success()) - { - for (auto row = results.begin(); row != results.end(); ++row) - { - lockouts.emplace_back( - row[0], // expedition_uuid - expedition_name, - row[1], // event_name - strtoull(row[2], nullptr, 10), // expire_time - static_cast(strtoul(row[3], nullptr, 10)) // duration - ); - } - } - - return lockouts; -} - -void ExpeditionDatabase::DeleteAllCharacterLockouts(uint32_t character_id) -{ - LogExpeditionsDetail("Deleting all character [{}] lockouts", character_id); - - if (character_id != 0) - { - std::string query = fmt::format(SQL( - DELETE FROM character_expedition_lockouts - WHERE character_id = {}; - ), character_id); - - database.QueryDatabase(query); - } -} - -void ExpeditionDatabase::DeleteAllCharacterLockouts( - uint32_t character_id, const std::string& expedition_name) -{ - LogExpeditionsDetail("Deleting all character [{}] lockouts for [{}]", character_id, expedition_name); - - if (character_id != 0 && !expedition_name.empty()) - { - std::string query = fmt::format(SQL( - DELETE FROM character_expedition_lockouts - WHERE character_id = {} AND expedition_name = '{}'; - ), character_id, Strings::Escape(expedition_name)); - - database.QueryDatabase(query); - } -} - -void ExpeditionDatabase::DeleteCharacterLockout( - uint32_t character_id, const std::string& expedition_name, const std::string& event_name) -{ - LogExpeditionsDetail( - "Deleting character [{}] lockout: [{}]:[{}]", character_id, expedition_name, event_name - ); - - auto query = fmt::format(SQL( - DELETE FROM character_expedition_lockouts - WHERE - character_id = {} - AND expedition_name = '{}' - AND event_name = '{}'; - ), character_id, Strings::Escape(expedition_name), Strings::Escape(event_name)); - - database.QueryDatabase(query); -} - -void ExpeditionDatabase::DeleteMembersLockout( - const std::vector& members, - const std::string& expedition_name, const std::string& event_name) -{ - LogExpeditionsDetail("Deleting members lockout: [{}]:[{}]", expedition_name, event_name); - - std::string query_character_ids; - for (const auto& member : members) - { - fmt::format_to(std::back_inserter(query_character_ids), "{},", member.id); - } - - if (!query_character_ids.empty()) - { - query_character_ids.pop_back(); // trailing comma - - auto query = fmt::format(SQL( - DELETE FROM character_expedition_lockouts - WHERE character_id - IN ({}) - AND expedition_name = '{}' - AND event_name = '{}'; - ), query_character_ids, Strings::Escape(expedition_name), Strings::Escape(event_name)); - - database.QueryDatabase(query); - } -} - -void ExpeditionDatabase::DeleteLockout(uint32_t expedition_id, const std::string& event_name) -{ - LogExpeditionsDetail("Deleting expedition [{}] lockout event [{}]", expedition_id, event_name); - - auto query = fmt::format(SQL( - DELETE FROM expedition_lockouts - WHERE expedition_id = {} AND event_name = '{}'; - ), expedition_id, Strings::Escape(event_name)); - - database.QueryDatabase(query); -} - -void ExpeditionDatabase::InsertCharacterLockouts(uint32_t character_id, - const std::vector& lockouts) -{ - LogExpeditionsDetail("Inserting [{}] lockouts for character [{}]", lockouts.size(), character_id); - - std::string insert_values; - for (const auto& lockout : lockouts) - { - fmt::format_to(std::back_inserter(insert_values), - "({}, FROM_UNIXTIME({}), {}, '{}', '{}', '{}'),", - character_id, - lockout.GetExpireTime(), - lockout.GetDuration(), - lockout.GetExpeditionUUID(), - Strings::Escape(lockout.GetExpeditionName()), - Strings::Escape(lockout.GetEventName()) - ); - } - - if (!insert_values.empty()) - { - insert_values.pop_back(); // trailing comma - - auto query = fmt::format(SQL( - INSERT INTO character_expedition_lockouts - (character_id, expire_time, duration, from_expedition_uuid, expedition_name, event_name) - VALUES {} - ON DUPLICATE KEY UPDATE - from_expedition_uuid = VALUES(from_expedition_uuid), - expire_time = VALUES(expire_time), - duration = VALUES(duration); - ), insert_values); - - database.QueryDatabase(query); - } -} - -void ExpeditionDatabase::InsertMembersLockout( - const std::vector& members, const ExpeditionLockoutTimer& lockout) -{ - LogExpeditionsDetail( - "Inserting members lockout [{}]:[{}] with expire time [{}]", - lockout.GetExpeditionName(), lockout.GetEventName(), lockout.GetExpireTime() - ); - - std::string insert_values; - for (const auto& member : members) - { - fmt::format_to(std::back_inserter(insert_values), - "({}, FROM_UNIXTIME({}), {}, '{}', '{}', '{}'),", - member.id, - lockout.GetExpireTime(), - lockout.GetDuration(), - lockout.GetExpeditionUUID(), - Strings::Escape(lockout.GetExpeditionName()), - Strings::Escape(lockout.GetEventName()) - ); - } - - if (!insert_values.empty()) - { - insert_values.pop_back(); // trailing comma - - auto query = fmt::format(SQL( - INSERT INTO character_expedition_lockouts - (character_id, expire_time, duration, from_expedition_uuid, expedition_name, event_name) - VALUES {} - ON DUPLICATE KEY UPDATE - from_expedition_uuid = VALUES(from_expedition_uuid), - expire_time = VALUES(expire_time), - duration = VALUES(duration); - ), insert_values); - - database.QueryDatabase(query); - } -} - -void ExpeditionDatabase::InsertLockout( - uint32_t expedition_id, const ExpeditionLockoutTimer& lockout) -{ - LogExpeditionsDetail( - "Inserting expedition [{}] lockout: [{}]:[{}] expire time: [{}]", - expedition_id, lockout.GetExpeditionName(), lockout.GetEventName(), lockout.GetExpireTime() - ); - - auto query = fmt::format(SQL( - INSERT INTO expedition_lockouts - (expedition_id, from_expedition_uuid, event_name, expire_time, duration) - VALUES - ({}, '{}', '{}', FROM_UNIXTIME({}), {}) - ON DUPLICATE KEY UPDATE - from_expedition_uuid = VALUES(from_expedition_uuid), - expire_time = VALUES(expire_time), - duration = VALUES(duration); - ), - expedition_id, - lockout.GetExpeditionUUID(), - Strings::Escape(lockout.GetEventName()), - lockout.GetExpireTime(), - lockout.GetDuration() - ); - - database.QueryDatabase(query); -} - -void ExpeditionDatabase::InsertLockouts( - uint32_t expedition_id, const std::unordered_map& lockouts) -{ - LogExpeditionsDetail("Inserting expedition [{}] lockouts", expedition_id); - - std::string insert_values; - for (const auto& lockout : lockouts) - { - fmt::format_to(std::back_inserter(insert_values), - "({}, '{}', '{}', FROM_UNIXTIME({}), {}),", - expedition_id, - lockout.second.GetExpeditionUUID(), - Strings::Escape(lockout.second.GetEventName()), - lockout.second.GetExpireTime(), - lockout.second.GetDuration() - ); - } - - if (!insert_values.empty()) - { - insert_values.pop_back(); // trailing comma - - auto query = fmt::format(SQL( - INSERT INTO expedition_lockouts - (expedition_id, from_expedition_uuid, event_name, expire_time, duration) - VALUES {} - ON DUPLICATE KEY UPDATE - from_expedition_uuid = VALUES(from_expedition_uuid), - expire_time = VALUES(expire_time), - duration = VALUES(duration); - ), insert_values); - - database.QueryDatabase(query); - } -} - -void ExpeditionDatabase::UpdateLockState(uint32_t expedition_id, bool is_locked) -{ - LogExpeditionsDetail("Updating lock state [{}] for expedition [{}]", is_locked, expedition_id); - - auto query = fmt::format(SQL( - UPDATE expeditions SET is_locked = {} WHERE id = {}; - ), is_locked, expedition_id); - - database.QueryDatabase(query); -} - -void ExpeditionDatabase::UpdateReplayLockoutOnJoin(uint32_t expedition_id, bool add_on_join) -{ - LogExpeditionsDetail("Updating replay lockout on join [{}] for expedition [{}]", add_on_join, expedition_id); - - auto query = fmt::format(SQL( - UPDATE expeditions SET add_replay_on_join = {} WHERE id = {}; - ), add_on_join, expedition_id); - - database.QueryDatabase(query); -} - -void ExpeditionDatabase::AddLockoutDuration(const std::vector& members, - const ExpeditionLockoutTimer& lockout, int seconds) -{ - LogExpeditionsDetail( - "Adding duration [{}] seconds to members lockouts [{}]:[{}]", - seconds, lockout.GetExpeditionName(), lockout.GetEventName()); - - std::string insert_values; - for (const auto& member : members) - { - fmt::format_to(std::back_inserter(insert_values), - "({}, FROM_UNIXTIME({}), {}, '{}', '{}', '{}'),", - member.id, - lockout.GetExpireTime(), - lockout.GetDuration(), - lockout.GetExpeditionUUID(), - Strings::Escape(lockout.GetExpeditionName()), - Strings::Escape(lockout.GetEventName()) - ); - } - - if (!insert_values.empty()) - { - insert_values.pop_back(); // trailing comma - - auto query = fmt::format(SQL( - INSERT INTO character_expedition_lockouts - (character_id, expire_time, duration, from_expedition_uuid, expedition_name, event_name) - VALUES {} - ON DUPLICATE KEY UPDATE - from_expedition_uuid = VALUES(from_expedition_uuid), - expire_time = DATE_ADD(expire_time, INTERVAL {} SECOND), - duration = GREATEST(0, CAST(duration AS SIGNED) + {}); - ), insert_values, seconds, seconds); - - database.QueryDatabase(query); - } -} diff --git a/zone/expedition_database.h b/zone/expedition_database.h deleted file mode 100644 index 5e35fb7b3..000000000 --- a/zone/expedition_database.h +++ /dev/null @@ -1,62 +0,0 @@ -/** - * EQEmulator: Everquest Server Emulator - * Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY except by those people which sell it, which - * are required to give you total support for your newly bought product; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR - * A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#ifndef EXPEDITION_DATABASE_H -#define EXPEDITION_DATABASE_H - -#include -#include -#include -#include -#include -#include - -class Expedition; -class ExpeditionLockoutTimer; -struct DynamicZoneMember; -class MySQLRequestResult; - -namespace ExpeditionDatabase -{ - uint32_t InsertExpedition(uint32_t dz_id); - std::vector LoadCharacterLockouts(uint32_t character_id); - std::vector LoadCharacterLockouts(uint32_t character_id, - const std::string& expedition_name); - void DeleteAllCharacterLockouts(uint32_t character_id); - void DeleteAllCharacterLockouts(uint32_t character_id, const std::string& expedition_name); - void DeleteCharacterLockout(uint32_t character_id, const std::string& expedition_name, - const std::string& event_name); - void DeleteLockout(uint32_t expedition_id, const std::string& event_name); - void DeleteMembersLockout(const std::vector& members, - const std::string& expedition_name, const std::string& event_name); - void InsertCharacterLockouts(uint32_t character_id, - const std::vector& lockouts); - void InsertMembersLockout(const std::vector& members, - const ExpeditionLockoutTimer& lockout); - void InsertLockout(uint32_t expedition_id, const ExpeditionLockoutTimer& lockout); - void InsertLockouts(uint32_t expedition_id, - const std::unordered_map& lockouts); - void UpdateLockState(uint32_t expedition_id, bool is_locked); - void UpdateReplayLockoutOnJoin(uint32_t expedition_id, bool add_on_join); - void AddLockoutDuration(const std::vector& members, - const ExpeditionLockoutTimer& lockout, int seconds); -}; - -#endif diff --git a/zone/expedition_request.cpp b/zone/expedition_request.cpp index 4c00f00dc..7c7763730 100644 --- a/zone/expedition_request.cpp +++ b/zone/expedition_request.cpp @@ -20,48 +20,30 @@ #include "expedition_request.h" #include "client.h" -#include "expedition.h" #include "groups.h" #include "raids.h" #include "string_ids.h" #include "../common/repositories/character_expedition_lockouts_repository.h" -constexpr char SystemName[] = "expedition"; - -// message string 8312 added in September 08 2020 Test patch (used by both dz and shared tasks) -constexpr const char* CREATE_NOT_ALL_ADDED = "Not all players in your {} were added to the {}. The {} can take a maximum of {} players, and your {} has {}."; -// message string 9265 (not in emu clients) -constexpr const char* EXPEDITION_OTHER_BELONGS = "{} attempted to create an expedition but {} already belongs to one."; - -ExpeditionRequest::ExpeditionRequest(const DynamicZone& dz, bool disable_messages) : - m_expedition_name(dz.GetName()), - m_min_players(dz.GetMinPlayers()), - m_max_players(dz.GetMaxPlayers()), - m_disable_messages(disable_messages) +ExpeditionRequest::ExpeditionRequest(const DynamicZone& dz, Client& client, bool silent) + : m_dz(&dz), m_requester(&client), m_silent(silent) { } -bool ExpeditionRequest::Validate(Client* requester) +bool ExpeditionRequest::Validate() { - m_requester = requester; - if (!m_requester) - { - return false; - } - // a message is sent to leader for every member that fails a requirement BenchTimer benchmark; bool requirements_met = false; - Raid* raid = m_requester->GetRaid(); - Group* group = m_requester->GetGroup(); - if (raid) + if (Raid* raid = m_requester->GetRaid()) { + m_is_raid = true; requirements_met = CanRaidRequest(raid); } - else if (group) + else if (Group* group = m_requester->GetGroup()) { requirements_met = CanGroupRequest(group); } @@ -89,21 +71,15 @@ bool ExpeditionRequest::CanRaidRequest(Raid* raid) // expedition max. members are added up to the max ordered by group number. auto raid_members = raid->GetMembers(); - if (raid_members.size() > m_max_players) + if (raid_members.size() > m_dz->GetMaxPlayers()) { - // stable_sort not needed, order within a raid group may not be what is displayed + // leader first then raid group, order within a raid group may not be what is displayed std::sort(raid_members.begin(), raid_members.end(), [&](const RaidMember& lhs, const RaidMember& rhs) { - if (m_leader_name == lhs.member_name) { // leader always added first - return true; - } else if (m_leader_name == rhs.member_name) { - return false; - } + if (m_leader_name == lhs.member_name) { return true; } + if (m_leader_name == rhs.member_name) { return false; } return lhs.group_number < rhs.group_number; }); - - m_not_all_added_msg = fmt::format(CREATE_NOT_ALL_ADDED, "raid", SystemName, - SystemName, m_max_players, "raid", raid_members.size()); } // live still performs conflict checks for all members even those beyond max @@ -139,12 +115,6 @@ bool ExpeditionRequest::CanGroupRequest(Group* group) } } - if (member_names.size() > m_max_players) - { - m_not_all_added_msg = fmt::format(CREATE_NOT_ALL_ADDED, "group", SystemName, - SystemName, m_max_players, "group", member_names.size()); - } - return CanMembersJoin(member_names); } @@ -172,7 +142,7 @@ bool ExpeditionRequest::CanMembersJoin(const std::vector& member_na return requirements_met; } -bool ExpeditionRequest::SaveLeaderLockouts(const std::vector& lockouts) +bool ExpeditionRequest::SaveLeaderLockouts(const std::vector& lockouts) { bool has_replay_lockout = false; @@ -180,9 +150,10 @@ bool ExpeditionRequest::SaveLeaderLockouts(const std::vector& member_names) { // order of member_names is preserved by queries for use with max member truncation - auto entries = ExpeditionsRepository::GetCharactersWithExpedition(database, member_names); + auto entries = DynamicZonesRepository::GetCharactersWithDz(database, member_names, static_cast(DynamicZoneType::Expedition)); if (entries.empty()) { LogExpeditions("Failed to load members for expedition request"); @@ -205,10 +176,10 @@ bool ExpeditionRequest::CheckMembersForConflicts(const std::vector& bool is_solo = (member_names.size() == 1); bool has_conflicts = false; - std::vector character_ids; + std::vector char_ids; for (const auto& character : entries) { - if (is_solo && character.expedition_id != 0) + if (is_solo && character.dz_id != 0) { // live doesn't bother checking replay lockout here SendLeaderMemberInExpedition(character.name, is_solo); @@ -216,43 +187,43 @@ bool ExpeditionRequest::CheckMembersForConflicts(const std::vector& } m_members.emplace_back(character.id, character.name, DynamicZoneMemberStatus::Online); - character_ids.emplace_back(character.id); + char_ids.push_back(character.id); } - auto member_lockouts = CharacterExpeditionLockoutsRepository::GetManyCharacterLockoutTimers( - database, character_ids, m_expedition_name, DZ_REPLAY_TIMER_NAME); + auto lockouts = CharacterExpeditionLockoutsRepository::GetLockouts(database, char_ids, m_dz->GetName()); // on live if leader has a replay lockout it never checks for event conflicts - bool leader_has_replay_lockout = false; - auto lockout_iter = member_lockouts.find(m_leader_id); - if (lockout_iter != member_lockouts.end()) + bool leader_replay = false; + auto it = lockouts.find(m_leader_id); + if (it != lockouts.end()) { - leader_has_replay_lockout = SaveLeaderLockouts(lockout_iter->second); + leader_replay = SaveLeaderLockouts(it->second); } for (const auto& character : entries) { - if (character.expedition_id != 0) + if (character.dz_id != 0) { has_conflicts = true; SendLeaderMemberInExpedition(character.name, is_solo); } - auto lockout_iter = member_lockouts.find(character.id); - if (lockout_iter != member_lockouts.end()) + auto it = lockouts.find(character.id); + if (it != lockouts.end()) { - for (const auto& lockout : lockout_iter->second) + for (const auto& lockout : it->second) { if (!lockout.IsExpired()) { + auto is_event = [&](const auto& l) { return l.IsEvent(lockout.Event()); }; + // replay timers were sorted by query so they show up before event conflicts - if (lockout.IsReplayTimer()) + if (lockout.IsReplay()) { has_conflicts = true; SendLeaderMemberReplayLockout(character.name, lockout, is_solo); } - else if (!leader_has_replay_lockout && character.id != m_leader_id && - m_lockouts.find(lockout.GetEventName()) == m_lockouts.end()) + else if (!leader_replay && character.id != m_leader_id && std::ranges::none_of(m_lockouts, is_event)) { // leader doesn't have this lockout has_conflicts = true; @@ -266,73 +237,60 @@ bool ExpeditionRequest::CheckMembersForConflicts(const std::vector& return has_conflicts; } -void ExpeditionRequest::SendLeaderMessage( - uint16_t chat_type, uint32_t string_id, const std::initializer_list& args) +void ExpeditionRequest::SendLeaderMessage(uint16_t chat_type, uint32_t string_id, std::initializer_list args) { - if (!m_disable_messages) + if (!m_silent) { Client::SendCrossZoneMessageString(m_leader, m_leader_name, chat_type, string_id, args); } } -void ExpeditionRequest::SendLeaderMemberInExpedition(const std::string& member_name, bool is_solo) +void ExpeditionRequest::SendLeaderMemberInExpedition(const std::string& name, bool is_solo) { - if (m_disable_messages) + if (m_silent) { return; } if (is_solo) { - SendLeaderMessage(Chat::Red, EXPEDITION_YOU_BELONG); + SendLeaderMessage(Chat::Red, DZ_YOU_BELONG); } else if (m_requester) { - std::string message = fmt::format(EXPEDITION_OTHER_BELONGS, m_requester->GetName(), member_name); - Client::SendCrossZoneMessage(m_leader, m_leader_name, Chat::Red, message); + // message string 9265 (not in emu clients) + Client::SendCrossZoneMessage(m_leader, m_leader_name, Chat::Red, fmt::format( + "{} attempted to create an expedition but {} already belongs to one.", m_requester->GetName(), name)); } } -void ExpeditionRequest::SendLeaderMemberReplayLockout( - const std::string& member_name, const ExpeditionLockoutTimer& lockout, bool is_solo) +void ExpeditionRequest::SendLeaderMemberReplayLockout(const std::string& name, const DzLockout& lockout, bool is_solo) { - if (m_disable_messages) + if (m_silent) { return; } - auto time_remaining = lockout.GetDaysHoursMinutesRemaining(); + auto time = lockout.GetTimeRemainingStrs(); if (is_solo) { - SendLeaderMessage(Chat::Red, EXPEDITION_YOU_PLAYED_HERE, { - time_remaining.days, time_remaining.hours, time_remaining.mins - }); + SendLeaderMessage(Chat::Red, DZ_REPLAY_YOU, { time.days, time.hours, time.mins }); } else { - SendLeaderMessage(Chat::Red, EXPEDITION_REPLAY_TIMER, { - member_name, time_remaining.days, time_remaining.hours, time_remaining.mins - }); + SendLeaderMessage(Chat::Red, DZ_REPLAY_OTHER, { name, time.days, time.hours, time.mins }); } } -void ExpeditionRequest::SendLeaderMemberEventLockout( - const std::string& member_name, const ExpeditionLockoutTimer& lockout) +void ExpeditionRequest::SendLeaderMemberEventLockout(const std::string& name, const DzLockout& lockout) { - if (m_disable_messages) + if (m_silent) { return; } - auto time_remaining = lockout.GetDaysHoursMinutesRemaining(); - SendLeaderMessage(Chat::Red, EXPEDITION_EVENT_TIMER, { - member_name, - lockout.GetEventName(), - time_remaining.days, - time_remaining.hours, - time_remaining.mins, - lockout.GetEventName() - }); + auto time = lockout.GetTimeRemainingStrs(); + SendLeaderMessage(Chat::Red, DZ_EVENT_TIMER, { name, lockout.Event(), time.days, time.hours, time.mins }); } bool ExpeditionRequest::IsPlayerCountValidated() @@ -343,19 +301,14 @@ bool ExpeditionRequest::IsPlayerCountValidated() auto bypass_status = RuleI(Expedition, MinStatusToBypassPlayerCountRequirements); auto gm_bypass = (m_requester && m_requester->GetGM() && m_requester->Admin() >= bypass_status); - if (m_members.size() > m_max_players) - { - // members were sorted at start, truncate after conflict checks to act like live - m_members.resize(m_max_players); - } - else if (!gm_bypass && m_members.size() < m_min_players) + if (!gm_bypass && m_members.size() < m_dz->GetMinPlayers()) { requirements_met = false; - SendLeaderMessage(Chat::System, REQUIRED_PLAYER_COUNT, { + SendLeaderMessage(Chat::System, DZ_PLAYER_COUNT, { fmt::format_int(m_members.size()).str(), - fmt::format_int(m_min_players).str(), - fmt::format_int(m_max_players).str() + fmt::format_int(m_dz->GetMinPlayers()).str(), + fmt::format_int(m_dz->GetMaxPlayers()).str() }); } diff --git a/zone/expedition_request.h b/zone/expedition_request.h index 059aa71de..037c43ae3 100644 --- a/zone/expedition_request.h +++ b/zone/expedition_request.h @@ -21,12 +21,11 @@ #ifndef EXPEDITION_REQUEST_H #define EXPEDITION_REQUEST_H -#include "expedition.h" -#include "../common/expedition_lockout_timer.h" +#include "dynamic_zone.h" +#include "../common/dynamic_zone_lockout.h" #include #include #include -#include class Client; class Group; @@ -35,19 +34,16 @@ class Raid; class ExpeditionRequest { public: - ExpeditionRequest(const DynamicZone& dz, bool disable_messages = false); + ExpeditionRequest(const DynamicZone& dz, Client& client, bool silent = false); - bool Validate(Client* requester); + bool Validate(); - const std::string& GetExpeditionName() const { return m_expedition_name; } Client* GetLeaderClient() const { return m_leader; } uint32_t GetLeaderID() const { return m_leader_id; } const std::string& GetLeaderName() const { return m_leader_name; } - const std::string& GetNotAllAddedMessage() const { return m_not_all_added_msg; } - uint32_t GetMinPlayers() const { return m_min_players; } - uint32_t GetMaxPlayers() const { return m_max_players; } const std::vector& GetMembers() const { return m_members; } - const std::unordered_map& GetLockouts() const { return m_lockouts; } + const std::vector& GetLockouts() const { return m_lockouts; } + bool IsRaid() const { return m_is_raid; } private: bool CanMembersJoin(const std::vector& member_names); @@ -55,23 +51,21 @@ private: bool CanGroupRequest(Group* group); bool CheckMembersForConflicts(const std::vector& member_names); bool IsPlayerCountValidated(); - bool SaveLeaderLockouts(const std::vector& leader_lockouts); - void SendLeaderMemberInExpedition(const std::string& member_name, bool is_solo); - void SendLeaderMemberReplayLockout(const std::string& member_name, const ExpeditionLockoutTimer& lockout, bool is_solo); - void SendLeaderMemberEventLockout(const std::string& member_name, const ExpeditionLockoutTimer& lockout); - void SendLeaderMessage(uint16_t chat_type, uint32_t string_id, const std::initializer_list& args = {}); + bool SaveLeaderLockouts(const std::vector& leader_lockouts); + void SendLeaderMemberInExpedition(const std::string& name, bool is_solo); + void SendLeaderMemberReplayLockout(const std::string& name, const DzLockout& lockout, bool is_solo); + void SendLeaderMemberEventLockout(const std::string& name, const DzLockout& lockout); + void SendLeaderMessage(uint16_t chat_type, uint32_t string_id, std::initializer_list args = {}); + const DynamicZone* m_dz = nullptr; Client* m_requester = nullptr; Client* m_leader = nullptr; uint32_t m_leader_id = 0; - uint32_t m_min_players = 0; - uint32_t m_max_players = 0; - bool m_disable_messages = false; - std::string m_expedition_name; + bool m_silent = false; + bool m_is_raid = false; std::string m_leader_name; - std::string m_not_all_added_msg; std::vector m_members; - std::unordered_map m_lockouts; + std::vector m_lockouts; }; #endif diff --git a/zone/fearpath.cpp b/zone/fearpath.cpp index e7fcd458b..e944b057d 100644 --- a/zone/fearpath.cpp +++ b/zone/fearpath.cpp @@ -365,7 +365,7 @@ void Mob::CalculateNewFearpoint() // fallback logic if pathing system can't be used bool inliquid = zone->HasWaterMap() && zone->watermap->InLiquid(glm::vec3(GetPosition())) || zone->IsWaterZone(GetZ()); bool stay_inliquid = (inliquid && IsNPC() && CastToNPC()->IsUnderwaterOnly()); - bool levitating = IsClient() && (FindType(SE_Levitate) || flymode != GravityBehavior::Ground); + bool levitating = IsClient() && (FindType(SpellEffect::Levitate) || flymode != GravityBehavior::Ground); bool open_outdoor_zone = !zone->CanCastOutdoor() && !zone->IsCity(); int loop = 0; diff --git a/zone/forage.cpp b/zone/forage.cpp index 1037c1fbb..2b5933053 100644 --- a/zone/forage.cpp +++ b/zone/forage.cpp @@ -35,8 +35,10 @@ #include "../common/repositories/fishing_repository.h" #include "../common/events/player_event_logs.h" #include "worldserver.h" +#include "queryserv.h" extern WorldServer worldserver; +extern QueryServ *QServ; #ifdef _WINDOWS #define snprintf _snprintf @@ -377,17 +379,24 @@ void Client::GoFish(bool guarantee, bool use_bait) } if (inst) { - if (player_event_logs.IsEventEnabled(PlayerEvent::FISH_SUCCESS)) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::FISH_SUCCESS)) { auto e = PlayerEvent::FishSuccessEvent{ - .item_id = inst->GetItem()->ID, - .item_name = inst->GetItem()->Name, + .item_id = inst->GetItem()->ID, + .augment_1_id = inst->GetAugmentItemID(0), + .augment_2_id = inst->GetAugmentItemID(1), + .augment_3_id = inst->GetAugmentItemID(2), + .augment_4_id = inst->GetAugmentItemID(3), + .augment_5_id = inst->GetAugmentItemID(4), + .augment_6_id = inst->GetAugmentItemID(5), + .item_name = inst->GetItem()->Name, }; - RecordPlayerEventLog(PlayerEvent::FISH_SUCCESS, e); } + CheckItemDiscoverability(inst->GetID()); + if (parse->PlayerHasQuestSub(EVENT_FISH_SUCCESS)) { - std::vector args = { inst }; + std::vector args = {inst}; parse->EventPlayer(EVENT_FISH_SUCCESS, this, "", inst->GetID(), &args); } } @@ -510,14 +519,22 @@ void Client::ForageItem(bool guarantee) { } if (inst) { - if (player_event_logs.IsEventEnabled(PlayerEvent::FORAGE_SUCCESS)) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::FORAGE_SUCCESS)) { auto e = PlayerEvent::ForageSuccessEvent{ - .item_id = inst->GetItem()->ID, - .item_name = inst->GetItem()->Name + .item_id = inst->GetItem()->ID, + .augment_1_id = inst->GetAugmentItemID(0), + .augment_2_id = inst->GetAugmentItemID(1), + .augment_3_id = inst->GetAugmentItemID(2), + .augment_4_id = inst->GetAugmentItemID(3), + .augment_5_id = inst->GetAugmentItemID(4), + .augment_6_id = inst->GetAugmentItemID(5), + .item_name = inst->GetItem()->Name, }; RecordPlayerEventLog(PlayerEvent::FORAGE_SUCCESS, e); } + CheckItemDiscoverability(inst->GetID()); + if (parse->PlayerHasQuestSub(EVENT_FORAGE_SUCCESS)) { std::vector args = { inst }; parse->EventPlayer(EVENT_FORAGE_SUCCESS, this, "", inst->GetID(), &args); diff --git a/zone/gm_commands/databuckets.cpp b/zone/gm_commands/databuckets.cpp index 8164e3ad4..af78f528e 100755 --- a/zone/gm_commands/databuckets.cpp +++ b/zone/gm_commands/databuckets.cpp @@ -1,8 +1,17 @@ #include "../client.h" -#include "../data_bucket.h" +#include "../../common/data_bucket.h" #include "../dialogue_window.h" #include "../../common/repositories/data_buckets_repository.h" +void SendDataBucketsSubCommands(Client *c) +{ + c->Message(Chat::White, "Usage: #databuckets delete [Key] [Character ID] [NPC ID] [Bot ID]"); + c->Message(Chat::White, "Usage: #databuckets edit [Key] [Character ID] [NPC ID] [Bot ID] [Value] [Expires]"); + c->Message(Chat::White, "Usage: #databuckets view [Partial Key] [Character ID] [NPC ID] [Bot ID]"); + c->Message(Chat::White, "Note: Character ID, NPC ID, and Bot ID are optional if not needed, if needed they are required for specificity"); + c->Message(Chat::White, "Note: Edit requires Character ID, NPC ID, Bot ID, and Value, Expires is optional and does not modify the existing expiration time if not provided"); +} + void command_databuckets(Client *c, const Seperator *sep) { const int arguments = sep->argnum; @@ -251,12 +260,3 @@ void command_databuckets(Client *c, const Seperator *sep) c->Message(Chat::White, response.c_str()); } } - -void SendDataBucketsSubCommands(Client *c) -{ - c->Message(Chat::White, "Usage: #databuckets delete [Key] [Character ID] [NPC ID] [Bot ID]"); - c->Message(Chat::White, "Usage: #databuckets edit [Key] [Character ID] [NPC ID] [Bot ID] [Value] [Expires]"); - c->Message(Chat::White, "Usage: #databuckets view [Partial Key] [Character ID] [NPC ID] [Bot ID]"); - c->Message(Chat::White, "Note: Character ID, NPC ID, and Bot ID are optional if not needed, if needed they are required for specificity"); - c->Message(Chat::White, "Note: Edit requires Character ID, NPC ID, Bot ID, and Value, Expires is optional and does not modify the existing expiration time if not provided"); -} diff --git a/zone/gm_commands/devtools.cpp b/zone/gm_commands/devtools.cpp index 477747ddb..c362a6d05 100755 --- a/zone/gm_commands/devtools.cpp +++ b/zone/gm_commands/devtools.cpp @@ -1,5 +1,5 @@ #include "../client.h" -#include "../data_bucket.h" +#include "../../common/data_bucket.h" void command_devtools(Client *c, const Seperator *sep) { diff --git a/zone/gm_commands/door_manipulation.cpp b/zone/gm_commands/door_manipulation.cpp index 4f136798f..614cc4484 100644 --- a/zone/gm_commands/door_manipulation.cpp +++ b/zone/gm_commands/door_manipulation.cpp @@ -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 | Sets selected door incline"); c->Message(Chat::White, "#door opentype | Sets selected door opentype"); + c->Message( + Chat::White, + fmt::format( + "{} | 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( diff --git a/zone/gm_commands/dz.cpp b/zone/gm_commands/dz.cpp index cab117b6d..fe2ce1252 100755 --- a/zone/gm_commands/dz.cpp +++ b/zone/gm_commands/dz.cpp @@ -1,5 +1,5 @@ #include "../client.h" -#include "../expedition.h" +#include "../dynamic_zone.h" void command_dz(Client *c, const Seperator *sep) { @@ -10,151 +10,43 @@ void command_dz(Client *c, const Seperator *sep) if (strcasecmp(sep->arg[1], "cache") == 0) { if (strcasecmp(sep->arg[2], "reload") == 0) { DynamicZone::CacheAllFromDatabase(); - Expedition::CacheAllFromDatabase(); - c->Message( - Chat::White, fmt::format( - "Reloaded [{}] dynamic zone(s) and [{}] expedition(s) from database", - zone->dynamic_zone_cache.size(), zone->expedition_cache.size() - ).c_str()); + c->Message(Chat::White, fmt::format("Reloaded [{}] dynamic zone(s) from database", zone->dynamic_zone_cache.size()).c_str()); } } - else if (strcasecmp(sep->arg[1], "expedition") == 0) { - if (strcasecmp(sep->arg[2], "list") == 0) { - std::vector expeditions; - for (const auto &expedition : zone->expedition_cache) { - expeditions.emplace_back(expedition.second.get()); - } - - std::sort( - expeditions.begin(), expeditions.end(), - [](const Expedition *lhs, const Expedition *rhs) { - return lhs->GetID() < rhs->GetID(); - } - ); - - c->Message(Chat::White, fmt::format("Total Active Expeditions: [{}]", expeditions.size()).c_str()); - for (const auto &expedition : expeditions) { - auto dz = expedition->GetDynamicZone(); - if (!dz) { - LogExpeditions("Expedition [{}] has an invalid dz [{}] in cache", - expedition->GetID(), - expedition->GetDynamicZoneID()); - continue; - } - - auto leader_saylink = Saylink::Silent( - fmt::format( - "#goto {}", - expedition->GetLeaderName() - ), - expedition->GetLeaderName() - ); - auto zone_saylink = Saylink::Silent( - fmt::format( - "#zoneinstance {}", - dz->GetInstanceID() - ), - "zone" - ); - - auto seconds = dz->GetSecondsRemaining(); - - c->Message( - Chat::White, fmt::format( - "expedition id: [{}] dz id: [{}] name: [{}] leader: [{}] {}: [{}]:[{}]:[{}]:[{}] members: [{}] remaining: [{:02}:{:02}:{:02}]", - expedition->GetID(), - expedition->GetDynamicZoneID(), - expedition->GetName(), - leader_saylink, - zone_saylink, - ZoneName(dz->GetZoneID()), - dz->GetZoneID(), - dz->GetInstanceID(), - dz->GetZoneVersion(), - dz->GetMemberCount(), - seconds / 3600, // hours - (seconds / 60) % 60, // minutes - seconds % 60 // seconds - ).c_str()); - } + else if (strcasecmp(sep->arg[1], "destroy") == 0 && sep->IsNumber(2)) { + auto dz_id = std::strtoul(sep->arg[2], nullptr, 10); + if (auto dz = DynamicZone::FindDynamicZoneByID(dz_id)) { + c->Message(Chat::White, fmt::format("Destroying dz [{}] ({})", dz_id, dz->GetName()).c_str()); + dz->RemoveAllMembers(); } - else if (strcasecmp(sep->arg[2], "reload") == 0) { - Expedition::CacheAllFromDatabase(); - c->Message( - Chat::White, fmt::format( - "Reloaded [{}] expeditions to cache from database.", zone->expedition_cache.size() - ).c_str()); - } - else if (strcasecmp(sep->arg[2], "destroy") == 0 && sep->IsNumber(3)) { - auto expedition_id = std::strtoul(sep->arg[3], nullptr, 10); - auto expedition = Expedition::FindCachedExpeditionByID(expedition_id); - if (expedition) { - c->Message( - Chat::White, fmt::format( - "Destroying expedition [{}] ({})", - expedition_id, expedition->GetName()).c_str()); - expedition->GetDynamicZone()->RemoveAllMembers(); - } - else { - c->Message(Chat::Red, fmt::format("Failed to destroy expedition [{}]", sep->arg[3]).c_str()); - } - } - else if (strcasecmp(sep->arg[2], "unlock") == 0 && sep->IsNumber(3)) { - auto expedition_id = std::strtoul(sep->arg[3], nullptr, 10); - auto expedition = Expedition::FindCachedExpeditionByID(expedition_id); - if (expedition) { - c->Message(Chat::White, fmt::format("Unlocking expedition [{}]", expedition_id).c_str()); - expedition->SetLocked(false, ExpeditionLockMessage::None, true); - } - else { - c->Message(Chat::Red, fmt::format("Failed to find expedition [{}]", sep->arg[3]).c_str()); - } + else { + c->Message(Chat::Red, fmt::format("Failed to destroy dz [{}]", sep->arg[3]).c_str()); } } else if (strcasecmp(sep->arg[1], "list") == 0) { - c->Message( - Chat::White, - fmt::format("Total Dynamic Zones (cache): [{}]", zone->dynamic_zone_cache.size()).c_str()); - - std::vector dynamic_zones; - for (const auto &dz : zone->dynamic_zone_cache) { - dynamic_zones.emplace_back(dz.second.get()); + std::vector dynamic_zones; + for (const auto& it : zone->dynamic_zone_cache) { + dynamic_zones.push_back(it.second.get()); } - std::sort( - dynamic_zones.begin(), dynamic_zones.end(), - [](const DynamicZone *lhs, const DynamicZone *rhs) { - return lhs->GetID() < rhs->GetID(); - } - ); - - for (const auto &dz : dynamic_zones) { - auto seconds = dz->GetSecondsRemaining(); - auto zone_saylink = Saylink::Silent( - fmt::format( - "#zoneinstance {}", - dz->GetInstanceID() - ), - "zone" - ); - - std::string aligned_type = fmt::format( - "[{}]", - DynamicZone::GetDynamicZoneTypeName(static_cast(dz->GetType()))); - c->Message( - Chat::White, fmt::format( - "id: [{}] type: {:>10} {}: [{}]:[{}]:[{}] members: [{}] remaining: [{:02}:{:02}:{:02}]", - dz->GetID(), - aligned_type, - zone_saylink, - dz->GetZoneID(), - dz->GetInstanceID(), - dz->GetZoneVersion(), - dz->GetMemberCount(), - seconds / 3600, // hours - (seconds / 60) % 60, // minutes - seconds % 60 // seconds - ).c_str()); + std::ranges::sort(dynamic_zones, {}, &DynamicZone::GetID); + c->Message(Chat::White, fmt::format("Total Dynamic Zones (cache): [{}]", dynamic_zones.size()).c_str()); + for (const DynamicZone* dz : dynamic_zones) { + uint32_t seconds = dz->GetSecondsRemaining(); + c->Message(Chat::White, fmt::format( + "id: [{}] - [{}] - {}: [{}:{}:{}] members: [{}] expires: [{:02}:{:02}:{:02}] leader: [{}]", + dz->GetID(), + DynamicZone::GetDynamicZoneTypeName(dz->GetType()), + Saylink::Silent(fmt::format("#zoneinstance {}", dz->GetInstanceID()), "zone"), + dz->GetZoneID(), + dz->GetInstanceID(), + dz->GetZoneVersion(), + dz->GetMemberCount(), + seconds / 3600, // hours + seconds / 60 % 60, // minutes + seconds % 60, // seconds + Saylink::Silent(fmt::format("#goto {}", dz->GetLeaderName()), dz->GetLeaderName()) + ).c_str()); } } else if (strcasecmp(sep->arg[1], "listdb") == 0) { @@ -163,97 +55,70 @@ void command_dz(Client *c, const Seperator *sep) auto now = std::chrono::system_clock::now(); - for (const auto &dz : dz_list) { - auto expire_time = std::chrono::system_clock::from_time_t(dz.start_time + dz.duration); - auto remaining = std::chrono::duration_cast(expire_time - now); - auto seconds = std::max(0, static_cast(remaining.count())); + for (const auto& dz : dz_list) { + auto expire_time = std::chrono::system_clock::from_time_t(static_cast(dz.start_time) + dz.duration); bool is_expired = now > expire_time; if (!is_expired || strcasecmp(sep->arg[2], "all") == 0) { - auto zone_saylink = is_expired ? "zone" : Saylink::Silent( - fmt::format( - "#zoneinstance {}", - dz.instance - ), - "zone" - ); - - c->Message( - Chat::White, fmt::format( - "id: [{}] type: [{}] {}: [{}]:[{}]:[{}] members: [{}] remaining: [{:02}:{:02}:{:02}]", - dz.id, - DynamicZone::GetDynamicZoneTypeName(static_cast(dz.type)), - zone_saylink, - dz.zone, - dz.instance, - dz.version, - dz.member_count, - seconds / 3600, // hours - (seconds / 60) % 60, // minutes - seconds % 60 // seconds - ).c_str()); + auto seconds = std::max(0, static_cast(std::chrono::duration_cast(expire_time - now).count())); + c->Message(Chat::White, fmt::format( + "id: [{}] - [{}] - {}: [{}:{}:{}] members: [{}] expires: [{:02}:{:02}:{:02}]", + dz.id, + DynamicZone::GetDynamicZoneTypeName(static_cast(dz.type)), + is_expired ? "zone" : Saylink::Silent(fmt::format("#zoneinstance {}", dz.instance), "zone"), + dz.zone, + dz.instance, + dz.version, + dz.member_count, + seconds / 3600, // hours + seconds / 60 % 60, // minutes + seconds % 60 // seconds + ).c_str()); } } } else if (strcasecmp(sep->arg[1], "lockouts") == 0) { if (strcasecmp(sep->arg[2], "remove") == 0 && sep->arg[3][0] != '\0') { if (sep->arg[5][0] == '\0') { - c->Message( - Chat::White, fmt::format( - "Removing [{}] lockouts on [{}].", sep->arg[4][0] ? sep->arg[4] : "all", sep->arg[3] - ).c_str()); + c->Message(Chat::White, fmt::format("Removing [{}] lockouts on [{}].", sep->arg[4][0] ? sep->arg[4] : "all", sep->arg[3]).c_str()); } else { - c->Message( - Chat::White, fmt::format( - "Removing [{}]:[{}] lockout on [{}].", sep->arg[4], sep->arg[5], sep->arg[3] - ).c_str()); + c->Message(Chat::White, fmt::format("Removing [{}]:[{}] lockout on [{}].", sep->arg[4], sep->arg[5], sep->arg[3]).c_str()); } - Expedition::RemoveLockoutsByCharacterName(sep->arg[3], sep->arg[4], sep->arg[5]); + DynamicZone::RemoveCharacterLockouts(sep->arg[3], sep->arg[4], sep->arg[5]); } } else if (strcasecmp(sep->arg[1], "makeleader") == 0 && sep->IsNumber(2) && sep->arg[3][0] != '\0') { - auto expedition_id = std::strtoul(sep->arg[2], nullptr, 10); - auto expedition = Expedition::FindCachedExpeditionByID(expedition_id); - if (expedition) { - auto char_name = FormatName(sep->arg[3]); - c->Message( - Chat::White, - fmt::format("Setting expedition [{}] leader to [{}]", expedition_id, char_name).c_str()); - expedition->SendWorldMakeLeaderRequest(c->CharacterID(), char_name); + uint32_t dz_id = std::strtoul(sep->arg[2], nullptr, 10); + if (auto dz = DynamicZone::FindDynamicZoneByID(dz_id)) { + std::string name = FormatName(sep->arg[3]); + c->Message(Chat::White, fmt::format("Setting expedition [{}] leader to [{}]", dz_id, name).c_str()); + dz->SendWorldMakeLeaderRequest(c->CharacterID(), name); } else { - c->Message(Chat::Red, fmt::format("Failed to find expedition [{}]", expedition_id).c_str()); + c->Message(Chat::Red, fmt::format("Failed to find expedition [{}]", dz_id).c_str()); + } + } + else if (strcasecmp(sep->arg[1], "unlock") == 0 && sep->IsNumber(2)) { + uint32_t dz_id = std::strtoul(sep->arg[2], nullptr, 10); + if (auto dz = DynamicZone::FindDynamicZoneByID(dz_id)) { + c->Message(Chat::White, fmt::format("Unlocking expedition dz [{}]", dz_id).c_str()); + dz->SetLocked(false, true); + } + else { + c->Message(Chat::Red, fmt::format("Failed to find dz [{}]", sep->arg[2]).c_str()); } } else { c->Message(Chat::White, "#dz usage:"); - c->Message( - Chat::White, - "#dz cache reload - reload the current zone cache from db (also reloads expedition cache dependency)" - ); - c->Message(Chat::White, "#dz expedition list - list expeditions in current zone cache"); - c->Message(Chat::White, "#dz expedition reload - reload expedition zone cache from database"); - c->Message( - Chat::White, - "#dz expedition destroy - destroy expedition globally (must be in cache)" - ); - c->Message(Chat::White, "#dz expedition unlock - unlock expedition"); - c->Message(Chat::White, "#dz list - list all dynamic zone instances from current zone cache"); - c->Message( - Chat::White, - "#dz listdb [all] - list dynamic zone instances from database -- 'all' includes expired" - ); + c->Message(Chat::White, "#dz cache reload - reload current zone cache from db (also reloads expedition cache)"); + c->Message(Chat::White, "#dz destroy - destroy dz globally (must be in cache)"); + c->Message(Chat::White, "#dz list - list dynamic zones in current zone cache"); + c->Message(Chat::White, "#dz listdb [all] - list dynamic zones in database -- 'all' includes expired"); c->Message(Chat::White, "#dz lockouts remove - delete all of character's expedition lockouts"); - c->Message( - Chat::White, - "#dz lockouts remove \"\" - delete lockouts by expedition" - ); - c->Message( - Chat::White, - "#dz lockouts remove \"\" \"\" - delete lockout by expedition event" - ); - c->Message(Chat::White, "#dz makeleader - set new expedition leader"); + c->Message(Chat::White, "#dz lockouts remove \"\" - delete lockouts by expedition"); + c->Message(Chat::White, "#dz lockouts remove \"\" \"\" - delete expedition event lockout"); + c->Message(Chat::White, "#dz makeleader - set new expedition leader"); + c->Message(Chat::White, "#dz unlock - unlock expedition"); } } - diff --git a/zone/gm_commands/dzkickplayers.cpp b/zone/gm_commands/dzkickplayers.cpp index 149ccc0af..1f56aee44 100755 --- a/zone/gm_commands/dzkickplayers.cpp +++ b/zone/gm_commands/dzkickplayers.cpp @@ -1,13 +1,11 @@ #include "../client.h" -#include "../expedition.h" void command_dzkickplayers(Client *c, const Seperator *sep) { if (c) { - auto expedition = c->GetExpedition(); - if (expedition) { - expedition->DzKickPlayers(c); + auto dz = c->GetExpedition(); + if (dz) { + dz->DzKickPlayers(c); } } } - diff --git a/zone/gm_commands/emptyinventory.cpp b/zone/gm_commands/emptyinventory.cpp index 582512289..e37167db6 100644 --- a/zone/gm_commands/emptyinventory.cpp +++ b/zone/gm_commands/emptyinventory.cpp @@ -2,31 +2,21 @@ void command_emptyinventory(Client *c, const Seperator *sep) { - auto target = c; + Client* t = c; if (c->GetGM() && c->GetTarget() && c->GetTarget()->IsClient()) { - target = c->GetTarget()->CastToClient(); + t = c->GetTarget()->CastToClient(); } EQ::ItemInstance *item = nullptr; - static const int16 slots[][2] = { - { EQ::invslot::POSSESSIONS_BEGIN, EQ::invslot::POSSESSIONS_END }, - { EQ::invbag::GENERAL_BAGS_BEGIN, EQ::invbag::GENERAL_BAGS_END }, - { EQ::invbag::CURSOR_BAG_BEGIN, EQ::invbag::CURSOR_BAG_END}, - { EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END }, - { EQ::invbag::BANK_BAGS_BEGIN, EQ::invbag::BANK_BAGS_END }, - { EQ::invslot::SHARED_BANK_BEGIN, EQ::invslot::SHARED_BANK_END }, - { EQ::invbag::SHARED_BANK_BAGS_BEGIN, EQ::invbag::SHARED_BANK_BAGS_END }, - }; - int removed_count = 0; - const size_t size = sizeof(slots) / sizeof(slots[0]); - for (int slot_index = 0; slot_index < size; ++slot_index) { - for (int slot_id = slots[slot_index][0]; slot_id <= slots[slot_index][1]; ++slot_id) { - item = target->GetInv().GetItem(slot_id); - if (item) { - int stack_size = std::max(static_cast(item->GetCharges()), 1); - removed_count += stack_size; - target->DeleteItemInInventory(slot_id, 0, true); - } + uint32 removed_count = 0; + + for (const int16& slot_id : t->GetInventorySlots()) { + item = t->GetInv().GetItem(slot_id); + + if (item) { + uint32 stack_size = std::max(static_cast(item->GetCharges()), static_cast(1)); + removed_count += stack_size; + t->DeleteItemInInventory(slot_id, 0, true); } } @@ -35,8 +25,8 @@ void command_emptyinventory(Client *c, const Seperator *sep) Chat::White, fmt::format( "{} {} no items to delete.", - c->GetTargetDescription(target, TargetDescriptionType::UCYou), - c == target ? "have" : "has" + c->GetTargetDescription(t, TargetDescriptionType::UCYou), + c == t ? "have" : "has" ).c_str() ); return; @@ -46,9 +36,9 @@ void command_emptyinventory(Client *c, const Seperator *sep) Chat::White, fmt::format( "Inventory cleared for {}, {} item{} deleted.", - c->GetTargetDescription(target), + c->GetTargetDescription(t), removed_count, removed_count != 1 ? "s" : "" ).c_str() ); -} \ No newline at end of file +} diff --git a/zone/gm_commands/evolving_items.cpp b/zone/gm_commands/evolving_items.cpp new file mode 100644 index 000000000..241a79a50 --- /dev/null +++ b/zone/gm_commands/evolving_items.cpp @@ -0,0 +1,301 @@ +#include "../client.h" +#include "../command.h" +#include "../../common/evolving_items.h" + +void command_evolvingitems(Client *c, const Seperator *sep) +{ + const uint16 arguments = sep->argnum; + if (!arguments) { + SendEvolvingItemsSubCommands(c); + return; + } + + Client* t = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + t = c->GetTarget()->CastToClient(); + } + + const bool is_item = !strcasecmp(sep->arg[1], "item"); + const bool is_target = !strcasecmp(sep->arg[1], "target"); + + if (!is_item && !is_target) { + SendEvolvingItemsSubCommands(c); + return; + } + + if (is_target) { + if (arguments > 1) { + c->Message(Chat::White, "Usage: #evolve target"); + } else { + c->Message(Chat::Red, "Worn Items"); + for (auto const &[key, value]: t->GetInv().GetWorn()) { + if (!value->IsEvolving()) { + continue; + } + + auto item = EvolvingItemsManager::Instance()->GetEvolvingItemsCache().at(value->GetID()); + c->Message( + Chat::Yellow, + fmt::format( + "Evolving Items | {:0d}", + value->GetID() + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Unique ID | {:0d}", + value->GetEvolveUniqueID() + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Final Item ID | {:0d}", + value->GetEvolveFinalItemID() + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Activated | {}", + value->GetEvolveActivated() ? "Yes" : "No" + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Equipped | {}", + value->GetEvolveEquipped() ? "Yes" : "No" + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Current Amount | {:0d}", + value->GetEvolveCurrentAmount() + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Required Amount | {:0d}", + item.required_amount + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Progression (%) | {:.2f}", + value->GetEvolveProgression() + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Type | {}", + item.type + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Subtype | {}", + item.sub_type + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Timer | {}", + value->GetTimers().at("evolve").Enabled() + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Timer Remaining | {}", + value->GetTimers().at("evolve").GetRemainingTime() + ).c_str() + ); + } + + c->Message(Chat::Red, "Personal Items"); + for (auto const &[key, value]: t->GetInv().GetPersonal()) { + if (!value->IsEvolving()) { + continue; + } + + auto item = EvolvingItemsManager::Instance()->GetEvolvingItemsCache().at(value->GetID()); + c->Message( + Chat::Yellow, + fmt::format( + "Evolving Items | {:0d}", + value->GetID() + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Unique ID | {:0d}", + value->GetEvolveUniqueID() + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Final Item ID | {:0d}", + value->GetEvolveFinalItemID() + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Activated | {}", + value->GetEvolveActivated() ? "Yes" : "No" + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Equipped | {}", + value->GetEvolveEquipped() ? "Yes" : "No" + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Current Amount | {:0d}", + value->GetEvolveCurrentAmount() + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Required Amount | {:0d}", + item.required_amount + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Progression (%) | {:.2f}", + value->GetEvolveProgression() + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Type | {}", + item.type + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Subtype | {}", + item.sub_type + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Timer | {}", + value->GetTimers().at("evolve").Enabled() + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Timer Remaining | {}", + value->GetTimers().at("evolve").GetRemainingTime() + ).c_str() + ); + } + } + } else if (is_item) { + if (arguments > 2) { + c->Message(Chat::White, "Usage: #evolve item item_id"); + } else if (sep->IsNumber(2)) { + auto item_id = Strings::ToUnsignedInt(sep->arg[2]); + auto item = c->GetInv().GetItem(c->GetInv().HasItem(item_id)); + if (item) { + c->Message( + Chat::Yellow, + fmt::format( + "Evolving Items | {:0d}", + item->GetID() + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Slot | {:0d}", + c->GetInv().HasItem(item_id) + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Activated | {}", + item->GetEvolveActivated() ? "Yes" : "No" + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Equipped | {}", + item->GetEvolveEquipped() ? "Yes" : "No" + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Unique ID | {:0d}", + item->GetEvolveUniqueID() + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Progression (%) | {:.2f}", + item->GetEvolveProgression() + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Timer | {}", + item->GetTimers().at("evolve").Enabled() + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Timer Remaining | {}", + item->GetTimers().at("evolve").GetRemainingTime() + ).c_str() + ); + } else { + c->Message( + Chat::Red, + fmt::format( + "Item {} could not be found in your inventory.", + item_id + ).c_str() + ); + } + } else { + SendEvolvingItemsSubCommands(c); + } + } +} + +void SendEvolvingItemsSubCommands(Client *c) +{ + c->Message(Chat::White, "#evolve item item_id (Shows evolve values within the iteminstance)"); + c->Message( + Chat::White, + "#evolve target (Shows evolve values within the target's cache - Must have a player target selected.)" + ); +} diff --git a/zone/gm_commands/feature.cpp b/zone/gm_commands/feature.cpp index 41999fe2c..f43f279fa 100644 --- a/zone/gm_commands/feature.cpp +++ b/zone/gm_commands/feature.cpp @@ -83,7 +83,7 @@ void command_feature(Client *c, const Seperator *sep) feature_changed = "Beard Color"; value_changed = f.beardcolor; } else if (is_details) { - if (t->GetRace() != DRAKKIN) { + if (t->GetRace() != Race::Drakkin) { c->Message(Chat::White, "You must target a Drakkin to use this command."); return; } @@ -116,7 +116,7 @@ void command_feature(Client *c, const Seperator *sep) feature_changed = "Helmet Texture"; value_changed = helm_texture; } else if (is_heritage) { - if (t->GetRace() != DRAKKIN) { + if (t->GetRace() != Race::Drakkin) { c->Message(Chat::White, "You must target a Drakkin to use this command."); return; } @@ -148,7 +148,7 @@ void command_feature(Client *c, const Seperator *sep) feature_changed = "Size"; value_changed = size; } else if (is_tattoo) { - if (t->GetRace() != DRAKKIN) { + if (t->GetRace() != Race::Drakkin) { c->Message(Chat::White, "You must target a Drakkin to use this command."); return; } diff --git a/zone/gm_commands/find.cpp b/zone/gm_commands/find.cpp index c2e1ee559..6ee5815cb 100644 --- a/zone/gm_commands/find.cpp +++ b/zone/gm_commands/find.cpp @@ -1,6 +1,8 @@ #include "../client.h" #include "find/aa.cpp" +#include "find/account.cpp" #include "find/body_type.cpp" +#include "find/bot.cpp" #include "find/bug_category.cpp" #include "find/character.cpp" #include "find/class.cpp" @@ -11,6 +13,7 @@ #include "find/faction.cpp" #include "find/item.cpp" #include "find/language.cpp" +#include "find/ldon_theme.cpp" #include "find/npctype.cpp" #include "find/object_type.cpp" #include "find/race.cpp" @@ -36,6 +39,7 @@ void command_find(Client *c, const Seperator *sep) std::vector commands = { Cmd{.cmd = "aa", .u = "aa [Search Criteria]", .fn = FindAA, .a = {"#findaa"}}, + Cmd{.cmd = "account", .u = "account [Search Criteria]", .fn = FindAccount, .a = {"#findaccount"}}, Cmd{.cmd = "body_type", .u = "body_type [Search Criteria]", .fn = FindBodyType, .a = {"#findbodytype"}}, Cmd{.cmd = "bug_category", .u = "bug_category [Search Criteria]", .fn = FindBugCategory, .a = {"#findbugcategory"}}, Cmd{.cmd = "character", .u = "character [Search Criteria]", .fn = FindCharacter, .a = {"#findcharacter"}}, @@ -47,6 +51,7 @@ void command_find(Client *c, const Seperator *sep) Cmd{.cmd = "faction", .u = "faction [Search Criteria]", .fn = FindFaction, .a = {"#findfaction"}}, Cmd{.cmd = "item", .u = "item [Search Criteria]", .fn = FindItem, .a = {"#fi", "#finditem"}}, Cmd{.cmd = "language", .u = "language [Search Criteria]", .fn = FindLanguage, .a = {"#findlanguage"}}, + Cmd{.cmd = "ldon_theme", .u = "ldon_theme [Search Criteria]", .fn = FindLDoNTheme, .a = {"#findldontheme"}}, Cmd{ .cmd = "npctype", .u = "npctype [Search Criteria]", .fn = FindNPCType, .a = { "#fn", @@ -65,6 +70,16 @@ void command_find(Client *c, const Seperator *sep) Cmd{.cmd = "zone", .u = "zone [Search Criteria]", .fn = FindZone, .a = {"#fz", "#findzone"}}, }; + if (RuleB(Bots, Enabled)) { + commands.emplace_back( + Cmd{.cmd = "bot", .u = "bot [Search Criteria]", .fn = FindBot, .a = {"#findbot"}} + ); + + std::sort(commands.begin(), commands.end(), [](const Cmd& a, const Cmd& b) { + return a.cmd < b.cmd; + }); + } + // Check for arguments const auto arguments = sep->argnum; if (!arguments) { diff --git a/zone/gm_commands/find/account.cpp b/zone/gm_commands/find/account.cpp new file mode 100644 index 000000000..1265c8431 --- /dev/null +++ b/zone/gm_commands/find/account.cpp @@ -0,0 +1,50 @@ +#include "../../client.h" +#include "../../common/repositories/account_repository.h" + +void FindAccount(Client *c, const Seperator *sep) +{ + const uint16 arguments = sep->argnum; + if (arguments < 2) { + c->Message(Chat::White, "Usage: #find account [Character Name]"); + c->Message(Chat::White, "Note: Used to print the account ID and name of the account a character belongs to."); + return; + } + + const std::string& character_name = sep->arg[2]; + + const auto& e = CharacterDataRepository::FindByName(database, character_name); + + if (!e.id) { + c->Message( + Chat::White, + fmt::format( + "Character '{}' does not exist.", + character_name + ).c_str() + ); + return; + } + + auto a = AccountRepository::FindOne(database, e.account_id); + + if (!a.id) { + c->Message( + Chat::White, + fmt::format( + "Character '{}' is not attached to an account.", + character_name + ).c_str() + ); + return; + } + + c->Message( + Chat::White, + fmt::format( + "Account {} ({}) owns the character {}.", + a.name, + a.id, + character_name + ).c_str() + ); +} diff --git a/zone/gm_commands/find/bot.cpp b/zone/gm_commands/find/bot.cpp new file mode 100644 index 000000000..061d4c428 --- /dev/null +++ b/zone/gm_commands/find/bot.cpp @@ -0,0 +1,95 @@ +#include "../../client.h" +#include "../../common/repositories/bot_data_repository.h" + +void FindBot(Client *c, const Seperator *sep) +{ + if (sep->IsNumber(2)) { + const auto bot_id = Strings::ToUnsignedInt(sep->arg[2]); + + const auto& e = BotDataRepository::FindOne(content_db, bot_id); + if (!e.bot_id) { + c->Message( + Chat::White, + fmt::format( + "Bot ID {} does not exist or is invalid.", + bot_id + ).c_str() + ); + + return; + } + + c->Message( + Chat::White, + fmt::format( + "Bot ID {} | {}", + bot_id, + e.name + ).c_str() + ); + + return; + } + + const auto search_criteria = Strings::ToLower(sep->argplus[2]); + + const auto& l = BotDataRepository::GetWhere( + content_db, + fmt::format( + "LOWER(`name`) LIKE '%%{}%%' AND `name` NOT LIKE '%-deleted-%' ORDER BY `bot_id` ASC LIMIT 50", + search_criteria + ) + ); + + if (l.empty()) { + c->Message( + Chat::White, + fmt::format( + "No bots found matching '{}'.", + sep->argplus[2] + ).c_str() + ); + } + + auto found_count = 0; + + for (const auto& e : l) { + c->Message( + Chat::White, + fmt::format( + "Bot ID {} | {}", + Strings::Commify(e.bot_id), + e.name + ).c_str() + ); + + found_count++; + + if (found_count == 50) { + break; + } + } + + if (found_count == 50) { + c->Message( + Chat::White, + fmt::format( + "50 Bots found matching '{}', max reached.", + sep->argplus[2] + ).c_str() + ); + + return; + } + + c->Message( + Chat::White, + fmt::format( + "{} Bot{} found matching '{}'.", + found_count, + found_count != 1 ? "s" : "", + sep->argplus[2] + ).c_str() + ); +} + diff --git a/zone/gm_commands/find/character.cpp b/zone/gm_commands/find/character.cpp index fcd1b7c92..9029c252e 100644 --- a/zone/gm_commands/find/character.cpp +++ b/zone/gm_commands/find/character.cpp @@ -4,9 +4,9 @@ void FindCharacter(Client *c, const Seperator *sep) { if (sep->IsNumber(2)) { - const auto character_id = Strings::ToUnsignedInt(sep->arg[2]); + const uint32 character_id = Strings::ToUnsignedInt(sep->arg[2]); - const auto& e = CharacterDataRepository::FindOne(content_db, character_id); + const auto& e = CharacterDataRepository::FindOne(database, character_id); if (!e.id) { c->Message( Chat::White, @@ -31,12 +31,12 @@ void FindCharacter(Client *c, const Seperator *sep) return; } - const auto search_criteria = Strings::ToLower(sep->argplus[2]); + const std::string& search_criteria = Strings::ToLower(sep->argplus[2]); const auto& l = CharacterDataRepository::GetWhere( - content_db, + database, fmt::format( - "LOWER(`name`) LIKE '%%{}%%' ORDER BY `id` ASC LIMIT 50", + "LOWER(`name`) LIKE '%%{}%%' AND `name` NOT LIKE '%-deleted-%' ORDER BY `id` ASC LIMIT 50", search_criteria ) ); @@ -51,7 +51,7 @@ void FindCharacter(Client *c, const Seperator *sep) ); } - auto found_count = 0; + uint32 found_count = 0; for (const auto& e : l) { c->Message( diff --git a/zone/gm_commands/find/class.cpp b/zone/gm_commands/find/class.cpp index 2ce3b1707..a89211e77 100644 --- a/zone/gm_commands/find/class.cpp +++ b/zone/gm_commands/find/class.cpp @@ -57,7 +57,7 @@ void FindClass(Client *c, const Seperator *sep) ( IsPlayerClass(class_id) ? fmt::format( - " | ({})", + " ({})", Strings::Commify(GetPlayerClassBit(class_id)) ) : "" diff --git a/zone/gm_commands/find/ldon_theme.cpp b/zone/gm_commands/find/ldon_theme.cpp new file mode 100644 index 000000000..aa7231297 --- /dev/null +++ b/zone/gm_commands/find/ldon_theme.cpp @@ -0,0 +1,65 @@ +#include "../../client.h" + +void FindLDoNTheme(Client *c, const Seperator *sep) +{ + if (sep->IsNumber(2)) { + const uint32 theme_id = Strings::ToUnsignedInt(sep->arg[2]); + if (LDoNTheme::IsValid(theme_id)) { + c->Message( + Chat::White, + fmt::format( + "Theme {} | {} ({})", + theme_id, + LDoNTheme::GetName(theme_id), + LDoNTheme::GetBitmask(theme_id) + ).c_str() + ); + + return; + } + + c->Message( + Chat::White, + fmt::format( + "Theme ID {} was not found.", + theme_id + ).c_str() + ); + + return; + } + + const std::string& search_criteria = Strings::ToLower(sep->argplus[2]); + + uint32 found_count = 0; + + for (const auto& l : ldon_theme_names) { + const std::string& ldon_theme_name_lower = Strings::ToLower(l.second.first); + if (!Strings::Contains(ldon_theme_name_lower, search_criteria)) { + continue; + } + + c->Message( + Chat::White, + fmt::format( + "Theme {} | {} ({})", + l.first, + l.second.first, + l.second.second + ).c_str() + ); + + found_count++; + } + + c->Message( + Chat::White, + fmt::format( + "{} Theme{} found matching '{}'.", + found_count, + found_count != 1 ? "s" : "", + sep->argplus[2] + ).c_str() + ); +} + diff --git a/zone/gm_commands/find/object_type.cpp b/zone/gm_commands/find/object_type.cpp index 39a94fd5e..1e7dc69aa 100644 --- a/zone/gm_commands/find/object_type.cpp +++ b/zone/gm_commands/find/object_type.cpp @@ -1,3 +1,4 @@ +#include "../../object.h" #include "../../client.h" void FindObjectType(Client *c, const Seperator *sep) diff --git a/zone/gm_commands/find/recipe.cpp b/zone/gm_commands/find/recipe.cpp index 41e58ab4a..5aefe1044 100644 --- a/zone/gm_commands/find/recipe.cpp +++ b/zone/gm_commands/find/recipe.cpp @@ -10,7 +10,7 @@ void FindRecipe(Client *c, const Seperator *sep) const auto recipe_id = static_cast(Strings::ToUnsignedInt(sep->arg[2])); const auto& l = TradeskillRecipeRepository::GetWhere( - database, + content_db, fmt::format("id = {}", recipe_id) ); @@ -56,7 +56,7 @@ void FindRecipe(Client *c, const Seperator *sep) auto found_count = 0; const auto& l = TradeskillRecipeRepository::GetWhere( - database, + content_db, fmt::format( "LOWER(`name`) LIKE '%%{}%%' ORDER BY `id` ASC", search_criteria diff --git a/zone/gm_commands/find/task.cpp b/zone/gm_commands/find/task.cpp index ed08ad534..c51f1892d 100644 --- a/zone/gm_commands/find/task.cpp +++ b/zone/gm_commands/find/task.cpp @@ -11,7 +11,7 @@ void FindTask(Client *c, const Seperator *sep) if (sep->IsNumber(2)) { const auto task_id = Strings::ToUnsignedInt(sep->arg[2]); - const auto& task_name = task_manager->GetTaskName(task_id); + const auto& task_name = TaskManager::Instance()->GetTaskName(task_id); if (task_name.empty()) { c->Message( @@ -41,7 +41,7 @@ void FindTask(Client *c, const Seperator *sep) auto found_count = 0; - for (const auto& t : task_manager->GetTaskData()) { + for (const auto& t : TaskManager::Instance()->GetTaskData()) { const auto& task_name = t.second.title; const auto& task_name_lower = Strings::ToLower(task_name); if (!Strings::Contains(task_name_lower, search_criteria)) { diff --git a/zone/gm_commands/find/zone.cpp b/zone/gm_commands/find/zone.cpp index 74e6e35de..c943f3e1d 100644 --- a/zone/gm_commands/find/zone.cpp +++ b/zone/gm_commands/find/zone.cpp @@ -1,35 +1,51 @@ #include "../../client.h" #include "../../common/content/world_content_service.h" -void FindZone(Client *c, const Seperator *sep) +void FindZone(Client* c, const Seperator* sep) { - std::string query = "SELECT zoneidnumber, short_name, long_name, version FROM zone WHERE "; + std::string where_filter = std::string(); - const auto is_expansion_search = !strcasecmp(sep->arg[2], "expansion"); - const auto is_id_search = Strings::IsNumber(sep->arg[2]); - const auto is_short_name_search = !is_expansion_search && !is_id_search; + const bool is_expansion_search = !strcasecmp(sep->arg[2], "expansion"); + const bool is_id_search = Strings::IsNumber(sep->arg[2]); + const bool is_short_name_search = !is_expansion_search && !is_id_search; std::string search_string; std::string search_type; if (is_expansion_search) { - query += fmt::format( - "expansion = {}", - Strings::ToInt(sep->arg[3]) - ); + int expansion_id = Strings::ToInt(sep->arg[3]); - search_string = Expansion::ExpansionName[Strings::ToInt(sep->arg[3])]; - search_type = "expansion"; + if ( + !EQ::ValueWithin(expansion_id, Expansion::Classic, Expansion::MaxId - 1) && + expansion_id != Expansion::EXPANSION_FILTER_MAX + ) { + c->Message( + Chat::White, + fmt::format( + "Invalid expansion ID: {}. Please enter a value between 0 and {}, or 99.", + expansion_id, + Expansion::MaxId - 1 + ).c_str() + ); + return; + } + + where_filter = fmt::format("expansion = {}", expansion_id); + + if (expansion_id == Expansion::EXPANSION_FILTER_MAX) { + search_string = "Test Zones"; + } else { + search_string = Expansion::ExpansionName[expansion_id]; + } + + search_type = "expansion"; } else if (is_id_search) { - query += fmt::format( - "zoneidnumber = {}", - Strings::ToUnsignedInt(sep->arg[2]) - ); + where_filter = fmt::format("zoneidnumber = {}", Strings::ToUnsignedInt(sep->arg[2])); search_string = sep->arg[2]; search_type = "ID"; } else if (is_short_name_search) { - query += fmt::format( + where_filter = fmt::format( "LOWER(`long_name`) LIKE '%%{}%%' OR LOWER(`short_name`) LIKE '%%{}%%'", Strings::Escape(Strings::ToLower(sep->argplus[2])), Strings::Escape(Strings::ToLower(sep->argplus[2])) @@ -39,35 +55,30 @@ void FindZone(Client *c, const Seperator *sep) search_type = "name"; } - query += " ORDER BY `zoneidnumber` ASC LIMIT 50"; + where_filter += " ORDER BY `zoneidnumber` ASC LIMIT 50"; - auto results = content_db.QueryDatabase(query); - if (!results.Success() || !results.RowCount()) { + const auto& l = ZoneRepository::GetWhere(content_db, where_filter); + + if (l.empty()) { c->Message(Chat::White, "No zones were found matching your search criteria."); - c->Message(Chat::White, query.c_str()); return; } - auto found_count = 0; - - for (auto row : results) { - const auto zone_id = Strings::ToUnsignedInt(row[0]); - const std::string& short_name = row[1]; - const std::string& long_name = row[2]; - const auto version = Strings::ToInt(row[3]); + uint32 found_count = 0; + for (const auto& e : l) { c->Message( Chat::White, fmt::format( "{}{} {} ({}) (ID {}){}", ( - version == 0 ? + e.version == 0 ? fmt::format( "{} | ", Saylink::Silent( fmt::format( "#zone {}", - short_name + e.short_name ), "Zone" ) @@ -79,20 +90,20 @@ void FindZone(Client *c, const Seperator *sep) Saylink::Silent( fmt::format( "#gmzone {} {}", - short_name, - version + e.short_name, + e.version ), "GM Zone" ) ), - long_name, - short_name, - zone_id, + e.long_name, + e.short_name, + e.zoneidnumber, ( - version != 0 ? + e.version != 0 ? fmt::format( " (Version {})", - version + e.version ) : "" ) @@ -110,7 +121,7 @@ void FindZone(Client *c, const Seperator *sep) c->Message( Chat::White, fmt::format( - "50 Zones found matching '{}' of '{}'.", + "50 Zones found matching '{}' of '{}', max reached.", search_type, search_string ).c_str() diff --git a/zone/gm_commands/gmzone.cpp b/zone/gm_commands/gmzone.cpp index d10885d62..a26a09d05 100755 --- a/zone/gm_commands/gmzone.cpp +++ b/zone/gm_commands/gmzone.cpp @@ -1,5 +1,5 @@ #include "../client.h" -#include "../data_bucket.h" +#include "../../common/data_bucket.h" void command_gmzone(Client *c, const Seperator *sep) { diff --git a/zone/gm_commands/goto.cpp b/zone/gm_commands/goto.cpp index 7ef84f1fa..9ab3ad4d1 100755 --- a/zone/gm_commands/goto.cpp +++ b/zone/gm_commands/goto.cpp @@ -57,7 +57,7 @@ void command_goto(Client *c, const Seperator *sep) Strings::ToFloat(sep->arg[1]), Strings::ToFloat(sep->arg[2]), Strings::ToFloat(sep->arg[3]), - sep->arg[4] && Strings::IsFloat(sep->arg[4]) ? Strings::ToFloat(sep->arg[1]) : c->GetHeading() + sep->arg[4] && Strings::IsFloat(sep->arg[4]) ? Strings::ToFloat(sep->arg[4]) : c->GetHeading() ); c->MovePC( zone->GetZoneID(), diff --git a/zone/gm_commands/guild.cpp b/zone/gm_commands/guild.cpp index 6e1f66d1c..97d280e80 100755 --- a/zone/gm_commands/guild.cpp +++ b/zone/gm_commands/guild.cpp @@ -1,11 +1,29 @@ #include "../client.h" #include "../worldserver.h" +#include "../queryserv.h" extern WorldServer worldserver; +extern QueryServ *QServ; #include "../guild_mgr.h" #include "../doors.h" +void SendGuildSubCommands(Client* c) +{ + c->Message(Chat::White, "#guild create [Character ID|Character Name] [Guild Name]"); + c->Message(Chat::White, "#guild delete [Guild ID]"); + c->Message(Chat::White, "#guild details [Guild ID]"); + c->Message(Chat::White, "#guild help"); + c->Message(Chat::White, "#guild info [Guild ID]"); + c->Message(Chat::White, "#guild list"); + c->Message(Chat::White, "#guild rename [Guild ID] [New Name]"); + c->Message(Chat::White, "#guild search [Search Criteria]"); + c->Message(Chat::White, "#guild set [Character ID|Character Name] [Guild ID] (Guild ID 0 is Guildless)"); + c->Message(Chat::White, "#guild setleader [Guild ID] [Character ID|Character Name]"); + c->Message(Chat::White, "#guild setrank [Character ID|Character Name] [Rank]"); + c->Message(Chat::White, "#guild status [Character ID|Character Name]"); +} + void command_guild(Client* c, const Seperator* sep) { const auto arguments = sep->argnum; @@ -89,6 +107,8 @@ void command_guild(Client* c, const Seperator* sep) else { auto guild_name = sep->argplus[3]; auto guild_id = guild_mgr.CreateGuild(sep->argplus[3], leader_id); + auto leader = entity_list.GetClientByCharID(leader_id); + LogGuilds( "[{}]: Creating guild [{}] with leader [{}] with GM command. It was given id [{}]", @@ -113,7 +133,7 @@ void command_guild(Client* c, const Seperator* sep) ).c_str() ); - if (!guild_mgr.SetGuild(leader_id, guild_id, GUILD_LEADER)) { + if (!guild_mgr.SetGuild(leader, guild_id, GUILD_LEADER)) { c->Message( Chat::White, fmt::format( @@ -266,6 +286,7 @@ void command_guild(Client* c, const Seperator* sep) database.GetCharacterID(sep->arg[2]) ); auto character_name = database.GetCharNameByID(character_id); + auto client = entity_list.GetClientByCharID(character_id); if (!character_id || character_name.empty()) { c->Message( Chat::White, @@ -303,14 +324,14 @@ void command_guild(Client* c, const Seperator* sep) "{} ({}) has {} put into {} ({}).", character_name, character_id, - guild_mgr.SetGuild(character_id, guild_id, GUILD_MEMBER) ? "been" : "failed to be", + guild_mgr.SetGuild(client, guild_id, GUILD_MEMBER) ? "been" : "failed to be", guild_mgr.GetGuildNameByID(guild_id), guild_id ).c_str() ); } else { - guild_mgr.SetGuild(character_id, GUILD_NONE, 0); + guild_mgr.SetGuild(client, GUILD_NONE, 0); c->Message( Chat::White, fmt::format( @@ -600,23 +621,74 @@ void command_guild(Client* c, const Seperator* sep) else { auto guild_id = Strings::ToUnsignedInt(sep->arg[2]); auto guild = guild_mgr.GetGuildByGuildID(guild_id); - c->SendGuildMembersList(); + + // PlayerEvent::LootItemEvent e{}; + // e.charges = -1; + // e.corpse_name = "Test Corpse Name"; + // e.item_id = 123456789; + // e.item_name = "Test Item Name"; + // e.npc_id = 987654321; + // e.augment_1_id = 11; + // e.augment_2_id = 0; + // e.augment_3_id = 0; + // e.augment_4_id = 44; + // e.augment_5_id = 55; + // e.augment_6_id = 66; + // + // RecordPlayerEventLogWithClient(c, PlayerEvent::LOOT_ITEM, e); + // + // PlayerEvent::DestroyItemEvent e2{}; + // e2.charges = -1; + // e2.attuned = true; + // e.augment_1_id = 11; + // e.augment_2_id = 0; + // e.augment_3_id = 0; + // e.augment_4_id = 44; + // e.augment_5_id = 55; + // e.augment_6_id = 66; + // e2.item_id = 123456789; + // e2.item_name = "Test Item Destroy Name"; + // e2.reason = "Test Item Destroy Reason"; + // + // RecordPlayerEventLogWithClient(c, PlayerEvent::ITEM_DESTROY, e2); + // auto id = Strings::ToUnsignedInt(sep->arg[3]); + // //auto guild = guild_mgr.GetGuildByGuildID(guild_id); + // // c->SendGuildMembersList(); + // auto bank = GuildBanks->GetGuildBank(guild_id); + // if (id == 1) { + // for (auto &[key, item]: bank->items.main_area) { + // auto i = ItemsRepository::FindOne(content_db, item.item_id); + // c->Message(Chat::Yellow, fmt::format("key:{:02} item:{:05} Name:{:40} Qty:{:40} Slot:{}", + // key, item.item_id, i.Name, item.quantity, item.slot).c_str()); + // } + // return; + // } + // if (id == 2) { + // for (auto &[key, item]: bank->items.deposit_area) { + // auto i = ItemsRepository::FindOne(content_db, item.item_id); + // c->Message(Chat::Yellow, fmt::format("key:{:02} item:{:05} Name:{} Qty:{:40} Slot:{}", + // key, item.item_id, i.Name, item.quantity, item.slot).c_str()); + // } + // return; + // } + // if (id == 3) { + // for (auto &[key, item]: bank->items.main_area) { + // if (item.item_id == 30416) { + // auto i = ItemsRepository::FindOne(content_db, item.item_id); + // c->Message(Chat::Yellow, fmt::format("key:{:02} item:{:05} Name:{:40} Qty:{:40} Slot:{}", + // key, item.item_id, i.Name, item.quantity, item.slot).c_str()); + // } + // } + // return; + // } + // if (id == 4) { + // c->Message(Chat::Yellow, "Guild Test 4"); + // auto inst = database.CreateItem(30416, 30); + // database.UpdateInventorySlot(c->CharacterID(), inst, -1); + // safe_delete(inst); + // + // return; + // } } } -} - -void SendGuildSubCommands(Client* c) -{ - c->Message(Chat::White, "#guild create [Character ID|Character Name] [Guild Name]"); - c->Message(Chat::White, "#guild delete [Guild ID]"); - c->Message(Chat::White, "#guild details [Guild ID]"); - c->Message(Chat::White, "#guild help"); - c->Message(Chat::White, "#guild info [Guild ID]"); - c->Message(Chat::White, "#guild list"); - c->Message(Chat::White, "#guild rename [Guild ID] [New Name]"); - c->Message(Chat::White, "#guild search [Search Criteria]"); - c->Message(Chat::White, "#guild set [Character ID|Character Name] [Guild ID] (Guild ID 0 is Guildless)"); - c->Message(Chat::White, "#guild setleader [Guild ID] [Character ID|Character Name]"); - c->Message(Chat::White, "#guild setrank [Character ID|Character Name] [Rank]"); - c->Message(Chat::White, "#guild status [Character ID|Character Name]"); -} +} \ No newline at end of file diff --git a/zone/gm_commands/illusion_block.cpp b/zone/gm_commands/illusion_block.cpp new file mode 100644 index 000000000..8aa274b4a --- /dev/null +++ b/zone/gm_commands/illusion_block.cpp @@ -0,0 +1,58 @@ +#include "../dialogue_window.h" +#include "../client.h" + +void command_illusion_block(Client* c, const Seperator* sep) +{ + int arguments = sep->argnum; + if (!arguments || !strcasecmp(sep->arg[1], "help")) { + BotCommandHelpParams p; + + p.description = { "Toggles whether or not you will block the illusion effects of spells cast by players or bots." }; + p.notes = { + "- This will still allow other portions of a spell to land on you.", + "- Spells cast by NPCs will ignore this setting and land as normal." + }; + p.example_format = { fmt::format("{} [value]", sep->arg[0]) }; + p.examples_one = + { + "To enable illusion block:", + fmt::format( + "{} 1", + sep->arg[0] + ) + }; + p.examples_two = + { + "To disable illusion block:", + fmt::format( + "{} 0", + sep->arg[0] + ) + }; + + std::string popup_text = c->SendBotCommandHelpWindow(p); + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + + return; + } + + if (sep->IsNumber(1)) { + int set_status = atoi(sep->arg[1]); + if (EQ::ValueWithin(set_status, 0, 1)) { + c->SetIllusionBlock(set_status); + c->Message(Chat::Green, "Your Illusion Block has been %s.", (set_status ? "enabled" : "disabled")); + } + else { + c->Message(Chat::Yellow, "You must enter 0 for disabled or 1 for enabled."); + return; + } + } + else if (!strcasecmp(sep->arg[1], "current")) { + c->Message(Chat::Green, "You're currently %s illusions.", (c->GetIllusionBlock() ? "blocking" : "allowing")); + } + else { + c->Message(Chat::Yellow , "Incorrect argument, use %s help for a list of options.", sep->arg[0]); + } +} diff --git a/zone/gm_commands/list.cpp b/zone/gm_commands/list.cpp index 5019c09b8..d39b424ba 100755 --- a/zone/gm_commands/list.cpp +++ b/zone/gm_commands/list.cpp @@ -2,6 +2,7 @@ #include "../corpse.h" #include "../object.h" #include "../doors.h" +#include "../command.h" struct UniqueEntity { uint16 entity_id; @@ -74,20 +75,22 @@ void command_list(Client *c, const Seperator *sep) if (is_bots) { const auto& l = entity_list.GetBotList(); - for (const auto& e: l) { + for (const auto& e : l) { + Bot* entity = e.second; + entity_count++; - const std::string& entity_name = Strings::ToLower(e->GetName()); + const std::string& entity_name = Strings::ToLower(entity->GetName()); if (!search_string.empty() && !Strings::Contains(entity_name, search_string)) { continue; } unique_entities.emplace_back( UniqueEntity{ - .entity_id = e->GetID(), - .entity_name = e->GetName(), - .unique_id = e->GetBotID(), - .position = e->GetPosition() + .entity_id = entity->GetID(), + .entity_name = entity->GetName(), + .unique_id = entity->GetBotID(), + .position = entity->GetPosition() } ); diff --git a/zone/gm_commands/loc.cpp b/zone/gm_commands/loc.cpp index 62fdf4be4..b7b627fcb 100755 --- a/zone/gm_commands/loc.cpp +++ b/zone/gm_commands/loc.cpp @@ -1,4 +1,5 @@ #include "../client.h" +#include "../water_map.h" void command_loc(Client *c, const Seperator *sep) { @@ -9,6 +10,19 @@ void command_loc(Client *c, const Seperator *sep) auto target_position = target->GetPosition(); + // check los benchmark + BenchTimer timer; + for (int i = 0; i < 1000; i++) { + zone->zonemap->CheckLoS(c->GetPosition(), target_position); + } + c->Message( + Chat::White, + fmt::format( + "CheckLoS benchmark took [{}]", + timer.elapsed() + ).c_str() + ); + c->Message( Chat::White, fmt::format( diff --git a/zone/gm_commands/logs.cpp b/zone/gm_commands/logs.cpp index 3cd590e6d..366a56c79 100755 --- a/zone/gm_commands/logs.cpp +++ b/zone/gm_commands/logs.cpp @@ -76,7 +76,7 @@ void command_logs(Client *c, const Seperator *sep) std::vector gmsay; for (int i = 0; i <= 2; i++) { - if (LogSys.log_settings[index].log_to_gmsay == i) { + if (EQEmuLogSys::Instance()->log_settings[index].log_to_gmsay == i) { gmsay.emplace_back(std::to_string(i)); continue; } @@ -90,7 +90,7 @@ void command_logs(Client *c, const Seperator *sep) std::vector file; for (int i = 0; i <= 2; i++) { - if (LogSys.log_settings[index].log_to_file == i) { + if (EQEmuLogSys::Instance()->log_settings[index].log_to_file == i) { file.emplace_back(std::to_string(i)); continue; } @@ -104,7 +104,7 @@ void command_logs(Client *c, const Seperator *sep) std::vector console; for (int i = 0; i <= 2; i++) { - if (LogSys.log_settings[index].log_to_console == i) { + if (EQEmuLogSys::Instance()->log_settings[index].log_to_console == i) { console.emplace_back(std::to_string(i)); continue; } @@ -118,7 +118,7 @@ void command_logs(Client *c, const Seperator *sep) std::vector discord; for (int i = 0; i <= 2; i++) { - if (LogSys.log_settings[index].log_to_discord == i) { + if (EQEmuLogSys::Instance()->log_settings[index].log_to_discord == i) { discord.emplace_back(std::to_string(i)); continue; } @@ -182,9 +182,7 @@ void command_logs(Client *c, const Seperator *sep) } else if (is_reload) { c->Message(Chat::White, "Attempting to reload Log Settings globally."); - auto pack = new ServerPacket(ServerOP_ReloadLogs, 0); - worldserver.SendPacket(pack); - safe_delete(pack); + worldserver.SendReload(ServerReload::Type::Logs); } else if (is_set && sep->IsNumber(3)) { auto logs_set = false; @@ -208,16 +206,16 @@ void command_logs(Client *c, const Seperator *sep) auto setting = Strings::ToUnsignedInt(sep->arg[4]); if (is_console) { - LogSys.log_settings[category_id].log_to_console = setting; + EQEmuLogSys::Instance()->log_settings[category_id].log_to_console = setting; } else if (is_file) { - LogSys.log_settings[category_id].log_to_file = setting; + EQEmuLogSys::Instance()->log_settings[category_id].log_to_file = setting; } else if (is_gmsay) { - LogSys.log_settings[category_id].log_to_gmsay = setting; + EQEmuLogSys::Instance()->log_settings[category_id].log_to_gmsay = setting; } else if (is_discord) { - LogSys.log_settings[category_id].log_to_discord = setting; + EQEmuLogSys::Instance()->log_settings[category_id].log_to_discord = setting; } if (logs_set) { @@ -233,7 +231,7 @@ void command_logs(Client *c, const Seperator *sep) ); } - LogSys.log_settings[category_id].is_category_enabled = setting ? 1 : 0; + EQEmuLogSys::Instance()->log_settings[category_id].is_category_enabled = setting ? 1 : 0; } } diff --git a/zone/gm_commands/lootsim.cpp b/zone/gm_commands/lootsim.cpp index b61957f1d..783206219 100755 --- a/zone/gm_commands/lootsim.cpp +++ b/zone/gm_commands/lootsim.cpp @@ -14,9 +14,9 @@ void command_lootsim(Client *c, const Seperator *sep) auto log_enabled = arguments > 3 ? Strings::ToUnsignedInt(sep->arg[4]) : false; // temporarily disable loot logging unless set explicitly - LogSys.log_settings[Logs::Loot].log_to_console = log_enabled ? LogSys.log_settings[Logs::Loot].log_to_console : 0; - LogSys.log_settings[Logs::Loot].log_to_file = log_enabled ? LogSys.log_settings[Logs::Loot].log_to_file : 0; - LogSys.log_settings[Logs::Loot].log_to_gmsay = log_enabled ? LogSys.log_settings[Logs::Loot].log_to_gmsay : 0; + EQEmuLogSys::Instance()->log_settings[Logs::Loot].log_to_console = log_enabled ? EQEmuLogSys::Instance()->log_settings[Logs::Loot].log_to_console : 0; + EQEmuLogSys::Instance()->log_settings[Logs::Loot].log_to_file = log_enabled ? EQEmuLogSys::Instance()->log_settings[Logs::Loot].log_to_file : 0; + EQEmuLogSys::Instance()->log_settings[Logs::Loot].log_to_gmsay = log_enabled ? EQEmuLogSys::Instance()->log_settings[Logs::Loot].log_to_gmsay : 0; auto npc_type = content_db.LoadNPCTypesData(npc_id); if (npc_type) { @@ -182,7 +182,7 @@ void command_lootsim(Client *c, const Seperator *sep) ); c->SendChatLineBreak(); - LogSys.LoadLogDatabaseSettings(); + EQEmuLogSys::Instance()->LoadLogDatabaseSettings(); } } else { diff --git a/zone/gm_commands/modifynpcstat.cpp b/zone/gm_commands/modifynpcstat.cpp index 463e625f1..124f8ceca 100755 --- a/zone/gm_commands/modifynpcstat.cpp +++ b/zone/gm_commands/modifynpcstat.cpp @@ -1,63 +1,5 @@ #include "../client.h" -void command_modifynpcstat(Client *c, const Seperator *sep) -{ - auto arguments = sep->argnum; - if (!arguments) { - c->Message(Chat::White, "Usage: #modifynpcstat [Stat] [Value]"); - ListModifyNPCStatMap(c); - return; - } - - if (!c->GetTarget() || !c->GetTarget()->IsNPC()) { - c->Message(Chat::White, "You must target an NPC to use this command."); - return; - } - - auto target = c->GetTarget()->CastToNPC(); - - const std::string& stat = sep->arg[1] ? sep->arg[1] : ""; - const std::string& value = sep->arg[2] ? sep->arg[2] : ""; - - if (stat.empty() || value.empty()) { - c->Message(Chat::White, "Usage: #modifynpcstat [Stat] [Value]"); - ListModifyNPCStatMap(c); - return; - } - - auto stat_description = GetModifyNPCStatDescription(stat); - if (!stat_description.length()) { - c->Message( - Chat::White, - fmt::format( - "Stat '{}' does not exist.", - stat - ).c_str() - ); - return; - } - - target->ModifyNPCStat(stat, value); - - c->Message( - Chat::White, - fmt::format( - "Stat Modified | Target: {}", - c->GetTargetDescription(target) - ).c_str() - ); - - c->Message( - Chat::White, - fmt::format( - "Stat Modified | Stat: {} ({}) Value: {}", - GetModifyNPCStatDescription(stat), - stat, - value - ).c_str() - ); -} - std::map GetModifyNPCStatMap() { std::map identifiers_map = { @@ -134,3 +76,61 @@ void ListModifyNPCStatMap(Client *c) ); } } + +void command_modifynpcstat(Client *c, const Seperator *sep) +{ + auto arguments = sep->argnum; + if (!arguments) { + c->Message(Chat::White, "Usage: #modifynpcstat [Stat] [Value]"); + ListModifyNPCStatMap(c); + return; + } + + if (!c->GetTarget() || !c->GetTarget()->IsNPC()) { + c->Message(Chat::White, "You must target an NPC to use this command."); + return; + } + + auto target = c->GetTarget()->CastToNPC(); + + const std::string& stat = sep->arg[1] ? sep->arg[1] : ""; + const std::string& value = sep->arg[2] ? sep->arg[2] : ""; + + if (stat.empty() || value.empty()) { + c->Message(Chat::White, "Usage: #modifynpcstat [Stat] [Value]"); + ListModifyNPCStatMap(c); + return; + } + + auto stat_description = GetModifyNPCStatDescription(stat); + if (!stat_description.length()) { + c->Message( + Chat::White, + fmt::format( + "Stat '{}' does not exist.", + stat + ).c_str() + ); + return; + } + + target->ModifyNPCStat(stat, value); + + c->Message( + Chat::White, + fmt::format( + "Stat Modified | Target: {}", + c->GetTargetDescription(target) + ).c_str() + ); + + c->Message( + Chat::White, + fmt::format( + "Stat Modified | Stat: {} ({}) Value: {}", + GetModifyNPCStatDescription(stat), + stat, + value + ).c_str() + ); +} diff --git a/zone/gm_commands/mystats.cpp b/zone/gm_commands/mystats.cpp index 69343b304..fc16929d4 100755 --- a/zone/gm_commands/mystats.cpp +++ b/zone/gm_commands/mystats.cpp @@ -1,3 +1,4 @@ +#include "../bot.h" #include "../client.h" void command_mystats(Client *c, const Seperator *sep) diff --git a/zone/gm_commands/npcedit.cpp b/zone/gm_commands/npcedit.cpp index a7ee685e2..ff0080e67 100755 --- a/zone/gm_commands/npcedit.cpp +++ b/zone/gm_commands/npcedit.cpp @@ -3,6 +3,116 @@ #include "../raids.h" #include "../../common/repositories/npc_types_repository.h" +void SendNPCEditSubCommands(Client *c) +{ + c->Message(Chat::White, "Usage: #npcedit name [Name] - Sets an NPC's Name"); + c->Message(Chat::White, "Usage: #npcedit lastname [Last Name] - Sets an NPC's Last Name"); + c->Message(Chat::White, "Usage: #npcedit level [Level] - Sets an NPC's Level"); + c->Message(Chat::White, "Usage: #npcedit race [Race ID] - Sets an NPC's Race"); + c->Message(Chat::White, "Usage: #npcedit class [Class ID] - Sets an NPC's Class"); + c->Message(Chat::White, "Usage: #npcedit bodytype [Body Type ID] - Sets an NPC's Bodytype"); + c->Message(Chat::White, "Usage: #npcedit hp [HP] - Sets an NPC's HP"); + c->Message(Chat::White, "Usage: #npcedit mana [Mana] - Sets an NPC's Mana"); + c->Message(Chat::White, "Usage: #npcedit gender [Gender ID] - Sets an NPC's Gender"); + c->Message(Chat::White, "Usage: #npcedit texture [Texture] - Sets an NPC's Texture"); + c->Message(Chat::White, "Usage: #npcedit helmtexture [Helmet Texture] - Sets an NPC's Helmet Texture"); + c->Message(Chat::White, "Usage: #npcedit herosforgemodel [Model Number] - Sets an NPC's Hero's Forge Model"); + c->Message(Chat::White, "Usage: #npcedit size [Size] - Sets an NPC's Size"); + c->Message(Chat::White, "Usage: #npcedit hpregen [HP Regen] - Sets an NPC's HP Regen Rate Per Tick"); + c->Message(Chat::White, "Usage: #npcedit hp_regen_per_second [HP Regen] - Sets an NPC's HP Regen Rate Per Second"); + c->Message(Chat::White, "Usage: #npcedit manaregen [Mana Regen] - Sets an NPC's Mana Regen Rate Per Tick"); + c->Message(Chat::White, "Usage: #npcedit loottable [Loottable ID] - Sets an NPC's Loottable ID"); + c->Message(Chat::White, "Usage: #npcedit merchantid [Merchant ID] - Sets an NPC's Merchant ID"); + c->Message(Chat::White, "Usage: #npcedit alt_currency_id [Alternate Currency ID] - Sets an NPC's Alternate Currency ID"); + c->Message(Chat::White, "Usage: #npcedit spell [Spell List ID] - Sets an NPC's Spells List ID"); + c->Message(Chat::White, "Usage: #npcedit npc_spells_effects_id [Spell Effects ID] - Sets an NPC's Spell Effects ID"); + c->Message(Chat::White, "Usage: #npcedit faction [Faction ID] - Sets an NPC's Faction ID"); + c->Message(Chat::White, "Usage: #npcedit adventure_template_id [Template ID] - Sets an NPC's Adventure Template ID"); + c->Message(Chat::White, "Usage: #npcedit trap_template [Template ID] - Sets an NPC's Trap Template ID"); + c->Message(Chat::White, "Usage: #npcedit damage [Minimum] [Maximum] - Sets an NPC's Damage"); + c->Message(Chat::White, "Usage: #npcedit attackcount [Attack Count] - Sets an NPC's Attack Count"); + c->Message(Chat::White, "Usage: #npcedit special_attacks [Special Attacks] - Sets an NPC's Special Attacks"); + c->Message(Chat::White, "Usage: #npcedit special_abilities [Special Abilities] - Sets an NPC's Special Abilities"); + c->Message(Chat::White, "Usage: #npcedit aggroradius [Radius] - Sets an NPC's Aggro Radius"); + c->Message(Chat::White, "Usage: #npcedit assistradius [Radius] - Sets an NPC's Assist Radius"); + c->Message(Chat::White, "Usage: #npcedit featuresave - Saves an NPC's current facial features to the database"); + c->Message(Chat::White, "Usage: #npcedit armortint_id [Armor Tint ID] - Sets an NPC's Armor Tint ID"); + c->Message(Chat::White, "Usage: #npcedit color [Red] [Green] [Blue] - Sets an NPC's Red, Green, and Blue armor tint"); + c->Message(Chat::White, "Usage: #npcedit ammoidfile [ID File] - Sets an NPC's Ammo ID File"); + c->Message(Chat::White, "Usage: #npcedit weapon [Primary Model] [Secondary Model] - Sets an NPC's Primary and Secondary Weapon Model"); + c->Message(Chat::White, "Usage: #npcedit meleetype [Primary Type] [Secondary Type] - Sets an NPC's Melee Skill Types"); + c->Message(Chat::White, "Usage: #npcedit rangedtype [Type] - Sets an NPC's Ranged Skill Type"); + c->Message(Chat::White, "Usage: #npcedit runspeed [Run Speed] - Sets an NPC's Run Speed"); + c->Message(Chat::White, "Usage: #npcedit mr [Resistance] - Sets an NPC's Magic Resistance"); + c->Message(Chat::White, "Usage: #npcedit pr [Resistance] - Sets an NPC's Poison Resistance"); + c->Message(Chat::White, "Usage: #npcedit dr [Resistance] - Sets an NPC's Disease Resistance"); + c->Message(Chat::White, "Usage: #npcedit fr [Resistance] - Sets an NPC's Fire Resistance"); + c->Message(Chat::White, "Usage: #npcedit cr [Resistance] - Sets an NPC's Cold Resistance"); + c->Message(Chat::White, "Usage: #npcedit corrup [Resistance] - Sets an NPC's Corruption Resistance"); + c->Message(Chat::White, "Usage: #npcedit phr [Resistance] - Sets and NPC's Physical Resistance"); + c->Message(Chat::White, "Usage: #npcedit seeinvis [Flag] - Sets an NPC's See Invisible Flag [0 = Cannot See Invisible, 1 = Can See Invisible]"); + c->Message(Chat::White, "Usage: #npcedit seeinvisundead [Flag] - Sets an NPC's See Invisible vs. Undead Flag [0 = Cannot See Invisible vs. Undead, 1 = Can See Invisible vs. Undead]"); + c->Message(Chat::White, "Usage: #npcedit qglobal [Flag] - Sets an NPC's Quest Global Flag [0 = Quest Globals Off, 1 = Quest Globals On]"); + c->Message(Chat::White, "Usage: #npcedit ac [Armor Class] - Sets an NPC's Armor Class"); + c->Message(Chat::White, "Usage: #npcedit npcaggro [Flag] - Sets an NPC's NPC Aggro Flag [0 = Aggro NPCs Off, 1 = Aggro NPCs On]"); + c->Message(Chat::White, "Usage: #npcedit spawn_limit [Limit] - Sets an NPC's Spawn Limit Counter"); + c->Message(Chat::White, "Usage: #npcedit attackspeed [Attack Speed] - Sets an NPC's Attack Speed Modifier"); + c->Message(Chat::White, "Usage: #npcedit attackdelay [Attack Delay] - Sets an NPC's Attack Delay"); + c->Message(Chat::White, "Usage: #npcedit findable [Flag] - Sets an NPC's Findable Flag [0 = Not Findable, 1 = Findable]"); + c->Message(Chat::White, "Usage: #npcedit str [Strength] - Sets an NPC's Strength"); + c->Message(Chat::White, "Usage: #npcedit sta [Stamina] - Sets an NPC's Stamina"); + c->Message(Chat::White, "Usage: #npcedit agi [Agility] - Sets an NPC's Agility"); + c->Message(Chat::White, "Usage: #npcedit dex [Dexterity] - Sets an NPC's Dexterity"); + c->Message(Chat::White, "Usage: #npcedit int [Intelligence] - Sets an NPC's Intelligence"); + c->Message(Chat::White, "Usage: #npcedit wis [Wisdom] - Sets an NPC's Wisdom"); + c->Message(Chat::White, "Usage: #npcedit cha [Charisma] - Sets an NPC's Charisma"); + c->Message(Chat::White, "Usage: #npcedit seehide [Flag] - Sets an NPC's See Hide Flag [0 = Cannot See Hide, 1 = Can See Hide]"); + c->Message(Chat::White, "Usage: #npcedit seeimprovedhide [Flag] - Sets an NPC's See Improved Hide Flag [0 = Cannot See Improved Hide, 1 = Can See Improved Hide]"); + c->Message(Chat::White, "Usage: #npcedit trackable [Flag] - Sets an NPC's Trackable Flag [0 = Not Trackable, 1 = Trackable]"); + c->Message(Chat::White, "Usage: #npcedit atk [Attack] - Sets an NPC's Attack"); + c->Message(Chat::White, "Usage: #npcedit accuracy [Accuracy] - Sets an NPC's Accuracy"); + c->Message(Chat::White, "Usage: #npcedit avoidance [Avoidance] - Sets an NPC's Avoidance"); + c->Message(Chat::White, "Usage: #npcedit slow_mitigation [Slow Mitigation] - Sets an NPC's Slow Mitigation"); + c->Message(Chat::White, "Usage: #npcedit version [Version] - Sets an NPC's Version"); + c->Message(Chat::White, "Usage: #npcedit maxlevel [Max Level] - Sets an NPC's Maximum Level"); + c->Message(Chat::White, "Usage: #npcedit scalerate [Scale Rate] - Sets an NPC's Scaling Rate [50 = 50%, 100 = 100%, 200 = 200%]"); + c->Message(Chat::White, "Usage: #npcedit emoteid [Emote ID] - Sets an NPC's Emote ID"); + c->Message(Chat::White, "Usage: #npcedit spellscale [Scale Rate] - Sets an NPC's Spell Scaling Rate [50 = 50%, 100 = 100%, 200 = 200%]"); + c->Message(Chat::White, "Usage: #npcedit healscale [Scale Rate] - Sets an NPC's Heal Scaling Rate [50 = 50%, 100 = 100%, 200 = 200%]"); + c->Message(Chat::White, "Usage: #npcedit no_target [Flag] - Sets an NPC's No Target Hotkey Flag [0 = Not Targetable with Target Hotkey, 1 = Targetable with Target Hotkey]"); + c->Message(Chat::White, "Usage: #npcedit raidtarget [Flag] - Sets an NPC's Raid Target Flag [0 = Not a Raid Target, 1 = Raid Target]"); + c->Message(Chat::White, "Usage: #npcedit armtexture [Texture] - Sets an NPC's Arm Texture"); + c->Message(Chat::White, "Usage: #npcedit bracertexture [Texture] - Sets an NPC's Bracer Texture"); + c->Message(Chat::White, "Usage: #npcedit handtexture [Texture] - Sets an NPC's Hand Texture"); + c->Message(Chat::White, "Usage: #npcedit legtexture [Texture] - Sets an NPC's Leg Texture"); + c->Message(Chat::White, "Usage: #npcedit feettexture [Texture] - Sets an NPC's Feet Texture"); + c->Message(Chat::White, "Usage: #npcedit walkspeed [Walk Speed] - Sets an NPC's Walk Speed"); + c->Message(Chat::White, "Usage: #npcedit show_name [Flag] - Sets an NPC's Show Name Flag [0 = Hidden, 1 = Shown]"); + c->Message(Chat::White, "Usage: #npcedit untargetable [Flag] - Sets an NPC's Untargetable Flag [0 = Targetable, 1 = Untargetable]"); + c->Message(Chat::White, "Usage: #npcedit charm_ac [Armor Class] - Sets an NPC's Armor Class while Charmed"); + c->Message(Chat::White, "Usage: #npcedit charm_min_dmg [Damage] - Sets an NPC's Minimum Damage while Charmed"); + c->Message(Chat::White, "Usage: #npcedit charm_max_dmg [Damage] - Sets an NPC's Maximum Damage while Charmed"); + c->Message(Chat::White, "Usage: #npcedit charm_attack_delay [Attack Delay] - Sets an NPC's Attack Delay while Charmed"); + c->Message(Chat::White, "Usage: #npcedit charm_accuracy_rating [Accuracy] - Sets an NPC's Accuracy Rating while Charmed"); + c->Message(Chat::White, "Usage: #npcedit charm_avoidance_rating [Avoidance] - Sets an NPC's Avoidance Rating while Charmed"); + c->Message(Chat::White, "Usage: #npcedit charm_atk [Attack] - Sets an NPC's Attack while Charmed"); + c->Message(Chat::White, "Usage: #npcedit skip_global_loot [Flag] - Sets an NPC's Skip Global Loot Flag [0 = Don't Skip, 1 = Skip"); + c->Message(Chat::White, "Usage: #npcedit rarespawn [Flag] - Sets an NPC's Rare Spawn Flag [0 = Not a Rare Spawn, 1 = Rare Spawn]"); + c->Message(Chat::White, "Usage: #npcedit stuck_behavior [Stuck Behavior] - Sets an NPC's Stuck Behavior [0 = Run to Target, 1 = Warp to Target, 2 = Take No Action, 3 = Evade Combat]"); + c->Message(Chat::White, "Usage: #npcedit model [Race ID] - Sets an NPC's Race Model"); + c->Message(Chat::White, "Usage: #npcedit flymode [Fly Mode] - Sets an NPC's Fly Mode [0 = Ground, 1 = Flying, 2 = Levitating, 3 = Water, 4 = Floating, 5 = Levitating While Running]"); + c->Message(Chat::White, "Usage: #npcedit always_aggro [Flag] - Sets an NPC's Always Aggro Flag [0 = Does not Always Aggro, 1 = Always Aggro]"); + c->Message(Chat::White, "Usage: #npcedit exp_mod [Modifier] - Sets an NPC's Experience Modifier [50 = 50%, 100 = 100%, 200 = 200%]"); + c->Message(Chat::White, "Usage: #npcedit heroic_strikethrough [Heroic Strikethrough] - Sets an NPC's Heroic Strikethrough"); + c->Message(Chat::White, "Usage: #npcedit faction_amount [Faction Amount] - Sets an NPC's Faction Amount"); + c->Message(Chat::White, "Usage: #npcedit keeps_sold_items [Flag] - Sets an NPC's Keeps Sold Items Flag [0 = False, 1 = True]"); + c->Message(Chat::White, "Usage: #npcedit is_parcel_merchant [Flag] - Sets an NPC's Parcel Merchant Flag [0 = False, 1 = True]"); + c->Message(Chat::White, "Usage: #npcedit setanimation [Animation ID] - Sets an NPC's Animation on Spawn (Stored in spawn2 table)"); + c->Message(Chat::White, "Usage: #npcedit respawntime [Respawn Time] - Sets an NPC's Respawn Timer in Seconds (Stored in spawn2 table)"); + c->Message(Chat::White, "Usage: #npcedit set_grid [Grid ID] - Sets an NPC's Grid ID"); + c->Message(Chat::White, "Usage: #npcedit npc_tint_id [NPC Tint ID] - Sets an NPC's Tint ID (0 to 78 for RoF2)"); +} + void command_npcedit(Client *c, const Seperator *sep) { if (!c->GetTarget() || !c->GetTarget()->IsNPC()) { @@ -1693,6 +1803,24 @@ void command_npcedit(Client *c, const Seperator *sep) c->Message(Chat::White, "Usage: #npcedit set_grid [Grid ID] - Sets an NPC's Grid ID"); return; } + } else if (!strcasecmp(sep->arg[1], "npc_tint_id")) { + if (sep->IsNumber(2)) { + const uint32 npc_tint_id = (Strings::ToUnsignedInt(sep->arg[2])); + + n.npc_tint_id = npc_tint_id; + + d = fmt::format( + "Set NPCTintID {} for {}", + npc_tint_id, + npc_id_string + ); + } else { + c->Message( + Chat::White, + "Usage: #npcedit npc_tint_id [id] - Sets an NPC's NPCTintID [0 - 78 for RoF2]" + ); + return; + } } else { SendNPCEditSubCommands(c); return; @@ -1710,112 +1838,3 @@ void command_npcedit(Client *c, const Seperator *sep) c->Message(Chat::White, d.c_str()); } - -void SendNPCEditSubCommands(Client *c) -{ - c->Message(Chat::White, "Usage: #npcedit name [Name] - Sets an NPC's Name"); - c->Message(Chat::White, "Usage: #npcedit lastname [Last Name] - Sets an NPC's Last Name"); - c->Message(Chat::White, "Usage: #npcedit level [Level] - Sets an NPC's Level"); - c->Message(Chat::White, "Usage: #npcedit race [Race ID] - Sets an NPC's Race"); - c->Message(Chat::White, "Usage: #npcedit class [Class ID] - Sets an NPC's Class"); - c->Message(Chat::White, "Usage: #npcedit bodytype [Body Type ID] - Sets an NPC's Bodytype"); - c->Message(Chat::White, "Usage: #npcedit hp [HP] - Sets an NPC's HP"); - c->Message(Chat::White, "Usage: #npcedit mana [Mana] - Sets an NPC's Mana"); - c->Message(Chat::White, "Usage: #npcedit gender [Gender ID] - Sets an NPC's Gender"); - c->Message(Chat::White, "Usage: #npcedit texture [Texture] - Sets an NPC's Texture"); - c->Message(Chat::White, "Usage: #npcedit helmtexture [Helmet Texture] - Sets an NPC's Helmet Texture"); - c->Message(Chat::White, "Usage: #npcedit herosforgemodel [Model Number] - Sets an NPC's Hero's Forge Model"); - c->Message(Chat::White, "Usage: #npcedit size [Size] - Sets an NPC's Size"); - c->Message(Chat::White, "Usage: #npcedit hpregen [HP Regen] - Sets an NPC's HP Regen Rate Per Tick"); - c->Message(Chat::White, "Usage: #npcedit hp_regen_per_second [HP Regen] - Sets an NPC's HP Regen Rate Per Second"); - c->Message(Chat::White, "Usage: #npcedit manaregen [Mana Regen] - Sets an NPC's Mana Regen Rate Per Tick"); - c->Message(Chat::White, "Usage: #npcedit loottable [Loottable ID] - Sets an NPC's Loottable ID"); - c->Message(Chat::White, "Usage: #npcedit merchantid [Merchant ID] - Sets an NPC's Merchant ID"); - c->Message(Chat::White, "Usage: #npcedit alt_currency_id [Alternate Currency ID] - Sets an NPC's Alternate Currency ID"); - c->Message(Chat::White, "Usage: #npcedit spell [Spell List ID] - Sets an NPC's Spells List ID"); - c->Message(Chat::White, "Usage: #npcedit npc_spells_effects_id [Spell Effects ID] - Sets an NPC's Spell Effects ID"); - c->Message(Chat::White, "Usage: #npcedit faction [Faction ID] - Sets an NPC's Faction ID"); - c->Message(Chat::White, "Usage: #npcedit adventure_template_id [Template ID] - Sets an NPC's Adventure Template ID"); - c->Message(Chat::White, "Usage: #npcedit trap_template [Template ID] - Sets an NPC's Trap Template ID"); - c->Message(Chat::White, "Usage: #npcedit damage [Minimum] [Maximum] - Sets an NPC's Damage"); - c->Message(Chat::White, "Usage: #npcedit attackcount [Attack Count] - Sets an NPC's Attack Count"); - c->Message(Chat::White, "Usage: #npcedit special_attacks [Special Attacks] - Sets an NPC's Special Attacks"); - c->Message(Chat::White, "Usage: #npcedit special_abilities [Special Abilities] - Sets an NPC's Special Abilities"); - c->Message(Chat::White, "Usage: #npcedit aggroradius [Radius] - Sets an NPC's Aggro Radius"); - c->Message(Chat::White, "Usage: #npcedit assistradius [Radius] - Sets an NPC's Assist Radius"); - c->Message(Chat::White, "Usage: #npcedit featuresave - Saves an NPC's current facial features to the database"); - c->Message(Chat::White, "Usage: #npcedit armortint_id [Armor Tint ID] - Sets an NPC's Armor Tint ID"); - c->Message(Chat::White, "Usage: #npcedit color [Red] [Green] [Blue] - Sets an NPC's Red, Green, and Blue armor tint"); - c->Message(Chat::White, "Usage: #npcedit ammoidfile [ID File] - Sets an NPC's Ammo ID File"); - c->Message(Chat::White, "Usage: #npcedit weapon [Primary Model] [Secondary Model] - Sets an NPC's Primary and Secondary Weapon Model"); - c->Message(Chat::White, "Usage: #npcedit meleetype [Primary Type] [Secondary Type] - Sets an NPC's Melee Skill Types"); - c->Message(Chat::White, "Usage: #npcedit rangedtype [Type] - Sets an NPC's Ranged Skill Type"); - c->Message(Chat::White, "Usage: #npcedit runspeed [Run Speed] - Sets an NPC's Run Speed"); - c->Message(Chat::White, "Usage: #npcedit mr [Resistance] - Sets an NPC's Magic Resistance"); - c->Message(Chat::White, "Usage: #npcedit pr [Resistance] - Sets an NPC's Poison Resistance"); - c->Message(Chat::White, "Usage: #npcedit dr [Resistance] - Sets an NPC's Disease Resistance"); - c->Message(Chat::White, "Usage: #npcedit fr [Resistance] - Sets an NPC's Fire Resistance"); - c->Message(Chat::White, "Usage: #npcedit cr [Resistance] - Sets an NPC's Cold Resistance"); - c->Message(Chat::White, "Usage: #npcedit corrup [Resistance] - Sets an NPC's Corruption Resistance"); - c->Message(Chat::White, "Usage: #npcedit phr [Resistance] - Sets and NPC's Physical Resistance"); - c->Message(Chat::White, "Usage: #npcedit seeinvis [Flag] - Sets an NPC's See Invisible Flag [0 = Cannot See Invisible, 1 = Can See Invisible]"); - c->Message(Chat::White, "Usage: #npcedit seeinvisundead [Flag] - Sets an NPC's See Invisible vs. Undead Flag [0 = Cannot See Invisible vs. Undead, 1 = Can See Invisible vs. Undead]"); - c->Message(Chat::White, "Usage: #npcedit qglobal [Flag] - Sets an NPC's Quest Global Flag [0 = Quest Globals Off, 1 = Quest Globals On]"); - c->Message(Chat::White, "Usage: #npcedit ac [Armor Class] - Sets an NPC's Armor Class"); - c->Message(Chat::White, "Usage: #npcedit npcaggro [Flag] - Sets an NPC's NPC Aggro Flag [0 = Aggro NPCs Off, 1 = Aggro NPCs On]"); - c->Message(Chat::White, "Usage: #npcedit spawn_limit [Limit] - Sets an NPC's Spawn Limit Counter"); - c->Message(Chat::White, "Usage: #npcedit attackspeed [Attack Speed] - Sets an NPC's Attack Speed Modifier"); - c->Message(Chat::White, "Usage: #npcedit attackdelay [Attack Delay] - Sets an NPC's Attack Delay"); - c->Message(Chat::White, "Usage: #npcedit findable [Flag] - Sets an NPC's Findable Flag [0 = Not Findable, 1 = Findable]"); - c->Message(Chat::White, "Usage: #npcedit str [Strength] - Sets an NPC's Strength"); - c->Message(Chat::White, "Usage: #npcedit sta [Stamina] - Sets an NPC's Stamina"); - c->Message(Chat::White, "Usage: #npcedit agi [Agility] - Sets an NPC's Agility"); - c->Message(Chat::White, "Usage: #npcedit dex [Dexterity] - Sets an NPC's Dexterity"); - c->Message(Chat::White, "Usage: #npcedit int [Intelligence] - Sets an NPC's Intelligence"); - c->Message(Chat::White, "Usage: #npcedit wis [Wisdom] - Sets an NPC's Wisdom"); - c->Message(Chat::White, "Usage: #npcedit cha [Charisma] - Sets an NPC's Charisma"); - c->Message(Chat::White, "Usage: #npcedit seehide [Flag] - Sets an NPC's See Hide Flag [0 = Cannot See Hide, 1 = Can See Hide]"); - c->Message(Chat::White, "Usage: #npcedit seeimprovedhide [Flag] - Sets an NPC's See Improved Hide Flag [0 = Cannot See Improved Hide, 1 = Can See Improved Hide]"); - c->Message(Chat::White, "Usage: #npcedit trackable [Flag] - Sets an NPC's Trackable Flag [0 = Not Trackable, 1 = Trackable]"); - c->Message(Chat::White, "Usage: #npcedit atk [Attack] - Sets an NPC's Attack"); - c->Message(Chat::White, "Usage: #npcedit accuracy [Accuracy] - Sets an NPC's Accuracy"); - c->Message(Chat::White, "Usage: #npcedit avoidance [Avoidance] - Sets an NPC's Avoidance"); - c->Message(Chat::White, "Usage: #npcedit slow_mitigation [Slow Mitigation] - Sets an NPC's Slow Mitigation"); - c->Message(Chat::White, "Usage: #npcedit version [Version] - Sets an NPC's Version"); - c->Message(Chat::White, "Usage: #npcedit maxlevel [Max Level] - Sets an NPC's Maximum Level"); - c->Message(Chat::White, "Usage: #npcedit scalerate [Scale Rate] - Sets an NPC's Scaling Rate [50 = 50%, 100 = 100%, 200 = 200%]"); - c->Message(Chat::White, "Usage: #npcedit emoteid [Emote ID] - Sets an NPC's Emote ID"); - c->Message(Chat::White, "Usage: #npcedit spellscale [Scale Rate] - Sets an NPC's Spell Scaling Rate [50 = 50%, 100 = 100%, 200 = 200%]"); - c->Message(Chat::White, "Usage: #npcedit healscale [Scale Rate] - Sets an NPC's Heal Scaling Rate [50 = 50%, 100 = 100%, 200 = 200%]"); - c->Message(Chat::White, "Usage: #npcedit no_target [Flag] - Sets an NPC's No Target Hotkey Flag [0 = Not Targetable with Target Hotkey, 1 = Targetable with Target Hotkey]"); - c->Message(Chat::White, "Usage: #npcedit raidtarget [Flag] - Sets an NPC's Raid Target Flag [0 = Not a Raid Target, 1 = Raid Target]"); - c->Message(Chat::White, "Usage: #npcedit armtexture [Texture] - Sets an NPC's Arm Texture"); - c->Message(Chat::White, "Usage: #npcedit bracertexture [Texture] - Sets an NPC's Bracer Texture"); - c->Message(Chat::White, "Usage: #npcedit handtexture [Texture] - Sets an NPC's Hand Texture"); - c->Message(Chat::White, "Usage: #npcedit legtexture [Texture] - Sets an NPC's Leg Texture"); - c->Message(Chat::White, "Usage: #npcedit feettexture [Texture] - Sets an NPC's Feet Texture"); - c->Message(Chat::White, "Usage: #npcedit walkspeed [Walk Speed] - Sets an NPC's Walk Speed"); - c->Message(Chat::White, "Usage: #npcedit show_name [Flag] - Sets an NPC's Show Name Flag [0 = Hidden, 1 = Shown]"); - c->Message(Chat::White, "Usage: #npcedit untargetable [Flag] - Sets an NPC's Untargetable Flag [0 = Targetable, 1 = Untargetable]"); - c->Message(Chat::White, "Usage: #npcedit charm_ac [Armor Class] - Sets an NPC's Armor Class while Charmed"); - c->Message(Chat::White, "Usage: #npcedit charm_min_dmg [Damage] - Sets an NPC's Minimum Damage while Charmed"); - c->Message(Chat::White, "Usage: #npcedit charm_max_dmg [Damage] - Sets an NPC's Maximum Damage while Charmed"); - c->Message(Chat::White, "Usage: #npcedit charm_attack_delay [Attack Delay] - Sets an NPC's Attack Delay while Charmed"); - c->Message(Chat::White, "Usage: #npcedit charm_accuracy_rating [Accuracy] - Sets an NPC's Accuracy Rating while Charmed"); - c->Message(Chat::White, "Usage: #npcedit charm_avoidance_rating [Avoidance] - Sets an NPC's Avoidance Rating while Charmed"); - c->Message(Chat::White, "Usage: #npcedit charm_atk [Attack] - Sets an NPC's Attack while Charmed"); - c->Message(Chat::White, "Usage: #npcedit skip_global_loot [Flag] - Sets an NPC's Skip Global Loot Flag [0 = Don't Skip, 1 = Skip"); - c->Message(Chat::White, "Usage: #npcedit rarespawn [Flag] - Sets an NPC's Rare Spawn Flag [0 = Not a Rare Spawn, 1 = Rare Spawn]"); - c->Message(Chat::White, "Usage: #npcedit stuck_behavior [Stuck Behavior] - Sets an NPC's Stuck Behavior [0 = Run to Target, 1 = Warp to Target, 2 = Take No Action, 3 = Evade Combat]"); - c->Message(Chat::White, "Usage: #npcedit model [Race ID] - Sets an NPC's Race Model"); - c->Message(Chat::White, "Usage: #npcedit flymode [Fly Mode] - Sets an NPC's Fly Mode [0 = Ground, 1 = Flying, 2 = Levitating, 3 = Water, 4 = Floating, 5 = Levitating While Running]"); - c->Message(Chat::White, "Usage: #npcedit always_aggro [Flag] - Sets an NPC's Always Aggro Flag [0 = Does not Always Aggro, 1 = Always Aggro]"); - c->Message(Chat::White, "Usage: #npcedit exp_mod [Modifier] - Sets an NPC's Experience Modifier [50 = 50%, 100 = 100%, 200 = 200%]"); - c->Message(Chat::White, "Usage: #npcedit heroic_strikethrough [Heroic Strikethrough] - Sets an NPC's Heroic Strikethrough"); - c->Message(Chat::White, "Usage: #npcedit faction_amount [Faction Amount] - Sets an NPC's Faction Amount"); - c->Message(Chat::White, "Usage: #npcedit keeps_sold_items [Flag] - Sets an NPC's Keeps Sold Items Flag [0 = False, 1 = True]"); - c->Message(Chat::White, "Usage: #npcedit is_parcel_merchant [Flag] - Sets an NPC's Parcel Merchant Flag [0 = False, 1 = True]"); - c->Message(Chat::White, "Usage: #npcedit setanimation [Animation ID] - Sets an NPC's Animation on Spawn (Stored in spawn2 table)"); - c->Message(Chat::White, "Usage: #npcedit respawntime [Respawn Time] - Sets an NPC's Respawn Timer in Seconds (Stored in spawn2 table)"); - c->Message(Chat::White, "Usage: #npcedit set_grid [Grid ID] - Sets an NPC's Grid ID"); -} diff --git a/zone/gm_commands/parcels.cpp b/zone/gm_commands/parcels.cpp index 740d078e7..264b4b670 100644 --- a/zone/gm_commands/parcels.cpp +++ b/zone/gm_commands/parcels.cpp @@ -1,9 +1,23 @@ #include "../client.h" +#include "../queryserv.h" #include "../worldserver.h" -#include "../../common/events/player_events.h" +#include "../../common/events/player_event_logs.h" +#include "../string_ids.h" +extern QueryServ *QServ; extern WorldServer worldserver; +void SendParcelsSubCommands(Client *c) +{ + c->Message(Chat::White, "#parcels listdb [Character Name]"); + c->Message(Chat::White, "#parcels listmemory [Character Name] (Must be in the same zone)"); + c->Message( + Chat::White, + "#parcels add [Character Name] [item id] [quantity] [note]. To send money use item id of 99990. Quantity is valid for stackable items, charges on an item, or amount of copper." + ); + c->Message(Chat::White, "#parcels details [Character Name]"); +} + void command_parcels(Client *c, const Seperator *sep) { const auto arguments = sep->argnum; @@ -197,11 +211,17 @@ 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 && PlayerEventLogs::Instance()->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; e.item_id = parcel_out.item_id; + e.augment_1_id = inst->GetAugmentItemID(0); + e.augment_2_id = inst->GetAugmentItemID(1); + e.augment_3_id = inst->GetAugmentItemID(2); + e.augment_4_id = inst->GetAugmentItemID(3); + e.augment_5_id = inst->GetAugmentItemID(4); + e.augment_6_id = inst->GetAugmentItemID(5); e.quantity = parcel_out.quantity; e.sent_date = parcel_out.sent_date; @@ -275,11 +295,17 @@ 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 && PlayerEventLogs::Instance()->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; e.item_id = parcel_out.item_id; + e.augment_1_id = inst->GetAugmentItemID(0); + e.augment_2_id = inst->GetAugmentItemID(1); + e.augment_3_id = inst->GetAugmentItemID(2); + e.augment_4_id = inst->GetAugmentItemID(3); + e.augment_5_id = inst->GetAugmentItemID(4); + e.augment_6_id = inst->GetAugmentItemID(5); e.quantity = parcel_out.quantity; e.sent_date = parcel_out.sent_date; @@ -293,15 +319,4 @@ void command_parcels(Client *c, const Seperator *sep) c->SendParcelDeliveryToWorld(ps); } } -} - -void SendParcelsSubCommands(Client *c) -{ - c->Message(Chat::White, "#parcels listdb [Character Name]"); - c->Message(Chat::White, "#parcels listmemory [Character Name] (Must be in the same zone)"); - c->Message( - Chat::White, - "#parcels add [Character Name] [item id] [quantity] [note]. To send money use item id of 99990. Quantity is valid for stackable items, charges on an item, or amount of copper." - ); - c->Message(Chat::White, "#parcels details [Character Name]"); -} +} \ No newline at end of file diff --git a/zone/gm_commands/petitems.cpp b/zone/gm_commands/petitems.cpp index 5fded03cc..6dd6462e5 100644 --- a/zone/gm_commands/petitems.cpp +++ b/zone/gm_commands/petitems.cpp @@ -1,3 +1,4 @@ +#include "../bot.h" #include "../client.h" void command_petitems(Client *c, const Seperator *sep) diff --git a/zone/gm_commands/randomfeatures.cpp b/zone/gm_commands/randomfeatures.cpp index 64a8bcda3..4a771ba13 100755 --- a/zone/gm_commands/randomfeatures.cpp +++ b/zone/gm_commands/randomfeatures.cpp @@ -8,7 +8,7 @@ void command_randomfeatures(Client *c, const Seperator *sep) } auto target = c->GetTarget(); - + if (target->RandomizeFeatures()) { c->Message( Chat::White, diff --git a/zone/gm_commands/reload.cpp b/zone/gm_commands/reload.cpp index 361b02a95..d0081e34a 100644 --- a/zone/gm_commands/reload.cpp +++ b/zone/gm_commands/reload.cpp @@ -1,5 +1,10 @@ +#include #include "../client.h" -#include "../../common/patches/patches.h" +#include "../../common/strings.h" +#include "../../common/server_reload_types.h" +#include "../worldserver.h" + +extern WorldServer worldserver; void command_reload(Client *c, const Seperator *sep) { @@ -10,261 +15,48 @@ void command_reload(Client *c, const Seperator *sep) return; } + std::string full_command = command; + for (int i = 1; i <= sep->argnum; ++i) { + full_command += " " + std::string(sep->arg[i]); + } + bool is_logs_reload_alias = sep->arg[0] && Strings::Contains(command, "#rl"); bool is_opcodes_reload_alias = sep->arg[0] && Strings::Contains(command, "#opcode"); bool is_rq_alias = sep->arg[0] && Strings::Contains(command, "#rq"); - bool is_aa = !strcasecmp(sep->arg[1], "aa"); - bool is_alternate_currencies = !strcasecmp(sep->arg[1], "alternate_currencies"); - bool is_base_data = !strcasecmp(sep->arg[1], "base_data"); - bool is_blocked_spells = !strcasecmp(sep->arg[1], "blocked_spells"); - bool is_commands = !strcasecmp(sep->arg[1], "commands"); - bool is_content_flags = !strcasecmp(sep->arg[1], "content_flags"); - bool is_data_buckets = !strcasecmp(sep->arg[1], "data_buckets_cache"); - bool is_doors = !strcasecmp(sep->arg[1], "doors"); - bool is_dztemplates = !strcasecmp(sep->arg[1], "dztemplates"); - bool is_factions = !strcasecmp(sep->arg[1], "factions"); - bool is_ground_spawns = !strcasecmp(sep->arg[1], "ground_spawns"); - bool is_level_mods = !strcasecmp(sep->arg[1], "level_mods"); - bool is_logs = !strcasecmp(sep->arg[1], "logs") || is_logs_reload_alias; - bool is_loot = !strcasecmp(sep->arg[1], "loot"); - bool is_merchants = !strcasecmp(sep->arg[1], "merchants"); - bool is_npc_emotes = !strcasecmp(sep->arg[1], "npc_emotes"); - bool is_npc_spells = !strcasecmp(sep->arg[1], "npc_spells"); - bool is_objects = !strcasecmp(sep->arg[1], "objects"); - bool is_opcodes = !strcasecmp(sep->arg[1], "opcodes") || is_opcodes_reload_alias; - bool is_perl_export = !strcasecmp(sep->arg[1], "perl_export"); - bool is_quest = !strcasecmp(sep->arg[1], "quest") || (is_rq_alias); - bool is_rules = !strcasecmp(sep->arg[1], "rules"); - bool is_skill_caps = !strcasecmp(sep->arg[1], "skill_caps"); - bool is_static = !strcasecmp(sep->arg[1], "static"); - bool is_tasks = !strcasecmp(sep->arg[1], "tasks"); - bool is_titles = !strcasecmp(sep->arg[1], "titles"); - bool is_traps = !strcasecmp(sep->arg[1], "traps"); - bool is_variables = !strcasecmp(sep->arg[1], "variables"); - bool is_veteran_rewards = !strcasecmp(sep->arg[1], "veteran_rewards"); - bool is_world = !strcasecmp(sep->arg[1], "world"); - bool is_zone = !strcasecmp(sep->arg[1], "zone"); - bool is_zone_points = !strcasecmp(sep->arg[1], "zone_points"); + if (is_logs_reload_alias) { + full_command = "#reload logs"; + } + else if (is_opcodes_reload_alias) { + full_command = "#reload opcodes"; + } + else if (is_rq_alias) { + full_command = "#reload quest"; + } - if ( - !is_aa && - !is_alternate_currencies && - !is_base_data && - !is_blocked_spells && - !is_commands && - !is_content_flags && - !is_data_buckets && - !is_doors && - !is_dztemplates && - !is_factions && - !is_ground_spawns && - !is_level_mods && - !is_logs && - !is_loot && - !is_merchants && - !is_npc_emotes && - !is_npc_spells && - !is_objects && - !is_opcodes && - !is_perl_export && - !is_quest && - !is_rules && - !is_skill_caps && - !is_static && - !is_tasks && - !is_titles && - !is_traps && - !is_variables && - !is_veteran_rewards && - !is_world && - !is_zone && - !is_zone_points - ) { + auto args = Strings::Split(full_command, ' '); + + bool found_command = false; + + for (auto &t: ServerReload::GetTypes()) { + std::string reload_slug = Strings::Slugify(ServerReload::GetNameClean(t), "_"); + std::string command_arg = args.size() > 1 ? args[1] : ""; + bool is_global = args.size() > 2 && args[2] == "global"; + if (sep->arg[0] && Strings::EqualFold(command_arg, reload_slug)) { + c->Message( + Chat::White, + fmt::format( + "Attempting to reload [{}] {} from command [{}]", + ServerReload::GetName(t), + is_global ? "globally" : "locally", + full_command + ).c_str() + ); + worldserver.SendReload(t, is_global); + found_command = true; + } + } + + if (!found_command) { c->SendReloadCommandMessages(); - return; } - - ServerPacket *pack = nullptr; - - if (is_aa) { - c->Message(Chat::White, "Attempting to reload Alternate Advancement Data globally."); - pack = new ServerPacket(ServerOP_ReloadAAData, 0); - } else if (is_alternate_currencies) { - c->Message(Chat::White, "Attempting to reload Alternate Currencies globally."); - pack = new ServerPacket(ServerOP_ReloadAlternateCurrencies, 0); - } else if (is_base_data) { - c->Message(Chat::White, "Attempting to reload Base Data globally."); - pack = new ServerPacket(ServerOP_ReloadBaseData, 0); - } else if (is_blocked_spells) { - c->Message(Chat::White, "Attempting to reload Blocked Spells globally."); - pack = new ServerPacket(ServerOP_ReloadBlockedSpells, 0); - } else if (is_commands) { - c->Message(Chat::White, "Attempting to reload Commands globally."); - pack = new ServerPacket(ServerOP_ReloadCommands, 0); - } else if (is_content_flags) { - c->Message(Chat::White, "Attempting to reload Content Flags globally."); - pack = new ServerPacket(ServerOP_ReloadContentFlags, 0); - } else if (is_doors) { - c->Message(Chat::White, "Attempting to reload Doors globally."); - pack = new ServerPacket(ServerOP_ReloadDoors, 0); - } else if (is_data_buckets) { - c->Message(Chat::White, "Attempting to flush data buckets cache globally."); - pack = new ServerPacket(ServerOP_ReloadDataBucketsCache, 0); - } else if (is_dztemplates) { - c->Message(Chat::White, "Attempting to reload Dynamic Zone Templates globally."); - pack = new ServerPacket(ServerOP_ReloadDzTemplates, 0); - } else if (is_factions) { - c->Message(Chat::White, "Attempting to reload Factions globally."); - pack = new ServerPacket(ServerOP_ReloadFactions, 0); - } else if (is_ground_spawns) { - c->Message(Chat::White, "Attempting to reload Ground Spawns globally."); - pack = new ServerPacket(ServerOP_ReloadGroundSpawns, 0); - } else if (is_level_mods) { - if (!RuleB(Zone, LevelBasedEXPMods)) { - c->Message(Chat::White, "Level Based Experience Modifiers are disabled."); - return; - } - - c->Message(Chat::White, "Attempting to reload Level Based Experience Modifiers globally."); - pack = new ServerPacket(ServerOP_ReloadLevelEXPMods, 0); - } else if (is_logs) { - c->Message(Chat::White, "Attempting to reload Log Settings globally."); - pack = new ServerPacket(ServerOP_ReloadLogs, 0); - } else if (is_loot) { - c->Message(Chat::White, "Attempting to reload Loot globally."); - pack = new ServerPacket(ServerOP_ReloadLoot, 0); - } else if (is_merchants) { - c->Message(Chat::White, "Attempting to reload Merchants globally."); - pack = new ServerPacket(ServerOP_ReloadMerchants, 0); - } else if (is_npc_emotes) { - c->Message(Chat::White, "Attempting to reload NPC Emotes globally."); - pack = new ServerPacket(ServerOP_ReloadNPCEmotes, 0); - } else if (is_npc_spells) { - c->Message(Chat::White, "Attempting to reload NPC Spells globally."); - pack = new ServerPacket(ServerOP_ReloadNPCSpells, 0); - } else if (is_objects) { - c->Message(Chat::White, "Attempting to reload Objects globally."); - pack = new ServerPacket(ServerOP_ReloadObjects, 0); - } else if (is_opcodes) { - c->Message(Chat::White, "Attempting to reload Opcodes globally."); - pack = new ServerPacket(ServerOP_ReloadOpcodes, 0); - } else if (is_perl_export) { - c->Message(Chat::White, "Attempting to reload Perl Event Export Settings globally."); - pack = new ServerPacket(ServerOP_ReloadPerlExportSettings, 0); - } else if (is_quest) { - const auto stop_timers = sep->IsNumber(2) ? Strings::ToBool(sep->arg[2]) : false; - - c->Message( - Chat::Yellow, - fmt::format( - "Quests reloaded{} for {}.", - stop_timers ? " and timers stopped" : "", - zone->GetZoneDescription() - ).c_str() - ); - - entity_list.ClearAreas(); - parse->ReloadQuests(stop_timers); - } else if (is_rules) { - c->Message(Chat::White, "Attempting to reload Rules globally."); - pack = new ServerPacket(ServerOP_ReloadRules, 0); - } else if (is_skill_caps) { - c->Message(Chat::White, "Attempting to reload Skill Caps globally."); - pack = new ServerPacket(ServerOP_ReloadSkillCaps, 0); - } else if (is_static) { - c->Message(Chat::White, "Attempting to reload Static Zone Data globally."); - pack = new ServerPacket(ServerOP_ReloadStaticZoneData, 0); - } else if (is_tasks) { - uint32 task_id = 0; - if (!sep->IsNumber(2)) { - c->Message(Chat::White, "Attempting to reload Tasks globally."); - pack = new ServerPacket(ServerOP_ReloadTasks, sizeof(ReloadTasks_Struct)); - } else { - task_id = Strings::ToUnsignedInt(sep->arg[2]); - } - - auto rts = (ReloadTasks_Struct *) pack->pBuffer; - rts->reload_type = RELOADTASKS; - rts->task_id = task_id; - } else if (is_titles) { - c->Message(Chat::White, "Attempting to reload Titles globally."); - pack = new ServerPacket(ServerOP_ReloadTitles, 0); - } else if (is_traps) { - if (arguments < 2 || !sep->IsNumber(2)) { - entity_list.UpdateAllTraps(true, true); - c->Message( - Chat::Yellow, - fmt::format( - "Traps reloaded for {}.", - zone->GetZoneDescription() - ).c_str() - ); - return; - } - - const auto global = Strings::ToBool(sep->arg[2]); - - if (!global) { - entity_list.UpdateAllTraps(true, true); - c->Message( - Chat::Yellow, - fmt::format( - "Traps reloaded for {}.", - zone->GetZoneDescription() - ).c_str() - ); - return; - } - - c->Message(Chat::White, "Attempting to reload Traps globally."); - pack = new ServerPacket(ServerOP_ReloadTraps, 0); - } else if (is_variables) { - c->Message(Chat::White, "Attempting to reload Variables globally."); - pack = new ServerPacket(ServerOP_ReloadVariables, 0); - } else if (is_veteran_rewards) { - c->Message(Chat::White, "Attempting to reload Veteran Rewards globally."); - pack = new ServerPacket(ServerOP_ReloadVeteranRewards, 0); - } else if (is_world) { - uint8 global_repop = ReloadWorld::NoRepop; - - if (sep->IsNumber(2)) { - global_repop = static_cast(Strings::ToUnsignedInt(sep->arg[2])); - - if (global_repop > ReloadWorld::ForceRepop) { - global_repop = ReloadWorld::ForceRepop; - } - } - - c->Message( - Chat::White, - fmt::format( - "Attempting to reload Quests {}worldwide.", - ( - global_repop ? - ( - global_repop == ReloadWorld::Repop ? - "and repop NPCs " : - "and forcefully repop NPCs " - ) : - "" - ) - ).c_str() - ); - - pack = new ServerPacket(ServerOP_ReloadWorld, sizeof(ReloadWorld_Struct)); - auto RW = (ReloadWorld_Struct *) pack->pBuffer; - RW->global_repop = global_repop; - } else if (is_zone) { - c->Message(Chat::White, "Attempting to reloading Zone data globally."); - pack = new ServerPacket(ServerOP_ReloadZoneData, sizeof(NewZone_Struct)); - } else if (is_zone_points) { - c->Message(Chat::White, "Attempting to reloading Zone Points globally."); - pack = new ServerPacket(ServerOP_ReloadZonePoints, 0); - } - - if (pack) { - worldserver.SendPacket(pack); - } - - safe_delete(pack); } diff --git a/zone/gm_commands/set/adventure_points.cpp b/zone/gm_commands/set/adventure_points.cpp index 3b49d06ec..55465039a 100755 --- a/zone/gm_commands/set/adventure_points.cpp +++ b/zone/gm_commands/set/adventure_points.cpp @@ -3,20 +3,21 @@ void SetAdventurePoints(Client *c, const Seperator *sep) { - const auto arguments = sep->argnum; + const uint16 arguments = sep->argnum; if (arguments < 3 || !sep->IsNumber(2) || !sep->IsNumber(3)) { c->Message(Chat::White, "Usage: #set adventure_points [Theme] [Points]"); c->Message(Chat::White, "Valid themes are as follows:"); - for (const auto& e : EQ::constants::GetLDoNThemeMap()) { - if (e.first != LDoNThemes::Unused) { + for (const auto& e : ldon_theme_names) { + if (e.first != LDoNTheme::Unused) { c->Message( Chat::White, fmt::format( - "Theme {} | {}", + "Theme {} | {} ({})", e.first, - e.second + e.second.first, + e.second.second ).c_str() ); } @@ -25,25 +26,26 @@ void SetAdventurePoints(Client *c, const Seperator *sep) return; } - auto t = c; + Client* t = c; if (c->GetTarget() && c->GetTarget()->IsClient()) { t = c->GetTarget()->CastToClient(); } const uint32 theme_id = Strings::ToUnsignedInt(sep->arg[2]); - const uint32 points = Strings::ToUnsignedInt(sep->arg[3]); + const uint32 points = Strings::ToUnsignedInt(sep->arg[3]); - if (!EQ::ValueWithin(theme_id, LDoNThemes::GUK, LDoNThemes::TAK)) { + if (!LDoNTheme::IsValid(theme_id)) { c->Message(Chat::White, "Valid themes are as follows:"); - for (const auto& e : EQ::constants::GetLDoNThemeMap()) { - if (e.first != LDoNThemes::Unused) { + for (const auto& e : ldon_theme_names) { + if (e.first != LDoNTheme::Unused) { c->Message( Chat::White, fmt::format( - "Theme {} | {}", + "Theme {} | {} ({})", e.first, - e.second + e.second.first, + e.second.second ).c_str() ); } @@ -56,7 +58,7 @@ void SetAdventurePoints(Client *c, const Seperator *sep) Chat::White, fmt::format( "Set {} Points to {} for {}.", - EQ::constants::GetLDoNThemeName(theme_id), + LDoNTheme::GetName(theme_id), Strings::Commify(points), c->GetTargetDescription(t) ).c_str() diff --git a/zone/gm_commands/set/checksum.cpp b/zone/gm_commands/set/checksum.cpp index 76df72d29..02032fe01 100755 --- a/zone/gm_commands/set/checksum.cpp +++ b/zone/gm_commands/set/checksum.cpp @@ -17,12 +17,8 @@ void SetChecksum(Client *c, const Seperator *sep) database.SetVariable("crc_basedata", account.crc_basedata); c->Message(Chat::White, "Attempting to reload Rules globally."); - auto pack = new ServerPacket(ServerOP_ReloadRules, 0); - worldserver.SendPacket(pack); - safe_delete(pack); + worldserver.SendReload(ServerReload::Type::Rules); c->Message(Chat::White, "Attempting to reload Variables globally."); - pack = new ServerPacket(ServerOP_ReloadVariables, 0); - worldserver.SendPacket(pack); - safe_delete(pack); + worldserver.SendReload(ServerReload::Type::Variables); } diff --git a/zone/gm_commands/set/haste.cpp b/zone/gm_commands/set/haste.cpp index a7e9b118f..96647c970 100644 --- a/zone/gm_commands/set/haste.cpp +++ b/zone/gm_commands/set/haste.cpp @@ -1,3 +1,4 @@ +#include "../../bot.h" #include "../../client.h" void SetHaste(Client *c, const Seperator *sep) diff --git a/zone/gm_commands/set/loginserver_info.cpp b/zone/gm_commands/set/loginserver_info.cpp index 3e42216de..5155bb284 100755 --- a/zone/gm_commands/set/loginserver_info.cpp +++ b/zone/gm_commands/set/loginserver_info.cpp @@ -14,14 +14,14 @@ void SetLoginserverInfo(Client *c, const Seperator *sep) const std::string& email = sep->arg[2]; const std::string& password = sep->arg[3]; - auto pack = new ServerPacket(ServerOP_LSAccountUpdate, sizeof(ServerLSAccountUpdate_Struct)); + auto pack = new ServerPacket(ServerOP_LSAccountUpdate, sizeof(LoginserverAccountUpdate)); - auto s = (ServerLSAccountUpdate_Struct *) pack->pBuffer; + auto s = (LoginserverAccountUpdate *) pack->pBuffer; - s->useraccountid = c->LSAccountID(); - strn0cpy(s->useraccount, c->AccountName(), 30); + s->user_account_id = c->LSAccountID(); + strn0cpy(s->user_account_name, c->AccountName(), 30); strn0cpy(s->user_email, email.c_str(), 100); - strn0cpy(s->userpassword, password.c_str(), 50); + strn0cpy(s->user_account_password, password.c_str(), 50); worldserver.SendPacket(pack); safe_delete(pack); diff --git a/zone/gm_commands/set/name.cpp b/zone/gm_commands/set/name.cpp index 0e3ca531a..c700060b9 100755 --- a/zone/gm_commands/set/name.cpp +++ b/zone/gm_commands/set/name.cpp @@ -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() diff --git a/zone/gm_commands/set/race.cpp b/zone/gm_commands/set/race.cpp index 48bb11f0c..4a5f86c31 100755 --- a/zone/gm_commands/set/race.cpp +++ b/zone/gm_commands/set/race.cpp @@ -44,6 +44,19 @@ void SetRace(Client *c, const Seperator *sep) } ); + if (race_id == Race::Doug) { + c->Message( + Chat::White, + fmt::format( + "{} {} been returned to {} base race.", + c->GetTargetDescription(t, TargetDescriptionType::UCYou), + c == t ? "have" : "has", + c == t ? "your" : "their" + ).c_str() + ); + return; + } + c->Message( Chat::White, fmt::format( diff --git a/zone/gm_commands/set/zone.cpp b/zone/gm_commands/set/zone.cpp index e1bf852ea..0ff3c24cb 100755 --- a/zone/gm_commands/set/zone.cpp +++ b/zone/gm_commands/set/zone.cpp @@ -273,7 +273,7 @@ void SetZoneData(Client *c, const Seperator *sep) entity_list.QueueClients(c, outapp); safe_delete(outapp); - zone_store.LoadZones(content_db); + ZoneStore::Instance()->LoadZones(content_db); c->Message( Chat::White, diff --git a/zone/gm_commands/show.cpp b/zone/gm_commands/show.cpp index 342eaae46..5a5de4560 100755 --- a/zone/gm_commands/show.cpp +++ b/zone/gm_commands/show.cpp @@ -15,6 +15,7 @@ #include "show/group_info.cpp" #include "show/hatelist.cpp" #include "show/inventory.cpp" +#include "show/keyring.cpp" #include "show/ip_lookup.cpp" #include "show/line_of_sight.cpp" #include "show/network.cpp" @@ -49,6 +50,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) { @@ -77,6 +79,7 @@ void command_show(Client *c, const Seperator *sep) Cmd{.cmd = "hatelist", .u = "hatelist", .fn = ShowHateList, .a = {"#hatelist"}}, Cmd{.cmd = "inventory", .u = "inventory", .fn = ShowInventory, .a = {"#peekinv"}}, Cmd{.cmd = "ip_lookup", .u = "ip_lookup", .fn = ShowIPLookup, .a = {"#iplookup"}}, + Cmd{.cmd = "keyring", .u = "keyring", .fn = ShowKeyring, .a = {"#showkeyring"}}, Cmd{.cmd = "line_of_sight", .u = "line_of_sight", .fn = ShowLineOfSight, .a = {"#checklos"}}, Cmd{.cmd = "network", .u = "network", .fn = ShowNetwork, .a = {"#network"}}, Cmd{.cmd = "network_stats", .u = "network_stats", .fn = ShowNetworkStats, .a = {"#netstats"}}, @@ -110,6 +113,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 diff --git a/zone/gm_commands/show/currencies.cpp b/zone/gm_commands/show/currencies.cpp index d63395822..4f4d3cfda 100644 --- a/zone/gm_commands/show/currencies.cpp +++ b/zone/gm_commands/show/currencies.cpp @@ -89,11 +89,11 @@ void ShowCurrencies(Client *c, const Seperator *sep) } } - for (const auto& l : EQ::constants::GetLDoNThemeMap()) { + for (const auto& l : ldon_theme_names) { const uint32 ldon_currency_value = t->GetLDoNPointsTheme(l.first); if (ldon_currency_value) { currency_table += DialogueWindow::TableRow( - DialogueWindow::TableCell(l.second) + + DialogueWindow::TableCell(l.second.first) + DialogueWindow::TableCell(Strings::Commify(ldon_currency_value)) ); diff --git a/zone/gm_commands/show/inventory.cpp b/zone/gm_commands/show/inventory.cpp index c2d22b2d9..2a3e2a377 100644 --- a/zone/gm_commands/show/inventory.cpp +++ b/zone/gm_commands/show/inventory.cpp @@ -1,6 +1,22 @@ #include "../../client.h" #include "../../object.h" +void SendShowInventorySubCommands(Client* c) { + c->Message(Chat::White, "Usage: #show inventory equip - Shows items in Equipment slots"); + c->Message(Chat::White, "Usage: #show inventory gen - Shows items in General slots"); + c->Message(Chat::White, "Usage: #show inventory cursor - Shows items in Cursor slots"); + c->Message(Chat::White, "Usage: #show inventory poss - Shows items in Equipment, General, and Cursor slots"); + c->Message(Chat::White, "Usage: #show inventory limbo - Shows items in Limbo slots"); + c->Message(Chat::White, "Usage: #show inventory curlim - Shows items in Cursor and Limbo slots"); + c->Message(Chat::White, "Usage: #show inventory trib - Shows items in Tribute slots"); + c->Message(Chat::White, "Usage: #show inventory bank - Shows items in Bank slots"); + c->Message(Chat::White, "Usage: #show inventory shbank - Shows items in Shared Bank slots"); + c->Message(Chat::White, "Usage: #show inventory allbank - Shows items in Bank and Shared Bank slots"); + c->Message(Chat::White, "Usage: #show inventory trade - Shows items in Trade slots"); + c->Message(Chat::White, "Usage: #show inventory world - Shows items in World slots"); + c->Message(Chat::White, "Usage: #show inventory all - Shows items in all slots"); +} + void ShowInventory(Client *c, const Seperator *sep) { const auto arguments = sep->argnum; @@ -310,7 +326,7 @@ void ShowInventory(Client *c, const Seperator *sep) Chat::White, fmt::format( "Slot {} | {} ({}){}", - (8000 + limboIndex), + (14000 + limboIndex), item_data->ID, linker.GenerateLink(), ( @@ -339,7 +355,7 @@ void ShowInventory(Client *c, const Seperator *sep) Chat::White, fmt::format( "Slot {} (Augment Slot {}) | {} ({}){}", - (8000 + limboIndex), + (14000 + limboIndex), augment_index, linker.GenerateLink(), item_data->ID, @@ -375,7 +391,7 @@ void ShowInventory(Client *c, const Seperator *sep) Chat::White, fmt::format( "Slot {} Bag Slot {} | {} ({}){}", - (8000 + limboIndex), + (14000 + limboIndex), sub_index, linker.GenerateLink(), item_data->ID, @@ -407,7 +423,7 @@ void ShowInventory(Client *c, const Seperator *sep) Chat::White, fmt::format( "Slot {} Bag Slot {} (Augment Slot {}) | {} ({}){}", - (8000 + limboIndex), + (14000 + limboIndex), sub_index, augment_index, linker.GenerateLink(), @@ -432,20 +448,4 @@ void ShowInventory(Client *c, const Seperator *sep) if (!found_items) { c->Message(Chat::White, "No items found."); } -} - -void SendShowInventorySubCommands(Client* c) { - c->Message(Chat::White, "Usage: #show inventory equip - Shows items in Equipment slots"); - c->Message(Chat::White, "Usage: #show inventory gen - Shows items in General slots"); - c->Message(Chat::White, "Usage: #show inventory cursor - Shows items in Cursor slots"); - c->Message(Chat::White, "Usage: #show inventory poss - Shows items in Equipment, General, and Cursor slots"); - c->Message(Chat::White, "Usage: #show inventory limbo - Shows items in Limbo slots"); - c->Message(Chat::White, "Usage: #show inventory curlim - Shows items in Cursor and Limbo slots"); - c->Message(Chat::White, "Usage: #show inventory trib - Shows items in Tribute slots"); - c->Message(Chat::White, "Usage: #show inventory bank - Shows items in Bank slots"); - c->Message(Chat::White, "Usage: #show inventory shbank - Shows items in Shared Bank slots"); - c->Message(Chat::White, "Usage: #show inventory allbank - Shows items in Bank and Shared Bank slots"); - c->Message(Chat::White, "Usage: #show inventory trade - Shows items in Trade slots"); - c->Message(Chat::White, "Usage: #show inventory world - Shows items in World slots"); - c->Message(Chat::White, "Usage: #show inventory all - Shows items in all slots"); -} +} \ No newline at end of file diff --git a/zone/gm_commands/show/ip_lookup.cpp b/zone/gm_commands/show/ip_lookup.cpp index b020541b4..aefcaeaf3 100644 --- a/zone/gm_commands/show/ip_lookup.cpp +++ b/zone/gm_commands/show/ip_lookup.cpp @@ -9,7 +9,7 @@ void ShowIPLookup(Client *c, const Seperator *sep) auto pack = new ServerPacket( ServerOP_IPLookup, - sizeof(ServerGenericWorldQuery_Struct) + ip_length + 1 + sizeof(ServerGenericWorldQuery_Struct) + ip_length ); auto s = (ServerGenericWorldQuery_Struct *) pack->pBuffer; diff --git a/zone/gm_commands/show/keyring.cpp b/zone/gm_commands/show/keyring.cpp new file mode 100644 index 000000000..39552f8cc --- /dev/null +++ b/zone/gm_commands/show/keyring.cpp @@ -0,0 +1,12 @@ +#include "../../client.h" +#include "../../dialogue_window.h" + +void ShowKeyring(Client *c, const Seperator *sep) +{ + Client* t = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + t = c->GetTarget()->CastToClient(); + } + + t->KeyRingList(c); +} diff --git a/zone/gm_commands/show/network.cpp b/zone/gm_commands/show/network.cpp index d9eff2c16..46d32a69c 100644 --- a/zone/gm_commands/show/network.cpp +++ b/zone/gm_commands/show/network.cpp @@ -16,17 +16,17 @@ void ShowNetwork(Client *c, const Seperator *sep) popup_table += DialogueWindow::TableRow( DialogueWindow::TableCell("Max Packet Size") + - DialogueWindow::TableCell(Strings::Commify(opts.daybreak_options.max_packet_size)) + DialogueWindow::TableCell(Strings::Commify(opts.reliable_stream_options.max_packet_size)) ); popup_table += DialogueWindow::TableRow( DialogueWindow::TableCell("Max Connection Count") + - DialogueWindow::TableCell(Strings::Commify(opts.daybreak_options.max_connection_count)) + DialogueWindow::TableCell(Strings::Commify(opts.reliable_stream_options.max_connection_count)) ); popup_table += DialogueWindow::TableRow( DialogueWindow::TableCell("Keep Alive Delay") + - DialogueWindow::TableCell(Strings::MillisecondsToTime(opts.daybreak_options.keepalive_delay_ms)) + DialogueWindow::TableCell(Strings::MillisecondsToTime(opts.reliable_stream_options.keepalive_delay_ms)) ); popup_table += DialogueWindow::TableRow( @@ -34,64 +34,64 @@ void ShowNetwork(Client *c, const Seperator *sep) DialogueWindow::TableCell( fmt::format( "{:.2f}", - opts.daybreak_options.resend_delay_factor + opts.reliable_stream_options.resend_delay_factor ) ) ); popup_table += DialogueWindow::TableRow( DialogueWindow::TableCell("Resend Delay") + - DialogueWindow::TableCell(Strings::MillisecondsToTime(opts.daybreak_options.resend_delay_ms)) + DialogueWindow::TableCell(Strings::MillisecondsToTime(opts.reliable_stream_options.resend_delay_ms)) ); popup_table += DialogueWindow::TableRow( DialogueWindow::TableCell("Resend Delay Minimum") + - DialogueWindow::TableCell(Strings::MillisecondsToTime(opts.daybreak_options.resend_delay_min)) + DialogueWindow::TableCell(Strings::MillisecondsToTime(opts.reliable_stream_options.resend_delay_min)) ); popup_table += DialogueWindow::TableRow( DialogueWindow::TableCell("Resend Delay Maximum") + - DialogueWindow::TableCell(Strings::MillisecondsToTime(opts.daybreak_options.resend_delay_max)) + DialogueWindow::TableCell(Strings::MillisecondsToTime(opts.reliable_stream_options.resend_delay_max)) ); popup_table += DialogueWindow::TableRow( DialogueWindow::TableCell("Connect Delay") + - DialogueWindow::TableCell(Strings::MillisecondsToTime(opts.daybreak_options.connect_delay_ms)) + DialogueWindow::TableCell(Strings::MillisecondsToTime(opts.reliable_stream_options.connect_delay_ms)) ); popup_table += DialogueWindow::TableRow( DialogueWindow::TableCell("Connect Stale") + - DialogueWindow::TableCell(Strings::MillisecondsToTime(opts.daybreak_options.connect_stale_ms)) + DialogueWindow::TableCell(Strings::MillisecondsToTime(opts.reliable_stream_options.connect_stale_ms)) ); popup_table += DialogueWindow::TableRow( DialogueWindow::TableCell("Stale Connection") + - DialogueWindow::TableCell(Strings::MillisecondsToTime(opts.daybreak_options.stale_connection_ms)) + DialogueWindow::TableCell(Strings::MillisecondsToTime(opts.reliable_stream_options.stale_connection_ms)) ); popup_table += DialogueWindow::TableRow( DialogueWindow::TableCell("CRC Length") + - DialogueWindow::TableCell(Strings::Commify(opts.daybreak_options.crc_length)) + DialogueWindow::TableCell(Strings::Commify(opts.reliable_stream_options.crc_length)) ); popup_table += DialogueWindow::TableRow( DialogueWindow::TableCell("Hold Size") + - DialogueWindow::TableCell(Strings::Commify(opts.daybreak_options.hold_size)) + DialogueWindow::TableCell(Strings::Commify(opts.reliable_stream_options.hold_size)) ); popup_table += DialogueWindow::TableRow( DialogueWindow::TableCell("Hold Length") + - DialogueWindow::TableCell(Strings::MillisecondsToTime(opts.daybreak_options.hold_length_ms)) + DialogueWindow::TableCell(Strings::MillisecondsToTime(opts.reliable_stream_options.hold_length_ms)) ); popup_table += DialogueWindow::TableRow( DialogueWindow::TableCell("Simulated In Packet Loss") + - DialogueWindow::TableCell(std::to_string(opts.daybreak_options.simulated_in_packet_loss)) + DialogueWindow::TableCell(std::to_string(opts.reliable_stream_options.simulated_in_packet_loss)) ); popup_table += DialogueWindow::TableRow( DialogueWindow::TableCell("Simulated Out Packet Loss") + - DialogueWindow::TableCell(std::to_string(opts.daybreak_options.simulated_out_packet_loss)) + DialogueWindow::TableCell(std::to_string(opts.reliable_stream_options.simulated_out_packet_loss)) ); popup_table += DialogueWindow::TableRow( @@ -99,34 +99,34 @@ void ShowNetwork(Client *c, const Seperator *sep) DialogueWindow::TableCell( fmt::format( "{:.2f}", - opts.daybreak_options.tic_rate_hertz + opts.reliable_stream_options.tic_rate_hertz ) ) ); popup_table += DialogueWindow::TableRow( DialogueWindow::TableCell("Resend Timeout") + - DialogueWindow::TableCell(Strings::MillisecondsToTime(opts.daybreak_options.resend_timeout)) + DialogueWindow::TableCell(Strings::MillisecondsToTime(opts.reliable_stream_options.resend_timeout)) ); popup_table += DialogueWindow::TableRow( DialogueWindow::TableCell("Connection Close Time") + - DialogueWindow::TableCell(Strings::MillisecondsToTime(opts.daybreak_options.connection_close_time)) + DialogueWindow::TableCell(Strings::MillisecondsToTime(opts.reliable_stream_options.connection_close_time)) ); popup_table += DialogueWindow::TableRow( DialogueWindow::TableCell("Encode Passes (1)") + - DialogueWindow::TableCell(Strings::Commify(opts.daybreak_options.encode_passes[0])) + DialogueWindow::TableCell(Strings::Commify(opts.reliable_stream_options.encode_passes[0])) ); popup_table += DialogueWindow::TableRow( DialogueWindow::TableCell("Encode Passes (2)") + - DialogueWindow::TableCell(Strings::Commify(opts.daybreak_options.encode_passes[1])) + DialogueWindow::TableCell(Strings::Commify(opts.reliable_stream_options.encode_passes[1])) ); popup_table += DialogueWindow::TableRow( DialogueWindow::TableCell("Port") + - DialogueWindow::TableCell(Strings::Commify(opts.daybreak_options.port)) + DialogueWindow::TableCell(Strings::Commify(opts.reliable_stream_options.port)) ); popup_table = DialogueWindow::Table(popup_table); diff --git a/zone/gm_commands/show/network_stats.cpp b/zone/gm_commands/show/network_stats.cpp index 8d51e8f69..75bf2b25f 100644 --- a/zone/gm_commands/show/network_stats.cpp +++ b/zone/gm_commands/show/network_stats.cpp @@ -7,7 +7,7 @@ void ShowNetworkStats(Client *c, const Seperator *sep) const auto opts = connection->GetManager()->GetOptions(); const auto eqs_stats = connection->GetStats(); - const auto& stats = eqs_stats.DaybreakStats; + const auto& stats = eqs_stats.ReliableStreamStats; const auto sec_since_stats_reset = std::chrono::duration_cast>( EQ::Net::Clock::now() - stats.created @@ -217,7 +217,7 @@ void ShowNetworkStats(Client *c, const Seperator *sep) ) ); - if (opts.daybreak_options.outgoing_data_rate > 0.0) { + if (opts.reliable_stream_options.outgoing_data_rate > 0.0) { popup_table += DialogueWindow::TableRow( DialogueWindow::TableCell("Outgoing Link Saturation") + DialogueWindow::TableCell( @@ -229,14 +229,14 @@ void ShowNetworkStats(Client *c, const Seperator *sep) 1.0 - ( ( - opts.daybreak_options.outgoing_data_rate - + opts.reliable_stream_options.outgoing_data_rate - stats.datarate_remaining ) / - opts.daybreak_options.outgoing_data_rate + opts.reliable_stream_options.outgoing_data_rate ) ) ), - opts.daybreak_options.outgoing_data_rate + opts.reliable_stream_options.outgoing_data_rate ) ) ); diff --git a/zone/gm_commands/show/recipe.cpp b/zone/gm_commands/show/recipe.cpp index acbe08d82..e9f8db666 100644 --- a/zone/gm_commands/show/recipe.cpp +++ b/zone/gm_commands/show/recipe.cpp @@ -10,15 +10,15 @@ void ShowRecipe(Client *c, const Seperator *sep) return; } - const uint16 recipe_id = static_cast(Strings::ToUnsignedInt(sep->arg[2])); + const uint32 recipe_id = Strings::ToUnsignedInt(sep->arg[2]); const auto& re = TradeskillRecipeEntriesRepository::GetWhere( - database, + content_db, fmt::format("recipe_id = {} ORDER BY id ASC", recipe_id) ); const auto& r = TradeskillRecipeRepository::GetWhere( - database, + content_db, fmt::format("id = {}", recipe_id) ); diff --git a/zone/gm_commands/show/zone_data.cpp b/zone/gm_commands/show/zone_data.cpp index 9a2f5491d..881f7d4da 100644 --- a/zone/gm_commands/show/zone_data.cpp +++ b/zone/gm_commands/show/zone_data.cpp @@ -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( diff --git a/zone/gm_commands/show/zone_variables.cpp b/zone/gm_commands/show/zone_variables.cpp new file mode 100644 index 000000000..4af24fa0b --- /dev/null +++ b/zone/gm_commands/show/zone_variables.cpp @@ -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."); + } +} diff --git a/zone/gm_commands/summonitem.cpp b/zone/gm_commands/summonitem.cpp index ddee8ab07..b72292a97 100755 --- a/zone/gm_commands/summonitem.cpp +++ b/zone/gm_commands/summonitem.cpp @@ -132,4 +132,6 @@ void command_summonitem(Client *c, const Seperator *sep) item_link ).c_str() ); + + safe_delete(new_item); } diff --git a/zone/gm_commands/task.cpp b/zone/gm_commands/task.cpp index 37aafc84b..407184f59 100755 --- a/zone/gm_commands/task.cpp +++ b/zone/gm_commands/task.cpp @@ -8,6 +8,11 @@ extern WorldServer worldserver; void command_task(Client *c, const Seperator *sep) { + if (!RuleB(TaskSystem, EnableTaskSystem)) { + c->Message(Chat::White, "This command cannot be used while the Task system is disabled."); + return; + } + const int arguments = sep->argnum; if (!arguments) { c->Message(Chat::White, "Syntax: #task [subcommand]"); @@ -171,7 +176,7 @@ void command_task(Client *c, const Seperator *sep) Chat::White, fmt::format( "Assigned {} (ID {}) to {}.", - task_manager->GetTaskName(task_id), + TaskManager::Instance()->GetTaskName(task_id), task_id, c->GetTargetDescription(t) ).c_str() @@ -192,7 +197,7 @@ void command_task(Client *c, const Seperator *sep) Chat::White, fmt::format( "Successfully completed {} (ID {}) for {}.", - task_manager->GetTaskName(task_id), + TaskManager::Instance()->GetTaskName(task_id), task_id, c->GetTargetDescription(t) ).c_str() @@ -202,7 +207,7 @@ void command_task(Client *c, const Seperator *sep) Chat::White, fmt::format( "Failed to complete {} (ID {}) for {}.", - task_manager->GetTaskName(task_id), + TaskManager::Instance()->GetTaskName(task_id), task_id, c->GetTargetDescription(t) ).c_str() @@ -215,7 +220,7 @@ void command_task(Client *c, const Seperator *sep) "{} {} not have not {} (ID {}) assigned to them.", c->GetTargetDescription(t, TargetDescriptionType::UCYou), c == t ? "do" : "does", - task_manager->GetTaskName(task_id), + TaskManager::Instance()->GetTaskName(task_id), task_id ).c_str() ); @@ -251,7 +256,7 @@ void command_task(Client *c, const Seperator *sep) Chat::White, fmt::format( "Attempting to reload {} (ID {}).", - task_manager->GetTaskName(task_id), + TaskManager::Instance()->GetTaskName(task_id), task_id ).c_str() ); @@ -260,7 +265,7 @@ void command_task(Client *c, const Seperator *sep) Chat::White, fmt::format( "Successfully reloaded {} (ID {}).", - task_manager->GetTaskName(task_id), + TaskManager::Instance()->GetTaskName(task_id), task_id ).c_str() ); @@ -299,21 +304,12 @@ void command_task(Client *c, const Seperator *sep) return; } - if ( - CompletedTasksRepository::DeleteWhere( - database, - fmt::format( - "charid = {} AND taskid = {}", - t->CharacterID(), - task_id - ) - ) - ) { + if (t->UncompleteTask(task_id)) { c->Message( Chat::White, fmt::format( "Successfully uncompleted {} (ID {}) for {}.", - task_manager->GetTaskName(task_id), + TaskManager::Instance()->GetTaskName(task_id), task_id, c->GetTargetDescription(t) ).c_str() @@ -325,7 +321,7 @@ void command_task(Client *c, const Seperator *sep) "{} {} not completed {} (ID {}).", c->GetTargetDescription(t, TargetDescriptionType::UCYou), c == t ? "have" : "has", - task_manager->GetTaskName(task_id), + TaskManager::Instance()->GetTaskName(task_id), task_id ).c_str() ); @@ -348,7 +344,7 @@ void command_task(Client *c, const Seperator *sep) Chat::White, fmt::format( "Updating {} (ID {}), activity {} with a count of {} for {}.", - task_manager->GetTaskName(task_id), + TaskManager::Instance()->GetTaskName(task_id), task_id, activity_id, count, diff --git a/zone/gm_commands/zone.cpp b/zone/gm_commands/zone.cpp index 13601da4d..46caf373f 100644 --- a/zone/gm_commands/zone.cpp +++ b/zone/gm_commands/zone.cpp @@ -1,3 +1,4 @@ +#include "../bot.h" #include "../client.h" void command_zone(Client *c, const Seperator *sep) @@ -25,7 +26,7 @@ void command_zone(Client *c, const Seperator *sep) } else { // validate - if (!zone_store.GetZone(zone_input)) { + if (!ZoneStore::Instance()->GetZone(zone_input)) { c->Message(Chat::White, fmt::format("Could not find zone by short_name [{}]", zone_input).c_str()); return; } diff --git a/zone/gm_commands/zone_shard.cpp b/zone/gm_commands/zone_shard.cpp new file mode 100644 index 000000000..9e468ba7d --- /dev/null +++ b/zone/gm_commands/zone_shard.cpp @@ -0,0 +1,137 @@ +#include "../client.h" + +void command_zone_shard(Client *c, const Seperator *sep) +{ + int arguments = sep->argnum; + if (!arguments || !sep->IsNumber(1)) { + if (!RuleB(Zone, ZoneShardQuestMenuOnly)) { + c->ShowZoneShardMenu(); + } + + return; + } + + if (c->GetAggroCount() > 0) { + c->Message(Chat::White, "You cannot request a shard change while in combat."); + return; + } + + std::string zone_input = sep->arg[1]; + uint32 zone_id = 0; + + // if input is id + if (Strings::IsNumber(zone_input)) { + zone_id = Strings::ToInt(zone_input); + + // validate + if (zone_id != 0 && !GetZone(zone_id)) { + c->Message(Chat::White, fmt::format("Could not find zone by id [{}]", zone_id).c_str()); + return; + } + } + else { + // validate + if (!ZoneStore::Instance()->GetZone(zone_input)) { + c->Message(Chat::White, fmt::format("Could not find zone by short_name [{}]", zone_input).c_str()); + return; + } + + // validate we got id + zone_id = ZoneID(zone_input); + if (zone_id == 0) { + c->Message(Chat::White, fmt::format("Could not find zone id by short_name [{}]", zone_input).c_str()); + return; + } + } + + auto z = GetZone(zone_id); + if (z && z->shard_at_player_count == 0) { + c->Message(Chat::White, "Zone does not have sharding enabled."); + return; + } + + auto instance_id = sep->arg[2] ? Strings::ToBigInt(sep->arg[2]) : 0; + if (instance_id < -1) { + c->Message(Chat::White, "You must enter a valid Instance ID."); + return; + } + + if (zone_id == c->GetZoneID() && c->GetInstanceID() == instance_id) { + c->Message(Chat::White, "You are already in this shard."); + return; + } + + if (zone_id != c->GetZoneID()) { + c->Message(Chat::White, "You must request a shard change from the zone you are currently in."); + return; + } + + auto results = CharacterDataRepository::GetInstanceZonePlayerCounts(database, c->GetZoneID()); + if (results.size() <= 1) { + c->Message(Chat::White, "No shards found."); + return; + } + + if (instance_id > 0) { + if (!database.CheckInstanceExists(instance_id)) { + c->Message( + Chat::White, + fmt::format( + "Instance ID {} does not exist.", + instance_id + ).c_str() + ); + return; + } + + auto instance_zone_id = database.GetInstanceZoneID(instance_id); + if (!instance_zone_id) { + c->Message( + Chat::White, + fmt::format( + "Instance ID {} not found or zone is set to null.", + instance_id + ).c_str() + ); + return; + } + + if (instance_zone_id != zone_id) { + c->Message( + Chat::White, + fmt::format( + "Instance Zone ID {} does not match zone ID {}.", + instance_id, + zone_id + ).c_str() + ); + return; + } + + if (!database.CheckInstanceByCharID(instance_id, c->CharacterID())) { + database.AddClientToInstance(instance_id, c->CharacterID()); + } + + if (!database.VerifyInstanceAlive(instance_id, c->CharacterID())) { + c->Message( + Chat::White, + fmt::format( + "Instance ID {} expired.", + instance_id + ).c_str() + ); + return; + } + } + + c->MovePC( + zone_id, + instance_id, + c->GetX(), + c->GetY(), + c->GetZ(), + c->GetHeading(), + 0, + ZoneSolicited + ); +} diff --git a/zone/gm_commands/zoneshutdown.cpp b/zone/gm_commands/zoneshutdown.cpp index b8141faed..c62e5a0b9 100755 --- a/zone/gm_commands/zoneshutdown.cpp +++ b/zone/gm_commands/zoneshutdown.cpp @@ -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)); diff --git a/zone/gm_commands/zonevariable.cpp b/zone/gm_commands/zonevariable.cpp new file mode 100644 index 000000000..54c4ae65f --- /dev/null +++ b/zone/gm_commands/zonevariable.cpp @@ -0,0 +1,100 @@ +#include "../client.h" + +void command_zonevariable(Client *c, const Seperator *sep) +{ + const uint16 arguments = sep->argnum; + + if (!arguments) { + c->Message(Chat::White, "Usage: #zonevariable clear - Clear all zone variables"); + c->Message(Chat::White, "Usage: #zonevariable delete [Variable Name] - Delete a zone variable"); + c->Message(Chat::White, "Usage: #zonevariable set [Variable Name] [Variable Value] - Set a zone variable"); + c->Message(Chat::White, "Usage: #zonevariable view [Variable Name] - View a zone variable"); + c->Message(Chat::White, "Note: You can have spaces in variable names and values by wrapping in double quotes like this"); + c->Message(Chat::White, "Example: #zonevariable set \"Test Variable\" \"Test Value\""); + c->Message(Chat::White, "Note: Variable Value is optional and can be set to empty by not providing a value"); + return; + } + + const char* action = arguments >= 1 ? sep->arg[1] : ""; + const bool is_clear = !strcasecmp(action, "clear"); + const bool is_delete = !strcasecmp(action, "delete"); + const bool is_set = !strcasecmp(action, "set"); + const bool is_view = !strcasecmp(action, "view"); + + if (!is_clear && !is_delete && !is_set && !is_view) { + c->Message(Chat::White, "Usage: #zonevariable clear - Clear all zone variables"); + c->Message(Chat::White, "Usage: #zonevariable delete [Variable Name] - Delete a zone variable"); + c->Message(Chat::White, "Usage: #zonevariable set [Variable Name] [Variable Value] - Set a zone variable"); + c->Message(Chat::White, "Usage: #zonevariable view [Variable Name] - View a zone variable"); + c->Message(Chat::White, "Note: You can have spaces in variable names and values by wrapping in double quotes like this"); + c->Message(Chat::White, "Example: #zonevariable set \"Test Variable\" \"Test Value\""); + c->Message(Chat::White, "Note: Variable Value is optional and can be set to empty by not providing a value"); + return; + } + + if (is_clear) { + const bool cleared = zone->ClearVariables(); + c->Message(Chat::White, cleared ? "Cleared all zone variables." : "There are no zone variables to clear."); + return; + } + + if (is_delete) { + const std::string variable_name = arguments >= 2 ? sep->argplus[2] : ""; + if (variable_name.empty() || !zone->VariableExists(variable_name)) { + c->Message(Chat::White, fmt::format("A zone variable named '{}' does not exist.", variable_name).c_str()); + return; + } + + zone->DeleteVariable(variable_name); + c->Message(Chat::White, fmt::format("Deleted a zone variable named '{}'.", variable_name).c_str()); + return; + } + + if (is_set) { + const std::string variable_name = arguments >= 2 ? sep->arg[2] : ""; + const std::string variable_value = arguments >= 3 ? sep->arg[3] : ""; + zone->SetVariable(variable_name, variable_value); + c->Message(Chat::White, fmt::format("Set a zone variable named '{}' to a value of '{}'.", variable_name, variable_value).c_str()); + return; + } + + if (is_view) { + const auto& l = zone->GetVariables(); + const std::string search_criteria = arguments >= 2 ? sep->argplus[2] : ""; + + uint32 variable_count = 0; + uint32 variable_number = 1; + + for (const auto& e : l) { + if (search_criteria.empty() || Strings::Contains(Strings::ToLower(e), Strings::ToLower(search_criteria))) { + c->Message(Chat::White, fmt::format( + "Variable {} | Name: {} Value: {} | {}", + variable_number, + e, + zone->GetVariable(e), + Saylink::Silent(fmt::format("#zonevariable delete {}", e), "Delete") + ).c_str()); + + variable_count++; + variable_number++; + } + } + + if (!variable_count) { + c->Message(Chat::White, fmt::format( + "There are no zone variables{}.", + (!search_criteria.empty() ? fmt::format(" matching '{}'", search_criteria) : "") + ).c_str()); + return; + } + + c->Message(Chat::White, fmt::format( + "There {} {} zone variable{}{}, would you like to {} zone variables?", + variable_count != 1 ? "are" : "is", + variable_count, + variable_count != 1 ? "s" : "", + (!search_criteria.empty() ? fmt::format(" matching '{}'", search_criteria) : ""), + Saylink::Silent("#zonevariable clear", "clear") + ).c_str()); + } +} diff --git a/zone/groups.cpp b/zone/groups.cpp index ad6972e2a..b70f44f18 100644 --- a/zone/groups.cpp +++ b/zone/groups.cpp @@ -18,17 +18,20 @@ #include "../common/global_define.h" #include "../common/eqemu_logsys.h" -#include "expedition.h" +#include "dynamic_zone.h" #include "masterentity.h" #include "worldserver.h" #include "string_ids.h" #include "../common/events/player_event_logs.h" +#include "../common/repositories/character_expedition_lockouts_repository.h" #include "../common/repositories/group_id_repository.h" #include "../common/repositories/group_leaders_repository.h" +#include "queryserv.h" -extern EntityList entity_list; +extern EntityList entity_list; extern WorldServer worldserver; +extern QueryServ *QServ; /* note about how groups work: @@ -203,7 +206,7 @@ void Group::SplitMoney(uint32 copper, uint32 silver, uint32 gold, uint32 platinu } // If logging of player money transactions is enabled, record the transaction. - if (player_event_logs.IsEventEnabled(PlayerEvent::SPLIT_MONEY)) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::SPLIT_MONEY)) { auto e = PlayerEvent::SplitMoneyEvent{ .copper = receive_copper, .silver = receive_silver, @@ -1017,6 +1020,27 @@ void Group::GetBotList(std::list& bot_list, bool clear_list) } } +std::list Group::GetRawBotList() +{ + std::list bot_list; + + const auto& l = GroupIdRepository::GetWhere( + database, + fmt::format( + "`group_id` = {}", + GetID() + ) + ); + + for (const auto& e : l) { + if (e.bot_id) { + bot_list.push_back(e.bot_id); + } + } + + return bot_list; +} + bool Group::Process() { if(disbandcheck && !GroupCount()) return false; @@ -1234,30 +1258,48 @@ void Group::GroupMessageString(Mob* sender, uint32 type, uint32 string_id, const void Client::LeaveGroup() { Group *g = GetGroup(); - if(g) - { + if (g) { int32 MemberCount = g->GroupCount(); // Account for both client and merc leaving the group - if (GetMerc() && g == GetMerc()->GetGroup()) - { + if (GetMerc() && g == GetMerc()->GetGroup()) { MemberCount -= 1; } - if(MemberCount < 3) - { + if (RuleB(Bots, Enabled)) { + std::list sbl = g->GetRawBotList(); + + for (auto botID : sbl) { + auto b = entity_list.GetBotByBotID(botID); + + if (b) { + if (b->GetBotOwnerCharacterID() == CharacterID()) { + MemberCount -= 1; + } + } + else { + if (database.botdb.GetOwnerID(botID) == CharacterID()) { + MemberCount -= 1; + } + } + } + } + + if (MemberCount < 3) { g->DisbandGroup(); } - else - { + else { g->DelMember(this); - if (GetMerc() != nullptr && g == GetMerc()->GetGroup() ) - { + + if (GetMerc() != nullptr && g == GetMerc()->GetGroup()) { GetMerc()->RemoveMercFromGroup(GetMerc(), GetMerc()->GetGroup()); } + + if (RuleB(Bots, Enabled)) { + g->RemoveClientsBots(this); + } } } - else - { + else { //force things a little Group::RemoveFromGroup(this); @@ -2222,24 +2264,24 @@ void Group::ClearAllNPCMarks() } -int8 Group::GetNumberNeedingHealedInGroup(int8 hpr, bool includePets) { - int8 needHealed = 0; +int8 Group::GetNumberNeedingHealedInGroup(int8 hpr, bool include_pets) { + int8 need_healed = 0; for( int i = 0; iqglobal) { if(members[i]->GetHPRatio() <= hpr) - needHealed++; + need_healed++; - if(includePets) { + if(include_pets) { if(members[i]->GetPet() && members[i]->GetPet()->GetHPRatio() <= hpr) { - needHealed++; + need_healed++; } } } } - return needHealed; + return need_healed; } void Group::UpdateGroupAAs() @@ -2419,7 +2461,7 @@ bool Group::AmIMainAssist(const char *mob_name) if (!mob_name) return false; - return !((bool)MainTankName.compare(mob_name)); + return !((bool)MainAssistName.compare(mob_name)); } bool Group::AmIPuller(const char *mob_name) @@ -2430,19 +2472,63 @@ bool Group::AmIPuller(const char *mob_name) return !((bool)PullerName.compare(mob_name)); } -bool Group::HasRole(Mob *m, uint8 Role) +bool Group::HasRole(Mob* m, uint8 Role) { - if(!m) + if (!m) { return false; - - for(uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i) - { - if((m == members[i]) && (MemberRoles[i] & Role)) - return true; } + + for (uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i) { + if (m == members[i] && MemberRoles[i] & Role) { + return true; + } + } + return false; } +uint8 Group::GetMemberRole(Mob* m) +{ + if (!m) { + return 0; + } + + for (uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i) { + if (m == members[i]) { + uint8 role = MemberRoles[i]; + + if (m == leader) { + role |= RoleLeader; + } + + return role; + } + } + + return 0; +} + +uint8 Group::GetMemberRole(const char* name) +{ + if (!name) { + return 0; + } + + for (uint32 i = 0; i < MAX_GROUP_MEMBERS; ++i) { + if (!strcasecmp(membername[i], name)) { + uint8 role = MemberRoles[i]; + + if (leader && !strcasecmp(leader->GetName(), name)) { + role |= RoleLeader; + } + + return role; + } + } + + return 0; +} + void Group::QueueClients(Mob *sender, const EQApplicationPacket *app, bool ack_required /*= true*/, bool ignore_sender /*= true*/, float distance /*= 0*/) { if (sender && sender->IsClient()) { for (uint32 i = 0; i < MAX_GROUP_MEMBERS; i++) { @@ -2474,25 +2560,21 @@ void Group::QueueClients(Mob *sender, const EQApplicationPacket *app, bool ack_r } } -bool Group::DoesAnyMemberHaveExpeditionLockout( - const std::string& expedition_name, const std::string& event_name, int max_check_count) +bool Group::AnyMemberHasDzLockout(const std::string& expedition, const std::string& event) { - if (max_check_count <= 0) + std::vector names; + for (int i = 0; i < MAX_GROUP_MEMBERS; ++i) { - max_check_count = MAX_GROUP_MEMBERS; - } - - for (int i = 0; i < MAX_GROUP_MEMBERS && i < max_check_count; ++i) - { - if (membername[i][0]) + if (!members[i] && membername[i][0]) { - if (Expedition::HasLockoutByCharacterName(membername[i], expedition_name, event_name)) - { - return true; - } + names.emplace_back(membername[i]); // out of zone member + } + else if (members[i] && members[i]->IsClient() && members[i]->CastToClient()->HasDzLockout(expedition, event)) + { + return true; } } - return false; + return !CharacterExpeditionLockoutsRepository::GetLockouts(database, names, expedition, event).empty(); } bool Group::IsLeader(const char* name) { @@ -2576,3 +2658,76 @@ void Group::AddToGroup(AddToGroupRequest r) } ); } + +void Group::RemoveClientsBots(Client* c) { + std::list sbl = GetRawBotList(); + + for (auto botID : sbl) { + auto b = entity_list.GetBotByBotID(botID); + + if (b) { + if (b->GetBotOwnerCharacterID() == c->CharacterID()) { + b->RemoveBotFromGroup(b, this); + } + } + else { + if (database.botdb.GetOwnerID(botID) == c->CharacterID()) { + auto botName = database.botdb.GetBotNameByID(botID); + + for (uint32 i = 0; i < MAX_GROUP_MEMBERS; i++) { + if (membername[i] == botName) { + members[i] = nullptr; + membername[i][0] = '\0'; + memset(membername[i], 0, 64); + MemberRoles[i] = 0; + break; + } + } + + auto pack = new ServerPacket(ServerOP_GroupLeave, sizeof(ServerGroupLeave_Struct)); + ServerGroupLeave_Struct* gl = (ServerGroupLeave_Struct*)pack->pBuffer; + gl->gid = GetID(); + gl->zoneid = zone->GetZoneID(); + gl->instance_id = zone->GetInstanceID(); + strcpy(gl->member_name, botName.c_str()); + worldserver.SendPacket(pack); + safe_delete(pack); + + auto outapp = new EQApplicationPacket(OP_GroupUpdate, sizeof(GroupJoin_Struct)); + GroupJoin_Struct* gu = (GroupJoin_Struct*)outapp->pBuffer; + gu->action = groupActLeave; + strcpy(gu->membername, botName.c_str()); + strcpy(gu->yourname, botName.c_str()); + + gu->leader_aas = LeaderAbilities; + + for (uint32 i = 0; i < MAX_GROUP_MEMBERS; i++) { + if (members[i] == nullptr) { + //if (DEBUG>=5) LogFile->write(EQEMuLog::Debug, "Group::DelMember() null member at slot %i", i); + continue; + } + + if (membername[i] != botName.c_str()) { + strcpy(gu->yourname, members[i]->GetCleanName()); + + if (members[i]->IsClient()) { + members[i]->CastToClient()->QueuePacket(outapp); + } + } + } + + safe_delete(outapp); + + DelMemberOOZ(botName.c_str()); + + GroupIdRepository::DeleteWhere( + database, + fmt::format( + "`bot_id` = {}", + botID + ) + ); + } + } + } +} diff --git a/zone/groups.h b/zone/groups.h index 28954ee75..8c312f6ef 100644 --- a/zone/groups.h +++ b/zone/groups.h @@ -30,7 +30,12 @@ class Mob; #define MAX_MARKED_NPCS 3 -enum { RoleAssist = 1, RoleTank = 2, RolePuller = 4 }; +enum { + RoleAssist = 1, + RoleTank = 2, + RolePuller = 4, + RoleLeader = 8 +}; class GroupIDConsumer { public: @@ -63,17 +68,16 @@ public: void AddMember(const std::string& new_member_name); void SendUpdate(uint32 type,Mob* member); void SendLeadershipAAUpdate(); - void SendWorldGroup(uint32 zone_id,Mob* zoningmember); bool DelMemberOOZ(const char *Name); bool DelMember(Mob* oldmember,bool ignoresender = false); void DisbandGroup(bool joinraid = false); void GetMemberList(std::list& member_list, bool clear_list = true); void GetClientList(std::list& client_list, bool clear_list = true); void GetBotList(std::list& bot_list, bool clear_list = true); + std::list GetRawBotList(); bool IsGroupMember(Mob* c); bool IsGroupMember(const char* name); bool Process(); - bool IsGroup() { return true; } void SendGroupJoinOOZ(Mob* NewMember); void CastGroupSpell(Mob* caster,uint16 spellid); void SplitExp(ExpSource exp_source, const uint64 exp, Mob* other); @@ -108,7 +112,7 @@ public: void UpdateGroupAAs(); void SaveGroupLeaderAA(); void MarkNPC(Mob* Target, int Number); - int8 GetNumberNeedingHealedInGroup(int8 hpr, bool includePets); + int8 GetNumberNeedingHealedInGroup(int8 hpr, bool include_pets); void DelegateMainTank(const char *NewMainAssistName, uint8 toggle = 0); void DelegateMainAssist(const char *NewMainAssistName, uint8 toggle = 0); void DelegatePuller(const char *NewMainAssistName, uint8 toggle = 0); @@ -120,6 +124,8 @@ public: void SetGroupTankTarget(Mob *m); void SetGroupPullerTarget(Mob *m); bool HasRole(Mob *m, uint8 Role); + uint8 GetMemberRole(Mob* m); + uint8 GetMemberRole(const char* name); void NotifyAssistTarget(Client *c); void NotifyTankTarget(Client *c); void NotifyPullerTarget(Client *c); @@ -155,13 +161,14 @@ public: void AddToGroup(AddToGroupRequest r); void AddToGroup(Mob* m); static void RemoveFromGroup(Mob* m); + void RemoveClientsBots(Client* c); void SetGroupMentor(int percent, char *name); void ClearGroupMentor(); inline int GetMentorPercent() { return mentor_percent; } inline Client *GetMentoree() { return mentoree; } - bool DoesAnyMemberHaveExpeditionLockout(const std::string& expedition_name, const std::string& event_name, int max_check_count = 0); + bool AnyMemberHasDzLockout(const std::string& expedition, const std::string& event); Mob* members[MAX_GROUP_MEMBERS] {nullptr}; char membername[MAX_GROUP_MEMBERS][64] {""}; diff --git a/zone/guild.cpp b/zone/guild.cpp index 40bddc2e6..9c48b5e3e 100644 --- a/zone/guild.cpp +++ b/zone/guild.cpp @@ -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(); diff --git a/zone/guild_mgr.cpp b/zone/guild_mgr.cpp index a453e5e9b..aa2d9fd70 100644 --- a/zone/guild_mgr.cpp +++ b/zone/guild_mgr.cpp @@ -16,15 +16,16 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "guild_mgr.h" +#include "../common/emu_versions.h" +#include "../common/repositories/guild_bank_repository.h" +#include "../common/repositories/guild_ranks_repository.h" #include "../common/servertalk.h" #include "../common/strings.h" -#include "string_ids.h" #include "client.h" -#include "guild_mgr.h" +#include "string_ids.h" #include "worldserver.h" #include "zonedb.h" -#include "../common/emu_versions.h" -#include "../common/repositories/guild_ranks_repository.h" ZoneGuildManager guild_mgr; @@ -405,6 +406,7 @@ void ZoneGuildManager::ProcessWorldPacket(ServerPacket *pack) c.second->SendGuildDeletePacket(s->guild_id); c.second->RefreshGuildInfo(); c.second->MessageString(Chat::Guild, GUILD_DISBANDED); + c.second->SendGuildList(); } } @@ -684,155 +686,108 @@ ZoneGuildManager::~ZoneGuildManager() GuildBankManager::~GuildBankManager() { - auto Iterator = Banks.begin(); - while(Iterator != Banks.end()) - { - safe_delete(*Iterator); - - ++Iterator; - } } -bool GuildBankManager::Load(uint32 guildID) +void GuildBankManager::Load(uint32 guild_id) { + auto results = GuildBankRepository::GetWhere( + database, + fmt::format("`guild_id` = '{}' ORDER BY `area`, `slot`", guild_id) + ); - std::string query = StringFormat("SELECT `area`, `slot`, `itemid`, `qty`, `donator`, `permissions`, `whofor` " - "FROM `guild_bank` WHERE `guildid` = %i", guildID); - auto results = database.QueryDatabase(query); - if(!results.Success()) { - return false; + auto bank = std::make_shared(); + bank->guild_id = guild_id; + int32 deposit_slot = 0; + int32 main_slot = 0; + + for (auto &it: results) { + if (it.area == GuildBankMainArea) { + it.slot = deposit_slot; + bank->items.main_area.emplace(deposit_slot, it); + deposit_slot++; + } + else { + it.slot = main_slot; + bank->items.deposit_area.emplace(main_slot, it); + main_slot++; + } } - auto bank = new GuildBank; - - bank->GuildID = guildID; - - for (int i = 0; i < GUILD_BANK_MAIN_AREA_SIZE; ++i) - bank->Items.MainArea[i].ItemID = 0; - - for (int i = 0; i < GUILD_BANK_DEPOSIT_AREA_SIZE; ++i) - bank->Items.DepositArea[i].ItemID = 0; - - char donator[64], whoFor[64]; - - for (auto row = results.begin(); row != results.end(); ++row) { - int area = Strings::ToInt(row[0]); - int slot = Strings::ToInt(row[1]); - int itemID = Strings::ToInt(row[2]); - int qty = Strings::ToInt(row[3]); - - if (row[4]) - strn0cpy(donator, row[4], sizeof(donator)); - else - donator[0] = '\0'; - - int permissions = Strings::ToInt(row[5]); - - if (row[6]) - strn0cpy(whoFor, row[6], sizeof(whoFor)); - else - whoFor[0] = '\0'; - - if (slot < 0) - continue; - - GuildBankItem *itemSection = nullptr; - - if (area == GuildBankMainArea && slot < GUILD_BANK_MAIN_AREA_SIZE) - itemSection = bank->Items.MainArea; - else if (area != GuildBankMainArea && slot < GUILD_BANK_DEPOSIT_AREA_SIZE) - itemSection = bank->Items.DepositArea; - else - continue; - - itemSection[slot].ItemID = itemID; - itemSection[slot].Quantity = qty; - - strn0cpy(itemSection[slot].Donator, donator, sizeof(donator)); - - itemSection[slot].Permissions = permissions; - - strn0cpy(itemSection[slot].WhoFor, whoFor, sizeof(whoFor)); - } - - Banks.push_back(bank); - - return true; + banks.push_back(bank); } -bool GuildBankManager::IsLoaded(uint32 GuildID) +bool GuildBankManager::IsLoaded(uint32 guild_id) { - auto Iterator = GetGuildBank(GuildID); - - return (Iterator != Banks.end()); + return GetGuildBank(guild_id) ? true : false; } void GuildBankManager::SendGuildBank(Client *c) { - if(!c || !c->IsInAGuild()) - return; - - if(!IsLoaded(c->GuildID())) - Load(c->GuildID()); - - auto Iterator = GetGuildBank(c->GuildID()); - - if(Iterator == Banks.end()) - { - LogError("Unable to find guild bank for guild ID [{}]", c->GuildID()); - + if (!c || !c->IsInAGuild()) { return; } - auto &guild_bank = *Iterator; + if (!IsLoaded(c->GuildID())) { + Load(c->GuildID()); + } + + auto guild_bank = GetGuildBank(c->GuildID()); + if (!guild_bank) { + LogError("Unable to find guild bank for guild ID [{}]", c->GuildID()); + return; + } // RoF+ uses a bulk list packet -- This is also how the Action 0 of older clients basically works if (c->ClientVersionBit() & EQ::versions::maskRoFAndLater) { + auto outapp = new EQApplicationPacket(OP_GuildBankItemList, sizeof(GuildBankItemListEntry_Struct) * 240); - for (int i = 0; i < GUILD_BANK_DEPOSIT_AREA_SIZE; ++i) { - const EQ::ItemData *Item = database.GetItem(guild_bank->Items.DepositArea[i].ItemID); - if (Item) { + for (int i = 0; i < c->GetInv().GetLookup()->InventoryTypeSize.GuildBankDeposit; i++) { + const EQ::ItemData *item = database.GetItem(guild_bank->items.deposit_area[i].item_id); + if (item) { outapp->WriteUInt8(1); - outapp->WriteUInt32(guild_bank->Items.DepositArea[i].Permissions); - outapp->WriteString(guild_bank->Items.DepositArea[i].WhoFor); - outapp->WriteString(guild_bank->Items.DepositArea[i].Donator); - outapp->WriteUInt32(Item->ID); - outapp->WriteUInt32(Item->Icon); - if (Item->Stackable) { - outapp->WriteUInt32(guild_bank->Items.DepositArea[i].Quantity); - outapp->WriteUInt8(Item->StackSize == guild_bank->Items.DepositArea[i].Quantity ? 1 : 1); - } else { + outapp->WriteUInt32(guild_bank->items.deposit_area[i].permissions); + outapp->WriteString(guild_bank->items.deposit_area[i].who_for.c_str()); + outapp->WriteString(guild_bank->items.deposit_area[i].donator.c_str()); + outapp->WriteUInt32(item->ID); + outapp->WriteUInt32(item->Icon); + if (item->Stackable) { + outapp->WriteUInt32(guild_bank->items.deposit_area[i].quantity); + outapp->WriteUInt8(item->StackSize == guild_bank->items.deposit_area[i].quantity ? 1 : 1); + } + else { outapp->WriteUInt32(1); outapp->WriteUInt8(0); } - outapp->WriteUInt8(Item->IsEquipable(c->GetBaseRace(), c->GetBaseClass()) ? 1 : 0); - outapp->WriteString(Item->Name); - } else { + outapp->WriteUInt8(item->IsEquipable(c->GetBaseRace(), c->GetBaseClass()) ? 1 : 0); + outapp->WriteString(item->Name); + } + else { outapp->WriteUInt8(0); // empty } } - outapp->SetWritePosition(outapp->GetWritePosition() + 20); // newer clients have 40 deposit slots, keep them 0 for now - for (int i = 0; i < GUILD_BANK_MAIN_AREA_SIZE; ++i) { - const EQ::ItemData *Item = database.GetItem(guild_bank->Items.MainArea[i].ItemID); + for (int i = 0; i < c->GetInv().GetLookup()->InventoryTypeSize.GuildBankMain; ++i) { + const EQ::ItemData *Item = database.GetItem(guild_bank->items.main_area[i].item_id); if (Item) { outapp->WriteUInt8(1); - outapp->WriteUInt32(guild_bank->Items.MainArea[i].Permissions); - outapp->WriteString(guild_bank->Items.MainArea[i].WhoFor); - outapp->WriteString(guild_bank->Items.MainArea[i].Donator); + outapp->WriteUInt32(guild_bank->items.main_area[i].permissions); + outapp->WriteString(guild_bank->items.main_area[i].who_for.c_str()); + outapp->WriteString(guild_bank->items.main_area[i].donator.c_str()); outapp->WriteUInt32(Item->ID); outapp->WriteUInt32(Item->Icon); if (Item->Stackable) { - outapp->WriteUInt32(guild_bank->Items.MainArea[i].Quantity); - outapp->WriteUInt8(Item->StackSize == guild_bank->Items.MainArea[i].Quantity ? 1 : 1); - } else { + outapp->WriteUInt32(guild_bank->items.main_area[i].quantity); + outapp->WriteUInt8(Item->StackSize == guild_bank->items.main_area[i].quantity ? 1 : 1); + } + else { outapp->WriteUInt32(1); outapp->WriteUInt8(0); } outapp->WriteUInt8(Item->IsEquipable(c->GetBaseRace(), c->GetBaseClass()) ? 1 : 0); outapp->WriteString(Item->Name); - } else { + } + else { outapp->WriteUInt8(0); // empty } } @@ -842,653 +797,557 @@ void GuildBankManager::SendGuildBank(Client *c) return; } - for(int i = 0; i < GUILD_BANK_DEPOSIT_AREA_SIZE; ++i) - { - if(guild_bank->Items.DepositArea[i].ItemID > 0) - { - const EQ::ItemData *Item = database.GetItem(guild_bank->Items.DepositArea[i].ItemID); - - if(!Item) + for (int i = 0; i < c->GetInv().GetLookup()->InventoryTypeSize.GuildBankDeposit; ++i) { + if (guild_bank->items.deposit_area[i].item_id > 0) { + const EQ::ItemData *item = database.GetItem(guild_bank->items.deposit_area[i].item_id); + if (!item) { continue; - - auto outapp = new EQApplicationPacket(OP_GuildBank, sizeof(GuildBankItemUpdate_Struct)); - - GuildBankItemUpdate_Struct *gbius = (GuildBankItemUpdate_Struct*)outapp->pBuffer; - - if(!Item->Stackable) - gbius->Init(GuildBankItemUpdate, 1, i, GuildBankDepositArea, 1, Item->ID, Item->Icon, 1, - guild_bank->Items.DepositArea[i].Permissions, 0, 0); - else - { - if(guild_bank->Items.DepositArea[i].Quantity == Item->StackSize) - gbius->Init(GuildBankItemUpdate, 1, i, GuildBankDepositArea, 1, Item->ID, Item->Icon, - guild_bank->Items.DepositArea[i].Quantity, guild_bank->Items.DepositArea[i].Permissions, 1, 0); - else - gbius->Init(GuildBankItemUpdate, 1, i, GuildBankDepositArea, 1, Item->ID, Item->Icon, - guild_bank->Items.DepositArea[i].Quantity, guild_bank->Items.DepositArea[i].Permissions, 1, 0); } - strn0cpy(gbius->ItemName, Item->Name, sizeof(gbius->ItemName)); + auto outapp = new EQApplicationPacket(OP_GuildBank, sizeof(GuildBankItemUpdate_Struct)); + auto gbius = (GuildBankItemUpdate_Struct *) outapp->pBuffer; - strn0cpy(gbius->Donator, guild_bank->Items.DepositArea[i].Donator, sizeof(gbius->Donator)); + if (!item->Stackable) { + gbius->Init( + GuildBankItemUpdate, + 1, + i, + GuildBankDepositArea, + 1, + item->ID, + item->Icon, + 1, + guild_bank->items.deposit_area[i].permissions, + 0, + false + ); + } + else { + if (guild_bank->items.deposit_area[i].quantity == item->StackSize) { + gbius->Init( + GuildBankItemUpdate, + 1, + i, + GuildBankDepositArea, + 1, + item->ID, + item->Icon, + guild_bank->items.deposit_area[i].quantity, + guild_bank->items.deposit_area[i].permissions, + 1, + false + ); + } + else { + gbius->Init( + GuildBankItemUpdate, + 1, + i, + GuildBankDepositArea, + 1, + item->ID, + item->Icon, + guild_bank->items.deposit_area[i].quantity, + guild_bank->items.deposit_area[i].permissions, + 1, + false + ); + } + } - strn0cpy(gbius->WhoFor, guild_bank->Items.DepositArea[i].WhoFor, sizeof(gbius->WhoFor)); + strn0cpy(gbius->item_name, item->Name, sizeof(gbius->item_name)); + strn0cpy(gbius->donator, guild_bank->items.deposit_area[i].donator.c_str(), sizeof(gbius->donator)); + strn0cpy(gbius->who_for, guild_bank->items.deposit_area[i].who_for.c_str(), sizeof(gbius->who_for)); c->FastQueuePacket(&outapp); } } - for(int i = 0; i < GUILD_BANK_MAIN_AREA_SIZE; ++i) - { - if(guild_bank->Items.MainArea[i].ItemID > 0) - { - const EQ::ItemData *Item = database.GetItem(guild_bank->Items.MainArea[i].ItemID); + for (int i = 0; i < c->GetInv().GetLookup()->InventoryTypeSize.GuildBankMain; ++i) { + if (guild_bank->items.main_area[i].item_id > 0) { + const EQ::ItemData *item = database.GetItem(guild_bank->items.main_area[i].item_id); - if(!Item) + if (!item) { continue; - - bool Useable = Item->IsEquipable(c->GetBaseRace(), c->GetBaseClass()); - - auto outapp = new EQApplicationPacket(OP_GuildBank, sizeof(GuildBankItemUpdate_Struct)); - - GuildBankItemUpdate_Struct *gbius = (GuildBankItemUpdate_Struct*)outapp->pBuffer; - - if(!Item->Stackable) - gbius->Init(GuildBankItemUpdate, 1, i, GuildBankMainArea, 1, Item->ID, Item->Icon, 1, - guild_bank->Items.MainArea[i].Permissions, 0, Useable); - else - { - if(guild_bank->Items.MainArea[i].Quantity == Item->StackSize) - gbius->Init(GuildBankItemUpdate, 1, i, GuildBankMainArea, 1, Item->ID, Item->Icon, - guild_bank->Items.MainArea[i].Quantity, guild_bank->Items.MainArea[i].Permissions, 1, Useable); - else - gbius->Init(GuildBankItemUpdate, 1, i, GuildBankMainArea, 1, Item->ID, Item->Icon, - guild_bank->Items.MainArea[i].Quantity, guild_bank->Items.MainArea[i].Permissions, 1, Useable); } - strn0cpy(gbius->ItemName, Item->Name, sizeof(gbius->ItemName)); + bool useable = item->IsEquipable(c->GetBaseRace(), c->GetBaseClass()); + auto outapp = new EQApplicationPacket(OP_GuildBank, sizeof(GuildBankItemUpdate_Struct)); + auto gbius = (GuildBankItemUpdate_Struct *) outapp->pBuffer; - strn0cpy(gbius->Donator, guild_bank->Items.MainArea[i].Donator, sizeof(gbius->Donator)); + if (!item->Stackable) { + gbius->Init( + GuildBankItemUpdate, + 1, + i, + GuildBankMainArea, + 1, + item->ID, + item->Icon, + 1, + guild_bank->items.main_area[i].permissions, + 0, + useable + ); + } + else { + if (guild_bank->items.main_area[i].quantity == item->StackSize) { + gbius->Init( + GuildBankItemUpdate, + 1, + i, + GuildBankMainArea, + 1, + item->ID, + item->Icon, + guild_bank->items.main_area[i].quantity, + guild_bank->items.main_area[i].permissions, + 1, + useable + ); + } + else { + gbius->Init( + GuildBankItemUpdate, + 1, + i, + GuildBankMainArea, + 1, + item->ID, + item->Icon, + guild_bank->items.main_area[i].quantity, + guild_bank->items.main_area[i].permissions, + 1, + useable + ); + } + } - strn0cpy(gbius->WhoFor, guild_bank->Items.MainArea[i].WhoFor, sizeof(gbius->WhoFor)); + strn0cpy(gbius->item_name, item->Name, sizeof(gbius->item_name)); + strn0cpy(gbius->donator, guild_bank->items.main_area[i].donator.c_str(), sizeof(gbius->donator)); + strn0cpy(gbius->who_for, guild_bank->items.main_area[i].who_for.c_str(), sizeof(gbius->who_for)); c->FastQueuePacket(&outapp); } } } -bool GuildBankManager::IsAreaFull(uint32 GuildID, uint16 Area) + +bool GuildBankManager::IsAreaFull(uint32 guild_id, uint16 area) { - auto Iterator = GetGuildBank(GuildID); + return NextFreeBankSlot(guild_id, area) == -1; +} - if(Iterator == Banks.end()) - return true; - - GuildBankItem* BankArea = nullptr; - - int AreaSize = 0; - - if(Area == GuildBankMainArea) - { - BankArea = &(*Iterator)->Items.MainArea[0]; - - AreaSize = GUILD_BANK_MAIN_AREA_SIZE; - } - else - { - BankArea = &(*Iterator)->Items.DepositArea[0]; - - AreaSize = GUILD_BANK_DEPOSIT_AREA_SIZE; +bool GuildBankManager::AddItem(GuildBankRepository::GuildBank &guild_bank_item, Client* c) +{ + auto guild_bank = GetGuildBank(guild_bank_item.guild_id); + if (!guild_bank) { + LogError("Unable to find guild bank for guild ID [{}]", guild_bank_item.guild_id); + return false; } - for(int i = 0; i < AreaSize; ++i) - if(BankArea[i].ItemID == 0) + switch (guild_bank_item.area) { + case GuildBankMainArea: { + for (auto &[key, item]: guild_bank->items.main_area) { + if (item.item_id == 0) { + item.slot = NextFreeBankSlot(guild_bank_item.guild_id, GuildBankMainArea); + guild_bank_item.slot = item.slot; + auto e = GuildBankRepository::InsertOne(database, guild_bank_item); + if (!e.id) { + return false; + } + + guild_bank_item.id = e.id; + item = guild_bank_item; + break; + } + } + break; + } + case GuildBankDepositArea: { + for (auto &[key, item]: guild_bank->items.deposit_area) { + if (item.item_id == 0) { + item.slot = NextFreeBankSlot(guild_bank_item.guild_id, GuildBankDepositArea); + guild_bank_item.slot = item.slot; + auto e = GuildBankRepository::InsertOne(database, guild_bank_item); + if (!e.id) { + return false; + } + guild_bank_item.id = e.id; + item = guild_bank_item; + break; + } + } + break; + } + default: { return false; + } + } + + SendGuildBankItemUpdate(guild_bank_item.guild_id, guild_bank_item.slot, guild_bank_item.area, true, c); return true; } -bool GuildBankManager::AddItem(uint32 GuildID, uint8 Area, uint32 ItemID, int32 QtyOrCharges, const char *Donator, uint8 Permissions, const char *WhoFor) +int GuildBankManager::Promote(uint32 guild_id, int slot_id, Client* c) { - auto Iterator = GetGuildBank(GuildID); - - if(Iterator == Banks.end()) - { - LogError("Unable to find guild bank for guild ID [{}]", GuildID); - - return false; + if (slot_id < 0) { + return -1; } - GuildBankItem* BankArea = nullptr; - - int AreaSize = 0; - - if(Area == GuildBankMainArea) - { - BankArea = &(*Iterator)->Items.MainArea[0]; - - AreaSize = GUILD_BANK_MAIN_AREA_SIZE; - } - else - { - BankArea = &(*Iterator)->Items.DepositArea[0]; - - AreaSize = GUILD_BANK_DEPOSIT_AREA_SIZE; + auto guild_bank = GetGuildBank(guild_id); + if (!guild_bank) { + return -1; } - int Slot = -1; - - for(int i = 0; i < AreaSize; ++i) - { - if(BankArea[i].ItemID == 0) - { - BankArea[i].ItemID = ItemID; - - BankArea[i].Quantity = QtyOrCharges; - - strn0cpy(BankArea[i].Donator, Donator, sizeof(BankArea[i].Donator)); - - BankArea[i].Permissions = Permissions; - - strn0cpy(BankArea[i].WhoFor, WhoFor, sizeof(BankArea[i].WhoFor)); - - Slot = i; - - break; - } + auto it_deposit = guild_bank->items.deposit_area.find(slot_id); + if (it_deposit == std::end(guild_bank->items.deposit_area)) { + return -1; } - if(Slot < 0) - { - LogError("No space to add item to the guild bank"); + auto new_item = GuildBankRepository::NewEntity(); + new_item.id = it_deposit->second.id; + new_item.guild_id = it_deposit->second.guild_id; + new_item.area = GuildBankMainArea; + new_item.slot = NextFreeBankSlot(guild_id, GuildBankMainArea); + new_item.item_id = it_deposit->second.item_id; + new_item.augment_one_id = it_deposit->second.augment_one_id; + new_item.augment_two_id = it_deposit->second.augment_two_id; + new_item.augment_three_id = it_deposit->second.augment_three_id; + new_item.augment_four_id = it_deposit->second.augment_four_id; + new_item.augment_five_id = it_deposit->second.augment_five_id; + new_item.augment_six_id = it_deposit->second.augment_six_id; + new_item.quantity = it_deposit->second.quantity; + new_item.donator = it_deposit->second.donator; + new_item.who_for = it_deposit->second.who_for; + new_item.permissions = it_deposit->second.permissions; - return false; - } + GuildBankRepository::UpdateOne(database, new_item); - std::string query = StringFormat("INSERT INTO `guild_bank` " - "(`guildid`, `area`, `slot`, `itemid`, `qty`, `donator`, `permissions`, `WhoFor`) " - "VALUES (%i, %i, %i, %i, %i, '%s', %i, '%s')", - GuildID, Area, Slot, ItemID, QtyOrCharges, Donator, Permissions, WhoFor); - auto results = database.QueryDatabase(query); - if(!results.Success()) { - return false; - } + SendGuildBankItemUpdate(it_deposit->second.guild_id, it_deposit->second.slot, it_deposit->second.area, false, c); + it_deposit->second = std::move(GuildBankRepository::NewEntity()); + guild_bank->items.main_area[new_item.slot] = new_item; + SendGuildBankItemUpdate(new_item.guild_id, new_item.slot, new_item.area, true, c); - const EQ::ItemData *Item = database.GetItem(ItemID); - - GuildBankItemUpdate_Struct gbius; - - if(!Item->Stackable) - gbius.Init(GuildBankItemUpdate, 1, Slot, Area, 1, ItemID, Item->Icon, Item->Stackable ? QtyOrCharges : 1, Permissions, 0, 0); - else - { - if(QtyOrCharges == Item->StackSize) - gbius.Init(GuildBankItemUpdate, 1, Slot, Area, 1, ItemID, Item->Icon, Item->Stackable ? QtyOrCharges : 1, Permissions, 1, 0); - else - gbius.Init(GuildBankItemUpdate, 1, Slot, Area, 1, ItemID, Item->Icon, Item->Stackable ? QtyOrCharges : 1, Permissions, 1, 0); - } - - strn0cpy(gbius.ItemName, Item->Name, sizeof(gbius.ItemName)); - - strn0cpy(gbius.Donator, Donator, sizeof(gbius.Donator)); - - strn0cpy(gbius.WhoFor, WhoFor, sizeof(gbius.WhoFor)); - - entity_list.QueueClientsGuildBankItemUpdate(&gbius, GuildID); - - return true; + return new_item.slot; } -int GuildBankManager::Promote(uint32 guildID, int slotID) +void GuildBankManager::SetPermissions(uint32 guild_id, uint16 slot_id, uint32 permissions, const char *member_name, Client* c) { - if((slotID < 0) || (slotID > (GUILD_BANK_DEPOSIT_AREA_SIZE - 1))) - return -1; - - auto iter = GetGuildBank(guildID); - - if(iter == Banks.end()) - return -1; - - if((*iter)->Items.DepositArea[slotID].ItemID == 0) - return -1; - - int mainSlot = -1; - - for(int i = 0; i < GUILD_BANK_MAIN_AREA_SIZE; ++i) - if((*iter)->Items.MainArea[i].ItemID == 0) { - mainSlot = i; - break; - } - - if(mainSlot == -1) - return -1; - - (*iter)->Items.MainArea[mainSlot].ItemID = (*iter)->Items.DepositArea[slotID].ItemID; - (*iter)->Items.MainArea[mainSlot].Quantity = (*iter)->Items.DepositArea[slotID].Quantity; - (*iter)->Items.MainArea[mainSlot].Permissions = (*iter)->Items.DepositArea[slotID].Permissions; - - strn0cpy((*iter)->Items.MainArea[mainSlot].Donator, (*iter)->Items.DepositArea[slotID].Donator, sizeof((*iter)->Items.MainArea[mainSlot].Donator)); - strn0cpy((*iter)->Items.MainArea[mainSlot].WhoFor, (*iter)->Items.DepositArea[slotID].WhoFor, sizeof((*iter)->Items.MainArea[mainSlot].WhoFor)); - - std::string query = StringFormat("UPDATE `guild_bank` SET `area` = 1, `slot` = %i " - "WHERE `guildid` = %i AND `area` = 0 AND `slot` = %i " - "LIMIT 1", mainSlot, guildID, slotID); - auto results = database.QueryDatabase(query); - if (!results.Success()) { - return -1; + auto guild_bank = GetGuildBank(guild_id); + if (!guild_bank) { + return; } - (*iter)->Items.DepositArea[slotID].ItemID = 0; + auto item = &guild_bank->items.main_area[slot_id]; + item->permissions = permissions; + item->who_for.clear(); - const EQ::ItemData *Item = database.GetItem((*iter)->Items.MainArea[mainSlot].ItemID); - - GuildBankItemUpdate_Struct gbius; - - if(!Item->Stackable) - gbius.Init(GuildBankItemUpdate, 1, mainSlot, GuildBankMainArea, 1, Item->ID, Item->Icon, 1, 0, 0, 0); - else - { - if((*iter)->Items.MainArea[mainSlot].Quantity == Item->StackSize) - gbius.Init(GuildBankItemUpdate, 1, mainSlot, GuildBankMainArea, 1, Item->ID, Item->Icon, - (*iter)->Items.MainArea[mainSlot].Quantity, 0, 1, 0); - else - gbius.Init(GuildBankItemUpdate, 1, mainSlot, GuildBankMainArea, 1, Item->ID, Item->Icon, - (*iter)->Items.MainArea[mainSlot].Quantity, 0, 1, 0); + if (permissions == GuildBankSingleMember) { + item->who_for = member_name; } - strn0cpy(gbius.ItemName, Item->Name, sizeof(gbius.ItemName)); - - entity_list.QueueClientsGuildBankItemUpdate(&gbius, guildID); - - gbius.Init(GuildBankItemUpdate, 1, slotID, GuildBankDepositArea, 0, 0, 0, 0, 0, 0, 0); - - entity_list.QueueClientsGuildBankItemUpdate(&gbius, guildID); - - return mainSlot; + GuildBankRepository::UpdateOne(database, *item); + SendGuildBankItemUpdate(item->guild_id, item->slot, item->area, true, c); } -void GuildBankManager::SetPermissions(uint32 guildID, uint16 slotID, uint32 permissions, const char *memberName) +std::unique_ptr GuildBankManager::GetItem(uint32 guild_id, uint16 area, uint16 slot_id, uint32 quantity) { - if((slotID > (GUILD_BANK_MAIN_AREA_SIZE - 1))) - return; - - auto iter = GetGuildBank(guildID); - - if(iter == Banks.end()) - return; - - if((*iter)->Items.MainArea[slotID].ItemID == 0) - return; - - std::string query = StringFormat("UPDATE `guild_bank` SET `permissions` = %i, `whofor` = '%s' " - "WHERE `guildid` = %i AND `area` = 1 AND `slot` = %i LIMIT 1", - permissions, memberName, guildID, slotID); - auto results = database.QueryDatabase(query); - if(!results.Success()) - { - return; - } - - (*iter)->Items.MainArea[slotID].Permissions = permissions; - - if(permissions == GuildBankSingleMember) - strn0cpy((*iter)->Items.MainArea[slotID].WhoFor, memberName, sizeof((*iter)->Items.MainArea[slotID].WhoFor)); - else - (*iter)->Items.MainArea[slotID].WhoFor[0] = '\0'; - - const EQ::ItemData *Item = database.GetItem((*iter)->Items.MainArea[slotID].ItemID); - - GuildBankItemUpdate_Struct gbius; - - if(!Item->Stackable) - gbius.Init(GuildBankItemUpdate, 1, slotID, GuildBankMainArea, 1, Item->ID, Item->Icon, 1, (*iter)->Items.MainArea[slotID].Permissions, 0, 0); - else - { - if((*iter)->Items.MainArea[slotID].Quantity == Item->StackSize) - gbius.Init(GuildBankItemUpdate, 1, slotID, GuildBankMainArea, 1, Item->ID, Item->Icon, - (*iter)->Items.MainArea[slotID].Quantity, (*iter)->Items.MainArea[slotID].Permissions, 1, 0); - else - gbius.Init(GuildBankItemUpdate, 1, slotID, GuildBankMainArea, 1, Item->ID, Item->Icon, - (*iter)->Items.MainArea[slotID].Quantity, (*iter)->Items.MainArea[slotID].Permissions, 1, 0); - } - - - strn0cpy(gbius.ItemName, Item->Name, sizeof(gbius.ItemName)); - - strn0cpy(gbius.WhoFor, (*iter)->Items.MainArea[slotID].WhoFor, sizeof(gbius.WhoFor)); - - entity_list.QueueClientsGuildBankItemUpdate(&gbius, guildID); -} - -EQ::ItemInstance* GuildBankManager::GetItem(uint32 GuildID, uint16 Area, uint16 SlotID, uint32 Quantity) -{ - auto Iterator = GetGuildBank(GuildID); - - if(Iterator == Banks.end()) + auto guild_bank = GetGuildBank(guild_id); + if (!guild_bank) { return nullptr; - - GuildBankItem* BankArea = nullptr; - - EQ::ItemInstance* inst = nullptr; - - if(Area == GuildBankDepositArea) - { - if((SlotID > (GUILD_BANK_DEPOSIT_AREA_SIZE - 1))) - return nullptr; - - inst = database.CreateItem((*Iterator)->Items.DepositArea[SlotID].ItemID); - - if(!inst) - return nullptr; - - BankArea = &(*Iterator)->Items.DepositArea[0]; - } - else - { - - if((SlotID > (GUILD_BANK_MAIN_AREA_SIZE - 1))) - return nullptr; - - inst = database.CreateItem((*Iterator)->Items.MainArea[SlotID].ItemID); - - if(!inst) - return nullptr; - - BankArea = &(*Iterator)->Items.MainArea[0]; } - if(!inst->IsStackable()) - inst->SetCharges(BankArea[SlotID].Quantity); - else - { - if(Quantity <= BankArea[SlotID].Quantity) - inst->SetCharges(Quantity); - else - inst->SetCharges(BankArea[SlotID].Quantity); + std::unique_ptr inst; + + if (area == GuildBankDepositArea) { + if (slot_id > guild_bank->items.deposit_area.size()) { + return nullptr; + } + + inst.reset(database.CreateItem( + guild_bank->items.deposit_area[slot_id].item_id, + guild_bank->items.deposit_area[slot_id].quantity, + guild_bank->items.deposit_area[slot_id].augment_one_id, + guild_bank->items.deposit_area[slot_id].augment_two_id, + guild_bank->items.deposit_area[slot_id].augment_three_id, + guild_bank->items.deposit_area[slot_id].augment_four_id, + guild_bank->items.deposit_area[slot_id].augment_five_id, + guild_bank->items.deposit_area[slot_id].augment_six_id) + ); + + if (!inst) { + return nullptr; + } + + if (!inst->IsStackable()) { + inst->SetCharges(guild_bank->items.deposit_area[slot_id].quantity); + } + else { + if (quantity <= guild_bank->items.deposit_area[slot_id].quantity) { + inst->SetCharges(quantity); + } + else { + inst->SetCharges(guild_bank->items.deposit_area[slot_id].quantity); + } + } + } + else { + if (slot_id > guild_bank->items.main_area.size()) { + return nullptr; + } + + inst.reset(database.CreateItem( + guild_bank->items.main_area[slot_id].item_id, + guild_bank->items.main_area[slot_id].quantity, + guild_bank->items.main_area[slot_id].augment_one_id, + guild_bank->items.main_area[slot_id].augment_two_id, + guild_bank->items.main_area[slot_id].augment_three_id, + guild_bank->items.main_area[slot_id].augment_four_id, + guild_bank->items.main_area[slot_id].augment_five_id, + guild_bank->items.main_area[slot_id].augment_six_id) + ); + + if (!inst) { + return nullptr; + } + + if (!inst->IsStackable()) { + inst->SetCharges(guild_bank->items.main_area[slot_id].quantity); + } + else { + if (quantity <= guild_bank->items.main_area[slot_id].quantity) { + inst->SetCharges(quantity); + } + else { + inst->SetCharges(guild_bank->items.main_area[slot_id].quantity); + } + } } return inst; } -bool GuildBankManager::HasItem(uint32 GuildID, uint32 ItemID) +bool GuildBankManager::HasItem(uint32 guild_id, uint32 item_id) { - auto Iterator = GetGuildBank(GuildID); - - if(Iterator == Banks.end()) + auto guild_bank = GetGuildBank(guild_id); + if (!guild_bank) { return false; + } - for(int i = 0; i < GUILD_BANK_MAIN_AREA_SIZE; ++i) - if((*Iterator)->Items.MainArea[i].ItemID == ItemID) + for (auto const &[slot, item]: guild_bank->items.deposit_area) { + if (item.item_id == item_id) { return true; + } + } - for(int i = 0; i < GUILD_BANK_DEPOSIT_AREA_SIZE; ++i) - if((*Iterator)->Items.DepositArea[i].ItemID == ItemID) + for (auto const &[slot, item]: guild_bank->items.main_area) { + if (item.item_id == item_id) { return true; + } + } return false; } -std::list::iterator GuildBankManager::GetGuildBank(uint32 GuildID) +std::shared_ptr GuildBankManager::GetGuildBank(const uint32 guild_id) { - auto Iterator = Banks.begin(); + if (!GuildBanks) { + return nullptr; + } - while(Iterator != Banks.end()) - { - if((*Iterator)->GuildID == GuildID) + for (auto &b:banks) { + if (b->guild_id == guild_id) { + return b; + } + } + + return nullptr; +} + +bool GuildBankManager::DeleteItem(uint32 guild_id, uint16 area, uint16 slot_id, uint32 quantity, Client* c) +{ + auto guild_bank = GetGuildBank(guild_id); + if (!guild_bank) { + return false; + } + + auto item = &guild_bank->items.deposit_area[slot_id]; + + if (area == GuildBankMainArea) { + item = &guild_bank->items.main_area[slot_id]; + } + + if (item->item_id == 0) { + return false; + } + + // delete the item + if (item->quantity == quantity) { + SendGuildBankItemUpdate(item->guild_id, item->slot, item->area, false, c); + GuildBankRepository::DeleteOne(database, item->id); + + item->item_id = 0; + item->id = 0; + item->guild_id = 0; + item->slot = 0; + item->quantity = 0; + item->permissions = 0; + item->donator.clear(); + item->who_for.clear(); + + return true; + } + + //otherwise update the quantity + item->quantity -= quantity; + GuildBankRepository::UpdateOne(database, *item); + SendGuildBankItemUpdate(item->guild_id, item->slot, item->area, true, c); + + return true; +} + +bool GuildBankManager::MergeStacks(uint32 guild_id, uint16 slot_id, Client* c) +{ + auto guild_bank = GetGuildBank(guild_id); + if (!guild_bank) { + return false; + } + + auto merge_bank_item = &guild_bank->items.main_area[slot_id]; + const auto merge_item = database.GetItem(merge_bank_item->item_id); + + if (merge_bank_item->item_id == 0 || !merge_item) { + return false; + } + + const int16 stack_size = merge_item->StackSize; + std::vector queue{}; + std::vector queue_delete{}; + std::vector queue_updates{}; + + for (auto &[key, item]: guild_bank->items.main_area) { + if (item.item_id == merge_bank_item->item_id && item.slot != slot_id) { + queue.push_back(&item); + } + } + + if (queue.empty()) { + return false; + } + + std::ranges::sort(queue.begin(), queue.end(), [](auto &a, auto &b) { return a->quantity < b->quantity; }); + + for (auto const &i: queue) { + const int16 remaining_stack_size = stack_size - static_cast(merge_bank_item->quantity); + merge_bank_item->quantity += i->quantity; + + if (merge_bank_item->quantity <= stack_size) { + i->item_id = 0; + queue_delete.push_back(std::to_string(i->id)); + queue_updates.push_back(*i); + continue; + } + + if (merge_bank_item->quantity > stack_size) { + merge_bank_item->quantity = stack_size; + i->quantity -= remaining_stack_size; + queue_updates.push_back(*i); + queue_updates.push_back(*merge_bank_item); break; - - ++Iterator; - } - - return Iterator; -} - -bool GuildBankManager::DeleteItem(uint32 guildID, uint16 area, uint16 slotID, uint32 quantity) -{ - auto iter = GetGuildBank(guildID); - - if(iter == Banks.end()) - return false; - - GuildBankItem* BankArea = nullptr; - - if(area == GuildBankMainArea) - { - if(slotID > (GUILD_BANK_MAIN_AREA_SIZE - 1)) - return false; - - BankArea = &(*iter)->Items.MainArea[0]; - } else { - if(slotID > (GUILD_BANK_DEPOSIT_AREA_SIZE - 1)) - return false; - - BankArea = &(*iter)->Items.DepositArea[0]; - } - - bool deleted = true; - - const EQ::ItemData *Item = database.GetItem(BankArea[slotID].ItemID); - - if(!Item->Stackable || (quantity >= BankArea[slotID].Quantity)) { - std::string query = StringFormat("DELETE FROM `guild_bank` WHERE `guildid` = %i " - "AND `area` = %i AND `slot` = %i LIMIT 1", - guildID, area, slotID); - auto results = database.QueryDatabase(query); - if(!results.Success()) { - return false; } - - BankArea[slotID].ItemID = 0; - - } else { - std::string query = StringFormat("UPDATE `guild_bank` SET `qty` = %i WHERE `guildid` = %i " - "AND `area` = %i AND `slot` = %i LIMIT 1", - BankArea[slotID].Quantity - quantity, guildID, area, slotID); - auto results = database.QueryDatabase(query); - if(!results.Success()) { - return false; - } - - BankArea[slotID].Quantity -= quantity; - - deleted = false; } - GuildBankItemUpdate_Struct gbius; - - if(!deleted) - { - gbius.Init(GuildBankItemUpdate, 1, slotID, area, 1, Item->ID, Item->Icon, BankArea[slotID].Quantity, BankArea[slotID].Permissions, 1, 0); - - strn0cpy(gbius.ItemName, Item->Name, sizeof(gbius.ItemName)); - - strn0cpy(gbius.WhoFor, BankArea[slotID].WhoFor, sizeof(gbius.WhoFor)); + if (merge_bank_item->quantity < stack_size) { + queue_updates.push_back(*merge_bank_item); + queue.push_back(merge_bank_item); } - else - gbius.Init(GuildBankItemUpdate, 1, slotID, area, 0, 0, 0, 0, 0, 0, 0); - entity_list.QueueClientsGuildBankItemUpdate(&gbius, guildID); + GuildBankRepository::ReplaceMany(database, queue_updates); + if (!queue_delete.empty()) { + GuildBankRepository::DeleteWhere( + database, fmt::format("`id` IN({})", Strings::Implode(",", queue_delete)).c_str() + ); + } - return true; - -} - -bool GuildBankManager::MergeStacks(uint32 GuildID, uint16 SlotID) -{ - if(SlotID > (GUILD_BANK_MAIN_AREA_SIZE - 1)) - return false; - - auto Iterator = GetGuildBank(GuildID); - - if(Iterator == Banks.end()) - return false; - - GuildBankItem* BankArea = &(*Iterator)->Items.MainArea[0]; - - if(BankArea[SlotID].ItemID == 0) - return false; - - const EQ::ItemData *Item = database.GetItem(BankArea[SlotID].ItemID); - - if(!Item->Stackable) - return false; - - uint32 ItemID = BankArea[SlotID].ItemID; - - for(int i = 0; i < GUILD_BANK_MAIN_AREA_SIZE - 1; ++i) - { - if(BankArea[i].ItemID != ItemID) + for (auto const &i: queue_updates) { + if (i.item_id == 0) { + SendGuildBankItemUpdate(i.guild_id, i.slot, GuildBankMainArea, false, c); continue; - - if(BankArea[i].Quantity == Item->StackSize) - continue; - - bool Merged = false; - - for(int j = i + 1; j < GUILD_BANK_MAIN_AREA_SIZE; ++j) - { - if(BankArea[j].ItemID != ItemID) - continue; - - if(BankArea[j].Permissions != BankArea[i].Permissions) - continue; - - if(BankArea[i].Permissions == 1) - if(strncmp(BankArea[i].WhoFor, BankArea[j].WhoFor, sizeof(BankArea[i].WhoFor))) - continue; - - if((BankArea[i].Quantity + BankArea[j].Quantity) <= Item->StackSize) - { - BankArea[i].Quantity += BankArea[j].Quantity; - - DeleteItem(GuildID, GuildBankMainArea, j, BankArea[j].Quantity); - - Merged = true; - - if(BankArea[i].Quantity == Item->StackSize) - break; - } - else - { - uint32 QuantityToMove = Item->StackSize - BankArea[i].Quantity; - - DeleteItem(GuildID, GuildBankMainArea, j, QuantityToMove); - - BankArea[i].Quantity = Item->StackSize; - - Merged = true; - - break; - } - } - - if(Merged) - { - UpdateItemQuantity(GuildID, GuildBankMainArea, i, BankArea[i].Quantity); - - GuildBankItemUpdate_Struct gbius; - - if(BankArea[i].Quantity == Item->StackSize) - gbius.Init(GuildBankItemUpdate, 1, i, GuildBankMainArea, 1, ItemID, Item->Icon, BankArea[i].Quantity, BankArea[i].Permissions, 1, 0); - else - gbius.Init(GuildBankItemUpdate, 1, i, GuildBankMainArea, 1, ItemID, Item->Icon, BankArea[i].Quantity, BankArea[i].Permissions, 1, 0); - - strn0cpy(gbius.ItemName, Item->Name, sizeof(gbius.ItemName)); - - strn0cpy(gbius.WhoFor, BankArea[i].WhoFor, sizeof(gbius.WhoFor)); - - entity_list.QueueClientsGuildBankItemUpdate(&gbius, GuildID); } + SendGuildBankItemUpdate(i.guild_id, i.slot, GuildBankMainArea, true, c); } return true; } -bool GuildBankManager::SplitStack(uint32 GuildID, uint16 SlotID, uint32 Quantity) +bool GuildBankManager::SplitStack(uint32 guild_id, uint16 slot_id, uint32 quantity, Client* c) { - if(SlotID > (GUILD_BANK_MAIN_AREA_SIZE - 1)) + auto guild_bank = GetGuildBank(guild_id); + if (!guild_bank) { return false; + } - auto Iterator = GetGuildBank(GuildID); - - if(Iterator == Banks.end()) + if (IsAreaFull(guild_id, GuildBankMainArea)) { return false; + } - if(IsAreaFull(GuildID, GuildBankMainArea)) + auto split_bank_item = &guild_bank->items.main_area[slot_id]; + const auto split_item = database.GetItem(split_bank_item->item_id); + + if (split_bank_item->item_id == 0 || !split_item) { return false; + } - GuildBankItem* BankArea = &(*Iterator)->Items.MainArea[0]; + auto new_item = GuildBankRepository::NewEntity(); + new_item = *split_bank_item; + new_item.id = 0; + new_item.slot = NextFreeBankSlot(guild_id, GuildBankMainArea); + new_item.quantity = quantity; + split_bank_item->quantity -= quantity; - if(BankArea[SlotID].ItemID == 0) - return false; + GuildBankRepository::UpdateOne(database, *split_bank_item); + auto e = GuildBankRepository::InsertOne(database, new_item); + new_item.id = e.id; + guild_bank->items.main_area[new_item.slot] = new_item; - if(BankArea[SlotID].Quantity <= Quantity || Quantity == 0) - return false; - - const EQ::ItemData *Item = database.GetItem(BankArea[SlotID].ItemID); - - if(!Item->Stackable) - return false; - - AddItem(GuildID, GuildBankMainArea, BankArea[SlotID].ItemID, Quantity, "", BankArea[SlotID].Permissions, BankArea[SlotID].WhoFor); - - DeleteItem(GuildID, GuildBankMainArea, SlotID, Quantity); + SendGuildBankItemUpdate(split_bank_item->guild_id, split_bank_item->slot, split_bank_item->area, true, c); + SendGuildBankItemUpdate(new_item.guild_id, new_item.slot, new_item.area, true, c); return true; } -void GuildBankManager::UpdateItemQuantity(uint32 guildID, uint16 area, uint16 slotID, uint32 quantity) +int32 GuildBankManager::NextFreeBankSlot(uint32 guild_id, uint32 area) { - // Helper method for MergeStacks. Assuming all passed parameters are valid. - // - std::string query = StringFormat("UPDATE `guild_bank` SET `qty` = %i " - "WHERE `guildid` = %i AND `area` = %i " - "AND `slot` = %i LIMIT 1", - quantity, guildID, area, slotID); - auto results = database.QueryDatabase(query); - if(!results.Success()) { - return; + auto guild_bank = GetGuildBank(guild_id); + if (!guild_bank) { + return -1; } -} + if (area == GuildBankMainArea) { + auto it = + std::ranges::find_if(guild_bank->items.main_area.begin(), guild_bank->items.main_area.end(), [&](auto &m) { + return m.second.item_id == 0; + } + ); + if (it == guild_bank->items.main_area.end()) { + return -1; + } -bool GuildBankManager::AllowedToWithdraw(uint32 GuildID, uint16 Area, uint16 SlotID, const char *Name) -{ - // Is a none-Guild Banker allowed to withdraw the item at this slot ? - // This is really here for anti-hacking measures, as the client should not request an item it does not have permission to withdraw. - // - if (SlotID > (GUILD_BANK_MAIN_AREA_SIZE - 1)) { - return false; + return it->first; } - auto Iterator = GetGuildBank(GuildID); - - if (Iterator == Banks.end()) { - return false; + auto it = std::ranges::find_if( + guild_bank->items.deposit_area.begin(), guild_bank->items.deposit_area.end(), [&](auto &m) { + return m.second.item_id == 0; + } + ); + if (it == guild_bank->items.deposit_area.end()) { + return -1; } - if (Area != GuildBankMainArea) { - return false; - } - - uint8 Permissions = (*Iterator)->Items.MainArea[SlotID].Permissions; - - if (Permissions == GuildBankBankerOnly) { - return false; - } - - if (Permissions != - GuildBankSingleMember) { // Public or Public-If-Useable (should really check if item is useable) - return true; - } - - if (!strncmp((*Iterator)->Items.MainArea[SlotID].WhoFor, - Name, - sizeof((*Iterator)->Items.MainArea[SlotID].WhoFor))) { - return true; - } - - return false; + return it->first; } void ZoneGuildManager::UpdateRankPermission(uint32 gid, uint32 charid, uint32 fid, uint32 rank, uint32 value) @@ -1829,3 +1688,75 @@ void ZoneGuildManager::SendToWorldSendGuildMembersList(uint32 guild_id) worldserver.SendPacket(outapp); safe_delete(outapp) } + +void GuildBankManager::SendGuildBankItemUpdate(uint32 guild_id, int32 slot_id, uint32 area, bool display, Client* c) +{ + auto guild_bank = GetGuildBank(guild_id); + if (!guild_bank) { + return; + } + + auto item = guild_bank->items.deposit_area[slot_id]; + if (area == GuildBankMainArea) { + item = guild_bank->items.main_area[slot_id]; + } + + const auto item_data = database.GetItem(item.item_id); + + if (item_data && (item_data->MaxCharges > 0 || item_data->MaxCharges < 0)) { + item.quantity = 1; + } + + GuildBankItemUpdate_Struct gbius{}; + gbius.action = GuildBankItemUpdate; + gbius.slot_id = item.slot; + gbius.area = item.area; + gbius.item_id = item.item_id; + gbius.permissions = item.permissions; + gbius.icon_id = item_data ? item_data->Icon : 0; + gbius.display = display; + gbius.allow_merge = item_data ? item_data->Stackable : false; + gbius.is_useable = item_data ? item_data->IsEquipable(c->GetRace(), c->GetClass()) : false; + gbius.quantity = item.quantity; + + strn0cpy(gbius.item_name, item_data ? item_data->Name : "", sizeof(gbius.item_name)); + strn0cpy(gbius.donator, item.donator.empty() ? "" : item.donator.c_str(), sizeof(gbius.donator)); + strn0cpy(gbius.who_for, item.who_for.empty() ? "" : item.who_for.c_str(), sizeof(gbius.who_for)); + + entity_list.QueueClientsGuildBankItemUpdate(&gbius, guild_id); +} + +bool ZoneGuildManager::SetGuild(Client *client, uint32 guild_id, uint8 rank) +{ + if (!client || rank > GUILD_MAX_RANK || !GetGuildByGuildID(guild_id)) { + return false; + } + + if (rank <= GUILD_RANK_NONE) { + rank = GUILD_RECRUIT; + } + + const uint32 current_guild_id = client->GuildID(); + if (current_guild_id == guild_id) { + return false; + } + + if (current_guild_id != guild_id && current_guild_id != GUILD_NONE) { + guild_mgr.RemoveMember(client->GuildID(), client->CharacterID(), std::string(client->GetCleanName())); + } + + client->SetGuildID(guild_id); + client->SetGuildRank(rank); + MemberAdd( + guild_id, + client->CharacterID(), + client->GetLevel(), + client->GetClass(), + rank, + client->GetZoneID(), + client->GetName() + ); + + client->SendGuildSpawnAppearance(); + return true; +} diff --git a/zone/guild_mgr.h b/zone/guild_mgr.h index 3256d82d2..0b59bd1f6 100644 --- a/zone/guild_mgr.h +++ b/zone/guild_mgr.h @@ -1,51 +1,64 @@ #ifndef GUILD_MGR_H_ #define GUILD_MGR_H_ -#include "../common/types.h" -#include "../common/guild_base.h" -#include #include +#include +#include "../common/guild_base.h" +#include "../common/types.h" + +#include "../common/repositories/guild_bank_repository.h" #include "../zone/petitions.h" -extern PetitionList petition_list; //extern GuildRanks_Struct guilds[512]; //extern ZoneDatabase database; #define PBUFFER 50 #define MBUFFER 50 -#define GUILD_BANK_MAIN_AREA_SIZE 200 -#define GUILD_BANK_DEPOSIT_AREA_SIZE 20 class Client; class ServerPacket; -struct GuildBankItem -{ - uint32 ItemID; - uint32 Quantity; - char Donator[64]; - uint8 Permissions; - char WhoFor[64]; -}; - struct GuildBankItems { - GuildBankItem MainArea[GUILD_BANK_MAIN_AREA_SIZE]; - GuildBankItem DepositArea[GUILD_BANK_DEPOSIT_AREA_SIZE]; + std::map main_area{}; + std::map deposit_area{}; }; struct GuildBank { - uint32 GuildID; - GuildBankItems Items; + uint32 guild_id; + GuildBankItems items{}; + + GuildBank() + { + guild_id = 0; + } }; -enum { GuildBankBulkItems = 0, GuildBankItemUpdate = 1, GuildBankPromote = 3, GuildBankViewItem = 4, GuildBankDeposit = 5, - GuildBankPermissions = 6, GuildBankWithdraw = 7, GuildBankSplitStacks = 8, GuildBankMergeStacks = 9, GuildBankAcknowledge = 10 }; +enum { + GuildBankBulkItems = 0, + GuildBankItemUpdate = 1, + GuildBankPromote = 3, + GuildBankViewItem = 4, + GuildBankDeposit = 5, + GuildBankPermissions = 6, + GuildBankWithdraw = 7, + GuildBankSplitStacks = 8, + GuildBankMergeStacks = 9, + GuildBankAcknowledge = 10 +}; -enum { GuildBankDepositArea = 0, GuildBankMainArea = 1 }; +enum { + GuildBankDepositArea = 0, + GuildBankMainArea = 1 +}; -enum { GuildBankBankerOnly = 0, GuildBankSingleMember = 1, GuildBankPublicIfUsable = 2, GuildBankPublic = 3 }; +enum { + GuildBankBankerOnly = 0, + GuildBankSingleMember = 1, + GuildBankPublicIfUsable = 2, + GuildBankPublic = 3 +}; class ZoneGuildManager : public BaseGuildManager { public: @@ -58,6 +71,7 @@ public: void ListGuilds(Client *c, uint32 guild_id = 0) const; void DescribeGuild(Client *c, uint32 guild_id) const; bool IsActionABankAction(GuildAction action); + bool SetGuild(Client *client, uint32 guild_id, uint8 rank); uint8 *MakeGuildMembers(uint32 guild_id, const char* prefix_name, uint32& length); void SendToWorldMemberLevelUpdate(uint32 guild_id, uint32 level, std::string player_name); @@ -82,10 +96,10 @@ public: void UpdateRankName(uint32 gid, uint32 rank, std::string rank_name); void SendRankName(uint32 guild_id, uint32 rank, std::string rank_name); void SendAllRankNames(uint32 guild_id, uint32 char_id); - BaseGuildManager::GuildInfo* GetGuildByGuildID(uint32 guild_id); + GuildInfo* GetGuildByGuildID(uint32 guild_id); + virtual void SendGuildRefresh(uint32 guild_id, bool name, bool motd, bool rank, bool relation); protected: - virtual void SendGuildRefresh(uint32 guild_id, bool name, bool motd, bool rank, bool relation); virtual void SendCharRefresh(uint32 old_guild_id, uint32 guild_id, uint32 charid); virtual void SendRankUpdate(uint32 CharID); virtual void SendGuildDelete(uint32 guild_id); @@ -103,30 +117,30 @@ class GuildBankManager public: ~GuildBankManager(); void SendGuildBank(Client *c); - bool AddItem(uint32 GuildID, uint8 Area, uint32 ItemID, int32 QtyOrCharges, const char *Donator, uint8 Permissions, const char *WhoFor); - int Promote(uint32 GuildID, int SlotID); - void SetPermissions(uint32 GuildID, uint16 SlotID, uint32 Permissions, const char *MemberName); - EQ::ItemInstance* GetItem(uint32 GuildID, uint16 Area, uint16 SlotID, uint32 Quantity); - bool DeleteItem(uint32 GuildID, uint16 Area, uint16 SlotID, uint32 Quantity); - bool HasItem(uint32 GuildID, uint32 ItemID); - bool IsAreaFull(uint32 GuildID, uint16 Area); - bool MergeStacks(uint32 GuildID, uint16 SlotID); - bool SplitStack(uint32 GuildID, uint16 SlotID, uint32 Quantity); - bool AllowedToWithdraw(uint32 GuildID, uint16 Area, uint16 SlotID, const char *Name); + bool AddItem(GuildBankRepository::GuildBank &guild_bank_item, Client* client); + int Promote(uint32 GuildID, int SlotID, Client* c); + void SetPermissions(uint32 GuildID, uint16 SlotID, uint32 Permissions, const char *MemberName, Client* c); + std::unique_ptr GetItem(uint32 guild_id, uint16 area, uint16 slot_id, uint32 quantity); + bool DeleteItem(uint32 GuildID, uint16 Area, uint16 SlotID, uint32 Quantity, Client* c); + bool HasItem(uint32 guild_id, uint32 item_id); + bool IsAreaFull(uint32 guild_id, uint16 area); + int32 NextFreeBankSlot(uint32 guild_id, uint32 area); + bool MergeStacks(uint32 GuildID, uint16 SlotID, Client* c); + bool SplitStack(uint32 GuildID, uint16 SlotID, uint32 Quantity, Client* c); + //bool AllowedToWithdraw(uint32 GuildID, uint16 Area, uint16 SlotID, const char *Name); + void SendGuildBankItemUpdate(uint32 guild_id, int32 slot_id, uint32 area, bool display, Client* c); + std::shared_ptr GetGuildBank(uint32 guild_id); private: bool IsLoaded(uint32 GuildID); - bool Load(uint32 GuildID); - std::list::iterator GetGuildBank(uint32 GuildID); + void Load(uint32 GuildID); void UpdateItemQuantity(uint32 GuildID, uint16 Area, uint16 SlotID, uint32 Quantity); - std::list Banks; - + std::list> banks{}; }; extern ZoneGuildManager guild_mgr; extern GuildBankManager *GuildBanks; - #endif /*GUILD_MGR_H_*/ diff --git a/zone/horse.cpp b/zone/horse.cpp index a5e24e0f8..c705a7c04 100644 --- a/zone/horse.cpp +++ b/zone/horse.cpp @@ -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; diff --git a/zone/inventory.cpp b/zone/inventory.cpp index 30f4c31da..1c2b62c4f 100644 --- a/zone/inventory.cpp +++ b/zone/inventory.cpp @@ -25,9 +25,12 @@ #include "zonedb.h" #include "../common/events/player_event_logs.h" #include "bot.h" +#include "../common/evolving_items.h" #include "../common/repositories/character_corpse_items_repository.h" +#include "queryserv.h" extern WorldServer worldserver; +extern QueryServ *QServ; // @merth: this needs to be touched up uint32 Client::NukeItem(uint32 itemnum, uint8 where_to_check) { @@ -180,10 +183,6 @@ bool Client::CheckLoreConflict(const EQ::ItemData* item) } bool Client::SummonItem(uint32 item_id, int16 charges, uint32 aug1, uint32 aug2, uint32 aug3, uint32 aug4, uint32 aug5, uint32 aug6, bool attuned, uint16 to_slot, uint32 ornament_icon, uint32 ornament_idfile, uint32 ornament_hero_model) { - EVENT_ITEM_ScriptStopReturn(); - - // TODO: update calling methods and script apis to handle a failure return - const EQ::ItemData* item = database.GetItem(item_id); // make sure the item exists @@ -632,19 +631,19 @@ bool Client::SummonItem(uint32 item_id, int16 charges, uint32 aug1, uint32 aug2, } } - if (player_event_logs.IsEventEnabled(PlayerEvent::ITEM_CREATION)) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::ITEM_CREATION)) { auto e = PlayerEvent::ItemCreationEvent{}; - e.item_id = item->ID; - e.item_name = item->Name; - e.to_slot = to_slot; - e.charges = charges; - e.aug1 = aug1; - e.aug2 = aug2; - e.aug3 = aug3; - e.aug4 = aug4; - e.aug5 = aug5; - e.aug6 = aug6; - e.attuned = attuned; + e.item_id = item->ID; + e.item_name = item->Name; + e.to_slot = to_slot; + e.charges = charges; + e.augment_1_id = aug1; + e.augment_2_id = aug2; + e.augment_3_id = aug3; + e.augment_4_id = aug4; + e.augment_5_id = aug5; + e.augment_6_id = aug6; + e.attuned = attuned; RecordPlayerEventLog(PlayerEvent::ITEM_CREATION, e); } @@ -657,6 +656,8 @@ bool Client::SummonItem(uint32 item_id, int16 charges, uint32 aug1, uint32 aug2, PutItemInInventory(to_slot, *inst, true); } + m_external_handin_items_returned.emplace_back(inst->GetItem()->ID); + safe_delete(inst); // discover item and any augments @@ -687,7 +688,7 @@ void Client::DropItem(int16 slot_id, bool recurse) LogInventory("Error in InventoryProfile::CheckNoDrop() - returned 'true' for empty slot"); } else { - if (LogSys.log_settings[Logs::Inventory].is_category_enabled) { + if (EQEmuLogSys::Instance()->log_settings[Logs::Inventory].is_category_enabled) { LogInventory("DropItem() Hack detected - full item parse:"); LogInventory("depth: 0, Item: [{}] (id: [{}]), IsDroppable: [{}]", (invalid_drop->GetItem() ? invalid_drop->GetItem()->Name : "null data"), invalid_drop->GetID(), (invalid_drop->IsDroppable(false) ? "true" : "false")); @@ -719,7 +720,7 @@ void Client::DropItem(int16 slot_id, bool recurse) // Take control of item in client inventory auto* inst = m_inv.PopItem(slot_id); if (inst) { - if (LogSys.log_settings[Logs::Inventory].is_category_enabled) { + if (EQEmuLogSys::Instance()->log_settings[Logs::Inventory].is_category_enabled) { LogInventory("DropItem() Processing - full item parse:"); LogInventory( "depth: 0, Item: [{}] (id: [{}]), IsDroppable: [{}]", @@ -762,12 +763,18 @@ void Client::DropItem(int16 slot_id, bool recurse) int i = 0; - if (player_event_logs.IsEventEnabled(PlayerEvent::DROPPED_ITEM)) { + if (inst && PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::DROPPED_ITEM)) { auto e = PlayerEvent::DroppedItemEvent{ - .item_id = inst->GetID(), - .item_name = inst->GetItem()->Name, - .slot_id = slot_id, - .charges = (uint32) inst->GetCharges() + .item_id = inst->GetID(), + .augment_1_id = inst->GetAugmentItemID(0), + .augment_2_id = inst->GetAugmentItemID(1), + .augment_3_id = inst->GetAugmentItemID(2), + .augment_4_id = inst->GetAugmentItemID(3), + .augment_5_id = inst->GetAugmentItemID(4), + .augment_6_id = inst->GetAugmentItemID(5), + .item_name = inst->GetItem()->Name, + .slot_id = slot_id, + .charges = (uint32) inst->GetCharges() }; RecordPlayerEventLog(PlayerEvent::DROPPED_ITEM, e); } @@ -821,79 +828,10 @@ void Client::DropItem(int16 slot_id, bool recurse) object->StartDecay(); LogInventory("[{}] dropped [{}] from slot [{}]", GetCleanName(), inst->GetItem()->Name, slot_id); - DropItemQS(inst, false); safe_delete(inst); } -void Client::DropItemQS(EQ::ItemInstance* inst, bool pickup) { - if (RuleB(QueryServ, PlayerDropItems)) { - QSPlayerDropItem_Struct qs_audit; - std::list event_details; - memset(&qs_audit, 0, sizeof(QSPlayerDropItem_Struct)); - - qs_audit.char_id = character_id; - qs_audit.pickup = pickup; - qs_audit.zone_id = GetZoneID(); - qs_audit.x = (int) GetX(); - qs_audit.y = (int) GetY(); - qs_audit.z = (int) GetZ(); - - if (inst) { - auto detail = new QSDropItems_Struct; - detail->item_id = inst->GetID(); - detail->charges = inst->IsClassBag() ? 1 : inst->GetCharges(); - detail->aug_1 = inst->GetAugmentItemID(1); - detail->aug_2 = inst->GetAugmentItemID(2); - detail->aug_3 = inst->GetAugmentItemID(3); - detail->aug_4 = inst->GetAugmentItemID(4); - detail->aug_5 = inst->GetAugmentItemID(5); - event_details.push_back(detail); - - if (inst->IsClassBag()) { - for (uint8 sub_slot = EQ::invbag::SLOT_BEGIN; (sub_slot <= EQ::invbag::SLOT_END); ++sub_slot) { // this is to catch ALL items - const EQ::ItemInstance* bag_inst = inst->GetItem(sub_slot); - if (bag_inst) { - detail = new QSDropItems_Struct; - detail->item_id = bag_inst->GetID(); - detail->charges = (!bag_inst->IsStackable() ? 1 : bag_inst->GetCharges()); - detail->aug_1 = bag_inst->GetAugmentItemID(1); - detail->aug_2 = bag_inst->GetAugmentItemID(2); - detail->aug_3 = bag_inst->GetAugmentItemID(3); - detail->aug_4 = bag_inst->GetAugmentItemID(4); - detail->aug_5 = bag_inst->GetAugmentItemID(5); - event_details.push_back(detail); - } - } - } - } - qs_audit._detail_count = event_details.size(); - - auto qs_pack = new ServerPacket( - ServerOP_QSPlayerDropItem, - sizeof(QSPlayerDropItem_Struct) + - (sizeof(QSDropItems_Struct) * qs_audit._detail_count)); - QSPlayerDropItem_Struct* qs_buf = (QSPlayerDropItem_Struct*) qs_pack->pBuffer; - - memcpy(qs_buf, &qs_audit, sizeof(QSPlayerDropItem_Struct)); - - int offset = 0; - - for (auto iter = event_details.begin(); iter != event_details.end(); ++iter, ++offset) { - QSDropItems_Struct* detail = reinterpret_cast(*iter); - qs_buf->items[offset] = *detail; - safe_delete(detail); - } - - event_details.clear(); - - if (worldserver.Connected()) - worldserver.SendPacket(qs_pack); - - safe_delete(qs_pack); - } -} - // Drop inst void Client::DropInst(const EQ::ItemInstance* inst) { @@ -1042,56 +980,11 @@ void Client::DeleteItemInInventory(int16 slot_id, int16 quantity, bool client_up return; } - // start QS code - if(RuleB(QueryServ, PlayerLogDeletes)) { - uint16 delete_count = 0; - - if(m_inv[slot_id]) { delete_count += m_inv.GetItem(slot_id)->GetTotalItemCount(); } - - auto qspack = - new ServerPacket(ServerOP_QSPlayerLogDeletes, - sizeof(QSPlayerLogDelete_Struct) + (sizeof(QSDeleteItems_Struct) * delete_count)); - QSPlayerLogDelete_Struct* qsaudit = (QSPlayerLogDelete_Struct*)qspack->pBuffer; - uint16 parent_offset = 0; - - qsaudit->char_id = character_id; - qsaudit->stack_size = quantity; - qsaudit->char_count = delete_count; - - qsaudit->items[parent_offset].char_slot = slot_id; - qsaudit->items[parent_offset].item_id = m_inv[slot_id]->GetID(); - qsaudit->items[parent_offset].charges = m_inv[slot_id]->GetCharges(); - qsaudit->items[parent_offset].aug_1 = m_inv[slot_id]->GetAugmentItemID(1); - qsaudit->items[parent_offset].aug_2 = m_inv[slot_id]->GetAugmentItemID(2); - qsaudit->items[parent_offset].aug_3 = m_inv[slot_id]->GetAugmentItemID(3); - qsaudit->items[parent_offset].aug_4 = m_inv[slot_id]->GetAugmentItemID(4); - qsaudit->items[parent_offset].aug_5 = m_inv[slot_id]->GetAugmentItemID(5); - - if (m_inv[slot_id]->IsClassBag()) { - for (uint8 bag_idx = EQ::invbag::SLOT_BEGIN; bag_idx < m_inv[slot_id]->GetItem()->BagSlots; bag_idx++) { - EQ::ItemInstance* bagitem = m_inv[slot_id]->GetItem(bag_idx); - - if(bagitem) { - int16 bagslot_id = EQ::InventoryProfile::CalcSlotId(slot_id, bag_idx); - - qsaudit->items[++parent_offset].char_slot = bagslot_id; - qsaudit->items[parent_offset].item_id = bagitem->GetID(); - qsaudit->items[parent_offset].charges = bagitem->GetCharges(); - qsaudit->items[parent_offset].aug_1 = bagitem->GetAugmentItemID(1); - qsaudit->items[parent_offset].aug_2 = bagitem->GetAugmentItemID(2); - qsaudit->items[parent_offset].aug_3 = bagitem->GetAugmentItemID(3); - qsaudit->items[parent_offset].aug_4 = bagitem->GetAugmentItemID(4); - qsaudit->items[parent_offset].aug_5 = bagitem->GetAugmentItemID(5); - } - } - } - - if(worldserver.Connected()) { worldserver.SendPacket(qspack); } - safe_delete(qspack); + uint64 evolve_id = m_inv[slot_id]->GetEvolveUniqueID(); + bool isDeleted = m_inv.DeleteItem(slot_id, quantity); + if (isDeleted && evolve_id && (slot_id > EQ::invslot::TRADE_END || slot_id < EQ::invslot::TRADE_BEGIN)) { + CharacterEvolvingItemsRepository::SoftDelete(database, evolve_id); } - // end QS code - - bool isDeleted = m_inv.DeleteItem(slot_id, quantity); const EQ::ItemInstance* inst = nullptr; if (slot_id == EQ::invslot::slotCursor) { @@ -1143,6 +1036,8 @@ void Client::DeleteItemInInventory(int16 slot_id, int16 quantity, bool client_up bool Client::PushItemOnCursor(const EQ::ItemInstance& inst, bool client_update) { LogInventory("Putting item [{}] ([{}]) on the cursor", inst.GetItem()->Name, inst.GetItem()->ID); + + EvolvingItemsManager::Instance()->DoLootChecks(CharacterID(), EQ::invslot::slotCursor, inst); m_inv.PushCursor(inst); if (client_update) { @@ -1163,9 +1058,9 @@ bool Client::PutItemInInventory(int16 slot_id, const EQ::ItemInstance& inst, boo if (slot_id == EQ::invslot::slotCursor) { // don't trust macros before conditional statements... return PushItemOnCursor(inst, client_update); } - else { - m_inv.PutItem(slot_id, inst); - } + + EvolvingItemsManager::Instance()->DoLootChecks(CharacterID(), slot_id, inst); + m_inv.PutItem(slot_id, inst); if (client_update) { @@ -1173,15 +1068,16 @@ bool Client::PutItemInInventory(int16 slot_id, const EQ::ItemInstance& inst, boo //SendWearChange(EQ::InventoryProfile::CalcMaterialFromSlot(slot_id)); } + CalcBonuses(); + if (slot_id == EQ::invslot::slotCursor) { auto s = m_inv.cursor_cbegin(), e = m_inv.cursor_cend(); return database.SaveCursor(CharacterID(), s, e); } - else { - return database.SaveInventory(CharacterID(), &inst, slot_id); - } - CalcBonuses(); + return database.SaveInventory(CharacterID(), &inst, slot_id); + + //CalcBonuses(); // this never fires?? // a lot of wasted checks and calls coded above... } @@ -1191,6 +1087,8 @@ void Client::PutLootInInventory(int16 slot_id, const EQ::ItemInstance &inst, Loo bool cursor_empty = m_inv.CursorEmpty(); + EvolvingItemsManager::Instance()->DoLootChecks(CharacterID(), slot_id, inst); + if (slot_id == EQ::invslot::slotCursor) { m_inv.PushCursor(inst); auto s = m_inv.cursor_cbegin(), e = m_inv.cursor_cend(); @@ -1724,7 +1622,6 @@ bool Client::SwapItem(MoveItem_Struct* move_in) { } if (move_in->from_slot == move_in->to_slot) { // Item summon, no further processing needed - if(RuleB(QueryServ, PlayerLogMoves)) { QSSwapItemAuditor(move_in); } // QS Audit if (ClientVersion() >= EQ::versions::ClientVersion::RoF) { return true; } // Can't do RoF+ if (move_in->to_slot == EQ::invslot::slotCursor) { @@ -1758,12 +1655,19 @@ bool Client::SwapItem(MoveItem_Struct* move_in) { DeleteItemInInventory(EQ::invslot::slotCursor, 0, true); - if (player_event_logs.IsEventEnabled(PlayerEvent::ITEM_DESTROY)) { + if (test_inst && PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::ITEM_DESTROY)) { auto e = PlayerEvent::DestroyItemEvent{ - .item_id = test_inst->GetItem()->ID, - .item_name = test_inst->GetItem()->Name, - .charges = test_inst->GetCharges(), - .reason = "Duplicate lore item", + .item_id = test_inst->GetItem()->ID, + .item_name = test_inst->GetItem()->Name, + .charges = test_inst->GetCharges(), + .augment_1_id = test_inst->GetAugmentItemID(0), + .augment_2_id = test_inst->GetAugmentItemID(1), + .augment_3_id = test_inst->GetAugmentItemID(2), + .augment_4_id = test_inst->GetAugmentItemID(3), + .augment_5_id = test_inst->GetAugmentItemID(4), + .augment_6_id = test_inst->GetAugmentItemID(5), + .attuned = test_inst->IsAttuned(), + .reason = "Duplicate lore item" }; RecordPlayerEventLog(PlayerEvent::ITEM_DESTROY, e); @@ -1777,17 +1681,23 @@ bool Client::SwapItem(MoveItem_Struct* move_in) { if (move_in->to_slot == (uint32)INVALID_INDEX) { if (move_in->from_slot == (uint32)EQ::invslot::slotCursor) { LogInventory("Client destroyed item from cursor slot [{}]", move_in->from_slot); - if(RuleB(QueryServ, PlayerLogMoves)) { QSSwapItemAuditor(move_in); } // QS Audit EQ::ItemInstance *inst = m_inv.GetItem(EQ::invslot::slotCursor); if (inst) { - if (player_event_logs.IsEventEnabled(PlayerEvent::ITEM_DESTROY)) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::ITEM_DESTROY)) { auto e = PlayerEvent::DestroyItemEvent{ - .item_id = inst->GetItem()->ID, - .item_name = inst->GetItem()->Name, - .charges = inst->GetCharges(), - .reason = "Client destroy cursor", + .item_id = inst->GetItem()->ID, + .item_name = inst->GetItem()->Name, + .charges = inst->GetCharges(), + .augment_1_id = inst->GetAugmentItemID(0), + .augment_2_id = inst->GetAugmentItemID(1), + .augment_3_id = inst->GetAugmentItemID(2), + .augment_4_id = inst->GetAugmentItemID(3), + .augment_5_id = inst->GetAugmentItemID(4), + .augment_6_id = inst->GetAugmentItemID(5), + .attuned = inst->IsAttuned(), + .reason = "Client destroy cursor" }; RecordPlayerEventLog(PlayerEvent::ITEM_DESTROY, e); @@ -1810,7 +1720,6 @@ bool Client::SwapItem(MoveItem_Struct* move_in) { } else { LogInventory("Deleted item from slot [{}] as a result of an inventory container tradeskill combine", move_in->from_slot); - if(RuleB(QueryServ, PlayerLogMoves)) { QSSwapItemAuditor(move_in); } // QS Audit DeleteItemInInventory(move_in->from_slot); return true; // Item deletion } @@ -1909,13 +1818,20 @@ bool Client::SwapItem(MoveItem_Struct* move_in) { return false; } //verify shared bank transactions in the database - if (src_inst && src_slot_id >= EQ::invslot::SHARED_BANK_BEGIN && src_slot_id <= EQ::invbag::SHARED_BANK_BAGS_END) { + if ( + src_inst && + ( + EQ::ValueWithin(src_slot_id, EQ::invslot::SHARED_BANK_BEGIN, EQ::invslot::SHARED_BANK_END) || + EQ::ValueWithin(src_slot_id, EQ::invbag::SHARED_BANK_BAGS_BEGIN, EQ::invbag::SHARED_BANK_BAGS_END) + ) + ) { if(!database.VerifyInventory(account_id, src_slot_id, src_inst)) { LogError("Player [{}] on account [{}] was found exploiting the shared bank.\n", GetName(), account_name); DeleteItemInInventory(dst_slot_id,0,true); return(false); } - if (src_slot_id >= EQ::invslot::SHARED_BANK_BEGIN && src_slot_id <= EQ::invslot::SHARED_BANK_END && src_inst->IsClassBag()){ + + if (EQ::ValueWithin(src_slot_id, EQ::invslot::SHARED_BANK_BEGIN, EQ::invslot::SHARED_BANK_END) && src_inst->IsClassBag()){ for (uint8 idx = EQ::invbag::SLOT_BEGIN; idx <= EQ::invbag::SLOT_END; idx++) { const EQ::ItemInstance* baginst = src_inst->GetItem(idx); if (baginst && !database.VerifyInventory(account_id, EQ::InventoryProfile::CalcSlotId(src_slot_id, idx), baginst)){ @@ -1924,13 +1840,21 @@ bool Client::SwapItem(MoveItem_Struct* move_in) { } } } - if (dst_inst && dst_slot_id >= EQ::invslot::SHARED_BANK_BEGIN && dst_slot_id <= EQ::invbag::SHARED_BANK_BAGS_END) { + + if ( + dst_inst && + ( + EQ::ValueWithin(dst_slot_id, EQ::invslot::SHARED_BANK_BEGIN, EQ::invslot::SHARED_BANK_END) || + EQ::ValueWithin(dst_slot_id, EQ::invbag::SHARED_BANK_BAGS_BEGIN, EQ::invbag::SHARED_BANK_BAGS_END) + ) + ) { if(!database.VerifyInventory(account_id, dst_slot_id, dst_inst)) { LogError("Player [{}] on account [{}] was found exploting the shared bank.\n", GetName(), account_name); DeleteItemInInventory(src_slot_id,0,true); return(false); } - if (dst_slot_id >= EQ::invslot::SHARED_BANK_BEGIN && dst_slot_id <= EQ::invslot::SHARED_BANK_END && dst_inst->IsClassBag()){ + + if (EQ::ValueWithin(dst_slot_id, EQ::invslot::SHARED_BANK_BEGIN, EQ::invslot::SHARED_BANK_END) && dst_inst->IsClassBag()){ for (uint8 idx = EQ::invbag::SLOT_BEGIN; idx <= EQ::invbag::SLOT_END; idx++) { const EQ::ItemInstance* baginst = dst_inst->GetItem(idx); if (baginst && !database.VerifyInventory(account_id, EQ::InventoryProfile::CalcSlotId(dst_slot_id, idx), baginst)){ @@ -1943,10 +1867,20 @@ bool Client::SwapItem(MoveItem_Struct* move_in) { // Check for No Drop Hacks Mob* with = trade->With(); - if (((with && with->IsClient() && !with->CastToClient()->IsBecomeNPC() && dst_slot_id >= EQ::invslot::TRADE_BEGIN && dst_slot_id <= EQ::invslot::TRADE_END) || - (dst_slot_id >= EQ::invslot::SHARED_BANK_BEGIN && dst_slot_id <= EQ::invbag::SHARED_BANK_BAGS_END)) - && GetInv().CheckNoDrop(src_slot_id) - && !CanTradeFVNoDropItem()) { + if ( + ( + ( + with && + with->IsClient() && + !with->CastToClient()->IsBecomeNPC() && + EQ::ValueWithin(dst_slot_id, EQ::invslot::TRADE_BEGIN, EQ::invslot::TRADE_END) + ) || + EQ::ValueWithin(dst_slot_id, EQ::invslot::SHARED_BANK_BEGIN, EQ::invslot::SHARED_BANK_END) || + EQ::ValueWithin(dst_slot_id, EQ::invbag::SHARED_BANK_BAGS_BEGIN, EQ::invbag::SHARED_BANK_BAGS_END) + ) && + GetInv().CheckNoDrop(src_slot_id) && + !CanTradeFVNoDropItem() + ) { auto ndh_inst = m_inv[src_slot_id]; std::string ndh_item_data; if (ndh_inst == nullptr) { @@ -1982,8 +1916,6 @@ bool Client::SwapItem(MoveItem_Struct* move_in) { safe_delete(inst); } - if(RuleB(QueryServ, PlayerLogMoves)) { QSSwapItemAuditor(move_in, true); } // QS Audit - return true; } else if (dst_slot_id >= EQ::invslot::WORLD_BEGIN && dst_slot_id <= EQ::invslot::WORLD_END) { @@ -2052,8 +1984,6 @@ bool Client::SwapItem(MoveItem_Struct* move_in) { database.SaveInventory(character_id, m_inv[src_slot_id], src_slot_id); } - if(RuleB(QueryServ, PlayerLogMoves)) { QSSwapItemAuditor(move_in, true); } // QS Audit - return true; } } @@ -2072,10 +2002,6 @@ bool Client::SwapItem(MoveItem_Struct* move_in) { return false; } - // Add cursor item to trade bucket - // Also sends trade information to other client of trade session - if(RuleB(QueryServ, PlayerLogMoves)) { QSSwapItemAuditor(move_in); } // QS Audit - trade->AddEntity(dst_slot_id, move_in->number_in_stack); if (dstitemid == 0) { @@ -2084,8 +2010,6 @@ bool Client::SwapItem(MoveItem_Struct* move_in) { return true; } else { - if(RuleB(QueryServ, PlayerLogMoves)) { QSSwapItemAuditor(move_in); } // QS Audit - SummonItem(src_inst->GetID(), src_inst->GetCharges()); DeleteItemInInventory(EQ::invslot::slotCursor); @@ -2306,8 +2230,6 @@ bool Client::SwapItem(MoveItem_Struct* move_in) { database.SaveInventory(character_id, m_inv.GetItem(dst_slot_id), dst_slot_id); } - if(RuleB(QueryServ, PlayerLogMoves)) { QSSwapItemAuditor(move_in, true); } // QS Audit - // Step 8: Re-calc stats CalcBonuses(); ApplyWeaponsStance(); @@ -2409,104 +2331,6 @@ void Client::SwapItemResync(MoveItem_Struct* move_slots) { } } -void Client::QSSwapItemAuditor(MoveItem_Struct* move_in, bool postaction_call) { - int16 from_slot_id = static_cast(move_in->from_slot); - int16 to_slot_id = static_cast(move_in->to_slot); - int16 move_amount = static_cast(move_in->number_in_stack); - - if(!m_inv[from_slot_id] && !m_inv[to_slot_id]) { return; } - - uint16 move_count = 0; - - if(m_inv[from_slot_id]) { move_count += m_inv[from_slot_id]->GetTotalItemCount(); } - if(to_slot_id != from_slot_id) { if(m_inv[to_slot_id]) { move_count += m_inv[to_slot_id]->GetTotalItemCount(); } } - - auto qspack = new ServerPacket(ServerOP_QSPlayerLogMoves, - sizeof(QSPlayerLogMove_Struct) + (sizeof(QSMoveItems_Struct) * move_count)); - QSPlayerLogMove_Struct* qsaudit = (QSPlayerLogMove_Struct*)qspack->pBuffer; - - qsaudit->char_id = character_id; - qsaudit->stack_size = move_amount; - qsaudit->char_count = move_count; - qsaudit->postaction = postaction_call; - qsaudit->from_slot = from_slot_id; - qsaudit->to_slot = to_slot_id; - - move_count = 0; - - const EQ::ItemInstance* from_inst = m_inv[postaction_call?to_slot_id:from_slot_id]; - - if(from_inst) { - qsaudit->items[move_count].from_slot = from_slot_id; - qsaudit->items[move_count].to_slot = to_slot_id; - qsaudit->items[move_count].item_id = from_inst->GetID(); - qsaudit->items[move_count].charges = from_inst->GetCharges(); - qsaudit->items[move_count].aug_1 = from_inst->GetAugmentItemID(1); - qsaudit->items[move_count].aug_2 = from_inst->GetAugmentItemID(2); - qsaudit->items[move_count].aug_3 = from_inst->GetAugmentItemID(3); - qsaudit->items[move_count].aug_4 = from_inst->GetAugmentItemID(4); - qsaudit->items[move_count++].aug_5 = from_inst->GetAugmentItemID(5); - - if (from_inst->IsType(EQ::item::ItemClassBag)) { - for (uint8 bag_idx = EQ::invbag::SLOT_BEGIN; bag_idx < from_inst->GetItem()->BagSlots; bag_idx++) { - const EQ::ItemInstance* from_baginst = from_inst->GetItem(bag_idx); - - if(from_baginst) { - qsaudit->items[move_count].from_slot = EQ::InventoryProfile::CalcSlotId(from_slot_id, bag_idx); - qsaudit->items[move_count].to_slot = EQ::InventoryProfile::CalcSlotId(to_slot_id, bag_idx); - qsaudit->items[move_count].item_id = from_baginst->GetID(); - qsaudit->items[move_count].charges = from_baginst->GetCharges(); - qsaudit->items[move_count].aug_1 = from_baginst->GetAugmentItemID(1); - qsaudit->items[move_count].aug_2 = from_baginst->GetAugmentItemID(2); - qsaudit->items[move_count].aug_3 = from_baginst->GetAugmentItemID(3); - qsaudit->items[move_count].aug_4 = from_baginst->GetAugmentItemID(4); - qsaudit->items[move_count++].aug_5 = from_baginst->GetAugmentItemID(5); - } - } - } - } - - if(to_slot_id != from_slot_id) { - const EQ::ItemInstance* to_inst = m_inv[postaction_call?from_slot_id:to_slot_id]; - - if(to_inst) { - qsaudit->items[move_count].from_slot = to_slot_id; - qsaudit->items[move_count].to_slot = from_slot_id; - qsaudit->items[move_count].item_id = to_inst->GetID(); - qsaudit->items[move_count].charges = to_inst->GetCharges(); - qsaudit->items[move_count].aug_1 = to_inst->GetAugmentItemID(1); - qsaudit->items[move_count].aug_2 = to_inst->GetAugmentItemID(2); - qsaudit->items[move_count].aug_3 = to_inst->GetAugmentItemID(3); - qsaudit->items[move_count].aug_4 = to_inst->GetAugmentItemID(4); - qsaudit->items[move_count++].aug_5 = to_inst->GetAugmentItemID(5); - - if (to_inst->IsType(EQ::item::ItemClassBag)) { - for (uint8 bag_idx = EQ::invbag::SLOT_BEGIN; bag_idx < to_inst->GetItem()->BagSlots; bag_idx++) { - const EQ::ItemInstance* to_baginst = to_inst->GetItem(bag_idx); - - if(to_baginst) { - qsaudit->items[move_count].from_slot = EQ::InventoryProfile::CalcSlotId(to_slot_id, bag_idx); - qsaudit->items[move_count].to_slot = EQ::InventoryProfile::CalcSlotId(from_slot_id, bag_idx); - qsaudit->items[move_count].item_id = to_baginst->GetID(); - qsaudit->items[move_count].charges = to_baginst->GetCharges(); - qsaudit->items[move_count].aug_1 = to_baginst->GetAugmentItemID(1); - qsaudit->items[move_count].aug_2 = to_baginst->GetAugmentItemID(2); - qsaudit->items[move_count].aug_3 = to_baginst->GetAugmentItemID(3); - qsaudit->items[move_count].aug_4 = to_baginst->GetAugmentItemID(4); - qsaudit->items[move_count++].aug_5 = to_baginst->GetAugmentItemID(5); - } - } - } - } - } - - if(move_count && worldserver.Connected()) { - worldserver.SendPacket(qspack); - } - - safe_delete(qspack); -} - void Client::DyeArmor(EQ::TintProfile* dye){ int16 slot=0; for (int i = EQ::textures::textureBegin; i <= EQ::textures::LastTintableTexture; i++) { @@ -2969,107 +2793,72 @@ void Client::RemoveNoRent(bool client_update) } // Two new methods to alleviate perpetual login desyncs -void Client::RemoveDuplicateLore(bool client_update) +void Client::RemoveDuplicateLore() { - for (auto slot_id = EQ::invslot::EQUIPMENT_BEGIN; slot_id <= EQ::invslot::EQUIPMENT_END; ++slot_id) { - if ((((uint64)1 << slot_id) & GetInv().GetLookup()->PossessionsBitmask) == 0) + for (auto slot_id : GetInventorySlots()) { + if ((((uint64) 1 << slot_id) & GetInv().GetLookup()->PossessionsBitmask) == 0) { continue; - - auto inst = m_inv.PopItem(slot_id); - if (inst == nullptr) { continue; } - if(CheckLoreConflict(inst->GetItem())) { - LogInventory("Lore Duplication Error: Deleting [{}] from slot [{}]", inst->GetItem()->Name, slot_id); - database.SaveInventory(character_id, nullptr, slot_id); } - else { - m_inv.PutItem(slot_id, *inst); - } - safe_delete(inst); - } - for (auto slot_id = EQ::invslot::GENERAL_BEGIN; slot_id <= EQ::invslot::GENERAL_END; ++slot_id) { - if ((((uint64)1 << slot_id) & GetInv().GetLookup()->PossessionsBitmask) == 0) + // ignore shared bank slots + if (slot_id >= EQ::invslot::SHARED_BANK_BEGIN && slot_id <= EQ::invslot::SHARED_BANK_END) { continue; + } + if (slot_id >= EQ::invbag::SHARED_BANK_BAGS_BEGIN && slot_id <= EQ::invbag::SHARED_BANK_BAGS_END) { + continue; + } + + // slot gets handled in a queue + if (slot_id == EQ::invslot::slotCursor) { + continue; + } + + // temporarily move the item off of the slot auto inst = m_inv.PopItem(slot_id); - if (inst == nullptr) { continue; } + if (!inst) { + continue; + } + if (CheckLoreConflict(inst->GetItem())) { - LogInventory("Lore Duplication Error: Deleting [{}] from slot [{}]", inst->GetItem()->Name, slot_id); + LogError( + "Lore Duplication Error | Deleting [{}] ({}) from slot [{}] client [{}]", + inst->GetItem()->Name, + inst->GetItem()->ID, + slot_id, + GetCleanName() + ); database.SaveInventory(character_id, nullptr, slot_id); + safe_delete(inst); } - else { - m_inv.PutItem(slot_id, *inst); - } - safe_delete(inst); + + // if no lore conflict, put the item back in the slot + m_inv.PushItem(slot_id, inst); } - for (auto slot_id = EQ::invbag::GENERAL_BAGS_BEGIN; slot_id <= EQ::invbag::CURSOR_BAG_END; ++slot_id) { - auto temp_slot = EQ::invslot::GENERAL_BEGIN + ((slot_id - EQ::invbag::GENERAL_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT); - if ((((uint64)1 << temp_slot) & GetInv().GetLookup()->PossessionsBitmask) == 0) - continue; - - auto inst = m_inv.PopItem(slot_id); - if (inst == nullptr) { continue; } - if(CheckLoreConflict(inst->GetItem())) { - LogInventory("Lore Duplication Error: Deleting [{}] from slot [{}]", inst->GetItem()->Name, slot_id); - database.SaveInventory(character_id, nullptr, slot_id); - } - else { - m_inv.PutItem(slot_id, *inst); - } - safe_delete(inst); - } - - for (auto slot_id = EQ::invslot::BANK_BEGIN; slot_id <= EQ::invslot::BANK_END; ++slot_id) { - if ((slot_id - EQ::invslot::BANK_BEGIN) >= GetInv().GetLookup()->InventoryTypeSize.Bank) - continue; - - auto inst = m_inv.PopItem(slot_id); - if (inst == nullptr) { continue; } - if(CheckLoreConflict(inst->GetItem())) { - LogInventory("Lore Duplication Error: Deleting [{}] from slot [{}]", inst->GetItem()->Name, slot_id); - database.SaveInventory(character_id, nullptr, slot_id); - } - else { - m_inv.PutItem(slot_id, *inst); - } - safe_delete(inst); - } - - for (auto slot_id = EQ::invbag::BANK_BAGS_BEGIN; slot_id <= EQ::invbag::BANK_BAGS_END; ++slot_id) { - auto temp_slot = (slot_id - EQ::invbag::BANK_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT; - if (temp_slot >= GetInv().GetLookup()->InventoryTypeSize.Bank) - continue; - - auto inst = m_inv.PopItem(slot_id); - if (inst == nullptr) { continue; } - if(CheckLoreConflict(inst->GetItem())) { - LogInventory("Lore Duplication Error: Deleting [{}] from slot [{}]", inst->GetItem()->Name, slot_id); - database.SaveInventory(character_id, nullptr, slot_id); - } - else { - m_inv.PutItem(slot_id, *inst); - } - safe_delete(inst); - } - - // Shared Bank and Shared Bank Containers are not checked due to their allowing duplicate lore items - if (!m_inv.CursorEmpty()) { std::list local_1; std::list local_2; while (!m_inv.CursorEmpty()) { auto inst = m_inv.PopItem(EQ::invslot::slotCursor); - if (inst == nullptr) { continue; } + if (!inst) { + continue; + } local_1.push_back(inst); } - for (auto iter = local_1.begin(); iter != local_1.end(); ++iter) { - auto inst = *iter; - if (inst == nullptr) { continue; } + for (auto inst: local_1) { + if (!inst) { + continue; + } if (CheckLoreConflict(inst->GetItem())) { - LogInventory("Lore Duplication Error: Deleting [{}] from `Limbo`", inst->GetItem()->Name); + LogError( + "Lore Duplication Error | Deleting [{}] ({}) from `Limbo` client [{}]", + inst->GetItem()->Name, + inst->GetItem()->ID, + GetCleanName() + ); safe_delete(inst); } else { @@ -3078,17 +2867,25 @@ void Client::RemoveDuplicateLore(bool client_update) } local_1.clear(); - for (auto iter = local_2.begin(); iter != local_2.end(); ++iter) { - auto inst = *iter; - if (inst == nullptr) { continue; } + for (auto inst: local_2) { + if (!inst) { + continue; + } if (!inst->GetItem()->LoreFlag || - ((inst->GetItem()->LoreGroup == -1) && (m_inv.HasItem(inst->GetID(), 0, invWhereCursor) == INVALID_INDEX)) || - (inst->GetItem()->LoreGroup && (~inst->GetItem()->LoreGroup) && (m_inv.HasItemByLoreGroup(inst->GetItem()->LoreGroup, invWhereCursor) == INVALID_INDEX)) + ((inst->GetItem()->LoreGroup == -1) && + (m_inv.HasItem(inst->GetID(), 0, invWhereCursor) == INVALID_INDEX)) || + (inst->GetItem()->LoreGroup && (~inst->GetItem()->LoreGroup) && + (m_inv.HasItemByLoreGroup(inst->GetItem()->LoreGroup, invWhereCursor) == INVALID_INDEX)) ) { m_inv.PushCursor(*inst); } else { - LogInventory("Lore Duplication Error: Deleting [{}] from `Limbo`", inst->GetItem()->Name); + LogError( + "Lore Duplication Error | Deleting [{}] ({}) from `Limbo` client [{}]", + inst->GetItem()->Name, + inst->GetItem()->ID, + GetCleanName() + ); } safe_delete(inst); } @@ -3173,8 +2970,13 @@ uint32 Client::GetEquipmentColor(uint8 material_slot) const // Send an item packet (including all subitems of the item) void Client::SendItemPacket(int16 slot_id, const EQ::ItemInstance* inst, ItemPacketType packet_type) { - if (!inst) + if (!inst) { return; + } + + if (!eqs) { + return; + } if (packet_type != ItemPacketMerchant) { if (slot_id <= EQ::invslot::POSSESSIONS_END && slot_id >= EQ::invslot::POSSESSIONS_BEGIN) { @@ -3264,12 +3066,14 @@ void Client::CreateBandolier(const EQApplicationPacket *app) LogInventory("Char: [{}] adding item [{}] to slot [{}]", GetName(),BaseItem->Name, WeaponSlot); m_pp.bandoliers[bs->Number].Items[BandolierSlot].ID = BaseItem->ID; m_pp.bandoliers[bs->Number].Items[BandolierSlot].Icon = BaseItem->Icon; + strncpy(m_pp.bandoliers[bs->Number].Items[BandolierSlot].Name, BaseItem->Name, sizeof(m_pp.bandoliers[bs->Number].Items[BandolierSlot].Name)); database.SaveCharacterBandolier(CharacterID(), bs->Number, BandolierSlot, m_pp.bandoliers[bs->Number].Items[BandolierSlot].ID, m_pp.bandoliers[bs->Number].Items[BandolierSlot].Icon, bs->Name); } else { LogInventory("Char: [{}] no item in slot [{}]", GetName(), WeaponSlot); m_pp.bandoliers[bs->Number].Items[BandolierSlot].ID = 0; m_pp.bandoliers[bs->Number].Items[BandolierSlot].Icon = 0; + m_pp.bandoliers[bs->Number].Items[BandolierSlot].Name[0] = '\0'; } } } @@ -3641,7 +3445,7 @@ bool Client::InterrogateInventory(Client* requester, bool log, bool silent, bool if (cursor_itr == m_inv.cursor_cbegin()) continue; - instmap[8000 + limbo] = *cursor_itr; + instmap[EQ::invbag::CURSOR_BAG_BEGIN + limbo] = *cursor_itr; } // call InterrogateInventory_ for error check @@ -3744,11 +3548,12 @@ bool Client::InterrogateInventory_error(int16 head, int16 index, const EQ::ItemI // very basic error checking - can be elaborated upon if more in-depth testing is needed... if ( - (head >= EQ::invslot::EQUIPMENT_BEGIN && head <= EQ::invslot::EQUIPMENT_END) || - (head >= EQ::invslot::TRIBUTE_BEGIN && head <= EQ::invslot::TRIBUTE_END) || - (head >= EQ::invslot::GUILD_TRIBUTE_BEGIN && head <= EQ::invslot::GUILD_TRIBUTE_END) || - (head >= EQ::invslot::WORLD_BEGIN && head <= EQ::invslot::WORLD_END) || - (head >= 8000 && head <= 8101)) { + EQ::ValueWithin(head, EQ::invslot::EQUIPMENT_BEGIN, EQ::invslot::EQUIPMENT_END) || + EQ::ValueWithin(head, EQ::invslot::TRIBUTE_BEGIN, EQ::invslot::TRIBUTE_END) || + EQ::ValueWithin(head, EQ::invslot::GUILD_TRIBUTE_BEGIN, EQ::invslot::GUILD_TRIBUTE_END) || + EQ::ValueWithin(head, EQ::invslot::WORLD_BEGIN, EQ::invslot::WORLD_END) || + EQ::ValueWithin(head, EQ::invbag::CURSOR_BAG_BEGIN, EQ::invbag::CURSOR_BAG_END) + ) { switch (depth) { case 0: // requirement: inst is extant diff --git a/zone/loot.cpp b/zone/loot.cpp index 6fcf22cca..c93151bff 100644 --- a/zone/loot.cpp +++ b/zone/loot.cpp @@ -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; @@ -52,7 +57,7 @@ void NPC::AddLootTable(uint32 loottable_id, bool is_global) .content_flags_disabled = l->content_flags_disabled }; - if (!content_service.DoesPassContentFiltering(content_flags)) { + if (!WorldContentService::Instance()->DoesPassContentFiltering(content_flags)) { return; } @@ -276,13 +281,18 @@ void NPC::AddLootDrop( uint32 augment_six ) { + if (m_resumed_from_zone_suspend) { + LogZoneState("NPC [{}] is resuming from zone suspend, skipping", GetCleanName()); + return; + } + if (!item2) { return; } auto item = new LootItem; - if (LogSys.log_settings[Logs::Loot].is_category_enabled == 1) { + if (EQEmuLogSys::Instance()->log_settings[Logs::Loot].is_category_enabled == 1) { EQ::SayLinkEngine linker; linker.SetLinkType(EQ::saylink::SayLinkItemData); linker.SetItemData(item2); @@ -370,6 +380,10 @@ void NPC::AddLootDrop( if (item2->Slots & slots) { if (equipment[i]) { compitem = database.GetItem(equipment[i]); + if (!compitem) { + continue; + } + if (item2->AC > compitem->AC || (item2->AC == compitem->AC && item2->HP > compitem->HP)) { // item would be an upgrade // check if we're multi-slot, if yes then we have to keep @@ -380,6 +394,9 @@ void NPC::AddLootDrop( else { // Unequip old item auto *old_item = GetItem(i); + if (!old_item) { + continue; + } old_item->equip_slot = EQ::invslot::SLOT_INVALID; @@ -500,6 +517,7 @@ void NPC::AddLootDrop( parse->EventNPC(EVENT_LOOT_ADDED, this, nullptr, "", 0, &args); } + item->lootdrop_id = loot_drop.lootdrop_id; m_loot_items.push_back(item); if (found) { @@ -671,7 +689,7 @@ LootItem *NPC::GetItem(int slot_id) end = m_loot_items.end(); for (; cur != end; ++cur) { LootItem *item = *cur; - if (item->equip_slot == slot_id) { + if (item && item->equip_slot == slot_id) { return item; } } @@ -687,6 +705,7 @@ void NPC::RemoveItem(uint32 item_id, uint16 quantity, uint16 slot) LootItem *item = *cur; if (item->item_id == item_id && slot <= 0 && quantity <= 0) { m_loot_items.erase(cur); + safe_delete(item); UpdateEquipmentLight(); if (UpdateActiveLight()) { SendAppearancePacket(AppearanceType::Light, GetActiveLightType()); } return; @@ -695,7 +714,10 @@ void NPC::RemoveItem(uint32 item_id, uint16 quantity, uint16 slot) if (item->charges <= quantity) { m_loot_items.erase(cur); UpdateEquipmentLight(); - if (UpdateActiveLight()) { SendAppearancePacket(AppearanceType::Light, GetActiveLightType()); } + if (UpdateActiveLight()) { + SendAppearancePacket(AppearanceType::Light, GetActiveLightType()); + } + safe_delete(item); } else { item->charges -= quantity; @@ -859,9 +881,9 @@ bool NPC::HasItem(uint32 item_id) return false; } -uint16 NPC::CountItem(uint32 item_id) +uint32 NPC::CountItem(uint32 item_id) { - uint16 item_count = 0; + uint32 item_count = 0; if (!database.GetItem(item_id)) { return item_count; } diff --git a/zone/lua_bot.cpp b/zone/lua_bot.cpp index 2cf0e7db3..1e537d76a 100644 --- a/zone/lua_bot.cpp +++ b/zone/lua_bot.cpp @@ -105,19 +105,14 @@ void Lua_Bot::SetExpansionBitmask(int expansion_bitmask) { self->SetExpansionBitmask(expansion_bitmask); } -void Lua_Bot::SetExpansionBitmask(int expansion_bitmask, bool save) { - Lua_Safe_Call_Void(); - self->SetExpansionBitmask(expansion_bitmask, save); -} - bool Lua_Bot::ReloadBotDataBuckets() { Lua_Safe_Call_Bool(); - return DataBucket::GetDataBuckets(self); + return self->LoadDataBucketsCache();; } bool Lua_Bot::ReloadBotOwnerDataBuckets() { Lua_Safe_Call_Bool(); - return self->HasOwner() && DataBucket::GetDataBuckets(self->GetBotOwner()); + return self->HasOwner() && self->LoadDataBucketsCache(); } bool Lua_Bot::ReloadBotSpells() { @@ -274,7 +269,7 @@ void Lua_Bot::SetSpellDurationRaid(int spell_id, int duration, int level, bool a self->SetSpellDuration(spell_id, duration, level, ApplySpellType::Raid, allow_pets, is_raid_group_only); } -int Lua_Bot::CountAugmentEquippedByID(uint32 item_id) { +uint32 Lua_Bot::CountAugmentEquippedByID(uint32 item_id) { Lua_Safe_Call_Int(); return self->GetInv().CountAugmentEquippedByID(item_id); } @@ -284,7 +279,7 @@ bool Lua_Bot::HasAugmentEquippedByID(uint32 item_id) { return self->GetInv().HasAugmentEquippedByID(item_id); } -int Lua_Bot::CountItemEquippedByID(uint32 item_id) { +uint32 Lua_Bot::CountItemEquippedByID(uint32 item_id) { Lua_Safe_Call_Int(); return self->GetInv().CountItemEquippedByID(item_id); } @@ -664,6 +659,11 @@ void Lua_Bot::DeleteBot() { self->DeleteBot(); } +void Lua_Bot::RaidGroupSay(const char* message) { + Lua_Safe_Call_Void(); + self->RaidGroupSay(message); +} + luabind::scope lua_register_bot() { return luabind::class_("Bot") .def(luabind::constructor<>()) @@ -693,6 +693,7 @@ luabind::scope lua_register_bot() { .def("ApplySpellRaid", (void(Lua_Bot::*)(int,int,int,bool))&Lua_Bot::ApplySpellRaid) .def("ApplySpellRaid", (void(Lua_Bot::*)(int,int,int,bool,bool))&Lua_Bot::ApplySpellRaid) .def("ApplySpellRaid", (void(Lua_Bot::*)(int,int,int,bool,bool))&Lua_Bot::ApplySpellRaid) + .def("RaidGroupSay", (void(Lua_Bot::*)(const char*))&Lua_Bot::RaidGroupSay) .def("Camp", (void(Lua_Bot::*)(void))&Lua_Bot::Camp) .def("Camp", (void(Lua_Bot::*)(bool))&Lua_Bot::Camp) .def("ClearDisciplineReuseTimer", (void(Lua_Bot::*)())&Lua_Bot::ClearDisciplineReuseTimer) @@ -701,8 +702,9 @@ luabind::scope lua_register_bot() { .def("ClearItemReuseTimer", (void(Lua_Bot::*)(uint32))&Lua_Bot::ClearItemReuseTimer) .def("ClearSpellRecastTimer", (void(Lua_Bot::*)())&Lua_Bot::ClearSpellRecastTimer) .def("ClearSpellRecastTimer", (void(Lua_Bot::*)(uint16))&Lua_Bot::ClearSpellRecastTimer) + .def("CountAugmentEquippedByID", (uint32(Lua_Bot::*)(uint32))&Lua_Bot::CountAugmentEquippedByID) .def("CountBotItem", (uint32(Lua_Bot::*)(uint32))&Lua_Bot::CountBotItem) - .def("CountItemEquippedByID", (int(Lua_Bot::*)(uint32))&Lua_Bot::CountItemEquippedByID) + .def("CountItemEquippedByID", (uint32(Lua_Bot::*)(uint32))&Lua_Bot::CountItemEquippedByID) .def("DeleteBot", (void(Lua_Bot::*)(void))&Lua_Bot::DeleteBot) .def("DeleteBucket", (void(Lua_Bot::*)(std::string))&Lua_Bot::DeleteBucket) .def("Escape", (void(Lua_Bot::*)(void))&Lua_Bot::Escape) @@ -762,7 +764,6 @@ luabind::scope lua_register_bot() { .def("SetBucket", (void(Lua_Bot::*)(std::string,std::string))&Lua_Bot::SetBucket) .def("SetBucket", (void(Lua_Bot::*)(std::string,std::string,std::string))&Lua_Bot::SetBucket) .def("SetExpansionBitmask", (void(Lua_Bot::*)(int))&Lua_Bot::SetExpansionBitmask) - .def("SetExpansionBitmask", (void(Lua_Bot::*)(int,bool))&Lua_Bot::SetExpansionBitmask) .def("SetDisciplineReuseTimer", (void(Lua_Bot::*)(uint16))&Lua_Bot::SetDisciplineReuseTimer) .def("SetDisciplineReuseTimer", (void(Lua_Bot::*)(uint16, uint32))&Lua_Bot::SetDisciplineReuseTimer) .def("SetItemReuseTimer", (void(Lua_Bot::*)(uint32))&Lua_Bot::SetItemReuseTimer) diff --git a/zone/lua_bot.h b/zone/lua_bot.h index b07d14abc..86e3ca28b 100644 --- a/zone/lua_bot.h +++ b/zone/lua_bot.h @@ -46,13 +46,13 @@ public: Lua_Mob GetOwner(); int16 HasBotItem(uint32 item_id); void OwnerMessage(std::string message); + void RaidGroupSay(const char* message); bool ReloadBotDataBuckets(); bool ReloadBotOwnerDataBuckets(); bool ReloadBotSpells(); void ReloadBotSpellSettings(); void RemoveBotItem(uint32 item_id); void SetExpansionBitmask(int expansion_bitmask); - void SetExpansionBitmask(int expansion_bitmask, bool save); void Signal(int signal_id); bool HasBotSpellEntry(uint16 spellid); void SendPayload(int payload_id); @@ -127,8 +127,8 @@ public: void SetSpellRecastTimer(uint16 spell_id); void SetSpellRecastTimer(uint16 spell_id, uint32 reuse_timer); - int CountAugmentEquippedByID(uint32 item_id); - int CountItemEquippedByID(uint32 item_id); + uint32 CountAugmentEquippedByID(uint32 item_id); + uint32 CountItemEquippedByID(uint32 item_id); bool HasAugmentEquippedByID(uint32 item_id); bool HasItemEquippedByID(uint32 item_id); int GetHealAmount(); diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index f8d5ca64d..15ec526b6 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -1199,16 +1199,6 @@ void Lua_Client::SetStartZone(int zone_id, float x, float y, float z) { self->SetStartZone(zone_id, x, y, z); } -void Lua_Client::KeyRingAdd(uint32 item) { - Lua_Safe_Call_Void(); - self->KeyRingAdd(item); -} - -bool Lua_Client::KeyRingCheck(uint32 item) { - Lua_Safe_Call_Bool(); - return self->KeyRingCheck(item); -} - void Lua_Client::AddPVPPoints(uint32 points) { Lua_Safe_Call_Void(); self->AddPVPPoints(points); @@ -1944,12 +1934,12 @@ Lua_Expedition Lua_Client::CreateExpedition(luabind::object expedition_table) { Lua_Expedition Lua_Client::CreateExpedition(std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, uint32 min_players, uint32 max_players) { Lua_Safe_Call_Class(Lua_Expedition); - return self->CreateExpedition(zone_name, version, duration, expedition_name, min_players, max_players); + return self->CreateExpedition(ZoneID(zone_name), version, duration, expedition_name, min_players, max_players); } Lua_Expedition Lua_Client::CreateExpedition(std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, uint32 min_players, uint32 max_players, bool disable_messages) { Lua_Safe_Call_Class(Lua_Expedition); - return self->CreateExpedition(zone_name, version, duration, expedition_name, min_players, max_players, disable_messages); + return self->CreateExpedition(ZoneID(zone_name), version, duration, expedition_name, min_players, max_players, disable_messages); } Lua_Expedition Lua_Client::CreateExpeditionFromTemplate(uint32_t dz_template_id) { @@ -1968,16 +1958,15 @@ luabind::object Lua_Client::GetExpeditionLockouts(lua_State* L) if (d_) { auto self = reinterpret_cast(d_); - auto lockouts = self->GetExpeditionLockouts(); - + const auto& lockouts = self->GetDzLockouts(); for (const auto& lockout : lockouts) { - auto lockout_table = lua_table[lockout.GetExpeditionName()]; + auto lockout_table = lua_table[lockout.DzName()]; if (luabind::type(lockout_table) != LUA_TTABLE) { lockout_table = luabind::newtable(L); } - lockout_table[lockout.GetEventName()] = lockout.GetSecondsRemaining(); + lockout_table[lockout.Event()] = lockout.GetSecondsRemaining(); } } return lua_table; @@ -1989,13 +1978,12 @@ luabind::object Lua_Client::GetExpeditionLockouts(lua_State* L, std::string expe if (d_) { auto self = reinterpret_cast(d_); - auto lockouts = self->GetExpeditionLockouts(); - + const auto& lockouts = self->GetDzLockouts(); for (const auto& lockout : lockouts) { - if (lockout.GetExpeditionName() == expedition_name) + if (lockout.DzName() == expedition_name) { - lua_table[lockout.GetEventName()] = lockout.GetSecondsRemaining(); + lua_table[lockout.Event()] = lockout.GetSecondsRemaining(); } } } @@ -2005,52 +1993,54 @@ luabind::object Lua_Client::GetExpeditionLockouts(lua_State* L, std::string expe std::string Lua_Client::GetLockoutExpeditionUUID(std::string expedition_name, std::string event_name) { Lua_Safe_Call_String(); std::string uuid; - auto lockout = self->GetExpeditionLockout(expedition_name, event_name); + auto lockout = self->GetDzLockout(expedition_name, event_name); if (lockout) { - uuid = lockout->GetExpeditionUUID(); + uuid = lockout->UUID(); } return uuid; } void Lua_Client::AddExpeditionLockout(std::string expedition_name, std::string event_name, uint32 seconds) { Lua_Safe_Call_Void(); - self->AddNewExpeditionLockout(expedition_name, event_name, seconds); + self->AddDzLockout(expedition_name, event_name, seconds); } void Lua_Client::AddExpeditionLockout(std::string expedition_name, std::string event_name, uint32 seconds, std::string uuid) { Lua_Safe_Call_Void(); - self->AddNewExpeditionLockout(expedition_name, event_name, seconds, uuid); + self->AddDzLockout(expedition_name, event_name, seconds, uuid); } void Lua_Client::AddExpeditionLockoutDuration(std::string expedition_name, std::string event_name, int seconds) { Lua_Safe_Call_Void(); - self->AddExpeditionLockoutDuration(expedition_name, event_name, seconds, {}, true); + auto lockout = DzLockout::Create(expedition_name, event_name, seconds); + self->AddDzLockoutDuration(lockout, seconds, {}, true); } void Lua_Client::AddExpeditionLockoutDuration(std::string expedition_name, std::string event_name, int seconds, std::string uuid) { Lua_Safe_Call_Void(); - self->AddExpeditionLockoutDuration(expedition_name, event_name, seconds, uuid, true); + auto lockout = DzLockout::Create(expedition_name, event_name, seconds, uuid); + self->AddDzLockoutDuration(lockout, seconds, uuid, true); } void Lua_Client::RemoveAllExpeditionLockouts() { Lua_Safe_Call_Void(); - self->RemoveAllExpeditionLockouts({}, true); + self->RemoveDzLockouts({}, true); } void Lua_Client::RemoveAllExpeditionLockouts(std::string expedition_name) { Lua_Safe_Call_Void(); - self->RemoveAllExpeditionLockouts(expedition_name, true); + self->RemoveDzLockouts(expedition_name, true); } void Lua_Client::RemoveExpeditionLockout(std::string expedition_name, std::string event_name) { Lua_Safe_Call_Void(); - self->RemoveExpeditionLockout(expedition_name, event_name, true); + self->RemoveDzLockout(expedition_name, event_name, true); } bool Lua_Client::HasExpeditionLockout(std::string expedition_name, std::string event_name) { Lua_Safe_Call_Bool(); - return self->HasExpeditionLockout(expedition_name, event_name); + return self->HasDzLockout(expedition_name, event_name); } void Lua_Client::MovePCDynamicZone(uint32 zone_id) { @@ -2287,7 +2277,7 @@ void Lua_Client::SendToInstance(std::string instance_type, std::string zone_shor self->SendToInstance(instance_type, zone_short_name, instance_version, x, y, z, heading, instance_identifier, duration); } -int Lua_Client::CountItem(uint32 item_id) { +uint32 Lua_Client::CountItem(uint32 item_id) { Lua_Safe_Call_Int(); return self->CountItem(item_id); } @@ -2480,7 +2470,7 @@ void Lua_Client::AddItem(luabind::object item_table) { ); } -int Lua_Client::CountAugmentEquippedByID(uint32 item_id) { +uint32 Lua_Client::CountAugmentEquippedByID(uint32 item_id) { Lua_Safe_Call_Int(); return self->GetInv().CountAugmentEquippedByID(item_id); } @@ -2490,7 +2480,7 @@ bool Lua_Client::HasAugmentEquippedByID(uint32 item_id) { return self->GetInv().HasAugmentEquippedByID(item_id); } -int Lua_Client::CountItemEquippedByID(uint32 item_id) { +uint32 Lua_Client::CountItemEquippedByID(uint32 item_id) { Lua_Safe_Call_Int(); return self->GetInv().CountItemEquippedByID(item_id); } @@ -3141,7 +3131,7 @@ bool Lua_Client::IsAutoFireEnabled() bool Lua_Client::ReloadDataBuckets() { Lua_Safe_Call_Bool(); - return DataBucket::GetDataBuckets(self); + return self->LoadDataBucketsCache(); } uint32 Lua_Client::GetEXPForLevel(uint16 check_level) @@ -3436,6 +3426,204 @@ void Lua_Client::AreaTaunt(float range, int bonus_hate) entity_list.AETaunt(self, range, bonus_hate); } +luabind::object Lua_Client::GetInventorySlots(lua_State* L) +{ + auto lua_table = luabind::newtable(L); + + if (d_) { + auto self = reinterpret_cast(d_); + int index = 1; + for (const int16 &slot_id: self->GetInventorySlots()) { + lua_table[index] = slot_id; + index++; + } + } + + return lua_table; +} + +void Lua_Client::ShowZoneShardMenu() +{ + Lua_Safe_Call_Void(); + self->ShowZoneShardMenu(); +} + +void Lua_Client::GrantPetNameChange() +{ + Lua_Safe_Call_Void(); + self->GrantPetNameChange(); +} + +void Lua_Client::SetAAEXPPercentage(uint8 percentage) +{ + Lua_Safe_Call_Void(); + self->SetAAEXPPercentage(percentage); +} + +void Lua_Client::SetAccountBucket(std::string bucket_name, std::string bucket_value) +{ + Lua_Safe_Call_Void(); + self->SetAccountBucket(bucket_name, bucket_value); +} + +void Lua_Client::SetAccountBucket(std::string bucket_name, std::string bucket_value, std::string expiration) +{ + Lua_Safe_Call_Void(); + self->SetAccountBucket(bucket_name, bucket_value, expiration); +} + +void Lua_Client::DeleteAccountBucket(std::string bucket_name) +{ + Lua_Safe_Call_Void(); + self->DeleteAccountBucket(bucket_name); +} + +std::string Lua_Client::GetAccountBucket(std::string bucket_name) +{ + Lua_Safe_Call_String(); + return self->GetAccountBucket(bucket_name); +} + +std::string Lua_Client::GetAccountBucketExpires(std::string bucket_name) +{ + Lua_Safe_Call_String(); + return self->GetAccountBucketExpires(bucket_name); +} + +std::string Lua_Client::GetAccountBucketRemaining(std::string bucket_name) +{ + Lua_Safe_Call_String(); + 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(); + return self->GetBandolierName(bandolier_slot); +} + +uint32 Lua_Client::GetBandolierItemIcon(uint8 bandolier_slot, uint8 slot_id) +{ + Lua_Safe_Call_Int(); + return self->GetBandolierItemIcon(bandolier_slot, slot_id); +} + +uint32 Lua_Client::GetBandolierItemID(uint8 bandolier_slot, uint8 slot_id) +{ + Lua_Safe_Call_Int(); + return self->GetBandolierItemID(bandolier_slot, slot_id); +} + +std::string Lua_Client::GetBandolierItemName(uint8 bandolier_slot, uint8 slot_id) +{ + Lua_Safe_Call_String(); + return self->GetBandolierItemName(bandolier_slot, slot_id); +} + +uint32 Lua_Client::GetPotionBeltItemIcon(uint8 slot_id) +{ + Lua_Safe_Call_Int(); + return self->GetPotionBeltItemIcon(slot_id); +} + +uint32 Lua_Client::GetPotionBeltItemID(uint8 slot_id) +{ + Lua_Safe_Call_Int(); + return self->GetPotionBeltItemID(slot_id); +} + +std::string Lua_Client::GetPotionBeltItemName(uint8 slot_id) +{ + Lua_Safe_Call_String(); + return self->GetPotionBeltItemName(slot_id); +} + +bool Lua_Client::KeyRingAdd(uint32 item) { + Lua_Safe_Call_Bool(); + return self->KeyRingAdd(item); +} + +bool Lua_Client::KeyRingCheck(uint32 item) { + Lua_Safe_Call_Bool(); + return self->KeyRingCheck(item); +} + +bool Lua_Client::KeyRingClear() +{ + Lua_Safe_Call_Bool(); + return self->KeyRingClear(); +} + +void Lua_Client::KeyRingList() +{ + Lua_Safe_Call_Void(); + self->KeyRingList(self); +} + +void Lua_Client::KeyRingList(Lua_Client c) +{ + Lua_Safe_Call_Void(); + self->KeyRingList(self); +} + +bool Lua_Client::KeyRingRemove(uint32 item_id) +{ + Lua_Safe_Call_Bool(); + return self->KeyRingRemove(item_id); +} + + +bool Lua_Client::CompleteTask(int task_id) +{ + Lua_Safe_Call_Bool(); + return self->CompleteTask(task_id); +} + +bool Lua_Client::UncompleteTask(int task_id) +{ + Lua_Safe_Call_Bool(); + return self->UncompleteTask(task_id); +} + +void Lua_Client::EnableTitleSet(uint32 title_set) { + Lua_Safe_Call_Void(); + self->EnableTitle(title_set); +} + +luabind::object Lua_Client::GetKeyRing(lua_State* L) +{ + auto lua_table = luabind::newtable(L); + + if (d_) { + auto self = reinterpret_cast(d_); + int index = 1; + for (const uint32& item_id: self->GetKeyRing()) { + lua_table[index] = item_id; + index++; + } + } + + return lua_table; +} + luabind::scope lua_register_client() { return luabind::class_("Client") .def(luabind::constructor<>()) @@ -3504,6 +3692,8 @@ luabind::scope lua_register_client() { .def("CanHaveSkill", (bool(Lua_Client::*)(int))&Lua_Client::CanHaveSkill) .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) @@ -3513,10 +3703,11 @@ luabind::scope lua_register_client() { .def("ClearPEQZoneFlag", (void(Lua_Client::*)(uint32))&Lua_Client::ClearPEQZoneFlag) .def("ClearXTargets", (void(Lua_Client::*)(void))&Lua_Client::ClearXTargets) .def("ClearZoneFlag", (void(Lua_Client::*)(uint32))&Lua_Client::ClearZoneFlag) + .def("CompleteTask", (bool(Lua_Client::*)(int))&Lua_Client::CompleteTask) .def("Connected", (bool(Lua_Client::*)(void))&Lua_Client::Connected) - .def("CountAugmentEquippedByID", (int(Lua_Client::*)(uint32))&Lua_Client::CountAugmentEquippedByID) - .def("CountItem", (int(Lua_Client::*)(uint32))&Lua_Client::CountItem) - .def("CountItemEquippedByID", (int(Lua_Client::*)(uint32))&Lua_Client::CountItemEquippedByID) + .def("CountAugmentEquippedByID", (uint32(Lua_Client::*)(uint32))&Lua_Client::CountAugmentEquippedByID) + .def("CountItem", (uint32(Lua_Client::*)(uint32))&Lua_Client::CountItem) + .def("CountItemEquippedByID", (uint32(Lua_Client::*)(uint32))&Lua_Client::CountItemEquippedByID) .def("CreateExpedition", (Lua_Expedition(Lua_Client::*)(luabind::object))&Lua_Client::CreateExpedition) .def("CreateExpedition", (Lua_Expedition(Lua_Client::*)(std::string, uint32, uint32, std::string, uint32, uint32))&Lua_Client::CreateExpedition) .def("CreateExpedition", (Lua_Expedition(Lua_Client::*)(std::string, uint32, uint32, std::string, uint32, uint32, bool))&Lua_Client::CreateExpedition) @@ -3524,6 +3715,7 @@ luabind::scope lua_register_client() { .def("CreateTaskDynamicZone", &Lua_Client::CreateTaskDynamicZone) .def("DecreaseByID", (bool(Lua_Client::*)(uint32,int))&Lua_Client::DecreaseByID) .def("DescribeSpecialAbilities", (void(Lua_Client::*)(Lua_NPC))&Lua_Client::DescribeSpecialAbilities) + .def("DeleteAccountBucket", (void(Lua_Client::*)(std::string))&Lua_Client::DeleteAccountBucket) .def("DeleteBucket", (void(Lua_Client::*)(std::string))&Lua_Client::DeleteBucket) .def("DeleteItemInInventory", (void(Lua_Client::*)(int,int))&Lua_Client::DeleteItemInInventory) .def("DeleteItemInInventory", (void(Lua_Client::*)(int,int,bool))&Lua_Client::DeleteItemInInventory) @@ -3542,6 +3734,7 @@ luabind::scope lua_register_client() { .def("EnableAreaHPRegen", &Lua_Client::EnableAreaHPRegen) .def("EnableAreaManaRegen", &Lua_Client::EnableAreaManaRegen) .def("EnableAreaRegens", &Lua_Client::EnableAreaRegens) + .def("EnableTitleSet", &Lua_Client::EnableTitleSet) .def("EndSharedTask", (void(Lua_Client::*)(void))&Lua_Client::EndSharedTask) .def("EndSharedTask", (void(Lua_Client::*)(bool))&Lua_Client::EndSharedTask) .def("Escape", (void(Lua_Client::*)(void))&Lua_Client::Escape) @@ -3578,6 +3771,10 @@ luabind::scope lua_register_client() { .def("GetAugmentIDAt", (int(Lua_Client::*)(int,int))&Lua_Client::GetAugmentIDAt) .def("GetAugmentIDsBySlotID", (luabind::object(Lua_Client::*)(lua_State* L,int16))&Lua_Client::GetAugmentIDsBySlotID) .def("GetAutoLoginCharacterName", (std::string(Lua_Client::*)(void))&Lua_Client::GetAutoLoginCharacterName) + .def("GetBandolierItemIcon", (uint32(Lua_Client::*)(uint8,uint8))&Lua_Client::GetBandolierItemIcon) + .def("GetBandolierItemID", (uint32(Lua_Client::*)(uint8,uint8))&Lua_Client::GetBandolierItemID) + .def("GetBandolierItemName", (std::string(Lua_Client::*)(uint8,uint8))&Lua_Client::GetBandolierItemName) + .def("GetBandolierName", (std::string(Lua_Client::*)(uint8))&Lua_Client::GetBandolierName) .def("GetBaseAGI", (int(Lua_Client::*)(void))&Lua_Client::GetBaseAGI) .def("GetBaseCHA", (int(Lua_Client::*)(void))&Lua_Client::GetBaseCHA) .def("GetBaseDEX", (int(Lua_Client::*)(void))&Lua_Client::GetBaseDEX) @@ -3602,6 +3799,9 @@ luabind::scope lua_register_client() { .def("GetBotRequiredLevel", (int(Lua_Client::*)(uint8))&Lua_Client::GetBotRequiredLevel) .def("GetBotSpawnLimit", (int(Lua_Client::*)(void))&Lua_Client::GetBotSpawnLimit) .def("GetBotSpawnLimit", (int(Lua_Client::*)(uint8))&Lua_Client::GetBotSpawnLimit) + .def("GetAccountBucket", (std::string(Lua_Client::*)(std::string))&Lua_Client::GetAccountBucket) + .def("GetAccountBucketExpires", (std::string(Lua_Client::*)(std::string))&Lua_Client::GetAccountBucketExpires) + .def("GetAccountBucketRemaining", (std::string(Lua_Client::*)(std::string))&Lua_Client::GetAccountBucketRemaining) .def("GetBucket", (std::string(Lua_Client::*)(std::string))&Lua_Client::GetBucket) .def("GetBucketExpires", (std::string(Lua_Client::*)(std::string))&Lua_Client::GetBucketExpires) .def("GetBucketRemaining", (std::string(Lua_Client::*)(std::string))&Lua_Client::GetBucketRemaining) @@ -3650,9 +3850,11 @@ luabind::scope lua_register_client() { .def("GetInstrumentMod", (int(Lua_Client::*)(int))&Lua_Client::GetInstrumentMod) .def("GetIntoxication", (int(Lua_Client::*)(void))&Lua_Client::GetIntoxication) .def("GetInventory", (Lua_Inventory(Lua_Client::*)(void))&Lua_Client::GetInventory) + .def("GetInventorySlots", (luabind::object(Lua_Client::*)(lua_State* L))&Lua_Client::GetInventorySlots) .def("GetInvulnerableEnvironmentDamage", (bool(Lua_Client::*)(void))&Lua_Client::GetInvulnerableEnvironmentDamage) .def("GetItemIDAt", (int(Lua_Client::*)(int))&Lua_Client::GetItemIDAt) .def("GetItemCooldown", (uint32(Lua_Client::*)(uint32))&Lua_Client::GetItemCooldown) + .def("GetKeyRing", (luabind::object(Lua_Client::*)(lua_State* L))&Lua_Client::GetKeyRing) .def("GetLDoNLosses", (int(Lua_Client::*)(void))&Lua_Client::GetLDoNLosses) .def("GetLDoNLossesTheme", (int(Lua_Client::*)(int))&Lua_Client::GetLDoNLossesTheme) .def("GetLDoNPointsTheme", (int(Lua_Client::*)(int))&Lua_Client::GetLDoNPointsTheme) @@ -3672,6 +3874,9 @@ luabind::scope lua_register_client() { .def("GetNextAvailableDisciplineSlot", (int(Lua_Client::*)(void))&Lua_Client::GetNextAvailableDisciplineSlot) .def("GetNextAvailableSpellBookSlot", (int(Lua_Client::*)(int))&Lua_Client::GetNextAvailableSpellBookSlot) .def("GetNextAvailableSpellBookSlot", (int(Lua_Client::*)(void))&Lua_Client::GetNextAvailableSpellBookSlot) + .def("GetPotionBeltItemIcon", (uint32(Lua_Client::*)(uint8))&Lua_Client::GetPotionBeltItemIcon) + .def("GetPotionBeltItemID", (uint32(Lua_Client::*)(uint8))&Lua_Client::GetPotionBeltItemID) + .def("GetPotionBeltItemName", (std::string(Lua_Client::*)(uint8))&Lua_Client::GetPotionBeltItemName) .def("GetPVP", (bool(Lua_Client::*)(void))&Lua_Client::GetPVP) .def("GetPVPPoints", (uint32(Lua_Client::*)(void))&Lua_Client::GetPVPPoints) .def("GetRaceBitmask", (uint16(Lua_Client::*)(void))&Lua_Client::GetRaceBitmask) @@ -3708,6 +3913,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) @@ -3738,14 +3944,19 @@ 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) .def("IsTaskActive", (bool(Lua_Client::*)(int))&Lua_Client::IsTaskActive) .def("IsTaskActivityActive", (bool(Lua_Client::*)(int,int))&Lua_Client::IsTaskActivityActive) .def("IsTaskCompleted", (bool(Lua_Client::*)(int))&Lua_Client::IsTaskCompleted) - .def("KeyRingAdd", (void(Lua_Client::*)(uint32))&Lua_Client::KeyRingAdd) + .def("KeyRingAdd", (bool(Lua_Client::*)(uint32))&Lua_Client::KeyRingAdd) .def("KeyRingCheck", (bool(Lua_Client::*)(uint32))&Lua_Client::KeyRingCheck) + .def("KeyRingClear", (bool(Lua_Client::*)(void))&Lua_Client::KeyRingClear) + .def("KeyRingList", (void(Lua_Client::*)(void))&Lua_Client::KeyRingList) + .def("KeyRingList", (void(Lua_Client::*)(Lua_Client))&Lua_Client::KeyRingList) + .def("KeyRingRemove", (bool(Lua_Client::*)(uint32))&Lua_Client::KeyRingRemove) .def("Kick", (void(Lua_Client::*)(void))&Lua_Client::Kick) .def("LearnDisciplines", (uint16(Lua_Client::*)(uint8,uint8))&Lua_Client::LearnDisciplines) .def("LearnRecipe", (void(Lua_Client::*)(uint32))&Lua_Client::LearnRecipe) @@ -3867,6 +4078,7 @@ luabind::scope lua_register_client() { .def("SetAAEXPModifier", (void(Lua_Client::*)(float))&Lua_Client::SetAAEXPModifier) .def("SetAAEXPModifier", (void(Lua_Client::*)(uint32,float))&Lua_Client::SetAAEXPModifier) .def("SetAAEXPModifier", (void(Lua_Client::*)(uint32,float,int16))&Lua_Client::SetAAEXPModifier) + .def("SetAAEXPPercentage", (void(Lua_Client::*)(uint8))&Lua_Client::SetAAEXPPercentage) .def("SetAAPoints", (void(Lua_Client::*)(int))&Lua_Client::SetAAPoints) .def("SetAATitle", (void(Lua_Client::*)(std::string))&Lua_Client::SetAATitle) .def("SetAATitle", (void(Lua_Client::*)(std::string,bool))&Lua_Client::SetAATitle) @@ -3892,6 +4104,8 @@ luabind::scope lua_register_client() { .def("SetBotRequiredLevel", (void(Lua_Client::*)(int,uint8))&Lua_Client::SetBotRequiredLevel) .def("SetBotSpawnLimit", (void(Lua_Client::*)(int))&Lua_Client::SetBotSpawnLimit) .def("SetBotSpawnLimit", (void(Lua_Client::*)(int,uint8))&Lua_Client::SetBotSpawnLimit) + .def("SetAccountBucket", (void(Lua_Client::*)(std::string,std::string))&Lua_Client::SetAccountBucket) + .def("SetAccountBucket", (void(Lua_Client::*)(std::string,std::string,std::string))&Lua_Client::SetAccountBucket) .def("SetBucket", (void(Lua_Client::*)(std::string,std::string))&Lua_Client::SetBucket) .def("SetBucket", (void(Lua_Client::*)(std::string,std::string,std::string))&Lua_Client::SetBucket) .def("SetClientMaxLevel", (void(Lua_Client::*)(int))&Lua_Client::SetClientMaxLevel) @@ -3954,6 +4168,7 @@ luabind::scope lua_register_client() { .def("SetTint", (void(Lua_Client::*)(int,uint32))&Lua_Client::SetTint) .def("SetTitleSuffix", (void(Lua_Client::*)(const char *))&Lua_Client::SetTitleSuffix) .def("SetZoneFlag", (void(Lua_Client::*)(uint32))&Lua_Client::SetZoneFlag) + .def("ShowZoneShardMenu", (void(Lua_Client::*)(void))&Lua_Client::ShowZoneShardMenu) .def("Signal", (void(Lua_Client::*)(int))&Lua_Client::Signal) .def("Sit", (void(Lua_Client::*)(void))&Lua_Client::Sit) .def("Stand", (void(Lua_Client::*)(void))&Lua_Client::Stand) @@ -3985,6 +4200,7 @@ luabind::scope lua_register_client() { .def("TrainDisc", (void(Lua_Client::*)(int))&Lua_Client::TrainDisc) .def("TrainDiscBySpellID", (void(Lua_Client::*)(int32))&Lua_Client::TrainDiscBySpellID) .def("UnFreeze", (void(Lua_Client::*)(void))&Lua_Client::UnFreeze) + .def("UncompleteTask", (bool(Lua_Client::*)(int))&Lua_Client::UncompleteTask) .def("Undye", (void(Lua_Client::*)(void))&Lua_Client::Undye) .def("UnmemSpell", (void(Lua_Client::*)(int))&Lua_Client::UnmemSpell) .def("UnmemSpell", (void(Lua_Client::*)(int,bool))&Lua_Client::UnmemSpell) diff --git a/zone/lua_client.h b/zone/lua_client.h index faff8b157..c85c061b6 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -297,6 +297,7 @@ public: int GetClientVersion(); uint32 GetClientVersionBit(); void SetTitleSuffix(const char *text); + void EnableTitleSet(uint32 title_set); void SetAAPoints(int points); int GetAAPoints(); int GetSpentAA(); @@ -313,8 +314,6 @@ public: void SetStartZone(int zone_id, float x, float y); void SetStartZone(int zone_id, float x, float y, float z); void SetStartZone(int zone_id, float x, float y, float z, float heading); - void KeyRingAdd(uint32 item); - bool KeyRingCheck(uint32 item); void AddPVPPoints(uint32 points); void AddCrystals(uint32 radiant_count, uint32 ebon_count); void SetEbonCrystals(uint32 value); @@ -441,14 +440,14 @@ public: void Popup(const char* title, const char* text, uint32 popup_id, uint32 negative_id, uint32 button_type, uint32 duration); void Popup(const char* title, const char* text, uint32 popup_id, uint32 negative_id, uint32 button_type, uint32 duration, const char* button_name_one, const char* button_name_two); void Popup(const char* title, const char* text, uint32 popup_id, uint32 negative_id, uint32 button_type, uint32 duration, const char* button_name_one, const char* button_name_two, uint32 sound_controls); - int CountItem(uint32 item_id); + uint32 CountItem(uint32 item_id); void RemoveItem(uint32 item_id); void RemoveItem(uint32 item_id, uint32 quantity); void SetGMStatus(int new_status); int16 GetGMStatus(); void AddItem(luabind::object item_table); - int CountAugmentEquippedByID(uint32 item_id); - int CountItemEquippedByID(uint32 item_id); + uint32 CountAugmentEquippedByID(uint32 item_id); + uint32 CountItemEquippedByID(uint32 item_id); bool HasAugmentEquippedByID(uint32 item_id); bool HasItemEquippedByID(uint32 item_id); int GetHealAmount(); @@ -509,6 +508,32 @@ public: void AreaTaunt(); void AreaTaunt(float range); void AreaTaunt(float range, int bonus_hate); + luabind::object GetInventorySlots(lua_State* L); + void SetAAEXPPercentage(uint8 percentage); + std::string GetBandolierName(uint8 bandolier_slot); + uint32 GetBandolierItemIcon(uint8 bandolier_slot, uint8 slot_id); + uint32 GetBandolierItemID(uint8 bandolier_slot, uint8 slot_id); + std::string GetBandolierItemName(uint8 bandolier_slot, uint8 slot_id); + uint32 GetPotionBeltItemIcon(uint8 slot_id); + uint32 GetPotionBeltItemID(uint8 slot_id); + std::string GetPotionBeltItemName(uint8 slot_id); + bool KeyRingAdd(uint32 item_id); + bool KeyRingCheck(uint32 item_id); + bool KeyRingClear(); + void KeyRingList(); + void KeyRingList(Lua_Client c); + bool KeyRingRemove(uint32 item_id); + bool CompleteTask(int task_id); + bool UncompleteTask(int task_id); + luabind::object GetKeyRing(lua_State* L); + + // account data buckets + void SetAccountBucket(std::string bucket_name, std::string bucket_value); + void SetAccountBucket(std::string bucket_name, std::string bucket_value, std::string expiration = ""); + void DeleteAccountBucket(std::string bucket_name); + std::string GetAccountBucket(std::string bucket_name); + std::string GetAccountBucketExpires(std::string bucket_name); + std::string GetAccountBucketRemaining(std::string bucket_name); void ApplySpell(int spell_id); void ApplySpell(int spell_id, int duration); @@ -586,6 +611,12 @@ public: void DialogueWindow(std::string markdown); bool ReloadDataBuckets(); + 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); diff --git a/zone/lua_corpse.cpp b/zone/lua_corpse.cpp index 321e4e543..2db6bad07 100644 --- a/zone/lua_corpse.cpp +++ b/zone/lua_corpse.cpp @@ -177,7 +177,7 @@ bool Lua_Corpse::HasItem(uint32 item_id) { return self->HasItem(item_id); } -uint16 Lua_Corpse::CountItem(uint32 item_id) { +uint32 Lua_Corpse::CountItem(uint32 item_id) { Lua_Safe_Call_Int(); return self->CountItem(item_id); } @@ -226,7 +226,7 @@ luabind::scope lua_register_corpse() { .def("AllowMobLoot", (void(Lua_Corpse::*)(Lua_Mob, uint8))&Lua_Corpse::AllowMobLoot) .def("Bury", (void(Lua_Corpse::*)(void))&Lua_Corpse::Bury) .def("CanMobLoot", (bool(Lua_Corpse::*)(int))&Lua_Corpse::CanMobLoot) - .def("CountItem", (uint16(Lua_Corpse::*)(uint32))&Lua_Corpse::CountItem) + .def("CountItem", (uint32(Lua_Corpse::*)(uint32))&Lua_Corpse::CountItem) .def("CountItems", (uint32(Lua_Corpse::*)(void))&Lua_Corpse::CountItems) .def("Delete", (void(Lua_Corpse::*)(void))&Lua_Corpse::Delete) .def("Depop", (void(Lua_Corpse::*)(void))&Lua_Corpse::Depop) diff --git a/zone/lua_corpse.h b/zone/lua_corpse.h index 9ad63e979..58bfb7d05 100644 --- a/zone/lua_corpse.h +++ b/zone/lua_corpse.h @@ -62,7 +62,7 @@ public: uint32 GetPlatinum(); void AddLooter(Lua_Mob who); bool HasItem(uint32 item_id); - uint16 CountItem(uint32 item_id); + uint32 CountItem(uint32 item_id); uint32 GetItemIDBySlot(uint16 loot_slot); uint16 GetFirstLootSlotByItemID(uint32 item_id); Lua_Corpse_Loot_List GetLootList(lua_State* L); diff --git a/zone/lua_entity_list.cpp b/zone/lua_entity_list.cpp index 45c0e6621..4b99c869e 100644 --- a/zone/lua_entity_list.cpp +++ b/zone/lua_entity_list.cpp @@ -382,12 +382,13 @@ Lua_Bot Lua_EntityList::GetBotByName(std::string bot_name) { Lua_Bot_List Lua_EntityList::GetBotList() { Lua_Safe_Call_Class(Lua_Bot_List); Lua_Bot_List ret; - auto &bot_list = self->GetBotList(); - if (bot_list.size()) { - for (auto bot : bot_list) { - ret.entries.emplace_back(Lua_Bot(bot)); - } + auto ¤t_bot_list = self->GetBotList(); + auto it = current_bot_list.begin(); + + while (it != current_bot_list.end()) { + ret.entries.emplace_back(it->second); + ++it; } return ret; diff --git a/zone/lua_expedition.cpp b/zone/lua_expedition.cpp index fca207077..41554b41e 100644 --- a/zone/lua_expedition.cpp +++ b/zone/lua_expedition.cpp @@ -1,7 +1,7 @@ #ifdef LUA_EQEMU #include "lua_expedition.h" -#include "expedition.h" +#include "dynamic_zone.h" #include "../common/zone_store.h" #include "lua.hpp" #include @@ -24,22 +24,17 @@ void Lua_Expedition::AddLockoutDuration(std::string event_name, int seconds, boo void Lua_Expedition::AddReplayLockout(uint32_t seconds) { Lua_Safe_Call_Void(); - self->AddReplayLockout(seconds); + self->AddLockout(DzLockout::ReplayTimer, seconds); } void Lua_Expedition::AddReplayLockoutDuration(int seconds) { Lua_Safe_Call_Void(); - self->AddReplayLockoutDuration(seconds); + self->AddLockoutDuration(DzLockout::ReplayTimer, seconds); } void Lua_Expedition::AddReplayLockoutDuration(int seconds, bool members_only) { Lua_Safe_Call_Void(); - self->AddReplayLockoutDuration(seconds, members_only); -} - -uint32_t Lua_Expedition::GetDynamicZoneID() { - Lua_Safe_Call_Int(); - return self->GetDynamicZoneID(); + self->AddLockoutDuration(DzLockout::ReplayTimer, seconds, members_only); } uint32_t Lua_Expedition::GetID() { @@ -49,7 +44,7 @@ uint32_t Lua_Expedition::GetID() { int Lua_Expedition::GetInstanceID() { Lua_Safe_Call_Int(); - return self->GetDynamicZone()->GetInstanceID(); + return self->GetInstanceID(); } std::string Lua_Expedition::GetLeaderName() { @@ -63,10 +58,10 @@ luabind::object Lua_Expedition::GetLockouts(lua_State* L) { if (d_) { auto self = reinterpret_cast(d_); - auto lockouts = self->GetLockouts(); + const auto& lockouts = self->GetLockouts(); for (const auto& lockout : lockouts) { - lua_table[lockout.first] = lockout.second.GetSecondsRemaining(); + lua_table[lockout.Event()] = lockout.GetSecondsRemaining(); } } return lua_table; @@ -74,17 +69,17 @@ luabind::object Lua_Expedition::GetLockouts(lua_State* L) { std::string Lua_Expedition::GetLootEventByNPCTypeID(uint32_t npc_type_id) { Lua_Safe_Call_String(); - return self->GetLootEventByNPCTypeID(npc_type_id); + return self->GetLootEvent(npc_type_id, DzLootEvent::Type::NpcType); } std::string Lua_Expedition::GetLootEventBySpawnID(uint32_t spawn_id) { Lua_Safe_Call_String(); - return self->GetLootEventBySpawnID(spawn_id); + return self->GetLootEvent(spawn_id, DzLootEvent::Type::Entity); } uint32_t Lua_Expedition::GetMemberCount() { Lua_Safe_Call_Int(); - return self->GetDynamicZone()->GetMemberCount(); + return self->GetMemberCount(); } luabind::object Lua_Expedition::GetMembers(lua_State* L) { @@ -93,7 +88,7 @@ luabind::object Lua_Expedition::GetMembers(lua_State* L) { if (d_) { auto self = reinterpret_cast(d_); - for (const auto& member : self->GetDynamicZone()->GetMembers()) + for (const auto& member : self->GetMembers()) { lua_table[member.name] = member.id; } @@ -108,27 +103,27 @@ std::string Lua_Expedition::GetName() { int Lua_Expedition::GetSecondsRemaining() { Lua_Safe_Call_Int(); - return self->GetDynamicZone()->GetSecondsRemaining(); + return self->GetSecondsRemaining(); } std::string Lua_Expedition::GetUUID() { Lua_Safe_Call_String(); - return self->GetDynamicZone()->GetUUID(); + return self->GetUUID(); } int Lua_Expedition::GetZoneID() { Lua_Safe_Call_Int(); - return self->GetDynamicZone()->GetZoneID(); + return self->GetZoneID(); } std::string Lua_Expedition::GetZoneName() { Lua_Safe_Call_String(); - return ZoneName(self->GetDynamicZone()->GetZoneID()); + return ZoneName(self->GetZoneID()); } int Lua_Expedition::GetZoneVersion() { Lua_Safe_Call_Int(); - return self->GetDynamicZone()->GetZoneVersion(); + return self->GetZoneVersion(); } bool Lua_Expedition::HasLockout(std::string event_name) { @@ -148,7 +143,7 @@ bool Lua_Expedition::IsLocked() { void Lua_Expedition::RemoveCompass() { Lua_Safe_Call_Void(); - self->GetDynamicZone()->SetCompass(0, 0, 0, 0, true); + self->SetCompass(0, 0, 0, 0, true); } void Lua_Expedition::RemoveLockout(std::string event_name) { @@ -158,69 +153,69 @@ void Lua_Expedition::RemoveLockout(std::string event_name) { void Lua_Expedition::SetCompass(uint32_t zone_id, float x, float y, float z) { Lua_Safe_Call_Void(); - self->GetDynamicZone()->SetCompass(zone_id, x, y, z, true); + self->SetCompass(zone_id, x, y, z, true); } void Lua_Expedition::SetCompass(std::string zone_name, float x, float y, float z) { Lua_Safe_Call_Void(); - self->GetDynamicZone()->SetCompass(ZoneID(zone_name), x, y, z, true); + self->SetCompass(ZoneID(zone_name), x, y, z, true); } void Lua_Expedition::SetLocked(bool lock_expedition) { Lua_Safe_Call_Void(); - self->SetLocked(lock_expedition, ExpeditionLockMessage::None, true); + self->SetLocked(lock_expedition, true); } void Lua_Expedition::SetLocked(bool lock_expedition, int lock_msg) { Lua_Safe_Call_Void(); - self->SetLocked(lock_expedition, static_cast(lock_msg), true); + self->SetLocked(lock_expedition, true, static_cast(lock_msg)); } void Lua_Expedition::SetLocked(bool lock_expedition, int lock_msg, uint32_t msg_color) { Lua_Safe_Call_Void(); - self->SetLocked(lock_expedition, static_cast(lock_msg), true, msg_color); + self->SetLocked(lock_expedition, true, static_cast(lock_msg), msg_color); } void Lua_Expedition::SetLootEventByNPCTypeID(uint32_t npc_type_id, std::string event_name) { Lua_Safe_Call_Void(); - self->SetLootEventByNPCTypeID(npc_type_id, event_name); + self->SetLootEvent(npc_type_id, event_name, DzLootEvent::Type::NpcType); } void Lua_Expedition::SetLootEventBySpawnID(uint32_t spawn_id, std::string event_name) { Lua_Safe_Call_Void(); - self->SetLootEventBySpawnID(spawn_id, event_name); + self->SetLootEvent(spawn_id, event_name, DzLootEvent::Type::Entity); } void Lua_Expedition::SetReplayLockoutOnMemberJoin(bool enable) { Lua_Safe_Call_Void(); - self->SetReplayLockoutOnMemberJoin(enable, true); + self->SetReplayOnJoin(enable, true); } void Lua_Expedition::SetSafeReturn(uint32_t zone_id, float x, float y, float z, float heading) { Lua_Safe_Call_Void(); - self->GetDynamicZone()->SetSafeReturn(zone_id, x, y, z, heading, true); + self->SetSafeReturn(zone_id, x, y, z, heading, true); } void Lua_Expedition::SetSafeReturn(std::string zone_name, float x, float y, float z, float heading) { Lua_Safe_Call_Void(); - self->GetDynamicZone()->SetSafeReturn(ZoneID(zone_name), x, y, z, heading, true); + self->SetSafeReturn(ZoneID(zone_name), x, y, z, heading, true); } void Lua_Expedition::SetSecondsRemaining(uint32_t seconds_remaining) { Lua_Safe_Call_Void(); - self->GetDynamicZone()->SetSecondsRemaining(seconds_remaining); + self->SetSecondsRemaining(seconds_remaining); } void Lua_Expedition::SetSwitchID(int dz_switch_id) { Lua_Safe_Call_Void(); - self->GetDynamicZone()->SetSwitchID(dz_switch_id, true); + self->SetSwitchID(dz_switch_id, true); } void Lua_Expedition::SetZoneInLocation(float x, float y, float z, float heading) { Lua_Safe_Call_Void(); - self->GetDynamicZone()->SetZoneInLocation(x, y, z, heading, true); + self->SetZoneInLocation(x, y, z, heading, true); } void Lua_Expedition::UpdateLockoutDuration(std::string event_name, uint32_t duration) { @@ -244,7 +239,7 @@ luabind::scope lua_register_expedition() { .def("AddReplayLockout", (void(Lua_Expedition::*)(uint32_t))&Lua_Expedition::AddReplayLockout) .def("AddReplayLockoutDuration", (void(Lua_Expedition::*)(int))&Lua_Expedition::AddReplayLockoutDuration) .def("AddReplayLockoutDuration", (void(Lua_Expedition::*)(int, bool))&Lua_Expedition::AddReplayLockoutDuration) - .def("GetDynamicZoneID", &Lua_Expedition::GetDynamicZoneID) + .def("GetDynamicZoneID", &Lua_Expedition::GetID) .def("GetID", (uint32_t(Lua_Expedition::*)(void))&Lua_Expedition::GetID) .def("GetInstanceID", (int(Lua_Expedition::*)(void))&Lua_Expedition::GetInstanceID) .def("GetLeaderName", (std::string(Lua_Expedition::*)(void))&Lua_Expedition::GetLeaderName) @@ -282,12 +277,12 @@ luabind::scope lua_register_expedition() { } luabind::scope lua_register_expedition_lock_messages() { - return luabind::class_("ExpeditionLockMessage") + return luabind::class_("ExpeditionLockMessage") .enum_("constants") [( - luabind::value("None", static_cast(ExpeditionLockMessage::None)), - luabind::value("Close", static_cast(ExpeditionLockMessage::Close)), - luabind::value("Begin", static_cast(ExpeditionLockMessage::Begin)) + luabind::value("None", static_cast(DzLockMsg::None)), + luabind::value("Close", static_cast(DzLockMsg::Close)), + luabind::value("Begin", static_cast(DzLockMsg::Begin)) )]; } diff --git a/zone/lua_expedition.h b/zone/lua_expedition.h index a338e3272..f11e3d6d6 100644 --- a/zone/lua_expedition.h +++ b/zone/lua_expedition.h @@ -26,7 +26,7 @@ #include "../common/types.h" #include -class Expedition; +class DynamicZone; class Lua_Client; struct lua_State; @@ -41,16 +41,16 @@ namespace luabind { luabind::scope lua_register_expedition(); luabind::scope lua_register_expedition_lock_messages(); -class Lua_Expedition : public Lua_Ptr +class Lua_Expedition : public Lua_Ptr { - typedef Expedition NativeType; + typedef DynamicZone NativeType; public: Lua_Expedition() : Lua_Ptr(nullptr) { } - Lua_Expedition(Expedition *d) : Lua_Ptr(d) { } + Lua_Expedition(DynamicZone* d) : Lua_Ptr(d) { } virtual ~Lua_Expedition() { } - operator Expedition*() { - return reinterpret_cast(GetLuaPtrData()); + operator DynamicZone*() { + return reinterpret_cast(GetLuaPtrData()); } void AddLockout(std::string event_name, uint32_t seconds); @@ -59,7 +59,6 @@ public: void AddReplayLockout(uint32_t seconds); void AddReplayLockoutDuration(int seconds); void AddReplayLockoutDuration(int seconds, bool members_only); - uint32_t GetDynamicZoneID(); uint32_t GetID(); int GetInstanceID(); std::string GetLeaderName(); diff --git a/zone/lua_general.cpp b/zone/lua_general.cpp index 6567d2476..1873aec0b 100644 --- a/zone/lua_general.cpp +++ b/zone/lua_general.cpp @@ -18,16 +18,18 @@ #include "lua_entity_list.h" #include "lua_expedition.h" #include "lua_spell.h" +#include "lua_zone.h" #include "quest_parser_collection.h" #include "questmgr.h" #include "qglobals.h" #include "encounter.h" #include "lua_encounter.h" -#include "data_bucket.h" -#include "expedition.h" +#include "../common/data_bucket.h" #include "dialogue_window.h" +#include "dynamic_zone.h" #include "../common/events/player_event_logs.h" #include "worldserver.h" +#include "zone.h" struct Events { }; struct Factions { }; @@ -466,7 +468,7 @@ void lua_set_sky(int sky) { } void lua_set_guild(int guild_id, int rank) { - quest_manager.setguild(guild_id, rank); + quest_manager.SetGuild(guild_id, rank); } void lua_create_guild(const char *name, const char *leader) { @@ -720,6 +722,14 @@ void lua_fail_task(int task_id) { quest_manager.failtask(task_id); } +bool lua_complete_task(int task_id) { + return quest_manager.completetask(task_id); +} + +bool lua_uncomplete_task(int task_id) { + return quest_manager.uncompletetask(task_id); +} + int lua_task_time_left(int task_id) { return quest_manager.tasktimeleft(task_id); } @@ -1092,8 +1102,8 @@ int lua_faction_value() { return quest_manager.FactionValue(); } -void lua_check_title(uint32 title_set) { - quest_manager.checktitle(title_set); +bool lua_check_title(uint32 title_set) { + return quest_manager.checktitle(title_set); } void lua_enable_title(uint32 title_set) { @@ -1599,263 +1609,263 @@ void lua_update_zone_header(std::string type, std::string value) { */ bool lua_is_classic_enabled() { - return content_service.IsClassicEnabled(); + return WorldContentService::Instance()->IsClassicEnabled(); } bool lua_is_the_ruins_of_kunark_enabled() { - return content_service.IsTheRuinsOfKunarkEnabled(); + return WorldContentService::Instance()->IsTheRuinsOfKunarkEnabled(); } bool lua_is_the_scars_of_velious_enabled() { - return content_service.IsTheScarsOfVeliousEnabled(); + return WorldContentService::Instance()->IsTheScarsOfVeliousEnabled(); } bool lua_is_the_shadows_of_luclin_enabled() { - return content_service.IsTheShadowsOfLuclinEnabled(); + return WorldContentService::Instance()->IsTheShadowsOfLuclinEnabled(); } bool lua_is_the_planes_of_power_enabled() { - return content_service.IsThePlanesOfPowerEnabled(); + return WorldContentService::Instance()->IsThePlanesOfPowerEnabled(); } bool lua_is_the_legacy_of_ykesha_enabled() { - return content_service.IsTheLegacyOfYkeshaEnabled(); + return WorldContentService::Instance()->IsTheLegacyOfYkeshaEnabled(); } bool lua_is_lost_dungeons_of_norrath_enabled() { - return content_service.IsLostDungeonsOfNorrathEnabled(); + return WorldContentService::Instance()->IsLostDungeonsOfNorrathEnabled(); } bool lua_is_gates_of_discord_enabled() { - return content_service.IsGatesOfDiscordEnabled(); + return WorldContentService::Instance()->IsGatesOfDiscordEnabled(); } bool lua_is_omens_of_war_enabled() { - return content_service.IsOmensOfWarEnabled(); + return WorldContentService::Instance()->IsOmensOfWarEnabled(); } bool lua_is_dragons_of_norrath_enabled() { - return content_service.IsDragonsOfNorrathEnabled(); + return WorldContentService::Instance()->IsDragonsOfNorrathEnabled(); } bool lua_is_depths_of_darkhollow_enabled() { - return content_service.IsDepthsOfDarkhollowEnabled(); + return WorldContentService::Instance()->IsDepthsOfDarkhollowEnabled(); } bool lua_is_prophecy_of_ro_enabled() { - return content_service.IsProphecyOfRoEnabled(); + return WorldContentService::Instance()->IsProphecyOfRoEnabled(); } bool lua_is_the_serpents_spine_enabled() { - return content_service.IsTheSerpentsSpineEnabled(); + return WorldContentService::Instance()->IsTheSerpentsSpineEnabled(); } bool lua_is_the_buried_sea_enabled() { - return content_service.IsTheBuriedSeaEnabled(); + return WorldContentService::Instance()->IsTheBuriedSeaEnabled(); } bool lua_is_secrets_of_faydwer_enabled() { - return content_service.IsSecretsOfFaydwerEnabled(); + return WorldContentService::Instance()->IsSecretsOfFaydwerEnabled(); } bool lua_is_seeds_of_destruction_enabled() { - return content_service.IsSeedsOfDestructionEnabled(); + return WorldContentService::Instance()->IsSeedsOfDestructionEnabled(); } bool lua_is_underfoot_enabled() { - return content_service.IsUnderfootEnabled(); + return WorldContentService::Instance()->IsUnderfootEnabled(); } bool lua_is_house_of_thule_enabled() { - return content_service.IsHouseOfThuleEnabled(); + return WorldContentService::Instance()->IsHouseOfThuleEnabled(); } bool lua_is_veil_of_alaris_enabled() { - return content_service.IsVeilOfAlarisEnabled(); + return WorldContentService::Instance()->IsVeilOfAlarisEnabled(); } bool lua_is_rain_of_fear_enabled() { - return content_service.IsRainOfFearEnabled(); + return WorldContentService::Instance()->IsRainOfFearEnabled(); } bool lua_is_call_of_the_forsaken_enabled() { - return content_service.IsCallOfTheForsakenEnabled(); + return WorldContentService::Instance()->IsCallOfTheForsakenEnabled(); } bool lua_is_the_darkened_sea_enabled() { - return content_service.IsTheDarkenedSeaEnabled(); + return WorldContentService::Instance()->IsTheDarkenedSeaEnabled(); } bool lua_is_the_broken_mirror_enabled() { - return content_service.IsTheBrokenMirrorEnabled(); + return WorldContentService::Instance()->IsTheBrokenMirrorEnabled(); } bool lua_is_empires_of_kunark_enabled() { - return content_service.IsEmpiresOfKunarkEnabled(); + return WorldContentService::Instance()->IsEmpiresOfKunarkEnabled(); } bool lua_is_ring_of_scale_enabled() { - return content_service.IsRingOfScaleEnabled(); + return WorldContentService::Instance()->IsRingOfScaleEnabled(); } bool lua_is_the_burning_lands_enabled() { - return content_service.IsTheBurningLandsEnabled(); + return WorldContentService::Instance()->IsTheBurningLandsEnabled(); } bool lua_is_torment_of_velious_enabled() { - return content_service.IsTormentOfVeliousEnabled(); + return WorldContentService::Instance()->IsTormentOfVeliousEnabled(); } bool lua_is_current_expansion_classic() { - return content_service.IsCurrentExpansionClassic(); + return WorldContentService::Instance()->IsCurrentExpansionClassic(); } bool lua_is_current_expansion_the_ruins_of_kunark() { - return content_service.IsCurrentExpansionTheRuinsOfKunark(); + return WorldContentService::Instance()->IsCurrentExpansionTheRuinsOfKunark(); } bool lua_is_current_expansion_the_scars_of_velious() { - return content_service.IsCurrentExpansionTheScarsOfVelious(); + return WorldContentService::Instance()->IsCurrentExpansionTheScarsOfVelious(); } bool lua_is_current_expansion_the_shadows_of_luclin() { - return content_service.IsCurrentExpansionTheShadowsOfLuclin(); + return WorldContentService::Instance()->IsCurrentExpansionTheShadowsOfLuclin(); } bool lua_is_current_expansion_the_planes_of_power() { - return content_service.IsCurrentExpansionThePlanesOfPower(); + return WorldContentService::Instance()->IsCurrentExpansionThePlanesOfPower(); } bool lua_is_current_expansion_the_legacy_of_ykesha() { - return content_service.IsCurrentExpansionTheLegacyOfYkesha(); + return WorldContentService::Instance()->IsCurrentExpansionTheLegacyOfYkesha(); } bool lua_is_current_expansion_lost_dungeons_of_norrath() { - return content_service.IsCurrentExpansionLostDungeonsOfNorrath(); + return WorldContentService::Instance()->IsCurrentExpansionLostDungeonsOfNorrath(); } bool lua_is_current_expansion_gates_of_discord() { - return content_service.IsCurrentExpansionGatesOfDiscord(); + return WorldContentService::Instance()->IsCurrentExpansionGatesOfDiscord(); } bool lua_is_current_expansion_omens_of_war() { - return content_service.IsCurrentExpansionOmensOfWar(); + return WorldContentService::Instance()->IsCurrentExpansionOmensOfWar(); } bool lua_is_current_expansion_dragons_of_norrath() { - return content_service.IsCurrentExpansionDragonsOfNorrath(); + return WorldContentService::Instance()->IsCurrentExpansionDragonsOfNorrath(); } bool lua_is_current_expansion_depths_of_darkhollow() { - return content_service.IsCurrentExpansionDepthsOfDarkhollow(); + return WorldContentService::Instance()->IsCurrentExpansionDepthsOfDarkhollow(); } bool lua_is_current_expansion_prophecy_of_ro() { - return content_service.IsCurrentExpansionProphecyOfRo(); + return WorldContentService::Instance()->IsCurrentExpansionProphecyOfRo(); } bool lua_is_current_expansion_the_serpents_spine() { - return content_service.IsCurrentExpansionTheSerpentsSpine(); + return WorldContentService::Instance()->IsCurrentExpansionTheSerpentsSpine(); } bool lua_is_current_expansion_the_buried_sea() { - return content_service.IsCurrentExpansionTheBuriedSea(); + return WorldContentService::Instance()->IsCurrentExpansionTheBuriedSea(); } bool lua_is_current_expansion_secrets_of_faydwer() { - return content_service.IsCurrentExpansionSecretsOfFaydwer(); + return WorldContentService::Instance()->IsCurrentExpansionSecretsOfFaydwer(); } bool lua_is_current_expansion_seeds_of_destruction() { - return content_service.IsCurrentExpansionSeedsOfDestruction(); + return WorldContentService::Instance()->IsCurrentExpansionSeedsOfDestruction(); } bool lua_is_current_expansion_underfoot() { - return content_service.IsCurrentExpansionUnderfoot(); + return WorldContentService::Instance()->IsCurrentExpansionUnderfoot(); } bool lua_is_current_expansion_house_of_thule() { - return content_service.IsCurrentExpansionHouseOfThule(); + return WorldContentService::Instance()->IsCurrentExpansionHouseOfThule(); } bool lua_is_current_expansion_veil_of_alaris() { - return content_service.IsCurrentExpansionVeilOfAlaris(); + return WorldContentService::Instance()->IsCurrentExpansionVeilOfAlaris(); } bool lua_is_current_expansion_rain_of_fear() { - return content_service.IsCurrentExpansionRainOfFear(); + return WorldContentService::Instance()->IsCurrentExpansionRainOfFear(); } bool lua_is_current_expansion_call_of_the_forsaken() { - return content_service.IsCurrentExpansionCallOfTheForsaken(); + return WorldContentService::Instance()->IsCurrentExpansionCallOfTheForsaken(); } bool lua_is_current_expansion_the_darkened_sea() { - return content_service.IsCurrentExpansionTheDarkenedSea(); + return WorldContentService::Instance()->IsCurrentExpansionTheDarkenedSea(); } bool lua_is_current_expansion_the_broken_mirror() { - return content_service.IsCurrentExpansionTheBrokenMirror(); + return WorldContentService::Instance()->IsCurrentExpansionTheBrokenMirror(); } bool lua_is_current_expansion_empires_of_kunark() { - return content_service.IsCurrentExpansionEmpiresOfKunark(); + return WorldContentService::Instance()->IsCurrentExpansionEmpiresOfKunark(); } bool lua_is_current_expansion_ring_of_scale() { - return content_service.IsCurrentExpansionRingOfScale(); + return WorldContentService::Instance()->IsCurrentExpansionRingOfScale(); } bool lua_is_current_expansion_the_burning_lands() { - return content_service.IsCurrentExpansionTheBurningLands(); + return WorldContentService::Instance()->IsCurrentExpansionTheBurningLands(); } bool lua_is_current_expansion_torment_of_velious() { - return content_service.IsCurrentExpansionTormentOfVelious(); + return WorldContentService::Instance()->IsCurrentExpansionTormentOfVelious(); } bool lua_is_content_flag_enabled(std::string content_flag){ - return content_service.IsContentFlagEnabled(content_flag); + return WorldContentService::Instance()->IsContentFlagEnabled(content_flag); } void lua_set_content_flag(std::string flag_name, bool enabled){ - content_service.SetContentFlag(flag_name, enabled); + WorldContentService::Instance()->SetContentFlag(flag_name, enabled); zone->ReloadContentFlags(); } Lua_Expedition lua_get_expedition() { if (zone && zone->GetInstanceID() != 0) { - return Expedition::FindCachedExpeditionByZoneInstance(zone->GetZoneID(), zone->GetInstanceID()); + return DynamicZone::FindExpeditionByZone(zone->GetZoneID(), zone->GetInstanceID()); } return nullptr; } Lua_Expedition lua_get_expedition_by_char_id(uint32 char_id) { - return Expedition::FindCachedExpeditionByCharacterID(char_id); + return DynamicZone::FindExpeditionByCharacter(char_id); } Lua_Expedition lua_get_expedition_by_dz_id(uint32 dz_id) { - return Expedition::FindCachedExpeditionByDynamicZoneID(dz_id); + return DynamicZone::FindDynamicZoneByID(dz_id, DynamicZoneType::Expedition); } Lua_Expedition lua_get_expedition_by_zone_instance(uint32 zone_id, uint32 instance_id) { - return Expedition::FindCachedExpeditionByZoneInstance(zone_id, instance_id); + return DynamicZone::FindExpeditionByZone(zone_id, instance_id); } luabind::object lua_get_expedition_lockout_by_char_id(lua_State* L, uint32 char_id, std::string expedition_name, std::string event_name) { luabind::adl::object lua_table = luabind::newtable(L); - auto lockouts = Expedition::GetExpeditionLockoutsByCharacterID(char_id); + auto lockouts = DynamicZone::GetCharacterLockouts(char_id); - auto it = std::find_if(lockouts.begin(), lockouts.end(), [&](const ExpeditionLockoutTimer& lockout) { - return lockout.IsSameLockout(expedition_name, event_name); + auto it = std::find_if(lockouts.begin(), lockouts.end(), [&](const DzLockout& lockout) { + return lockout.IsSame(expedition_name, event_name); }); if (it != lockouts.end()) { lua_table["remaining"] = it->GetSecondsRemaining(); - lua_table["uuid"] = it->GetExpeditionUUID(); + lua_table["uuid"] = it->UUID(); } return lua_table; @@ -1864,23 +1874,23 @@ luabind::object lua_get_expedition_lockout_by_char_id(lua_State* L, uint32 char_ luabind::object lua_get_expedition_lockouts_by_char_id(lua_State* L, uint32 char_id) { luabind::adl::object lua_table = luabind::newtable(L); - auto lockouts = Expedition::GetExpeditionLockoutsByCharacterID(char_id); + auto lockouts = DynamicZone::GetCharacterLockouts(char_id); for (const auto& lockout : lockouts) { - auto lockout_table = lua_table[lockout.GetExpeditionName()]; + auto lockout_table = lua_table[lockout.DzName()]; if (luabind::type(lockout_table) != LUA_TTABLE) { lockout_table = luabind::newtable(L); } - auto event_table = lockout_table[lockout.GetEventName()]; + auto event_table = lockout_table[lockout.Event()]; if (luabind::type(event_table) != LUA_TTABLE) { event_table = luabind::newtable(L); } event_table["remaining"] = lockout.GetSecondsRemaining(); - event_table["uuid"] = lockout.GetExpeditionUUID(); + event_table["uuid"] = lockout.UUID(); } return lua_table; } @@ -1888,51 +1898,51 @@ luabind::object lua_get_expedition_lockouts_by_char_id(lua_State* L, uint32 char luabind::object lua_get_expedition_lockouts_by_char_id(lua_State* L, uint32 char_id, std::string expedition_name) { luabind::adl::object lua_table = luabind::newtable(L); - auto lockouts = Expedition::GetExpeditionLockoutsByCharacterID(char_id); + auto lockouts = DynamicZone::GetCharacterLockouts(char_id); for (const auto& lockout : lockouts) { - if (lockout.GetExpeditionName() == expedition_name) + if (lockout.DzName() == expedition_name) { - auto event_table = lua_table[lockout.GetEventName()]; + auto event_table = lua_table[lockout.Event()]; if (luabind::type(event_table) != LUA_TTABLE) { event_table = luabind::newtable(L); } event_table["remaining"] = lockout.GetSecondsRemaining(); - event_table["uuid"] = lockout.GetExpeditionUUID(); + event_table["uuid"] = lockout.UUID(); } } return lua_table; } void lua_add_expedition_lockout_all_clients(std::string expedition_name, std::string event_name, uint32 seconds) { - auto lockout = ExpeditionLockoutTimer::CreateLockout(expedition_name, event_name, seconds); - Expedition::AddLockoutClients(lockout); + auto lockout = DzLockout::Create(expedition_name, event_name, seconds); + DynamicZone::AddClientsLockout(lockout); } void lua_add_expedition_lockout_all_clients(std::string expedition_name, std::string event_name, uint32 seconds, std::string uuid) { - auto lockout = ExpeditionLockoutTimer::CreateLockout(expedition_name, event_name, seconds, uuid); - Expedition::AddLockoutClients(lockout); + auto lockout = DzLockout::Create(expedition_name, event_name, seconds, uuid); + DynamicZone::AddClientsLockout(lockout); } void lua_add_expedition_lockout_by_char_id(uint32 char_id, std::string expedition_name, std::string event_name, uint32 seconds) { - Expedition::AddLockoutByCharacterID(char_id, expedition_name, event_name, seconds); + DynamicZone::AddCharacterLockout(char_id, expedition_name, event_name, seconds); } void lua_add_expedition_lockout_by_char_id(uint32 char_id, std::string expedition_name, std::string event_name, uint32 seconds, std::string uuid) { - Expedition::AddLockoutByCharacterID(char_id, expedition_name, event_name, seconds, uuid); + DynamicZone::AddCharacterLockout(char_id, expedition_name, event_name, seconds, uuid); } void lua_remove_expedition_lockout_by_char_id(uint32 char_id, std::string expedition_name, std::string event_name) { - Expedition::RemoveLockoutsByCharacterID(char_id, expedition_name, event_name); + DynamicZone::RemoveCharacterLockouts(char_id, expedition_name, event_name); } void lua_remove_all_expedition_lockouts_by_char_id(uint32 char_id) { - Expedition::RemoveLockoutsByCharacterID(char_id); + DynamicZone::RemoveCharacterLockouts(char_id); } void lua_remove_all_expedition_lockouts_by_char_id(uint32 char_id, std::string expedition_name) { - Expedition::RemoveLockoutsByCharacterID(char_id, expedition_name); + DynamicZone::RemoveCharacterLockouts(char_id, expedition_name); } std::string lua_seconds_to_time(int duration) { @@ -4155,617 +4165,607 @@ void lua_send_player_handin_event() float lua_get_zone_safe_x(uint32 zone_id) { - return zone_store.GetZoneSafeCoordinates(zone_id).x; + return ZoneStore::Instance()->GetZoneSafeCoordinates(zone_id).x; } float lua_get_zone_safe_x(uint32 zone_id, int version) { - return zone_store.GetZoneSafeCoordinates(zone_id, version).x; + return ZoneStore::Instance()->GetZoneSafeCoordinates(zone_id, version).x; } float lua_get_zone_safe_y(uint32 zone_id) { - return zone_store.GetZoneSafeCoordinates(zone_id).y; + return ZoneStore::Instance()->GetZoneSafeCoordinates(zone_id).y; } float lua_get_zone_safe_y(uint32 zone_id, int version) { - return zone_store.GetZoneSafeCoordinates(zone_id, version).y; + return ZoneStore::Instance()->GetZoneSafeCoordinates(zone_id, version).y; } float lua_get_zone_safe_z(uint32 zone_id) { - return zone_store.GetZoneSafeCoordinates(zone_id).z; + return ZoneStore::Instance()->GetZoneSafeCoordinates(zone_id).z; } float lua_get_zone_safe_z(uint32 zone_id, int version) { - return zone_store.GetZoneSafeCoordinates(zone_id, version).z; + return ZoneStore::Instance()->GetZoneSafeCoordinates(zone_id, version).z; } float lua_get_zone_safe_heading(uint32 zone_id) { - return zone_store.GetZoneSafeCoordinates(zone_id).w; + return ZoneStore::Instance()->GetZoneSafeCoordinates(zone_id).w; } float lua_get_zone_safe_heading(uint32 zone_id, int version) { - return zone_store.GetZoneSafeCoordinates(zone_id, version).w; + return ZoneStore::Instance()->GetZoneSafeCoordinates(zone_id, version).w; } float lua_get_zone_graveyard_id(uint32 zone_id) { - return zone_store.GetZoneGraveyardID(zone_id); + return ZoneStore::Instance()->GetZoneGraveyardID(zone_id); } float lua_get_zone_graveyard_id(uint32 zone_id, int version) { - return zone_store.GetZoneGraveyardID(zone_id, version); + return ZoneStore::Instance()->GetZoneGraveyardID(zone_id, version); } uint8 lua_get_zone_minimum_level(uint32 zone_id) { - return zone_store.GetZoneMinimumLevel(zone_id); + return ZoneStore::Instance()->GetZoneMinimumLevel(zone_id); } uint8 lua_get_zone_minimum_level(uint32 zone_id, int version) { - return zone_store.GetZoneMinimumLevel(zone_id, version); + return ZoneStore::Instance()->GetZoneMinimumLevel(zone_id, version); } uint8 lua_get_zone_maximum_level(uint32 zone_id) { - return zone_store.GetZoneMaximumLevel(zone_id); + return ZoneStore::Instance()->GetZoneMaximumLevel(zone_id); } uint8 lua_get_zone_maximum_level(uint32 zone_id, int version) { - return zone_store.GetZoneMaximumLevel(zone_id, version); + return ZoneStore::Instance()->GetZoneMaximumLevel(zone_id, version); } uint8 lua_get_zone_minimum_status(uint32 zone_id) { - return zone_store.GetZoneMinimumStatus(zone_id); + return ZoneStore::Instance()->GetZoneMinimumStatus(zone_id); } uint8 lua_get_zone_minimum_status(uint32 zone_id, int version) { - return zone_store.GetZoneMinimumStatus(zone_id, version); + return ZoneStore::Instance()->GetZoneMinimumStatus(zone_id, version); } int lua_get_zone_time_zone(uint32 zone_id) { - return zone_store.GetZoneTimeZone(zone_id); + return ZoneStore::Instance()->GetZoneTimeZone(zone_id); } int lua_get_zone_time_zone(uint32 zone_id, int version) { - return zone_store.GetZoneTimeZone(zone_id, version); + return ZoneStore::Instance()->GetZoneTimeZone(zone_id, version); } int lua_get_zone_maximum_players(uint32 zone_id) { - return zone_store.GetZoneMaximumPlayers(zone_id); + return ZoneStore::Instance()->GetZoneMaximumPlayers(zone_id); } int lua_get_zone_maximum_players(uint32 zone_id, int version) { - return zone_store.GetZoneMaximumPlayers(zone_id, version); + return ZoneStore::Instance()->GetZoneMaximumPlayers(zone_id, version); } uint32 lua_get_zone_rule_set(uint32 zone_id) { - return zone_store.GetZoneRuleSet(zone_id); + return ZoneStore::Instance()->GetZoneRuleSet(zone_id); } uint32 lua_get_zone_rule_set(uint32 zone_id, int version) { - return zone_store.GetZoneRuleSet(zone_id, version); + return ZoneStore::Instance()->GetZoneRuleSet(zone_id, version); } std::string lua_get_zone_note(uint32 zone_id) { - return zone_store.GetZoneNote(zone_id); + return ZoneStore::Instance()->GetZoneNote(zone_id); } std::string lua_get_zone_note(uint32 zone_id, int version) { - return zone_store.GetZoneNote(zone_id, version); + return ZoneStore::Instance()->GetZoneNote(zone_id, version); } float lua_get_zone_underworld(uint32 zone_id) { - return zone_store.GetZoneUnderworld(zone_id); + return ZoneStore::Instance()->GetZoneUnderworld(zone_id); } float lua_get_zone_underworld(uint32 zone_id, int version) { - return zone_store.GetZoneUnderworld(zone_id, version); + return ZoneStore::Instance()->GetZoneUnderworld(zone_id, version); } float lua_get_zone_minimum_clip(uint32 zone_id) { - return zone_store.GetZoneMinimumClip(zone_id); + return ZoneStore::Instance()->GetZoneMinimumClip(zone_id); } float lua_get_zone_minimum_clip(uint32 zone_id, int version) { - return zone_store.GetZoneMinimumClip(zone_id, version); + return ZoneStore::Instance()->GetZoneMinimumClip(zone_id, version); } float lua_get_zone_maximum_clip(uint32 zone_id) { - return zone_store.GetZoneMaximumClip(zone_id); + return ZoneStore::Instance()->GetZoneMaximumClip(zone_id); } float lua_get_zone_maximum_clip(uint32 zone_id, int version) { - return zone_store.GetZoneMaximumClip(zone_id, version); + return ZoneStore::Instance()->GetZoneMaximumClip(zone_id, version); } float lua_get_zone_fog_minimum_clip(uint32 zone_id) { - return zone_store.GetZoneFogMinimumClip(zone_id); + return ZoneStore::Instance()->GetZoneFogMinimumClip(zone_id); } float lua_get_zone_fog_minimum_clip(uint32 zone_id, uint8 slot) { - return zone_store.GetZoneFogMinimumClip(zone_id, slot); + return ZoneStore::Instance()->GetZoneFogMinimumClip(zone_id, slot); } float lua_get_zone_fog_minimum_clip(uint32 zone_id, uint8 slot, int version) { - return zone_store.GetZoneFogMinimumClip(zone_id, slot); + return ZoneStore::Instance()->GetZoneFogMinimumClip(zone_id, slot); } float lua_get_zone_fog_maximum_clip(uint32 zone_id) { - return zone_store.GetZoneFogMaximumClip(zone_id); + return ZoneStore::Instance()->GetZoneFogMaximumClip(zone_id); } float lua_get_zone_fog_maximum_clip(uint32 zone_id, uint8 slot) { - return zone_store.GetZoneFogMaximumClip(zone_id, slot); + return ZoneStore::Instance()->GetZoneFogMaximumClip(zone_id, slot); } float lua_get_zone_fog_maximum_clip(uint32 zone_id, uint8 slot, int version) { - return zone_store.GetZoneFogMaximumClip(zone_id, slot, version); + return ZoneStore::Instance()->GetZoneFogMaximumClip(zone_id, slot, version); } uint8 lua_get_zone_fog_red(uint32 zone_id) { - return zone_store.GetZoneFogRed(zone_id); + return ZoneStore::Instance()->GetZoneFogRed(zone_id); } uint8 lua_get_zone_fog_red(uint32 zone_id, uint8 slot) { - return zone_store.GetZoneFogRed(zone_id, slot); + return ZoneStore::Instance()->GetZoneFogRed(zone_id, slot); } uint8 lua_get_zone_fog_red(uint32 zone_id, uint8 slot, int version) { - return zone_store.GetZoneFogRed(zone_id, slot, version); + return ZoneStore::Instance()->GetZoneFogRed(zone_id, slot, version); } uint8 lua_get_zone_fog_green(uint32 zone_id) { - return zone_store.GetZoneFogGreen(zone_id); + return ZoneStore::Instance()->GetZoneFogGreen(zone_id); } uint8 lua_get_zone_fog_green(uint32 zone_id, uint8 slot) { - return zone_store.GetZoneFogGreen(zone_id, slot); + return ZoneStore::Instance()->GetZoneFogGreen(zone_id, slot); } uint8 lua_get_zone_fog_green(uint32 zone_id, uint8 slot, int version) { - return zone_store.GetZoneFogGreen(zone_id, slot, version); + return ZoneStore::Instance()->GetZoneFogGreen(zone_id, slot, version); } uint8 lua_get_zone_fog_blue(uint32 zone_id) { - return zone_store.GetZoneFogBlue(zone_id); + return ZoneStore::Instance()->GetZoneFogBlue(zone_id); } uint8 lua_get_zone_fog_blue(uint32 zone_id, uint8 slot) { - return zone_store.GetZoneFogBlue(zone_id, slot); + return ZoneStore::Instance()->GetZoneFogBlue(zone_id, slot); } uint8 lua_get_zone_fog_blue(uint32 zone_id, uint8 slot, int version) { - return zone_store.GetZoneFogBlue(zone_id, slot, version); + return ZoneStore::Instance()->GetZoneFogBlue(zone_id, slot, version); } uint8 lua_get_zone_sky(uint32 zone_id) { - return zone_store.GetZoneSky(zone_id); + return ZoneStore::Instance()->GetZoneSky(zone_id); } uint8 lua_get_zone_sky(uint32 zone_id, int version) { - return zone_store.GetZoneSky(zone_id, version); + return ZoneStore::Instance()->GetZoneSky(zone_id, version); } uint8 lua_get_zone_ztype(uint32 zone_id) { - return zone_store.GetZoneZType(zone_id); + return ZoneStore::Instance()->GetZoneZType(zone_id); } uint8 lua_get_zone_ztype(uint32 zone_id, int version) { - return zone_store.GetZoneZType(zone_id, version); + return ZoneStore::Instance()->GetZoneZType(zone_id, version); } float lua_get_zone_experience_multiplier(uint32 zone_id) { - return zone_store.GetZoneExperienceMultiplier(zone_id); + return ZoneStore::Instance()->GetZoneExperienceMultiplier(zone_id); } float lua_get_zone_experience_multiplier(uint32 zone_id, int version) { - return zone_store.GetZoneExperienceMultiplier(zone_id, version); + return ZoneStore::Instance()->GetZoneExperienceMultiplier(zone_id, version); } float lua_get_zone_walk_speed(uint32 zone_id) { - return zone_store.GetZoneWalkSpeed(zone_id); + return ZoneStore::Instance()->GetZoneWalkSpeed(zone_id); } float lua_get_zone_walk_speed(uint32 zone_id, int version) { - return zone_store.GetZoneWalkSpeed(zone_id, version); + return ZoneStore::Instance()->GetZoneWalkSpeed(zone_id, version); } uint8 lua_get_zone_time_type(uint32 zone_id) { - return zone_store.GetZoneTimeType(zone_id); + return ZoneStore::Instance()->GetZoneTimeType(zone_id); } uint8 lua_get_zone_time_type(uint32 zone_id, int version) { - return zone_store.GetZoneTimeType(zone_id, version); + return ZoneStore::Instance()->GetZoneTimeType(zone_id, version); } float lua_get_zone_fog_density(uint32 zone_id) { - return zone_store.GetZoneFogDensity(zone_id); + return ZoneStore::Instance()->GetZoneFogDensity(zone_id); } float lua_get_zone_fog_density(uint32 zone_id, int version) { - return zone_store.GetZoneFogDensity(zone_id, version); + return ZoneStore::Instance()->GetZoneFogDensity(zone_id, version); } std::string lua_get_zone_flag_needed(uint32 zone_id) { - return zone_store.GetZoneFlagNeeded(zone_id); + return ZoneStore::Instance()->GetZoneFlagNeeded(zone_id); } std::string lua_get_zone_flag_needed(uint32 zone_id, int version) { - return zone_store.GetZoneFlagNeeded(zone_id, version); + return ZoneStore::Instance()->GetZoneFlagNeeded(zone_id, version); } int8 lua_get_zone_can_bind(uint32 zone_id) { - return zone_store.GetZoneCanBind(zone_id); + return ZoneStore::Instance()->GetZoneCanBind(zone_id); } int8 lua_get_zone_can_bind(uint32 zone_id, int version) { - return zone_store.GetZoneCanBind(zone_id, version); + return ZoneStore::Instance()->GetZoneCanBind(zone_id, version); } int8 lua_get_zone_can_combat(uint32 zone_id) { - return zone_store.GetZoneCanCombat(zone_id); + return ZoneStore::Instance()->GetZoneCanCombat(zone_id); } int8 lua_get_zone_can_combat(uint32 zone_id, int version) { - return zone_store.GetZoneCanCombat(zone_id, version); + return ZoneStore::Instance()->GetZoneCanCombat(zone_id, version); } int8 lua_get_zone_can_levitate(uint32 zone_id) { - return zone_store.GetZoneCanLevitate(zone_id); + return ZoneStore::Instance()->GetZoneCanLevitate(zone_id); } int8 lua_get_zone_can_levitate(uint32 zone_id, int version) { - return zone_store.GetZoneCanLevitate(zone_id, version); + return ZoneStore::Instance()->GetZoneCanLevitate(zone_id, version); } int8 lua_get_zone_cast_outdoor(uint32 zone_id) { - return zone_store.GetZoneCastOutdoor(zone_id); + return ZoneStore::Instance()->GetZoneCastOutdoor(zone_id); } int8 lua_get_zone_cast_outdoor(uint32 zone_id, int version) { - return zone_store.GetZoneCastOutdoor(zone_id, version); + return ZoneStore::Instance()->GetZoneCastOutdoor(zone_id, version); } uint8 lua_get_zone_hotzone(uint32 zone_id) { - return zone_store.GetZoneHotzone(zone_id); + return ZoneStore::Instance()->GetZoneHotzone(zone_id); } uint8 lua_get_zone_hotzone(uint32 zone_id, int version) { - return zone_store.GetZoneHotzone(zone_id, version); + return ZoneStore::Instance()->GetZoneHotzone(zone_id, version); } uint8 lua_get_zone_instance_type(uint32 zone_id) { - return zone_store.GetZoneInstanceType(zone_id); + return ZoneStore::Instance()->GetZoneInstanceType(zone_id); } uint8 lua_get_zone_instance_type(uint32 zone_id, int version) { - return zone_store.GetZoneInstanceType(zone_id, version); + return ZoneStore::Instance()->GetZoneInstanceType(zone_id, version); } uint64 lua_get_zone_shutdown_delay(uint32 zone_id) { - return zone_store.GetZoneShutdownDelay(zone_id); + return ZoneStore::Instance()->GetZoneShutdownDelay(zone_id); } uint64 lua_get_zone_shutdown_delay(uint32 zone_id, int version) { - return zone_store.GetZoneShutdownDelay(zone_id, version); + return ZoneStore::Instance()->GetZoneShutdownDelay(zone_id, version); } int8 lua_get_zone_peqzone(uint32 zone_id) { - return zone_store.GetZonePEQZone(zone_id); + return ZoneStore::Instance()->GetZonePEQZone(zone_id); } int8 lua_get_zone_peqzone(uint32 zone_id, int version) { - return zone_store.GetZonePEQZone(zone_id, version); + return ZoneStore::Instance()->GetZonePEQZone(zone_id, version); } int8 lua_get_zone_expansion(uint32 zone_id) { - return zone_store.GetZoneExpansion(zone_id); + return ZoneStore::Instance()->GetZoneExpansion(zone_id); } int8 lua_get_zone_expansion(uint32 zone_id, int version) { - return zone_store.GetZoneExpansion(zone_id, version); + return ZoneStore::Instance()->GetZoneExpansion(zone_id, version); } int8 lua_get_zone_bypass_expansion_check(uint32 zone_id) { - return zone_store.GetZoneBypassExpansionCheck(zone_id); + return ZoneStore::Instance()->GetZoneBypassExpansionCheck(zone_id); } int8 lua_get_zone_bypass_expansion_check(uint32 zone_id, int version) { - return zone_store.GetZoneBypassExpansionCheck(zone_id, version); + return ZoneStore::Instance()->GetZoneBypassExpansionCheck(zone_id, version); } int8 lua_get_zone_suspend_buffs(uint32 zone_id) { - return zone_store.GetZoneSuspendBuffs(zone_id); + return ZoneStore::Instance()->GetZoneSuspendBuffs(zone_id); } int8 lua_get_zone_suspend_buffs(uint32 zone_id, int version) { - return zone_store.GetZoneSuspendBuffs(zone_id, version); + return ZoneStore::Instance()->GetZoneSuspendBuffs(zone_id, version); } int lua_get_zone_rain_chance(uint32 zone_id) { - return zone_store.GetZoneRainChance(zone_id); + return ZoneStore::Instance()->GetZoneRainChance(zone_id); } int lua_get_zone_rain_chance(uint32 zone_id, int version) { - return zone_store.GetZoneRainChance(zone_id, version); + return ZoneStore::Instance()->GetZoneRainChance(zone_id, version); } int lua_get_zone_rain_duration(uint32 zone_id) { - return zone_store.GetZoneRainDuration(zone_id); + return ZoneStore::Instance()->GetZoneRainDuration(zone_id); } int lua_get_zone_rain_duration(uint32 zone_id, int version) { - return zone_store.GetZoneRainDuration(zone_id, version); + return ZoneStore::Instance()->GetZoneRainDuration(zone_id, version); } int lua_get_zone_snow_chance(uint32 zone_id) { - return zone_store.GetZoneSnowChance(zone_id); + return ZoneStore::Instance()->GetZoneSnowChance(zone_id); } int lua_get_zone_snow_chance(uint32 zone_id, int version) { - return zone_store.GetZoneSnowChance(zone_id, version); + return ZoneStore::Instance()->GetZoneSnowChance(zone_id, version); } int lua_get_zone_snow_duration(uint32 zone_id) { - return zone_store.GetZoneSnowDuration(zone_id); + return ZoneStore::Instance()->GetZoneSnowDuration(zone_id); } int lua_get_zone_snow_duration(uint32 zone_id, int version) { - return zone_store.GetZoneSnowDuration(zone_id, version); + return ZoneStore::Instance()->GetZoneSnowDuration(zone_id, version); } float lua_get_zone_gravity(uint32 zone_id) { - return zone_store.GetZoneGravity(zone_id); + return ZoneStore::Instance()->GetZoneGravity(zone_id); } float lua_get_zone_gravity(uint32 zone_id, int version) { - return zone_store.GetZoneGravity(zone_id, version); + return ZoneStore::Instance()->GetZoneGravity(zone_id, version); } int lua_get_zone_type(uint32 zone_id) { - return zone_store.GetZoneType(zone_id); + return ZoneStore::Instance()->GetZoneType(zone_id); } int lua_get_zone_type(uint32 zone_id, int version) { - return zone_store.GetZoneType(zone_id, version); + return ZoneStore::Instance()->GetZoneType(zone_id, version); } int lua_get_zone_sky_lock(uint32 zone_id) { - return zone_store.GetZoneSkyLock(zone_id); + return ZoneStore::Instance()->GetZoneSkyLock(zone_id); } int lua_get_zone_sky_lock(uint32 zone_id, int version) { - return zone_store.GetZoneSkyLock(zone_id, version); + return ZoneStore::Instance()->GetZoneSkyLock(zone_id, version); } int lua_get_zone_fast_regen_hp(uint32 zone_id) { - return zone_store.GetZoneFastRegenHP(zone_id); + return ZoneStore::Instance()->GetZoneFastRegenHP(zone_id); } int lua_get_zone_fast_regen_hp(uint32 zone_id, int version) { - return zone_store.GetZoneFastRegenHP(zone_id, version); + return ZoneStore::Instance()->GetZoneFastRegenHP(zone_id, version); } int lua_get_zone_fast_regen_mana(uint32 zone_id) { - return zone_store.GetZoneFastRegenMana(zone_id); + return ZoneStore::Instance()->GetZoneFastRegenMana(zone_id); } int lua_get_zone_fast_regen_mana(uint32 zone_id, int version) { - return zone_store.GetZoneFastRegenMana(zone_id, version); + return ZoneStore::Instance()->GetZoneFastRegenMana(zone_id, version); } int lua_get_zone_fast_regen_endurance(uint32 zone_id) { - return zone_store.GetZoneFastRegenEndurance(zone_id); + return ZoneStore::Instance()->GetZoneFastRegenEndurance(zone_id); } int lua_get_zone_fast_regen_endurance(uint32 zone_id, int version) { - return zone_store.GetZoneFastRegenEndurance(zone_id, version); + return ZoneStore::Instance()->GetZoneFastRegenEndurance(zone_id, version); } int lua_get_zone_npc_maximum_aggro_distance(uint32 zone_id) { - return zone_store.GetZoneNPCMaximumAggroDistance(zone_id); + return ZoneStore::Instance()->GetZoneNPCMaximumAggroDistance(zone_id); } int lua_get_zone_npc_maximum_aggro_distance(uint32 zone_id, int version) { - return zone_store.GetZoneNPCMaximumAggroDistance(zone_id, version); -} - -uint32 lua_get_zone_maximum_movement_update_range(uint32 zone_id) -{ - return zone_store.GetZoneMaximumMovementUpdateRange(zone_id); -} - -uint32 lua_get_zone_maximum_movement_update_range(uint32 zone_id, int version) -{ - return zone_store.GetZoneMaximumMovementUpdateRange(zone_id, version); + return ZoneStore::Instance()->GetZoneNPCMaximumAggroDistance(zone_id, version); } int8 lua_get_zone_minimum_expansion(uint32 zone_id) { - return zone_store.GetZoneMinimumExpansion(zone_id); + return ZoneStore::Instance()->GetZoneMinimumExpansion(zone_id); } int8 lua_get_zone_minimum_expansion(uint32 zone_id, int version) { - return zone_store.GetZoneMinimumExpansion(zone_id, version); + return ZoneStore::Instance()->GetZoneMinimumExpansion(zone_id, version); } int8 lua_get_zone_maximum_expansion(uint32 zone_id) { - return zone_store.GetZoneMaximumExpansion(zone_id); + return ZoneStore::Instance()->GetZoneMaximumExpansion(zone_id); } int8 lua_get_zone_maximum_expansion(uint32 zone_id, int version) { - return zone_store.GetZoneMaximumExpansion(zone_id, version); + return ZoneStore::Instance()->GetZoneMaximumExpansion(zone_id, version); } std::string lua_get_zone_content_flags(uint32 zone_id) { - return zone_store.GetZoneContentFlags(zone_id); + return ZoneStore::Instance()->GetZoneContentFlags(zone_id); } std::string lua_get_zone_content_flags(uint32 zone_id, int version) { - return zone_store.GetZoneContentFlags(zone_id, version); + return ZoneStore::Instance()->GetZoneContentFlags(zone_id, version); } std::string lua_get_zone_content_flags_disabled(uint32 zone_id) { - return zone_store.GetZoneContentFlagsDisabled(zone_id); + return ZoneStore::Instance()->GetZoneContentFlagsDisabled(zone_id); } std::string lua_get_zone_content_flags_disabled(uint32 zone_id, int version) { - return zone_store.GetZoneContentFlagsDisabled(zone_id, version); + return ZoneStore::Instance()->GetZoneContentFlagsDisabled(zone_id, version); } int lua_get_zone_underworld_teleport_index(uint32 zone_id) { - return zone_store.GetZoneUnderworldTeleportIndex(zone_id); + return ZoneStore::Instance()->GetZoneUnderworldTeleportIndex(zone_id); } int lua_get_zone_underworld_teleport_index(uint32 zone_id, int version) { - return zone_store.GetZoneUnderworldTeleportIndex(zone_id, version); + return ZoneStore::Instance()->GetZoneUnderworldTeleportIndex(zone_id, version); } int lua_get_zone_lava_damage(uint32 zone_id) { - return zone_store.GetZoneLavaDamage(zone_id); + return ZoneStore::Instance()->GetZoneLavaDamage(zone_id); } int lua_get_zone_lava_damage(uint32 zone_id, int version) { - return zone_store.GetZoneLavaDamage(zone_id, version); + return ZoneStore::Instance()->GetZoneLavaDamage(zone_id, version); } int lua_get_zone_minimum_lava_damage(uint32 zone_id) { - return zone_store.GetZoneMinimumLavaDamage(zone_id); + return ZoneStore::Instance()->GetZoneMinimumLavaDamage(zone_id); } int lua_get_zone_minimum_lava_damage(uint32 zone_id, int version) { - return zone_store.GetZoneMinimumLavaDamage(zone_id, version); + return ZoneStore::Instance()->GetZoneMinimumLavaDamage(zone_id, version); } uint8 lua_get_zone_idle_when_empty(uint32 zone_id) { - return zone_store.GetZoneIdleWhenEmpty(zone_id); + return ZoneStore::Instance()->GetZoneIdleWhenEmpty(zone_id); } uint8 lua_get_zone_idle_when_empty(uint32 zone_id, int version) { - return zone_store.GetZoneIdleWhenEmpty(zone_id, version); + return ZoneStore::Instance()->GetZoneIdleWhenEmpty(zone_id, version); } uint32 lua_get_zone_seconds_before_idle(uint32 zone_id) { - return zone_store.GetZoneSecondsBeforeIdle(zone_id); + return ZoneStore::Instance()->GetZoneSecondsBeforeIdle(zone_id); } uint32 lua_get_zone_seconds_before_idle(uint32 zone_id, int version) { - return zone_store.GetZoneSecondsBeforeIdle(zone_id, version); + return ZoneStore::Instance()->GetZoneSecondsBeforeIdle(zone_id, version); } void lua_send_channel_message(uint8 channel_number, uint32 guild_id, uint8 language_id, uint8 language_skill, const char* message) @@ -5505,11 +5505,11 @@ bool lua_set_auto_login_character_name_by_account_id(uint32 account_id, std::str } uint32 lua_get_zone_id_by_long_name(std::string zone_long_name) { - return zone_store.GetZoneIDByLongName(zone_long_name); + return ZoneStore::Instance()->GetZoneIDByLongName(zone_long_name); } std::string lua_get_zone_short_name_by_long_name(std::string zone_long_name) { - return zone_store.GetZoneShortNameByLongName(zone_long_name); + return ZoneStore::Instance()->GetZoneShortNameByLongName(zone_long_name); } bool lua_send_parcel(luabind::object lua_table) @@ -5645,6 +5645,72 @@ void lua_spawn_grid(uint32 npc_id, float x, float y, float z, float heading, flo quest_manager.SpawnGrid(npc_id, glm::vec4(x, y, z, heading), spacing, spawn_count); } +Lua_Zone lua_get_zone() +{ + return Lua_Zone(zone); +} + +bool lua_handin(luabind::adl::object handin_table) +{ + std::map handin_map; + + for (luabind::iterator i(handin_table), end; i != end; i++) { + std::string key; + if (luabind::type(i.key()) == LUA_TSTRING) { + key = luabind::object_cast(i.key()); + } + else if (luabind::type(i.key()) == LUA_TNUMBER) { + key = fmt::format("{}", luabind::object_cast(i.key())); + } + else { + LogError("Handin key type [{}] not supported", luabind::type(i.key())); + } + + if (!key.empty()) { + handin_map[key] = luabind::object_cast(handin_table[i.key()]); + LogNpcHandinDetail("Handin key [{}] value [{}]", key, handin_map[key]); + } + } + + return quest_manager.handin(handin_map); +} + +luabind::object lua_get_paused_timers(lua_State* L, Mob* m) { + auto t = luabind::newtable(L); + auto v = quest_manager.GetPausedTimers(m); + int i = 1; + + for (const auto& e : v) { + t[i] = e; + i++; + } + + return t; +} + +luabind::object lua_get_timers(lua_State* L, Mob* m) { + auto t = luabind::newtable(L); + auto v = quest_manager.GetTimers(m); + int i = 1; + + for (const auto& e : v) { + t[i] = e; + i++; + } + + return t; +} + +std::string lua_get_pet_command_name(uint8 pet_command) +{ + return PetCommand::GetName(pet_command); +} + +std::string lua_get_pet_type_name(uint8 pet_type) +{ + return PetType::GetName(pet_type); +} + #define LuaCreateNPCParse(name, c_type, default_value) do { \ cur = table[#name]; \ if(luabind::type(cur) != LUA_TNIL) { \ @@ -5948,6 +6014,8 @@ luabind::scope lua_register_general() { luabind::def("reset_task_activity", &lua_reset_task_activity), luabind::def("assign_task", &lua_assign_task), luabind::def("fail_task", &lua_fail_task), + luabind::def("complete_task", &lua_complete_task), + luabind::def("uncomplete_task", &lua_uncomplete_task), luabind::def("task_time_left", &lua_task_time_left), luabind::def("is_task_completed", &lua_is_task_completed), luabind::def("enabled_task_count", &lua_enabled_task_count), @@ -6287,8 +6355,6 @@ luabind::scope lua_register_general() { luabind::def("get_zone_fast_regen_endurance", (int(*)(uint32,int))&lua_get_zone_fast_regen_endurance), luabind::def("get_zone_npc_maximum_aggro_distance", (int(*)(uint32))&lua_get_zone_npc_maximum_aggro_distance), luabind::def("get_zone_npc_maximum_aggro_distance", (int(*)(uint32,int))&lua_get_zone_npc_maximum_aggro_distance), - luabind::def("get_zone_npc_maximum_movement_update_range", (uint32(*)(uint32))&lua_get_zone_maximum_movement_update_range), - luabind::def("get_zone_npc_maximum_movement_update_range", (uint32(*)(uint32,int))&lua_get_zone_maximum_movement_update_range), luabind::def("get_zone_minimum_expansion", (int8(*)(uint32))&lua_get_zone_minimum_expansion), luabind::def("get_zone_minimum_expansion", (int8(*)(uint32,int))&lua_get_zone_minimum_expansion), luabind::def("get_zone_maximum_expansion", (int8(*)(uint32))&lua_get_zone_maximum_expansion), @@ -6454,6 +6520,12 @@ luabind::scope lua_register_general() { luabind::def("are_tasks_completed", &lua_are_tasks_completed), luabind::def("spawn_circle", &lua_spawn_circle), luabind::def("spawn_grid", &lua_spawn_grid), + luabind::def("get_zone", &lua_get_zone), + luabind::def("handin", &lua_handin), + luabind::def("get_paused_timers", &lua_get_paused_timers), + luabind::def("get_timers", &lua_get_timers), + luabind::def("get_pet_command_name", &lua_get_pet_command_name), + luabind::def("get_pet_type_name", &lua_get_pet_type_name), /* Cross Zone */ @@ -6919,7 +6991,10 @@ luabind::scope lua_register_events() { luabind::value("entity_variable_set", static_cast(EVENT_ENTITY_VARIABLE_SET)), luabind::value("entity_variable_update", static_cast(EVENT_ENTITY_VARIABLE_UPDATE)), luabind::value("aa_loss", static_cast(EVENT_AA_LOSS)), - luabind::value("read", static_cast(EVENT_READ_ITEM)) + luabind::value("read", static_cast(EVENT_READ_ITEM)), + luabind::value("pet_command", static_cast(EVENT_PET_COMMAND)), + luabind::value("charm_start", static_cast(EVENT_CHARM_START)), + luabind::value("charm_end", static_cast(EVENT_CHARM_END)) )]; } @@ -7941,33 +8016,6 @@ luabind::scope lua_register_languages() { )]; } -luabind::scope lua_register_rules_const() { - return luabind::class_("Rule") - .enum_("constants") - [( -#define RULE_INT(cat, rule, default_value, notes) \ - luabind::value(#rule, RuleManager::Int__##rule), -#include "../common/ruletypes.h" - luabind::value("_IntRuleCount", RuleManager::_IntRuleCount), -#undef RULE_INT -#define RULE_REAL(cat, rule, default_value, notes) \ - luabind::value(#rule, RuleManager::Real__##rule), -#include "../common/ruletypes.h" - luabind::value("_RealRuleCount", RuleManager::_RealRuleCount), -#undef RULE_REAL -#define RULE_BOOL(cat, rule, default_value, notes) \ - luabind::value(#rule, RuleManager::Bool__##rule), -#include "../common/ruletypes.h" - luabind::value("_BoolRuleCount", RuleManager::_BoolRuleCount), -#undef RULE_BOOL -#define RULE_STRING(cat, rule, default_value, notes) \ - luabind::value(#rule, RuleManager::String__##rule), -#include "../common/ruletypes.h" - luabind::value("_StringRuleCount", RuleManager::_StringRuleCount) -#undef RULE_STRING - )]; -} - luabind::scope lua_register_rulei() { return luabind::namespace_("RuleI") [ diff --git a/zone/lua_group.cpp b/zone/lua_group.cpp index 049ecaa4a..4bb1f4c4f 100644 --- a/zone/lua_group.cpp +++ b/zone/lua_group.cpp @@ -122,16 +122,26 @@ Lua_Mob Lua_Group::GetMember(int member_index) { return self->members[member_index]; } +uint8 Lua_Group::GetMemberRole(Lua_Mob member) { + Lua_Safe_Call_Int(); + return self->GetMemberRole(member); +} + +uint8 Lua_Group::GetMemberRole(const char* name) { + Lua_Safe_Call_Int(); + return self->GetMemberRole(name); +} + bool Lua_Group::DoesAnyMemberHaveExpeditionLockout(std::string expedition_name, std::string event_name) { Lua_Safe_Call_Bool(); - return self->DoesAnyMemberHaveExpeditionLockout(expedition_name, event_name); + return self->AnyMemberHasDzLockout(expedition_name, event_name); } bool Lua_Group::DoesAnyMemberHaveExpeditionLockout(std::string expedition_name, std::string event_name, int max_check_count) { Lua_Safe_Call_Bool(); - return self->DoesAnyMemberHaveExpeditionLockout(expedition_name, event_name, max_check_count); + return self->AnyMemberHasDzLockout(expedition_name, event_name); // max_check_count deprecated } uint32 Lua_Group::GetAverageLevel() { @@ -155,6 +165,8 @@ luabind::scope lua_register_group() { .def("GetLeaderName", (const char*(Lua_Group::*)(void))&Lua_Group::GetLeaderName) .def("GetLowestLevel", (uint32(Lua_Group::*)(void))&Lua_Group::GetLowestLevel) .def("GetMember", (Lua_Mob(Lua_Group::*)(int))&Lua_Group::GetMember) + .def("GetMemberRole", (uint8(Lua_Group::*)(Lua_Mob))&Lua_Group::GetMemberRole) + .def("GetMemberRole", (uint8(Lua_Group::*)(const char*))&Lua_Group::GetMemberRole) .def("GetTotalGroupDamage", (uint32(Lua_Group::*)(Lua_Mob))&Lua_Group::GetTotalGroupDamage) .def("GroupCount", (int(Lua_Group::*)(void))&Lua_Group::GroupCount) .def("GroupMessage", (void(Lua_Group::*)(Lua_Mob,const char*))&Lua_Group::GroupMessage) diff --git a/zone/lua_group.h b/zone/lua_group.h index 949740d66..ee4e353bc 100644 --- a/zone/lua_group.h +++ b/zone/lua_group.h @@ -49,6 +49,8 @@ public: void TeleportGroup(Lua_Mob sender, uint32 zone_id, uint32 instance_id, float x, float y, float z, float h); int GetID(); Lua_Mob GetMember(int member_index); + uint8 GetMemberRole(Lua_Mob member); + uint8 GetMemberRole(const char* name); bool DoesAnyMemberHaveExpeditionLockout(std::string expedition_name, std::string event_name); bool DoesAnyMemberHaveExpeditionLockout(std::string expedition_name, std::string event_name, int max_check_count); }; diff --git a/zone/lua_inventory.cpp b/zone/lua_inventory.cpp index 48987de68..3d464a915 100644 --- a/zone/lua_inventory.cpp +++ b/zone/lua_inventory.cpp @@ -164,7 +164,7 @@ int Lua_Inventory::GetSlotByItemInst(Lua_ItemInst inst) { return self->GetSlotByItemInst(inst); } -int Lua_Inventory::CountAugmentEquippedByID(uint32 item_id) { +uint32 Lua_Inventory::CountAugmentEquippedByID(uint32 item_id) { Lua_Safe_Call_Int(); return self->CountAugmentEquippedByID(item_id); } @@ -174,7 +174,7 @@ bool Lua_Inventory::HasAugmentEquippedByID(uint32 item_id) { return self->HasAugmentEquippedByID(item_id); } -int Lua_Inventory::CountItemEquippedByID(uint32 item_id) { +uint32 Lua_Inventory::CountItemEquippedByID(uint32 item_id) { Lua_Safe_Call_Int(); return self->CountItemEquippedByID(item_id); } @@ -208,8 +208,8 @@ luabind::scope lua_register_inventory() { .def("CalcSlotId", (int(Lua_Inventory::*)(int,int))&Lua_Inventory::CalcSlotId) .def("CanItemFitInContainer", (bool(Lua_Inventory::*)(Lua_Item,Lua_Item))&Lua_Inventory::CanItemFitInContainer) .def("CheckNoDrop", (bool(Lua_Inventory::*)(int))&Lua_Inventory::CheckNoDrop) - .def("CountAugmentEquippedByID", (int(Lua_Inventory::*)(uint32))&Lua_Inventory::CountAugmentEquippedByID) - .def("CountItemEquippedByID", (int(Lua_Inventory::*)(uint32))&Lua_Inventory::CountItemEquippedByID) + .def("CountAugmentEquippedByID", (uint32(Lua_Inventory::*)(uint32))&Lua_Inventory::CountAugmentEquippedByID) + .def("CountItemEquippedByID", (uint32(Lua_Inventory::*)(uint32))&Lua_Inventory::CountItemEquippedByID) .def("DeleteItem", (bool(Lua_Inventory::*)(int))&Lua_Inventory::DeleteItem) .def("DeleteItem", (bool(Lua_Inventory::*)(int,int))&Lua_Inventory::DeleteItem) .def("FindFreeSlot", (int(Lua_Inventory::*)(bool,bool))&Lua_Inventory::FindFreeSlot) diff --git a/zone/lua_inventory.h b/zone/lua_inventory.h index 472447e62..a9c5c8d5e 100644 --- a/zone/lua_inventory.h +++ b/zone/lua_inventory.h @@ -43,8 +43,8 @@ public: bool DeleteItem(int slot_id); bool DeleteItem(int slot_id, int quantity); bool CheckNoDrop(int slot_id); - int CountAugmentEquippedByID(uint32 item_id); - int CountItemEquippedByID(uint32 item_id); + uint32 CountAugmentEquippedByID(uint32 item_id); + uint32 CountItemEquippedByID(uint32 item_id); Lua_ItemInst PopItem(int slot_id); bool HasAugmentEquippedByID(uint32 item_id); bool HasItemEquippedByID(uint32 item_id); diff --git a/zone/lua_iteminst.cpp b/zone/lua_iteminst.cpp index ad9ed1405..a911fcee0 100644 --- a/zone/lua_iteminst.cpp +++ b/zone/lua_iteminst.cpp @@ -255,11 +255,6 @@ int8 Lua_ItemInst::GetMaxEvolveLvl() { return self->GetMaxEvolveLvl(); } -uint32 Lua_ItemInst::GetKillsNeeded(uint8 current_level) { - Lua_Safe_Call_Int(); - return self->GetKillsNeeded(current_level); -} - Lua_ItemInst Lua_ItemInst::Clone() { Lua_Safe_Call_Class(Lua_ItemInst); return Lua_ItemInst(self->Clone(), true); @@ -348,6 +343,102 @@ std::string Lua_ItemInst::GetItemLink() return linker.GenerateLink(); } +void Lua_ItemInst::AddEvolveAmount(uint64 amount) +{ + Lua_Safe_Call_Void(); + self->SetEvolveAddToCurrentAmount(amount); +} + +uint32 Lua_ItemInst::GetAugmentEvolveUniqueID(uint8 slot_id) +{ + Lua_Safe_Call_Int(); + return self->GetAugmentEvolveUniqueID(slot_id); +} + +bool Lua_ItemInst::GetEvolveActivated() +{ + Lua_Safe_Call_Bool(); + return self->GetEvolveActivated(); +} + +uint64 Lua_ItemInst::GetEvolveAmount() +{ + Lua_Safe_Call_Int(); + return self->GetEvolveCurrentAmount(); +} + +uint32 Lua_ItemInst::GetEvolveCharacterID() +{ + Lua_Safe_Call_Int(); + return self->GetEvolveCharID(); +} + +bool Lua_ItemInst::GetEvolveEquipped() +{ + Lua_Safe_Call_Bool(); + return self->GetEvolveEquipped(); +} + +uint32 Lua_ItemInst::GetEvolveFinalItemID() +{ + Lua_Safe_Call_Int(); + return self->GetEvolveFinalItemID(); +} + +uint32 Lua_ItemInst::GetEvolveItemID() +{ + Lua_Safe_Call_Int(); + return self->GetEvolveItemID(); +} + +int8 Lua_ItemInst::GetEvolveLevel() +{ + Lua_Safe_Call_Int(); + return self->GetEvolveLvl(); +} + +uint32 Lua_ItemInst::GetEvolveLoreID() +{ + Lua_Safe_Call_Int(); + return self->GetEvolveLoreID(); +} + +float Lua_ItemInst::GetEvolveProgression() +{ + Lua_Safe_Call_Real(); + return self->GetEvolveProgression(); +} + +uint64 Lua_ItemInst::GetEvolveUniqueID() +{ + Lua_Safe_Call_Int(); + return self->GetEvolveUniqueID(); +} + +bool Lua_ItemInst::IsEvolving() +{ + Lua_Safe_Call_Bool(); + return self->IsEvolving(); +} + +void Lua_ItemInst::SetEvolveAmount(uint64 amount) +{ + Lua_Safe_Call_Void(); + self->SetEvolveCurrentAmount(amount); +} + +void Lua_ItemInst::SetEvolveProgression(float amount) +{ + Lua_Safe_Call_Void(); + self->SetEvolveProgression(amount); +} + +int Lua_ItemInst::GetSerialNumber() +{ + Lua_Safe_Call_Int(); + return self->GetSerialNumber(); +} + luabind::scope lua_register_iteminst() { return luabind::class_("ItemInst") .def(luabind::constructor<>()) @@ -356,12 +447,14 @@ luabind::scope lua_register_iteminst() { .property("null", &Lua_ItemInst::Null) .property("valid", &Lua_ItemInst::Valid) .def("AddExp", (void(Lua_ItemInst::*)(uint32))&Lua_ItemInst::AddExp) + .def("AddEvolveAmount", (void(Lua_ItemInst::*)(uint64))&Lua_ItemInst::AddEvolveAmount) .def("ClearTimers", (void(Lua_ItemInst::*)(void))&Lua_ItemInst::ClearTimers) .def("Clone", (Lua_ItemInst(Lua_ItemInst::*)(void))&Lua_ItemInst::Clone) .def("ContainsAugmentByID", (bool(Lua_ItemInst::*)(uint32))&Lua_ItemInst::ContainsAugmentByID) .def("CountAugmentByID", (int(Lua_ItemInst::*)(uint32))&Lua_ItemInst::CountAugmentByID) .def("DeleteCustomData", (void(Lua_ItemInst::*)(const std::string &))&Lua_ItemInst::DeleteCustomData) .def("GetAugment", (Lua_ItemInst(Lua_ItemInst::*)(int))&Lua_ItemInst::GetAugment) + .def("GetAugmentEvolveUniqueID", (uint32(Lua_ItemInst::*)(uint8))&Lua_ItemInst::GetAugmentEvolveUniqueID) .def("GetAugmentIDs", (luabind::object(Lua_ItemInst::*)(lua_State*))&Lua_ItemInst::GetAugmentIDs) .def("GetAugmentItemID", (uint32(Lua_ItemInst::*)(int))&Lua_ItemInst::GetAugmentItemID) .def("GetAugmentType", (int(Lua_ItemInst::*)(void))&Lua_ItemInst::GetAugmentType) @@ -370,15 +463,25 @@ luabind::scope lua_register_iteminst() { .def("GetCustomData", (std::string(Lua_ItemInst::*)(const std::string &))&Lua_ItemInst::GetCustomData) .def("GetCustomDataString", (std::string(Lua_ItemInst::*)(void))&Lua_ItemInst::GetCustomDataString) .def("GetExp", (uint32(Lua_ItemInst::*)(void))&Lua_ItemInst::GetExp) + .def("GetEvolveActivated", (bool(Lua_ItemInst::*)(void))&Lua_ItemInst::GetEvolveActivated) + .def("GetEvolveAmount", (uint64(Lua_ItemInst::*)(void))&Lua_ItemInst::GetEvolveAmount) + .def("GetEvolveCharacterID", (uint32(Lua_ItemInst::*)(void))&Lua_ItemInst::GetEvolveCharacterID) + .def("GetEvolveEquipped", (bool(Lua_ItemInst::*)(void))&Lua_ItemInst::GetEvolveEquipped) + .def("GetEvolveFinalItemID", (uint64(Lua_ItemInst::*)(void))&Lua_ItemInst::GetEvolveFinalItemID) + .def("GetEvolveItemID", (uint32(Lua_ItemInst::*)(void))&Lua_ItemInst::GetEvolveItemID) + .def("GetEvolveLevel", (int8(Lua_ItemInst::*)(void))&Lua_ItemInst::GetEvolveLevel) + .def("GetEvolveLoreID", (uint32(Lua_ItemInst::*)(void))&Lua_ItemInst::GetEvolveLoreID) + .def("GetEvolveProgression", (float(Lua_ItemInst::*)(void))&Lua_ItemInst::GetEvolveProgression) + .def("GetEvolveUniqueID", (uint64(Lua_ItemInst::*)(void))&Lua_ItemInst::GetEvolveUniqueID) .def("GetID", (uint32(Lua_ItemInst::*)(void))&Lua_ItemInst::GetID) .def("GetItem", (Lua_Item(Lua_ItemInst::*)(void))&Lua_ItemInst::GetItem) .def("GetItem", (Lua_ItemInst(Lua_ItemInst::*)(uint8))&Lua_ItemInst::GetItem) .def("GetItemID", (uint32(Lua_ItemInst::*)(int))&Lua_ItemInst::GetItemID) .def("GetItemLink", (std::string(Lua_ItemInst::*)(void))&Lua_ItemInst::GetItemLink) .def("GetItemScriptID", (uint32(Lua_ItemInst::*)(void))&Lua_ItemInst::GetItemScriptID) - .def("GetKillsNeeded", (uint32(Lua_ItemInst::*)(int))&Lua_ItemInst::GetKillsNeeded) .def("GetMaxEvolveLvl", (int(Lua_ItemInst::*)(void))&Lua_ItemInst::GetMaxEvolveLvl) .def("GetName", (std::string(Lua_ItemInst::*)(void))&Lua_ItemInst::GetName) + .def("GetSerialNumber", (int(Lua_ItemInst::*)(void))&Lua_ItemInst::GetSerialNumber) .def("GetPrice", (uint32(Lua_ItemInst::*)(void))&Lua_ItemInst::GetPrice) .def("GetTaskDeliveredCount", &Lua_ItemInst::GetTaskDeliveredCount) .def("GetTotalItemCount", (uint8(Lua_ItemInst::*)(void))&Lua_ItemInst::GetTotalItemCount) @@ -389,6 +492,7 @@ luabind::scope lua_register_iteminst() { .def("IsAugmented", (bool(Lua_ItemInst::*)(void))&Lua_ItemInst::IsAugmented) .def("IsEquipable", (bool(Lua_ItemInst::*)(int16))&Lua_ItemInst::IsEquipable) .def("IsEquipable", (bool(Lua_ItemInst::*)(uint16,uint16))&Lua_ItemInst::IsEquipable) + .def("IsEvolving", (bool(Lua_ItemInst::*)(void))&Lua_ItemInst::IsEvolving) .def("IsExpendable", (bool(Lua_ItemInst::*)(void))&Lua_ItemInst::IsExpendable) .def("IsInstNoDrop", (bool(Lua_ItemInst::*)(void))&Lua_ItemInst::IsInstNoDrop) .def("IsStackable", (bool(Lua_ItemInst::*)(void))&Lua_ItemInst::IsStackable) @@ -404,6 +508,8 @@ luabind::scope lua_register_iteminst() { .def("SetCustomData", (void(Lua_ItemInst::*)(const std::string&,float))&Lua_ItemInst::SetCustomData) .def("SetCustomData", (void(Lua_ItemInst::*)(const std::string&,int))&Lua_ItemInst::SetCustomData) .def("SetCustomData", (void(Lua_ItemInst::*)(const std::string&,const std::string&))&Lua_ItemInst::SetCustomData) + .def("SetEvolveAmount", (void(Lua_ItemInst::*)(uint64))&Lua_ItemInst::SetEvolveAmount) + .def("SetEvolveProgression", (void(Lua_ItemInst::*)(float))&Lua_ItemInst::SetEvolveProgression) .def("SetExp", (void(Lua_ItemInst::*)(uint32))&Lua_ItemInst::SetExp) .def("SetInstNoDrop", (void(Lua_ItemInst::*)(bool))&Lua_ItemInst::SetInstNoDrop) .def("SetPrice", (void(Lua_ItemInst::*)(uint32))&Lua_ItemInst::SetPrice) diff --git a/zone/lua_iteminst.h b/zone/lua_iteminst.h index 2ff99165c..486378058 100644 --- a/zone/lua_iteminst.h +++ b/zone/lua_iteminst.h @@ -77,7 +77,6 @@ public: void SetExp(uint32 exp); void AddExp(uint32 exp); int8 GetMaxEvolveLvl(); - uint32 GetKillsNeeded(uint8 current_level); Lua_ItemInst Clone(); void SetTimer(std::string name, uint32 time); void StopTimer(std::string name); @@ -87,10 +86,26 @@ public: int GetTaskDeliveredCount(); int RemoveTaskDeliveredItems(); std::string GetName(); + int GetSerialNumber(); void ItemSay(const char* text); void ItemSay(const char* text, uint8 language_id); luabind::object GetAugmentIDs(lua_State* L); std::string GetItemLink(); + void AddEvolveAmount(uint64 amount); + uint32 GetAugmentEvolveUniqueID(uint8 slot_id); + bool GetEvolveActivated(); + uint64 GetEvolveAmount(); + uint32 GetEvolveCharacterID(); + bool GetEvolveEquipped(); + uint32 GetEvolveFinalItemID(); + uint32 GetEvolveItemID(); + int8 GetEvolveLevel(); + uint32 GetEvolveLoreID(); + float GetEvolveProgression(); + uint64 GetEvolveUniqueID(); + bool IsEvolving(); + void SetEvolveAmount(uint64 amount); + void SetEvolveProgression(float amount); private: bool cloned_; diff --git a/zone/lua_mob.cpp b/zone/lua_mob.cpp index 0e644f4f2..e23eb2d03 100644 --- a/zone/lua_mob.cpp +++ b/zone/lua_mob.cpp @@ -1241,12 +1241,12 @@ float Lua_Mob::GetAssistRange() { return self->GetAssistRange(); } -void Lua_Mob::SetPetOrder(int order) { +void Lua_Mob::SetPetOrder(uint8 pet_order) { Lua_Safe_Call_Void(); - self->SetPetOrder(static_cast(order)); + self->SetPetOrder(pet_order); } -int Lua_Mob::GetPetOrder() { +uint8 Lua_Mob::GetPetOrder() { Lua_Safe_Call_Int(); return self->GetPetOrder(); } @@ -3268,6 +3268,12 @@ bool Lua_Mob::IsPetOwnerNPC() return self->IsPetOwnerNPC(); } +bool Lua_Mob::IsPetOwnerOfClientBot() +{ + Lua_Safe_Call_Bool(); + return self->IsPetOwnerOfClientBot(); +} + bool Lua_Mob::IsDestructibleObject() { Lua_Safe_Call_Bool(); @@ -3476,6 +3482,54 @@ void Lua_Mob::BuffFadeSongs() self->BuffFadeSongs(); } +luabind::object Lua_Mob::GetPausedTimers(lua_State* L) { + auto t = luabind::newtable(L); + if (d_) { + auto self = reinterpret_cast(d_); + auto l = quest_manager.GetPausedTimers(self); + int i = 1; + for (const auto& v : l) { + t[i] = v; + i++; + } + } + + return t; +} + +luabind::object Lua_Mob::GetTimers(lua_State* L) { + auto t = luabind::newtable(L); + if (d_) { + auto self = reinterpret_cast(d_); + auto l = quest_manager.GetTimers(self); + int i = 1; + for (const auto& v : l) { + t[i] = v; + i++; + } + } + + return t; +} + +uint8 Lua_Mob::GetPetType() +{ + Lua_Safe_Call_Int(); + return self->GetPetType(); +} + +std::string Lua_Mob::GetPetTypeName() +{ + Lua_Safe_Call_String(); + return PetType::GetName(self->GetPetType()); +} + +void Lua_Mob::SetPetType(uint8 pet_type) +{ + Lua_Safe_Call_Void(); + self->SetPetType(pet_type); +} + luabind::scope lua_register_mob() { return luabind::class_("Mob") .def(luabind::constructor<>()) @@ -3816,8 +3870,11 @@ luabind::scope lua_register_mob() { .def("GetOwner", &Lua_Mob::GetOwner) .def("GetOwnerID", &Lua_Mob::GetOwnerID) .def("GetPR", &Lua_Mob::GetPR) + .def("GetPausedTimers", &Lua_Mob::GetPausedTimers) .def("GetPet", &Lua_Mob::GetPet) - .def("GetPetOrder", (int(Lua_Mob::*)(void))&Lua_Mob::GetPetOrder) + .def("GetPetOrder", (uint8(Lua_Mob::*)(void))&Lua_Mob::GetPetOrder) + .def("GetPetType", &Lua_Mob::GetPetType) + .def("GetPetTypeName", &Lua_Mob::GetPetTypeName) .def("GetPhR", &Lua_Mob::GetPhR) .def("GetRace", &Lua_Mob::GetRace) .def("GetRaceName", &Lua_Mob::GetRaceName) @@ -3841,6 +3898,7 @@ luabind::scope lua_register_mob() { .def("GetTarget", &Lua_Mob::GetTarget) .def("GetTexture", &Lua_Mob::GetTexture) .def("GetTimerDurationMS", &Lua_Mob::GetTimerDurationMS) + .def("GetTimers", &Lua_Mob::GetTimers) .def("GetUltimateOwner", &Lua_Mob::GetUltimateOwner) .def("GetWIS", &Lua_Mob::GetWIS) .def("GetWalkspeed", &Lua_Mob::GetWalkspeed) @@ -3904,6 +3962,7 @@ luabind::scope lua_register_mob() { .def("IsPetOwnerBot", &Lua_Mob::IsPetOwnerBot) .def("IsPetOwnerClient", &Lua_Mob::IsPetOwnerClient) .def("IsPetOwnerNPC", &Lua_Mob::IsPetOwnerNPC) + .def("IsPetOwnerOfClientBot", &Lua_Mob::IsPetOwnerOfClientBot) .def("IsPureMeleeClass", &Lua_Mob::IsPureMeleeClass) .def("IsRoamer", (bool(Lua_Mob::*)(void))&Lua_Mob::IsRoamer) .def("IsRooted", (bool(Lua_Mob::*)(void))&Lua_Mob::IsRooted) @@ -4010,7 +4069,8 @@ luabind::scope lua_register_mob() { .def("SetMana", &Lua_Mob::SetMana) .def("SetOOCRegen", (void(Lua_Mob::*)(int64))&Lua_Mob::SetOOCRegen) .def("SetPet", &Lua_Mob::SetPet) - .def("SetPetOrder", (void(Lua_Mob::*)(int))&Lua_Mob::SetPetOrder) + .def("SetPetOrder", (void(Lua_Mob::*)(uint8))&Lua_Mob::SetPetOrder) + .def("SetPetType", &Lua_Mob::SetPetType) .def("SetPseudoRoot", (void(Lua_Mob::*)(bool))&Lua_Mob::SetPseudoRoot) .def("SetRace", (void(Lua_Mob::*)(uint16))&Lua_Mob::SetRace) .def("SetRunning", (void(Lua_Mob::*)(bool))&Lua_Mob::SetRunning) diff --git a/zone/lua_mob.h b/zone/lua_mob.h index dfe646cc0..b5019aa5f 100644 --- a/zone/lua_mob.h +++ b/zone/lua_mob.h @@ -280,8 +280,8 @@ public: bool IsAIControlled(); float GetAggroRange(); float GetAssistRange(); - void SetPetOrder(int order); - int GetPetOrder(); + void SetPetOrder(uint8 pet_order); + uint8 GetPetOrder(); bool IsRoamer(); bool IsRooted(); bool IsEngaged(); @@ -576,6 +576,7 @@ public: bool IsPetOwnerBot(); bool IsPetOwnerClient(); bool IsPetOwnerNPC(); + bool IsPetOwnerOfClientBot(); bool IsDestructibleObject(); bool IsBoat(); bool IsControllableBoat(); @@ -611,6 +612,11 @@ public: void BuffFadeDetrimentalByCaster(Lua_Mob caster); void BuffFadeNonPersistDeath(); void BuffFadeSongs(); + luabind::object GetPausedTimers(lua_State* L); + luabind::object GetTimers(lua_State* L); + uint8 GetPetType(); + std::string GetPetTypeName(); + void SetPetType(uint8 pet_type); }; #endif diff --git a/zone/lua_npc.cpp b/zone/lua_npc.cpp index b17804edc..b0cd59c49 100644 --- a/zone/lua_npc.cpp +++ b/zone/lua_npc.cpp @@ -7,6 +7,9 @@ #include "npc.h" #include "lua_npc.h" #include "lua_client.h" +#include "lua_item.h" +#include "lua_iteminst.h" +#include "lua_spawn.h" struct Lua_NPC_Loot_List { std::vector entries; @@ -597,7 +600,7 @@ bool Lua_NPC::HasItem(uint32 item_id) return self->HasItem(item_id); } -uint16 Lua_NPC::CountItem(uint32 item_id) +uint32 Lua_NPC::CountItem(uint32 item_id) { Lua_Safe_Call_Int(); return self->CountItem(item_id); @@ -837,6 +840,123 @@ void Lua_NPC::DescribeSpecialAbilities(Lua_Client c) self->DescribeSpecialAbilities(c); } +bool Lua_NPC::IsMultiQuestEnabled() +{ + Lua_Safe_Call_Bool(); + return self->IsMultiQuestEnabled(); +} + +void Lua_NPC::MultiQuestEnable() +{ + Lua_Safe_Call_Void(); + self->MultiQuestEnable(); +} + +bool Lua_NPC::LuaCheckHandin( + Lua_Client c, + luabind::adl::object handin_table, + luabind::adl::object required_table, + luabind::adl::object items_table +) +{ + Lua_Safe_Call_Bool(); + + if ( + luabind::type(handin_table) != LUA_TTABLE || + luabind::type(required_table) != LUA_TTABLE || + luabind::type(items_table) != LUA_TTABLE + ) { + return false; + } + + std::map handin_map; + std::map required_map; + std::vector items; + + for (luabind::iterator i(handin_table), end; i != end; i++) { + std::string key; + if (luabind::type(i.key()) == LUA_TSTRING) { + key = luabind::object_cast(i.key()); + } + else if (luabind::type(i.key()) == LUA_TNUMBER) { + key = fmt::format("{}", luabind::object_cast(i.key())); + } + else { + LogError("Handin key type [{}] not supported", luabind::type(i.key())); + } + + if (!key.empty()) { + handin_map[key] = luabind::object_cast(handin_table[i.key()]); + LogNpcHandinDetail("Handin key [{}] value [{}]", key, handin_map[key]); + } + } + + for (luabind::iterator i(required_table), end; i != end; i++) { + std::string key; + if (luabind::type(i.key()) == LUA_TSTRING) { + key = luabind::object_cast(i.key()); + } + else if (luabind::type(i.key()) == LUA_TNUMBER) { + key = fmt::format("{}", luabind::object_cast(i.key())); + } + else { + LogError("Required key type [{}] not supported", luabind::type(i.key())); + } + + if (!key.empty()) { + required_map[key] = luabind::object_cast(required_table[i.key()]); + LogNpcHandinDetail("Required key [{}] value [{}]", key, required_map[key]); + } + } + + for (luabind::iterator i(items_table), end; i != end; i++) { + auto item = luabind::object_cast(items_table[i.key()]); + + if (item && item.GetItem()) { + LogNpcHandinDetail( + "Item instance [{}] ({}) UUID ({}) added to handin list", + item.GetName(), + item.GetID(), + item.GetSerialNumber() + ); + + items.emplace_back(item); + } + } + + return self->CheckHandin(c, handin_map, required_map, items); +} + +void Lua_NPC::ReturnHandinItems(Lua_Client c) +{ + Lua_Safe_Call_Void(); + self->ReturnHandinItems(c); +} + +Lua_Spawn Lua_NPC::GetSpawn(lua_State* L) +{ + Lua_Safe_Call_Class(Lua_Spawn); + return Lua_Spawn(self->GetSpawn()); +} + +bool Lua_NPC::IsResumedFromZoneSuspend() +{ + Lua_Safe_Call_Bool(); + return self->IsResumedFromZoneSuspend(); +} + +void Lua_NPC::SetNPCTintIndex(uint32 index) +{ + Lua_Safe_Call_Void(); + self->SetNPCTintIndex(index); +} + +uint32 Lua_NPC::GetNPCTintIndex() +{ + Lua_Safe_Call_Int(); + return self->GetNPCTintIndex(); +} + luabind::scope lua_register_npc() { return luabind::class_("NPC") .def(luabind::constructor<>()) @@ -859,10 +979,11 @@ luabind::scope lua_register_npc() { .def("AssignWaypoints", (void(Lua_NPC::*)(int))&Lua_NPC::AssignWaypoints) .def("CalculateNewWaypoint", (void(Lua_NPC::*)(void))&Lua_NPC::CalculateNewWaypoint) .def("ChangeLastName", (void(Lua_NPC::*)(std::string))&Lua_NPC::ChangeLastName) + .def("CheckHandin", (bool(Lua_NPC::*)(Lua_Client,luabind::adl::object,luabind::adl::object,luabind::adl::object))&Lua_NPC::LuaCheckHandin) .def("CheckNPCFactionAlly", (int(Lua_NPC::*)(int))&Lua_NPC::CheckNPCFactionAlly) .def("ClearItemList", (void(Lua_NPC::*)(void))&Lua_NPC::ClearLootItems) .def("ClearLastName", (void(Lua_NPC::*)(void))&Lua_NPC::ClearLastName) - .def("CountItem", (uint16(Lua_NPC::*)(uint32))&Lua_NPC::CountItem) + .def("CountItem", (uint32(Lua_NPC::*)(uint32))&Lua_NPC::CountItem) .def("CountLoot", (int(Lua_NPC::*)(void))&Lua_NPC::CountLoot) .def("DeleteBucket", (void(Lua_NPC::*)(std::string))&Lua_NPC::DeleteBucket) .def("DescribeSpecialAbilities", (void(Lua_NPC::*)(Lua_Client))&Lua_NPC::DescribeSpecialAbilities) @@ -903,6 +1024,7 @@ luabind::scope lua_register_npc() { .def("GetNPCSpellsEffectsID", (uint32(Lua_NPC::*)(void))&Lua_NPC::GetNPCSpellsEffectsID) .def("GetNPCSpellsID", (uint32(Lua_NPC::*)(void))&Lua_NPC::GetNPCSpellsID) .def("GetNPCStat", (float(Lua_NPC::*)(std::string))&Lua_NPC::GetNPCStat) + .def("GetNPCTintIndex", (uint32(Lua_NPC::*)(void))&Lua_NPC::GetNPCTintIndex) .def("GetPetSpellID", (int(Lua_NPC::*)(void))&Lua_NPC::GetPetSpellID) .def("GetPlatinum", (uint32(Lua_NPC::*)(void))&Lua_NPC::GetPlatinum) .def("GetPrimSkill", (int(Lua_NPC::*)(void))&Lua_NPC::GetPrimSkill) @@ -913,6 +1035,7 @@ luabind::scope lua_register_npc() { .def("GetSilver", (uint32(Lua_NPC::*)(void))&Lua_NPC::GetSilver) .def("GetSlowMitigation", (int(Lua_NPC::*)(void))&Lua_NPC::GetSlowMitigation) .def("GetSp2", (uint32(Lua_NPC::*)(void))&Lua_NPC::GetSp2) + .def("GetSpawn", (Lua_Spawn(Lua_NPC::*)(void))&Lua_NPC::GetSpawn) .def("GetSpawnKillCount", (int(Lua_NPC::*)(void))&Lua_NPC::GetSpawnKillCount) .def("GetSpawnPointH", (float(Lua_NPC::*)(void))&Lua_NPC::GetSpawnPointH) .def("GetSpawnPointID", (int(Lua_NPC::*)(void))&Lua_NPC::GetSpawnPointID) @@ -932,15 +1055,18 @@ luabind::scope lua_register_npc() { .def("IsLDoNLocked", (bool(Lua_NPC::*)(void))&Lua_NPC::IsLDoNLocked) .def("IsLDoNTrapped", (bool(Lua_NPC::*)(void))&Lua_NPC::IsLDoNTrapped) .def("IsLDoNTrapDetected", (bool(Lua_NPC::*)(void))&Lua_NPC::IsLDoNTrapDetected) + .def("IsMultiQuestEnabled", (bool(Lua_NPC::*)(void))&Lua_NPC::IsMultiQuestEnabled) .def("IsOnHatelist", (bool(Lua_NPC::*)(Lua_Mob))&Lua_NPC::IsOnHatelist) .def("IsRaidTarget", (bool(Lua_NPC::*)(void))&Lua_NPC::IsRaidTarget) .def("IsRareSpawn", (bool(Lua_NPC::*)(void))&Lua_NPC::IsRareSpawn) + .def("IsResumedFromZoneSuspend",(bool(Lua_NPC::*)(void))&Lua_NPC::IsResumedFromZoneSuspend) .def("IsTaunting", (bool(Lua_NPC::*)(void))&Lua_NPC::IsTaunting) .def("IsUnderwaterOnly", (bool(Lua_NPC::*)(void))&Lua_NPC::IsUnderwaterOnly) .def("MerchantCloseShop", (void(Lua_NPC::*)(void))&Lua_NPC::MerchantCloseShop) .def("MerchantOpenShop", (void(Lua_NPC::*)(void))&Lua_NPC::MerchantOpenShop) .def("ModifyNPCStat", (void(Lua_NPC::*)(std::string,std::string))&Lua_NPC::ModifyNPCStat) .def("MoveTo", (void(Lua_NPC::*)(float,float,float,float,bool))&Lua_NPC::MoveTo) + .def("MultiQuestEnable", &Lua_NPC::MultiQuestEnable) .def("NextGuardPosition", (void(Lua_NPC::*)(void))&Lua_NPC::NextGuardPosition) .def("PauseWandering", (void(Lua_NPC::*)(int))&Lua_NPC::PauseWandering) .def("PickPocket", (void(Lua_NPC::*)(Lua_Client))&Lua_NPC::PickPocket) @@ -953,6 +1079,7 @@ luabind::scope lua_register_npc() { .def("RemoveItem", (void(Lua_NPC::*)(int,int))&Lua_NPC::RemoveItem) .def("RemoveItem", (void(Lua_NPC::*)(int,int,int))&Lua_NPC::RemoveItem) .def("ResumeWandering", (void(Lua_NPC::*)(void))&Lua_NPC::ResumeWandering) + .def("ReturnHandinItems", (void(Lua_NPC::*)(Lua_Client))&Lua_NPC::ReturnHandinItems) .def("SaveGuardSpot", (void(Lua_NPC::*)(void))&Lua_NPC::SaveGuardSpot) .def("SaveGuardSpot", (void(Lua_NPC::*)(bool))&Lua_NPC::SaveGuardSpot) .def("SaveGuardSpot", (void(Lua_NPC::*)(float,float,float,float))&Lua_NPC::SaveGuardSpot) @@ -977,6 +1104,7 @@ luabind::scope lua_register_npc() { .def("SetLDoNTrapType", (void(Lua_NPC::*)(uint8))&Lua_NPC::SetLDoNTrapType) .def("SetNPCAggro", (void(Lua_NPC::*)(bool))&Lua_NPC::SetNPCAggro) .def("SetNPCFactionID", (void(Lua_NPC::*)(int))&Lua_NPC::SetNPCFactionID) + .def("SetNPCTintIndex", &Lua_NPC::SetNPCTintIndex) .def("SetPetSpellID", (void(Lua_NPC::*)(int))&Lua_NPC::SetPetSpellID) .def("SetPlatinum", (void(Lua_NPC::*)(uint32))&Lua_NPC::SetPlatinum) .def("SetPrimSkill", (void(Lua_NPC::*)(int))&Lua_NPC::SetPrimSkill) diff --git a/zone/lua_npc.h b/zone/lua_npc.h index ec916593a..d166ccdff 100644 --- a/zone/lua_npc.h +++ b/zone/lua_npc.h @@ -9,6 +9,8 @@ class Lua_Mob; class Lua_NPC; class Lua_Client; struct Lua_NPC_Loot_List; +class Lua_Inventory; +class Lua_Spawn; namespace luabind { struct scope; @@ -146,7 +148,7 @@ public: void ChangeLastName(std::string last_name); void ClearLastName(); bool HasItem(uint32 item_id); - uint16 CountItem(uint32 item_id); + uint32 CountItem(uint32 item_id); uint32 GetLootItemIDBySlot(uint16 loot_slot); uint16 GetFirstLootSlotByItemID(uint32 item_id); float GetHealScale(); @@ -186,6 +188,20 @@ public: void SetNPCAggro(bool in_npc_aggro); uint32 GetNPCSpellsEffectsID(); void DescribeSpecialAbilities(Lua_Client c); + bool IsMultiQuestEnabled(); + void MultiQuestEnable(); + bool LuaCheckHandin( + Lua_Client c, + luabind::adl::object handin_table, + luabind::adl::object required_table, + luabind::adl::object items_table + ); + void ReturnHandinItems(Lua_Client c); + Lua_Spawn GetSpawn(lua_State* L); + bool IsResumedFromZoneSuspend(); + void SetNPCTintIndex(uint32 id); + uint32 GetNPCTintIndex(); + }; #endif diff --git a/zone/lua_packet.cpp b/zone/lua_packet.cpp index c6a1ab6e2..c9a20aab2 100644 --- a/zone/lua_packet.cpp +++ b/zone/lua_packet.cpp @@ -896,6 +896,7 @@ luabind::scope lua_register_packet_opcodes() { luabind::value("Weblink", static_cast(OP_Weblink)), luabind::value("InspectMessageUpdate", static_cast(OP_InspectMessageUpdate)), luabind::value("ItemPreview", static_cast(OP_ItemPreview)), + luabind::value("ItemPreviewRequest", static_cast(OP_ItemPreviewRequest)), luabind::value("MercenaryDataRequest", static_cast(OP_MercenaryDataRequest)), luabind::value("MercenaryDataResponse", static_cast(OP_MercenaryDataResponse)), luabind::value("MercenaryHire", static_cast(OP_MercenaryHire)), @@ -915,7 +916,9 @@ luabind::scope lua_register_packet_opcodes() { luabind::value("Marquee", static_cast(OP_Marquee)), luabind::value("ClientTimeStamp", static_cast(OP_ClientTimeStamp)), luabind::value("GuildPromote", static_cast(OP_GuildPromote)), - luabind::value("Fling", static_cast(OP_Fling)) + luabind::value("Fling", static_cast(OP_Fling)), + luabind::value("PickZoneWindow", static_cast(OP_PickZoneWindow)), + luabind::value("TradeSkillRecipeInspect", static_cast(OP_TradeSkillRecipeInspect)) )]; } diff --git a/zone/lua_parser.cpp b/zone/lua_parser.cpp index 6f624f149..3e3a5257f 100644 --- a/zone/lua_parser.cpp +++ b/zone/lua_parser.cpp @@ -43,6 +43,7 @@ #include "lua_spell.h" #include "lua_stat_bonuses.h" #include "lua_database.h" +#include "lua_zone.h" const char *LuaEvents[_LargestEventID] = { "event_say", @@ -187,7 +188,10 @@ const char *LuaEvents[_LargestEventID] = { "event_entity_variable_update", "event_aa_loss", "event_spell_blocked", - "event_read_item" + "event_read_item", + "event_pet_command", + "event_charm_start", + "event_charm_end" }; extern Zone *zone; @@ -218,6 +222,7 @@ LuaParser::LuaParser() { SpellArgumentDispatch[i] = handle_spell_null; EncounterArgumentDispatch[i] = handle_encounter_null; BotArgumentDispatch[i] = handle_bot_null; + ZoneArgumentDispatch[i] = handle_zone_null; } NPCArgumentDispatch[EVENT_SAY] = handle_npc_event_say; @@ -262,6 +267,9 @@ LuaParser::LuaParser() { NPCArgumentDispatch[EVENT_ENTITY_VARIABLE_SET] = handle_npc_entity_variable; NPCArgumentDispatch[EVENT_ENTITY_VARIABLE_UPDATE] = handle_npc_entity_variable; NPCArgumentDispatch[EVENT_SPELL_BLOCKED] = handle_npc_spell_blocked; + NPCArgumentDispatch[EVENT_PET_COMMAND] = handle_npc_pet_command; + NPCArgumentDispatch[EVENT_CHARM_START] = handle_npc_single_mob; + NPCArgumentDispatch[EVENT_CHARM_END] = handle_npc_single_mob; PlayerArgumentDispatch[EVENT_SAY] = handle_player_say; PlayerArgumentDispatch[EVENT_ENVIRONMENTAL_DAMAGE] = handle_player_environmental_damage; @@ -352,6 +360,8 @@ LuaParser::LuaParser() { PlayerArgumentDispatch[EVENT_AA_LOSS] = handle_player_aa_loss; PlayerArgumentDispatch[EVENT_SPELL_BLOCKED] = handle_player_spell_blocked; PlayerArgumentDispatch[EVENT_READ_ITEM] = handle_player_read_item; + PlayerArgumentDispatch[EVENT_CONNECT] = handle_player_connect; + PlayerArgumentDispatch[EVENT_PET_COMMAND] = handle_player_pet_command; ItemArgumentDispatch[EVENT_ITEM_CLICK] = handle_item_click; ItemArgumentDispatch[EVENT_ITEM_CLICK_CAST] = handle_item_click; @@ -407,6 +417,23 @@ LuaParser::LuaParser() { BotArgumentDispatch[EVENT_ENTITY_VARIABLE_SET] = handle_bot_entity_variable; BotArgumentDispatch[EVENT_ENTITY_VARIABLE_UPDATE] = handle_bot_entity_variable; BotArgumentDispatch[EVENT_SPELL_BLOCKED] = handle_bot_spell_blocked; + + ZoneArgumentDispatch[EVENT_CLICK_DOOR] = handle_zone_click_door; + ZoneArgumentDispatch[EVENT_CLICK_OBJECT] = handle_zone_click_object; + ZoneArgumentDispatch[EVENT_DEATH_ZONE] = handle_zone_death; + ZoneArgumentDispatch[EVENT_DESPAWN_ZONE] = handle_zone_despawn; + ZoneArgumentDispatch[EVENT_ENTER_ZONE] = handle_zone_enter; + ZoneArgumentDispatch[EVENT_LOOT_ZONE] = handle_zone_loot; + ZoneArgumentDispatch[EVENT_PAYLOAD] = handle_zone_payload; + ZoneArgumentDispatch[EVENT_PLAYER_PICKUP] = handle_zone_pickup; + ZoneArgumentDispatch[EVENT_POPUP_RESPONSE] = handle_zone_popup; + ZoneArgumentDispatch[EVENT_SIGNAL] = handle_zone_signal; + ZoneArgumentDispatch[EVENT_SPAWN_ZONE] = handle_zone_spawn; + ZoneArgumentDispatch[EVENT_TIMER] = handle_zone_timer; + ZoneArgumentDispatch[EVENT_TIMER_PAUSE] = handle_zone_timer_pause_resume_start; + ZoneArgumentDispatch[EVENT_TIMER_RESUME] = handle_zone_timer_pause_resume_start; + ZoneArgumentDispatch[EVENT_TIMER_START] = handle_zone_timer_pause_resume_start; + ZoneArgumentDispatch[EVENT_TIMER_STOP] = handle_zone_timer_stop; #endif L = nullptr; @@ -487,7 +514,13 @@ int LuaParser::_EventNPC(std::string package_name, QuestEventID evt, NPC* npc, M arg_function(this, L, npc, init, data, extra_data, extra_pointers); Client *c = (init && init->IsClient()) ? init->CastToClient() : nullptr; - quest_manager.StartQuest(npc, c); + RunningQuest q; + + q.owner = npc; + q.initiator = c; + + quest_manager.StartQuest(q); + if(lua_pcall(L, 1, 1, start + 1)) { std::string error = lua_tostring(L, -1); AddError(error); @@ -580,7 +613,13 @@ int LuaParser::_EventPlayer(std::string package_name, QuestEventID evt, Client * auto arg_function = PlayerArgumentDispatch[evt]; arg_function(this, L, client, data, extra_data, extra_pointers); - quest_manager.StartQuest(client, client); + RunningQuest q; + + q.owner = client; + q.initiator = client; + + quest_manager.StartQuest(q); + if(lua_pcall(L, 1, 1, start + 1)) { std::string error = lua_tostring(L, -1); AddError(error); @@ -664,7 +703,14 @@ int LuaParser::_EventItem(std::string package_name, QuestEventID evt, Client *cl auto arg_function = ItemArgumentDispatch[evt]; arg_function(this, L, client, item, mob, data, extra_data, extra_pointers); - quest_manager.StartQuest(client, client, item); + RunningQuest q; + + q.owner = client; + q.initiator = client; + q.questitem = item; + + quest_manager.StartQuest(q); + if(lua_pcall(L, 1, 1, start + 1)) { std::string error = lua_tostring(L, -1); AddError(error); @@ -746,7 +792,14 @@ int LuaParser::_EventSpell(std::string package_name, QuestEventID evt, Mob* mob, auto arg_function = SpellArgumentDispatch[evt]; arg_function(this, L, mob, client, spell_id, data, extra_data, extra_pointers); - quest_manager.StartQuest(mob, client, nullptr, const_cast(&spells[spell_id])); + RunningQuest q; + + q.owner = client; + q.initiator = client; + q.questspell = const_cast(&spells[spell_id]); + + quest_manager.StartQuest(q); + if(lua_pcall(L, 1, 1, start + 1)) { std::string error = lua_tostring(L, -1); AddError(error); @@ -812,7 +865,13 @@ int LuaParser::_EventEncounter(std::string package_name, QuestEventID evt, std:: auto arg_function = EncounterArgumentDispatch[evt]; arg_function(this, L, enc, data, extra_data, extra_pointers); - quest_manager.StartQuest(enc, nullptr, nullptr, nullptr, encounter_name); + RunningQuest q; + + q.owner = enc; + q.encounter = encounter_name; + + quest_manager.StartQuest(q); + if(lua_pcall(L, 1, 1, start + 1)) { std::string error = lua_tostring(L, -1); AddError(error); @@ -1065,13 +1124,16 @@ void LuaParser::ReloadQuests() { lua_getglobal(L, "package"); lua_getfield(L, -1, "path"); - std::string module_path = lua_tostring(L,-1); - module_path += ";" + path.GetLuaModulesPath() + "/?.lua;" + path.GetLuaModulesPath() + "/?/init.lua"; - // luarock paths using lua_modules as tree - // to path it adds foo/share/lua/5.1/?.lua and foo/share/lua/5.1/?/init.lua - module_path += ";" + path.GetLuaModulesPath() + "/share/lua/" + lua_version + "/?.lua"; - module_path += ";" + path.GetLuaModulesPath() + "/share/lua/" + lua_version + "/?/init.lua"; + std::string module_path = lua_tostring(L, -1); lua_pop(L, 1); + + for (const auto& dir : PathManager::Instance()->GetLuaModulePaths()) { + module_path += fmt::format(";{}/?.lua", dir); + module_path += fmt::format(";{}/?/init.lua", dir); + module_path += fmt::format(";{}/share/lua/{}/?.lua", dir, lua_version); + module_path += fmt::format(";{}/share/lua/{}/?/init.lua", dir, lua_version); + } + lua_pushstring(L, module_path.c_str()); lua_setfield(L, -2, "path"); lua_pop(L, 1); @@ -1079,11 +1141,13 @@ void LuaParser::ReloadQuests() { lua_getglobal(L, "package"); lua_getfield(L, -1, "cpath"); module_path = lua_tostring(L, -1); - module_path += ";" + path.GetLuaModulesPath() + "/?" + libext; - // luarock paths using lua_modules as tree - // luarocks adds foo/lib/lua/5.1/?.so for cpath - module_path += ";" + path.GetLuaModulesPath() + "/lib/lua/" + lua_version + "/?" + libext; lua_pop(L, 1); + + for (const auto& dir : PathManager::Instance()->GetLuaModulePaths()) { + module_path += fmt::format(";{}/?{}", dir, libext); + module_path += fmt::format(";{}/lib/lua/{}/?{}", dir, lua_version, libext); + } + lua_pushstring(L, module_path.c_str()); lua_setfield(L, -2, "cpath"); lua_pop(L, 1); @@ -1091,40 +1155,31 @@ void LuaParser::ReloadQuests() { MapFunctions(L); // load init - std::string filename = fmt::format("{}/{}/script_init.lua", path.GetQuestsPath(), QUEST_GLOBAL_DIRECTORY); + for (auto& dir : PathManager::Instance()->GetQuestPaths()) { + std::string filename = fmt::format("{}/{}/script_init.lua", dir, QUEST_GLOBAL_DIRECTORY); - FILE *f = fopen(filename.c_str(), "r"); - if(f) { - fclose(f); - - if(luaL_dofile(L, filename.c_str())) { - std::string error = lua_tostring(L, -1); - AddError(error); - } - } - - //zone init - always loads after global - if(zone) { - std::string zone_script = fmt::format( - "{}/{}/script_init_v{}.lua", - path.GetQuestsPath(), - zone->GetShortName(), - zone->GetInstanceVersion() - ); - - f = fopen(zone_script.c_str(), "r"); - if(f) { + FILE* f = fopen(filename.c_str(), "r"); + if (f) { fclose(f); - if(luaL_dofile(L, zone_script.c_str())) { + if (luaL_dofile(L, filename.c_str())) { std::string error = lua_tostring(L, -1); AddError(error); } } - else { - zone_script = fmt::format("{}/{}/script_init.lua", path.GetQuestsPath(), zone->GetShortName()); + } - f = fopen(zone_script.c_str(), "r"); + //zone init - always loads after global + if (zone) { + for (auto& dir : PathManager::Instance()->GetQuestPaths()) { + std::string zone_script = fmt::format( + "{}/{}/script_init_v{}.lua", + dir, + zone->GetShortName(), + zone->GetInstanceVersion() + ); + + FILE* f = fopen(zone_script.c_str(), "r"); if (f) { fclose(f); @@ -1133,10 +1188,23 @@ void LuaParser::ReloadQuests() { AddError(error); } } + else { + zone_script = fmt::format("{}/{}/script_init.lua", dir, zone->GetShortName()); + + f = fopen(zone_script.c_str(), "r"); + if (f) { + fclose(f); + + if (luaL_dofile(L, zone_script.c_str())) { + std::string error = lua_tostring(L, -1); + AddError(error); + } + } + } } } - FILE *load_order = fopen(fmt::format("{}/load_order.txt", path.GetLuaModsPath()).c_str(), "r"); + FILE *load_order = fopen(fmt::format("{}/load_order.txt", PathManager::Instance()->GetLuaModsPath()).c_str(), "r"); if (load_order) { char file_name[256] = { 0 }; while (fgets(file_name, 256, load_order) != nullptr) { @@ -1148,7 +1216,7 @@ void LuaParser::ReloadQuests() { } } - LoadScript(fmt::format("{}/{}", path.GetLuaModsPath(), std::string(file_name)), file_name); + LoadScript(fmt::format("{}/{}", PathManager::Instance()->GetLuaModsPath(), std::string(file_name)), file_name); mods_.emplace_back(L, this, file_name); } @@ -1309,7 +1377,6 @@ void LuaParser::MapFunctions(lua_State *L) { lua_register_packet(), lua_register_packet_opcodes(), lua_register_stat_bonuses(), - lua_register_rules_const(), lua_register_rulei(), lua_register_ruler(), lua_register_ruleb(), @@ -1320,7 +1387,8 @@ void LuaParser::MapFunctions(lua_State *L) { lua_register_expedition_lock_messages(), lua_register_buff(), lua_register_exp_source(), - lua_register_database() + lua_register_database(), + lua_register_zone() )]; } catch(std::exception &ex) { @@ -1746,7 +1814,13 @@ int LuaParser::_EventBot( arg_function(this, L, bot, init, data, extra_data, extra_pointers); auto* c = (init && init->IsClient()) ? init->CastToClient() : nullptr; - quest_manager.StartQuest(bot, c); + RunningQuest q; + + q.owner = bot; + q.initiator = c; + + quest_manager.StartQuest(q); + if(lua_pcall(L, 1, 1, start + 1)) { std::string error = lua_tostring(L, -1); AddError(error); @@ -1925,7 +1999,13 @@ int LuaParser::_EventMerc( arg_function(this, L, merc, init, data, extra_data, extra_pointers); auto* c = (init && init->IsClient()) ? init->CastToClient() : nullptr; - quest_manager.StartQuest(merc, c); + RunningQuest q; + + q.owner = merc; + q.initiator = c; + + quest_manager.StartQuest(q); + if(lua_pcall(L, 1, 1, start + 1)) { std::string error = lua_tostring(L, -1); AddError(error); @@ -2028,3 +2108,190 @@ void LuaParser::LoadMercScript(std::string filename) { void LuaParser::LoadGlobalMercScript(std::string filename) { LoadScript(filename, "global_merc"); } + +int LuaParser::EventZone( + QuestEventID evt, + Zone *zone, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +) { + evt = ConvertLuaEvent(evt); + if (evt >= _LargestEventID) { + return 0; + } + + if (!zone) { + return 0; + } + + if (!ZoneHasQuestSub(evt)) { + return 0; + } + + return _EventZone("zone", evt, zone, data, extra_data, extra_pointers); +} + +int LuaParser::EventGlobalZone( + QuestEventID evt, + Zone *zone, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +) { + evt = ConvertLuaEvent(evt); + if (evt >= _LargestEventID) { + return 0; + } + + if (!zone) { + return 0; + } + + if (!GlobalZoneHasQuestSub(evt)) { + return 0; + } + + return _EventZone("global_zone", evt, zone, data, extra_data, extra_pointers); +} + +int LuaParser::_EventZone( + std::string package_name, + QuestEventID evt, + Zone *zone, + std::string data, + uint32 extra_data, + std::vector *extra_pointers, + luabind::adl::object *l_func +) { + const char *sub_name = LuaEvents[evt]; + int start = lua_gettop(L); + + try { + int npop = 2; + PushErrorHandler(L); + if(l_func != nullptr) { + l_func->push(L); + } else { + lua_getfield(L, LUA_REGISTRYINDEX, package_name.c_str()); + lua_getfield(L, -1, sub_name); + npop = 3; + } + + lua_createtable(L, 0, 0); + //push self + Lua_Zone l_zone(zone); + luabind::adl::object l_zone_o = luabind::adl::object(L, l_zone); + l_zone_o.push(L); + lua_setfield(L, -2, "self"); + + auto arg_function = ZoneArgumentDispatch[evt]; + arg_function(this, L, zone, data, extra_data, extra_pointers); + + RunningQuest q; + + q.zone = zone; + + quest_manager.StartQuest(q); + + if(lua_pcall(L, 1, 1, start + 1)) { + std::string error = lua_tostring(L, -1); + AddError(error); + quest_manager.EndQuest(); + lua_pop(L, npop); + return 0; + } + quest_manager.EndQuest(); + + if(lua_isnumber(L, -1)) { + int ret = static_cast(lua_tointeger(L, -1)); + lua_pop(L, npop); + return ret; + } + + lua_pop(L, npop); + } catch(std::exception &ex) { + AddError( + fmt::format( + "Lua Exception | [{}] for Zone [{}] in [{}]: {}", + sub_name, + zone->GetShortName(), + package_name, + ex.what() + ) + ); + + //Restore our stack to the best of our ability + int end = lua_gettop(L); + int n = end - start; + if(n > 0) { + lua_pop(L, n); + } + } + + return 0; +} + +int LuaParser::DispatchEventZone( + QuestEventID evt, + Zone *zone, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +) { + evt = ConvertLuaEvent(evt); + if (evt >= _LargestEventID) { + return 0; + } + + std::string package_name = "zone"; + + auto iter = lua_encounter_events_registered.find(package_name); + if (iter == lua_encounter_events_registered.end()) { + return 0; + } + + int ret = 0; + auto riter = iter->second.begin(); + while (riter != iter->second.end()) { + if (riter->event_id == evt) { + package_name = fmt::format("encounter_{}", riter->encounter_name); + int i = _EventZone(package_name, evt, zone, data, extra_data, extra_pointers, &riter->lua_reference); + if (i != 0) { + ret = i; + } + } + + ++riter; + } + + return ret; +} + +bool LuaParser::ZoneHasQuestSub(QuestEventID evt) { + evt = ConvertLuaEvent(evt); + if (evt >= _LargestEventID) { + return false; + } + + const char *subname = LuaEvents[evt]; + return HasFunction(subname, "zone"); +} + +bool LuaParser::GlobalZoneHasQuestSub(QuestEventID evt) { + evt = ConvertLuaEvent(evt); + if (evt >= _LargestEventID) { + return false; + } + + const char *subname = LuaEvents[evt]; + return HasFunction(subname, "global_zone"); +} + +void LuaParser::LoadZoneScript(std::string filename) { + LoadScript(filename, "zone"); +} + +void LuaParser::LoadGlobalZoneScript(std::string filename) { + LoadScript(filename, "global_zone"); +} diff --git a/zone/lua_parser.h b/zone/lua_parser.h index fbd50a984..c1bad7088 100644 --- a/zone/lua_parser.h +++ b/zone/lua_parser.h @@ -125,6 +125,20 @@ public: uint32 extra_data, std::vector* extra_pointers ); + virtual int EventZone( + QuestEventID evt, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector* extra_pointers + ); + virtual int EventGlobalZone( + QuestEventID evt, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector* extra_pointers + ); virtual bool HasQuestSub(uint32 npc_id, QuestEventID evt); virtual bool HasGlobalQuestSub(QuestEventID evt); @@ -138,6 +152,8 @@ public: virtual bool GlobalBotHasQuestSub(QuestEventID evt); virtual bool MercHasQuestSub(QuestEventID evt); virtual bool GlobalMercHasQuestSub(QuestEventID evt); + virtual bool ZoneHasQuestSub(QuestEventID evt); + virtual bool GlobalZoneHasQuestSub(QuestEventID evt); virtual void LoadNPCScript(std::string filename, int npc_id); virtual void LoadGlobalNPCScript(std::string filename); @@ -150,6 +166,8 @@ public: virtual void LoadGlobalBotScript(std::string filename); virtual void LoadMercScript(std::string filename); virtual void LoadGlobalMercScript(std::string filename); + virtual void LoadZoneScript(std::string filename); + virtual void LoadGlobalZoneScript(std::string filename); virtual void AddVar(std::string name, std::string val); virtual std::string GetVar(std::string name); @@ -207,6 +225,13 @@ public: uint32 extra_data, std::vector* extra_pointers ); + virtual int DispatchEventZone( + QuestEventID evt, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector* extra_pointers + ); static LuaParser* Instance() { static LuaParser inst; @@ -307,6 +332,15 @@ private: std::vector* extra_pointers, luabind::adl::object* l_func = nullptr ); + int _EventZone( + std::string package_name, + QuestEventID evt, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector* extra_pointers, + luabind::adl::object* l_func = nullptr + ); void LoadScript(std::string filename, std::string package_name); void MapFunctions(lua_State *L); @@ -317,12 +351,13 @@ private: std::vector mods_; lua_State *L; - NPCArgumentHandler NPCArgumentDispatch[_LargestEventID]; - PlayerArgumentHandler PlayerArgumentDispatch[_LargestEventID]; - ItemArgumentHandler ItemArgumentDispatch[_LargestEventID]; - SpellArgumentHandler SpellArgumentDispatch[_LargestEventID]; + NPCArgumentHandler NPCArgumentDispatch[_LargestEventID]; + PlayerArgumentHandler PlayerArgumentDispatch[_LargestEventID]; + ItemArgumentHandler ItemArgumentDispatch[_LargestEventID]; + SpellArgumentHandler SpellArgumentDispatch[_LargestEventID]; EncounterArgumentHandler EncounterArgumentDispatch[_LargestEventID]; - BotArgumentHandler BotArgumentDispatch[_LargestEventID]; + BotArgumentHandler BotArgumentDispatch[_LargestEventID]; + ZoneArgumentHandler ZoneArgumentDispatch[_LargestEventID]; }; #endif diff --git a/zone/lua_parser_events.cpp b/zone/lua_parser_events.cpp index fb6cb8708..68dd7ca2a 100644 --- a/zone/lua_parser_events.cpp +++ b/zone/lua_parser_events.cpp @@ -56,6 +56,11 @@ void handle_npc_event_trade( uint32 extra_data, std::vector *extra_pointers ) { + Lua_NPC l_npc(reinterpret_cast(npc)); + luabind::adl::object l_npc_o = luabind::adl::object(L, l_npc); + l_npc_o.push(L); + lua_setfield(L, -2, "self"); + Lua_Client l_client(reinterpret_cast(init)); luabind::adl::object l_client_o = luabind::adl::object(L, l_client); l_client_o.push(L); @@ -102,6 +107,10 @@ void handle_npc_event_trade( lua_pushinteger(L, money_value); lua_setfield(L, -2, "copper"); + // set a reference to the NPC inside the trade object as well for plugins to process + l_npc_o.push(L); + lua_setfield(L, -2, "self"); + // set a reference to the client inside of the trade object as well for plugins to process l_client_o.push(L); lua_setfield(L, -2, "other"); @@ -659,6 +668,23 @@ void handle_npc_spell_blocked( lua_setfield(L, -2, "cast_spell"); } +void handle_npc_pet_command( + QuestInterface *parse, + lua_State* L, + NPC* npc, + Mob *init, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +) +{ + lua_pushinteger(L, extra_data); + lua_setfield(L, -2, "pet_command"); + + lua_pushstring(L, data.c_str()); + lua_setfield(L, -2, "pet_command_name"); +} + // Player void handle_player_say( QuestInterface *parse, @@ -1800,6 +1826,42 @@ void handle_player_read_item( } } +void handle_player_connect( + QuestInterface *parse, + lua_State* L, + Client* client, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +) +{ + Seperator sep(data.c_str()); + lua_pushinteger(L, Strings::ToUnsignedInt(sep.arg[0])); + lua_setfield(L, -2, "last_login"); + + lua_pushinteger(L, Strings::ToUnsignedInt(sep.arg[1])); + lua_setfield(L, -2, "seconds_since_last_login"); + + lua_pushboolean(L, Strings::ToBool(sep.arg[2])); + lua_setfield(L, -2, "is_first_login"); +} + +void handle_player_pet_command( + QuestInterface *parse, + lua_State* L, + Client* client, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +) +{ + lua_pushinteger(L, extra_data); + lua_setfield(L, -2, "pet_command"); + + lua_pushstring(L, data.c_str()); + lua_setfield(L, -2, "pet_command_name"); +} + // Item void handle_item_click( QuestInterface *parse, @@ -2844,4 +2906,293 @@ void handle_bot_spell_blocked( lua_setfield(L, -2, "cast_spell"); } +// Zone + +void handle_zone_null( + QuestInterface *parse, + lua_State* L, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +) { +} + +void handle_zone_click_door( + QuestInterface *parse, + lua_State* L, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +) { + Lua_Door l_door(std::any_cast(extra_pointers->at(0))); + luabind::adl::object l_door_o = luabind::adl::object(L, l_door); + l_door_o.push(L); + lua_setfield(L, -2, "door"); + + Lua_Client l_client(std::any_cast(extra_pointers->at(1))); + luabind::adl::object l_client_o = luabind::adl::object(L, l_client); + l_client_o.push(L); + lua_setfield(L, -2, "other"); +} + +void handle_zone_click_object( + QuestInterface *parse, + lua_State* L, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +) { + Lua_Object l_object(std::any_cast(extra_pointers->at(0))); + luabind::adl::object l_object_o = luabind::adl::object(L, l_object); + l_object_o.push(L); + lua_setfield(L, -2, "object"); + + Lua_Client l_client(std::any_cast(extra_pointers->at(1))); + luabind::adl::object l_client_o = luabind::adl::object(L, l_client); + l_client_o.push(L); + lua_setfield(L, -2, "other"); +} + +void handle_zone_death( + QuestInterface *parse, + lua_State* L, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +) { + Seperator sep(data.c_str()); + + Lua_Mob l_mob(std::any_cast(extra_pointers->at(2))); + luabind::adl::object l_mob_o = luabind::adl::object(L, l_mob); + l_mob_o.push(L); + lua_setfield(L, -2, "other"); + + lua_pushinteger(L, Strings::ToInt(sep.arg[0])); + lua_setfield(L, -2, "killer_id"); + + lua_pushinteger(L, Strings::ToInt(sep.arg[1])); + lua_setfield(L, -2, "damage"); + + const uint32 spell_id = Strings::ToUnsignedInt(sep.arg[2]); + if (IsValidSpell(spell_id)) { + Lua_Spell l_spell(&spells[spell_id]); + luabind::adl::object l_spell_o = luabind::adl::object(L, l_spell); + l_spell_o.push(L); + lua_setfield(L, -2, "spell"); + } else { + Lua_Spell l_spell(nullptr); + luabind::adl::object l_spell_o = luabind::adl::object(L, l_spell); + l_spell_o.push(L); + lua_setfield(L, -2, "spell"); + } + + lua_pushinteger(L, Strings::ToInt(sep.arg[3])); + lua_setfield(L, -2, "skill_id"); + + lua_pushinteger(L, Strings::ToUnsignedInt(sep.arg[4])); + lua_setfield(L, -2, "killed_entity_id"); + + lua_pushinteger(L, Strings::ToUnsignedInt(sep.arg[5])); + lua_setfield(L, -2, "combat_start_time"); + + lua_pushinteger(L, Strings::ToUnsignedInt(sep.arg[6])); + lua_setfield(L, -2, "combat_end_time"); + + lua_pushinteger(L, Strings::ToBigInt(sep.arg[7])); + lua_setfield(L, -2, "damage_received"); + + lua_pushinteger(L, Strings::ToBigInt(sep.arg[8])); + lua_setfield(L, -2, "healing_received"); + + if (extra_pointers && extra_pointers->size() >= 1) { + Lua_Corpse l_corpse(std::any_cast(extra_pointers->at(0))); + luabind::adl::object l_corpse_o = luabind::adl::object(L, l_corpse); + l_corpse_o.push(L); + lua_setfield(L, -2, "corpse"); + } + + if (extra_pointers && extra_pointers->size() >= 2) { + Lua_NPC l_npc(std::any_cast(extra_pointers->at(1))); + luabind::adl::object l_npc_o = luabind::adl::object(L, l_npc); + l_npc_o.push(L); + lua_setfield(L, -2, "killed"); + } +} + +void handle_zone_despawn( + QuestInterface *parse, + lua_State* L, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +) { + Lua_NPC l_npc(std::any_cast(extra_pointers->at(0))); + luabind::adl::object l_npc_o = luabind::adl::object(L, l_npc); + l_npc_o.push(L); + lua_setfield(L, -2, "other"); +} + +void handle_zone_enter( + QuestInterface *parse, + lua_State* L, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +) { + Lua_Client l_client(std::any_cast(extra_pointers->at(0))); + luabind::adl::object l_client_o = luabind::adl::object(L, l_client); + l_client_o.push(L); + lua_setfield(L, -2, "other"); +} + +void handle_zone_loot( + QuestInterface *parse, + lua_State* L, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +) { + Lua_Client l_client(std::any_cast(extra_pointers->at(2))); + luabind::adl::object l_client_o = luabind::adl::object(L, l_client); + l_client_o.push(L); + lua_setfield(L, -2, "other"); + + Lua_ItemInst l_item(std::any_cast(extra_pointers->at(0))); + luabind::adl::object l_item_o = luabind::adl::object(L, l_item); + l_item_o.push(L); + lua_setfield(L, -2, "item"); + + Lua_Corpse l_corpse(std::any_cast(extra_pointers->at(1))); + luabind::adl::object l_corpse_o = luabind::adl::object(L, l_corpse); + l_corpse_o.push(L); + lua_setfield(L, -2, "corpse"); +} + +void handle_zone_payload( + QuestInterface *parse, + lua_State* L, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +) { + Seperator sep(data.c_str()); + + lua_pushinteger(L, Strings::ToInt(sep.arg[0])); + lua_setfield(L, -2, "payload_id"); + + lua_pushstring(L, sep.argplus[1]); + lua_setfield(L, -2, "payload_value"); +} + +void handle_zone_pickup( + QuestInterface *parse, + lua_State* L, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +) { + Lua_ItemInst l_item(std::any_cast(extra_pointers->at(0))); + luabind::adl::object l_item_o = luabind::adl::object(L, l_item); + l_item_o.push(L); + lua_setfield(L, -2, "item"); + + Lua_Client l_client(std::any_cast(extra_pointers->at(1))); + luabind::adl::object l_client_o = luabind::adl::object(L, l_client); + l_client_o.push(L); + lua_setfield(L, -2, "other"); +} + +void handle_zone_popup( + QuestInterface *parse, + lua_State* L, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +) { + lua_pushinteger(L, Strings::ToInt(data)); + lua_setfield(L, -2, "popup_id"); + + Lua_Client l_client(std::any_cast(extra_pointers->at(0))); + luabind::adl::object l_client_o = luabind::adl::object(L, l_client); + l_client_o.push(L); + lua_setfield(L, -2, "other"); +} + +void handle_zone_signal( + QuestInterface *parse, + lua_State* L, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +) { + lua_pushinteger(L, Strings::ToInt(data)); + lua_setfield(L, -2, "signal"); +} + +void handle_zone_spawn( + QuestInterface *parse, + lua_State* L, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +) { + Lua_NPC l_npc(std::any_cast(extra_pointers->at(0))); + luabind::adl::object l_npc_o = luabind::adl::object(L, l_npc); + l_npc_o.push(L); + lua_setfield(L, -2, "other"); +} + +void handle_zone_timer( + QuestInterface *parse, + lua_State* L, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +) { + lua_pushstring(L, data.c_str()); + lua_setfield(L, -2, "timer"); +} + +void handle_zone_timer_pause_resume_start( + QuestInterface *parse, + lua_State* L, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +) { + Seperator sep(data.c_str()); + + lua_pushstring(L, sep.arg[0]); + lua_setfield(L, -2, "timer"); + + lua_pushinteger(L, Strings::ToUnsignedInt(sep.arg[1])); + lua_setfield(L, -2, "duration"); +} + +void handle_zone_timer_stop( + QuestInterface *parse, + lua_State* L, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +) { + lua_pushstring(L, data.c_str()); + lua_setfield(L, -2, "timer"); +} + #endif diff --git a/zone/lua_parser_events.h b/zone/lua_parser_events.h index 4a9a1bbb9..79135bd6e 100644 --- a/zone/lua_parser_events.h +++ b/zone/lua_parser_events.h @@ -9,6 +9,7 @@ typedef void(*SpellArgumentHandler)(QuestInterface*, lua_State*, Mob*, Client*, typedef void(*EncounterArgumentHandler)(QuestInterface*, lua_State*, Encounter* encounter, std::string, uint32, std::vector*); typedef void(*BotArgumentHandler)(QuestInterface*, lua_State*, Bot*, Mob*, std::string, uint32, std::vector*); typedef void(*MercArgumentHandler)(QuestInterface*, lua_State*, Merc*, Mob*, std::string, uint32, std::vector*); +typedef void(*ZoneArgumentHandler)(QuestInterface*, lua_State*, Zone*, std::string, uint32, std::vector*); // NPC void handle_npc_event_say( @@ -261,6 +262,16 @@ void handle_npc_spell_blocked( std::vector *extra_pointers ); +void handle_npc_pet_command( + QuestInterface *parse, + lua_State* L, + NPC* npc, + Mob *init, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +); + // Player void handle_player_say( QuestInterface *parse, @@ -865,6 +876,24 @@ void handle_player_read_item( std::vector *extra_pointers ); +void handle_player_connect( + QuestInterface *parse, + lua_State* L, + Client* client, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +); + +void handle_player_pet_command( + QuestInterface *parse, + lua_State* L, + Client* client, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +); + // Item void handle_item_click( QuestInterface *parse, @@ -1269,5 +1298,141 @@ void handle_bot_spell_blocked( std::vector *extra_pointers ); +// Zone +void handle_zone_null( + QuestInterface *parse, + lua_State* L, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +); + +void handle_zone_click_door( + QuestInterface *parse, + lua_State* L, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +); + +void handle_zone_click_object( + QuestInterface *parse, + lua_State* L, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +); + +void handle_zone_death( + QuestInterface *parse, + lua_State* L, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +); + +void handle_zone_despawn( + QuestInterface *parse, + lua_State* L, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +); + +void handle_zone_enter( + QuestInterface *parse, + lua_State* L, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +); + +void handle_zone_loot( + QuestInterface *parse, + lua_State* L, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +); + +void handle_zone_payload( + QuestInterface *parse, + lua_State* L, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +); + +void handle_zone_pickup( + QuestInterface *parse, + lua_State* L, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +); + +void handle_zone_popup( + QuestInterface *parse, + lua_State* L, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +); + +void handle_zone_signal( + QuestInterface *parse, + lua_State* L, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +); + +void handle_zone_spawn( + QuestInterface *parse, + lua_State* L, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +); + +void handle_zone_timer( + QuestInterface *parse, + lua_State* L, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +); + +void handle_zone_timer_pause_resume_start( + QuestInterface *parse, + lua_State* L, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +); + +void handle_zone_timer_stop( + QuestInterface *parse, + lua_State* L, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +); + #endif #endif diff --git a/zone/lua_raid.cpp b/zone/lua_raid.cpp index a5f05f00f..46290a861 100644 --- a/zone/lua_raid.cpp +++ b/zone/lua_raid.cpp @@ -157,13 +157,13 @@ int Lua_Raid::GetGroupNumber(int member_index) { bool Lua_Raid::DoesAnyMemberHaveExpeditionLockout(std::string expedition_name, std::string event_name) { Lua_Safe_Call_Bool(); - return self->DoesAnyMemberHaveExpeditionLockout(expedition_name, event_name); + return self->AnyMemberHasDzLockout(expedition_name, event_name); } bool Lua_Raid::DoesAnyMemberHaveExpeditionLockout(std::string expedition_name, std::string event_name, int max_check_count) { Lua_Safe_Call_Bool(); - return self->DoesAnyMemberHaveExpeditionLockout(expedition_name, event_name, max_check_count); + return self->AnyMemberHasDzLockout(expedition_name, event_name); // max_check_count deprecated } luabind::scope lua_register_raid() { diff --git a/zone/lua_zone.cpp b/zone/lua_zone.cpp new file mode 100644 index 000000000..037571620 --- /dev/null +++ b/zone/lua_zone.cpp @@ -0,0 +1,1014 @@ +#include "../common/features.h" +#include "zone.h" + + +#include +#include "../common/global_define.h" +#include "lua_zone.h" + +bool Lua_Zone::BuffTimersSuspended() +{ + Lua_Safe_Call_Bool(); + return self->BuffTimersSuspended(); +} + +bool Lua_Zone::BypassesExpansionCheck() +{ + Lua_Safe_Call_Bool(); + return ZoneStore::Instance()->GetZoneBypassExpansionCheck(self->GetZoneID(), self->GetInstanceVersion()); +} + +bool Lua_Zone::CanBind() +{ + Lua_Safe_Call_Bool(); + return self->CanBind(); +} + +bool Lua_Zone::CanCastOutdoor() +{ + Lua_Safe_Call_Bool(); + return self->CanCastOutdoor(); +} + +bool Lua_Zone::CanDoCombat() +{ + Lua_Safe_Call_Bool(); + return self->CanDoCombat(); +} + +bool Lua_Zone::CanLevitate() +{ + Lua_Safe_Call_Bool(); + return self->CanLevitate(); +} + +void Lua_Zone::ClearSpawnTimers() +{ + Lua_Safe_Call_Void(); + self->ClearSpawnTimers(); +} + +void Lua_Zone::Depop() +{ + Lua_Safe_Call_Void(); + self->Depop(); +} + +void Lua_Zone::Depop(bool start_spawn_timers) +{ + Lua_Safe_Call_Void(); + self->Depop(start_spawn_timers); +} + +void Lua_Zone::Despawn(uint32 spawngroup_id) +{ + Lua_Safe_Call_Void(); + self->Despawn(spawngroup_id); +} + +void Lua_Zone::DisableRespawnTimers() +{ + Lua_Safe_Call_Void(); + self->DisableRespawnTimers(); +} + +float Lua_Zone::GetAAEXPModifier(Lua_Client c) +{ + Lua_Safe_Call_Real(); + return self->GetAAEXPModifier(c); +} + +float Lua_Zone::GetAAEXPModifierByCharacterID(uint32 character_id) +{ + Lua_Safe_Call_Real(); + return self->GetAAEXPModifierByCharacterID(character_id); +} + +std::string Lua_Zone::GetContentFlags() +{ + Lua_Safe_Call_String(); + return ZoneStore::Instance()->GetZoneContentFlags(self->GetZoneID(), self->GetInstanceVersion()); +} + +std::string Lua_Zone::GetContentFlagsDisabled() +{ + Lua_Safe_Call_String(); + return ZoneStore::Instance()->GetZoneContentFlagsDisabled(self->GetZoneID(), self->GetInstanceVersion()); +} + +float Lua_Zone::GetExperienceMultiplier() +{ + Lua_Safe_Call_Real(); + return ZoneStore::Instance()->GetZoneExperienceMultiplier(self->GetZoneID(), self->GetInstanceVersion()); +} + +int8 Lua_Zone::GetExpansion() +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneExpansion(self->GetZoneID(), self->GetInstanceVersion()); +} + +float Lua_Zone::GetEXPModifier(Lua_Client c) +{ + Lua_Safe_Call_Real(); + return self->GetEXPModifier(c); +} + +float Lua_Zone::GetEXPModifierByCharacterID(uint32 character_id) +{ + Lua_Safe_Call_Real(); + return self->GetEXPModifierByCharacterID(character_id); +} + +int Lua_Zone::GetFastRegenEndurance() +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneFastRegenEndurance(self->GetZoneID(), self->GetInstanceVersion()); +} + +int Lua_Zone::GetFastRegenHP() +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneFastRegenHP(self->GetZoneID(), self->GetInstanceVersion()); +} + +int Lua_Zone::GetFastRegenMana() +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneFastRegenMana(self->GetZoneID(), self->GetInstanceVersion()); +} + +std::string Lua_Zone::GetFileName() +{ + Lua_Safe_Call_String(); + return self->GetFileName(); +} + +std::string Lua_Zone::GetFlagNeeded() +{ + Lua_Safe_Call_String(); + return ZoneStore::Instance()->GetZoneFlagNeeded(self->GetZoneID(), self->GetInstanceVersion()); +} + +uint8 Lua_Zone::GetFogBlue() +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneFogBlue(self->GetZoneID(), 0, self->GetInstanceVersion()); +} + +uint8 Lua_Zone::GetFogBlue(uint8 slot) +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneFogBlue(self->GetZoneID(), slot, self->GetInstanceVersion()); +} + +float Lua_Zone::GetFogDensity() +{ + Lua_Safe_Call_Real(); + return ZoneStore::Instance()->GetZoneFogDensity(self->GetZoneID(), self->GetInstanceVersion()); +} + +uint8 Lua_Zone::GetFogGreen() +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneFogGreen(self->GetZoneID(), 0, self->GetInstanceVersion()); +} + +uint8 Lua_Zone::GetFogGreen(uint8 slot) +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneFogGreen(self->GetZoneID(), slot, self->GetInstanceVersion()); +} + +float Lua_Zone::GetFogMaximumClip() +{ + Lua_Safe_Call_Real(); + return ZoneStore::Instance()->GetZoneFogMaximumClip(self->GetZoneID(), 0, self->GetInstanceVersion()); +} + +float Lua_Zone::GetFogMaximumClip(uint8 slot) +{ + Lua_Safe_Call_Real(); + return ZoneStore::Instance()->GetZoneFogMaximumClip(self->GetZoneID(), slot, self->GetInstanceVersion()); +} + +float Lua_Zone::GetFogMinimumClip() +{ + Lua_Safe_Call_Real(); + return ZoneStore::Instance()->GetZoneFogMinimumClip(self->GetZoneID(), 0, self->GetInstanceVersion()); +} + +float Lua_Zone::GetFogMinimumClip(uint8 slot) +{ + Lua_Safe_Call_Real(); + return ZoneStore::Instance()->GetZoneFogMinimumClip(self->GetZoneID(), slot, self->GetInstanceVersion()); +} + +uint8 Lua_Zone::GetFogRed() +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneFogRed(self->GetZoneID(), 0, self->GetInstanceVersion()); +} + +uint8 Lua_Zone::GetFogRed(uint8 slot) +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneFogRed(self->GetZoneID(), slot, self->GetInstanceVersion()); +} + +float Lua_Zone::GetGraveyardHeading() +{ + Lua_Safe_Call_Real(); + return self->GetGraveyardPoint().w; +} + +uint32 Lua_Zone::GetGraveyardID() +{ + Lua_Safe_Call_Int(); + return self->graveyard_id(); +} + +float Lua_Zone::GetGraveyardX() +{ + Lua_Safe_Call_Real(); + return self->GetGraveyardPoint().x; +} + +float Lua_Zone::GetGraveyardY() +{ + Lua_Safe_Call_Real(); + return self->GetGraveyardPoint().y; +} + +float Lua_Zone::GetGraveyardZ() +{ + Lua_Safe_Call_Real(); + return self->GetGraveyardPoint().z; +} + +uint32 Lua_Zone::GetGraveyardZoneID() +{ + Lua_Safe_Call_Int(); + return self->graveyard_zoneid(); +} + +float Lua_Zone::GetGravity() +{ + Lua_Safe_Call_Real(); + return ZoneStore::Instance()->GetZoneGravity(self->GetZoneID(), self->GetInstanceVersion()); +} + +uint32 Lua_Zone::GetInstanceID() +{ + Lua_Safe_Call_Int(); + return self->GetInstanceID(); +} + +uint8 Lua_Zone::GetInstanceType() +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneInstanceType(self->GetZoneID(), self->GetInstanceVersion()); +} + +uint16 Lua_Zone::GetInstanceVersion() +{ + Lua_Safe_Call_Int(); + return self->GetInstanceVersion(); +} + +uint32 Lua_Zone::GetInstanceTimeRemaining() +{ + Lua_Safe_Call_Int(); + return self->GetInstanceTimeRemaining(); +} + +int Lua_Zone::GetLavaDamage() +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneLavaDamage(self->GetZoneID(), self->GetInstanceVersion()); +} + +std::string Lua_Zone::GetLongName() +{ + Lua_Safe_Call_String(); + return self->GetLongName(); +} + +float Lua_Zone::GetMaximumClip() +{ + Lua_Safe_Call_Real(); + return ZoneStore::Instance()->GetZoneMaximumClip(self->GetZoneID(), self->GetInstanceVersion()); +} + +int8 Lua_Zone::GetMaximumExpansion() +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneMaximumExpansion(self->GetZoneID(), self->GetInstanceVersion()); +} + +uint8 Lua_Zone::GetMaximumLevel() +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneMaximumLevel(self->GetZoneID(), self->GetInstanceVersion()); +} + +uint32 Lua_Zone::GetMaxClients() +{ + Lua_Safe_Call_Int(); + return self->GetMaxClients(); +} + +float Lua_Zone::GetMinimumClip() +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneMinimumClip(self->GetZoneID(), self->GetInstanceVersion()); +} + +int8 Lua_Zone::GetMinimumExpansion() +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneMinimumExpansion(self->GetZoneID(), self->GetInstanceVersion()); +} + +uint8 Lua_Zone::GetMinimumLevel() +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneMinimumLevel(self->GetZoneID(), self->GetInstanceVersion()); +} + +int Lua_Zone::GetMinimumLavaDamage() +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneMinimumLavaDamage(self->GetZoneID(), self->GetInstanceVersion()); +} + +uint8 Lua_Zone::GetMinimumStatus() +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneMinimumStatus(self->GetZoneID(), self->GetInstanceVersion()); +} + +std::string Lua_Zone::GetNote() +{ + Lua_Safe_Call_String(); + return ZoneStore::Instance()->GetZoneNote(self->GetZoneID(), self->GetInstanceVersion()); +} + +int Lua_Zone::GetNPCMaximumAggroDistance() +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneNPCMaximumAggroDistance(self->GetZoneID(), self->GetInstanceVersion()); +} + +int8 Lua_Zone::GetPEQZone() +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZonePEQZone(self->GetZoneID(), self->GetInstanceVersion()); +} + +int Lua_Zone::GetRainChance() +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneRainChance(self->GetZoneID(), 0, self->GetInstanceVersion()); +} + +int Lua_Zone::GetRainChance(uint8 slot) +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneRainChance(self->GetZoneID(), slot, self->GetInstanceVersion()); +} + +int Lua_Zone::GetRainDuration() +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneRainDuration(self->GetZoneID(), 0, self->GetInstanceVersion()); +} + +int Lua_Zone::GetRainDuration(uint8 slot) +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneRainDuration(self->GetZoneID(), slot, self->GetInstanceVersion()); +} + +uint32 Lua_Zone::GetRuleSet() +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneRuleSet(self->GetZoneID(), self->GetInstanceVersion()); +} + +float Lua_Zone::GetSafeHeading() +{ + Lua_Safe_Call_Real(); + return self->GetSafePoint().w; +} + +float Lua_Zone::GetSafeX() +{ + Lua_Safe_Call_Real(); + return self->GetSafePoint().x; +} + +float Lua_Zone::GetSafeY() +{ + Lua_Safe_Call_Real(); + return self->GetSafePoint().y; +} + +float Lua_Zone::GetSafeZ() +{ + Lua_Safe_Call_Real(); + return self->GetSafePoint().z; +} + +std::string Lua_Zone::GetShortName() +{ + Lua_Safe_Call_String(); + return self->GetShortName(); +} + +uint32 Lua_Zone::GetSecondsBeforeIdle() +{ + Lua_Safe_Call_Int(); + return self->GetSecondsBeforeIdle(); +} + +uint64 Lua_Zone::GetShutdownDelay() +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneShutdownDelay(self->GetZoneID(), self->GetInstanceVersion()); +} + +uint8 Lua_Zone::GetSky() +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneSky(self->GetZoneID(), self->GetInstanceVersion()); +} + +int8 Lua_Zone::GetSkyLock() +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneSkyLock(self->GetZoneID(), self->GetInstanceVersion()); +} + +int Lua_Zone::GetSnowChance() +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneSnowChance(self->GetZoneID(), 0, self->GetInstanceVersion()); +} + +int Lua_Zone::GetSnowChance(uint8 slot) +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneSnowChance(self->GetZoneID(), slot, self->GetInstanceVersion()); +} + +int Lua_Zone::GetSnowDuration() +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneSnowDuration(self->GetZoneID(), 0, self->GetInstanceVersion()); +} + +int Lua_Zone::GetSnowDuration(uint8 slot) +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneSnowDuration(self->GetZoneID(), slot, self->GetInstanceVersion()); +} + +uint8 Lua_Zone::GetTimeType() +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneTimeType(self->GetZoneID(), self->GetInstanceVersion()); +} + +int Lua_Zone::GetTimeZone() +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneTimeZone(self->GetZoneID(), self->GetInstanceVersion()); +} + +std::string Lua_Zone::GetZoneDescription() +{ + Lua_Safe_Call_String(); + return self->GetZoneDescription(); +} + +uint32 Lua_Zone::GetZoneID() +{ + Lua_Safe_Call_Int(); + return self->GetZoneID(); +} + +uint8 Lua_Zone::GetZoneType() +{ + Lua_Safe_Call_Int(); + return self->GetZoneType(); +} + +float Lua_Zone::GetUnderworld() +{ + Lua_Safe_Call_Real(); + return ZoneStore::Instance()->GetZoneUnderworld(self->GetZoneID(), self->GetInstanceVersion()); +} + +int Lua_Zone::GetUnderworldTeleportIndex() +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneUnderworldTeleportIndex(self->GetZoneID(), self->GetInstanceVersion()); +} + +float Lua_Zone::GetWalkSpeed() +{ + Lua_Safe_Call_Real(); + return ZoneStore::Instance()->GetZoneWalkSpeed(self->GetZoneID(), self->GetInstanceVersion()); +} + +uint8 Lua_Zone::GetZoneZType() +{ + Lua_Safe_Call_Int(); + return ZoneStore::Instance()->GetZoneZType(self->GetZoneID(), self->GetInstanceVersion()); +} + +int Lua_Zone::GetZoneTotalBlockedSpells() +{ + Lua_Safe_Call_Int(); + return self->GetZoneTotalBlockedSpells(); +} + +bool Lua_Zone::HasGraveyard() +{ + Lua_Safe_Call_Bool(); + return self->HasGraveyard(); +} + +bool Lua_Zone::HasMap() +{ + Lua_Safe_Call_Bool(); + return self->HasMap(); +} + +bool Lua_Zone::HasWaterMap() +{ + Lua_Safe_Call_Bool(); + return self->HasWaterMap(); +} + +bool Lua_Zone::HasWeather() +{ + Lua_Safe_Call_Bool(); + return self->HasWeather(); +} + +bool Lua_Zone::IsCity() +{ + Lua_Safe_Call_Bool(); + return self->IsCity(); +} + +bool Lua_Zone::IsHotzone() +{ + Lua_Safe_Call_Bool(); + return self->IsHotzone(); +} + +bool Lua_Zone::IsInstancePersistent() +{ + Lua_Safe_Call_Bool(); + return self->IsInstancePersistent(); +} + +bool Lua_Zone::IsIdleWhenEmpty() +{ + Lua_Safe_Call_Bool(); + return self->IsIdleWhenEmpty(); +} + +bool Lua_Zone::IsPVPZone() +{ + Lua_Safe_Call_Bool(); + return self->IsPVPZone(); +} + +bool Lua_Zone::IsRaining() +{ + Lua_Safe_Call_Bool(); + return self->IsRaining(); +} + +bool Lua_Zone::IsSnowing() +{ + Lua_Safe_Call_Bool(); + return self->IsSnowing(); +} + +bool Lua_Zone::IsSpecialBindLocation(float x, float y, float z, float heading) +{ + Lua_Safe_Call_Bool(); + return self->IsSpecialBindLocation(glm::vec4(x, y, z, heading)); +} + +bool Lua_Zone::IsSpellBlocked(uint32 spell_id, float x, float y, float z) +{ + Lua_Safe_Call_Bool(); + return self->IsSpellBlocked(spell_id, glm::vec3(x, y, z)); +} + +bool Lua_Zone::IsStaticZone() +{ + Lua_Safe_Call_Bool(); + return self->IsStaticZone(); +} + +bool Lua_Zone::IsUCSServerAvailable() +{ + Lua_Safe_Call_Bool(); + return self->IsUCSServerAvailable(); +} + +bool Lua_Zone::IsWaterZone(float z) +{ + Lua_Safe_Call_Bool(); + return self->IsWaterZone(z); +} + +void Lua_Zone::Repop() +{ + Lua_Safe_Call_Void(); + self->Repop(); +} + +void Lua_Zone::Repop(bool is_forced) +{ + Lua_Safe_Call_Void(); + self->Repop(is_forced); +} + +void Lua_Zone::SetAAEXPModifier(Lua_Client c, float aa_modifier) +{ + Lua_Safe_Call_Void(); + self->SetAAEXPModifier(c, aa_modifier); +} + +void Lua_Zone::SetAAEXPModifierByCharacterID(uint32 character_id, float aa_modifier) +{ + Lua_Safe_Call_Void(); + self->SetAAEXPModifierByCharacterID(character_id, aa_modifier); +} + +void Lua_Zone::SetEXPModifier(Lua_Client c, float exp_modifier) +{ + Lua_Safe_Call_Void(); + self->SetAAEXPModifier(c, exp_modifier); +} + +void Lua_Zone::SetEXPModifierByCharacterID(uint32 character_id, float exp_modifier) +{ + Lua_Safe_Call_Void(); + self->SetAAEXPModifierByCharacterID(character_id, exp_modifier); +} + +void Lua_Zone::SetInstanceTimer(uint32 new_duration) +{ + Lua_Safe_Call_Void(); + self->SetInstanceTimer(new_duration); +} + +void Lua_Zone::SetInstanceTimeRemaining(uint32 time_remaining) +{ + Lua_Safe_Call_Void(); + self->SetInstanceTimeRemaining(time_remaining); +} + +void Lua_Zone::SetIsHotzone(bool is_hotzone) +{ + Lua_Safe_Call_Void(); + self->SetIsHotzone(is_hotzone); +} + +void Lua_Zone::ShowZoneGlobalLoot(Lua_Client c) +{ + Lua_Safe_Call_Void(); + self->ShowZoneGlobalLoot(c); +} + +void Lua_Zone::SetBucket(const std::string& bucket_name, const std::string& bucket_value) +{ + Lua_Safe_Call_Void(); + self->SetBucket(bucket_name, bucket_value); +} + +void Lua_Zone::SetBucket(const std::string& bucket_name, const std::string& bucket_value, const std::string& expiration) +{ + Lua_Safe_Call_Void(); + self->SetBucket(bucket_name, bucket_value, expiration); +} + +void Lua_Zone::DeleteBucket(const std::string& bucket_name) +{ + Lua_Safe_Call_Void(); + self->DeleteBucket(bucket_name); +} + +std::string Lua_Zone::GetBucket(const std::string& bucket_name) +{ + Lua_Safe_Call_String(); + return self->GetBucket(bucket_name); +} + +std::string Lua_Zone::GetBucketExpires(const std::string& bucket_name) +{ + Lua_Safe_Call_String(); + return self->GetBucketExpires(bucket_name); +} + +std::string Lua_Zone::GetBucketRemaining(const std::string& bucket_name) +{ + Lua_Safe_Call_String(); + return self->GetBucketRemaining(bucket_name); +} + +bool Lua_Zone::ClearVariables() +{ + Lua_Safe_Call_Bool(); + return self->ClearVariables(); +} + +bool Lua_Zone::DeleteVariable(const std::string& variable_name) +{ + Lua_Safe_Call_Bool(); + return self->DeleteVariable(variable_name); +} + +std::string Lua_Zone::GetVariable(const std::string& variable_name) +{ + Lua_Safe_Call_String(); + return self->GetVariable(variable_name); +} + +luabind::object Lua_Zone::GetVariables(lua_State* L) +{ + auto t = luabind::newtable(L); + if (d_) { + auto self = reinterpret_cast(d_); + auto l = self->GetVariables(); + int i = 1; + for (const auto& v : l) { + t[i] = v; + i++; + } + } + + return t; +} + +void Lua_Zone::SetVariable(const std::string& variable_name, const std::string& variable_value) +{ + Lua_Safe_Call_Void(); + self->SetVariable(variable_name, variable_value); +} + +bool Lua_Zone::VariableExists(const std::string& variable_name) +{ + Lua_Safe_Call_Bool(); + return self->VariableExists(variable_name); +} + +uint32 Lua_Zone::GetTimerDuration(std::string name) +{ + Lua_Safe_Call_Int(); + return self->GetTimerDuration(name); +} + +uint32 Lua_Zone::GetTimerRemainingTime(std::string name) +{ + Lua_Safe_Call_Int(); + return self->GetTimerRemainingTime(name); +} + +bool Lua_Zone::HasTimer(std::string name) +{ + Lua_Safe_Call_Bool(); + return self->HasTimer(name); +} + +bool Lua_Zone::IsPausedTimer(std::string name) +{ + Lua_Safe_Call_Bool(); + return self->IsPausedTimer(name); +} + +void Lua_Zone::PauseTimer(std::string name) +{ + Lua_Safe_Call_Void(); + self->PauseTimer(name); +} + +void Lua_Zone::ResumeTimer(std::string name) +{ + Lua_Safe_Call_Void(); + self->ResumeTimer(name); +} + +void Lua_Zone::SetTimer(std::string name, uint32 duration) +{ + Lua_Safe_Call_Void(); + self->SetTimer(name, duration); +} + +void Lua_Zone::StopTimer(std::string name) +{ + Lua_Safe_Call_Void(); + self->StopTimer(name); +} + +void Lua_Zone::StopAllTimers() +{ + Lua_Safe_Call_Void(); + self->StopAllTimers(); +} + +void Lua_Zone::SendPayload(int payload_id, std::string payload_value) +{ + Lua_Safe_Call_Void(); + self->SendPayload(payload_id, payload_value); +} + +void Lua_Zone::Signal(int signal_id) +{ + Lua_Safe_Call_Void(); + self->Signal(signal_id); +} + +luabind::object Lua_Zone::GetPausedTimers(lua_State* L) { + auto t = luabind::newtable(L); + if (d_) { + auto self = reinterpret_cast(d_); + auto l = self->GetPausedTimers(); + int i = 1; + for (const auto& v : l) { + t[i] = v; + i++; + } + } + + return t; +} + +luabind::object Lua_Zone::GetTimers(lua_State* L) { + auto t = luabind::newtable(L); + if (d_) { + auto self = reinterpret_cast(d_); + auto l = self->GetTimers(); + int i = 1; + for (const auto& v : l) { + t[i] = v; + i++; + } + } + + return t; +} + +luabind::scope lua_register_zone() { + return luabind::class_("Zones") + .def(luabind::constructor<>()) + .def("BuffTimersSuspended", &Lua_Zone::BuffTimersSuspended) + .def("BypassesExpansionCheck", &Lua_Zone::BypassesExpansionCheck) + .def("CanBind", &Lua_Zone::CanBind) + .def("CanCastOutdoor", &Lua_Zone::CanCastOutdoor) + .def("CanDoCombat", &Lua_Zone::CanDoCombat) + .def("CanLevitate", &Lua_Zone::CanLevitate) + .def("ClearSpawnTimers", &Lua_Zone::ClearSpawnTimers) + .def("ClearVariables", &Lua_Zone::ClearVariables) + .def("DeleteBucket", (void(Lua_Zone::*)(const std::string&))&Lua_Zone::DeleteBucket) + .def("DeleteVariable", &Lua_Zone::DeleteVariable) + .def("Depop", (void(Lua_Zone::*)(void))&Lua_Zone::Depop) + .def("Depop", (void(Lua_Zone::*)(bool))&Lua_Zone::Depop) + .def("Despawn", &Lua_Zone::Despawn) + .def("DisableRespawnTimers", &Lua_Zone::DisableRespawnTimers) + .def("GetAAEXPModifier", &Lua_Zone::GetAAEXPModifier) + .def("GetAAEXPModifierByCharacterID", &Lua_Zone::GetAAEXPModifierByCharacterID) + .def("GetBucket", (std::string(Lua_Zone::*)(const std::string&))&Lua_Zone::GetBucket) + .def("GetBucketExpires", (std::string(Lua_Zone::*)(const std::string&))&Lua_Zone::GetBucketExpires) + .def("GetBucketRemaining", (std::string(Lua_Zone::*)(const std::string&))&Lua_Zone::GetBucketRemaining) + .def("GetContentFlags", &Lua_Zone::GetContentFlags) + .def("GetContentFlagsDisabled", &Lua_Zone::GetContentFlagsDisabled) + .def("GetExperienceMultiplier", &Lua_Zone::GetExperienceMultiplier) + .def("GetExpansion", &Lua_Zone::GetExpansion) + .def("GetEXPModifier", &Lua_Zone::GetEXPModifier) + .def("GetEXPModifierByCharacterID", &Lua_Zone::GetEXPModifierByCharacterID) + .def("GetFastRegenEndurance", &Lua_Zone::GetFastRegenEndurance) + .def("GetFastRegenHP", &Lua_Zone::GetFastRegenHP) + .def("GetFastRegenMana", &Lua_Zone::GetFastRegenMana) + .def("GetFileName", &Lua_Zone::GetFileName) + .def("GetFlagNeeded", &Lua_Zone::GetFlagNeeded) + .def("GetFogBlue", (uint8(Lua_Zone::*)(void))&Lua_Zone::GetFogBlue) + .def("GetFogBlue", (uint8(Lua_Zone::*)(uint8))&Lua_Zone::GetFogBlue) + .def("GetFogDensity", &Lua_Zone::GetFogDensity) + .def("GetFogGreen", (uint8(Lua_Zone::*)(void))&Lua_Zone::GetFogGreen) + .def("GetFogGreen", (uint8(Lua_Zone::*)(uint8))&Lua_Zone::GetFogGreen) + .def("GetFogMaximumClip", (float(Lua_Zone::*)(void))&Lua_Zone::GetFogMaximumClip) + .def("GetFogMaximumClip", (float(Lua_Zone::*)(uint8))&Lua_Zone::GetFogMaximumClip) + .def("GetFogMinimumClip", (float(Lua_Zone::*)(void))&Lua_Zone::GetFogMinimumClip) + .def("GetFogMinimumClip", (float(Lua_Zone::*)(uint8))&Lua_Zone::GetFogMinimumClip) + .def("GetFogRed", (uint8(Lua_Zone::*)(void))&Lua_Zone::GetFogRed) + .def("GetFogRed", (uint8(Lua_Zone::*)(uint8))&Lua_Zone::GetFogRed) + .def("GetGraveyardHeading", &Lua_Zone::GetGraveyardHeading) + .def("GetGraveyardID", &Lua_Zone::GetGraveyardID) + .def("GetGraveyardX", &Lua_Zone::GetGraveyardX) + .def("GetGraveyardY", &Lua_Zone::GetGraveyardY) + .def("GetGraveyardZ", &Lua_Zone::GetGraveyardZ) + .def("GetGraveyardZoneID", &Lua_Zone::GetGraveyardZoneID) + .def("GetGravity", &Lua_Zone::GetGravity) + .def("GetInstanceID", &Lua_Zone::GetInstanceID) + .def("GetInstanceType", &Lua_Zone::GetInstanceType) + .def("GetInstanceVersion", &Lua_Zone::GetInstanceVersion) + .def("GetInstanceTimeRemaining", &Lua_Zone::GetInstanceTimeRemaining) + .def("GetLavaDamage", &Lua_Zone::GetLavaDamage) + .def("GetLongName", &Lua_Zone::GetLongName) + .def("GetMaximumClip", &Lua_Zone::GetMaximumClip) + .def("GetMaximumExpansion", &Lua_Zone::GetMaximumExpansion) + .def("GetMaximumLevel", &Lua_Zone::GetMaximumLevel) + .def("GetMaxClients", &Lua_Zone::GetMaxClients) + .def("GetMinimumClip", &Lua_Zone::GetMinimumClip) + .def("GetMinimumExpansion", &Lua_Zone::GetMinimumExpansion) + .def("GetMinimumLevel", &Lua_Zone::GetMinimumLevel) + .def("GetMinimumLavaDamage", &Lua_Zone::GetMinimumLavaDamage) + .def("GetMinimumStatus", &Lua_Zone::GetMinimumStatus) + .def("GetNote", &Lua_Zone::GetNote) + .def("GetNPCMaximumAggroDistance", &Lua_Zone::GetNPCMaximumAggroDistance) + .def("GetPausedTimers", &Lua_Zone::GetPausedTimers) + .def("GetPEQZone", &Lua_Zone::GetPEQZone) + .def("GetRainChance", (int(Lua_Zone::*)(void))&Lua_Zone::GetRainChance) + .def("GetRainChance", (int(Lua_Zone::*)(uint8))&Lua_Zone::GetRainChance) + .def("GetRainDuration", (int(Lua_Zone::*)(void))&Lua_Zone::GetRainDuration) + .def("GetRainDuration", (int(Lua_Zone::*)(uint8))&Lua_Zone::GetRainDuration) + .def("GetRuleSet", &Lua_Zone::GetRuleSet) + .def("GetSafeHeading", &Lua_Zone::GetSafeHeading) + .def("GetSafeX", &Lua_Zone::GetSafeX) + .def("GetSafeY", &Lua_Zone::GetSafeY) + .def("GetSafeZ", &Lua_Zone::GetSafeZ) + .def("GetShortName", &Lua_Zone::GetShortName) + .def("GetSecondsBeforeIdle", &Lua_Zone::GetSecondsBeforeIdle) + .def("GetShutdownDelay", &Lua_Zone::GetShutdownDelay) + .def("GetSky", &Lua_Zone::GetSky) + .def("GetSkyLock", &Lua_Zone::GetSkyLock) + .def("GetSnowChance", (int(Lua_Zone::*)(void))&Lua_Zone::GetSnowChance) + .def("GetSnowChance", (int(Lua_Zone::*)(uint8))&Lua_Zone::GetSnowChance) + .def("GetSnowDuration", (int(Lua_Zone::*)(void))&Lua_Zone::GetSnowDuration) + .def("GetSnowDuration", (int(Lua_Zone::*)(uint8))&Lua_Zone::GetSnowDuration) + .def("GetTimeType", &Lua_Zone::GetTimeType) + .def("GetTimeZone", &Lua_Zone::GetTimeZone) + .def("GetTimerDuration", &Lua_Zone::GetTimerDuration) + .def("GetTimerRemainingTime", &Lua_Zone::GetTimerRemainingTime) + .def("GetTimers", &Lua_Zone::GetTimers) + .def("GetZoneDescription", &Lua_Zone::GetZoneDescription) + .def("GetZoneID", &Lua_Zone::GetZoneID) + .def("GetZoneType", &Lua_Zone::GetZoneType) + .def("GetUnderworld", &Lua_Zone::GetUnderworld) + .def("GetUnderworldTeleportIndex", &Lua_Zone::GetUnderworldTeleportIndex) + .def("GetVariable", &Lua_Zone::GetVariable) + .def("GetVariables", &Lua_Zone::GetVariables) + .def("GetWalkSpeed", &Lua_Zone::GetWalkSpeed) + .def("GetZoneZType", &Lua_Zone::GetZoneZType) + .def("GetZoneTotalBlockedSpells", &Lua_Zone::GetZoneTotalBlockedSpells) + .def("HasGraveyard", &Lua_Zone::HasGraveyard) + .def("HasMap", &Lua_Zone::HasMap) + .def("HasWaterMap", &Lua_Zone::HasWaterMap) + .def("HasWeather", &Lua_Zone::HasWeather) + .def("HasTimer", &Lua_Zone::HasTimer) + .def("IsCity", &Lua_Zone::IsCity) + .def("IsHotzone", &Lua_Zone::IsHotzone) + .def("IsInstancePersistent", &Lua_Zone::IsInstancePersistent) + .def("IsIdleWhenEmpty", &Lua_Zone::IsIdleWhenEmpty) + .def("IsPausedTimer", &Lua_Zone::IsPausedTimer) + .def("IsPVPZone", &Lua_Zone::IsPVPZone) + .def("IsRaining", &Lua_Zone::IsRaining) + .def("IsSnowing", &Lua_Zone::IsSnowing) + .def("IsSpecialBindLocation", &Lua_Zone::IsSpecialBindLocation) + .def("IsSpellBlocked", &Lua_Zone::IsSpellBlocked) + .def("IsStaticZone", &Lua_Zone::IsStaticZone) + .def("IsUCSServerAvailable", &Lua_Zone::IsUCSServerAvailable) + .def("IsWaterZone", &Lua_Zone::IsWaterZone) + .def("PauseTimer", &Lua_Zone::PauseTimer) + .def("Repop", (void(Lua_Zone::*)(void))&Lua_Zone::Repop) + .def("Repop", (void(Lua_Zone::*)(bool))&Lua_Zone::Repop) + .def("ResumeTimer", &Lua_Zone::ResumeTimer) + .def("SendPayload", &Lua_Zone::SendPayload) + .def("SetAAEXPModifier", &Lua_Zone::SetAAEXPModifier) + .def("SetAAEXPModifierByCharacterID", &Lua_Zone::SetAAEXPModifierByCharacterID) + .def("SetBucket", (void(Lua_Zone::*)(const std::string&,const std::string&))&Lua_Zone::SetBucket) + .def("SetBucket", (void(Lua_Zone::*)(const std::string&,const std::string&,const std::string&))&Lua_Zone::SetBucket) + .def("SetEXPModifier", &Lua_Zone::SetEXPModifier) + .def("SetEXPModifierByCharacterID", &Lua_Zone::SetEXPModifierByCharacterID) + .def("SetInstanceTimer", &Lua_Zone::SetInstanceTimer) + .def("SetInstanceTimeRemaining", &Lua_Zone::SetInstanceTimeRemaining) + .def("SetIsHotzone", &Lua_Zone::SetIsHotzone) + .def("SetTimer", &Lua_Zone::SetTimer) + .def("SetVariable", &Lua_Zone::SetVariable) + .def("ShowZoneGlobalLoot", &Lua_Zone::ShowZoneGlobalLoot) + .def("Signal", &Lua_Zone::Signal) + .def("StopTimer", &Lua_Zone::StopTimer) + .def("StopAllTimers", &Lua_Zone::StopAllTimers) + .def("VariableExists", &Lua_Zone::VariableExists); +} + diff --git a/zone/lua_zone.h b/zone/lua_zone.h new file mode 100644 index 000000000..42a1d8841 --- /dev/null +++ b/zone/lua_zone.h @@ -0,0 +1,174 @@ +#pragma once + +#ifdef LUA_EQEMU + +#include "lua_client.h" +#include "lua_ptr.h" +#include "common.h" +#include "zone.h" + +class Lua_Zone; + +namespace luabind { + struct scope; +} + +luabind::scope lua_register_zone(); + +class Lua_Zone : public Lua_Ptr +{ + typedef Zone NativeType; +public: + Lua_Zone() : Lua_Ptr(nullptr) { } + Lua_Zone(Zone* d) : Lua_Ptr(d) { } + virtual ~Lua_Zone() { } + + operator Zone*() { + return reinterpret_cast(GetLuaPtrData()); + } + + bool BuffTimersSuspended(); + bool BypassesExpansionCheck(); + bool CanBind(); + bool CanCastOutdoor(); + bool CanDoCombat(); + bool CanLevitate(); + void ClearSpawnTimers(); + void Depop(); + void Depop(bool start_spawn_timers); + void Despawn(uint32 spawngroup_id); + void DisableRespawnTimers(); + float GetAAEXPModifier(Lua_Client c); + float GetAAEXPModifierByCharacterID(uint32 character_id); + std::string GetContentFlags(); + std::string GetContentFlagsDisabled(); + float GetExperienceMultiplier(); + int8 GetExpansion(); + float GetEXPModifier(Lua_Client c); + float GetEXPModifierByCharacterID(uint32 character_id); + int GetFastRegenEndurance(); + int GetFastRegenHP(); + int GetFastRegenMana(); + std::string GetFileName(); + std::string GetFlagNeeded(); + uint8 GetFogBlue(); + uint8 GetFogBlue(uint8 slot); + float GetFogDensity(); + uint8 GetFogGreen(); + uint8 GetFogGreen(uint8 slot); + float GetFogMaximumClip(); + float GetFogMaximumClip(uint8 slot); + float GetFogMinimumClip(); + float GetFogMinimumClip(uint8 slot); + uint8 GetFogRed(); + uint8 GetFogRed(uint8 slot); + float GetGraveyardHeading(); + uint32 GetGraveyardID(); + float GetGraveyardX(); + float GetGraveyardY(); + float GetGraveyardZ(); + uint32 GetGraveyardZoneID(); + float GetGravity(); + uint32 GetInstanceID(); + uint8 GetInstanceType(); + uint16 GetInstanceVersion(); + uint32 GetInstanceTimeRemaining(); + int GetLavaDamage(); + std::string GetLongName(); + float GetMaximumClip(); + int8 GetMaximumExpansion(); + uint8 GetMaximumLevel(); + uint32 GetMaxClients(); + float GetMinimumClip(); + int8 GetMinimumExpansion(); + uint8 GetMinimumLevel(); + int GetMinimumLavaDamage(); + uint8 GetMinimumStatus(); + std::string GetNote(); + int GetNPCMaximumAggroDistance(); + int8 GetPEQZone(); + int GetRainChance(); + int GetRainChance(uint8 slot); + int GetRainDuration(); + int GetRainDuration(uint8 slot); + uint32 GetRuleSet(); + float GetSafeHeading(); + float GetSafeX(); + float GetSafeY(); + float GetSafeZ(); + std::string GetShortName(); + uint32 GetSecondsBeforeIdle(); + uint64 GetShutdownDelay(); + uint8 GetSky(); + int8 GetSkyLock(); + int GetSnowChance(); + int GetSnowChance(uint8 slot); + int GetSnowDuration(); + int GetSnowDuration(uint8 slot); + uint8 GetTimeType(); + int GetTimeZone(); + std::string GetZoneDescription(); + uint32 GetZoneID(); + uint8 GetZoneType(); + float GetUnderworld(); + int GetUnderworldTeleportIndex(); + float GetWalkSpeed(); + uint8 GetZoneZType(); + int GetZoneTotalBlockedSpells(); + bool HasGraveyard(); + bool HasMap(); + bool HasWaterMap(); + bool HasWeather(); + bool IsCity(); + bool IsHotzone(); + bool IsInstancePersistent(); + bool IsIdleWhenEmpty(); + bool IsPVPZone(); + bool IsRaining(); + bool IsSnowing(); + bool IsSpecialBindLocation(float x, float y, float z, float heading); + bool IsSpellBlocked(uint32 spell_id, float x, float y, float z); + bool IsStaticZone(); + bool IsUCSServerAvailable(); + bool IsWaterZone(float z); + void Repop(); + void Repop(bool is_forced); + void SetAAEXPModifier(Lua_Client c, float aa_modifier); + void SetAAEXPModifierByCharacterID(uint32 character_id, float aa_modifier); + void SetEXPModifier(Lua_Client c, float exp_modifier); + void SetEXPModifierByCharacterID(uint32 character_id, float exp_modifier); + void SetInstanceTimer(uint32 new_duration); + void SetInstanceTimeRemaining(uint32 time_remaining); + void SetIsHotzone(bool is_hotzone); + void ShowZoneGlobalLoot(Lua_Client c); + bool ClearVariables(); + bool DeleteVariable(const std::string& variable_name); + std::string GetVariable(const std::string& variable_name); + luabind::object GetVariables(lua_State* L); + void SetVariable(const std::string& variable_name, const std::string& variable_value); + bool VariableExists(const std::string& variable_name); + uint32 GetTimerDuration(std::string name); + uint32 GetTimerRemainingTime(std::string name); + bool HasTimer(std::string name); + bool IsPausedTimer(std::string name); + void PauseTimer(std::string name); + void ResumeTimer(std::string name); + void SetTimer(std::string name, uint32 duration); + void StopTimer(std::string name); + void StopAllTimers(); + void Signal(int signal_id); + void SendPayload(int payload_id, std::string payload_value); + luabind::object GetPausedTimers(lua_State* L); + luabind::object GetTimers(lua_State* L); + + // data buckets + void SetBucket(const std::string& bucket_name, const std::string& bucket_value); + void SetBucket(const std::string& bucket_name, const std::string& bucket_value, const std::string& expiration = ""); + void DeleteBucket(const std::string& bucket_name); + std::string GetBucket(const std::string& bucket_name); + std::string GetBucketExpires(const std::string& bucket_name); + std::string GetBucketRemaining(const std::string& bucket_name); + +}; + +#endif diff --git a/zone/main.cpp b/zone/main.cpp index a05af774a..4f5916078 100644 --- a/zone/main.cpp +++ b/zone/main.cpp @@ -52,6 +52,7 @@ #include "task_manager.h" #include "quest_parser_collection.h" #include "embparser.h" +#include "../common/evolving_items.h" #include "lua_parser.h" #include "questmgr.h" #include "npc_scale_manager.h" @@ -87,12 +88,10 @@ extern volatile bool is_zone_loaded; #include "../common/path_manager.h" #include "../common/database/database_update.h" #include "../common/skill_caps.h" -#include "zone_event_scheduler.h" #include "zone_cli.h" EntityList entity_list; WorldServer worldserver; -ZoneStore zone_store; uint32 numclients = 0; char errorname[32]; extern Zone *zone; @@ -100,16 +99,8 @@ extern Zone *zone; npcDecayTimes_Struct npcCorpseDecayTimes[100]; TitleManager title_manager; QueryServ *QServ = 0; -TaskManager *task_manager = 0; NpcScaleManager *npc_scale_manager; QuestParserCollection *parse = 0; -EQEmuLogSys LogSys; -ZoneEventScheduler event_scheduler; -WorldContentService content_service; -PathManager path; -PlayerEventLogs player_event_logs; -DatabaseUpdate database_update; -SkillCaps skill_caps; const SPDat_Spell_Struct* spells; int32 SPDAT_RECORDS = -1; @@ -122,19 +113,20 @@ void CatchSignal(int sig_num); extern void MapOpcodes(); +bool CheckForCompatibleQuestPlugins(); int main(int argc, char **argv) { RegisterExecutablePlatform(ExePlatformZone); - LogSys.LoadLogSettingsDefaults(); + EQEmuLogSys::Instance()->LoadLogSettingsDefaults(); set_exception_handler(); // silence logging if we ran a command - if (ZoneCLI::RanConsoleCommand(argc, argv)) { - LogSys.SilenceConsoleLogging(); + if (ZoneCLI::RanConsoleCommand(argc, argv) || ZoneCLI::RanTestCommand(argc, argv)) { + EQEmuLogSys::Instance()->SilenceConsoleLogging(); } - path.LoadPaths(); + PathManager::Instance()->Init(); #ifdef USE_MAP_MMFS if (argc == 3 && strcasecmp(argv[1], "convert_map") == 0) { @@ -295,25 +287,29 @@ int main(int argc, char **argv) EQ::InitializeDynamicLookups(); } - // command handler - if (ZoneCLI::RanConsoleCommand(argc, argv) && !ZoneCLI::RanSidecarCommand(argc, argv)) { - LogSys.EnableConsoleLogging(); + // command handler (no sidecar or test commands) + if (ZoneCLI::RanConsoleCommand(argc, argv) && !(ZoneCLI::RanSidecarCommand(argc, argv) || ZoneCLI::RanTestCommand(argc, argv))) { + EQEmuLogSys::Instance()->EnableConsoleLogging(); ZoneCLI::CommandHandler(argc, argv); } - LogSys.SetDatabase(&database) - ->SetLogPath(path.GetLogPath()) - ->LoadLogDatabaseSettings() + EQEmuLogSys::Instance()->SetDatabase(&database) + ->SetLogPath(PathManager::Instance()->GetLogPath()) + ->LoadLogDatabaseSettings(ZoneCLI::RanTestCommand(argc, argv)) ->SetGMSayHandler(&Zone::GMSayHookCallBackProcess) ->StartFileLogs(); - player_event_logs.SetDatabase(&database)->Init(); + if (ZoneCLI::RanTestCommand(argc, argv)) { + EQEmuLogSys::Instance()->SilenceConsoleLogging(); + } - skill_caps.SetContentDatabase(&content_db)->LoadSkillCaps(); + PlayerEventLogs::Instance()->SetDatabase(&database)->Init(); + + SkillCaps::Instance()->SetContentDatabase(&content_db)->LoadSkillCaps(); const auto c = EQEmuConfig::get(); if (c->auto_database_updates) { - if (database_update.SetDatabase(&database)->HasPendingUpdates()) { + if (DatabaseUpdate::Instance()->SetDatabase(&database)->HasPendingUpdates()) { LogWarning("Database is not up to date [world] needs to be ran to apply updates, shutting down in 5 seconds"); std::this_thread::sleep_for(std::chrono::milliseconds(5000)); LogInfo("Exiting due to pending database updates"); @@ -360,13 +356,18 @@ int main(int argc, char **argv) } } - zone_store.LoadZones(content_db); + ZoneStore::Instance()->LoadZones(content_db); - if (zone_store.GetZones().empty()) { + if (ZoneStore::Instance()->GetZones().empty()) { LogError("Failed to load zones data, check your schema for possible errors"); return 1; } + if (!CheckForCompatibleQuestPlugins()) { + LogError("Incompatible quest plugins detected, please update your plugins to the latest version"); + return 1; + } + // load these here for now until spells and items can be truly repointed to "content_db" database.SetSharedItemsCount(content_db.GetItemsCount()); database.SetSharedSpellsCount(content_db.GetSpellsCount()); @@ -387,6 +388,11 @@ int main(int argc, char **argv) title_manager.LoadTitles(); content_db.LoadTributes(); + // Load evolving item data + EvolvingItemsManager::Instance()->SetDatabase(&database); + EvolvingItemsManager::Instance()->SetContentDatabase(&content_db); + EvolvingItemsManager::Instance()->LoadEvolvingItems(); + database.GetDecayTimes(npcCorpseDecayTimes); if (!EQ::ProfanityManager::LoadProfanityList(&database)) { @@ -401,12 +407,12 @@ int main(int argc, char **argv) LogInfo("Loaded [{}] commands loaded", Strings::Commify(std::to_string(retval))); } - content_service.SetDatabase(&database) + WorldContentService::Instance()->SetDatabase(&database) ->SetContentDatabase(&content_db) ->SetExpansionContext() ->ReloadContentFlags(); - event_scheduler.SetDatabase(&database)->LoadScheduledEvents(); + ZoneEventScheduler::Instance()->SetDatabase(&database)->LoadScheduledEvents(); EQ::SayLinkEngine::LoadCachedSaylinks(); @@ -433,8 +439,7 @@ int main(int argc, char **argv) npc_scale_manager->LoadScaleData(); if (RuleB(TaskSystem, EnableTaskSystem)) { - task_manager = new TaskManager; - task_manager->LoadTasks(); + TaskManager::Instance()->LoadTasks(); } parse = new QuestParserCollection(); @@ -470,15 +475,22 @@ int main(int argc, char **argv) LogInfo("Loading quests"); parse->ReloadQuests(); + QServ->CheckForConnectState(); + worldserver.Connect(); - worldserver.SetScheduler(&event_scheduler); + worldserver.SetScheduler(ZoneEventScheduler::Instance()); // sidecar command handler - if (ZoneCLI::RanConsoleCommand(argc, argv) && ZoneCLI::RanSidecarCommand(argc, argv)) { + if (ZoneCLI::RanConsoleCommand(argc, argv) + && (ZoneCLI::RanSidecarCommand(argc, argv) || ZoneCLI::RanTestCommand(argc, argv))) { + EQEmuLogSys::Instance()->EnableConsoleLogging(); ZoneCLI::CommandHandler(argc, argv); } Timer InterserverTimer(INTERSERVER_TIMER); // does MySQL pings and auto-reconnect + Timer UpdateWhoTimer(RuleI(Zone, UpdateWhoTimer) * 1000); // updates who list every 2 minutes + Timer WorldserverProcess(1000); + #ifdef EQPROFILE #ifdef PROFILE_DUMP_TIME Timer profile_dump_timer(PROFILE_DUMP_TIME * 1000); @@ -543,11 +555,11 @@ int main(int argc, char **argv) LogInfo("Starting EQ Network server on port [{}]", Config->ZonePort); EQStreamManagerInterfaceOptions opts(Config->ZonePort, false, RuleB(Network, CompressZoneStream)); - opts.daybreak_options.resend_delay_ms = RuleI(Network, ResendDelayBaseMS); - opts.daybreak_options.resend_delay_factor = RuleR(Network, ResendDelayFactor); - opts.daybreak_options.resend_delay_min = RuleI(Network, ResendDelayMinMS); - opts.daybreak_options.resend_delay_max = RuleI(Network, ResendDelayMaxMS); - opts.daybreak_options.outgoing_data_rate = RuleR(Network, ClientDataRate); + opts.reliable_stream_options.resend_delay_ms = RuleI(Network, ResendDelayBaseMS); + opts.reliable_stream_options.resend_delay_factor = RuleR(Network, ResendDelayFactor); + opts.reliable_stream_options.resend_delay_min = RuleI(Network, ResendDelayMinMS); + opts.reliable_stream_options.resend_delay_max = RuleI(Network, ResendDelayMaxMS); + opts.reliable_stream_options.outgoing_data_rate = RuleR(Network, ClientDataRate); eqsm = std::make_unique(opts); eqsf_open = true; @@ -594,6 +606,10 @@ int main(int argc, char **argv) } } + if (WorldserverProcess.Check()) { + worldserver.Process(); + } + if (is_zone_loaded) { { entity_list.GroupProcess(); @@ -606,26 +622,31 @@ int main(int argc, char **argv) entity_list.MobProcess(); entity_list.BeaconProcess(); entity_list.EncounterProcess(); - event_scheduler.Process(zone, &content_service); + + ZoneEventScheduler::Instance()->Process(zone, WorldContentService::Instance()); if (zone) { if (!zone->Process()) { - Zone::Shutdown(); + zone->Shutdown(); } } if (quest_timers.Check()) { quest_manager.Process(); } - } } + QServ->CheckForConnectState(); + if (InterserverTimer.Check()) { InterserverTimer.Start(); database.ping(); content_db.ping(); - entity_list.UpdateWho(); + if (UpdateWhoTimer.Check()) { + UpdateWhoTimer.SetTimer(RuleI(Zone, UpdateWhoTimer) * 1000); // in-case it was changed + entity_list.UpdateWho(); + } } }; @@ -646,27 +667,28 @@ int main(int argc, char **argv) safe_delete(Config); if (zone != 0) { - Zone::Shutdown(true); + zone->SetSaveZoneState(false); + zone->Shutdown(true); } //Fix for Linux world server problem. - safe_delete(task_manager); safe_delete(npc_scale_manager); command_deinit(); bot_command_deinit(); safe_delete(parse); LogInfo("Proper zone shutdown complete."); - LogSys.CloseFileLogs(); + EQEmuLogSys::Instance()->CloseFileLogs(); safe_delete(mutex); + safe_delete(QServ); return 0; } void Shutdown() { - Zone::Shutdown(true); + zone->Shutdown(true); LogInfo("Shutting down..."); - LogSys.CloseFileLogs(); + EQEmuLogSys::Instance()->CloseFileLogs(); EQ::EventLoop::Get().Shutdown(); } @@ -705,3 +727,51 @@ void UpdateWindowTitle(char *iNewTitle) SetConsoleTitle(tmp); #endif } + +bool CheckForCompatibleQuestPlugins() +{ + const std::vector> directories = { + {"lua_modules", nullptr}, + {"plugins", nullptr} + }; + + bool lua_found = false; + bool perl_found = false; + + try { + for (const auto &[directory, flag]: directories) { + std::string dir_path = PathManager::Instance()->GetServerPath() + "/" + directory; + if (!File::Exists(dir_path)) { continue; } + + for (const auto &file: fs::directory_iterator(dir_path)) { + if (!file.is_regular_file()) { continue; } + + std::string file_path = file.path().string(); + if (!File::Exists(file_path)) { continue; } + + auto r = File::GetContents(file_path); + if (!Strings::Contains(r.contents, "CheckHandin")) { continue; } + + if (directory == "lua_modules") { + lua_found = true; + } + else { + perl_found = true; + } + + if (lua_found && perl_found) { return true; } + } + } + } catch (const fs::filesystem_error &ex) { + LogError("Failed to check for compatible quest plugins: {}", ex.what()); + } + + if (!lua_found) { + LogError("Failed to find CheckHandin in lua_modules"); + } + if (!perl_found) { + LogError("Failed to find CheckHandin in plugins"); + } + + return lua_found && perl_found; +} diff --git a/zone/map.cpp b/zone/map.cpp index 410392e8a..bf6ff68ec 100644 --- a/zone/map.cpp +++ b/zone/map.cpp @@ -7,6 +7,7 @@ #include "raycast_mesh.h" #include "zone.h" #include "../common/file.h" +#include "../common/memory/ksm.hpp" #include #include @@ -270,7 +271,7 @@ bool Map::DoCollisionCheck(glm::vec3 myloc, glm::vec3 oloc, glm::vec3 &outnorm, Map *Map::LoadMapFile(std::string file) { std::transform(file.begin(), file.end(), file.begin(), ::tolower); - std::string filename = fmt::format("{}/base/{}.map", path.GetMapsPath(), file); + std::string filename = fmt::format("{}/base/{}.map", PathManager::Instance()->GetMapsPath(), file); LogInfo("Attempting to load Map File [{}]", filename.c_str()); @@ -320,7 +321,7 @@ bool Map::Load(const std::string &filename) } #ifdef USE_MAP_MMFS - if (v) + if (loaded_map_file) return SaveMMF(filename, force_mmf_overwrite); #endif /*USE_MAP_MMFS*/ @@ -338,7 +339,7 @@ bool Map::Load(const std::string &filename) } #ifdef USE_MAP_MMFS - if (v) + if (loaded_map_file) return SaveMMF(filename, force_mmf_overwrite); #endif /*USE_MAP_MMFS*/ @@ -953,6 +954,7 @@ bool Map::LoadV2(FILE *f) { return true; } + void Map::RotateVertex(glm::vec3 &v, float rx, float ry, float rz) { glm::vec3 nv = v; @@ -1064,7 +1066,7 @@ bool Map::LoadMMF(const std::string& map_file_name, bool force_mmf_overwrite) fclose(f); std::vector rm_buffer(rm_buffer_size); - uint32 v = InflateData(mmf_buffer.data(), mmf_buffer_size, rm_buffer.data(), rm_buffer_size); + uint32 v = EQ::InflateData(mmf_buffer.data(), mmf_buffer_size, rm_buffer.data(), rm_buffer_size); if (imp) { imp->rm->release(); @@ -1120,11 +1122,11 @@ bool Map::SaveMMF(const std::string& map_file_name, bool force_mmf_overwrite) } uint32 rm_buffer_size = rm_buffer.size(); - uint32 mmf_buffer_size = EstimateDeflateBuffer(rm_buffer.size()); + uint32 mmf_buffer_size = EQ::EstimateDeflateBuffer(rm_buffer.size()); std::vector mmf_buffer(mmf_buffer_size); - mmf_buffer_size = DeflateData(rm_buffer.data(), rm_buffer.size(), mmf_buffer.data(), mmf_buffer.size()); + mmf_buffer_size = EQ::DeflateData(rm_buffer.data(), rm_buffer.size(), mmf_buffer.data(), mmf_buffer.size()); if (!mmf_buffer_size) { LogInfo("Failed to save Map MMF file: [{}] - null MMF buffer size", mmf_file_name.c_str()); return false; diff --git a/zone/merc.cpp b/zone/merc.cpp index 6200e5b89..c29faff18 100644 --- a/zone/merc.cpp +++ b/zone/merc.cpp @@ -67,7 +67,7 @@ Merc::Merc(const NPCType* d, float x, float y, float z, float heading) int r; for (r = 0; r <= EQ::skills::HIGHEST_SKILL; r++) { - skills[r] = skill_caps.GetSkillCap(GetClass(), (EQ::skills::SkillType)r, GetLevel()).cap; + skills[r] = SkillCaps::Instance()->GetSkillCap(GetClass(), (EQ::skills::SkillType)r, GetLevel()).cap; } size = d->size; @@ -118,7 +118,7 @@ void Merc::CalcBonuses() CalcMaxMana(); CalcMaxEndurance(); - rooted = FindType(SE_Root); + rooted = FindType(SpellEffect::Root); } float Merc::GetDefaultSize() { @@ -774,12 +774,12 @@ bool Merc::HasSkill(EQ::skills::SkillType skill_id) const { } bool Merc::CanHaveSkill(EQ::skills::SkillType skill_id) const { - return skill_caps.GetSkillCap(GetClass(), skill_id, RuleI(Character, MaxLevel)).cap > 0; + return SkillCaps::Instance()->GetSkillCap(GetClass(), skill_id, RuleI(Character, MaxLevel)).cap > 0; //if you don't have it by max level, then odds are you never will? } uint16 Merc::MaxSkill(EQ::skills::SkillType skillid, uint16 class_, uint16 level) const { - return skill_caps.GetSkillCap(class_, skillid, level).cap; + return SkillCaps::Instance()->GetSkillCap(class_, skillid, level).cap; } void Merc::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) { @@ -1500,7 +1500,7 @@ bool Merc::AI_IdleCastCheck() { bool EntityList::Merc_AICheckCloseBeneficialSpells(Merc* caster, uint8 iChance, float iRange, uint32 iSpellTypes) { - if((iSpellTypes & SPELL_TYPES_DETRIMENTAL) != 0) { + if ((iSpellTypes & SPELL_TYPES_DETRIMENTAL) != 0) { //according to live, you can buff and heal through walls... //now with PCs, this only applies if you can TARGET the target, but // according to Rogean, Live NPCs will just cast through walls/floors, no problem.. @@ -1730,7 +1730,7 @@ bool Merc::AICastSpell(int8 iChance, uint32 iSpellTypes) { } else { //check for heal over time. if not present, try it first - if (!tar->FindType(SE_HealOverTime)) { + if (!tar->FindType(SpellEffect::HealOverTime)) { selectedMercSpell = GetBestMercSpellForHealOverTime(this); //get regular heal @@ -1779,7 +1779,7 @@ bool Merc::AICastSpell(int8 iChance, uint32 iSpellTypes) { if( !IsImmuneToSpell(selectedMercSpell.spellid, this) && (CanBuffStack(selectedMercSpell.spellid, mercLevel, true) >= 0)) { - if( GetArchetype() == Archetype::Melee && IsEffectInSpell(selectedMercSpell.spellid, SE_IncreaseSpellHaste)) { + if( GetArchetype() == Archetype::Melee && IsEffectInSpell(selectedMercSpell.spellid, SpellEffect::IncreaseSpellHaste)) { continue; } @@ -1806,7 +1806,7 @@ bool Merc::AICastSpell(int8 iChance, uint32 iSpellTypes) { if( !tar->IsImmuneToSpell(selectedMercSpell.spellid, this) && (tar->CanBuffStack(selectedMercSpell.spellid, mercLevel, true) >= 0)) { - if( tar->GetArchetype() == Archetype::Melee && IsEffectInSpell(selectedMercSpell.spellid, SE_IncreaseSpellHaste)) { + if( tar->GetArchetype() == Archetype::Melee && IsEffectInSpell(selectedMercSpell.spellid, SpellEffect::IncreaseSpellHaste)) { continue; } @@ -2292,7 +2292,7 @@ int64 Merc::GetFocusEffect(focusType type, uint16 spell_id, bool from_buff_tic) if(type == focusReagentCost && IsSummonPetSpell(spell_id) && GetAA(aaElementalPact)) return 100; - if(type == focusReagentCost && (IsEffectInSpell(spell_id, SE_SummonItem) || IsSacrificeSpell(spell_id))) + if(type == focusReagentCost && (IsEffectInSpell(spell_id, SpellEffect::SummonItem) || IsSacrificeSpell(spell_id))) return 0; //Summon Spells that require reagents are typically imbue type spells, enchant metal, sacrifice and shouldn't be affected //by reagent conservation for obvious reasons. @@ -2620,7 +2620,7 @@ MercSpell Merc::GetBestMercSpellForVeryFastHeal(Merc* caster) { result.time_cancast = 0; if(caster) { - std::list mercSpellList = GetMercSpellsForSpellEffect(caster, SE_CurrentHP); + std::list mercSpellList = GetMercSpellsForSpellEffect(caster, SpellEffect::CurrentHP); for (auto mercSpellListItr = mercSpellList.begin(); mercSpellListItr != mercSpellList.end(); ++mercSpellListItr) { @@ -2653,7 +2653,7 @@ MercSpell Merc::GetBestMercSpellForFastHeal(Merc* caster) { result.time_cancast = 0; if(caster) { - std::list mercSpellList = GetMercSpellsForSpellEffect(caster, SE_CurrentHP); + std::list mercSpellList = GetMercSpellsForSpellEffect(caster, SpellEffect::CurrentHP); for (auto mercSpellListItr = mercSpellList.begin(); mercSpellListItr != mercSpellList.end(); ++mercSpellListItr) { @@ -2686,7 +2686,7 @@ MercSpell Merc::GetBestMercSpellForHealOverTime(Merc* caster) { result.time_cancast = 0; if(caster) { - std::list mercHoTSpellList = GetMercSpellsForSpellEffect(caster, SE_HealOverTime); + std::list mercHoTSpellList = GetMercSpellsForSpellEffect(caster, SpellEffect::HealOverTime); for (auto mercSpellListItr = mercHoTSpellList.begin(); mercSpellListItr != mercHoTSpellList.end(); ++mercSpellListItr) { @@ -2727,7 +2727,7 @@ MercSpell Merc::GetBestMercSpellForPercentageHeal(Merc* caster) { result.time_cancast = 0; if(caster && caster->AI_HasSpells()) { - std::list mercSpellList = GetMercSpellsForSpellEffect(caster, SE_CurrentHP); + std::list mercSpellList = GetMercSpellsForSpellEffect(caster, SpellEffect::CurrentHP); for (auto mercSpellListItr = mercSpellList.begin(); mercSpellListItr != mercSpellList.end(); ++mercSpellListItr) { @@ -2760,7 +2760,7 @@ MercSpell Merc::GetBestMercSpellForRegularSingleTargetHeal(Merc* caster) { result.time_cancast = 0; if(caster) { - std::list mercSpellList = GetMercSpellsForSpellEffect(caster, SE_CurrentHP); + std::list mercSpellList = GetMercSpellsForSpellEffect(caster, SpellEffect::CurrentHP); for (auto mercSpellListItr = mercSpellList.begin(); mercSpellListItr != mercSpellList.end(); ++mercSpellListItr) { @@ -2793,7 +2793,7 @@ MercSpell Merc::GetFirstMercSpellForSingleTargetHeal(Merc* caster) { result.time_cancast = 0; if(caster) { - std::list mercSpellList = GetMercSpellsForSpellEffect(caster, SE_CurrentHP); + std::list mercSpellList = GetMercSpellsForSpellEffect(caster, SpellEffect::CurrentHP); for (auto mercSpellListItr = mercSpellList.begin(); mercSpellListItr != mercSpellList.end(); ++mercSpellListItr) { @@ -2827,7 +2827,7 @@ MercSpell Merc::GetBestMercSpellForGroupHeal(Merc* caster) { result.time_cancast = 0; if(caster) { - std::list mercSpellList = GetMercSpellsForSpellEffect(caster, SE_CurrentHP); + std::list mercSpellList = GetMercSpellsForSpellEffect(caster, SpellEffect::CurrentHP); for (auto mercSpellListItr = mercSpellList.begin(); mercSpellListItr != mercSpellList.end(); ++mercSpellListItr) { @@ -2860,7 +2860,7 @@ MercSpell Merc::GetBestMercSpellForGroupHealOverTime(Merc* caster) { result.time_cancast = 0; if(caster) { - std::list mercHoTSpellList = GetMercSpellsForSpellEffect(caster, SE_HealOverTime); + std::list mercHoTSpellList = GetMercSpellsForSpellEffect(caster, SpellEffect::HealOverTime); for (auto mercSpellListItr = mercHoTSpellList.begin(); mercSpellListItr != mercHoTSpellList.end(); ++mercSpellListItr) { @@ -2901,7 +2901,7 @@ MercSpell Merc::GetBestMercSpellForGroupCompleteHeal(Merc* caster) { result.time_cancast = 0; if(caster) { - std::list mercSpellList = GetMercSpellsForSpellEffect(caster, SE_CompleteHeal); + std::list mercSpellList = GetMercSpellsForSpellEffect(caster, SpellEffect::CompleteHeal); for (auto mercSpellListItr = mercSpellList.begin(); mercSpellListItr != mercSpellList.end(); ++mercSpellListItr) { @@ -2934,7 +2934,7 @@ MercSpell Merc::GetBestMercSpellForAETaunt(Merc* caster) { result.time_cancast = 0; if(caster) { - std::list mercSpellList = GetMercSpellsForSpellEffect(caster, SE_Taunt); + std::list mercSpellList = GetMercSpellsForSpellEffect(caster, SpellEffect::Taunt); for (auto mercSpellListItr = mercSpellList.begin(); mercSpellListItr != mercSpellList.end(); ++mercSpellListItr) { @@ -2969,7 +2969,7 @@ MercSpell Merc::GetBestMercSpellForTaunt(Merc* caster) { result.time_cancast = 0; if(caster) { - std::list mercSpellList = GetMercSpellsForSpellEffect(caster, SE_Taunt); + std::list mercSpellList = GetMercSpellsForSpellEffect(caster, SpellEffect::Taunt); for (auto mercSpellListItr = mercSpellList.begin(); mercSpellListItr != mercSpellList.end(); ++mercSpellListItr) { @@ -3002,7 +3002,7 @@ MercSpell Merc::GetBestMercSpellForHate(Merc* caster) { result.time_cancast = 0; if(caster) { - std::list mercSpellList = GetMercSpellsForSpellEffect(caster, SE_InstantHate); + std::list mercSpellList = GetMercSpellsForSpellEffect(caster, SpellEffect::InstantHate); for (auto mercSpellListItr = mercSpellList.begin(); mercSpellListItr != mercSpellList.end(); ++mercSpellListItr) { @@ -3038,10 +3038,10 @@ MercSpell Merc::GetBestMercSpellForCure(Merc* caster, Mob *tar) { return result; int countNeedsCured = 0; - bool isPoisoned = tar->FindType(SE_PoisonCounter); - bool isDiseased = tar->FindType(SE_DiseaseCounter); - bool isCursed = tar->FindType(SE_CurseCounter); - bool isCorrupted = tar->FindType(SE_CorruptionCounter); + bool isPoisoned = tar->FindType(SpellEffect::PoisonCounter); + bool isDiseased = tar->FindType(SpellEffect::DiseaseCounter); + bool isCursed = tar->FindType(SpellEffect::CurseCounter); + bool isCorrupted = tar->FindType(SpellEffect::CorruptionCounter); if(caster && caster->AI_HasSpells()) { std::list cureList = GetMercSpellsBySpellType(caster, SpellType_Cure); @@ -3068,19 +3068,19 @@ MercSpell Merc::GetBestMercSpellForCure(Merc* caster, Mob *tar) { if(selectedMercSpell.spellid == 0) continue; - if(isPoisoned && IsEffectInSpell(itr->spellid, SE_PoisonCounter)) { + if(isPoisoned && IsEffectInSpell(itr->spellid, SpellEffect::PoisonCounter)) { spellSelected = true; } - else if(isDiseased && IsEffectInSpell(itr->spellid, SE_DiseaseCounter)) { + else if(isDiseased && IsEffectInSpell(itr->spellid, SpellEffect::DiseaseCounter)) { spellSelected = true; } - else if(isCursed && IsEffectInSpell(itr->spellid, SE_CurseCounter)) { + else if(isCursed && IsEffectInSpell(itr->spellid, SpellEffect::CurseCounter)) { spellSelected = true; } - else if(isCorrupted && IsEffectInSpell(itr->spellid, SE_CorruptionCounter)) { + else if(isCorrupted && IsEffectInSpell(itr->spellid, SpellEffect::CorruptionCounter)) { spellSelected = true; } - else if(IsEffectInSpell(itr->spellid, SE_DispelDetrimental)) { + else if(IsEffectInSpell(itr->spellid, SpellEffect::DispelDetrimental)) { spellSelected = true; } @@ -3108,19 +3108,19 @@ MercSpell Merc::GetBestMercSpellForCure(Merc* caster, Mob *tar) { if(selectedMercSpell.spellid == 0) continue; - if(isPoisoned && IsEffectInSpell(itr->spellid, SE_PoisonCounter)) { + if(isPoisoned && IsEffectInSpell(itr->spellid, SpellEffect::PoisonCounter)) { spellSelected = true; } - else if(isDiseased && IsEffectInSpell(itr->spellid, SE_DiseaseCounter)) { + else if(isDiseased && IsEffectInSpell(itr->spellid, SpellEffect::DiseaseCounter)) { spellSelected = true; } - else if(isCursed && IsEffectInSpell(itr->spellid, SE_CurseCounter)) { + else if(isCursed && IsEffectInSpell(itr->spellid, SpellEffect::CurseCounter)) { spellSelected = true; } - else if(isCorrupted && IsEffectInSpell(itr->spellid, SE_CorruptionCounter)) { + else if(isCorrupted && IsEffectInSpell(itr->spellid, SpellEffect::CorruptionCounter)) { spellSelected = true; } - else if(IsEffectInSpell(itr->spellid, SE_DispelDetrimental)) { + else if(IsEffectInSpell(itr->spellid, SpellEffect::DispelDetrimental)) { spellSelected = true; } @@ -3155,7 +3155,7 @@ MercSpell Merc::GetBestMercSpellForStun(Merc* caster) { result.time_cancast = 0; if(caster) { - std::list mercSpellList = GetMercSpellsForSpellEffect(caster, SE_Stun); + std::list mercSpellList = GetMercSpellsForSpellEffect(caster, SpellEffect::Stun); for (auto mercSpellListItr = mercSpellList.begin(); mercSpellListItr != mercSpellList.end(); ++mercSpellListItr) { @@ -3492,7 +3492,7 @@ bool Merc::GetNeedsCured(Mob *tar) { bool needCured = false; if(tar) { - if(tar->FindType(SE_PoisonCounter) || tar->FindType(SE_DiseaseCounter) || tar->FindType(SE_CurseCounter) || tar->FindType(SE_CorruptionCounter)) { + if(tar->FindType(SpellEffect::PoisonCounter) || tar->FindType(SpellEffect::DiseaseCounter) || tar->FindType(SpellEffect::CurseCounter) || tar->FindType(SpellEffect::CorruptionCounter)) { uint32 buff_count = tar->GetMaxTotalSlots(); int buffsWithCounters = 0; needCured = true; diff --git a/zone/mob.cpp b/zone/mob.cpp index c6c81629c..1302246b2 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -24,7 +24,7 @@ #include "../common/repositories/bot_data_repository.h" #include "../common/repositories/character_data_repository.h" -#include "data_bucket.h" +#include "../common/data_bucket.h" #include "quest_parser_collection.h" #include "string_ids.h" #include "worldserver.h" @@ -101,7 +101,8 @@ Mob::Mob( bool in_always_aggro, int32 in_heroic_strikethrough, bool in_keeps_sold_items, - int64 in_hp_regen_per_second + int64 in_hp_regen_per_second, + uint32 npc_tint_id ) : attack_timer(2000), attack_dw_timer(2000), @@ -125,12 +126,15 @@ Mob::Mob( tmHidden(-1), mitigation_ac(0), m_specialattacks(eSpecialAttacks::None), - attack_anim_timer(500), + attack_anim_timer(100), position_update_melee_push_timer(500), hate_list_cleanup_timer(6000), m_scan_close_mobs_timer(6000), + m_see_close_mobs_timer(1000), m_mob_check_moving_timer(1000), - bot_attack_flag_timer(10000) + bot_attack_flag_timer(10000), + m_clear_wearchange_cache_timer(60 * 10 * 1000), // 10 minutes + m_destroying(false) { mMovementManager = &MobMovementManager::Get(); mMovementManager->AddMob(this); @@ -287,6 +291,7 @@ Mob::Mob( always_aggro = in_always_aggro; heroic_strikethrough = in_heroic_strikethrough; keeps_sold_items = in_keeps_sold_items; + m_npc_tint_id = npc_tint_id; InitializeBuffSlots(); @@ -387,7 +392,7 @@ Mob::Mob( spellbonuses.AssistRange = -1; SetPetID(0); SetOwnerID(0); - SetPetType(petNone); // default to not a pet + SetPetType(PetType::None); // default to not a pet SetPetPower(0); held = false; gheld = false; @@ -449,8 +454,8 @@ Mob::Mob( weaponstance.itembonus_buff_spell_id = 0; weaponstance.aabonus_buff_spell_id = 0; - pStandingPetOrder = SPO_Follow; - m_previous_pet_order = SPO_Follow; + m_pet_order = PetOrder::Follow; + m_previous_pet_order = PetOrder::Follow; pseudo_rooted = false; nobuff_invisible = 0; @@ -492,6 +497,8 @@ Mob::Mob( m_AllowBeneficial = false; m_DisableMelee = false; + SetMerchantSessionEntityID(0); + for (int i = 0; i < EQ::skills::HIGHEST_SKILL + 2; i++) { SkillDmgTaken_Mod[i] = 0; } @@ -528,6 +535,11 @@ Mob::Mob( Mob::~Mob() { + m_destroying = true; + + entity_list.RemoveMobFromCloseLists(this); + m_close_mobs.clear(); + quest_manager.stopalltimers(this); mMovementManager->RemoveMob(this); @@ -535,7 +547,7 @@ Mob::~Mob() AI_Stop(); if (GetPet()) { if (GetPet()->Charmed()) { - GetPet()->BuffFadeByEffect(SE_Charm); + GetPet()->BuffFadeByEffect(SpellEffect::Charm); } else { SetPet(0); @@ -567,10 +579,9 @@ Mob::~Mob() entity_list.UnMarkNPC(GetID()); UninitializeBuffSlots(); - entity_list.RemoveMobFromCloseLists(this); entity_list.RemoveAuraFromMobs(this); - m_close_mobs.clear(); + ClearDataBucketCache(); LeaveHealRotationTargetPool(); } @@ -612,9 +623,9 @@ bool Mob::HasAnInvisibilityEffect() { void Mob::BreakCharmPetIfConditionsMet() { auto pet = GetPet(); - if (pet && pet->GetPetType() == petCharmed && HasAnInvisibilityEffect()) { + if (pet && pet->GetPetType() == PetType::Charmed && HasAnInvisibilityEffect()) { if (RuleB(Pets, LivelikeBreakCharmOnInvis) || IsInvisible(pet)) { - pet->BuffFadeByEffect(SE_Charm); + pet->BuffFadeByEffect(SpellEffect::Charm); } LogRules( "Pets:LivelikeBreakCharmOnInvis for [{}] invisible [{}] hidden [{}] improved_hidden (shroud of stealth) [{}] invisible_animals [{}] invisible_undead [{}]", @@ -644,14 +655,14 @@ void Mob::CalcInvisibleLevel() BreakCharmPetIfConditionsMet(); } -void Mob::SetPetOrder(eStandingPetOrder i) { - if (i == SPO_Sit || i == SPO_FeignDeath) { - if (pStandingPetOrder == SPO_Follow || pStandingPetOrder == SPO_Guard) { - m_previous_pet_order = pStandingPetOrder; +void Mob::SetPetOrder(uint8 pet_order) { + if (pet_order == PetOrder::Sit || pet_order == PetOrder::Feign) { + if (m_pet_order == PetOrder::Follow || m_pet_order == PetOrder::Guard) { + m_previous_pet_order = m_pet_order; } } - pStandingPetOrder = i; + m_pet_order = pet_order; } void Mob::SetInvisible(uint8 state, bool set_on_bonus_calc) { @@ -1277,23 +1288,24 @@ void Mob::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) strn0cpy(ns->spawn.lastName, lastname, sizeof(ns->spawn.lastName)); } - ns->spawn.heading = FloatToEQ12(m_Position.w); - ns->spawn.x = FloatToEQ19(m_Position.x);//((int32)x_pos)<<3; - ns->spawn.y = FloatToEQ19(m_Position.y);//((int32)y_pos)<<3; - ns->spawn.z = FloatToEQ19(m_Position.z);//((int32)z_pos)<<3; - ns->spawn.spawnId = GetID(); - ns->spawn.curHp = static_cast(GetHPRatio()); - ns->spawn.max_hp = 100; //this field needs a better name - ns->spawn.race = (use_model) ? use_model : race; - ns->spawn.runspeed = runspeed; - ns->spawn.walkspeed = walkspeed; - ns->spawn.class_ = class_; - ns->spawn.gender = gender; - ns->spawn.level = level; - ns->spawn.PlayerState = GetPlayerState(); - ns->spawn.deity = deity; - ns->spawn.animation = 0; - ns->spawn.findable = findable?1:0; + ns->spawn.heading = FloatToEQ12(m_Position.w); + ns->spawn.x = FloatToEQ19(m_Position.x); //((int32)x_pos)<<3; + ns->spawn.y = FloatToEQ19(m_Position.y); //((int32)y_pos)<<3; + ns->spawn.z = FloatToEQ19(m_Position.z); //((int32)z_pos)<<3; + ns->spawn.spawnId = GetID(); + ns->spawn.curHp = static_cast(GetHPRatio()); + ns->spawn.max_hp = 100; // this field needs a better name + ns->spawn.race = (use_model) ? use_model : race; + ns->spawn.runspeed = runspeed; + ns->spawn.walkspeed = walkspeed; + ns->spawn.class_ = class_; + ns->spawn.gender = gender; + ns->spawn.level = level; + ns->spawn.PlayerState = GetPlayerState(); + ns->spawn.deity = deity; + ns->spawn.animation = 0; + ns->spawn.findable = findable ? 1 : 0; + ns->spawn.npc_tint_id = GetNpcTintId(); UpdateActiveLight(); ns->spawn.light = m_Light.Type[EQ::lightsource::LightActive]; @@ -1304,6 +1316,7 @@ void Mob::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) ns->spawn.NPC = IsClient() ? 0 : 1; ns->spawn.IsMercenary = IsMerc() ? 1 : 0; ns->spawn.targetable_with_hotkey = no_target_hotkey ? 0 : 1; // opposite logic! + ns->spawn.untargetable = IsTargetable(); ns->spawn.petOwnerId = ownerid; @@ -1339,7 +1352,7 @@ void Mob::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) // 3 - Mobs in water do not sink. A value of 3 in this field appears to be the default setting for all mobs // (in water or not) according to 6.2 era packet collects. if(IsClient()) - ns->spawn.flymode = FindType(SE_Levitate) ? 2 : 0; + ns->spawn.flymode = FindType(SpellEffect::Levitate) ? 2 : 0; else ns->spawn.flymode = flymode; @@ -1448,6 +1461,10 @@ void Mob::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) ns->spawn.flymode = 0; } + if (IsZoneController()) { + ns->spawn.invis = 255; // gm invis + } + if (RuleB(Character, AllowCrossClassTrainers) && ForWho) { if (ns->spawn.class_ >= Class::WarriorGM && ns->spawn.class_ <= Class::BerserkerGM) { int trainer_class = Class::WarriorGM + (ForWho->GetClass() - 1); @@ -1519,16 +1536,12 @@ void Mob::SendHPUpdate(bool force_update_all) last_hp ); - auto client_packet = new EQApplicationPacket(OP_HPUpdate, sizeof(SpawnHPUpdate_Struct)); - auto *hp_packet_client = (SpawnHPUpdate_Struct *) client_packet->pBuffer; - - hp_packet_client->cur_hp = static_cast(CastToClient()->GetHP() - itembonuses.HP); - hp_packet_client->spawn_id = GetID(); - hp_packet_client->max_hp = CastToClient()->GetMaxHP() - itembonuses.HP; - - CastToClient()->QueuePacket(client_packet); - - safe_delete(client_packet); + static EQApplicationPacket p(OP_HPUpdate, sizeof(SpawnHPUpdate_Struct)); + auto b = (SpawnHPUpdate_Struct*) p.pBuffer; + b->cur_hp = static_cast(CastToClient()->GetHP() - itembonuses.HP); + b->spawn_id = GetID(); + b->max_hp = CastToClient()->GetMaxHP() - itembonuses.HP; + CastToClient()->QueuePacket(&p); ResetHPUpdateTimer(); @@ -2048,19 +2061,19 @@ void Mob::SendStatsWindow(Client* c, bool use_window) case 0: { mod2a_name = "Avoidance"; mod2b_name = "Combat Effects"; - mod2a_cap = RuleI(Character, ItemAvoidanceCap); - mod2b_cap = RuleI(Character, ItemCombatEffectsCap); + mod2a_cap = Strings::Commify(RuleI(Character, ItemAvoidanceCap)); + mod2b_cap = Strings::Commify(RuleI(Character, ItemCombatEffectsCap)); if (IsBot()) { - mod2a = CastToBot()->GetAvoidance(); + mod2a = Strings::Commify(CastToBot()->GetAvoidance()); } else if (IsClient()) { - mod2a = CastToClient()->GetAvoidance(); + mod2a = Strings::Commify(CastToClient()->GetAvoidance()); } if (IsBot()) { - mod2b = CastToBot()->GetCombatEffects(); + mod2b = Strings::Commify(CastToBot()->GetCombatEffects()); } else if (IsClient()) { - mod2b = CastToClient()->GetCombatEffects(); + mod2b = Strings::Commify(CastToClient()->GetCombatEffects()); } break; @@ -2068,19 +2081,19 @@ void Mob::SendStatsWindow(Client* c, bool use_window) case 1: { mod2a_name = "Accuracy"; mod2b_name = "Strikethrough"; - mod2a_cap = RuleI(Character, ItemAccuracyCap); - mod2b_cap = RuleI(Character, ItemStrikethroughCap); + mod2a_cap = Strings::Commify(RuleI(Character, ItemAccuracyCap)); + mod2b_cap = Strings::Commify(RuleI(Character, ItemStrikethroughCap)); if (IsBot()) { - mod2a = CastToBot()->GetAccuracy(); + mod2a = Strings::Commify(CastToBot()->GetAccuracy()); } else if (IsClient()) { - mod2a = CastToClient()->GetAccuracy(); + mod2a = Strings::Commify(CastToClient()->GetAccuracy()); } if (IsBot()) { - mod2b = CastToBot()->GetStrikeThrough(); + mod2b = Strings::Commify(CastToBot()->GetStrikeThrough()); } else if (IsClient()) { - mod2b = CastToClient()->GetStrikeThrough(); + mod2b = Strings::Commify(CastToClient()->GetStrikeThrough()); } break; @@ -2088,20 +2101,20 @@ void Mob::SendStatsWindow(Client* c, bool use_window) case 2: { mod2a_name = "Shielding"; mod2b_name = "Spell Shielding"; - mod2a_cap = RuleI(Character, ItemShieldingCap); - mod2b_cap = RuleI(Character, ItemSpellShieldingCap); + mod2a_cap = Strings::Commify(RuleI(Character, ItemShieldingCap)); + mod2b_cap = Strings::Commify(RuleI(Character, ItemSpellShieldingCap)); if (IsBot()) { - mod2a = CastToBot()->GetShielding(); + mod2a = Strings::Commify(CastToBot()->GetShielding()); } else if (IsClient()) { - mod2a = CastToClient()->GetShielding(); + mod2a = Strings::Commify(CastToClient()->GetShielding()); } if (IsBot()) { - mod2b = CastToBot()->GetSpellShield(); + mod2b = Strings::Commify(CastToBot()->GetSpellShield()); } else if (IsClient()) { - mod2b = CastToClient()->GetSpellShield(); + mod2b = Strings::Commify(CastToClient()->GetSpellShield()); } break; @@ -2109,19 +2122,19 @@ void Mob::SendStatsWindow(Client* c, bool use_window) case 3: { mod2a_name = "Stun Resist"; mod2b_name = "DOT Shielding"; - mod2a_cap = RuleI(Character, ItemStunResistCap); - mod2b_cap = RuleI(Character, ItemDoTShieldingCap); + mod2a_cap = Strings::Commify(RuleI(Character, ItemStunResistCap)); + mod2b_cap = Strings::Commify(RuleI(Character, ItemDoTShieldingCap)); if (IsBot()) { - mod2a = CastToBot()->GetStunResist(); + mod2a = Strings::Commify(CastToBot()->GetStunResist()); } else if (IsClient()) { - mod2a = CastToClient()->GetStunResist(); + mod2a = Strings::Commify(CastToClient()->GetStunResist()); } if (IsBot()) { - mod2b = CastToBot()->GetDoTShield(); + mod2b = Strings::Commify(CastToBot()->GetDoTShield()); } else if (IsClient()) { - mod2b = CastToClient()->GetDoTShield(); + mod2b = Strings::Commify(CastToClient()->GetDoTShield()); } break; @@ -2133,16 +2146,16 @@ void Mob::SendStatsWindow(Client* c, bool use_window) fmt::format( "{}: {} / {}", mod2a_name, - Strings::Commify(mod2a), - Strings::Commify(mod2a_cap) + mod2a, + mod2a_cap ) ) + DialogueWindow::TableCell( fmt::format( "{}: {} / {}", mod2b_name, - Strings::Commify(mod2b), - Strings::Commify(mod2b_cap) + mod2b, + mod2b_cap ) ) ); @@ -2348,7 +2361,7 @@ void Mob::SendStatsWindow(Client* c, bool use_window) // Attack 2 final_string += fmt::format( - "Offense: {}{} | {}{}", + "Offense: {}{}{}{}", Strings::Commify(offense(skill)), ( itembonuses.ATK ? @@ -2634,7 +2647,7 @@ void Mob::SendStatsWindow(Client* c, bool use_window) Chat::White, fmt::format( " AFK: {} LFG: {} Anon: {} PVP: {} GM: {} Fly Mode: {} ({}) GM Speed: {} Hide Me: {} Invulnerability: {} LD: {} Client Version: {} Tells Off: {}", - CastToClient()->AFK ? "Yes" : "No", + CastToClient()->m_is_afk ? "Yes" : "No", CastToClient()->LFG ? "Yes" : "No", CastToClient()->GetAnon() ? "Yes" : "No", CastToClient()->GetPVP() ? "Yes" : "No", @@ -3537,24 +3550,21 @@ void Mob::DoAnim(const int animation_id, int animation_speed, bool ackreq, eqFil return; } - auto outapp = new EQApplicationPacket(OP_Animation, sizeof(Animation_Struct)); - auto *a = (Animation_Struct *) outapp->pBuffer; - + static EQApplicationPacket p(OP_Animation, sizeof(Animation_Struct)); + auto a = (Animation_Struct*) p.pBuffer; a->spawnid = GetID(); a->action = animation_id; a->speed = animation_speed ? animation_speed : 10; entity_list.QueueCloseClients( this, /* Sender */ - outapp, /* Packet */ + &p, /* Packet */ false, /* Ignore Sender */ RuleI(Range, Anims), 0, /* Skip this mob */ ackreq, /* Packet ACK */ filter /* eqFilterType filter */ ); - - safe_delete(outapp); } void Mob::ShowBuffs(Client* c) { @@ -3809,7 +3819,7 @@ bool Mob::RandomizeFeatures(bool send_illusion, bool set_variables) // Adjust all settings based on the min and max for each feature of each race and gender switch (GetRace()) { - case HUMAN: + case Race::Human: new_hair_color = zone->random.Int(0, 19); if (current_gender == Gender::Male) { @@ -3821,7 +3831,7 @@ bool Mob::RandomizeFeatures(bool send_illusion, bool set_variables) } break; - case BARBARIAN: + case Race::Barbarian: new_hair_color = zone->random.Int(0, 19); new_luclin_face = zone->random.Int(0, 87); @@ -3834,7 +3844,7 @@ bool Mob::RandomizeFeatures(bool send_illusion, bool set_variables) } break; - case ERUDITE: + case Race::Erudite: if (current_gender == Gender::Male) { new_beard_color = zone->random.Int(0, 19); new_beard = zone->random.Int(0, 5); @@ -3844,7 +3854,7 @@ bool Mob::RandomizeFeatures(bool send_illusion, bool set_variables) } break; - case WOOD_ELF: + case Race::WoodElf: new_hair_color = zone->random.Int(0, 19); if (current_gender == Gender::Male) { @@ -3854,7 +3864,7 @@ bool Mob::RandomizeFeatures(bool send_illusion, bool set_variables) } break; - case HIGH_ELF: + case Race::HighElf: new_hair_color = zone->random.Int(0, 14); if (current_gender == Gender::Male) { @@ -3866,7 +3876,7 @@ bool Mob::RandomizeFeatures(bool send_illusion, bool set_variables) } break; - case DARK_ELF: + case Race::DarkElf: new_hair_color = zone->random.Int(13, 18); if (current_gender == Gender::Male) { @@ -3878,7 +3888,7 @@ bool Mob::RandomizeFeatures(bool send_illusion, bool set_variables) } break; - case HALF_ELF: + case Race::HalfElf: new_hair_color = zone->random.Int(0, 19); if (current_gender == Gender::Male) { @@ -3890,7 +3900,7 @@ bool Mob::RandomizeFeatures(bool send_illusion, bool set_variables) } break; - case DWARF: + case Race::Dwarf: new_hair_color = zone->random.Int(0, 19); new_beard_color = new_hair_color; @@ -3903,7 +3913,7 @@ bool Mob::RandomizeFeatures(bool send_illusion, bool set_variables) } break; - case TROLL: + case Race::Troll: new_eye_color_one = zone->random.Int(0, 10); new_eye_color_two = zone->random.Int(0, 10); @@ -3913,14 +3923,14 @@ bool Mob::RandomizeFeatures(bool send_illusion, bool set_variables) } break; - case OGRE: + case Race::Ogre: if (current_gender == Gender::Female) { new_hair_style = zone->random.Int(0, 3); new_hair_color = zone->random.Int(0, 23); } break; - case HALFLING: + case Race::Halfling: new_hair_color = zone->random.Int(0, 19); if (current_gender == Gender::Male) { @@ -3932,7 +3942,7 @@ bool Mob::RandomizeFeatures(bool send_illusion, bool set_variables) } break; - case GNOME: + case Race::Gnome: new_hair_color = zone->random.Int(0, 24); if (current_gender == Gender::Male) { @@ -3944,14 +3954,14 @@ bool Mob::RandomizeFeatures(bool send_illusion, bool set_variables) } break; - case IKSAR: - case VAHSHIR: + case Race::Iksar: + case Race::VahShir: new_luclin_face = zone->random.Int(0, 7); break; - case FROGLOK: + case Race::Froglok2: new_luclin_face = zone->random.Int(0, 9); break; - case DRAKKIN: + case Race::Drakkin: new_hair_color = zone->random.Int(0, 3); new_beard_color = new_hair_color; new_eye_color_one = zone->random.Int(0, 11); @@ -4013,8 +4023,8 @@ bool Mob::RandomizeFeatures(bool send_illusion, bool set_variables) uint16 Mob::GetFactionRace() { uint16 current_race = GetRace(); - if (IsPlayerRace(current_race) || current_race == TREE || - current_race == MINOR_ILL_OBJ) { + if (IsPlayerRace(current_race) || current_race == Race::Tree || + current_race == Race::MinorIllusion) { return current_race; } else { @@ -4557,8 +4567,8 @@ void Mob::SetOwnerID(uint16 new_owner_id) { if ( !ownerid && IsNPC() && - GetPetType() != petCharmed && - GetPetType() != petNone + GetPetType() != PetType::Charmed && + GetPetType() != PetType::None ) { Depop(); } @@ -4609,8 +4619,12 @@ void Mob::SetZone(uint32 zone_id, uint32 instance_id) { CastToClient()->GetPP().zone_id = zone_id; CastToClient()->GetPP().zoneInstance = instance_id; + CastToClient()->SaveCharacterData(); + } + + if (!IsClient()) { + Save(); // bots or other things might be covered here for some reason } - Save(); } void Mob::Kill() { @@ -4660,25 +4674,22 @@ bool Mob::CanThisClassDoubleAttack(void) const } } -bool Mob::CanThisClassTripleAttack() const -{ - if (!IsClient()) { - return false; // When they added the real triple attack skill, mobs lost the ability to triple - } else { - if (RuleB(Combat, ClassicTripleAttack)) { - return ( - GetLevel() >= 60 && - ( - GetClass() == Class::Warrior || - GetClass() == Class::Ranger || - GetClass() == Class::Monk || - GetClass() == Class::Berserker - ) - ); - } else { - return CastToClient()->HasSkill(EQ::skills::SkillTripleAttack); - } +bool Mob::CanThisClassTripleAttack() const { + if (!IsOfClientBot()) { + return false; // Mobs lost the ability to triple attack when the real skill was added } + + if (RuleB(Combat, ClassicTripleAttack)) { + return GetLevel() >= 60 && ( + GetClass() == Class::Warrior || + GetClass() == Class::Ranger || + GetClass() == Class::Monk || + GetClass() == Class::Berserker + ); + } + + return IsClient() ? CastToClient()->HasSkill(EQ::skills::SkillTripleAttack) + : GetSkill(EQ::skills::SkillTripleAttack) > 0; } bool Mob::CanThisClassParry(void) const @@ -4878,7 +4889,7 @@ bool Mob::HateSummon() { } else { bool target_is_client_pet = ( target->IsPet() && - target->IsPetOwnerClient() + target->IsPetOwnerOfClientBot() ); bool set_new_guard_spot = !(IsNPC() && target_is_client_pet); @@ -5325,14 +5336,7 @@ void Mob::ExecWeaponProc(const EQ::ItemInstance* inst, uint16 spell_id, Mob* on, //It should be safe as we don't have any truly const EQ::ItemInstance floating around anywhere. //So we'll live with it for now if (parse->ItemHasQuestSub(const_cast(inst), EVENT_WEAPON_PROC)) { - int i = parse->EventItem( - EVENT_WEAPON_PROC, - CastToClient(), - const_cast(inst), - on, - "", - spell_id - ); + int i = parse->EventItem(EVENT_WEAPON_PROC, CastToClient(), const_cast(inst), on, "", spell_id); if (i != 0) { return; @@ -5625,7 +5629,7 @@ int Mob::GetSnaredAmount() for(int j = 0; j < EFFECT_COUNT; j++) { - if (spells[buffs[i].spellid].effect_id[j] == SE_MovementSpeed) + if (spells[buffs[i].spellid].effect_id[j] == SpellEffect::MovementSpeed) { int64 val = CalcSpellEffectValue_formula(spells[buffs[i].spellid].formula[j], spells[buffs[i].spellid].base_value[j], spells[buffs[i].spellid].max_value[j], buffs[i].casterlevel, buffs[i].spellid); //int effect = CalcSpellEffectValue(buffs[i].spellid, spells[buffs[i].spellid].effectid[j], buffs[i].casterlevel); @@ -5823,7 +5827,7 @@ bool Mob::TrySpellTrigger(Mob *target, uint32 spell_id, int effect) if (!target || !IsValidSpell(spell_id)) return false; - /*The effects SE_SpellTrigger (SPA 340) and SE_Chance_Best_in_Spell_Grp (SPA 469) work as follows, you typically will have 2-3 different spells each with their own + /*The effects SpellEffect::SpellTrigger (SPA 340) and SpellEffect::Chance_Best_in_Spell_Grp (SPA 469) work as follows, you typically will have 2-3 different spells each with their own chance to be triggered with all chances equaling up to 100 pct, with only 1 spell out of the group being ultimately cast. (ie Effect1 trigger spellA with 30% chance, Effect2 triggers spellB with 20% chance, Effect3 triggers spellC with 50% chance). The following function ensures a statistically accurate chance for each spell to be cast based on their chance values. These effects are also used in spells where there @@ -5838,7 +5842,7 @@ bool Mob::TrySpellTrigger(Mob *target, uint32 spell_id, int effect) for (int i = 0; i < EFFECT_COUNT; i++) { - if (spells[spell_id].effect_id[i] == SE_SpellTrigger || spells[spell_id].effect_id[i] == SE_Chance_Best_in_Spell_Grp) + if (spells[spell_id].effect_id[i] == SpellEffect::SpellTrigger || spells[spell_id].effect_id[i] == SpellEffect::Chance_Best_in_Spell_Grp) total_chance += spells[spell_id].base_value[i]; } @@ -5848,7 +5852,7 @@ bool Mob::TrySpellTrigger(Mob *target, uint32 spell_id, int effect) for (int i = 0; i < EFFECT_COUNT; i++){ //Find spells with SPA 340 and add the cumulative percent chances to the roll array - if ((spells[spell_id].effect_id[i] == SE_SpellTrigger) || (spells[spell_id].effect_id[i] == SE_Chance_Best_in_Spell_Grp)){ + if ((spells[spell_id].effect_id[i] == SpellEffect::SpellTrigger) || (spells[spell_id].effect_id[i] == SpellEffect::Chance_Best_in_Spell_Grp)){ const int cumulative_chance = current_chance + spells[spell_id].base_value[i]; chance_array[i] = cumulative_chance; current_chance = cumulative_chance; @@ -5871,11 +5875,11 @@ bool Mob::TrySpellTrigger(Mob *target, uint32 spell_id, int effect) } if (CastSpell) { - if (spells[spell_id].effect_id[effect_slot] == SE_SpellTrigger && IsValidSpell(spells[spell_id].limit_value[effect_slot])) { + if (spells[spell_id].effect_id[effect_slot] == SpellEffect::SpellTrigger && IsValidSpell(spells[spell_id].limit_value[effect_slot])) { SpellFinished(spells[spell_id].limit_value[effect_slot], target, EQ::spells::CastingSlot::Item, 0, -1, spells[spells[spell_id].limit_value[effect_slot]].resist_difficulty); return true; } - else if (IsClient() && spells[spell_id].effect_id[effect_slot] == SE_Chance_Best_in_Spell_Grp) { + else if (IsClient() && spells[spell_id].effect_id[effect_slot] == SpellEffect::Chance_Best_in_Spell_Grp) { uint32 best_spell_id = CastToClient()->GetHighestScribedSpellinSpellGroup(spells[spell_id].limit_value[effect_slot]); if (IsValidSpell(best_spell_id)) { SpellFinished(best_spell_id, target, EQ::spells::CastingSlot::Item, 0, -1, spells[best_spell_id].resist_difficulty); @@ -5895,7 +5899,7 @@ void Mob::TryTriggerOnCastRequirement() int spell_id = buffs[e].spellid; if (IsValidSpell(spell_id)) { for (int i = 0; i < EFFECT_COUNT; i++) { - if ((spells[spell_id].effect_id[i] == SE_TriggerOnReqTarget) || (spells[spell_id].effect_id[i] == SE_TriggerOnReqCaster)) { + if ((spells[spell_id].effect_id[i] == SpellEffect::TriggerOnReqTarget) || (spells[spell_id].effect_id[i] == SpellEffect::TriggerOnReqCaster)) { if (PassCastRestriction(spells[spell_id].limit_value[i])) { SpellFinished(spells[spell_id].base_value[i], this, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].resist_difficulty); if (!TryFadeEffect(e)) { @@ -5938,7 +5942,7 @@ void Mob::TryTwincast(Mob *caster, Mob *target, uint32 spell_id) int buff_count = GetMaxTotalSlots(); for(int i = 0; i < buff_count; i++) { - if(IsEffectInSpell(buffs[i].spellid, SE_FcTwincast)) + if(IsEffectInSpell(buffs[i].spellid, SpellEffect::FcTwincast)) { int32 focus = CalcFocusEffect(focusTwincast, buffs[i].spellid, spell_id); if(focus > 0) @@ -5964,10 +5968,10 @@ void Mob::ApplyHealthTransferDamage(Mob *caster, Mob *target, uint16 spell_id) This allows for the AE spells to function without repeatedly killing caster Damage or heal portion can be found as regular single use spell effect */ - if (IsEffectInSpell(spell_id, SE_Health_Transfer)){ + if (IsEffectInSpell(spell_id, SpellEffect::Health_Transfer)){ for (int i = 0; i < EFFECT_COUNT; i++) { - if (spells[spell_id].effect_id[i] == SE_Health_Transfer) { + if (spells[spell_id].effect_id[i] == SpellEffect::Health_Transfer) { int64 new_hp = GetMaxHP(); new_hp -= GetMaxHP() * spells[spell_id].base_value[i] / 1000; @@ -6025,18 +6029,17 @@ int32 Mob::GetVulnerability(Mob *caster, uint32 spell_id, uint32 ticsremaining, bool Mob::IsTargetedFocusEffect(int focus_type) { switch (focus_type) { - case focusSpellVulnerability: - case focusFcSpellDamagePctIncomingPC: - case focusFcDamageAmtIncoming: - case focusFcSpellDamageAmtIncomingPC: - case focusFcCastSpellOnLand: - case focusFcHealAmtIncoming: - case focusFcHealPctCritIncoming: - case focusFcHealPctIncoming: - return true; - default: - return false; - + case focusSpellVulnerability: + case focusFcSpellDamagePctIncomingPC: + case focusFcDamageAmtIncoming: + case focusFcSpellDamageAmtIncomingPC: + case focusFcCastSpellOnLand: + case focusFcHealAmtIncoming: + case focusFcHealPctCritIncoming: + case focusFcHealPctIncoming: + return true; + default: + return false; } } @@ -6172,8 +6175,8 @@ bool Mob::TryFadeEffect(int slot) if (!spells[buffs[slot].spellid].effect_id[i]) continue; - if (spells[buffs[slot].spellid].effect_id[i] == SE_CastOnFadeEffectAlways || - spells[buffs[slot].spellid].effect_id[i] == SE_CastOnRuneFadeEffect) + if (spells[buffs[slot].spellid].effect_id[i] == SpellEffect::CastOnFadeEffectAlways || + spells[buffs[slot].spellid].effect_id[i] == SpellEffect::CastOnRuneFadeEffect) { uint16 spell_id = spells[buffs[slot].spellid].base_value[i]; BuffFadeBySlot(slot); @@ -6557,9 +6560,9 @@ void Mob::TrySpellOnKill(uint8 level, uint16 spell_id) { if (IsValidSpell(spell_id)) { - if(IsEffectInSpell(spell_id, SE_ProcOnSpellKillShot)) { + if(IsEffectInSpell(spell_id, SpellEffect::ProcOnSpellKillShot)) { for (int i = 0; i < EFFECT_COUNT; i++) { - if (spells[spell_id].effect_id[i] == SE_ProcOnSpellKillShot) + if (spells[spell_id].effect_id[i] == SpellEffect::ProcOnSpellKillShot) { if (IsValidSpell(spells[spell_id].limit_value[i]) && spells[spell_id].max_value[i] <= level) { @@ -6894,11 +6897,11 @@ void Mob::DoGravityEffect() int buff_count = GetMaxTotalSlots(); for (int slot = 0; slot < buff_count; slot++) { - if (IsValidSpell(buffs[slot].spellid) && IsEffectInSpell(buffs[slot].spellid, SE_GravityEffect)) + if (IsValidSpell(buffs[slot].spellid) && IsEffectInSpell(buffs[slot].spellid, SpellEffect::GravityEffect)) { for (int i = 0; i < EFFECT_COUNT; i++) { - if(spells[buffs[slot].spellid].effect_id[i] == SE_GravityEffect) { + if(spells[buffs[slot].spellid].effect_id[i] == SpellEffect::GravityEffect) { int casterId = buffs[slot].casterid; if(casterId) @@ -7105,7 +7108,7 @@ void Mob::CastOnCurer(uint32 spell_id) { for(int i = 0; i < EFFECT_COUNT; i++) { - if (spells[spell_id].effect_id[i] == SE_CastOnCurer) + if (spells[spell_id].effect_id[i] == SpellEffect::CastOnCurer) { if(IsValidSpell(spells[spell_id].base_value[i])) { @@ -7119,7 +7122,7 @@ void Mob::CastOnCure(uint32 spell_id) { for(int i = 0; i < EFFECT_COUNT; i++) { - if (spells[spell_id].effect_id[i] == SE_CastOnCure) + if (spells[spell_id].effect_id[i] == SpellEffect::CastOnCure) { if(IsValidSpell(spells[spell_id].base_value[i])) { @@ -7136,7 +7139,7 @@ void Mob::CastOnNumHitFade(uint32 spell_id) for(int i = 0; i < EFFECT_COUNT; i++) { - if (spells[spell_id].effect_id[i] == SE_CastonNumHitFade) + if (spells[spell_id].effect_id[i] == SpellEffect::CastonNumHitFade) { if(IsValidSpell(spells[spell_id].base_value[i])) { @@ -7167,56 +7170,56 @@ void Mob::SlowMitigation(Mob* caster) EQ::skills::SkillType Mob::GetSkillByItemType(int ItemType) { switch (ItemType) { - case EQ::item::ItemType1HSlash: - return EQ::skills::Skill1HSlashing; - case EQ::item::ItemType2HSlash: - return EQ::skills::Skill2HSlashing; - case EQ::item::ItemType1HPiercing: - return EQ::skills::Skill1HPiercing; - case EQ::item::ItemType1HBlunt: - return EQ::skills::Skill1HBlunt; - case EQ::item::ItemType2HBlunt: - return EQ::skills::Skill2HBlunt; - case EQ::item::ItemType2HPiercing: - if (IsClient() && CastToClient()->ClientVersion() < EQ::versions::ClientVersion::RoF2) + case EQ::item::ItemType1HSlash: + return EQ::skills::Skill1HSlashing; + case EQ::item::ItemType2HSlash: + return EQ::skills::Skill2HSlashing; + case EQ::item::ItemType1HPiercing: return EQ::skills::Skill1HPiercing; - else - return EQ::skills::Skill2HPiercing; - case EQ::item::ItemTypeBow: - return EQ::skills::SkillArchery; - case EQ::item::ItemTypeLargeThrowing: - case EQ::item::ItemTypeSmallThrowing: - return EQ::skills::SkillThrowing; - case EQ::item::ItemTypeMartial: - return EQ::skills::SkillHandtoHand; - default: - return EQ::skills::SkillHandtoHand; + case EQ::item::ItemType1HBlunt: + return EQ::skills::Skill1HBlunt; + case EQ::item::ItemType2HBlunt: + return EQ::skills::Skill2HBlunt; + case EQ::item::ItemType2HPiercing: + if (IsClient() && CastToClient()->ClientVersion() < EQ::versions::ClientVersion::RoF2) + return EQ::skills::Skill1HPiercing; + else + return EQ::skills::Skill2HPiercing; + case EQ::item::ItemTypeBow: + return EQ::skills::SkillArchery; + case EQ::item::ItemTypeLargeThrowing: + case EQ::item::ItemTypeSmallThrowing: + return EQ::skills::SkillThrowing; + case EQ::item::ItemTypeMartial: + return EQ::skills::SkillHandtoHand; + default: + return EQ::skills::SkillHandtoHand; } } uint8 Mob::GetItemTypeBySkill(EQ::skills::SkillType skill) { switch (skill) { - case EQ::skills::SkillThrowing: - return EQ::item::ItemTypeSmallThrowing; - case EQ::skills::SkillArchery: - return EQ::item::ItemTypeArrow; - case EQ::skills::Skill1HSlashing: - return EQ::item::ItemType1HSlash; - case EQ::skills::Skill2HSlashing: - return EQ::item::ItemType2HSlash; - case EQ::skills::Skill1HPiercing: - return EQ::item::ItemType1HPiercing; - case EQ::skills::Skill2HPiercing: // watch for undesired client behavior - return EQ::item::ItemType2HPiercing; - case EQ::skills::Skill1HBlunt: - return EQ::item::ItemType1HBlunt; - case EQ::skills::Skill2HBlunt: - return EQ::item::ItemType2HBlunt; - case EQ::skills::SkillHandtoHand: - return EQ::item::ItemTypeMartial; - default: - return EQ::item::ItemTypeMartial; + case EQ::skills::SkillThrowing: + return EQ::item::ItemTypeSmallThrowing; + case EQ::skills::SkillArchery: + return EQ::item::ItemTypeArrow; + case EQ::skills::Skill1HSlashing: + return EQ::item::ItemType1HSlash; + case EQ::skills::Skill2HSlashing: + return EQ::item::ItemType2HSlash; + case EQ::skills::Skill1HPiercing: + return EQ::item::ItemType1HPiercing; + case EQ::skills::Skill2HPiercing: // watch for undesired client behavior + return EQ::item::ItemType2HPiercing; + case EQ::skills::Skill1HBlunt: + return EQ::item::ItemType1HBlunt; + case EQ::skills::Skill2HBlunt: + return EQ::item::ItemType2HBlunt; + case EQ::skills::SkillHandtoHand: + return EQ::item::ItemTypeMartial; + default: + return EQ::item::ItemTypeMartial; } } @@ -7224,7 +7227,6 @@ uint16 Mob::GetWeaponSpeedbyHand(uint16 hand) { uint16 weapon_speed = 0; switch (hand) { - case 13: weapon_speed = attack_timer.GetDuration(); break; @@ -7702,7 +7704,7 @@ bool Mob::CanRaceEquipItem(uint32 item_id) } auto item_races = itm->Races; - if(item_races == PLAYER_RACE_ALL_MASK) { + if(item_races == RaceBitmask::All) { return true; } @@ -7721,28 +7723,26 @@ bool Mob::CanRaceEquipItem(uint32 item_id) void Mob::SendAddPlayerState(PlayerState new_state) { - auto app = new EQApplicationPacket(OP_PlayerStateAdd, sizeof(PlayerState_Struct)); - auto ps = (PlayerState_Struct *)app->pBuffer; + static EQApplicationPacket p(OP_PlayerStateAdd, sizeof(PlayerState_Struct)); + auto b = (PlayerState_Struct *) p.pBuffer; - ps->spawn_id = GetID(); - ps->state = static_cast(new_state); + b->spawn_id = GetID(); + b->state = static_cast(new_state); - AddPlayerState(ps->state); - entity_list.QueueClients(nullptr, app); - safe_delete(app); + AddPlayerState(b->state); + entity_list.QueueClients(nullptr, &p); } void Mob::SendRemovePlayerState(PlayerState old_state) { - auto app = new EQApplicationPacket(OP_PlayerStateRemove, sizeof(PlayerState_Struct)); - auto ps = (PlayerState_Struct *)app->pBuffer; + static EQApplicationPacket p(OP_PlayerStateRemove, sizeof(PlayerState_Struct)); + auto b = (PlayerState_Struct *) p.pBuffer; - ps->spawn_id = GetID(); - ps->state = static_cast(old_state); + b->spawn_id = GetID(); + b->state = static_cast(old_state); - RemovePlayerState(ps->state); - entity_list.QueueClients(nullptr, app); - safe_delete(app); + RemovePlayerState(b->state); + entity_list.QueueClients(nullptr, &p); } int32 Mob::GetMeleeMitigation() { @@ -8356,7 +8356,7 @@ int Mob::DispatchZoneControllerEvent( RuleB(Zone, UseZoneController) && ( !IsNPC() || - (IsNPC() && GetNPCTypeID() != ZONE_CONTROLLER_NPC_ID) + (IsNPC() && !IsZoneController()) ) ) { auto controller = entity_list.GetNPCByNPCTypeID(ZONE_CONTROLLER_NPC_ID); @@ -8579,8 +8579,9 @@ bool Mob::HasBotAttackFlag(Mob* tar) { return false; } + const uint16 scan_close_mobs_timer_moving = 6000; // 6 seconds -const uint16 scan_close_mobs_timer_idle = 60000; // 60 seconds +const uint16 scan_close_mobs_timer_idle = 60000; // 60 seconds // If the moving timer triggers, lets see if we are moving or idle to restart the appropriate dynamic timer void Mob::CheckScanCloseMobsMovingTimer() @@ -8609,7 +8610,186 @@ void Mob::CheckScanCloseMobsMovingTimer() } } -std::unordered_map &Mob::GetCloseMobList(float distance) +std::unordered_map& Mob::GetCloseMobList(float distance) { return entity_list.GetCloseMobList(this, distance); } + +void Mob::ClearDataBucketCache() +{ + if (IsOfClientBot()) { + uint64 id = 0; + DataBucketLoadType::Type t{}; + if (IsBot()) { + id = CastToBot()->GetBotID(); + t = DataBucketLoadType::Bot; + } + else if (IsClient()) { + id = CastToClient()->CharacterID(); + t = DataBucketLoadType::Client; + } + + DataBucket::DeleteFromCache(id, t); + } +} + +bool Mob::IsInGroupOrRaid(Mob* other, bool same_raid_group) { + if (!other || !IsOfClientBotMerc() || !other->IsOfClientBotMerc()) { + return false; + } + + if (this == other) { + return true; + } + + Raid* raid = IsBot() ? CastToBot()->GetStoredRaid() : (IsRaidGrouped() ? GetRaid() : nullptr); + + if (raid) { + if (!other->IsRaidGrouped()) { + return false; + } + + Raid* other_raid = other->IsBot() ? other->CastToBot()->GetStoredRaid() : other->GetRaid(); + + if (!other_raid) { + return false; + } + + auto raid_group = raid->GetGroup(GetCleanName()); + auto other_raid_group = other_raid->GetGroup(other->GetCleanName()); + + if ( + raid_group == RAID_GROUPLESS || + other_raid_group == RAID_GROUPLESS || + (same_raid_group && raid_group != other_raid_group) + ) { + return false; + } + + return true; + } + + Group* group = GetGroup(); + Group* other_group = other->GetGroup(); + + return group && group == other_group; +} + +bool Mob::DoLosChecks(Mob* other) { + if (!CheckLosFN(other) || !CheckWaterLoS(other)) { + if (RuleB(Map, EnableLoSCheatExemptions) && CheckLosCheatExempt(other)) { + return true; + } + + return false; + } + + if (RuleB(Map, CheckForDoorLoSCheat) && !CheckDoorLoSCheat(other)) { + return false; + } + + return true; +} + +bool Mob::CheckDoorLoSCheat(Mob* other) { + if (!other->IsOfClientBotMerc() && other->CastToNPC()->IsOnHatelist(this)) { + return true; + } + + const std::string& zones_to_check = RuleS(Map, ZonesToCheckDoorCheat); + + if (zones_to_check.empty()) { + return true; + } + + const auto& v = Strings::Split(zones_to_check, ","); + + if (zones_to_check == "all" || std::find(v.begin(), v.end(), std::to_string(zone->GetZoneID())) != v.end()) { + for (auto itr: entity_list.GetDoorsList()) { + Doors *d = itr.second; + + if ( + !d->IsDoorOpen() && + ( + d->GetKeyItem() || + d->GetLockpick() || + d->IsDoorBlacklisted() || + d->GetNoKeyring() != 0 + ) + ) { + float distance = Distance(m_Position, d->GetPosition()); + + if (distance > RuleR(Map, RangeCheckForDoorLoSCheat) || !CheckLosFN(d->GetX(), d->GetY(), d->GetZ(), GetSize())) { + continue; + } + + if (d->IsDoorBetween(GetPosition(), other->GetPosition(), d->GetSize())) { + return false; + } + } + } + } + + return true; +} + +bool Mob::CheckLosCheatExempt(Mob* other) { + glm::vec4 exempt_check_who; + + switch (zone->GetZoneID()) { + case Zones::POEARTHB: + exempt_check_who.x = 2053; exempt_check_who.y = 408; exempt_check_who.z = -219; //Middle of councilman spawns + //if the player is inside the cove they cannot be higher than the ceiling (no exploiting from uptop) --- 800 from center of council to furthest corner in cove + if (GetZ() <= -171 && other->GetZ() <= -171 && DistanceNoZ(other->GetPosition(), exempt_check_who) <= 800 && DistanceNoZ(GetPosition(), exempt_check_who) <= 800) { + return true; + } + default: + return false; + } + + return false; +} + +bool Mob::IsGuildmaster() const { + switch (GetClass()) { + case Class::WarriorGM: + case Class::ClericGM: + case Class::PaladinGM: + case Class::RangerGM: + case Class::ShadowKnightGM: + case Class::DruidGM: + case Class::MonkGM: + case Class::BardGM: + case Class::RogueGM: + case Class::ShamanGM: + case Class::NecromancerGM: + case Class::WizardGM: + case Class::MagicianGM: + case Class::EnchanterGM: + case Class::BeastlordGM: + case Class::BerserkerGM: + return true; + default: + return false; + } +} + +bool Mob::LoadDataBucketsCache() +{ + const uint32 id = GetMobTypeIdentifier(); + + if (!id) { + return false; + } + + if (IsBot()) { + DataBucket::BulkLoadEntitiesToCache(DataBucketLoadType::Bot, {id}); + } + else if (IsClient()) { + uint32 account_id = CastToClient()->AccountID(); + DataBucket::BulkLoadEntitiesToCache(DataBucketLoadType::Account, {account_id}); + DataBucket::BulkLoadEntitiesToCache(DataBucketLoadType::Client, {id}); + } + + return true; +} diff --git a/zone/mob.h b/zone/mob.h index d1bf386d6..ca0abe268 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -20,7 +20,7 @@ #define MOB_H #include "common.h" -#include "data_bucket.h" +#include "../common/data_bucket.h" #include "entity.h" #include "hate_list.h" #include "pathfinder_interface.h" @@ -98,7 +98,6 @@ class Mob : public Entity { public: enum CLIENT_CONN_STATUS { CLIENT_CONNECTING, CLIENT_CONNECTED, CLIENT_LINKDEAD, CLIENT_KICKED, DISCONNECTED, CLIENT_ERROR, CLIENT_CONNECTINGALL }; - enum eStandingPetOrder { SPO_Follow, SPO_Sit, SPO_Guard, SPO_FeignDeath }; struct MobSpecialAbility { MobSpecialAbility() { @@ -192,7 +191,8 @@ public: bool in_always_aggros_foes, int32 in_heroic_strikethrough, bool keeps_sold_items, - int64 in_hp_regen_per_second = 0 + int64 in_hp_regen_per_second = 0, + uint32 npc_tint_id = 0 ); virtual ~Mob(); @@ -201,9 +201,16 @@ public: void DisplayInfo(Mob *mob); - std::unordered_map m_close_mobs; - Timer m_scan_close_mobs_timer; - Timer m_mob_check_moving_timer; + std::unordered_map m_close_mobs; + std::unordered_map m_last_seen_mob_position; + Timer m_scan_close_mobs_timer; + Timer m_see_close_mobs_timer; + Timer m_mob_check_moving_timer; + + uint16 m_last_wearchange_race_id = 0; + // client_id -> slot_id -> key + std::unordered_map> m_last_seen_wearchange; + Timer m_clear_wearchange_cache_timer; // Bot attack flag Timer bot_attack_flag_timer; @@ -403,6 +410,7 @@ public: virtual bool CheckFizzle(uint16 spell_id); virtual bool CheckSpellLevelRestriction(Mob *caster, uint16 spell_id); virtual bool IsImmuneToSpell(uint16 spell_id, Mob *caster); + virtual float GetAOERange(uint16 spell_id); void InterruptSpell(uint16 spellid = SPELL_UNKNOWN); void InterruptSpell(uint16, uint16, uint16 spellid = SPELL_UNKNOWN); @@ -511,6 +519,7 @@ public: void ApplySpellBuff(int spell_id, int duration = 0, int level_override = -1); int GetBuffStatValueBySpell(int32 spell_id, const char* stat_identifier); int GetBuffStatValueBySlot(uint8 slot, const char* stat_identifier); + virtual bool GetIllusionBlock() const { return false; } //Basic Stats/Inventory virtual void SetLevel(uint8 in_level, bool command = false) { level = in_level; } @@ -591,7 +600,7 @@ public: inline const char* GetName() const { return name; } inline const char* GetOrigName() const { return orig_name; } inline const char* GetLastName() const { return lastname; } - inline const eStandingPetOrder GetPreviousPetOrder() const { return m_previous_pet_order; } + inline const uint8 GetPreviousPetOrder() const { return m_previous_pet_order; } const char *GetCleanName(); virtual void SetName(const char *new_name = nullptr) { new_name ? strn0cpy(name, new_name, 64) : strn0cpy(name, GetName(), 64); return; }; @@ -667,6 +676,7 @@ public: ((static_cast(current_mana) / max_mana) * 100); } virtual int64 CalcMaxMana(); uint32 GetNPCTypeID() const { return npctype_id; } + inline bool IsZoneController() const { return npctype_id == ZONE_CONTROLLER_NPC_ID; } void SetNPCTypeID(uint32 npctypeid) { npctype_id = npctypeid; } inline const glm::vec4& GetPosition() const { return m_Position; } inline void SetPosition(const float x, const float y, const float z) { m_Position.x = x; m_Position.y = y; m_Position.z = z; } @@ -705,6 +715,7 @@ public: virtual bool HasGroup() = 0; virtual Raid* GetRaid() = 0; virtual Group* GetGroup() = 0; + bool IsInGroupOrRaid(Mob* other, bool same_raid_group = false); //Faction virtual inline int32 GetPrimaryFaction() const { return 0; } @@ -791,6 +802,10 @@ public: bool CheckLosFN(float posX, float posY, float posZ, float mobSize); static bool CheckLosFN(glm::vec3 posWatcher, float sizeWatcher, glm::vec3 posTarget, float sizeTarget); virtual bool CheckWaterLoS(Mob* m); + bool CheckPositioningLosFN(Mob* other, float posX, float posY, float posZ); + bool CheckDoorLoSCheat(Mob* other); //door skipping checks for LoS + bool CheckLosCheatExempt(Mob* other); //exemptions to bypass los + bool DoLosChecks(Mob* other); inline void SetLastLosState(bool value) { last_los_check = value; } inline bool CheckLastLosState() const { return last_los_check; } std::string GetMobDescription(); @@ -854,6 +869,7 @@ public: void ShowStats(Client* client); void ShowBuffs(Client* c); bool PlotPositionAroundTarget(Mob* target, float &x_dest, float &y_dest, float &z_dest, bool lookForAftArc = true); + virtual int GetKillExpMod() const { return 100; } // aura functions @@ -951,7 +967,7 @@ public: uint16 GetSympatheticFocusEffect(focusType type, uint16 spell_id); bool TryFadeEffect(int slot); void DispelMagic(Mob* casterm, uint16 spell_id, int effect_value); - uint16 GetSpellEffectResistChance(uint16 spell_id); + bool TrySpellEffectResist(uint16 spell_id); int32 GetVulnerability(Mob *caster, uint32 spell_id, uint32 ticsremaining, bool from_buff_tic = false); int64 GetFcDamageAmtIncoming(Mob *caster, int32 spell_id, bool from_buff_tic = false); int64 GetFocusIncoming(focusType type, int effect, Mob *caster, uint32 spell_id); //**** This can be removed when bot healing focus code is updated **** @@ -1055,6 +1071,7 @@ public: void SendWearChangeAndLighting(int8 last_texture); inline uint8 GetActiveLightType() { return m_Light.Type[EQ::lightsource::LightActive]; } bool UpdateActiveLight(); // returns true if change, false if no change + uint32 GetNpcTintId() { return m_npc_tint_id; } EQ::LightSourceProfile* GetLightProfile() { return &m_Light; } @@ -1065,14 +1082,14 @@ public: Mob* GetUltimateOwner(); void SetPetID(uint16 NewPetID); inline uint16 GetPetID() const { return petid; } - inline PetType GetPetType() const { return type_of_pet; } - void SetPetType(PetType p) { type_of_pet = p; } + inline uint8 GetPetType() const { return type_of_pet; } + void SetPetType(uint8 pet_type) { type_of_pet = pet_type; } inline int16 GetPetPower() const { return (petpower < 0) ? 0 : petpower; } void SetPetPower(int16 p) { if (p < 0) petpower = 0; else petpower = p; } - bool IsFamiliar() const { return type_of_pet == petFamiliar; } - bool IsAnimation() const { return type_of_pet == petAnimation; } - bool IsCharmed() const { return type_of_pet == petCharmed; } - bool IsTargetLockPet() const { return type_of_pet == petTargetLock; } + bool IsFamiliar() const { return type_of_pet == PetType::Familiar; } + bool IsAnimation() const { return type_of_pet == PetType::Animation; } + bool IsCharmed() const { return type_of_pet == PetType::Charmed; } + bool IsTargetLockPet() const { return type_of_pet == PetType::TargetLock; } inline uint32 GetPetTargetLockID() { return pet_targetlock_id; }; inline void SetPetTargetLockID(uint32 value) { pet_targetlock_id = value; }; void SetOwnerID(uint16 new_owner_id); @@ -1092,6 +1109,7 @@ public: inline void SetPetOwnerClient(bool b) { pet_owner_client = b; } inline bool IsPetOwnerNPC() const { return pet_owner_npc; } inline void SetPetOwnerNPC(bool b) { pet_owner_npc = b; } + inline bool IsPetOwnerOfClientBot() const { return pet_owner_bot || pet_owner_client; } inline bool IsTempPet() const { return _IsTempPet; } inline void SetTempPet(bool value) { _IsTempPet = value; } inline bool IsHorse() { return is_horse; } @@ -1113,7 +1131,7 @@ public: virtual void SetAttackTimer(); inline void SetInvul(bool invul) { invulnerable=invul; } - inline bool GetInvul(void) { return invulnerable; } + inline bool GetInvul() { return invulnerable; } void SetExtraHaste(int haste, bool need_to_save = true); inline int GetExtraHaste() { return extra_haste; } virtual int GetHaste(); @@ -1193,8 +1211,8 @@ public: inline const float GetAssistRange() const { return (spellbonuses.AssistRange == -1) ? pAssistRange : spellbonuses.AssistRange; } - void SetPetOrder(eStandingPetOrder i); - inline const eStandingPetOrder GetPetOrder() const { return pStandingPetOrder; } + void SetPetOrder(uint8 pet_order); + inline const uint8 GetPetOrder() const { return m_pet_order; } inline void SetHeld(bool nState) { held = nState; } inline const bool IsHeld() const { return held; } inline void SetGHeld(bool nState) { gheld = nState; } @@ -1250,19 +1268,20 @@ public: float GetFixedZ(const glm::vec3 &destination, int32 z_find_offset = 5); virtual int GetStuckBehavior() const { return 0; } - void NPCSpecialAttacks(const char* parse, int permtag, bool reset = true, bool remove = false); - inline uint32 DontHealMeBefore() const { return pDontHealMeBefore; } - inline uint32 DontBuffMeBefore() const { return pDontBuffMeBefore; } - inline uint32 DontDotMeBefore() const { return pDontDotMeBefore; } - inline uint32 DontRootMeBefore() const { return pDontRootMeBefore; } - inline uint32 DontSnareMeBefore() const { return pDontSnareMeBefore; } - inline uint32 DontCureMeBefore() const { return pDontCureMeBefore; } - void SetDontRootMeBefore(uint32 time) { pDontRootMeBefore = time; } - void SetDontHealMeBefore(uint32 time) { pDontHealMeBefore = time; } - void SetDontBuffMeBefore(uint32 time) { pDontBuffMeBefore = time; } - void SetDontDotMeBefore(uint32 time) { pDontDotMeBefore = time; } - void SetDontSnareMeBefore(uint32 time) { pDontSnareMeBefore = time; } - void SetDontCureMeBefore(uint32 time) { pDontCureMeBefore = time; } + void NPCSpecialAttacks(const char *parse, int permtag, bool reset = true, bool remove = false); + inline uint32 DontHealMeBefore() const { return m_dont_heal_me_before; } + inline uint32 DontBuffMeBefore() const { return m_dont_buff_me_before; } + inline uint32 DontDotMeBefore() const { return m_dont_dot_me_before; } + inline uint32 DontRootMeBefore() const { return m_dont_root_me_before; } + inline uint32 DontSnareMeBefore() const { return m_dont_snare_me_before; } + inline uint32 DontCureMeBefore() const { return m_dont_cure_me_before; } + + void SetDontRootMeBefore(uint32 time) { m_dont_root_me_before = time; } + void SetDontHealMeBefore(uint32 time) { m_dont_heal_me_before = time; } + void SetDontBuffMeBefore(uint32 time) { m_dont_buff_me_before = time; } + void SetDontDotMeBefore(uint32 time) { m_dont_dot_me_before = time; } + void SetDontSnareMeBefore(uint32 time) { m_dont_snare_me_before = time; } + void SetDontCureMeBefore(uint32 time) { m_dont_cure_me_before = time; } // calculate interruption of spell via movement of mob void SaveSpellLoc() { m_SpellLocation = glm::vec3(m_Position); } @@ -1277,9 +1296,10 @@ public: void SetLooting(uint16 val) { entity_id_being_looted = val; } bool CheckWillAggro(Mob *mob); + bool IsPetAggroExempt(Mob *pet_owner); void InstillDoubt(Mob *who); - bool Charmed() const { return type_of_pet == petCharmed; } + bool Charmed() const { return type_of_pet == PetType::Charmed; } static uint32 GetLevelHP(uint8 tlevel); uint32 GetZoneID() const; //for perl uint16 GetInstanceVersion() const; //for perl @@ -1375,6 +1395,9 @@ public: std::string GetRacePlural(); std::string GetClassPlural(); + inline void SetMerchantSessionEntityID(uint16 value) { m_merchant_session_entity_id = value; } + inline uint16 GetMerchantSessionEntityID() { return m_merchant_session_entity_id; } + //Command #Tune functions void TuneGetStats(Mob* defender, Mob *attacker); void TuneGetACByPctMitigation(Mob* defender, Mob *attacker, float pct_mitigation, int interval = 10, int max_loop = 1000, int atk_override = 0, int Msg = 0); @@ -1485,12 +1508,17 @@ public: void CalcHeroicBonuses(StatBonuses* newbon); DataBucketKey GetScopedBucketKeys(); + bool LoadDataBucketsCache(); bool IsCloseToBanker(); std::unordered_map &GetCloseMobList(float distance = 0.0f); void CheckScanCloseMobsMovingTimer(); + void ClearDataBucketCache(); + bool IsGuildmaster() const; + bool IsDestroying() const { return m_destroying; } + protected: void CommonDamage(Mob* other, int64 &damage, const uint16 spell_id, const EQ::skills::SkillType attack_skill, bool &avoidable, const int8 buffslot, const bool iBuffTic, eSpecialAttacks specal = eSpecialAttacks::None); static uint16 GetProcID(uint16 spell_id, uint8 effect_index); @@ -1567,7 +1595,7 @@ protected: StatBonuses aabonuses; uint16 petid; uint16 ownerid; - PetType type_of_pet; + uint8 type_of_pet; int16 petpower; uint32 follow_id; uint32 follow_dist; @@ -1576,6 +1604,7 @@ protected: bool rare_spawn; int32 heroic_strikethrough; bool keeps_sold_items; + uint32 m_npc_tint_id; uint32 m_PlayerState; uint32 GetPlayerState() { return m_PlayerState; } @@ -1795,8 +1824,8 @@ protected: Timer viral_timer; // MobAI stuff - eStandingPetOrder pStandingPetOrder; - eStandingPetOrder m_previous_pet_order; + uint8 m_pet_order; + uint8 m_previous_pet_order; uint32 minLastFightingDelayMoving; uint32 maxLastFightingDelayMoving; float pAggroRange = 0; @@ -1855,12 +1884,12 @@ protected: bool pause_timer_complete; bool DistractedFromGrid; - uint32 pDontHealMeBefore; - uint32 pDontBuffMeBefore; - uint32 pDontDotMeBefore; - uint32 pDontRootMeBefore; - uint32 pDontSnareMeBefore; - uint32 pDontCureMeBefore; + uint32 m_dont_heal_me_before; + uint32 m_dont_buff_me_before; + uint32 m_dont_dot_me_before; + uint32 m_dont_root_me_before; + uint32 m_dont_snare_me_before; + uint32 m_dont_cure_me_before; // hp event int nexthpevent; @@ -1905,11 +1934,14 @@ protected: MobMovementManager *mMovementManager; + uint16 m_merchant_session_entity_id; + private: Mob* target; EQ::InventoryProfile m_inv; std::shared_ptr m_target_of_heal_rotation; bool m_manual_follow; + bool m_destroying; void SetHeroicStrBonuses(StatBonuses* n); void SetHeroicStaBonuses(StatBonuses* n); diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index 69255073b..516df6384 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -139,7 +139,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes, bool bInnates if ( (spells[AIspells[i].spellid].target_type == ST_Target || tar == this) && tar->DontHealMeBefore() < Timer::GetCurrentTime() - && !(tar->IsPet() && tar->GetOwner()->IsClient()) //no buffing PC's pets + && !(tar->IsPet() && tar->GetOwner()->IsOfClientBot()) //no buffing PC's pets ) { auto hp_ratio = tar->GetIntHPRatio(); @@ -180,7 +180,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes, bool bInnates && tar->DontBuffMeBefore() < Timer::GetCurrentTime() && !tar->IsImmuneToSpell(AIspells[i].spellid, this) && tar->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0 - && !(tar->IsPet() && tar->GetOwner()->IsClient() && this != tar) //no buffing PC's pets, but they can buff themself + && !(tar->IsPet() && tar->GetOwner()->IsOfClientBot() && this != tar) //no buffing PC's pets, but they can buff themself ) { if(!checked_los) { @@ -393,12 +393,12 @@ void Mob::AI_Init() minLastFightingDelayMoving = RuleI(NPC, LastFightingDelayMovingMin); maxLastFightingDelayMoving = RuleI(NPC, LastFightingDelayMovingMax); - pDontHealMeBefore = 0; - pDontBuffMeBefore = Timer::GetCurrentTime() + 400; - pDontDotMeBefore = 0; - pDontRootMeBefore = 0; - pDontSnareMeBefore = 0; - pDontCureMeBefore = 0; + m_dont_heal_me_before = 0; + m_dont_buff_me_before = Timer::GetCurrentTime() + 400; + m_dont_dot_me_before = 0; + m_dont_root_me_before = 0; + m_dont_snare_me_before = 0; + m_dont_cure_me_before = 0; } void NPC::AI_Init() @@ -615,7 +615,7 @@ void Client::AI_SpellCast() continue; } - if(IsEffectInSpell(current_spell, SE_Charm)) + if(IsEffectInSpell(current_spell, SpellEffect::Charm)) { continue; } @@ -724,7 +724,7 @@ void Client::AI_Process() { if(!IsFeared() && !IsLD()) { - BuffFadeByEffect(SE_Charm); + BuffFadeByEffect(SpellEffect::Charm); return; } } @@ -1219,7 +1219,7 @@ void Mob::AI_Process() { //SE_PC_Pet_Rampage SPA 464 on pet, chance modifier - if ((IsPet() || IsTempPet()) && IsPetOwnerClient()) { + if ((IsPet() || IsTempPet()) && IsPetOwnerOfClientBot()) { int chance = spellbonuses.PC_Pet_Rampage[SBIndex::PET_RAMPAGE_CHANCE] + itembonuses.PC_Pet_Rampage[SBIndex::PET_RAMPAGE_CHANCE] + aabonuses.PC_Pet_Rampage[SBIndex::PET_RAMPAGE_CHANCE]; if (chance && zone->random.Roll(chance)) { Rampage(nullptr); @@ -1262,7 +1262,7 @@ void Mob::AI_Process() { } //SE_PC_Pet_Rampage SPA 465 on pet, chance modifier - if ((IsPet() || IsTempPet()) && IsPetOwnerClient()) { + if ((IsPet() || IsTempPet()) && IsPetOwnerOfClientBot()) { int chance = spellbonuses.PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_CHANCE] + itembonuses.PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_CHANCE] + aabonuses.PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_CHANCE]; if (chance && zone->random.Roll(chance)) { Rampage(nullptr); @@ -1397,8 +1397,8 @@ void Mob::AI_Process() { else if (AI_movement_timer->Check() && !IsRooted()) { if (IsPet()) { // we're a pet, do as we're told - switch (pStandingPetOrder) { - case SPO_Follow: { + switch (m_pet_order) { + case PetOrder::Follow: { Mob *owner = GetOwner(); if (owner == nullptr) { @@ -1447,18 +1447,18 @@ void Mob::AI_Process() { break; } - case SPO_Sit: { + case PetOrder::Sit: { SetAppearance(eaSitting, false); break; } - case SPO_Guard: { + case PetOrder::Guard: { //only NPCs can guard stuff. (forced by where the guard movement code is in the AI) if (IsNPC()) { CastToNPC()->NextGuardPosition(); } break; } - case SPO_FeignDeath: { + case PetOrder::Feign: { SetAppearance(eaDead, false); break; } @@ -1945,8 +1945,8 @@ void Mob::StartEnrage() return; } - if(RuleB(NPC, LiveLikeEnrage) && !((IsPet() && !IsCharmed() && GetOwner() && GetOwner()->IsClient()) || - (CastToNPC()->GetSwarmOwner() && entity_list.GetMob(CastToNPC()->GetSwarmOwner())->IsClient()))) { + if(RuleB(NPC, LiveLikeEnrage) && !((IsPet() && !IsCharmed() && GetOwner() && GetOwner()->IsOfClientBot()) || + (CastToNPC()->GetSwarmOwner() && entity_list.GetMob(CastToNPC()->GetSwarmOwner())->IsOfClientBot()))) { return; } diff --git a/zone/mob_appearance.cpp b/zone/mob_appearance.cpp index 4f6b2b509..2556a41b1 100644 --- a/zone/mob_appearance.cpp +++ b/zone/mob_appearance.cpp @@ -286,8 +286,8 @@ uint32 Mob::GetHerosForgeModel(uint8 material_slot) const if (augment) { item = augment->GetItem(); heros_forge_model = item->HerosForgeModel; - } else if (inst->GetOrnamentHeroModel()) { - heros_forge_model = inst->GetOrnamentHeroModel(); + } else if (inst->GetOrnamentHeroModel(material_slot)) { + heros_forge_model = inst->GetOrnamentHeroModel(material_slot); } } } @@ -381,10 +381,6 @@ void Mob::SendWearChange(uint8 material_slot, Client *one_client) auto packet = new EQApplicationPacket(OP_WearChange, sizeof(WearChange_Struct)); auto w = (WearChange_Struct *) packet->pBuffer; - Log(Logs::Detail, Logs::MobAppearance, "[%s]", - GetCleanName() - ); - w->spawn_id = GetID(); w->material = static_cast(GetEquipmentMaterial(material_slot)); w->elite_material = IsEliteMaterialItem(material_slot); @@ -399,10 +395,50 @@ void Mob::SendWearChange(uint8 material_slot, Client *one_client) w->wear_slot_id = material_slot; - if (!one_client) { - entity_list.QueueClients(this, packet); - } else { - one_client->QueuePacket(packet, false, Client::CLIENT_CONNECTED); + if (GetRace() != m_last_wearchange_race_id) { + m_last_seen_wearchange.clear(); + m_last_wearchange_race_id = GetRace(); + } + + // this is a hash-like key to deduplicate packets sent to clients + // it includes spawn_id, material, elite_material, hero_forge_model, wear_slot_id, and color + // we send an enormous amount of wearchange packets in brute-force fashion and this is a low cost way to deduplicate them + // we could remove all the extra wearchanges at the expense of tracing down intermittent visual bugs over a long time + auto build_key = [&](const WearChange_Struct& s) -> uint64_t { + uint64_t key = 0; + + key |= static_cast(s.material & 0xFFF) << 0; // 12 bits + key |= static_cast(s.elite_material & 0x1) << 12; // 1 bit + key |= static_cast(s.hero_forge_model & 0xFFFFF) << 13; // 20 bits + key |= static_cast(GetRace() & 0xFFFF) << 33; // 16 bits + + // Optional: Fold in color for appearance differences + uint8_t folded_color = static_cast( + (s.color.Color * 17ull) & 0xFF + ); + key |= static_cast(folded_color) << 49; + + return key; + }; + + + auto dedupe_key = build_key(*w); + auto send_if_changed = [&](Client* client) { + auto& last_key = m_last_seen_wearchange[client->GetID()][material_slot]; + if (last_key == dedupe_key) { + return; + } + last_key = dedupe_key; + client->QueuePacket(packet, true, Client::CLIENT_CONNECTED); + }; + + if (one_client) { + send_if_changed(one_client); + } + else { + for (auto& [_, client] : entity_list.GetClientList()) { + send_if_changed(client); + } } safe_delete(packet); diff --git a/zone/mob_info.cpp b/zone/mob_info.cpp index 1e9de2dd9..a896f8f01 100644 --- a/zone/mob_info.cpp +++ b/zone/mob_info.cpp @@ -626,6 +626,9 @@ inline void NPCCommandsMenu(Client* client, NPC* npc) if (npc->GetLoottableID() > 0) { menu_commands += "[" + Saylink::Silent("#npcloot show", "Loot") + "] "; + if (npc) { + menu_commands += fmt::format(" Item(s) ({}) ", npc->GetLootItems().size()); + } } if (npc->IsProximitySet()) { diff --git a/zone/mob_movement_manager.cpp b/zone/mob_movement_manager.cpp index 3bb5996a2..e82202df6 100644 --- a/zone/mob_movement_manager.cpp +++ b/zone/mob_movement_manager.cpp @@ -824,8 +824,8 @@ void MobMovementManager::SendCommandToClients( return; } - EQApplicationPacket outapp(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); - auto *spu = (PlayerPositionUpdateServer_Struct *) outapp.pBuffer; + static EQApplicationPacket p(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); + auto *spu = (PlayerPositionUpdateServer_Struct *) p.pBuffer; FillCommandStruct(spu, mob, delta_x, delta_y, delta_z, delta_heading, anim); @@ -839,6 +839,10 @@ void MobMovementManager::SendCommandToClients( continue; } + if (c->IsIdle()) { + continue; + } + _impl->Stats.TotalSent++; if (anim != 0) { @@ -851,12 +855,24 @@ void MobMovementManager::SendCommandToClients( _impl->Stats.TotalSentPosition++; } - c->QueuePacket(&outapp, false); + if (!mob->IsClient() && c->m_last_seen_mob_position.contains(mob->GetID())) { + if (c->m_last_seen_mob_position[mob->GetID()] == mob->GetPosition() && anim == 0) { + LogPositionUpdate( + "Mob [{}] has already been sent to client [{}] at this position, skipping", + mob->GetCleanName(), + c->GetCleanName() + ); + continue; + } + } + + c->QueuePacket(&p, false); + c->m_last_seen_mob_position[mob->GetID()] = mob->GetPosition(); } } else { float short_range = RuleR(Pathing, ShortMovementUpdateRange); - float long_range = zone->GetNpcPositionUpdateDistance(); + float long_range = RuleI(Range, MobCloseScanDistance); for (auto &c : _impl->Clients) { if (single_client && c != single_client) { @@ -867,6 +883,10 @@ void MobMovementManager::SendCommandToClients( continue; } + if (c->IsIdle()) { + continue; + } + float distance = c->CalculateDistance(mob->GetX(), mob->GetY(), mob->GetZ()); bool match = false; @@ -901,7 +921,19 @@ void MobMovementManager::SendCommandToClients( _impl->Stats.TotalSentPosition++; } - c->QueuePacket(&outapp, false); + if (!mob->IsClient() && c->m_last_seen_mob_position.contains(mob->GetID())) { + if (c->m_last_seen_mob_position[mob->GetID()] == mob->GetPosition() && anim == 0) { + LogPositionUpdate( + "Mob [{}] has already been sent to client [{}] at this position, skipping", + mob->GetCleanName(), + c->GetCleanName() + ); + continue; + } + } + + c->QueuePacket(&p, false); + c->m_last_seen_mob_position[mob->GetID()] = mob->GetPosition(); } } } @@ -909,16 +941,11 @@ void MobMovementManager::SendCommandToClients( float MobMovementManager::FixHeading(float in) { - auto h = in; - while (h > 512.0) { - h -= 512.0; + int h = static_cast(in) % 512; + if (h < 0) { + h += 512; } - - while (h < 0.0) { - h += 512.0; - } - - return h; + return static_cast(h); } void MobMovementManager::DumpStats(Client *client) diff --git a/zone/npc.cpp b/zone/npc.cpp index 38f597e63..0a91d8e52 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -49,6 +49,7 @@ #include "bot.h" #include "../common/skill_caps.h" +#include "../common/events/player_event_logs.h" #include #include @@ -61,9 +62,11 @@ #else #include #include + #endif extern Zone* zone; +extern QueryServ* QServ; extern volatile bool is_zone_loaded; extern EntityList entity_list; @@ -125,10 +128,12 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi npc_type_data->always_aggro, npc_type_data->heroic_strikethrough, npc_type_data->keeps_sold_items, - npc_type_data->hp_regen_per_second + npc_type_data->hp_regen_per_second, + npc_type_data->m_npc_tint_id ), attacked_timer(CombatEventTimer_expire), swarm_timer(100), + m_resumed_from_zone_suspend_shutoff_timer(10000), classattack_timer(1000), monkattack_timer(1000), knightattack_timer(1000), @@ -226,6 +231,7 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi ATK = npc_type_data->ATK; heroic_strikethrough = npc_type_data->heroic_strikethrough; keeps_sold_items = npc_type_data->keeps_sold_items; + m_multiquest_enabled = npc_type_data->multiquest_enabled; // used for when switch back to charm default_ac = npc_type_data->AC; @@ -364,7 +370,7 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi //give NPCs skill values... int r; for (r = 0; r <= EQ::skills::HIGHEST_SKILL; r++) { - skills[r] = skill_caps.GetSkillCap(GetClass(), (EQ::skills::SkillType)r, moblevel).cap; + skills[r] = SkillCaps::Instance()->GetSkillCap(GetClass(), (EQ::skills::SkillType)r, moblevel).cap; } // some overrides -- really we need to be able to set skills for mobs in the DB // There are some known low level SHM/BST pets that do not follow this, which supports @@ -446,6 +452,7 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi raid_target = npc_type_data->raid_target; ignore_despawn = npc_type_data->ignore_despawn; m_targetable = !npc_type_data->untargetable; + m_npc_tint_id = npc_type_data->m_npc_tint_id; npc_scale_manager->ScaleNPC(this); @@ -615,7 +622,20 @@ bool NPC::Process() } } + // zone state corpse creation timer + if (RuleB(Zone, StateSavingOnShutdown)) { + // shuts off the temporary spawn protected state of the NPC + if (m_resumed_from_zone_suspend_shutoff_timer.Check()) { + m_resumed_from_zone_suspend_shutoff_timer.Disable(); + SetResumedFromZoneSuspend(false); + } + } + if (tic_timer.Check()) { + if (m_clear_wearchange_cache_timer.Check()) { + m_last_seen_wearchange.clear(); + } + if (parse->HasQuestSub(GetNPCTypeID(), EVENT_TICK)) { parse->EventNPC(EVENT_TICK, this, nullptr, "", 0); } @@ -844,6 +864,11 @@ void NPC::Depop(bool start_spawn_timer) { DispatchZoneControllerEvent(EVENT_DESPAWN_ZONE, this, "", 0, nullptr); } + if (parse->ZoneHasQuestSub(EVENT_DESPAWN_ZONE)) { + std::vector args = { this }; + parse->EventZone(EVENT_DESPAWN_ZONE, zone, "", 0, &args); + } + p_depop = true; if (respawn2) { if (start_spawn_timer) { @@ -856,9 +881,9 @@ void NPC::Depop(bool start_spawn_timer) { bool NPC::SpawnZoneController() { - - if (!RuleB(Zone, UseZoneController)) + if (!RuleB(Zone, UseZoneController)) { return false; + } auto npc_type = new NPCType; memset(npc_type, 0, sizeof(NPCType)); @@ -892,13 +917,14 @@ bool NPC::SpawnZoneController() npc_type->findable = 0; npc_type->trackable = 0; + npc_type->untargetable = 1; - strcpy(npc_type->special_abilities, "12,1^13,1^14,1^15,1^16,1^17,1^19,1^22,1^24,1^25,1^28,1^31,1^35,1^39,1^42,1"); + strcpy(npc_type->special_abilities, "1,1,3000,50^12,1^14,1^16,1^18,1^19,1^20,1^21,1^22,1^23,1^24,1^25,1^26,1^32,1^33,1^35,1^46,1^47,1^48,1^49,1^50,1^52,1^53,1^54,1^55,1^56,1^57,1"); glm::vec4 point; - point.x = 3000; - point.y = 1000; - point.z = 500; + point.x = 30000; + point.y = 10000; + point.z = -10000; auto npc = new NPC(npc_type, nullptr, point, GravityBehavior::Flying); npc->GiveNPCTypeData(npc_type); @@ -997,8 +1023,8 @@ NPC * NPC::SpawnNodeNPC(std::string name, std::string last_name, const glm::vec4 npc_type->current_hp = 4000000; npc_type->max_hp = 4000000; - npc_type->race = 2254; - npc_type->gender = 2; + npc_type->race = 127; + npc_type->gender = 0; npc_type->class_ = 9; npc_type->deity = 1; npc_type->level = 200; @@ -1241,10 +1267,11 @@ uint32 ZoneDatabase::CreateNewNPCCommand( e.Avoidance = n->GetAvoidanceRating(); e.heroic_strikethrough = n->GetHeroicStrikethrough(); - e.see_hide = n->SeeHide(); - e.see_improved_hide = n->SeeImprovedHide(); - e.see_invis = n->SeeInvisible(); - e.see_invis_undead = n->SeeInvisibleUndead(); + e.see_hide = n->SeeHide(); + e.see_improved_hide = n->SeeImprovedHide(); + e.see_invis = n->SeeInvisible(); + e.see_invis_undead = n->SeeInvisibleUndead(); + e.npc_tint_id = n->GetNpcTintId(); e = NpcTypesRepository::InsertOne(*this, e); @@ -1384,6 +1411,7 @@ uint32 ZoneDatabase::UpdateNPCTypeAppearance(Client* c, NPC* n) e.loottable_id = n->GetLoottableID(); e.merchant_id = n->MerchantType; e.face = n->GetLuclinFace(); + e.npc_tint_id = n->GetNpcTintId(); const int updated = NpcTypesRepository::UpdateOne(*this, e); @@ -1524,6 +1552,7 @@ uint32 ZoneDatabase::AddNPCTypes( e.runspeed = n->GetRunspeed(); e.prim_melee_type = static_cast(EQ::skills::SkillHandtoHand); e.sec_melee_type = static_cast(EQ::skills::SkillHandtoHand); + e.npc_tint_id = n->GetNpcTintId(); e = NpcTypesRepository::InsertOne(*this, e); @@ -2154,9 +2183,10 @@ void NPC::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) PetOnSpawn(ns); ns->spawn.is_npc = 1; UpdateActiveLight(); - ns->spawn.light = GetActiveLightType(); - ns->spawn.show_name = NPCTypedata->show_name; - ns->spawn.trader = false; + ns->spawn.light = GetActiveLightType(); + ns->spawn.show_name = NPCTypedata->show_name; + ns->spawn.trader = false; + ns->spawn.npc_tint_id = GetNpcTintId(); } void NPC::PetOnSpawn(NewSpawn_Struct* ns) @@ -2244,9 +2274,9 @@ void NPC::SetLevel(uint8 in_level, bool command) void NPC::ModifyNPCStat(const std::string& stat, const std::string& value) { - auto stat_lower = Strings::ToLower(stat); + const std::string& stat_lower = Strings::ToLower(stat); - auto variable_key = fmt::format( + const std::string& variable_key = fmt::format( "modify_stat_{}", stat_lower ); @@ -2258,40 +2288,24 @@ void NPC::ModifyNPCStat(const std::string& stat, const std::string& value) if (stat_lower == "ac") { AC = Strings::ToInt(value); CalcAC(); - return; - } - else if (stat_lower == "str") { + } else if (stat_lower == "str") { STR = Strings::ToInt(value); - return; - } - else if (stat_lower == "sta") { + } else if (stat_lower == "sta") { STA = Strings::ToInt(value); - return; - } - else if (stat_lower == "agi") { + } else if (stat_lower == "agi") { AGI = Strings::ToInt(value); CalcAC(); - return; - } - else if (stat_lower == "dex") { + } else if (stat_lower == "dex") { DEX = Strings::ToInt(value); - return; - } - else if (stat_lower == "wis") { + } else if (stat_lower == "wis") { WIS = Strings::ToInt(value); CalcMaxMana(); - return; - } - else if (stat_lower == "int" || stat_lower == "_int") { + } else if (stat_lower == "int" || stat_lower == "_int") { INT = Strings::ToInt(value); CalcMaxMana(); - return; - } - else if (stat_lower == "cha") { + } else if (stat_lower == "cha") { CHA = Strings::ToInt(value); - return; - } - else if (stat_lower == "max_hp") { + } else if (stat_lower == "max_hp") { base_hp = Strings::ToBigInt(value); CalcMaxHP(); @@ -2299,45 +2313,27 @@ void NPC::ModifyNPCStat(const std::string& stat, const std::string& value) current_hp = max_hp; } - return; - } - else if (stat_lower == "max_mana") { + } else if (stat_lower == "max_mana") { npc_mana = Strings::ToUnsignedBigInt(value); CalcMaxMana(); if (current_mana > max_mana) { current_mana = max_mana; } - return; - } - else if (stat_lower == "mr") { + } else if (stat_lower == "mr") { MR = Strings::ToInt(value); - return; - } - else if (stat_lower == "fr") { + } else if (stat_lower == "fr") { FR = Strings::ToInt(value); - return; - } - else if (stat_lower == "cr") { + } else if (stat_lower == "cr") { CR = Strings::ToInt(value); - return; - } - else if (stat_lower == "cor") { + } else if (stat_lower == "cor") { Corrup = Strings::ToInt(value); - return; - } - else if (stat_lower == "pr") { + } else if (stat_lower == "pr") { PR = Strings::ToInt(value); - return; - } - else if (stat_lower == "dr") { + } else if (stat_lower == "dr") { DR = Strings::ToInt(value); - return; - } - else if (stat_lower == "phr") { + } else if (stat_lower == "phr") { PhR = Strings::ToInt(value); - return; - } - else if (stat_lower == "runspeed") { + } else if (stat_lower == "runspeed") { runspeed = Strings::ToFloat(value); base_runspeed = (int) (runspeed * 40.0f); base_walkspeed = base_runspeed * 100 / 265; @@ -2345,297 +2341,229 @@ void NPC::ModifyNPCStat(const std::string& stat, const std::string& value) base_fearspeed = base_runspeed * 100 / 127; fearspeed = ((float) base_fearspeed) * 0.025f; CalcBonuses(); - return; - } - else if (stat_lower == "special_attacks") { + } else if (stat_lower == "special_attacks") { NPCSpecialAttacks(value.c_str(), 0, true); - return; - } - else if (stat_lower == "special_abilities") { + } else if (stat_lower == "special_abilities") { ProcessSpecialAbilities(value); - return; - } - else if (stat_lower == "attack_speed") { + } else if (stat_lower == "attack_speed") { attack_speed = Strings::ToFloat(value); CalcBonuses(); - return; - } - else if (stat_lower == "attack_delay") { + } else if (stat_lower == "attack_delay") { /* TODO: fix DB */ attack_delay = Strings::ToInt(value) * 100; CalcBonuses(); - return; - } - else if (stat_lower == "atk") { + } else if (stat_lower == "atk") { ATK = Strings::ToInt(value); - return; - } - else if (stat_lower == "accuracy") { + } else if (stat_lower == "accuracy") { accuracy_rating = Strings::ToInt(value); - return; - } - else if (stat_lower == "avoidance") { + } else if (stat_lower == "avoidance") { avoidance_rating = Strings::ToInt(value); - return; - } - else if (stat_lower == "trackable") { + } else if (stat_lower == "trackable") { trackable = Strings::ToInt(value); - return; - } - else if (stat_lower == "min_hit") { - min_dmg = Strings::ToInt(value); + } else if (stat_lower == "min_hit") { + min_dmg = Strings::ToInt(value); // Clamp max_dmg to be >= min_dmg - max_dmg = std::max(min_dmg, max_dmg); + max_dmg = std::max(min_dmg, max_dmg); base_damage = round((max_dmg - min_dmg) / 1.9); min_damage = min_dmg - round(base_damage / 10.0); - return; - } - else if (stat_lower == "max_hit") { - max_dmg = Strings::ToInt(value); + } else if (stat_lower == "max_hit") { + max_dmg = Strings::ToInt(value); // Clamp min_dmg to be <= max_dmg - min_dmg = std::min(min_dmg, max_dmg); + min_dmg = std::min(min_dmg, max_dmg); base_damage = round((max_dmg - min_dmg) / 1.9); min_damage = min_dmg - round(base_damage / 10.0); - return; - } - else if (stat_lower == "attack_count") { + } else if (stat_lower == "attack_count") { attack_count = Strings::ToInt(value); - return; - } - else if (stat_lower == "see_invis") { + } else if (stat_lower == "see_invis") { see_invis = Strings::ToInt(value); - return; - } - else if (stat_lower == "see_invis_undead") { + } else if (stat_lower == "see_invis_undead") { see_invis_undead = Strings::ToInt(value); - return; - } - else if (stat_lower == "see_hide") { + } else if (stat_lower == "see_hide") { see_hide = Strings::ToInt(value); - return; - } - else if (stat_lower == "see_improved_hide") { + } else if (stat_lower == "see_improved_hide") { see_improved_hide = Strings::ToInt(value); - return; - } - else if (stat_lower == "hp_regen") { + } else if (stat_lower == "hp_regen") { hp_regen = Strings::ToBigInt(value); - return; - } - else if (stat_lower == "hp_regen_per_second") { + } else if (stat_lower == "hp_regen_per_second") { hp_regen_per_second = Strings::ToBigInt(value); - return; - } - else if (stat_lower == "mana_regen") { + } else if (stat_lower == "mana_regen") { mana_regen = Strings::ToBigInt(value); - return; - } - else if (stat_lower == "level") { + } else if (stat_lower == "level") { SetLevel(Strings::ToInt(value)); - return; - } - else if (stat_lower == "aggro") { + } else if (stat_lower == "aggro") { pAggroRange = Strings::ToFloat(value); - return; - } - else if (stat_lower == "assist") { + } else if (stat_lower == "assist") { pAssistRange = Strings::ToFloat(value); - return; - } - else if (stat_lower == "slow_mitigation") { + } else if (stat_lower == "slow_mitigation") { slow_mitigation = Strings::ToInt(value); - return; - } - else if (stat_lower == "loottable_id") { + } else if (stat_lower == "loottable_id") { m_loottable_id = Strings::ToFloat(value); - return; - } - else if (stat_lower == "healscale") { + } else if (stat_lower == "healscale") { healscale = Strings::ToFloat(value); - return; - } - else if (stat_lower == "spellscale") { + } else if (stat_lower == "spellscale") { spellscale = Strings::ToFloat(value); - return; - } - else if (stat_lower == "npc_spells_id") { + } else if (stat_lower == "npc_spells_id") { AI_AddNPCSpells(Strings::ToInt(value)); - return; - } - else if (stat_lower == "npc_spells_effects_id") { + } else if (stat_lower == "npc_spells_effects_id") { AI_AddNPCSpellsEffects(Strings::ToInt(value)); CalcBonuses(); - return; - } - else if (stat_lower == "heroic_strikethrough") { + } else if (stat_lower == "heroic_strikethrough") { heroic_strikethrough = Strings::ToInt(value); - return; - } - else if (stat_lower == "keeps_sold_items") { + } else if (stat_lower == "keeps_sold_items") { SetKeepsSoldItems(Strings::ToBool(value)); - return; + } else if (stat_lower == "charm_ac") { + charm_ac = Strings::ToInt(value); + } else if (stat_lower == "charm_min_dmg") { + charm_min_dmg = Strings::ToInt(value); + } else if (stat_lower == "charm_max_dmg") { + charm_max_dmg = Strings::ToInt(value); + } else if (stat_lower == "charm_attack_delay") { + charm_attack_delay = Strings::ToInt(value); + } else if (stat_lower == "charm_accuracy_rating") { + charm_accuracy_rating = Strings::ToInt(value); + } else if (stat_lower == "charm_avoidance_rating") { + charm_avoidance_rating = Strings::ToInt(value); + } else if (stat_lower == "charm_atk") { + charm_atk = Strings::ToInt(value); + } else if (stat_lower == "default_ac") { + default_ac = Strings::ToInt(value); + } else if (stat_lower == "default_min_dmg") { + default_min_dmg = Strings::ToInt(value); + } else if (stat_lower == "default_max_dmg") { + default_max_dmg = Strings::ToInt(value); + } else if (stat_lower == "default_attack_delay") { + default_attack_delay = Strings::ToInt(value); + } else if (stat_lower == "default_accuracy_rating") { + default_accuracy_rating = Strings::ToInt(value); + } else if (stat_lower == "default_avoidance_rating") { + default_avoidance_rating = Strings::ToInt(value); + } else if (stat_lower == "default_atk") { + default_atk = Strings::ToInt(value); } } float NPC::GetNPCStat(const std::string& stat) { + const std::string& stat_lower = Strings::ToLower(stat); - if (auto stat_lower = Strings::ToLower(stat); stat_lower == "ac") { + if (stat_lower == "ac") { return AC; - } - else if (stat_lower == "str") { + } else if (stat_lower == "str") { return STR; - } - else if (stat_lower == "sta") { + } else if (stat_lower == "sta") { return STA; - } - else if (stat_lower == "agi") { + } else if (stat_lower == "agi") { return AGI; - } - else if (stat_lower == "dex") { + } else if (stat_lower == "dex") { return DEX; - } - else if (stat_lower == "wis") { + } else if (stat_lower == "wis") { return WIS; - } - else if (stat_lower == "int" || stat_lower == "_int") { + } else if (stat_lower == "int" || stat_lower == "_int") { return INT; - } - else if (stat_lower == "cha") { + } else if (stat_lower == "cha") { return CHA; - } - else if (stat_lower == "max_hp") { + } else if (stat_lower == "max_hp") { return base_hp; - } - else if (stat_lower == "max_mana") { + } else if (stat_lower == "max_mana") { return npc_mana; - } - else if (stat_lower == "mr") { + } else if (stat_lower == "mr") { return MR; - } - else if (stat_lower == "fr") { + } else if (stat_lower == "fr") { return FR; - } - else if (stat_lower == "cr") { + } else if (stat_lower == "cr") { return CR; - } - else if (stat_lower == "cor") { + } else if (stat_lower == "cor") { return Corrup; - } - else if (stat_lower == "phr") { + } else if (stat_lower == "phr") { return PhR; - } - else if (stat_lower == "pr") { + } else if (stat_lower == "pr") { return PR; - } - else if (stat_lower == "dr") { + } else if (stat_lower == "dr") { return DR; - } - else if (stat_lower == "runspeed") { + } else if (stat_lower == "runspeed") { return runspeed; - } - else if (stat_lower == "attack_speed") { + } else if (stat_lower == "attack_speed") { return attack_speed; - } - else if (stat_lower == "attack_delay") { + } else if (stat_lower == "attack_delay") { return attack_delay; - } - else if (stat_lower == "atk") { + } else if (stat_lower == "atk") { return ATK; - } - else if (stat_lower == "accuracy") { + } else if (stat_lower == "accuracy") { return accuracy_rating; - } - else if (stat_lower == "avoidance") { + } else if (stat_lower == "avoidance") { return avoidance_rating; - } - else if (stat_lower == "trackable") { + } else if (stat_lower == "trackable") { return trackable; - } - else if (stat_lower == "min_hit") { + } else if (stat_lower == "min_hit") { return min_dmg; - } - else if (stat_lower == "max_hit") { + } else if (stat_lower == "max_hit") { return max_dmg; - } - else if (stat_lower == "attack_count") { + } else if (stat_lower == "attack_count") { return attack_count; - } - else if (stat_lower == "see_invis") { + } else if (stat_lower == "see_invis") { return see_invis; - } - else if (stat_lower == "see_invis_undead") { + } else if (stat_lower == "see_invis_undead") { return see_invis_undead; - } - else if (stat_lower == "see_hide") { + } else if (stat_lower == "see_hide") { return see_hide; - } - else if (stat_lower == "see_improved_hide") { + } else if (stat_lower == "see_improved_hide") { return see_improved_hide; - } - else if (stat_lower == "hp_regen") { + } else if (stat_lower == "hp_regen") { return hp_regen; - } - else if (stat_lower == "hp_regen_per_second") { + } else if (stat_lower == "hp_regen_per_second") { return hp_regen_per_second; - } - else if (stat_lower == "mana_regen") { + } else if (stat_lower == "mana_regen") { return mana_regen; - } - else if (stat_lower == "level") { + } else if (stat_lower == "level") { return GetOrigLevel(); - } - else if (stat_lower == "aggro") { + } else if (stat_lower == "aggro") { return pAggroRange; - } - else if (stat_lower == "assist") { + } else if (stat_lower == "assist") { return pAssistRange; - } - else if (stat_lower == "slow_mitigation") { + } else if (stat_lower == "slow_mitigation") { return slow_mitigation; - } - else if (stat_lower == "loottable_id") { + } else if (stat_lower == "loottable_id") { return m_loottable_id; - } - else if (stat_lower == "healscale") { + } else if (stat_lower == "healscale") { return healscale; - } - else if (stat_lower == "spellscale") { + } else if (stat_lower == "spellscale") { return spellscale; - } - else if (stat_lower == "npc_spells_id") { + } else if (stat_lower == "npc_spells_id") { return npc_spells_id; - } - else if (stat_lower == "npc_spells_effects_id") { + } else if (stat_lower == "npc_spells_effects_id") { return npc_spells_effects_id; - } - else if (stat_lower == "heroic_strikethrough") { + } else if (stat_lower == "heroic_strikethrough") { return heroic_strikethrough; - } - else if (stat_lower == "keeps_sold_items") { + } else if (stat_lower == "keeps_sold_items") { return keeps_sold_items; - } - //default values - else if (stat_lower == "default_ac") { + } else if (stat_lower == "default_ac") { return default_ac; - } - else if (stat_lower == "default_min_hit") { + } else if (stat_lower == "default_min_hit") { return default_min_dmg; - } - else if (stat_lower == "default_max_hit") { + } else if (stat_lower == "default_max_hit") { return default_max_dmg; - } - else if (stat_lower == "default_attack_delay") { + } else if (stat_lower == "default_attack_delay") { return default_attack_delay; - } - else if (stat_lower == "default_accuracy") { + } else if (stat_lower == "default_accuracy") { return default_accuracy_rating; - } - else if (stat_lower == "default_avoidance") { + } else if (stat_lower == "default_avoidance") { return default_avoidance_rating; - } - else if (stat_lower == "default_atk") { + } else if (stat_lower == "default_atk") { return default_atk; + } else if (stat_lower == "charm_ac") { + return charm_ac; + } else if (stat_lower == "charm_min_hit") { + return charm_min_dmg; + } else if (stat_lower == "charm_max_hit") { + return charm_max_dmg; + } else if (stat_lower == "charm_attack_delay") { + return charm_attack_delay; + } else if (stat_lower == "charm_accuracy") { + return charm_accuracy_rating; + } else if (stat_lower == "charm_avoidance") { + return charm_avoidance_rating; + } else if (stat_lower == "charm_atk") { + return charm_atk; } return 0.0f; @@ -2748,10 +2676,7 @@ void NPC::LevelScale() { uint32 NPC::GetSpawnPointID() const { - if (respawn2) { - return respawn2->GetID(); - } - return 0; + return respawn2 ? respawn2->GetID() : 0; } void NPC::NPCSlotTexture(uint8 slot, uint32 texture) @@ -2874,7 +2799,7 @@ void NPC::DoNPCEmote(uint8 event_, uint32 emote_id, Mob* t) // Mob Variables Strings::FindReplace(processed, "$mname", GetCleanName()); Strings::FindReplace(processed, "$mracep", GetRacePlural()); - Strings::FindReplace(processed, "$mrace", GetPlayerRaceName(GetRace())); + Strings::FindReplace(processed, "$mrace", GetRaceIDName(GetRace())); Strings::FindReplace(processed, "$mclass", GetClassIDName(GetClass())); Strings::FindReplace(processed, "$mclassp", GetClassPlural()); @@ -2882,7 +2807,7 @@ void NPC::DoNPCEmote(uint8 event_, uint32 emote_id, Mob* t) Strings::FindReplace(processed, "$name", t ? t->GetCleanName() : "foe"); Strings::FindReplace(processed, "$class", t ? GetClassIDName(t->GetClass()) : "class"); Strings::FindReplace(processed, "$classp", t ? t->GetClassPlural() : "classes"); - Strings::FindReplace(processed, "$race", t ? GetPlayerRaceName(t->GetRace()) : "race"); + Strings::FindReplace(processed, "$race", t ? GetRaceIDName(t->GetRace()) : "race"); Strings::FindReplace(processed, "$racep", t ? t->GetRacePlural() : "races"); if (emoteid == e->emoteid) { @@ -3360,62 +3285,55 @@ void NPC::DepopSwarmPets() } } -void NPC::ModifyStatsOnCharm(bool is_charm_removed) +void NPC::ModifyStatsOnCharm(bool remove_charm, Mob* charmer) { - if (is_charm_removed) { - if (charm_ac) { - AC = default_ac; - } - if (charm_attack_delay) { - attack_delay = default_attack_delay; - } - if (charm_accuracy_rating) { - accuracy_rating = default_accuracy_rating; - } - if (charm_avoidance_rating) { - avoidance_rating = default_avoidance_rating; - } - if (charm_atk) { - ATK = default_atk; - } - if (charm_min_dmg || charm_max_dmg) { - base_damage = round((default_max_dmg - default_min_dmg) / 1.9); - min_damage = default_min_dmg - round(base_damage / 10.0); - } - if (RuleB(Spells, CharmDisablesSpecialAbilities)) { - ProcessSpecialAbilities(default_special_abilities); - } - - SetAttackTimer(); - CalcAC(); - - return; + if (!remove_charm && parse->HasQuestSub(GetNPCTypeID(), EVENT_CHARM_START)) { + parse->EventNPC(EVENT_CHARM_START, this, charmer, "", 0); + } else if (remove_charm && parse->HasQuestSub(GetNPCTypeID(), EVENT_CHARM_END)) { + parse->EventNPC(EVENT_CHARM_END, this, charmer, "", 0); } - if (charm_ac) { - AC = charm_ac; + const int new_ac = remove_charm ? default_ac : charm_ac; + const int new_attack_delay = remove_charm ? default_attack_delay : charm_attack_delay; + const int new_accuracy_rating = remove_charm ? default_accuracy_rating : charm_accuracy_rating; + const int new_avoidance_rating = remove_charm ? default_avoidance_rating : charm_avoidance_rating; + const int new_atk = remove_charm ? default_atk : charm_atk; + const int new_min_dmg = remove_charm ? default_min_dmg : charm_min_dmg; + const int new_max_dmg = remove_charm ? default_max_dmg : charm_max_dmg; + + if (new_ac) { + AC = new_ac; } - if (charm_attack_delay) { - attack_delay = charm_attack_delay; + + if (new_attack_delay) { + attack_delay = new_attack_delay; } - if (charm_accuracy_rating) { - accuracy_rating = charm_accuracy_rating; + + if (new_accuracy_rating) { + accuracy_rating = new_accuracy_rating; } - if (charm_avoidance_rating) { - avoidance_rating = charm_avoidance_rating; + + if (new_avoidance_rating) { + avoidance_rating = new_avoidance_rating; } - if (charm_atk) { - ATK = charm_atk; + + if (new_atk) { + ATK = new_atk; } - if (charm_min_dmg || charm_max_dmg) { - base_damage = round((charm_max_dmg - charm_min_dmg) / 1.9); - min_damage = charm_min_dmg - round(base_damage / 10.0); + + if (new_min_dmg || new_max_dmg) { + base_damage = std::round((new_max_dmg - new_min_dmg) / 1.9); + min_damage = new_min_dmg - std::round(base_damage / 10.0); } + if (RuleB(Spells, CharmDisablesSpecialAbilities)) { - ClearSpecialAbilities(); + if (remove_charm) { + ProcessSpecialAbilities(default_special_abilities); + } else { + ClearSpecialAbilities(); + } } - // the rest of the stats aren't cached, so lets just do these two instead of full CalcBonuses() SetAttackTimer(); CalcAC(); } @@ -3666,7 +3584,7 @@ void NPC::AIYellForHelp(Mob *sender, Mob *attacker) && mob != attacker && mob->GetPrimaryFaction() != 0 && !mob->IsEngaged() - && ((!mob->IsPet()) || (mob->IsPet() && mob->GetOwner() && !mob->GetOwner()->IsClient())) + && ((!mob->IsPet()) || (mob->IsPet() && mob->GetOwner() && !mob->GetOwner()->IsOfClientBot())) ) { /** @@ -3708,7 +3626,7 @@ void NPC::RecalculateSkills() { int r; for (r = 0; r <= EQ::skills::HIGHEST_SKILL; r++) { - skills[r] = skill_caps.GetSkillCap(GetClass(), (EQ::skills::SkillType)r, level).cap; + skills[r] = SkillCaps::Instance()->GetSkillCap(GetClass(), (EQ::skills::SkillType)r, level).cap; } // some overrides -- really we need to be able to set skills for mobs in the DB @@ -3765,7 +3683,7 @@ bool NPC::IsGuard() case Race::HalasCitizen: case Race::NeriakCitizen: case Race::GrobbCitizen: - case OGGOK_CITIZEN: + case Race::OggokCitizen: case Race::KaladimCitizen: return true; default: @@ -3824,13 +3742,12 @@ int NPC::GetRolledItemCount(uint32 item_id) void NPC::SendPositionToClients() { - auto p = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); - auto *s = (PlayerPositionUpdateServer_Struct *) p->pBuffer; + static EQApplicationPacket p(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); + auto *s = (PlayerPositionUpdateServer_Struct *) p.pBuffer; for (auto &c: entity_list.GetClientList()) { MakeSpawnUpdate(s); - c.second->QueuePacket(p, false); + c.second->QueuePacket(&p, false); } - safe_delete(p); } void NPC::HandleRoambox() @@ -3988,7 +3905,7 @@ void NPC::SetTaunting(bool is_taunting) { taunting = is_taunting; if (IsPet() && IsPetOwnerClient()) { - GetOwner()->CastToClient()->SetPetCommandState(PET_BUTTON_TAUNT, is_taunting); + GetOwner()->CastToClient()->SetPetCommandState(PetButton::Taunt, is_taunting); } } @@ -4264,3 +4181,666 @@ bool NPC::FacesTarget() return std::find(v.begin(), v.end(), std::to_string(GetBaseRace())) == v.end(); } +bool NPC::CanPetTakeItem(const EQ::ItemInstance *inst) +{ + if (!inst) { + return false; + } + + if (!IsPetOwnerOfClientBot() && !IsCharmedPet()) { + return false; + } + + const bool can_take_nodrop = (RuleB(Pets, CanTakeNoDrop) || inst->GetItem()->NoDrop != 0) + || inst->GetItem()->NoRent == 0; + + const bool is_charmed_with_attuned = IsCharmed() && inst->IsAttuned(); + + auto o = GetOwner() && GetOwner()->IsClient() ? GetOwner()->CastToClient() : nullptr; + + struct Check { + bool condition; + std::string message; + }; + + const Check checks[] = { + {inst->IsAttuned(), "I cannot equip attuned items, master."}, + {!can_take_nodrop || is_charmed_with_attuned, "I cannot equip no-drop items, master."}, + {inst->GetItem()->IsQuestItem(), "I cannot equip quest items, master."}, + {!inst->GetItem()->IsPetUsable(), "I cannot equip that item, master."} + }; + + // Iterate over checks and return false if any condition is true + for (const auto &c : checks) { + if (c.condition) { + if (o) { + o->Message(Chat::PetResponse, fmt::format("{} says '{}'", GetCleanName(), c.message).c_str()); + } + return false; + } + } + + return true; +} + +bool NPC::IsGuildmasterForClient(Client *c) { + std::map guildmaster_map = { + { Class::Warrior, Class::WarriorGM }, + { Class::Cleric, Class::ClericGM }, + { Class::Paladin, Class::PaladinGM }, + { Class::Ranger, Class::RangerGM }, + { Class::ShadowKnight, Class::ShadowKnightGM }, + { Class::Druid, Class::DruidGM }, + { Class::Monk, Class::MonkGM }, + { Class::Bard, Class::BardGM }, + { Class::Rogue, Class::RogueGM }, + { Class::Shaman, Class::ShamanGM }, + { Class::Necromancer, Class::NecromancerGM }, + { Class::Wizard, Class::WizardGM }, + { Class::Magician, Class::MagicianGM }, + { Class::Enchanter, Class::EnchanterGM }, + { Class::Beastlord, Class::BeastlordGM }, + { Class::Berserker, Class::BerserkerGM }, + }; + + if (guildmaster_map.find(c->GetClass()) != guildmaster_map.end()) { + if (guildmaster_map[c->GetClass()] == GetClass()) { + return true; + } + } + + return false; +} + +bool NPC::CheckHandin( + Client *c, + std::map handin, + std::map required, + std::vector items +) +{ + auto h = Handin{}; + auto r = Handin{}; + + std::string log_handin_prefix = fmt::format("[{}] -> [{}]", c->GetCleanName(), GetCleanName()); + + // if the npc is a multi-quest npc, we want to re-use our previously set hand-in bucket + if (!m_handin_started && IsMultiQuestEnabled()) { + h = m_hand_in; + } + + if (IsMultiQuestEnabled()) { + LogNpcHandin("{} Multi-Quest hand-in enabled", log_handin_prefix); + } + + std::vector&, Handin&>> datasets = {}; + + // if we've already started the hand-in process, we don't want to re-process the hand-in data + // we continue to use the originally set hand-in bucket and decrement from it with each successive hand-in + if (m_handin_started) { + h = m_hand_in; + } else { + datasets.emplace_back(handin, h); + } + datasets.emplace_back(required, r); + + const std::string set_hand_in = "Hand-in"; + const std::string set_required = "Required"; + for (const auto &[data_map, current_handin]: datasets) { + std::string current_dataset = ¤t_handin == &h ? set_hand_in : set_required; + for (const auto &[key, value]: data_map) { + LogNpcHandinDetail("Processing [{}] key [{}] value [{}]", current_dataset, key, value); + + // Handle items + if (Strings::IsNumber(key)) { + if (const auto *exists = database.GetItem(Strings::ToUnsignedInt(key)); + exists && current_dataset == set_required) { + current_handin.items.emplace_back(HandinEntry{.item_id = key, .count = value}); + } + continue; + } + + // Handle money and any other key-value pairs + if (key == "platinum") { current_handin.money.platinum = value; } + else if (key == "gold") { current_handin.money.gold = value; } + else if (key == "silver") { current_handin.money.silver = value; } + else if (key == "copper") { current_handin.money.copper = value; } + } + } + + // pull hand-in items from the item instances + if (!m_handin_started) { + for (const auto &i: items) { + if (!i) { + continue; + } + + h.items.emplace_back( + HandinEntry{ + .item_id = std::to_string(i->GetItem()->ID), + .count = std::max(static_cast(i->IsStackable() ? i->GetCharges() : 1), static_cast(1)), + .item = i->Clone(), + .is_multiquest_item = false + } + ); + } + } + + // compare hand-in to required, the item_id can be in any slot + bool requirement_met = true; + + // money + bool money_met = h.money.platinum == r.money.platinum + && h.money.gold == r.money.gold + && h.money.silver == r.money.silver + && h.money.copper == r.money.copper; + + // if we started the hand-in process, we want to use the hand-in items from the member variable hand-in bucket + auto &handin_items = !m_handin_started ? h.items : m_hand_in.items; + + for (auto &h_item: h.items) { + LogNpcHandinDetail( + "{} Hand-in item [{}] ({}) count [{}] is_multiquest_item [{}]", + log_handin_prefix, + h_item.item->GetItem()->Name, + h_item.item_id, + h_item.count, + h_item.is_multiquest_item + ); + } + + // remove items from the hand-in bucket that were used to fulfill the requirement + std::vector items_to_remove; + + // multi-quest + if (IsMultiQuestEnabled()) { + for (auto &h_item: m_hand_in.items) { + for (const auto &r_item: r.items) { + if (h_item.item_id == r_item.item_id && h_item.count == r_item.count) { + h_item.is_multiquest_item = true; + } + } + } + } + + // check if the hand-in items fulfill the requirement + bool items_met = true; + if (!handin_items.empty() && !r.items.empty()) { + std::vector before_handin_state = handin_items; + for (const auto &r_item : r.items) { + uint32 remaining_requirement = r_item.count; + bool fulfilled = false; + + // Process the hand-in items using a standard for loop + for (size_t i = 0; i < handin_items.size() && remaining_requirement > 0; ++i) { + auto &h_item = handin_items[i]; + + // Check if the item IDs match (normalize if necessary) + bool id_match = (h_item.item_id == r_item.item_id); + + if (id_match) { + uint32 used_count = std::min(remaining_requirement, h_item.count); + // If the item is a multi-quest item, we don't want to consume it for the hand-in bucket + if (!IsMultiQuestEnabled()) { + h_item.count -= used_count; + } + remaining_requirement -= used_count; + + LogNpcHandinDetail( + "{} >>>> Using item [{}] ({}) count [{}] to fulfill [{}], remaining requirement [{}]", + log_handin_prefix, + h_item.item->GetItem()->Name, + h_item.item_id, + used_count, + r_item.item_id, + remaining_requirement + ); + + // If the item is fully consumed, mark it for removal + if (h_item.count == 0) { + items_to_remove.push_back(h_item); + } + } + } + + // If we cannot fulfill the requirement, mark as not met + if (remaining_requirement > 0) { + LogNpcHandinDetail( + "{} >>>> Failed to fulfill requirement for [{}], remaining [{}]", + log_handin_prefix, + r_item.item_id, + remaining_requirement + ); + items_met = false; + break; + } else { + fulfilled = true; + } + } + + // reset the hand-in items to the state prior to processing the hand-in + // if we failed to fulfill the requirement + if (!items_met) { + handin_items = before_handin_state; + items_to_remove.clear(); + } + } + else if (h.items.empty() && r.items.empty()) { // no items required, money only + items_met = true; + } + else { + items_met = false; + } + + requirement_met = money_met && items_met; + + // in-case we trigger CheckHand-in multiple times, only set these once + if (!m_handin_started) { + m_handin_started = true; + m_hand_in = h; + // save original items for logging + m_hand_in.original_items = m_hand_in.items; + m_hand_in.original_money = m_hand_in.money; + } + + // check if npc is guildmaster + if (IsGuildmaster()) { + for (const auto &remove_item : items_to_remove) { + if (!remove_item.item) { + continue; + } + + if (!IsDisciplineTome(remove_item.item->GetItem())) { + continue; + } + + if (IsGuildmasterForClient(c)) { + c->TrainDiscipline(remove_item.item->GetID()); + m_hand_in.items.erase( + std::remove_if( + m_hand_in.items.begin(), + m_hand_in.items.end(), + [&](const HandinEntry &i) { + bool removed = i.item == remove_item.item; + if (removed) { + LogNpcHandin( + "{} Hand-in success, removing discipline tome [{}] from hand-in bucket", + log_handin_prefix, + i.item_id + ); + } + return removed; + } + ), + m_hand_in.items.end() + ); + } else { + Say("You are not a member of my guild. I will not train you!"); + requirement_met = false; + break; + } + } + } + + // print current hand-in bucket + LogNpcHandin( + "{} > Before processing hand-in | requirement_met [{}] item_count [{}] platinum [{}] gold [{}] silver [{}] copper [{}]", + log_handin_prefix, + requirement_met, + h.items.size(), + h.money.platinum, + h.money.gold, + h.money.silver, + h.money.copper + ); + + LogNpcHandin( + "{} >> Handed Items | Item(s) ({}) platinum [{}] gold [{}] silver [{}] copper [{}]", + log_handin_prefix, + h.items.size(), + h.money.platinum, + h.money.gold, + h.money.silver, + h.money.copper + ); + + int item_count = 1; + for (const auto &i: h.items) { + LogNpcHandin( + "{} >>> item{} [{}] ({}) count [{}]", + log_handin_prefix, + item_count, + i.item->GetItem()->Name, + i.item_id, + i.count + ); + item_count++; + } + + LogNpcHandin( + "{} >> Required Items | Item(s) ({}) platinum [{}] gold [{}] silver [{}] copper [{}]", + log_handin_prefix, + r.items.size(), + r.money.platinum, + r.money.gold, + r.money.silver, + r.money.copper + ); + + item_count = 1; + for (const auto &i: r.items) { + auto item = database.GetItem(Strings::ToUnsignedInt(i.item_id)); + + LogNpcHandin( + "{} >>> item{} [{}] ({}) count [{}]", + log_handin_prefix, + item_count, + item ? item->Name : "Unknown", + i.item_id, + i.count + ); + + item_count++; + } + + if (requirement_met) { + std::vector log_entries = {}; + for (const auto &remove_item: items_to_remove) { + m_hand_in.items.erase( + std::remove_if( + m_hand_in.items.begin(), + m_hand_in.items.end(), + [&](const HandinEntry &i) { + bool removed = (remove_item.item == i.item); + if (removed) { + log_entries.emplace_back( + fmt::format( + "{} >>> Hand-in success | Removing from hand-in bucket | item [{}] ({}) count [{}]", + log_handin_prefix, + i.item->GetItem()->Name, + i.item_id, + i.count + ) + ); + } + return removed; + } + ), + m_hand_in.items.end() + ); + } + + // log successful hand-in items + if (!log_entries.empty()) { + for (const auto& log : log_entries) { + LogNpcHandin("{}", log); + } + } + + // decrement successful hand-in money from current hand-in bucket + if (h.money.platinum > 0 || h.money.gold > 0 || h.money.silver > 0 || h.money.copper > 0) { + LogNpcHandin( + "{} Hand-in success, removing money p [{}] g [{}] s [{}] c [{}]", + log_handin_prefix, + h.money.platinum, + h.money.gold, + h.money.silver, + h.money.copper + ); + m_hand_in.money.platinum -= h.money.platinum; + m_hand_in.money.gold -= h.money.gold; + m_hand_in.money.silver -= h.money.silver; + m_hand_in.money.copper -= h.money.copper; + } + + LogNpcHandin( + "{} > End of hand-in | requirement_met [{}] item_count [{}] platinum [{}] gold [{}] silver [{}] copper [{}]", + log_handin_prefix, + requirement_met, + m_hand_in.items.size(), + m_hand_in.money.platinum, + m_hand_in.money.gold, + m_hand_in.money.silver, + m_hand_in.money.copper + ); + for (const auto &i: m_hand_in.items) { + LogNpcHandin( + "{} Hand-in success, item [{}] ({}) count [{}]", + log_handin_prefix, + i.item->GetItem()->Name, + i.item_id, + i.count + ); + } + } + + // when we meet requirements under multi-quest, we want to reset the hand-in bucket + if (requirement_met && IsMultiQuestEnabled()) { + ResetMultiQuest(); + } + + return requirement_met; +} + +NPC::Handin NPC::ReturnHandinItems(Client *c) +{ + // player event + std::vector handin_items; + PlayerEvent::HandinMoney handin_money{}; + std::vector return_items; + PlayerEvent::HandinMoney return_money{}; + for (const auto& i : m_hand_in.original_items) { + if (i.item && i.item->GetItem()) { + handin_items.emplace_back( + PlayerEvent::HandinEntry{ + .item_id = i.item->GetID(), + .item_name = i.item->GetItem()->Name, + .augment_ids = i.item->GetAugmentIDs(), + .augment_names = i.item->GetAugmentNames(), + .charges = std::max(static_cast(i.item->GetCharges()), static_cast(1)) + } + ); + } + } + + auto returned = m_hand_in; + + // check if any money was handed in + if (m_hand_in.original_money.platinum > 0 || + m_hand_in.original_money.gold > 0 || + m_hand_in.original_money.silver > 0 || + m_hand_in.original_money.copper > 0 + ) { + handin_money.copper = m_hand_in.original_money.copper; + handin_money.silver = m_hand_in.original_money.silver; + handin_money.gold = m_hand_in.original_money.gold; + handin_money.platinum = m_hand_in.original_money.platinum; + } + + // if scripts have their own implementation of returning items instead of + // going through return_items, this guards against returning items twice (duplicate items) + bool external_returned_items = c->GetExternalHandinItemsReturned().size() > 0; + bool returned_items_already = false; + for (auto &handin_item: m_hand_in.items) { + for (auto &i: c->GetExternalHandinItemsReturned()) { + auto item = database.GetItem(i); + if (item && std::to_string(item->ID) == handin_item.item_id) { + LogNpcHandin(" -- External quest methods already returned item [{}] ({})", item->Name, item->ID); + returned_items_already = true; + } + } + } + + if (returned_items_already) { + LogNpcHandin("External quest methods returned items, not returning items to player via ReturnHandinItems"); + } + + bool returned_handin = false; + m_hand_in.items.erase( + std::remove_if( + m_hand_in.items.begin(), + m_hand_in.items.end(), + [&](HandinEntry &i) { + if (i.item && i.item->GetItem() && !i.is_multiquest_item && !returned_items_already) { + return_items.emplace_back( + PlayerEvent::HandinEntry{ + .item_id = i.item->GetID(), + .item_name = i.item->GetItem()->Name, + .augment_ids = i.item->GetAugmentIDs(), + .augment_names = i.item->GetAugmentNames(), + .charges = std::max(static_cast(i.item->GetCharges()), static_cast(1)) + } + ); + + // If the item is stackable and the new charges don't match the original count + // set the charges to the original count + if (i.item->IsStackable() && i.item->GetCharges() != i.count) { + i.item->SetCharges(i.count); + } + + c->PushItemOnCursor(*i.item, true); + LogNpcHandin( + "Hand-in failed, returning item [{}] i.is_multiquest_item [{}]", + i.item->GetItem()->Name, + i.is_multiquest_item + ); + + returned_handin = true; + return true; // Mark this item for removal + } + return false; + } + ), + m_hand_in.items.end() + ); + + // check if any money was handed in via external quest methods + auto em = c->GetExternalHandinMoneyReturned(); + + bool money_returned_via_external_quest_methods = + em.copper > 0 || + em.silver > 0 || + em.gold > 0 || + em.platinum > 0; + + // check if any money was handed in + bool money_handed = m_hand_in.money.platinum > 0 || + m_hand_in.money.gold > 0 || + m_hand_in.money.silver > 0 || + m_hand_in.money.copper > 0; + if (money_handed && !money_returned_via_external_quest_methods) { + c->AddMoneyToPP( + m_hand_in.money.copper, + m_hand_in.money.silver, + m_hand_in.money.gold, + m_hand_in.money.platinum, + true + ); + returned_handin = true; + LogNpcHandin( + "Hand-in failed, returning money p [{}] g [{}] s [{}] c [{}]", + m_hand_in.money.platinum, + m_hand_in.money.gold, + m_hand_in.money.silver, + m_hand_in.money.copper + ); + + // player event + return_money.copper = m_hand_in.money.copper; + return_money.silver = m_hand_in.money.silver; + return_money.gold = m_hand_in.money.gold; + return_money.platinum = m_hand_in.money.platinum; + + // if multi-quest and we returned money, reset the hand-in bucket + if (IsMultiQuestEnabled()) { + m_hand_in.money = {}; + m_hand_in.original_money = {}; + } + } + + if (money_returned_via_external_quest_methods) { + LogNpcHandin( + "Money handed in was returned via external quest methods, not returning money to player via ReturnHandinItems | handed-in p [{}] g [{}] s [{}] c [{}] returned-external p [{}] g [{}] s [{}] c [{}] source [{}]", + m_hand_in.money.platinum, + m_hand_in.money.gold, + m_hand_in.money.silver, + m_hand_in.money.copper, + em.platinum, + em.gold, + em.silver, + em.copper, + em.return_source + ); + } + + m_has_processed_handin_return = returned_handin; + + if (returned_handin) { + Say( + fmt::format( + "I have no need for this {}, you can have it back.", + c->GetCleanName() + ).c_str() + ); + } + + const bool handed_in_money = ( + handin_money.platinum > 0 || + handin_money.gold > 0 || + handin_money.silver > 0 || + handin_money.copper > 0 + ); + const bool event_has_data_to_record = !handin_items.empty() || handed_in_money; + + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::NPC_HANDIN) && event_has_data_to_record) { + auto e = PlayerEvent::HandinEvent{ + .npc_id = GetNPCTypeID(), + .npc_name = GetCleanName(), + .handin_items = handin_items, + .handin_money = handin_money, + .return_items = return_items, + .return_money = return_money, + .is_quest_handin = parse->HasQuestSub(GetNPCTypeID(), EVENT_TRADE) + }; + + RecordPlayerEventLogWithClient(c, PlayerEvent::NPC_HANDIN, e); + } + + return returned; +} + +void NPC::ResetHandin() +{ + LogNpcHandin("Resetting hand-in bucket for [{}]", GetCleanName()); + m_has_processed_handin_return = false; + m_handin_started = false; + if (!IsMultiQuestEnabled()) { + for (auto &i: m_hand_in.original_items) { + safe_delete(i.item); + } + + m_hand_in = {}; + } +} + +void NPC::ResetMultiQuest() { + LogNpcHandin("Resetting multi-quest hand-in bucket for [{}]", GetCleanName()); + for (auto &i: m_hand_in.original_items) { + safe_delete(i.item); + } + + m_hand_in = {}; +} + +void NPC::SetNPCTintIndex(uint32 index) +{ + auto outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct)); + auto* s = (SpawnAppearance_Struct*) outapp->pBuffer; + + s->spawn_id = GetID(); + s->type = AppearanceType::NPCTintIndex; + s->parameter = index; + + entity_list.QueueClients(this, outapp); + safe_delete(outapp); +} diff --git a/zone/npc.h b/zone/npc.h index a20d975b1..90569d12b 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -222,7 +222,7 @@ public: void RemoveLootCash(); void QueryLoot(Client *to, bool is_pet_query = false); bool HasItem(uint32 item_id); - uint16 CountItem(uint32 item_id); + uint32 CountItem(uint32 item_id); uint32 GetLootItemIDBySlot(uint16 loot_slot); uint16 GetFirstLootSlotByItemID(uint32 item_id); std::vector GetLootList(); @@ -248,6 +248,7 @@ public: uint16 GetWaypointMax() const { return wp_m; } int32 GetGrid() const { return grid; } + Spawn2* GetSpawn() { return respawn2 ? respawn2 : nullptr; } uint32 GetSpawnGroupId() const { return spawn_group_id; } uint32 GetSpawnPointID() const; @@ -345,7 +346,7 @@ public: int64 GetNPCHPRegen() const { return hp_regen + itembonuses.HPRegen + spellbonuses.HPRegen; } inline const char* GetAmmoIDfile() const { return ammo_idfile; } - void ModifyStatsOnCharm(bool is_charm_removed); + void ModifyStatsOnCharm(bool remove_charm, Mob* charmer); //waypoint crap int GetMaxWp() const { return max_wp; } @@ -551,6 +552,9 @@ public: void ScaleNPC(uint8 npc_level, bool always_scale = false, bool override_special_abilities = false); + uint32 GetNPCTintIndex() { return m_npc_tint_id; } + void SetNPCTintIndex(uint32 index); + void RecalculateSkills(); void ReloadSpells(); @@ -559,6 +563,81 @@ public: bool CanPathTo(float x, float y, float z); void DoNpcToNpcAggroScan(); + + // hand-ins + bool CanPetTakeItem(const EQ::ItemInstance *inst); + + struct HandinEntry { + std::string item_id = "0"; + uint32 count = 0; + 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 original_items = {}; // this is what the player originally handed in, never modified + std::vector items = {}; // items can be removed from this set as successful handins are made + HandinMoney original_money = {}; // this is what the player originally handed in, never modified + HandinMoney money = {}; // money can be removed from this set as successful handins are made + }; + + // NPC Hand-in + bool IsMultiQuestEnabled() { return m_multiquest_enabled; } + void MultiQuestEnable() { m_multiquest_enabled = true; } + bool IsGuildmasterForClient(Client *c); + bool CheckHandin( + Client *c, + std::map handin, + std::map required, + std::vector items + ); + Handin ReturnHandinItems(Client *c); + void ResetHandin(); + void ResetMultiQuest(); + bool HasProcessedHandinReturn() { return m_has_processed_handin_return; } + bool HandinStarted() { return m_handin_started; } + + // zone state save + inline void SetQueuedToCorpse() { m_queued_for_corpse = true; } + inline bool IsQueuedForCorpse() const { return m_queued_for_corpse; } + inline void SetResumedFromZoneSuspend(bool state = true) { m_resumed_from_zone_suspend = state; } + inline bool IsResumedFromZoneSuspend() const { return m_resumed_from_zone_suspend; } + + inline void LoadBuffsFromState(std::vector in_buffs) { + int i = 0; + for (auto &b: in_buffs) { + buffs[i].spellid = b.spellid; + buffs[i].casterlevel = b.casterlevel; + buffs[i].casterid = b.casterid; + strncpy(buffs[i].caster_name, b.caster_name, 64); + buffs[i].ticsremaining = b.ticsremaining; + buffs[i].counters = b.counters; + buffs[i].hit_number = b.hit_number; + buffs[i].melee_rune = b.melee_rune; + buffs[i].magic_rune = b.magic_rune; + buffs[i].dot_rune = b.dot_rune; + buffs[i].caston_x = b.caston_x; + buffs[i].caston_y = b.caston_y; + buffs[i].caston_z = b.caston_z; + buffs[i].ExtraDIChance = b.ExtraDIChance; + buffs[i].RootBreakChance = b.RootBreakChance; + buffs[i].instrument_mod = b.instrument_mod; + buffs[i].virus_spread_time = b.virus_spread_time; + buffs[i].persistant_buff = b.persistant_buff; + buffs[i].client = b.client; + buffs[i].UpdateClient = b.UpdateClient; + i++; + } + CalcBonuses(); + } + protected: void HandleRoambox(); @@ -580,6 +659,15 @@ protected: uint32 m_loot_platinum; LootItems m_loot_items; + // zone state + bool m_resumed_from_zone_suspend = false; + bool m_queued_for_corpse = false; // this is to check for corpse creation on zone state restore + + // this is a timer that protects a NPC from having double assignment of loot + // this is to prevent a player from killing a NPC and then zoning out and back in to get loot again + // if loot was to be assigned via script again, this protects double assignment for a short time + Timer m_resumed_from_zone_suspend_shutoff_timer = {}; + std::list faction_list; int32 npc_faction_id; @@ -700,6 +788,17 @@ protected: bool raid_target; bool ignore_despawn; //NPCs with this set to 1 will ignore the despawn value in spawngroup + // NPC Hand-in + bool m_multiquest_enabled = false; + bool m_handin_started = false; + bool m_has_processed_handin_return = false; + + // this is the working handin data from the player + // items can be decremented from this as each successful + // check is ran in scripts, the remainder is what is returned + Handin m_hand_in = {}; +public: + const Handin GetHandin() { return m_hand_in; } private: uint32 m_loottable_id; diff --git a/zone/npc_scale_manager.cpp b/zone/npc_scale_manager.cpp index f8fef83e4..2a66b613a 100644 --- a/zone/npc_scale_manager.cpp +++ b/zone/npc_scale_manager.cpp @@ -187,7 +187,7 @@ void NpcScaleManager::ScaleNPC( npc->ModifyNPCStat("special_abilities", scale_data.special_abilities); } - if (LogSys.log_settings[Logs::NPCScaling].is_category_enabled == 1) { + if (EQEmuLogSys::Instance()->log_settings[Logs::NPCScaling].is_category_enabled == 1) { std::string scale_log; for (const auto &stat : scaling_stats) { diff --git a/zone/object.cpp b/zone/object.cpp index f740bf93c..6da171b23 100644 --- a/zone/object.cpp +++ b/zone/object.cpp @@ -31,15 +31,17 @@ #include "../common/events/player_event_logs.h" #include "../common/repositories/ground_spawns_repository.h" #include "../common/repositories/object_repository.h" +#include "queryserv.h" const char DEFAULT_OBJECT_NAME[] = "IT63_ACTORDEF"; const char DEFAULT_OBJECT_NAME_SUFFIX[] = "_ACTORDEF"; -extern Zone* zone; -extern EntityList entity_list; +extern Zone *zone; +extern EntityList entity_list; extern WorldServer worldserver; +extern QueryServ *QServ; // Loading object from database Object::Object( @@ -73,6 +75,8 @@ decay_timer(300000) decay_timer.Disable(); } + memset(m_display_name, 0, sizeof(m_display_name)); + respawn_timer.Disable(); // Set drop_id to zero - it will be set when added to zone with SetID() @@ -120,6 +124,8 @@ decay_timer(300000) // Set as much struct data as we can memset(&m_data, 0, sizeof(Object_Struct)); + memset(m_display_name, 0, sizeof(m_display_name)); + m_data.heading = heading; m_data.z = z; m_data.zone_id = zone->GetZoneID(); @@ -162,6 +168,8 @@ decay_timer(300000) // Set as much struct data as we can memset(&m_data, 0, sizeof(Object_Struct)); + memset(m_display_name, 0, sizeof(m_display_name)); + m_data.heading = client->GetHeading(); m_data.x = client->GetX(); m_data.y = client->GetY(); @@ -234,6 +242,8 @@ decay_timer(decay_time) // Set as much struct data as we can memset(&m_data, 0, sizeof(Object_Struct)); + memset(m_display_name, 0, sizeof(m_display_name)); + m_data.heading = heading; m_data.x = x; m_data.y = y; @@ -310,6 +320,8 @@ decay_timer(decay_time) m_data.z = z; m_data.zone_id = zone->GetZoneID(); + memset(m_display_name, 0, sizeof(m_display_name)); + if (!IsFixZEnabled()) { FixZ(); } @@ -351,6 +363,8 @@ void Object::SetID(uint16 set_id) // Reset state of object back to zero void Object::ResetState() { + Close(); + safe_delete(m_inst); m_id = 0; @@ -438,6 +452,12 @@ void Object::Close() { } } + auto outapp = new EQApplicationPacket(OP_ClearObject, sizeof(ClearObject_Struct)); + ClearObject_Struct *cos = (ClearObject_Struct *)outapp->pBuffer; + cos->Clear = 1; + user->QueuePacket(outapp); + safe_delete(outapp); + user->SetTradeskillObject(nullptr); } @@ -605,7 +625,7 @@ bool Object::HandleClick(Client* sender, const ClickObject_Struct* click_object) } } - if (player_event_logs.IsEventEnabled(PlayerEvent::GROUNDSPAWN_PICKUP)) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::GROUNDSPAWN_PICKUP)) { auto e = PlayerEvent::GroundSpawnPickupEvent{ .item_id = item->ID, .item_name = item->Name, @@ -637,32 +657,40 @@ bool Object::HandleClick(Client* sender, const ClickObject_Struct* click_object) } } + if (parse->ZoneHasQuestSub(EVENT_PLAYER_PICKUP)) { + std::vector args = { m_inst, sender }; + + if (parse->EventZone(EVENT_PLAYER_PICKUP, zone, std::to_string(item->ID), GetID(), &args)) { + auto outapp = new EQApplicationPacket(OP_ClickObject, sizeof(ClickObject_Struct)); + + memcpy(outapp->pBuffer, click_object, sizeof(ClickObject_Struct)); + + auto co = (ClickObject_Struct*) outapp->pBuffer; + + co->drop_id = 0; + + entity_list.QueueClients(nullptr, outapp, false); + + safe_delete(outapp); + + sender->SetTradeskillObject(nullptr); + + user = nullptr; + + return true; + } + } + // Transfer item to client sender->PutItemInInventory(EQ::invslot::slotCursor, *m_inst, false); sender->SendItemPacket(EQ::invslot::slotCursor, m_inst, ItemPacketTrade); - // Could be an undiscovered ground_spawn - if (m_ground_spawn && RuleB(Character, EnableDiscoveredItems) && !sender->IsDiscovered(item->ID)) { - if (!sender->GetGM()) { - sender->DiscoverItem(item->ID); - } else { - const std::string& item_link = database.CreateItemLink(item->ID); - sender->Message( - Chat::White, - fmt::format( - "Your GM flag prevents {} from being added to discovered items.", - item_link - ).c_str() - ); - } - } + sender->CheckItemDiscoverability(m_inst->GetID()); if (cursor_delete) { // delete the item if it's a duplicate lore. We have to do this because the client expects the item packet sender->DeleteItemInInventory(EQ::invslot::slotCursor, 1, true); } - sender->DropItemQS(m_inst, true); - if (!m_ground_spawn) { safe_delete(m_inst); } diff --git a/zone/parcels.cpp b/zone/parcels.cpp index e3661906f..f1dc874c1 100644 --- a/zone/parcels.cpp +++ b/zone/parcels.cpp @@ -27,6 +27,7 @@ #include "../common/ruletypes.h" extern WorldServer worldserver; +extern QueryServ *QServ; void Client::SendBulkParcels() { @@ -57,9 +58,10 @@ void Client::SendBulkParcels() p.second.aug_slot_6 )); if (inst) { - inst->SetCharges(p.second.quantity > 0 ? p.second.quantity : 1); + inst->SetCharges(p.second.quantity); inst->SetMerchantCount(1); inst->SetMerchantSlot(p.second.slot_id); + inst->SetEvolveCurrentAmount(p.second.evolve_amount); if (inst->IsStackable()) { inst->SetCharges(p.second.quantity); } @@ -160,9 +162,10 @@ void Client::SendParcel(Parcel_Struct &parcel_in) p.aug_slot_6 )); if (inst) { - inst->SetCharges(p.quantity > 0 ? p.quantity : 1); + inst->SetCharges(p.quantity); inst->SetMerchantCount(1); inst->SetMerchantSlot(p.slot_id); + inst->SetEvolveCurrentAmount(p.evolve_amount); if (inst->IsStackable()) { inst->SetCharges(p.quantity); } @@ -271,6 +274,10 @@ void Client::SendParcelStatus() void Client::DoParcelSend(const Parcel_Struct *parcel_in) { + if (IsCasting()) { + StopCasting(); + } + auto send_to_client = CharacterParcelsRepository::GetParcelCountAndCharacterName(database, parcel_in->send_to); auto merchant = entity_list.GetMob(parcel_in->npc_id); if (!merchant) { @@ -376,23 +383,23 @@ void Client::DoParcelSend(const Parcel_Struct *parcel_in) return; } - uint32 quantity{}; + uint32 quantity = 1; if (inst->IsStackable()) { quantity = parcel_in->quantity; - } - else { - quantity = inst->GetCharges() > 0 ? inst->GetCharges() : parcel_in->quantity; + } else if (inst->GetItem()->MaxCharges > 0) { + quantity = inst->GetCharges(); } CharacterParcelsRepository::CharacterParcels parcel_out{}; - parcel_out.from_name = GetName(); - parcel_out.note = parcel_in->note; - parcel_out.sent_date = time(nullptr); - parcel_out.quantity = quantity; - parcel_out.item_id = inst->GetID(); - parcel_out.char_id = send_to_client.at(0).char_id; - parcel_out.slot_id = next_slot; - parcel_out.id = 0; + parcel_out.from_name = GetName(); + parcel_out.note = parcel_in->note; + parcel_out.sent_date = time(nullptr); + parcel_out.quantity = quantity; + parcel_out.item_id = inst->GetID(); + parcel_out.char_id = send_to_client.at(0).char_id; + parcel_out.slot_id = next_slot; + parcel_out.evolve_amount = inst->GetEvolveCurrentAmount(); + parcel_out.id = 0; if (inst->IsAugmented()) { auto augs = inst->GetAugmentIDs(); @@ -404,6 +411,13 @@ void Client::DoParcelSend(const Parcel_Struct *parcel_in) parcel_out.aug_slot_6 = augs.at(5); } + if (!inst->IsDroppable(true)) { + Message(Chat::Yellow, "Unable to send a parcel that is NO-DROP or contains a NO-DROP item."); + SendParcelAck(); + DoParcelCancel(); + return; + } + auto result = CharacterParcelsRepository::InsertOne(database, parcel_out); if (!result.id) { LogError( @@ -433,13 +447,15 @@ void Client::DoParcelSend(const Parcel_Struct *parcel_in) cpc.aug_slot_5 = augs.at(4); cpc.aug_slot_6 = augs.at(5); } - cpc.quantity = kv.second->GetCharges() > 0 ? kv.second->GetCharges() : 1; + + cpc.quantity = kv.second->GetCharges() >= 0 ? kv.second->GetCharges() : 1; + cpc.evolve_amount = kv.second->GetEvolveCurrentAmount(); all_entries.push_back(cpc); } CharacterParcelsContainersRepository::InsertMany(database, all_entries); } - RemoveItemBySerialNumber(inst->GetSerialNumber(), parcel_out.quantity); + RemoveItemBySerialNumber(inst->GetSerialNumber(), parcel_out.quantity == 0 ? 1 : parcel_out.quantity); std::unique_ptr outapp(new EQApplicationPacket(OP_ShopSendParcel)); QueuePacket(outapp.get()); @@ -456,17 +472,17 @@ void Client::DoParcelSend(const Parcel_Struct *parcel_in) send_to_client.at(0).character_name.c_str() ); - if (player_event_logs.IsEventEnabled(PlayerEvent::PARCEL_SEND)) { + if (PlayerEventLogs::Instance()->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; e.item_id = parcel_out.item_id; - e.aug_slot_1 = parcel_out.aug_slot_1; - e.aug_slot_2 = parcel_out.aug_slot_2; - e.aug_slot_3 = parcel_out.aug_slot_3; - e.aug_slot_4 = parcel_out.aug_slot_4; - e.aug_slot_5 = parcel_out.aug_slot_5; - e.aug_slot_6 = parcel_out.aug_slot_6; + e.augment_1_id = parcel_out.aug_slot_1; + e.augment_2_id = parcel_out.aug_slot_2; + e.augment_3_id = parcel_out.aug_slot_3; + e.augment_4_id = parcel_out.aug_slot_4; + e.augment_5_id = parcel_out.aug_slot_5; + e.augment_6_id = parcel_out.aug_slot_6; e.quantity = parcel_out.quantity; e.sent_date = parcel_out.sent_date; @@ -477,12 +493,12 @@ void Client::DoParcelSend(const Parcel_Struct *parcel_in) e.from_player_name = parcel_out.from_name; e.to_player_name = send_to_client.at(0).character_name; e.item_id = i.item_id; - e.aug_slot_1 = i.aug_slot_1; - e.aug_slot_2 = i.aug_slot_2; - e.aug_slot_3 = i.aug_slot_3; - e.aug_slot_4 = i.aug_slot_4; - e.aug_slot_5 = i.aug_slot_5; - e.aug_slot_6 = i.aug_slot_6; + e.augment_1_id = i.aug_slot_1; + e.augment_2_id = i.aug_slot_2; + e.augment_3_id = i.aug_slot_3; + e.augment_4_id = i.aug_slot_4; + e.augment_5_id = i.aug_slot_5; + e.augment_6_id = i.aug_slot_6; e.quantity = i.quantity; e.sent_date = parcel_out.sent_date; RecordPlayerEventLog(PlayerEvent::PARCEL_SEND, e); @@ -560,7 +576,7 @@ void Client::DoParcelSend(const Parcel_Struct *parcel_in) send_to_client.at(0).character_name.c_str() ); - if (player_event_logs.IsEventEnabled(PlayerEvent::PARCEL_SEND)) { + if (PlayerEventLogs::Instance()->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; @@ -631,13 +647,19 @@ void Client::DoParcelRetrieve(const ParcelRetrieve_Struct &parcel_in) return; } - auto p = m_parcels.find(parcel_in.parcel_slot_id); + auto p = std::find_if( + m_parcels.begin(), + m_parcels.end(), + [&](const std::pair &x) { + return x.first == parcel_in.parcel_slot_id && x.second.item_id == parcel_in.parcel_item_id; + } + ); if (p != m_parcels.end()) { uint32 item_id = parcel_in.parcel_item_id; uint32 item_quantity = p->second.quantity; - if (!item_id || !item_quantity) { + if (!item_id) { LogError( - "Attempt to retrieve parcel with erroneous item id or quantity for client character id {}.", + "Attempt to retrieve parcel with erroneous item id for client character id {}.", CharacterID() ); SendParcelRetrieveAck(); @@ -655,25 +677,74 @@ void Client::DoParcelRetrieve(const ParcelRetrieve_Struct &parcel_in) p->second.aug_slot_6 ) ); + if (!inst) { SendParcelRetrieveAck(); return; } + inst->SetEvolveCurrentAmount(p->second.evolve_amount); + + if (inst->IsStackable()) { + inst->SetCharges(item_quantity > 0 ? item_quantity : 1); + } + switch (parcel_in.parcel_item_id) { case PARCEL_MONEY_ITEM_ID: { AddMoneyToPP(p->second.quantity, true); MessageString( - Chat::Yellow, - PARCEL_DELIVERED, - merchant->GetCleanName(), - "Money", - p->second.from_name.c_str() + Chat::Yellow, PARCEL_DELIVERED, merchant->GetCleanName(), "Money", p->second.from_name.c_str() ); break; } default: { - auto free_id = GetInv().FindFreeSlot(false, false); + std::vector results{}; + if (inst->IsClassBag() && inst->GetItem()->BagSlots > 0) { + auto contents = inst->GetContents(); + results = CharacterParcelsContainersRepository::GetWhere( + database, fmt::format("`parcels_id` = {}", p->second.id) + ); + for (auto i: results) { + auto item = database.CreateItem( + i.item_id, + i.quantity, + i.aug_slot_1, + i.aug_slot_2, + i.aug_slot_3, + i.aug_slot_4, + i.aug_slot_5, + i.aug_slot_6 + ); + + if (!item) { + SendParcelRetrieveAck(); + return; + } + + item->SetEvolveCurrentAmount(i.evolve_amount); + + if (CheckLoreConflict(item->GetItem())) { + if (RuleB(Parcel, DeleteOnDuplicate)) { + MessageString(Chat::Yellow, PARCEL_DUPLICATE_DELETE, inst->GetItem()->Name); + continue; + } + + MessageString(Chat::Yellow, DUP_LORE); + SendParcelRetrieveAck(); + return; + } + + contents->emplace(i.slot_id, item); + } + } + + auto const free_id = GetInv().FindFirstFreeSlotThatFitsItemWithStacking(inst.get()); + if (free_id == INVALID_INDEX) { + SendParcelRetrieveAck(); + MessageString(Chat::White, PARCEL_INV_FULL, merchant->GetCleanName()); + return; + } + if (CheckLoreConflict(inst->GetItem())) { if (RuleB(Parcel, DeleteOnDuplicate)) { MessageString(Chat::Yellow, PARCEL_DUPLICATE_DELETE, inst->GetItem()->Name); @@ -684,132 +755,47 @@ void Client::DoParcelRetrieve(const ParcelRetrieve_Struct &parcel_in) return; } } - else if (inst->IsStackable()) { - inst->SetCharges(item_quantity); - if (TryStacking(inst.get(), ItemPacketTrade, true, false)) { - MessageString( - Chat::Yellow, - PARCEL_DELIVERED_2, - merchant->GetCleanName(), - std::to_string(item_quantity).c_str(), - inst->GetItem()->Name, - p->second.from_name.c_str() - ); - } - else if (free_id != INVALID_INDEX) { - inst->SetCharges(item_quantity); - if (PutItemInInventory(free_id, *inst, true)) { - MessageString( - Chat::Yellow, - PARCEL_DELIVERED_2, - merchant->GetCleanName(), - std::to_string(item_quantity).c_str(), - inst->GetItem()->Name, - p->second.from_name.c_str() - ); - } - } - else { - MessageString(Chat::Yellow, PARCEL_INV_FULL, merchant->GetCleanName()); - SendParcelRetrieveAck(); - return; - } + + if (AutoPutLootInInventory(*inst.get(), false, true)) { + MessageString( + Chat::Yellow, + PARCEL_DELIVERED_2, + merchant->GetCleanName(), + std::to_string(item_quantity).c_str(), + inst->GetItem()->Name, + p->second.from_name.c_str() + ); } - else if (free_id != INVALID_INDEX) { - std::vector results{}; - if (inst->IsClassBag() && inst->GetItem()->BagSlots > 0) { - results = CharacterParcelsContainersRepository::GetWhere(database, fmt::format("`parcels_id` = {}", p->second.id)); - for (auto const &i : results) { - std::unique_ptr item( - database.CreateItem( - i.item_id, - i.quantity, - i.aug_slot_1, - i.aug_slot_2, - i.aug_slot_3, - i.aug_slot_4, - i.aug_slot_5, - i.aug_slot_6 - ) - ); - if (CheckLoreConflict(item->GetItem())) { - Message( - Chat::Yellow, - fmt::format("Lore Item Found in Inventory: {}", item->GetItem()->Name).c_str()); - MessageString(Chat::Yellow, DUP_LORE); - Message(Chat::Red, "Unable to retrieve parcel."); - SendParcelRetrieveAck(); - return; - } - } - } - inst->SetCharges(item_quantity > 0 ? item_quantity : 1); - if (PutItemInInventory(free_id, *inst.get(), true)) { - if (inst->IsClassBag() && inst->GetItem()->BagSlots > 0) { - for (auto const &i: results) { - std::unique_ptr item( - database.CreateItem( - i.item_id, - i.quantity, - i.aug_slot_1, - i.aug_slot_2, - i.aug_slot_3, - i.aug_slot_4, - i.aug_slot_5, - i.aug_slot_6 - ) - ); - auto bag_slot = EQ::InventoryProfile::CalcSlotId(free_id, i.slot_id); - PutItemInInventory(bag_slot, *item.get(), true); - } - } - MessageString( - Chat::Yellow, - PARCEL_DELIVERED, - merchant->GetCleanName(), - inst->GetItem()->Name, - p->second.from_name.c_str() - ); - if (player_event_logs.IsEventEnabled(PlayerEvent::PARCEL_RETRIEVE)) { - PlayerEvent::ParcelRetrieve e{}; - e.from_player_name = p->second.from_name; - e.item_id = p->second.item_id; - e.aug_slot_1 = p->second.aug_slot_1; - e.aug_slot_2 = p->second.aug_slot_2; - e.aug_slot_3 = p->second.aug_slot_3; - e.aug_slot_4 = p->second.aug_slot_4; - e.aug_slot_5 = p->second.aug_slot_5; - e.aug_slot_6 = p->second.aug_slot_6; - e.quantity = p->second.quantity; - e.sent_date = p->second.sent_date; - RecordPlayerEventLog(PlayerEvent::PARCEL_RETRIEVE, e); + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::PARCEL_RETRIEVE)) { + PlayerEvent::ParcelRetrieve e{}; + e.from_player_name = p->second.from_name; + e.item_id = p->second.item_id; + e.augment_1_id = p->second.aug_slot_1; + e.augment_2_id = p->second.aug_slot_2; + e.augment_3_id = p->second.aug_slot_3; + e.augment_4_id = p->second.aug_slot_4; + e.augment_5_id = p->second.aug_slot_5; + e.augment_6_id = p->second.aug_slot_6; + e.quantity = p->second.quantity; + e.sent_date = p->second.sent_date; + RecordPlayerEventLog(PlayerEvent::PARCEL_RETRIEVE, e); - for (auto const &i:results) { + for (auto const &i:results) { e.from_player_name = p->second.from_name; e.item_id = i.item_id; - e.aug_slot_1 = i.aug_slot_1; - e.aug_slot_2 = i.aug_slot_2; - e.aug_slot_3 = i.aug_slot_3; - e.aug_slot_4 = i.aug_slot_4; - e.aug_slot_5 = i.aug_slot_5; - e.aug_slot_6 = i.aug_slot_6; + e.augment_1_id = i.aug_slot_1; + e.augment_2_id = i.aug_slot_2; + e.augment_3_id = i.aug_slot_3; + e.augment_4_id = i.aug_slot_4; + e.augment_5_id = i.aug_slot_5; + e.augment_6_id = i.aug_slot_6; e.quantity = i.quantity; e.sent_date = p->second.sent_date; RecordPlayerEventLog(PlayerEvent::PARCEL_RETRIEVE, e); - } - } + + } - else { - MessageString(Chat::Yellow, PARCEL_INV_FULL, merchant->GetCleanName()); - SendParcelRetrieveAck(); - return; - } - } - else { - MessageString(Chat::Yellow, PARCEL_INV_FULL, merchant->GetCleanName()); - SendParcelRetrieveAck(); - return; } } } @@ -818,6 +804,7 @@ void Client::DoParcelRetrieve(const ParcelRetrieve_Struct &parcel_in) SendParcelDelete(parcel_in); m_parcels.erase(p); } + SendParcelRetrieveAck(); SendParcelIconStatus(); } @@ -830,7 +817,6 @@ bool Client::DeleteParcel(uint32 parcel_id) return false; } - auto it = std::find_if(m_parcels.cbegin(), m_parcels.cend(), [&](const auto &x) { return x.second.id == parcel_id; }); SetParcelCount(GetParcelCount() - 1); return true; @@ -917,6 +903,22 @@ void Client::AddParcel(CharacterParcelsRepository::CharacterParcels &parcel) "Unable to send parcel at this time. Please try again later." ); SendParcelAck(); - return; } } + +int32 Client::FindNextFreeParcelSlotUsingMemory() +{ + auto const results = GetParcels(); + + if (results.empty()) { + return PARCEL_BEGIN_SLOT; + } + + for (uint32 i = PARCEL_BEGIN_SLOT; i <= RuleI(Parcel, ParcelMaxItems); i++) { + if (!results.contains(i)) { + return i; + } + } + + return INVALID_INDEX; +} diff --git a/zone/pathfinder_interface.cpp b/zone/pathfinder_interface.cpp index 74209703c..e6aef0cd9 100644 --- a/zone/pathfinder_interface.cpp +++ b/zone/pathfinder_interface.cpp @@ -7,7 +7,7 @@ IPathfinder *IPathfinder::Load(const std::string &zone) { struct stat statbuffer; - std::string navmesh_path = fmt::format("{}/maps/nav/{}.nav", path.GetServerPath(), zone); + std::string navmesh_path = fmt::format("{}/maps/nav/{}.nav", PathManager::Instance()->GetServerPath(), zone); if (stat(navmesh_path.c_str(), &statbuffer) == 0) { return new PathfinderNavmesh(navmesh_path); } diff --git a/zone/pch/pch.h b/zone/pch/pch.h new file mode 100644 index 000000000..a34b69021 --- /dev/null +++ b/zone/pch/pch.h @@ -0,0 +1,11 @@ +#pragma once + +#include "../merc.h" +#include "../mob.h" +#include "../npc.h" +#include "../corpse.h" +#include "../doors.h" +#include "../bot.h" +#include "../entity.h" +#include "../client.h" +#include "../zone.h" \ No newline at end of file diff --git a/zone/perl_bot.cpp b/zone/perl_bot.cpp index 86f104565..f667eaa44 100644 --- a/zone/perl_bot.cpp +++ b/zone/perl_bot.cpp @@ -145,7 +145,7 @@ EQ::ItemInstance* Perl_Bot_GetAugmentAt(Bot* self, int16 slot_id, uint8 augment_ return nullptr; } -int Perl_Bot_CountAugmentEquippedByID(Bot* self, uint32 item_id) +uint32 Perl_Bot_CountAugmentEquippedByID(Bot* self, uint32 item_id) { return self->GetInv().CountAugmentEquippedByID(item_id); } @@ -155,7 +155,7 @@ bool Perl_Bot_HasAugmentEquippedByID(Bot* self, uint32 item_id) return self->GetInv().HasAugmentEquippedByID(item_id); } -int Perl_Bot_CountItemEquippedByID(Bot* self, uint32 item_id) +uint32 Perl_Bot_CountItemEquippedByID(Bot* self, uint32 item_id) { return self->GetInv().CountItemEquippedByID(item_id); } @@ -425,11 +425,6 @@ void Perl_Bot_SetExpansionBitmask(Bot* self, int expansion_bitmask) self->SetExpansionBitmask(expansion_bitmask); } -void Perl_Bot_SetExpansionBitmask(Bot* self, int expansion_bitmask, bool save) -{ - self->SetExpansionBitmask(expansion_bitmask, save); -} - void Perl_Bot_SetSpellDuration(Bot* self, int spell_id) { self->SetSpellDuration(spell_id); @@ -497,12 +492,12 @@ void Perl_Bot_SetSpellDurationRaid(Bot* self, int spell_id, int duration, int le bool Perl_Bot_ReloadBotDataBuckets(Bot* self) { - return DataBucket::GetDataBuckets(self); + return self->LoadDataBucketsCache(); } bool Perl_Bot_ReloadBotOwnerDataBuckets(Bot* self) { - return self->HasOwner() && DataBucket::GetDataBuckets(self->GetBotOwner()); + return self->HasOwner() && self->LoadDataBucketsCache(); } bool Perl_Bot_ReloadBotSpells(Bot* self) @@ -620,6 +615,11 @@ void Perl_Bot_DeleteBot(Bot* self) // @categories Script Utility self->DeleteBot(); } +void Perl_Bot_RaidGroupSay(Bot* self, const char* message) // @categories Script Utility +{ + self->RaidGroupSay(message); +} + void perl_register_bot() { perl::interpreter state(PERL_GET_THX); @@ -649,8 +649,9 @@ void perl_register_bot() package.add("ApplySpellRaid", (void(*)(Bot*, int, int, int))&Perl_Bot_ApplySpellRaid); package.add("ApplySpellRaid", (void(*)(Bot*, int, int, int, bool))&Perl_Bot_ApplySpellRaid); package.add("ApplySpellRaid", (void(*)(Bot*, int, int, int, bool, bool))&Perl_Bot_ApplySpellRaid); + package.add("RaidGroupSay", &Perl_Bot_RaidGroupSay); package.add("Camp", (void(*)(Bot*))&Perl_Bot_Camp); - package.add("Camp", (void(*)(Bot*, bool))&Perl_Bot_Camp); + package.add("Camp", (void(*)(Bot*, bool))&Perl_Bot_Camp); package.add("ClearDisciplineReuseTimer", (void(*)(Bot*))&Perl_Bot_ClearDisciplineReuseTimer); package.add("ClearDisciplineReuseTimer", (void(*)(Bot*, uint16))&Perl_Bot_ClearDisciplineReuseTimer); package.add("ClearItemReuseTimer", (void(*)(Bot*))&Perl_Bot_ClearItemReuseTimer); @@ -716,7 +717,6 @@ void perl_register_bot() package.add("SendPayload", (void(*)(Bot*, int, std::string))&Perl_Bot_SendPayload); package.add("SendSpellAnim", &Perl_Bot_SendSpellAnim); package.add("SetExpansionBitmask", (void(*)(Bot*, int))&Perl_Bot_SetExpansionBitmask); - package.add("SetExpansionBitmask", (void(*)(Bot*, int, bool))&Perl_Bot_SetExpansionBitmask); package.add("SetDisciplineReuseTimer", (void(*)(Bot*, uint16))&Perl_Bot_SetDisciplineReuseTimer); package.add("SetDisciplineReuseTimer", (void(*)(Bot*, uint16, uint32))&Perl_Bot_SetDisciplineReuseTimer); package.add("SetItemReuseTimer", (void(*)(Bot*, uint32))&Perl_Bot_SetItemReuseTimer); diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index 8bb39afda..49a2d9d02 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -5,7 +5,7 @@ #include "../common/global_define.h" #include "embperl.h" #include "client.h" -#include "expedition.h" +#include "dynamic_zone.h" #include "titles.h" #include "dialogue_window.h" @@ -1142,19 +1142,9 @@ void Perl_Client_SetStartZone(Client* self, uint32 zone_id, float x, float y, fl self->SetStartZone(zone_id, x, y, z, heading); } -void Perl_Client_KeyRingAdd(Client* self, uint32 item_id) // @categories Account and Character, Inventory and Items -{ - self->KeyRingAdd(item_id);; -} - -bool Perl_Client_KeyRingCheck(Client* self, uint32 item_id) // @categories Account and Character, Inventory and Items -{ - return self->KeyRingCheck(item_id); -} - void Perl_Client_AddPVPPoints(Client* self, uint32 points) // @categories Currency and Points { - self->AddPVPPoints(points);; + self->AddPVPPoints(points); } void Perl_Client_AddCrystals(Client* self, uint32 radiant_count, uint32 ebon_count) // @categories Currency and Points @@ -1756,6 +1746,11 @@ int Perl_Client_GetClientMaxLevel(Client* self) return self->GetClientMaxLevel(); } +void Perl_Client_ShowZoneShardMenu(Client* self) // @categories Script Utility +{ + self->ShowZoneShardMenu(); +} + DynamicZoneLocation GetDynamicZoneLocationFromHash(perl::hash table) { // dynamic zone helper method, defaults invalid/missing keys to 0 @@ -1770,7 +1765,7 @@ DynamicZoneLocation GetDynamicZoneLocationFromHash(perl::hash table) return { zone_id, x, y, z, h }; } -Expedition* Perl_Client_CreateExpedition(Client* self, perl::reference table_ref) +DynamicZone* Perl_Client_CreateExpedition(Client* self, perl::reference table_ref) { perl::hash table = table_ref; perl::hash expedition = table["expedition"]; @@ -1817,17 +1812,17 @@ Expedition* Perl_Client_CreateExpedition(Client* self, perl::reference table_ref return self->CreateExpedition(dz); } -Expedition* Perl_Client_CreateExpedition(Client* self, std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, uint32 min_players, uint32 max_players) +DynamicZone* Perl_Client_CreateExpedition(Client* self, std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, uint32 min_players, uint32 max_players) { - return self->CreateExpedition(zone_name, version, duration, expedition_name, min_players, max_players); + return self->CreateExpedition(ZoneID(zone_name), version, duration, expedition_name, min_players, max_players); } -Expedition* Perl_Client_CreateExpedition(Client* self, std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, uint32 min_players, uint32 max_players, bool disable_messages) +DynamicZone* Perl_Client_CreateExpedition(Client* self, std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, uint32 min_players, uint32 max_players, bool disable_messages) { - return self->CreateExpedition(zone_name, version, duration, expedition_name, min_players, max_players, disable_messages); + return self->CreateExpedition(ZoneID(zone_name), version, duration, expedition_name, min_players, max_players, disable_messages); } -Expedition* Perl_Client_CreateExpeditionFromTemplate(Client* self, uint32_t dz_template_id) +DynamicZone* Perl_Client_CreateExpeditionFromTemplate(Client* self, uint32_t dz_template_id) { return self->CreateExpeditionFromTemplate(dz_template_id); } @@ -1870,7 +1865,7 @@ void Perl_Client_CreateTaskDynamicZone(Client* self, int task_id, perl::referenc self->CreateTaskDynamicZone(task_id, dz); } -Expedition* Perl_Client_GetExpedition(Client* self) +DynamicZone* Perl_Client_GetExpedition(Client* self) { return self->GetExpedition(); } @@ -1879,15 +1874,15 @@ perl::reference Perl_Client_GetExpeditionLockouts(Client* self) { perl::hash lockout_hash; - auto lockouts = self->GetExpeditionLockouts(); + const auto& lockouts = self->GetDzLockouts(); for (const auto& lockout : lockouts) { - if (!lockout_hash.exists(lockout.GetExpeditionName())) + if (!lockout_hash.exists(lockout.DzName())) { - lockout_hash[lockout.GetExpeditionName()] = perl::reference(perl::hash()); + lockout_hash[lockout.DzName()] = perl::reference(perl::hash()); } - perl::hash events = lockout_hash[lockout.GetExpeditionName()]; // nested - events[lockout.GetEventName()] = lockout.GetSecondsRemaining(); + perl::hash events = lockout_hash[lockout.DzName()]; // nested + events[lockout.Event()] = lockout.GetSecondsRemaining(); } return perl::reference(lockout_hash); @@ -1897,10 +1892,10 @@ perl::reference Perl_Client_GetExpeditionLockouts(Client* self, std::string expe { perl::hash event_hash; - auto lockouts = self->GetExpeditionLockouts(expedition_name); + auto lockouts = self->GetDzLockouts(expedition_name); for (const auto& lockout : lockouts) { - event_hash[lockout.GetEventName()] = lockout.GetSecondsRemaining(); + event_hash[lockout.Event()] = lockout.GetSecondsRemaining(); } return perl::reference(event_hash); @@ -1908,48 +1903,50 @@ perl::reference Perl_Client_GetExpeditionLockouts(Client* self, std::string expe std::string Perl_Client_GetLockoutExpeditionUUID(Client* self, std::string expedition_name, std::string event_name) { - auto lockout = self->GetExpeditionLockout(expedition_name, event_name); - return lockout ? lockout->GetExpeditionUUID() : std::string{}; + auto lockout = self->GetDzLockout(expedition_name, event_name); + return lockout ? lockout->UUID() : std::string{}; } void Perl_Client_AddExpeditionLockout(Client* self, std::string expedition_name, std::string event_name, uint32 seconds) { - self->AddNewExpeditionLockout(expedition_name, event_name, seconds); + self->AddDzLockout(expedition_name, event_name, seconds); } void Perl_Client_AddExpeditionLockout(Client* self, std::string expedition_name, std::string event_name, uint32 seconds, std::string uuid) { - self->AddNewExpeditionLockout(expedition_name, event_name, seconds, uuid); + self->AddDzLockout(expedition_name, event_name, seconds, uuid); } void Perl_Client_AddExpeditionLockoutDuration(Client* self, std::string expedition_name, std::string event_name, int seconds) { - self->AddExpeditionLockoutDuration(expedition_name, event_name, seconds, {}, true); + auto lockout = DzLockout::Create(expedition_name, event_name, seconds); + self->AddDzLockoutDuration(lockout, seconds, {}, true); } void Perl_Client_AddExpeditionLockoutDuration(Client* self, std::string expedition_name, std::string event_name, int seconds, std::string uuid) { - self->AddExpeditionLockoutDuration(expedition_name, event_name, seconds, uuid, true); + auto lockout = DzLockout::Create(expedition_name, event_name, seconds, uuid); + self->AddDzLockoutDuration(lockout, seconds, uuid, true); } void Perl_Client_RemoveAllExpeditionLockouts(Client* self) { - self->RemoveAllExpeditionLockouts({}, true); + self->RemoveDzLockouts({}, true); } void Perl_Client_RemoveAllExpeditionLockouts(Client* self, std::string expedition_name) { - self->RemoveAllExpeditionLockouts(expedition_name, true); + self->RemoveDzLockouts(expedition_name, true); } void Perl_Client_RemoveExpeditionLockout(Client* self, std::string expedition_name, std::string event_name) { - self->RemoveExpeditionLockout(expedition_name, event_name, true); + self->RemoveDzLockout(expedition_name, event_name, true); } bool Perl_Client_HasExpeditionLockout(Client* self, std::string expedition_name, std::string event_name) { - return self->HasExpeditionLockout(expedition_name, event_name); + return self->HasDzLockout(expedition_name, event_name); } void Perl_Client_MovePCDynamicZone(Client* self, perl::scalar zone) @@ -2209,7 +2206,7 @@ void Perl_Client_SendToInstance(Client* self, std::string instance_type, std::st self->SendToInstance(instance_type, zone_short_name, instance_version, x, y, z, heading, instance_identifier, duration); } -int Perl_Client_CountItem(Client* self, uint32 item_id) +uint32 Perl_Client_CountItem(Client* self, uint32 item_id) { return self->CountItem(item_id); } @@ -2388,7 +2385,7 @@ void Perl_Client_AddItem(Client* self, perl::reference table_ref) augment_four, augment_five, augment_six, attuned, slot_id); } -int Perl_Client_CountAugmentEquippedByID(Client* self, uint32 item_id) +uint32 Perl_Client_CountAugmentEquippedByID(Client* self, uint32 item_id) { return self->GetInv().CountAugmentEquippedByID(item_id); } @@ -2398,7 +2395,7 @@ bool Perl_Client_HasAugmentEquippedByID(Client* self, uint32 item_id) return self->GetInv().HasAugmentEquippedByID(item_id); } -int Perl_Client_CountItemEquippedByID(Client* self, uint32 item_id) +uint32 Perl_Client_CountItemEquippedByID(Client* self, uint32 item_id) { return self->GetInv().CountItemEquippedByID(item_id); } @@ -2998,7 +2995,7 @@ bool Perl_Client_IsAutoFireEnabled(Client* self) bool Perl_Client_ReloadDataBuckets(Client* self) { - return DataBucket::GetDataBuckets(self); + return self->LoadDataBucketsCache(); } uint32 Perl_Client_GetEXPForLevel(Client* self, uint16 check_level) @@ -3212,6 +3209,160 @@ Merc* Perl_Client_GetMerc(Client* self) return self->GetMerc(); } +perl::array Perl_Client_GetInventorySlots(Client* self) +{ + perl::array result; + const auto& v = self->GetInventorySlots(); + + for (int i = 0; i < v.size(); ++i) { + result.push_back(v[i]); + } + + return result; +} + +void Perl_Client_GrantPetNameChange(Client* self) +{ + self->GrantPetNameChange(); +} + +void Perl_Client_SetAAEXPPercentage(Client* self, uint8 percentage) +{ + self->SetAAEXPPercentage(percentage); +} + +void Perl_Client_SetAccountBucket(Client* self, std::string bucket_name, std::string bucket_value) +{ + self->SetAccountBucket(bucket_name, bucket_value); +} + +void Perl_Client_SetAccountBucket(Client* self, std::string bucket_name, std::string bucket_value, std::string expiration = "") +{ + self->SetAccountBucket(bucket_name, bucket_value, expiration); +} + +void Perl_Client_DeleteAccountBucket(Client* self, std::string bucket_name) +{ + self->DeleteAccountBucket(bucket_name); +} + +std::string Perl_Client_GetAccountBucket(Client* self, std::string bucket_name) +{ + return self->GetAccountBucket(bucket_name); +} + +std::string Perl_Client_GetAccountBucketExpires(Client* self, std::string bucket_name) +{ + return self->GetAccountBucketExpires(bucket_name); +} + +std::string Perl_Client_GetAccountBucketRemaining(Client* self, std::string bucket_name) +{ + return self->GetAccountBucketRemaining(bucket_name); +} + +void Perl_Client_GrantNameChange(Client* self) +{ + self->GrantNameChange(); +} + +bool Perl_Client_IsNameChangeAllowed(Client* self) +{ + return self->IsNameChangeAllowed(); +} + +bool Perl_Client_ClearNameChange(Client* self) +{ + return self->ClearNameChange(); +} + +std::string Perl_Client_GetBandolierName(Client* self, uint8 bandolier_slot) +{ + return self->GetBandolierName(bandolier_slot); +} + +uint32 Perl_Client_GetBandolierItemIcon(Client* self, uint8 bandolier_slot, uint8 slot_id) +{ + return self->GetBandolierItemIcon(bandolier_slot, slot_id); +} + +uint32 Perl_Client_GetBandolierItemID(Client* self, uint8 bandolier_slot, uint8 slot_id) +{ + return self->GetBandolierItemID(bandolier_slot, slot_id); +} + +std::string Perl_Client_GetBandolierItemName(Client* self, uint8 bandolier_slot, uint8 slot_id) +{ + return self->GetBandolierItemName(bandolier_slot, slot_id); +} + +uint32 Perl_Client_GetPotionBeltItemIcon(Client* self, uint8 slot_id) +{ + return self->GetPotionBeltItemIcon(slot_id); +} + +uint32 Perl_Client_GetPotionBeltItemID(Client* self, uint8 slot_id) +{ + return self->GetPotionBeltItemID(slot_id); +} + +std::string Perl_Client_GetPotionBeltItemName(Client* self, uint8 slot_id) +{ + return self->GetPotionBeltItemName(slot_id); +} + +bool Perl_Client_KeyRingAdd(Client* self, uint32 item_id) // @categories Account and Character, Inventory and Items +{ + return self->KeyRingAdd(item_id); +} + +bool Perl_Client_KeyRingCheck(Client* self, uint32 item_id) // @categories Account and Character, Inventory and Items +{ + return self->KeyRingCheck(item_id); +} + +bool Perl_Client_KeyRingClear(Client* self) +{ + return self->KeyRingClear(); +} + +void Perl_Client_KeyRingList(Client* self) +{ + self->KeyRingList(self); +} + +void Perl_Client_KeyRingList(Client* self, Client* c) +{ + self->KeyRingList(c); +} + +bool Perl_Client_KeyRingRemove(Client* self, uint32 item_id) +{ + return self->KeyRingRemove(item_id); +} + +bool Perl_Client_CompleteTask(Client* self, int task_id) +{ + return self->CompleteTask(task_id); +} + +bool Perl_Client_UncompleteTask(Client* self, int task_id) +{ + return self->UncompleteTask(task_id); +} + +perl::array Perl_Client_GetKeyRing(Client* self) +{ + perl::array result; + const auto& v = self->GetKeyRing(); + + for (int i = 0; i < v.size(); ++i) { + result.push_back(v[i]); + } + + return result; +} + void perl_register_client() { perl::interpreter perl(PERL_GET_THX); @@ -3283,6 +3434,8 @@ void perl_register_client() package.add("CanHaveSkill", &Perl_Client_CanHaveSkill); package.add("CashReward", &Perl_Client_CashReward); package.add("ChangeLastName", &Perl_Client_ChangeLastName); + package.add("GrantPetNameChange", &Perl_Client_GrantPetNameChange); + package.add("ClearNameChange", (bool(*)(Client*))&Perl_Client_ClearNameChange); package.add("CharacterID", &Perl_Client_CharacterID); package.add("CheckIncreaseSkill", (bool(*)(Client*, int))&Perl_Client_CheckIncreaseSkill); package.add("CheckIncreaseSkill", (bool(*)(Client*, int, int))&Perl_Client_CheckIncreaseSkill); @@ -3292,17 +3445,19 @@ void perl_register_client() package.add("ClearPEQZoneFlag", &Perl_Client_ClearPEQZoneFlag); package.add("ClearXTargets", &Perl_Client_ClearXTargets); package.add("ClearZoneFlag", &Perl_Client_ClearZoneFlag); + package.add("CompleteTask", &Perl_Client_CompleteTask); package.add("Connected", &Perl_Client_Connected); package.add("CountAugmentEquippedByID", &Perl_Client_CountAugmentEquippedByID); package.add("CountItem", &Perl_Client_CountItem); package.add("CountItemEquippedByID", &Perl_Client_CountItemEquippedByID); - package.add("CreateExpedition", (Expedition*(*)(Client*, perl::reference))&Perl_Client_CreateExpedition); - package.add("CreateExpedition", (Expedition*(*)(Client*, std::string, uint32, uint32, std::string, uint32, uint32))&Perl_Client_CreateExpedition); - package.add("CreateExpedition", (Expedition*(*)(Client*, std::string, uint32, uint32, std::string, uint32, uint32, bool))&Perl_Client_CreateExpedition); + package.add("CreateExpedition", (DynamicZone*(*)(Client*, perl::reference))&Perl_Client_CreateExpedition); + package.add("CreateExpedition", (DynamicZone*(*)(Client*, std::string, uint32, uint32, std::string, uint32, uint32))&Perl_Client_CreateExpedition); + package.add("CreateExpedition", (DynamicZone*(*)(Client*, std::string, uint32, uint32, std::string, uint32, uint32, bool))&Perl_Client_CreateExpedition); package.add("CreateExpeditionFromTemplate", &Perl_Client_CreateExpeditionFromTemplate); package.add("CreateTaskDynamicZone", &Perl_Client_CreateTaskDynamicZone); package.add("DecreaseByID", &Perl_Client_DecreaseByID); package.add("DescribeSpecialAbilities", &Perl_Client_DescribeSpecialAbilities); + package.add("DeleteAccountBucket", &Perl_Client_DeleteAccountBucket); package.add("DeleteItemInInventory", (void(*)(Client*, int16))&Perl_Client_DeleteItemInInventory); package.add("DeleteItemInInventory", (void(*)(Client*, int16, int16))&Perl_Client_DeleteItemInInventory); package.add("DeleteItemInInventory", (void(*)(Client*, int16, int16, bool))&Perl_Client_DeleteItemInInventory); @@ -3340,6 +3495,9 @@ void perl_register_client() package.add("GetAAPoints", &Perl_Client_GetAAPoints); package.add("GetAFK", &Perl_Client_GetAFK); package.add("GetAccountAge", &Perl_Client_GetAccountAge); + package.add("GetAccountBucket", &Perl_Client_GetAccountBucket); + package.add("GetAccountBucketExpires", &Perl_Client_GetAccountBucketExpires); + package.add("GetGetAccountBucketRemaining", &Perl_Client_GetAccountBucketRemaining); package.add("GetAccountFlag", &Perl_Client_GetAccountFlag); package.add("GetAccountFlags", &Perl_Client_GetAccountFlags); package.add("GetAggroCount", &Perl_Client_GetAggroCount); @@ -3350,6 +3508,10 @@ void perl_register_client() package.add("GetAugmentIDAt", &Perl_Client_GetAugmentIDAt); package.add("GetAugmentIDsBySlotID", &Perl_Client_GetAugmentIDsBySlotID); package.add("GetAutoLoginCharacterName", &Perl_Client_GetAutoLoginCharacterName); + package.add("GetBandolierItemIcon", &Perl_Client_GetBandolierItemIcon); + package.add("GetBandolierItemID", &Perl_Client_GetBandolierItemID); + package.add("GetBandolierItemName", &Perl_Client_GetBandolierItemName); + package.add("GetBandolierName", &Perl_Client_GetBandolierName); package.add("GetBaseAGI", &Perl_Client_GetBaseAGI); package.add("GetBaseCHA", &Perl_Client_GetBaseCHA); package.add("GetBaseDEX", &Perl_Client_GetBaseDEX); @@ -3426,11 +3588,13 @@ void perl_register_client() package.add("GetInstanceID", &Perl_Client_GetInstanceID); package.add("GetInstrumentMod", &Perl_Client_GetInstrumentMod); package.add("GetInventory", &Perl_Client_GetInventory); + package.add("GetInventorySlots", &Perl_Client_GetInventorySlots); package.add("GetInvulnerableEnvironmentDamage", &Perl_Client_GetInvulnerableEnvironmentDamage); package.add("GetItemAt", &Perl_Client_GetItemAt); package.add("GetItemCooldown", &Perl_Client_GetItemCooldown); package.add("GetItemIDAt", &Perl_Client_GetItemIDAt); package.add("GetItemInInventory", &Perl_Client_GetItemInInventory); + package.add("GetKeyRing", &Perl_Client_GetKeyRing); package.add("GetLDoNLosses", &Perl_Client_GetLDoNLosses); package.add("GetLDoNLossesTheme", &Perl_Client_GetLDoNLossesTheme); package.add("GetLDoNPointsTheme", &Perl_Client_GetLDoNPointsTheme); @@ -3447,6 +3611,9 @@ void perl_register_client() package.add("GetMemmedSpells", &Perl_Client_GetMemmedSpells); package.add("GetModCharacterFactionLevel", &Perl_Client_GetModCharacterFactionLevel); package.add("GetMoney", &Perl_Client_GetMoney); + package.add("GetPotionBeltItemIcon", &Perl_Client_GetPotionBeltItemIcon); + package.add("GetPotionBeltItemID", &Perl_Client_GetPotionBeltItemID); + package.add("GetPotionBeltItemName", &Perl_Client_GetPotionBeltItemName); package.add("GetPVP", &Perl_Client_GetPVP); package.add("GetPVPPoints", &Perl_Client_GetPVPPoints); package.add("GetRaceAbbreviation", &Perl_Client_GetRaceAbbreviation); @@ -3485,6 +3652,7 @@ void perl_register_client() package.add("GrantAllAAPoints", (void(*)(Client*, uint8, bool))&Perl_Client_GrantAllAAPoints); package.add("GrantAlternateAdvancementAbility", (bool(*)(Client*, int, int))&Perl_Client_GrantAlternateAdvancementAbility); package.add("GrantAlternateAdvancementAbility", (bool(*)(Client*, int, int, bool))&Perl_Client_GrantAlternateAdvancementAbility); + package.add("GrantNameChange", (void(*)(Client*))&Perl_Client_GrantNameChange); package.add("GuildID", &Perl_Client_GuildID); package.add("GuildRank", &Perl_Client_GuildRank); package.add("HasAugmentEquippedByID", &Perl_Client_HasAugmentEquippedByID); @@ -3515,6 +3683,7 @@ void perl_register_client() package.add("IsInAGuild", &Perl_Client_IsInAGuild); package.add("IsLD", &Perl_Client_IsLD); package.add("IsMedding", &Perl_Client_IsMedding); + package.add("IsNameChangeAllowed", (bool(*)(Client*))&Perl_Client_IsNameChangeAllowed); package.add("IsRaidGrouped", &Perl_Client_IsRaidGrouped); package.add("IsSitting", &Perl_Client_IsSitting); package.add("IsStanding", &Perl_Client_IsStanding); @@ -3523,6 +3692,10 @@ void perl_register_client() package.add("IsTaskCompleted", &Perl_Client_IsTaskCompleted); package.add("KeyRingAdd", &Perl_Client_KeyRingAdd); package.add("KeyRingCheck", &Perl_Client_KeyRingCheck); + package.add("KeyRingClear", &Perl_Client_KeyRingClear); + package.add("KeyRingList", (void(*)(Client*))&Perl_Client_KeyRingList); + package.add("KeyRingList", (void(*)(Client*, Client*))&Perl_Client_KeyRingList); + package.add("KeyRingRemove", &Perl_Client_KeyRingRemove); package.add("Kick", &Perl_Client_Kick); package.add("LearnDisciplines", &Perl_Client_LearnDisciplines); package.add("LearnRecipe", &Perl_Client_LearnRecipe); @@ -3639,11 +3812,14 @@ void perl_register_client() package.add("SetAAEXPModifier", (void(*)(Client*, float))&Perl_Client_SetAAEXPModifier); package.add("SetAAEXPModifier", (void(*)(Client*, uint32, float))&Perl_Client_SetAAEXPModifier); package.add("SetAAEXPModifier", (void(*)(Client*, uint32, float, int16))&Perl_Client_SetAAEXPModifier); + package.add("SetAAEXPPercentage", &Perl_Client_SetAAEXPPercentage); package.add("SetAAPoints", &Perl_Client_SetAAPoints); package.add("SetAATitle", (void(*)(Client*, std::string))&Perl_Client_SetAATitle); package.add("SetAATitle", (void(*)(Client*, std::string, bool))&Perl_Client_SetAATitle); package.add("SetAFK", &Perl_Client_SetAFK); package.add("SetAccountFlag", &Perl_Client_SetAccountFlag); + package.add("SetAccountBucket", (void(*)(Client*, std::string, std::string))&Perl_Client_SetAccountBucket); + package.add("SetAccountBucket", (void(*)(Client*, std::string, std::string, std::string))&Perl_Client_SetAccountBucket); package.add("SetAlternateCurrencyValue", &Perl_Client_SetAlternateCurrencyValue); package.add("SetAnon", &Perl_Client_SetAnon); package.add("SetAutoLoginCharacterName", (bool(*)(Client*))&Perl_Client_SetAutoLoginCharacterName); @@ -3729,6 +3905,7 @@ void perl_register_client() package.add("SetTitleSuffix", (void(*)(Client*, std::string))&Perl_Client_SetTitleSuffix); package.add("SetTitleSuffix", (void(*)(Client*, std::string, bool))&Perl_Client_SetTitleSuffix); package.add("SetZoneFlag", &Perl_Client_SetZoneFlag); + package.add("ShowZoneShardMenu", &Perl_Client_ShowZoneShardMenu); package.add("Signal", &Perl_Client_Signal); package.add("SignalClient", &Perl_Client_SignalClient); package.add("SilentMessage", &Perl_Client_SilentMessage); @@ -3762,6 +3939,7 @@ void perl_register_client() package.add("Thirsty", &Perl_Client_Thirsty); package.add("TrainDiscBySpellID", &Perl_Client_TrainDiscBySpellID); package.add("UnFreeze", &Perl_Client_UnFreeze); + package.add("UncompleteTask", &Perl_Client_UncompleteTask); package.add("Undye", &Perl_Client_Undye); package.add("UnmemSpell", (void(*)(Client*, int))&Perl_Client_UnmemSpell); package.add("UnmemSpell", (void(*)(Client*, int, bool))&Perl_Client_UnmemSpell); diff --git a/zone/perl_entity.cpp b/zone/perl_entity.cpp index 64860f4a2..eaad24db5 100644 --- a/zone/perl_entity.cpp +++ b/zone/perl_entity.cpp @@ -419,10 +419,13 @@ perl::array Perl_EntityList_GetBotList(EntityList* self) // @categories Script U { perl::array result; auto current_bot_list = self->GetBotList(); - for (Bot* bot_iterator : current_bot_list) - { - result.push_back(bot_iterator); + auto it = current_bot_list.begin(); + + while (it != current_bot_list.end()) { + result.push_back(it->second); + ++it; } + return result; } diff --git a/zone/perl_expedition.cpp b/zone/perl_expedition.cpp index c46cb907c..a9b485f9a 100644 --- a/zone/perl_expedition.cpp +++ b/zone/perl_expedition.cpp @@ -3,214 +3,209 @@ #ifdef EMBPERL_XS_CLASSES #include "embperl.h" -#include "expedition.h" +#include "dynamic_zone.h" #include "../common/zone_store.h" #include "../common/global_define.h" -void Perl_Expedition_AddLockout(Expedition* self, std::string event_name, uint32_t seconds) +void Perl_Expedition_AddLockout(DynamicZone* self, std::string event_name, uint32_t seconds) { self->AddLockout(event_name, seconds); } -void Perl_Expedition_AddLockoutDuration(Expedition* self, std::string event_name, int seconds) +void Perl_Expedition_AddLockoutDuration(DynamicZone* self, std::string event_name, int seconds) { self->AddLockoutDuration(event_name, seconds); } -void Perl_Expedition_AddLockoutDuration(Expedition* self, std::string event_name, int seconds, bool members_only) +void Perl_Expedition_AddLockoutDuration(DynamicZone* self, std::string event_name, int seconds, bool members_only) { self->AddLockoutDuration(event_name, seconds, members_only); } -void Perl_Expedition_AddReplayLockout(Expedition* self, uint32_t seconds) +void Perl_Expedition_AddReplayLockout(DynamicZone* self, uint32_t seconds) { - self->AddReplayLockout(seconds); + self->AddLockout(DzLockout::ReplayTimer, seconds); } -void Perl_Expedition_AddReplayLockoutDuration(Expedition* self, int seconds) +void Perl_Expedition_AddReplayLockoutDuration(DynamicZone* self, int seconds) { - self->AddReplayLockoutDuration(seconds); + self->AddLockoutDuration(DzLockout::ReplayTimer, seconds);; } -void Perl_Expedition_AddReplayLockoutDuration(Expedition* self, int seconds, bool members_only) +void Perl_Expedition_AddReplayLockoutDuration(DynamicZone* self, int seconds, bool members_only) { - self->AddReplayLockoutDuration(seconds, members_only); + self->AddLockoutDuration(DzLockout::ReplayTimer, seconds, members_only); } -uint32_t Perl_Expedition_GetDynamicZoneID(Expedition* self) -{ - return self->GetDynamicZone()->GetID(); -} - -uint32_t Perl_Expedition_GetID(Expedition* self) +uint32_t Perl_Expedition_GetID(DynamicZone* self) { return self->GetID(); } -uint16_t Perl_Expedition_GetInstanceID(Expedition* self) +uint16_t Perl_Expedition_GetInstanceID(DynamicZone* self) { - return self->GetDynamicZone()->GetInstanceID(); + return self->GetInstanceID(); } -std::string Perl_Expedition_GetLeaderName(Expedition* self) +std::string Perl_Expedition_GetLeaderName(DynamicZone* self) { return self->GetLeaderName(); } -perl::reference Perl_Expedition_GetLockouts(Expedition* self) +perl::reference Perl_Expedition_GetLockouts(DynamicZone* self) { perl::hash table; - auto lockouts = self->GetLockouts(); + const auto& lockouts = self->GetLockouts(); for (const auto& lockout : lockouts) { - table[lockout.first] = lockout.second.GetSecondsRemaining(); + table[lockout.Event()] = lockout.GetSecondsRemaining(); } return perl::reference(table); } -std::string Perl_Expedition_GetLootEventByNPCTypeID(Expedition* self, uint32_t npc_type_id) +std::string Perl_Expedition_GetLootEventByNPCTypeID(DynamicZone* self, uint32_t npc_type_id) { - return self->GetLootEventByNPCTypeID(npc_type_id); + return self->GetLootEvent(npc_type_id, DzLootEvent::Type::NpcType); } -std::string Perl_Expedition_GetLootEventBySpawnID(Expedition* self, uint32_t spawn_id) +std::string Perl_Expedition_GetLootEventBySpawnID(DynamicZone* self, uint32_t spawn_id) { - return self->GetLootEventBySpawnID(spawn_id); + return self->GetLootEvent(spawn_id, DzLootEvent::Type::Entity); } -uint32_t Perl_Expedition_GetMemberCount(Expedition* self) +uint32_t Perl_Expedition_GetMemberCount(DynamicZone* self) { - return self->GetDynamicZone()->GetMemberCount(); + return self->GetMemberCount(); } -perl::reference Perl_Expedition_GetMembers(Expedition* self) +perl::reference Perl_Expedition_GetMembers(DynamicZone* self) { perl::hash table; - for (const auto& member : self->GetDynamicZone()->GetMembers()) + for (const auto& member : self->GetMembers()) { table[member.name] = member.id; } return perl::reference(table); } -std::string Perl_Expedition_GetName(Expedition* self) +std::string Perl_Expedition_GetName(DynamicZone* self) { return self->GetName(); } -uint32_t Perl_Expedition_GetSecondsRemaining(Expedition* self) +uint32_t Perl_Expedition_GetSecondsRemaining(DynamicZone* self) { - return self->GetDynamicZone()->GetSecondsRemaining(); + return self->GetSecondsRemaining(); } -std::string Perl_Expedition_GetUUID(Expedition* self) +std::string Perl_Expedition_GetUUID(DynamicZone* self) { - return self->GetDynamicZone()->GetUUID(); + return self->GetUUID(); } -uint16_t Perl_Expedition_GetZoneID(Expedition* self) +uint16_t Perl_Expedition_GetZoneID(DynamicZone* self) { - return self->GetDynamicZone()->GetZoneID(); + return self->GetZoneID(); } -std::string Perl_Expedition_GetZoneName(Expedition* self) +std::string Perl_Expedition_GetZoneName(DynamicZone* self) { - return ZoneName(self->GetDynamicZone()->GetZoneID()); + return ZoneName(self->GetZoneID()); } -uint32_t Perl_Expedition_GetZoneVersion(Expedition* self) +uint32_t Perl_Expedition_GetZoneVersion(DynamicZone* self) { - return self->GetDynamicZone()->GetZoneVersion(); + return self->GetZoneVersion(); } -bool Perl_Expedition_HasLockout(Expedition* self, std::string event_name) +bool Perl_Expedition_HasLockout(DynamicZone* self, std::string event_name) { return self->HasLockout(event_name); } -bool Perl_Expedition_HasReplayLockout(Expedition* self) +bool Perl_Expedition_HasReplayLockout(DynamicZone* self) { return self->HasReplayLockout(); } -bool Perl_Expedition_IsLocked(Expedition* self) +bool Perl_Expedition_IsLocked(DynamicZone* self) { return self->IsLocked(); } -void Perl_Expedition_RemoveCompass(Expedition* self) +void Perl_Expedition_RemoveCompass(DynamicZone* self) { - self->GetDynamicZone()->SetCompass(0, 0, 0, 0, true); + self->SetCompass(0, 0, 0, 0, true); } -void Perl_Expedition_RemoveLockout(Expedition* self, std::string event_name) +void Perl_Expedition_RemoveLockout(DynamicZone* self, std::string event_name) { self->RemoveLockout(event_name); } -void Perl_Expedition_SetCompass(Expedition* self, perl::scalar zone, float x, float y, float z) +void Perl_Expedition_SetCompass(DynamicZone* self, perl::scalar zone, float x, float y, float z) { uint32_t zone_id = zone.is_string() ? ZoneID(zone.c_str()) : zone.as(); - self->GetDynamicZone()->SetCompass(zone_id, x, y, z, true); + self->SetCompass(zone_id, x, y, z, true); } -void Perl_Expedition_SetLocked(Expedition* self, bool locked) +void Perl_Expedition_SetLocked(DynamicZone* self, bool locked) { - self->SetLocked(locked, ExpeditionLockMessage::None); + self->SetLocked(locked, true); } -void Perl_Expedition_SetLocked(Expedition* self, bool locked, int lock_msg) +void Perl_Expedition_SetLocked(DynamicZone* self, bool locked, int lock_msg) { - self->SetLocked(locked, static_cast(lock_msg), true); + self->SetLocked(locked, true, static_cast(lock_msg)); } -void Perl_Expedition_SetLocked(Expedition* self, bool locked, int lock_msg, uint32_t color) +void Perl_Expedition_SetLocked(DynamicZone* self, bool locked, int lock_msg, uint32_t color) { - self->SetLocked(locked, static_cast(lock_msg), true, color); + self->SetLocked(locked, true, static_cast(lock_msg), color); } -void Perl_Expedition_SetLootEventByNPCTypeID(Expedition* self, uint32_t npc_type_id, std::string event_name) +void Perl_Expedition_SetLootEventByNPCTypeID(DynamicZone* self, uint32_t npc_type_id, std::string event_name) { - self->SetLootEventByNPCTypeID(npc_type_id, event_name); + self->SetLootEvent(npc_type_id, event_name, DzLootEvent::Type::NpcType); } -void Perl_Expedition_SetLootEventBySpawnID(Expedition* self, uint32_t entity_id, std::string event_name) +void Perl_Expedition_SetLootEventBySpawnID(DynamicZone* self, uint32_t entity_id, std::string event_name) { - self->SetLootEventBySpawnID(entity_id, event_name); + self->SetLootEvent(entity_id, event_name, DzLootEvent::Type::Entity); } -void Perl_Expedition_SetReplayLockoutOnMemberJoin(Expedition* self, bool enable) +void Perl_Expedition_SetReplayLockoutOnMemberJoin(DynamicZone* self, bool enable) { - self->SetReplayLockoutOnMemberJoin(enable, true); + self->SetReplayOnJoin(enable, true); } -void Perl_Expedition_SetSafeReturn(Expedition* self, perl::scalar zone, float x, float y, float z, float heading) +void Perl_Expedition_SetSafeReturn(DynamicZone* self, perl::scalar zone, float x, float y, float z, float heading) { uint32_t zone_id = zone.is_string() ? ZoneID(zone.c_str()) : zone.as(); - self->GetDynamicZone()->SetSafeReturn(zone_id, x, y, z, heading, true); + self->SetSafeReturn(zone_id, x, y, z, heading, true); } -void Perl_Expedition_SetSecondsRemaining(Expedition* self, uint32_t seconds_remaining) +void Perl_Expedition_SetSecondsRemaining(DynamicZone* self, uint32_t seconds_remaining) { - self->GetDynamicZone()->SetSecondsRemaining(seconds_remaining); + self->SetSecondsRemaining(seconds_remaining); } -void Perl_Expedition_SetSwitchID(Expedition* self, int dz_switch_id) +void Perl_Expedition_SetSwitchID(DynamicZone* self, int dz_switch_id) { - self->GetDynamicZone()->SetSwitchID(dz_switch_id, true); + self->SetSwitchID(dz_switch_id, true); } -void Perl_Expedition_SetZoneInLocation(Expedition* self, float x, float y, float z, float heading) +void Perl_Expedition_SetZoneInLocation(DynamicZone* self, float x, float y, float z, float heading) { - self->GetDynamicZone()->SetZoneInLocation(x, y, z, heading, true); + self->SetZoneInLocation(x, y, z, heading, true); } -void Perl_Expedition_UpdateLockoutDuration(Expedition* self, std::string event_name, uint32_t seconds) +void Perl_Expedition_UpdateLockoutDuration(DynamicZone* self, std::string event_name, uint32_t seconds) { self->UpdateLockoutDuration(event_name, seconds); } -void Perl_Expedition_UpdateLockoutDuration(Expedition* self, std::string event_name, uint32_t seconds, bool members_only) +void Perl_Expedition_UpdateLockoutDuration(DynamicZone* self, std::string event_name, uint32_t seconds, bool members_only) { self->UpdateLockoutDuration(event_name, seconds, members_only); } @@ -219,14 +214,14 @@ void perl_register_expedition() { perl::interpreter perl(PERL_GET_THX); - auto package = perl.new_class("Expedition"); + auto package = perl.new_class("Expedition"); package.add("AddLockout", &Perl_Expedition_AddLockout); - package.add("AddLockoutDuration", (void(*)(Expedition*, std::string, int))&Perl_Expedition_AddLockoutDuration); - package.add("AddLockoutDuration", (void(*)(Expedition*, std::string, int, bool))&Perl_Expedition_AddLockoutDuration); + package.add("AddLockoutDuration", (void(*)(DynamicZone*, std::string, int))&Perl_Expedition_AddLockoutDuration); + package.add("AddLockoutDuration", (void(*)(DynamicZone*, std::string, int, bool))&Perl_Expedition_AddLockoutDuration); package.add("AddReplayLockout", &Perl_Expedition_AddReplayLockout); - package.add("AddReplayLockoutDuration", (void(*)(Expedition*, int))&Perl_Expedition_AddReplayLockoutDuration); - package.add("AddReplayLockoutDuration", (void(*)(Expedition*, int, bool))&Perl_Expedition_AddReplayLockoutDuration); - package.add("GetDynamicZoneID", &Perl_Expedition_GetDynamicZoneID); + package.add("AddReplayLockoutDuration", (void(*)(DynamicZone*, int))&Perl_Expedition_AddReplayLockoutDuration); + package.add("AddReplayLockoutDuration", (void(*)(DynamicZone*, int, bool))&Perl_Expedition_AddReplayLockoutDuration); + package.add("GetDynamicZoneID", &Perl_Expedition_GetID); package.add("GetID", &Perl_Expedition_GetID); package.add("GetInstanceID", &Perl_Expedition_GetInstanceID); package.add("GetLeaderName", &Perl_Expedition_GetLeaderName); @@ -247,9 +242,9 @@ void perl_register_expedition() package.add("RemoveCompass", &Perl_Expedition_RemoveCompass); package.add("RemoveLockout", &Perl_Expedition_RemoveLockout); package.add("SetCompass", &Perl_Expedition_SetCompass); - package.add("SetLocked", (void(*)(Expedition*, bool))&Perl_Expedition_SetLocked); - package.add("SetLocked", (void(*)(Expedition*, bool, int))&Perl_Expedition_SetLocked); - package.add("SetLocked", (void(*)(Expedition*, bool, int, uint32_t))&Perl_Expedition_SetLocked); + package.add("SetLocked", (void(*)(DynamicZone*, bool))&Perl_Expedition_SetLocked); + package.add("SetLocked", (void(*)(DynamicZone*, bool, int))&Perl_Expedition_SetLocked); + package.add("SetLocked", (void(*)(DynamicZone*, bool, int, uint32_t))&Perl_Expedition_SetLocked); package.add("SetLootEventByNPCTypeID", &Perl_Expedition_SetLootEventByNPCTypeID); package.add("SetLootEventBySpawnID", &Perl_Expedition_SetLootEventBySpawnID); package.add("SetReplayLockoutOnMemberJoin", &Perl_Expedition_SetReplayLockoutOnMemberJoin); @@ -257,8 +252,8 @@ void perl_register_expedition() package.add("SetSecondsRemaining", &Perl_Expedition_SetSecondsRemaining); package.add("SetSwitchID", &Perl_Expedition_SetSwitchID); package.add("SetZoneInLocation", &Perl_Expedition_SetZoneInLocation); - package.add("UpdateLockoutDuration", (void(*)(Expedition*, std::string, uint32_t))&Perl_Expedition_UpdateLockoutDuration); - package.add("UpdateLockoutDuration", (void(*)(Expedition*, std::string, uint32_t, bool))&Perl_Expedition_UpdateLockoutDuration); + package.add("UpdateLockoutDuration", (void(*)(DynamicZone*, std::string, uint32_t))&Perl_Expedition_UpdateLockoutDuration); + package.add("UpdateLockoutDuration", (void(*)(DynamicZone*, std::string, uint32_t, bool))&Perl_Expedition_UpdateLockoutDuration); } void perl_register_expedition_lock_messages() @@ -266,9 +261,9 @@ void perl_register_expedition_lock_messages() perl::interpreter perl(PERL_GET_THX); auto package = perl.new_package("ExpeditionLockMessage"); - package.add_const("None", static_cast(ExpeditionLockMessage::None)); - package.add_const("Close", static_cast(ExpeditionLockMessage::Close)); - package.add_const("Begin", static_cast(ExpeditionLockMessage::Begin)); + package.add_const("None", static_cast(DzLockMsg::None)); + package.add_const("Close", static_cast(DzLockMsg::Close)); + package.add_const("Begin", static_cast(DzLockMsg::Begin)); } #endif //EMBPERL_XS_CLASSES diff --git a/zone/perl_groups.cpp b/zone/perl_groups.cpp index 3c87a494f..a97716a28 100644 --- a/zone/perl_groups.cpp +++ b/zone/perl_groups.cpp @@ -127,14 +127,24 @@ Client* Perl_Group_GetMember(Group* self, int member_index) // @categories Accou return member ? member->CastToClient() : nullptr; } +uint8_t Perl_Group_GetMemberRole(Group* self, Mob* member) // @categories Account and Character, Script Utility, Group +{ + return self->GetMemberRole(member); +} + +uint8_t Perl_Group_GetMemberRole(Group* self, const char* name) // @categories Account and Character, Script Utility, Group +{ + return self->GetMemberRole(name); +} + bool Perl_Group_DoesAnyMemberHaveExpeditionLockout(Group* self, std::string expedition_name, std::string event_name) { - return self->DoesAnyMemberHaveExpeditionLockout(expedition_name, event_name); + return self->AnyMemberHasDzLockout(expedition_name, event_name); } bool Perl_Group_DoesAnyMemberHaveExpeditionLockout(Group* self, std::string expedition_name, std::string event_name, int max_check_count) { - return self->DoesAnyMemberHaveExpeditionLockout(expedition_name, event_name, max_check_count); + return self->AnyMemberHasDzLockout(expedition_name, event_name); // max_check_count deprecated } uint32_t Perl_Group_GetLowestLevel(Group* self) // @categories Script Utility, Group @@ -163,6 +173,8 @@ void perl_register_group() package.add("GetLeaderName", &Perl_Group_GetLeaderName); package.add("GetLowestLevel", &Perl_Group_GetLowestLevel); package.add("GetMember", &Perl_Group_GetMember); + package.add("GetMemberRole", (uint8_t(*)(Group*, Mob*))&Perl_Group_GetMemberRole); + package.add("GetMemberRole", (uint8_t(*)(Group*, const char*))&Perl_Group_GetMemberRole); package.add("GetTotalGroupDamage", &Perl_Group_GetTotalGroupDamage); package.add("GroupCount", &Perl_Group_GroupCount); package.add("GroupMessage", (void(*)(Group*, Mob*, const char*))&Perl_Group_GroupMessage); diff --git a/zone/perl_inventory.cpp b/zone/perl_inventory.cpp index ba083e936..a002a24a7 100644 --- a/zone/perl_inventory.cpp +++ b/zone/perl_inventory.cpp @@ -150,7 +150,7 @@ bool Perl_Inventory_HasAugmentEquippedByID(EQ::InventoryProfile* self, uint32_t return self->HasAugmentEquippedByID(item_id); } -int Perl_Inventory_CountAugmentEquippedByID(EQ::InventoryProfile* self, uint32_t item_id) +uint32 Perl_Inventory_CountAugmentEquippedByID(EQ::InventoryProfile* self, uint32_t item_id) { return self->CountAugmentEquippedByID(item_id); } @@ -160,7 +160,7 @@ bool Perl_Inventory_HasItemEquippedByID(EQ::InventoryProfile* self, uint32_t ite return self->HasItemEquippedByID(item_id); } -int Perl_Inventory_CountItemEquippedByID(EQ::InventoryProfile* self, uint32_t item_id) +uint32 Perl_Inventory_CountItemEquippedByID(EQ::InventoryProfile* self, uint32_t item_id) { return self->CountItemEquippedByID(item_id); } diff --git a/zone/perl_mob.cpp b/zone/perl_mob.cpp index a4042dc4b..dd06db33b 100644 --- a/zone/perl_mob.cpp +++ b/zone/perl_mob.cpp @@ -1114,11 +1114,16 @@ uint16 Perl_Mob_GetOwnerID(Mob* self) // @categories Script Utility, Pet return self->GetOwnerID(); } -int Perl_Mob_GetPetType(Mob* self) // @categories Script Utility, Pet +uint8 Perl_Mob_GetPetType(Mob* self) // @categories Script Utility, Pet { return self->GetPetType(); } +std::string Perl_Mob_GetPetTypeName(Mob* self) // @categories Script Utility, Pet +{ + return PetType::GetName(self->GetPetType()); +} + int Perl_Mob_GetBodyType(Mob* self) // @categories Stats and Attributes { return self->GetBodyType(); @@ -1254,12 +1259,12 @@ float Perl_Mob_GetAssistRange(Mob* self) // @categories Stats and Attributes, Ha return self->GetAssistRange(); } -void Perl_Mob_SetPetOrder(Mob* self, int order) // @categories Pet +void Perl_Mob_SetPetOrder(Mob* self, uint8 pet_order) // @categories Pet { - self->SetPetOrder(static_cast(order)); + self->SetPetOrder(pet_order); } -int Perl_Mob_GetPetOrder(Mob* self) // @categories Script Utility, Pet +uint8 Perl_Mob_GetPetOrder(Mob* self) // @categories Script Utility, Pet { return self->GetPetOrder(); } @@ -3400,6 +3405,11 @@ bool Perl_Mob_IsPetOwnerNPC(Mob* self) return self->IsPetOwnerNPC(); } +bool Perl_Mob_IsPetOwnerOfClientBot(Mob* self) +{ + return self->IsPetOwnerOfClientBot(); +} + bool Perl_Mob_IsDestructibleObject(Mob* self) { return self->IsDestructibleObject(); @@ -3568,6 +3578,35 @@ void Perl_Mob_BuffFadeSongs(Mob* self) self->BuffFadeSongs(); } +perl::array Perl_Mob_GetPausedTimers(Mob* self) +{ + perl::array a; + + const auto& l = quest_manager.GetPausedTimers(self); + for (const auto& v : l) { + a.push_back(v); + } + + return a; +} + +perl::array Perl_Mob_GetTimers(Mob* self) +{ + perl::array a; + + const auto& l = quest_manager.GetTimers(self); + for (const auto& v : l) { + a.push_back(v); + } + + return a; +} + +void Perl_Mob_SetPetType(Mob* self, uint8 pet_type) +{ + self->SetPetType(pet_type); +} + void perl_register_mob() { perl::interpreter perl(PERL_GET_THX); @@ -3892,10 +3931,12 @@ void perl_register_mob() package.add("GetOwner", &Perl_Mob_GetOwner); package.add("GetOwnerID", &Perl_Mob_GetOwnerID); package.add("GetPR", &Perl_Mob_GetPR); + package.add("GetPausedTimers", &Perl_Mob_GetPausedTimers); package.add("GetPet", &Perl_Mob_GetPet); package.add("GetPetID", &Perl_Mob_GetPetID); package.add("GetPetOrder", &Perl_Mob_GetPetOrder); package.add("GetPetType", &Perl_Mob_GetPetType); + package.add("GetPetTypeName", &Perl_Mob_GetPetTypeName); package.add("GetPhR", &Perl_Mob_GetPhR); package.add("GetRace", &Perl_Mob_GetRace); package.add("GetRaceName", &Perl_Mob_GetRaceName); @@ -3922,6 +3963,7 @@ void perl_register_mob() package.add("GetTarget", &Perl_Mob_GetTarget); package.add("GetTexture", &Perl_Mob_GetTexture); package.add("GetTimerDurationMS", &Perl_Mob_GetTimerDurationMS); + package.add("GetTimers", &Perl_Mob_GetTimers); package.add("GetUltimateOwner", &Perl_Mob_GetUltimateOwner); package.add("GetWIS", &Perl_Mob_GetWIS); package.add("GetWalkspeed", &Perl_Mob_GetWalkspeed); @@ -3999,6 +4041,7 @@ void perl_register_mob() package.add("IsPetOwnerBot", &Perl_Mob_IsPetOwnerBot); package.add("IsPetOwnerClient", &Perl_Mob_IsPetOwnerClient); package.add("IsPetOwnerNPC", &Perl_Mob_IsPetOwnerNPC); + package.add("IsPetOwnerOfClientBot", &Perl_Mob_IsPetOwnerOfClientBot); package.add("IsPlayerCorpse", &Perl_Mob_IsPlayerCorpse); package.add("IsPureMeleeClass", &Perl_Mob_IsPureMeleeClass); package.add("IsRoamer", &Perl_Mob_IsRoamer); @@ -4155,6 +4198,7 @@ void perl_register_mob() package.add("SetPet", &Perl_Mob_SetPet); package.add("SetPetID", &Perl_Mob_SetPetID); package.add("SetPetOrder", &Perl_Mob_SetPetOrder); + package.add("SetPetType", &Perl_Mob_SetPetType); package.add("SetRace", &Perl_Mob_SetRace); package.add("SetRunAnimSpeed", &Perl_Mob_SetRunAnimSpeed); package.add("SetRunning", &Perl_Mob_SetRunning); diff --git a/zone/perl_npc.cpp b/zone/perl_npc.cpp index b47b8c0e8..6eaf0ac47 100644 --- a/zone/perl_npc.cpp +++ b/zone/perl_npc.cpp @@ -615,7 +615,7 @@ bool Perl_NPC_HasItem(NPC* self, uint32 item_id) // @categories Script Utility return self->HasItem(item_id); } -int Perl_NPC_CountItem(NPC* self, uint32 item_id) +uint32 Perl_NPC_CountItem(NPC* self, uint32 item_id) { return self->CountItem(item_id); } @@ -796,6 +796,105 @@ void Perl_NPC_DescribeSpecialAbilities(NPC* self, Client* c) self->DescribeSpecialAbilities(c); } +bool Perl_NPC_IsMultiQuestEnabled(NPC* self) +{ + return self->IsMultiQuestEnabled(); +} + +void Perl_NPC_MultiQuestEnable(NPC* self) +{ + self->MultiQuestEnable(); +} + +bool Perl_NPC_IsResumedFromZoneSuspend(NPC* self) +{ + return self->IsResumedFromZoneSuspend(); +} + +bool Perl_NPC_CheckHandin( + NPC* self, + Client* c, + perl::reference handin_ref, + perl::reference required_ref, + perl::array items_ref +) +{ + perl::hash handin = handin_ref; + perl::hash required = required_ref; + + std::map handin_map; + std::map required_map; + std::vector items; + + for (auto e: handin) { + if (!e.first) { + continue; + } + + if (Strings::EqualFold(e.first, "0")) { + continue; + } + + LogNpcHandinDetail("Handin key [{}] value [{}]", e.first, handin.at(e.first).c_str()); + + const uint32 count = static_cast(handin.at(e.first)); + handin_map[e.first] = count; + } + + for (auto e: required) { + if (!e.first) { + continue; + } + + if (Strings::EqualFold(e.first, "0")) { + continue; + } + + LogNpcHandinDetail("Required key [{}] value [{}]", e.first, required.at(e.first).c_str()); + + const uint32 count = static_cast(required.at(e.first)); + required_map[e.first] = count; + } + + for (auto e : items_ref) { + EQ::ItemInstance* i = static_cast(e); + if (!i) { + continue; + } + + items.emplace_back(i); + + LogNpcHandinDetail( + "Item instance [{}] ({}) UUID ({}) added to handin list", + i->GetItem()->Name, + i->GetItem()->ID, + i->GetSerialNumber() + ); + } + + return self->CheckHandin(c, handin_map, required_map, items); +} + +void Perl_NPC_ReturnHandinItems(NPC *self, Client* c) +{ + self->ReturnHandinItems(c); +} + +Spawn2* Perl_NPC_GetSpawn(NPC* self) +{ + return self->GetSpawn(); +} + +void Perl_NPC_SetNPCTintIndex(NPC* self, uint32 index) +{ + self->SetNPCTintIndex(index); +} + +uint32 Perl_NPC_GetNPCTintIndex(NPC* self) +{ + return self->GetNPCTintIndex(); +} + void perl_register_npc() { perl::interpreter perl(PERL_GET_THX); @@ -827,6 +926,7 @@ void perl_register_npc() package.add("CalculateNewWaypoint", &Perl_NPC_CalculateNewWaypoint); package.add("ChangeLastName", &Perl_NPC_ChangeLastName); package.add("CheckNPCFactionAlly", &Perl_NPC_CheckNPCFactionAlly); + package.add("CheckHandin", &Perl_NPC_CheckHandin); package.add("ClearItemList", &Perl_NPC_ClearLootItems); package.add("ClearLastName", &Perl_NPC_ClearLastName); package.add("CountItem", &Perl_NPC_CountItem); @@ -864,6 +964,7 @@ void perl_register_npc() package.add("GetNPCSpellsEffectsID", &Perl_NPC_GetNPCSpellsEffectsID); package.add("GetNPCSpellsID", &Perl_NPC_GetNPCSpellsID); package.add("GetNPCStat", &Perl_NPC_GetNPCStat); + package.add("GetNPCTintIndex", &Perl_NPC_GetNPCTintIndex); package.add("GetPetSpellID", &Perl_NPC_GetPetSpellID); package.add("GetPlatinum", &Perl_NPC_GetPlatinum); package.add("GetPrimSkill", &Perl_NPC_GetPrimSkill); @@ -873,6 +974,7 @@ void perl_register_npc() package.add("GetSilver", &Perl_NPC_GetSilver); package.add("GetSlowMitigation", &Perl_NPC_GetSlowMitigation); package.add("GetSp2", &Perl_NPC_GetSp2); + package.add("GetSpawn", &Perl_NPC_GetSpawn); package.add("GetSpawnKillCount", &Perl_NPC_GetSpawnKillCount); package.add("GetSpawnPointH", &Perl_NPC_GetSpawnPointH); package.add("GetSpawnPointID", &Perl_NPC_GetSpawnPointID); @@ -892,10 +994,12 @@ void perl_register_npc() package.add("IsGuarding", &Perl_NPC_IsGuarding); package.add("IsLDoNLocked", &Perl_NPC_IsLDoNLocked); package.add("IsLDoNTrapped", &Perl_NPC_IsLDoNTrapped); - package.add("IsLDoNTrapDetected", &Perl_NPC_IsLDoNTrapDetected);; + package.add("IsLDoNTrapDetected", &Perl_NPC_IsLDoNTrapDetected); + package.add("IsMultiQuestEnabled", &Perl_NPC_IsMultiQuestEnabled); package.add("IsOnHatelist", &Perl_NPC_IsOnHatelist); package.add("IsRaidTarget", &Perl_NPC_IsRaidTarget); package.add("IsRareSpawn", &Perl_NPC_IsRareSpawn); + package.add("IsResumedFromZoneSuspend", &Perl_NPC_IsResumedFromZoneSuspend); package.add("IsTaunting", &Perl_NPC_IsTaunting); package.add("IsUnderwaterOnly", (bool(*)(NPC*))&Perl_NPC_IsUnderwaterOnly); package.add("MerchantCloseShop", &Perl_NPC_MerchantCloseShop); @@ -904,6 +1008,7 @@ void perl_register_npc() package.add("MoveTo", (void(*)(NPC*, float, float, float))&Perl_NPC_MoveTo); package.add("MoveTo", (void(*)(NPC*, float, float, float, float))&Perl_NPC_MoveTo); package.add("MoveTo", (void(*)(NPC*, float, float, float, float, bool))&Perl_NPC_MoveTo); + package.add("MultiQuestEnable", &Perl_NPC_MultiQuestEnable); package.add("NextGuardPosition", &Perl_NPC_NextGuardPosition); package.add("PauseWandering", &Perl_NPC_PauseWandering); package.add("PickPocket", &Perl_NPC_PickPocket); @@ -920,6 +1025,7 @@ void perl_register_npc() package.add("RemoveMeleeProc", &Perl_NPC_RemoveMeleeProc); package.add("RemoveRangedProc", &Perl_NPC_RemoveRangedProc); package.add("ResumeWandering", &Perl_NPC_ResumeWandering); + package.add("ReturnHandinItems", &Perl_NPC_ReturnHandinItems); package.add("SaveGuardSpot", (void(*)(NPC*))&Perl_NPC_SaveGuardSpot); package.add("SaveGuardSpot", (void(*)(NPC*, bool))&Perl_NPC_SaveGuardSpot); package.add("SaveGuardSpot", (void(*)(NPC*, float, float, float, float))&Perl_NPC_SaveGuardSpot); @@ -939,6 +1045,7 @@ void perl_register_npc() package.add("SetGold", &Perl_NPC_SetGold); package.add("SetGrid", &Perl_NPC_SetGrid); package.add("SetNPCFactionID", &Perl_NPC_SetNPCFactionID); + package.add("SetNPCTintIndex", &Perl_NPC_SetNPCTintIndex); package.add("SetPetSpellID", &Perl_NPC_SetPetSpellID); package.add("SetPlatinum", &Perl_NPC_SetPlatinum); package.add("SetPrimSkill", &Perl_NPC_SetPrimSkill); diff --git a/zone/perl_player_corpse.cpp b/zone/perl_player_corpse.cpp index 60de30201..c034e0312 100644 --- a/zone/perl_player_corpse.cpp +++ b/zone/perl_player_corpse.cpp @@ -161,7 +161,7 @@ bool Perl_Corpse_HasItem(Corpse* self, uint32_t item_id) // @categories Script U return self->HasItem(item_id); } -int Perl_Corpse_CountItem(Corpse* self, uint32_t item_id) // @categories Script Utility +uint32 Perl_Corpse_CountItem(Corpse* self, uint32_t item_id) // @categories Script Utility { return self->CountItem(item_id); } diff --git a/zone/perl_questitem.cpp b/zone/perl_questitem.cpp index fea1bf061..53e906de7 100644 --- a/zone/perl_questitem.cpp +++ b/zone/perl_questitem.cpp @@ -152,11 +152,6 @@ uint32 Perl_QuestItem_GetItemScriptID(EQ::ItemInstance* self) return self->GetItemScriptID(); } -uint32 Perl_QuestItem_GetKillsNeeded(EQ::ItemInstance* self, uint8 current_level) -{ - return self->GetKillsNeeded(current_level); -} - int8 Perl_QuestItem_GetMaxEvolveLevel(EQ::ItemInstance* self) { return self->GetMaxEvolveLvl(); @@ -297,12 +292,88 @@ std::string Perl_QuestItem_GetItemLink(EQ::ItemInstance* self) return linker.GenerateLink(); } +void Perl_QuestItem_AddEvolveAmount(EQ::ItemInstance* self, uint64 amount) +{ + self->SetEvolveAddToCurrentAmount(amount); +} + +uint32 Perl_QuestItem_GetAugmentEvolveUniqueID(EQ::ItemInstance* self, uint8 slot_id) +{ + return self->GetAugmentEvolveUniqueID(slot_id); +} + +bool Perl_QuestItem_GetEvolveActivated(EQ::ItemInstance* self) +{ + return self->GetEvolveActivated(); +} + +uint64 Perl_QuestItem_GetEvolveAmount(EQ::ItemInstance* self) +{ + return self->GetEvolveCurrentAmount(); +} + +uint32 Perl_QuestItem_GetEvolveCharacterID(EQ::ItemInstance* self) +{ + return self->GetEvolveCharID(); +} + +bool Perl_QuestItem_GetEvolveEquipped(EQ::ItemInstance* self) +{ + return self->GetEvolveEquipped(); +} + +uint32 Perl_QuestItem_GetEvolveItemID(EQ::ItemInstance* self) +{ + return self->GetEvolveItemID(); +} + +uint32 Perl_QuestItem_GetEvolveFinalItemID(EQ::ItemInstance* self) +{ + return self->GetEvolveFinalItemID(); +} + +int8 Perl_QuestItem_GetEvolveLevel(EQ::ItemInstance* self) +{ + return self->GetEvolveLvl(); +} + +uint32 Perl_QuestItem_GetEvolveLoreID(EQ::ItemInstance* self) +{ + return self->GetEvolveLoreID(); +} + +double Perl_QuestItem_GetEvolveProgression(EQ::ItemInstance* self) +{ + return self->GetEvolveProgression(); +} + +uint64 Perl_QuestItem_GetEvolveUniqueID(EQ::ItemInstance* self) +{ + return self->GetEvolveUniqueID(); +} + +bool Perl_QuestItem_IsEvolving(EQ::ItemInstance* self) +{ + return self->IsEvolving(); +} + +void Perl_QuestItem_SetEvolveProgression(EQ::ItemInstance* self, float amount) +{ + self->SetEvolveProgression(amount); +} + +void Perl_QuestItem_SetEvolveAmount(EQ::ItemInstance* self, uint64 amount) +{ + self->SetEvolveCurrentAmount(amount); +} + void perl_register_questitem() { perl::interpreter perl(PERL_GET_THX); auto package = perl.new_class("QuestItem"); + package.add("AddEvolveAmount", &Perl_QuestItem_AddEvolveAmount); package.add("AddEXP", &Perl_QuestItem_AddEXP); package.add("ClearTimers", &Perl_QuestItem_ClearTimers); package.add("Clone", &Perl_QuestItem_Clone); @@ -310,6 +381,7 @@ void perl_register_questitem() package.add("CountAugmentByID", &Perl_QuestItem_CountAugmentByID); package.add("DeleteCustomData", &Perl_QuestItem_DeleteCustomData); package.add("GetAugment", &Perl_QuestItem_GetAugment); + package.add("GetAugmentEvolveUniqueID", &Perl_QuestItem_GetAugmentEvolveUniqueID); package.add("GetAugmentIDs", &Perl_QuestItem_GetAugmentIDs); package.add("GetAugmentItemID", &Perl_QuestItem_GetAugmentItemID); package.add("GetAugmentType", &Perl_QuestItem_GetAugmentType); @@ -317,6 +389,16 @@ void perl_register_questitem() package.add("GetColor", &Perl_QuestItem_GetColor); package.add("GetCustomData", &Perl_QuestItem_GetCustomData); package.add("GetCustomDataString", &Perl_QuestItem_GetCustomDataString); + package.add("GetEvolveActivated", &Perl_QuestItem_GetEvolveActivated); + package.add("GetEvolveAmount", &Perl_QuestItem_GetEvolveAmount); + package.add("GetEvolveCharacterID", &Perl_QuestItem_GetEvolveCharacterID); + package.add("GetEvolveEquipped", &Perl_QuestItem_GetEvolveEquipped); + package.add("GetEvolveFinalItemID", &Perl_QuestItem_GetEvolveFinalItemID); + package.add("GetEvolveItemID", &Perl_QuestItem_GetEvolveItemID); + package.add("GetEvolveLevel", &Perl_QuestItem_GetEvolveLevel); + package.add("GetEvolveLoreID", &Perl_QuestItem_GetEvolveLoreID); + package.add("GetEvolveProgression", &Perl_QuestItem_GetEvolveProgression); + package.add("GetEvolveUniqueID", &Perl_QuestItem_GetEvolveUniqueID); package.add("GetEXP", &Perl_QuestItem_GetEXP); package.add("GetID", &Perl_QuestItem_GetID); package.add("GetItem", (EQ::ItemData*(*)(EQ::ItemInstance*))&Perl_QuestItem_GetItem); @@ -324,7 +406,6 @@ void perl_register_questitem() package.add("GetItemID", &Perl_QuestItem_GetItemID); package.add("GetItemLink", &Perl_QuestItem_GetItemLink); package.add("GetItemScriptID", &Perl_QuestItem_GetItemScriptID); - package.add("GetKillsNeeded", &Perl_QuestItem_GetKillsNeeded); package.add("GetMaxEvolveLevel", &Perl_QuestItem_GetMaxEvolveLevel); package.add("GetName", &Perl_QuestItem_GetName); package.add("GetPrice", &Perl_QuestItem_GetPrice); @@ -337,6 +418,7 @@ void perl_register_questitem() package.add("IsAugmented", &Perl_QuestItem_IsAugmented); package.add("IsEquipable", (bool(*)(EQ::ItemInstance*, int16))&Perl_QuestItem_IsEquipable); package.add("IsEquipable", (bool(*)(EQ::ItemInstance*, uint16, uint16))&Perl_QuestItem_IsEquipable); + package.add("IsEvolving", &Perl_QuestItem_IsEvolving); package.add("IsExpendable", &Perl_QuestItem_IsExpendable); package.add("IsInstanceNoDrop", &Perl_QuestItem_IsInstanceNoDrop); package.add("IsStackable", &Perl_QuestItem_IsStackable); @@ -352,6 +434,8 @@ void perl_register_questitem() package.add("SetCustomData", (void(*)(EQ::ItemInstance*, std::string, float))&Perl_QuestItem_SetCustomData); package.add("SetCustomData", (void(*)(EQ::ItemInstance*, std::string, int))&Perl_QuestItem_SetCustomData); package.add("SetCustomData", (void(*)(EQ::ItemInstance*, std::string, std::string))&Perl_QuestItem_SetCustomData); + package.add("SetEvolveAmount", &Perl_QuestItem_SetEvolveAmount); + package.add("SetEvolveProgression", &Perl_QuestItem_SetEvolveProgression); package.add("SetEXP", &Perl_QuestItem_SetEXP); package.add("SetInstanceNoDrop", &Perl_QuestItem_SetInstanceNoDrop); package.add("SetPrice", &Perl_QuestItem_SetPrice); diff --git a/zone/perl_raids.cpp b/zone/perl_raids.cpp index 6b5668b6f..c01785121 100644 --- a/zone/perl_raids.cpp +++ b/zone/perl_raids.cpp @@ -139,12 +139,12 @@ Client* Perl_Raid_GetMember(Raid* self, int member_index) // @categories Raid bool Perl_Raid_DoesAnyMemberHaveExpeditionLockout(Raid* self, std::string expedition_name, std::string event_name) { - return self->DoesAnyMemberHaveExpeditionLockout(expedition_name, event_name); + return self->AnyMemberHasDzLockout(expedition_name, event_name); } bool Perl_Raid_DoesAnyMemberHaveExpeditionLockout(Raid* self, std::string expedition_name, std::string event_name, int max_check_count) { - return self->DoesAnyMemberHaveExpeditionLockout(expedition_name, event_name, max_check_count); + return self->AnyMemberHasDzLockout(expedition_name, event_name); // max_check_count deprecated } int Perl_Raid_GetGroupNumber(Raid* self, int member_index) { diff --git a/zone/perl_zone.cpp b/zone/perl_zone.cpp new file mode 100644 index 000000000..b81bfe344 --- /dev/null +++ b/zone/perl_zone.cpp @@ -0,0 +1,837 @@ +#include "../common/features.h" +#include "zone.h" + +#ifdef EMBPERL_XS_CLASSES + +#include "../common/global_define.h" +#include "embperl.h" + +bool Perl_Zone_BuffTimersSuspended(Zone* self) +{ + return self->BuffTimersSuspended(); +} + +bool Perl_Zone_BypassesExpansionCheck(Zone* self) +{ + return ZoneStore::Instance()->GetZoneBypassExpansionCheck(self->GetZoneID(), self->GetInstanceVersion()); +} + +bool Perl_Zone_CanBind(Zone* self) +{ + return self->CanBind(); +} + +bool Perl_Zone_CanCastOutdoor(Zone* self) +{ + return self->CanCastOutdoor(); +} + +bool Perl_Zone_CanDoCombat(Zone* self) +{ + return self->CanDoCombat(); +} + +bool Perl_Zone_CanLevitate(Zone* self) +{ + return self->CanLevitate(); +} + +void Perl_Zone_ClearSpawnTimers(Zone* self) +{ + self->ClearSpawnTimers(); +} + +void Perl_Zone_Depop(Zone* self) +{ + self->Depop(); +} + +void Perl_Zone_Depop(Zone* self, bool start_spawn_timers) +{ + self->Depop(start_spawn_timers); +} + +void Perl_Zone_Despawn(Zone* self, uint32 spawngroup_id) +{ + self->Despawn(spawngroup_id); +} + +void Perl_Zone_DisableRespawnTimers(Zone* self) +{ + self->DisableRespawnTimers(); +} + +float Perl_Zone_GetAAEXPModifier(Zone* self, Client* c) +{ + return self->GetAAEXPModifier(c); +} + +float Perl_Zone_GetAAEXPModifierByCharacterID(Zone* self, uint32 character_id) +{ + return self->GetAAEXPModifierByCharacterID(character_id); +} + +std::string Perl_Zone_GetContentFlags(Zone* self) +{ + return ZoneStore::Instance()->GetZoneContentFlags(self->GetZoneID(), self->GetInstanceVersion()); +} + +std::string Perl_Zone_GetContentFlagsDisabled(Zone* self) +{ + return ZoneStore::Instance()->GetZoneContentFlagsDisabled(self->GetZoneID(), self->GetInstanceVersion()); +} + +float Perl_Zone_GetExperienceMultiplier(Zone* self) +{ + return ZoneStore::Instance()->GetZoneExperienceMultiplier(self->GetZoneID(), self->GetInstanceVersion()); +} + +int8 Perl_Zone_GetExpansion(Zone* self) +{ + return ZoneStore::Instance()->GetZoneExpansion(self->GetZoneID(), self->GetInstanceVersion()); +} + +float Perl_Zone_GetEXPModifier(Zone* self, Client* c) +{ + return self->GetEXPModifier(c); +} + +float Perl_Zone_GetEXPModifierByCharacterID(Zone* self, uint32 character_id) +{ + return self->GetEXPModifierByCharacterID(character_id); +} + +int Perl_Zone_GetFastRegenEndurance(Zone* self) +{ + return ZoneStore::Instance()->GetZoneFastRegenEndurance(self->GetZoneID(), self->GetInstanceVersion()); +} + +int Perl_Zone_GetFastRegenHP(Zone* self) +{ + return ZoneStore::Instance()->GetZoneFastRegenHP(self->GetZoneID(), self->GetInstanceVersion()); +} + +int Perl_Zone_GetFastRegenMana(Zone* self) +{ + return ZoneStore::Instance()->GetZoneFastRegenMana(self->GetZoneID(), self->GetInstanceVersion()); +} + +std::string Perl_Zone_GetFileName(Zone* self) +{ + return self->GetFileName(); +} + +std::string Perl_Zone_GetFlagNeeded(Zone* self) +{ + return ZoneStore::Instance()->GetZoneFlagNeeded(self->GetZoneID(), self->GetInstanceVersion()); +} + +uint8 Perl_Zone_GetFogBlue(Zone* self, uint8 slot = 0) +{ + return ZoneStore::Instance()->GetZoneFogBlue(self->GetZoneID(), slot, self->GetInstanceVersion()); +} + +float Perl_Zone_GetFogDensity(Zone* self) +{ + return ZoneStore::Instance()->GetZoneFogDensity(self->GetZoneID(), self->GetInstanceVersion()); +} + +uint8 Perl_Zone_GetFogGreen(Zone* self, uint8 slot = 0) +{ + return ZoneStore::Instance()->GetZoneFogGreen(self->GetZoneID(), slot, self->GetInstanceVersion()); +} + +float Perl_Zone_GetFogMaximumClip(Zone* self, uint8 slot = 0) +{ + return ZoneStore::Instance()->GetZoneFogMaximumClip(self->GetZoneID(), slot, self->GetInstanceVersion()); +} + +float Perl_Zone_GetFogMinimumClip(Zone* self, uint8 slot = 0) +{ + return ZoneStore::Instance()->GetZoneFogMinimumClip(self->GetZoneID(), slot, self->GetInstanceVersion()); +} + +uint8 Perl_Zone_GetFogRed(Zone* self, uint8 slot = 0) +{ + return ZoneStore::Instance()->GetZoneFogRed(self->GetZoneID(), slot, self->GetInstanceVersion()); +} + +float Perl_Zone_GetGraveyardHeading(Zone* self) +{ + return self->GetGraveyardPoint().w; +} + +uint32 Perl_Zone_GetGraveyardID(Zone* self) +{ + return self->graveyard_id(); +} + +float Perl_Zone_GetGraveyardX(Zone* self) +{ + return self->GetGraveyardPoint().x; +} + +float Perl_Zone_GetGraveyardY(Zone* self) +{ + return self->GetGraveyardPoint().y; +} + +float Perl_Zone_GetGraveyardZ(Zone* self) +{ + return self->GetGraveyardPoint().z; +} + +uint32 Perl_Zone_GetGraveyardZoneID(Zone* self) +{ + return self->graveyard_zoneid(); +} + +float Perl_Zone_GetGravity(Zone* self) +{ + return ZoneStore::Instance()->GetZoneGravity(self->GetZoneID(), self->GetInstanceVersion()); +} + +uint32 Perl_Zone_GetInstanceID(Zone* self) +{ + return self->GetInstanceID(); +} + +uint8 Perl_Zone_GetInstanceType(Zone* self) +{ + return ZoneStore::Instance()->GetZoneInstanceType(self->GetZoneID(), self->GetInstanceVersion()); +} + +uint16 Perl_Zone_GetInstanceVersion(Zone* self) +{ + return self->GetInstanceVersion(); +} + +uint32 Perl_Zone_GetInstanceTimeRemaining(Zone* self) +{ + return self->GetInstanceTimeRemaining(); +} + +int Perl_Zone_GetLavaDamage(Zone* self) +{ + return ZoneStore::Instance()->GetZoneLavaDamage(self->GetZoneID(), self->GetInstanceVersion()); +} + +std::string Perl_Zone_GetLongName(Zone* self) +{ + return self->GetLongName(); +} + +float Perl_Zone_GetMaximumClip(Zone* self) +{ + return ZoneStore::Instance()->GetZoneMaximumClip(self->GetZoneID(), self->GetInstanceVersion()); +} + +int8 Perl_Zone_GetMaximumExpansion(Zone* self) +{ + return ZoneStore::Instance()->GetZoneMaximumExpansion(self->GetZoneID(), self->GetInstanceVersion()); +} + +uint8 Perl_Zone_GetMaximumLevel(Zone* self) +{ + return ZoneStore::Instance()->GetZoneMaximumLevel(self->GetZoneID(), self->GetInstanceVersion()); +} + +uint32 Perl_Zone_GetMaxClients(Zone* self) +{ + return self->GetMaxClients(); +} + +float Perl_Zone_GetMinimumClip(Zone* self) +{ + return ZoneStore::Instance()->GetZoneMinimumClip(self->GetZoneID(), self->GetInstanceVersion()); +} + +int8 Perl_Zone_GetMinimumExpansion(Zone* self) +{ + return ZoneStore::Instance()->GetZoneMinimumExpansion(self->GetZoneID(), self->GetInstanceVersion()); +} + +uint8 Perl_Zone_GetMinimumLevel(Zone* self) +{ + return ZoneStore::Instance()->GetZoneMinimumLevel(self->GetZoneID(), self->GetInstanceVersion()); +} + +int Perl_Zone_GetMinimumLavaDamage(Zone* self) +{ + return ZoneStore::Instance()->GetZoneMinimumLavaDamage(self->GetZoneID(), self->GetInstanceVersion()); +} + +uint8 Perl_Zone_GetMinimumStatus(Zone* self) +{ + return ZoneStore::Instance()->GetZoneMinimumStatus(self->GetZoneID(), self->GetInstanceVersion()); +} + +std::string Perl_Zone_GetNote(Zone* self) +{ + return ZoneStore::Instance()->GetZoneNote(self->GetZoneID(), self->GetInstanceVersion()); +} + +int Perl_Zone_GetNPCMaximumAggroDistance(Zone* self) +{ + return ZoneStore::Instance()->GetZoneNPCMaximumAggroDistance(self->GetZoneID(), self->GetInstanceVersion()); +} + +int8 Perl_Zone_GetPEQZone(Zone* self) +{ + return ZoneStore::Instance()->GetZonePEQZone(self->GetZoneID(), self->GetInstanceVersion()); +} + +int Perl_Zone_GetRainChance(Zone* self, uint8 slot = 0) +{ + return ZoneStore::Instance()->GetZoneRainChance(self->GetZoneID(), slot, self->GetInstanceVersion()); +} + +int Perl_Zone_GetRainDuration(Zone* self, uint8 slot = 0) +{ + return ZoneStore::Instance()->GetZoneRainDuration(self->GetZoneID(), slot, self->GetInstanceVersion()); +} + +uint32 Perl_Zone_GetRuleSet(Zone* self) +{ + return ZoneStore::Instance()->GetZoneRuleSet(self->GetZoneID(), self->GetInstanceVersion()); +} + +float Perl_Zone_GetSafeHeading(Zone* self) +{ + return self->GetSafePoint().w; +} + +float Perl_Zone_GetSafeX(Zone* self) +{ + return self->GetSafePoint().x; +} + +float Perl_Zone_GetSafeY(Zone* self) +{ + return self->GetSafePoint().y; +} + +float Perl_Zone_GetSafeZ(Zone* self) +{ + return self->GetSafePoint().z; +} + +std::string Perl_Zone_GetShortName(Zone* self) +{ + return self->GetShortName(); +} + +uint32 Perl_Zone_GetSecondsBeforeIdle(Zone* self) +{ + return self->GetSecondsBeforeIdle(); +} + +uint64 Perl_Zone_GetShutdownDelay(Zone* self) +{ + return ZoneStore::Instance()->GetZoneShutdownDelay(self->GetZoneID(), self->GetInstanceVersion()); +} + +uint8 Perl_Zone_GetSky(Zone* self) +{ + return ZoneStore::Instance()->GetZoneSky(self->GetZoneID(), self->GetInstanceVersion()); +} + +int8 Perl_Zone_GetSkyLock(Zone* self) +{ + return ZoneStore::Instance()->GetZoneSkyLock(self->GetZoneID(), self->GetInstanceVersion()); +} + +int Perl_Zone_GetSnowChance(Zone* self, uint8 slot = 0) +{ + return ZoneStore::Instance()->GetZoneSnowChance(self->GetZoneID(), slot, self->GetInstanceVersion()); +} + +int Perl_Zone_GetSnowDuration(Zone* self, uint8 slot = 0) +{ + return ZoneStore::Instance()->GetZoneSnowDuration(self->GetZoneID(), slot, self->GetInstanceVersion()); +} + +uint8 Perl_Zone_GetTimeType(Zone* self) +{ + return ZoneStore::Instance()->GetZoneTimeType(self->GetZoneID(), self->GetInstanceVersion()); +} + +int Perl_Zone_GetTimeZone(Zone* self) +{ + return ZoneStore::Instance()->GetZoneTimeZone(self->GetZoneID(), self->GetInstanceVersion()); +} + +std::string Perl_Zone_GetZoneDescription(Zone* self) +{ + return self->GetZoneDescription(); +} + +uint32 Perl_Zone_GetZoneID(Zone* self) +{ + return self->GetZoneID(); +} + +uint8 Perl_Zone_GetZoneType(Zone* self) +{ + return self->GetZoneType(); +} + +float Perl_Zone_GetUnderworld(Zone* self) +{ + return ZoneStore::Instance()->GetZoneUnderworld(self->GetZoneID(), self->GetInstanceVersion()); +} + +int Perl_Zone_GetUnderworldTeleportIndex(Zone* self) +{ + return ZoneStore::Instance()->GetZoneUnderworldTeleportIndex(self->GetZoneID(), self->GetInstanceVersion()); +} + +float Perl_Zone_GetWalkSpeed(Zone* self) +{ + return ZoneStore::Instance()->GetZoneWalkSpeed(self->GetZoneID(), self->GetInstanceVersion()); +} + +uint8 Perl_Zone_GetZoneZType(Zone* self) +{ + return ZoneStore::Instance()->GetZoneZType(self->GetZoneID(), self->GetInstanceVersion()); +} + +int Perl_Zone_GetZoneTotalBlockedSpells(Zone* self) +{ + return self->GetZoneTotalBlockedSpells(); +} + +bool Perl_Zone_HasGraveyard(Zone* self) +{ + return self->HasGraveyard(); +} + +bool Perl_Zone_HasMap(Zone* self) +{ + return self->HasMap(); +} + +bool Perl_Zone_HasWaterMap(Zone* self) +{ + return self->HasWaterMap(); +} + +bool Perl_Zone_HasWeather(Zone* self) +{ + return self->HasWeather(); +} + +bool Perl_Zone_IsCity(Zone* self) +{ + return self->IsCity(); +} + +bool Perl_Zone_IsHotzone(Zone* self) +{ + return self->IsHotzone(); +} + +bool Perl_Zone_IsInstancePersistent(Zone* self) +{ + return self->IsInstancePersistent(); +} + +bool Perl_Zone_IsIdleWhenEmpty(Zone* self) +{ + return self->IsIdleWhenEmpty(); +} + +bool Perl_Zone_IsPVPZone(Zone* self) +{ + return self->IsPVPZone(); +} + +bool Perl_Zone_IsRaining(Zone* self) +{ + return self->IsRaining(); +} + +bool Perl_Zone_IsSnowing(Zone* self) +{ + return self->IsSnowing(); +} + +bool Perl_Zone_IsSpecialBindLocation(Zone* self, float x, float y, float z, float heading) +{ + return self->IsSpecialBindLocation(glm::vec4(x, y, z, heading)); +} + +bool Perl_Zone_IsSpellBlocked(Zone* self, uint32 spell_id, float x, float y, float z) +{ + return self->IsSpellBlocked(spell_id, glm::vec3(x, y, z)); +} + +bool Perl_Zone_IsStaticZone(Zone* self) +{ + return self->IsStaticZone(); +} + +bool Perl_Zone_IsUCSServerAvailable(Zone* self) +{ + return self->IsUCSServerAvailable(); +} + +bool Perl_Zone_IsWaterZone(Zone* self, float z) +{ + return self->IsWaterZone(z); +} + +void Perl_Zone_Repop(Zone* self) +{ + self->Repop(); +} + +void Perl_Zone_Repop(Zone* self, bool is_forced) +{ + self->Repop(is_forced); +} + +void Perl_Zone_SetAAEXPModifier(Zone* self, Client* c, float aa_modifier) +{ + self->SetAAEXPModifier(c, aa_modifier); +} + +void Perl_Zone_SetAAEXPModifierByCharacterID(Zone* self, uint32 character_id, float aa_modifier) +{ + self->SetAAEXPModifierByCharacterID(character_id, aa_modifier); +} + +void Perl_Zone_SetEXPModifier(Zone* self, Client* c, float exp_modifier) +{ + self->SetAAEXPModifier(c, exp_modifier); +} + +void Perl_Zone_SetEXPModifierByCharacterID(Zone* self, uint32 character_id, float exp_modifier) +{ + self->SetAAEXPModifierByCharacterID(character_id, exp_modifier); +} + +void Perl_Zone_SetInstanceTimer(Zone* self, uint32 new_duration) +{ + self->SetInstanceTimer(new_duration); +} + +void Perl_Zone_SetInstanceTimeRemaining(Zone* self, uint32 time_remaining) +{ + self->SetInstanceTimeRemaining(time_remaining); +} + +void Perl_Zone_SetIsHotzone(Zone* self, bool is_hotzone) +{ + self->SetIsHotzone(is_hotzone); +} + +void Perl_Zone_ShowZoneGlobalLoot(Zone* self, Client* c) +{ + self->ShowZoneGlobalLoot(c); +} + +void Perl_Zone_SetBucket(Zone* self, const std::string bucket_name, const std::string bucket_value) +{ + self->SetBucket(bucket_name, bucket_value); +} + +void Perl_Zone_SetBucket(Zone* self, const std::string bucket_name, const std::string bucket_value, const std::string expiration) +{ + self->SetBucket(bucket_name, bucket_value, expiration); +} + +void Perl_Zone_DeleteBucket(Zone* self, const std::string bucket_name) +{ + self->DeleteBucket(bucket_name); +} + +std::string Perl_Zone_GetBucket(Zone* self, const std::string bucket_name) +{ + return self->GetBucket(bucket_name); +} + +std::string Perl_Zone_GetBucketExpires(Zone* self, const std::string bucket_name) +{ + return self->GetBucketExpires(bucket_name); +} + +std::string Perl_Zone_GetBucketRemaining(Zone* self, const std::string bucket_name) +{ + return self->GetBucketRemaining(bucket_name); +} + +bool Perl_Zone_ClearVariables(Zone* self) +{ + return self->ClearVariables(); +} + +bool Perl_Zone_DeleteVariable(Zone* self, const std::string variable_name) +{ + return self->DeleteVariable(variable_name); +} + +std::string Perl_Zone_GetVariable(Zone* self, const std::string variable_name) +{ + return self->GetVariable(variable_name); +} + +perl::array Perl_Zone_GetVariables(Zone* self) +{ + perl::array a; + + const auto& l = self->GetVariables(); + for (const auto& v : l) { + a.push_back(v); + } + + return a; +} + +void Perl_Zone_SetVariable(Zone* self, const std::string variable_name, const std::string variable_value) +{ + self->SetVariable(variable_name, variable_value); +} + +bool Perl_Zone_VariableExists(Zone* self, const std::string variable_name) +{ + return self->VariableExists(variable_name); +} + +uint32 Perl_Zone_GetTimerDuration(Zone* self, std::string name) +{ + return self->GetTimerDuration(name); +} + +uint32 Perl_Zone_GetTimerRemainingTime(Zone* self, std::string name) +{ + return self->GetTimerRemainingTime(name); +} + +bool Perl_Zone_HasTimer(Zone* self, std::string name) +{ + return self->HasTimer(name); +} + +bool Perl_Zone_IsPausedTimer(Zone* self, std::string name) +{ + return self->IsPausedTimer(name); +} + +void Perl_Zone_PauseTimer(Zone* self, std::string name) +{ + self->PauseTimer(name); +} + +void Perl_Zone_ResumeTimer(Zone* self, std::string name) +{ + self->ResumeTimer(name); +} + +void Perl_Zone_SetTimer(Zone* self, std::string name, uint32 duration) +{ + self->SetTimer(name, duration); +} + +void Perl_Zone_StopTimer(Zone* self, std::string name) +{ + self->StopTimer(name); +} + +void Perl_Zone_StopAllTimers(Zone* self) +{ + self->StopAllTimers(); +} + +void Perl_Zone_SendPayload(Zone* self, int payload_id, std::string payload_value) +{ + self->SendPayload(payload_id, payload_value); +} + +void Perl_Zone_Signal(Zone* self, int signal_id) +{ + self->Signal(signal_id); +} + +perl::array Perl_Zone_GetPausedTimers(Zone* self) +{ + perl::array a; + + const auto& l = self->GetPausedTimers(); + + if (!l.empty()) { + a.reserve(l.size()); + + for (const auto& v : l) { + a.push_back(v); + } + } + + return a; +} + +perl::array Perl_Zone_GetTimers(Zone* self) +{ + perl::array a; + + const auto& l = self->GetTimers(); + + if (!l.empty()) { + a.reserve(l.size()); + + for (const auto& v : l) { + a.push_back(v); + } + } + + return a; +} + +void perl_register_zone() +{ + perl::interpreter perl(PERL_GET_THX); + + auto package = perl.new_class("Zone"); + package.add("BuffTimersSuspended", &Perl_Zone_BuffTimersSuspended); + package.add("BypassesExpansionCheck", &Perl_Zone_BypassesExpansionCheck); + package.add("CanBind", &Perl_Zone_CanBind); + package.add("CanCastOutdoor", &Perl_Zone_CanCastOutdoor); + package.add("CanDoCombat", &Perl_Zone_CanDoCombat); + package.add("CanLevitate", &Perl_Zone_CanLevitate); + package.add("ClearSpawnTimers", &Perl_Zone_ClearSpawnTimers); + package.add("ClearVariables", &Perl_Zone_ClearVariables); + package.add("DeleteBucket", &Perl_Zone_DeleteBucket); + package.add("DeleteVariable", &Perl_Zone_DeleteVariable); + package.add("Depop", (void(*)(Zone*))&Perl_Zone_Depop); + package.add("Depop", (void(*)(Zone*, bool))&Perl_Zone_Depop); + package.add("Despawn", &Perl_Zone_Despawn); + package.add("DisableRespawnTimers", &Perl_Zone_DisableRespawnTimers); + package.add("GetAAEXPModifier", &Perl_Zone_GetAAEXPModifier); + package.add("GetAAEXPModifierByCharacterID", &Perl_Zone_GetAAEXPModifierByCharacterID); + package.add("GetBucket", &Perl_Zone_GetBucket); + package.add("GetBucketExpires", &Perl_Zone_GetBucketExpires); + package.add("GetBucketRemaining", &Perl_Zone_GetBucketRemaining); + package.add("GetContentFlags", &Perl_Zone_GetContentFlags); + package.add("GetContentFlagsDisabled", &Perl_Zone_GetContentFlagsDisabled); + package.add("GetExperienceMultiplier", &Perl_Zone_GetExperienceMultiplier); + package.add("GetExpansion", &Perl_Zone_GetExpansion); + package.add("GetEXPModifier", &Perl_Zone_GetEXPModifier); + package.add("GetEXPModifierByCharacterID", &Perl_Zone_GetEXPModifierByCharacterID); + package.add("GetFastRegenEndurance", &Perl_Zone_GetFastRegenEndurance); + package.add("GetFastRegenHP", &Perl_Zone_GetFastRegenHP); + package.add("GetFastRegenMana", &Perl_Zone_GetFastRegenMana); + package.add("GetFileName", &Perl_Zone_GetFileName); + package.add("GetFlagNeeded", &Perl_Zone_GetFlagNeeded); + package.add("GetFogBlue", (uint8(*)(Zone*))&Perl_Zone_GetFogBlue); + package.add("GetFogBlue", (uint8(*)(Zone*, uint8))&Perl_Zone_GetFogBlue); + package.add("GetFogDensity", &Perl_Zone_GetFogDensity); + package.add("GetFogGreen", (uint8(*)(Zone*))&Perl_Zone_GetFogGreen); + package.add("GetFogGreen", (uint8(*)(Zone*, uint8))&Perl_Zone_GetFogGreen); + package.add("GetFogMaximumClip", (float(*)(Zone*))&Perl_Zone_GetFogMaximumClip); + package.add("GetFogMaximumClip", (float(*)(Zone*, uint8))&Perl_Zone_GetFogMaximumClip); + package.add("GetFogMinimumClip", (float(*)(Zone*))&Perl_Zone_GetFogMinimumClip); + package.add("GetFogMinimumClip", (float(*)(Zone*, uint8))&Perl_Zone_GetFogMinimumClip); + package.add("GetFogRed", (uint8(*)(Zone*))&Perl_Zone_GetFogRed); + package.add("GetFogRed", (uint8(*)(Zone*, uint8))&Perl_Zone_GetFogRed); + package.add("GetGraveyardHeading", &Perl_Zone_GetGraveyardHeading); + package.add("GetGraveyardID", &Perl_Zone_GetGraveyardID); + package.add("GetGraveyardX", &Perl_Zone_GetGraveyardX); + package.add("GetGraveyardY", &Perl_Zone_GetGraveyardY); + package.add("GetGraveyardZ", &Perl_Zone_GetGraveyardZ); + package.add("GetGraveyardZoneID", &Perl_Zone_GetGraveyardZoneID); + package.add("GetGravity", &Perl_Zone_GetGravity); + package.add("GetInstanceID", &Perl_Zone_GetInstanceID); + package.add("GetInstanceType", &Perl_Zone_GetInstanceType); + package.add("GetInstanceVersion", &Perl_Zone_GetInstanceVersion); + package.add("GetInstanceTimeRemaining", &Perl_Zone_GetInstanceTimeRemaining); + package.add("GetLavaDamage", &Perl_Zone_GetLavaDamage); + package.add("GetLongName", &Perl_Zone_GetLongName); + package.add("GetMaximumClip", &Perl_Zone_GetMaximumClip); + package.add("GetMaximumExpansion", &Perl_Zone_GetMaximumExpansion); + package.add("GetMaximumLevel", &Perl_Zone_GetMaximumLevel); + package.add("GetMaxClients", &Perl_Zone_GetMaxClients); + package.add("GetMinimumClip", &Perl_Zone_GetMinimumClip); + package.add("GetMinimumExpansion", &Perl_Zone_GetMinimumExpansion); + package.add("GetMinimumLevel", &Perl_Zone_GetMinimumLevel); + package.add("GetMinimumLavaDamage", &Perl_Zone_GetMinimumLavaDamage); + package.add("GetMinimumStatus", &Perl_Zone_GetMinimumStatus); + package.add("GetNote", &Perl_Zone_GetNote); + package.add("GetNPCMaximumAggroDistance", &Perl_Zone_GetNPCMaximumAggroDistance); + package.add("GetPausedTimers", &Perl_Zone_GetPausedTimers); + package.add("GetPEQZone", &Perl_Zone_GetPEQZone); + package.add("GetRainChance", (int(*)(Zone*))&Perl_Zone_GetRainChance); + package.add("GetRainChance", (int(*)(Zone*, uint8))&Perl_Zone_GetRainChance); + package.add("GetRainDuration", (int(*)(Zone*))&Perl_Zone_GetRainDuration); + package.add("GetRainDuration", (int(*)(Zone*, uint8))&Perl_Zone_GetRainDuration); + package.add("GetRuleSet", &Perl_Zone_GetRuleSet); + package.add("GetSafeHeading", &Perl_Zone_GetSafeHeading); + package.add("GetSafeX", &Perl_Zone_GetSafeX); + package.add("GetSafeY", &Perl_Zone_GetSafeY); + package.add("GetSafeZ", &Perl_Zone_GetSafeZ); + package.add("GetShortName", &Perl_Zone_GetShortName); + package.add("GetSecondsBeforeIdle", &Perl_Zone_GetSecondsBeforeIdle); + package.add("GetShutdownDelay", &Perl_Zone_GetShutdownDelay); + package.add("GetSky", &Perl_Zone_GetSky); + package.add("GetSkyLock", &Perl_Zone_GetSkyLock); + package.add("GetSnowChance", (int(*)(Zone*))&Perl_Zone_GetSnowChance); + package.add("GetSnowChance", (int(*)(Zone*, uint8))&Perl_Zone_GetSnowChance); + package.add("GetSnowDuration", (int(*)(Zone*))&Perl_Zone_GetSnowDuration); + package.add("GetSnowDuration", (int(*)(Zone*, uint8))&Perl_Zone_GetSnowDuration); + package.add("GetTimeType", &Perl_Zone_GetTimeType); + package.add("GetTimeZone", &Perl_Zone_GetTimeZone); + package.add("GetTimerDuration", &Perl_Zone_GetTimerDuration); + package.add("GetTimerRemainingTime", &Perl_Zone_GetTimerRemainingTime); + package.add("GetTimers", &Perl_Zone_GetTimers); + package.add("GetZoneDescription", &Perl_Zone_GetZoneDescription); + package.add("GetZoneID", &Perl_Zone_GetZoneID); + package.add("GetZoneType", &Perl_Zone_GetZoneType); + package.add("GetUnderworld", &Perl_Zone_GetUnderworld); + package.add("GetUnderworldTeleportIndex", &Perl_Zone_GetUnderworldTeleportIndex); + package.add("GetVariable", &Perl_Zone_GetVariable); + package.add("GetVariables", &Perl_Zone_GetVariables); + package.add("GetWalkSpeed", &Perl_Zone_GetWalkSpeed); + package.add("GetZoneZType", &Perl_Zone_GetZoneZType); + package.add("GetZoneTotalBlockedSpells", &Perl_Zone_GetZoneTotalBlockedSpells); + package.add("HasGraveyard", &Perl_Zone_HasGraveyard); + package.add("HasMap", &Perl_Zone_HasMap); + package.add("HasTimer", &Perl_Zone_HasTimer); + package.add("HasWaterMap", &Perl_Zone_HasWaterMap); + package.add("HasWeather", &Perl_Zone_HasWeather); + package.add("IsCity", &Perl_Zone_IsCity); + package.add("IsHotzone", &Perl_Zone_IsHotzone); + package.add("IsInstancePersistent", &Perl_Zone_IsInstancePersistent); + package.add("IsIdleWhenEmpty", &Perl_Zone_IsIdleWhenEmpty); + package.add("IsPausedTimer", &Perl_Zone_IsPausedTimer); + package.add("IsPVPZone", &Perl_Zone_IsPVPZone); + package.add("IsRaining", &Perl_Zone_IsRaining); + package.add("IsSnowing", &Perl_Zone_IsSnowing); + package.add("IsSpecialBindLocation", &Perl_Zone_IsSpecialBindLocation); + package.add("IsSpellBlocked", &Perl_Zone_IsSpellBlocked); + package.add("IsStaticZone", &Perl_Zone_IsStaticZone); + package.add("IsUCSServerAvailable", &Perl_Zone_IsUCSServerAvailable); + package.add("IsWaterZone", &Perl_Zone_IsWaterZone); + package.add("PauseTimer", &Perl_Zone_PauseTimer); + package.add("Repop", (void(*)(Zone*))&Perl_Zone_Repop); + package.add("Repop", (void(*)(Zone*, bool))&Perl_Zone_Repop); + package.add("ResumeTimer", &Perl_Zone_ResumeTimer); + package.add("SendPayload", &Perl_Zone_SendPayload); + package.add("SetAAEXPModifier", &Perl_Zone_SetAAEXPModifier); + package.add("SetAAEXPModifierByCharacterID", &Perl_Zone_SetAAEXPModifierByCharacterID); + package.add("SetBucket", (void(*)(Zone*, const std::string, const std::string))&Perl_Zone_SetBucket); + package.add("SetBucket", (void(*)(Zone*, const std::string, const std::string, const std::string))&Perl_Zone_SetBucket); + package.add("SetEXPModifier", &Perl_Zone_SetEXPModifier); + package.add("SetEXPModifierByCharacterID", &Perl_Zone_SetEXPModifierByCharacterID); + package.add("SetInstanceTimer", &Perl_Zone_SetInstanceTimer); + package.add("SetInstanceTimeRemaining", &Perl_Zone_SetInstanceTimeRemaining); + package.add("SetIsHotzone", &Perl_Zone_SetIsHotzone); + package.add("SetTimer", &Perl_Zone_SetTimer); + package.add("SetVariable", &Perl_Zone_SetVariable); + package.add("Signal", &Perl_Zone_Signal); + package.add("StopTimer", &Perl_Zone_StopTimer); + package.add("StopAllTimers", &Perl_Zone_StopAllTimers); + package.add("ShowZoneGlobalLoot", &Perl_Zone_ShowZoneGlobalLoot); + package.add("VariableExists", &Perl_Zone_VariableExists); +} + +#endif //EMBPERL_XS_CLASSES + diff --git a/zone/petitions.cpp b/zone/petitions.cpp index 1f4dd8ff7..2790a0df8 100644 --- a/zone/petitions.cpp +++ b/zone/petitions.cpp @@ -38,8 +38,6 @@ Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org) #include "petitions.h" #include "worldserver.h" -PetitionList petition_list; - extern WorldServer worldserver; void Petition::SendPetitionToPlayer(Client* clientto) { @@ -288,7 +286,7 @@ void ZoneDatabase::RefreshPetitionsFromDB() newpet->SetCheckedOut(true); else newpet->SetCheckedOut(false); - petition_list.AddPetition(newpet); + PetitionList::Instance()->AddPetition(newpet); } } diff --git a/zone/petitions.h b/zone/petitions.h index b3a0dc5fd..20ddf8137 100644 --- a/zone/petitions.h +++ b/zone/petitions.h @@ -112,6 +112,12 @@ public: bool FindPetitionByAccountName(const char* acctname); bool DeletePetitionByCharName(const char* charname); + static PetitionList* Instance() + { + static PetitionList instance; + return &instance; + } + private: LinkedList list; Mutex PList_Mutex; diff --git a/zone/pets.cpp b/zone/pets.cpp index 08fc523c1..fea4c7f7b 100644 --- a/zone/pets.cpp +++ b/zone/pets.cpp @@ -22,6 +22,7 @@ #include "../common/repositories/pets_repository.h" #include "../common/repositories/pets_beastlord_data_repository.h" +#include "../common/repositories/character_pet_name_repository.h" #include "entity.h" #include "client.h" @@ -164,6 +165,15 @@ void Mob::MakePoweredPet(uint16 spell_id, const char* pettype, int16 petpower, // 4 - Keep DB name // 5 - `s ward + const auto vanity_name = (IsClient() && !petname) ? CharacterPetNameRepository::FindOne(database, CastToClient()->CharacterID()) : CharacterPetNameRepository::CharacterPetName{}; + + if ( + IsClient() && + !petname && + !vanity_name.name.empty() + ) { + petname = vanity_name.name.c_str(); + } if (petname != nullptr) { // Name was provided, use it. @@ -257,7 +267,7 @@ void Mob::MakePoweredPet(uint16 spell_id, const char* pettype, int16 petpower, } //this takes ownership of the npc_type data - auto npc = new Pet(npc_type, this, (PetType)record.petcontrol, spell_id, record.petpower); + auto npc = new Pet(npc_type, this, record.petcontrol, spell_id, record.petpower); // Now that we have an actual object to interact with, load // the base items for the pet. These are always loaded @@ -285,7 +295,7 @@ void Mob::MakePoweredPet(uint16 spell_id, const char* pettype, int16 petpower, SetPetID(npc->GetID()); // We need to handle PetType 5 (petHatelist), add the current target to the hatelist of the pet - if (record.petcontrol == petTargetLock) + if (record.petcontrol == PetType::TargetLock) { Mob* m_target = GetTarget(); @@ -331,7 +341,7 @@ void NPC::TryDepopTargetLockedPets(Mob* current_target) { return; } //Use when pets are given petype 5 - if (IsPet() && GetPetType() == petTargetLock && GetPetTargetLockID()) { + if (IsPet() && GetPetType() == PetType::TargetLock && GetPetTargetLockID()) { CastSpell(SPELL_UNSUMMON_SELF, GetID()); //Live like behavior, damages self for 20K if (!HasDied()) { Kill(); //Ensure pet dies if over 20k HP. @@ -346,11 +356,11 @@ void NPC::TryDepopTargetLockedPets(Mob* current_target) { /* This is why the pets ghost - pets were being spawned too far away from its npc owner and some into walls or objects (+10), this sometimes creates the "ghost" effect. I changed to +2 (as close as I could get while it still looked good). I also noticed this can happen if an NPC is spawned on the same spot of another or in a related bad spot.*/ -Pet::Pet(NPCType *type_data, Mob *owner, PetType type, uint16 spell_id, int16 power) +Pet::Pet(NPCType *type_data, Mob *owner, uint8 pet_type, uint16 spell_id, int16 power) : NPC(type_data, 0, owner->GetPosition() + glm::vec4(2.0f, 2.0f, 0.0f, 0.0f), GravityBehavior::Water) { GiveNPCTypeData(type_data); - SetPetType(type); + SetPetType(pet_type); SetPetPower(power); SetOwnerID(owner ? owner->GetID() : 0); SetPetSpellID(spell_id); @@ -364,8 +374,8 @@ Pet::Pet(NPCType *type_data, Mob *owner, PetType type, uint16 spell_id, int16 po if (owner && owner->IsClient()) { if (!(owner->CastToClient()->ClientVersionBit() & EQ::versions::maskUFAndLater)) { if ( - (GetPetType() != petFamiliar && GetPetType() != petAnimation) || - aabonuses.PetCommands[PET_TAUNT] + (GetPetType() != PetType::Familiar && GetPetType() != PetType::Animation) || + aabonuses.PetCommands[PetCommand::Taunt] ) { SetTaunting(true); } @@ -537,22 +547,22 @@ void NPC::SetPetState(SpellBuff_Struct *pet_buffs, uint32 *items) { if (buffs[j1].spellid <= (uint32)SPDAT_RECORDS) { for (int x1=0; x1 < EFFECT_COUNT; x1++) { switch (spells[buffs[j1].spellid].effect_id[x1]) { - case SE_AddMeleeProc: - case SE_WeaponProc: + case SpellEffect::AddMeleeProc: + case SpellEffect::WeaponProc: // We need to reapply buff based procs // We need to do this here so suspended pets also regain their procs. AddProcToWeapon(GetProcID(buffs[j1].spellid,x1), false, 100+spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid, buffs[j1].casterlevel, GetSpellProcLimitTimer(buffs[j1].spellid, ProcType::MELEE_PROC)); break; - case SE_DefensiveProc: + case SpellEffect::DefensiveProc: AddDefensiveProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid, GetSpellProcLimitTimer(buffs[j1].spellid, ProcType::DEFENSIVE_PROC)); break; - case SE_RangedProc: + case SpellEffect::RangedProc: AddRangedProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid, GetSpellProcLimitTimer(buffs[j1].spellid, ProcType::RANGED_PROC)); break; - case SE_Charm: - case SE_Rune: - case SE_NegateAttacks: - case SE_Illusion: + case SpellEffect::Charm: + case SpellEffect::Rune: + case SpellEffect::NegateAttacks: + case SpellEffect::Illusion: buffs[j1].spellid = SPELL_UNKNOWN; pet_buffs[j1].spellid = SPELLBOOK_UNKNOWN; pet_buffs[j1].effect_type = 0; @@ -577,7 +587,7 @@ void NPC::SetPetState(SpellBuff_Struct *pet_buffs, uint32 *items) { if (item2) { bool noDrop = (item2->NoDrop == 0); // Field is reverse logic - bool petCanHaveNoDrop = (RuleB(Pets, CanTakeNoDrop) && _CLIENTPET(this) && GetPetType() <= petOther); + bool petCanHaveNoDrop = (RuleB(Pets, CanTakeNoDrop) && _CLIENTPET(this) && GetPetType() <= PetType::Normal); if (!noDrop || petCanHaveNoDrop) { AddLootDrop(item2, LootdropEntriesRepository::NewNpcEntity(), true); diff --git a/zone/pets.h b/zone/pets.h index f8ab50eca..dc656fb08 100644 --- a/zone/pets.h +++ b/zone/pets.h @@ -6,7 +6,7 @@ struct NPCType; class Pet : public NPC { public: - Pet(NPCType *type_data, Mob *owner, PetType type, uint16 spell_id, int16 power); + Pet(NPCType *type_data, Mob *owner, uint8 pet_type, uint16 spell_id, int16 power); virtual bool CheckSpellLevelRestriction(Mob *caster, uint16 spell_id); }; diff --git a/zone/position.cpp b/zone/position.cpp index 52062120e..39cb5b6d4 100644 --- a/zone/position.cpp +++ b/zone/position.cpp @@ -6,6 +6,7 @@ #include "../common/data_verification.h" #include #include "../common/types.h" +#include constexpr float position_eps = 0.0001f; diff --git a/zone/queryserv.cpp b/zone/queryserv.cpp index de26c6be1..5a5200d5f 100644 --- a/zone/queryserv.cpp +++ b/zone/queryserv.cpp @@ -1,30 +1,13 @@ -/* EQEMu: Everquest Server Emulator -Copyright (C) 2001-2014 EQEMu Development Team (http://eqemulator.net) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; version 2 of the License. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY except by those people which sell it, which - are required to give you total support for your newly bought product; - without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - #include "../common/global_define.h" #include "../common/servertalk.h" #include "../common/strings.h" +#include "../common/events/player_event_logs.h" #include "queryserv.h" #include "worldserver.h" extern WorldServer worldserver; -extern QueryServ *QServ; +extern QueryServ* QServ; QueryServ::QueryServ() { @@ -38,17 +21,101 @@ void QueryServ::SendQuery(std::string Query) { auto pack = new ServerPacket(ServerOP_QSSendQuery, Query.length() + 5); pack->WriteUInt32(Query.length()); /* Pack Query String Size so it can be dynamically broken out at queryserv */ - pack->WriteString(Query.c_str()); /* Query */ + pack->WriteString(Query.c_str()); /* Query */ worldserver.SendPacket(pack); safe_delete(pack); } -void QueryServ::PlayerLogEvent(int Event_Type, int Character_ID, std::string Event_Desc) +void QueryServ::Connect() { - std::string query = StringFormat( - "INSERT INTO `qs_player_events` (event, char_id, event_desc, time) VALUES (%i, %i, '%s', UNIX_TIMESTAMP(now()))", - Event_Type, - Character_ID, - Strings::Escape(Event_Desc).c_str()); - SendQuery(query); + m_connection = std::make_unique( + Config->QSHost, + Config->QSPort, + false, + "Zone", + Config->SharedKey + ); + m_connection->OnMessage(std::bind(&QueryServ::HandleMessage, this, std::placeholders::_1, std::placeholders::_2)); + m_connection->OnConnect([this](EQ::Net::ServertalkClient* client) + { + m_is_qs_connected = true; + LogInfo( + "Query Server connection established to [{}] [{}]", + client->Handle()->RemoteIP(), + client->Handle()->RemotePort() + ); + }); + + LogInfo( + "New Query Server connection to [{}:{}]", + Config->QSHost, + Config->QSPort + ); +} + +bool QueryServ::SendPacket(ServerPacket* pack) +{ + if (m_connection.get() == nullptr) { + Connect(); + } + + if (!m_connection.get()) { + return false; + } + + if (m_is_qs_connected) { + m_connection->SendPacket(pack); + return true; + } + + return false; +} + +void QueryServ::HandleMessage(uint16 opcode, const EQ::Net::Packet& p) +{ + ServerPacket tpack(opcode, p); + auto pack = &tpack; + + switch (opcode) { + case ServerOP_SendPlayerEventSettings: + { + if (pack->size < sizeof(ServerSendPlayerEvent_Struct)) { + LogError("ServerOP_SendPlayerEventSettings: packet too small"); + break; + } + + auto* data = reinterpret_cast(pack->pBuffer); + const uint32_t expected_size = sizeof(ServerSendPlayerEvent_Struct) + data->cereal_size; + + if (pack->size < expected_size) { + LogError( + "ServerOP_SendPlayerEventSettings: packet size mismatch, expected {}, got {}", expected_size, + pack->size + ); + break; + } + + std::vector settings; + + try { + EQ::Util::MemoryStreamReader ms(const_cast(data->cereal_data), data->cereal_size); + cereal::BinaryInputArchive ar(ms); + ar(settings); + } + catch (const std::exception& ex) { + LogError("Failed to deserialize PlayerEventLogSettings: {}", ex.what()); + break; + } + + PlayerEventLogs::Instance()->LoadPlayerEventSettingsFromQS(settings); + LogInfo("Loaded {} PlayerEventLogSettings from queryserv", settings.size()); + break; + } + + default: + { + LogInfo("Unknown ServerOP Received [{}]", opcode); + break; + } + } } diff --git a/zone/queryserv.h b/zone/queryserv.h index 69392037c..f1a100c31 100644 --- a/zone/queryserv.h +++ b/zone/queryserv.h @@ -1,37 +1,62 @@ #ifndef QUERYSERV_ZONE_H #define QUERYSERV_ZONE_H +#include "../common/net/servertalk_server.h" +#include "../common/net/servertalk_client_connection.h" +#include "../common/event/timer.h" +#include "../common/rulesys.h" +#include "../common/eqemu_logsys.h" -/* - enum PlayerGenericLogEventTypes - These Enums are for the generic logging table that are not complex and require more advanced logic -*/ +class QueryServ { +public: + QueryServ(); + ~QueryServ(); + void SendQuery(std::string Query); + void Connect(); + inline void Disconnect() { + if (m_connection) { + m_connection->Handle()->Disconnect(); + m_connection.reset(); + } + } + bool SendPacket(ServerPacket *pack); + void HandleMessage(uint16 opcode, const EQ::Net::Packet &p); -enum PlayerGenericLogEventTypes { - Player_Log_Quest = 1, - Player_Log_Zoning, - Player_Log_Deaths, - Player_Log_Connect_State, - Player_Log_Levels, - Player_Log_Keyring_Addition, - Player_Log_QGlobal_Update, - Player_Log_Task_Updates, - Player_Log_AA_Purchases, - Player_Log_Trade_Skill_Events, - Player_Log_Issued_Commands, - Player_Log_Money_Transactions, - Player_Log_Alternate_Currency_Transactions, - Player_Log_Guild_Tribute_Item_Donation, - Player_Log_Guild_Tribute_Plat_Donation + bool HasConnection() const + { + return m_connection && m_connection->Handle() && m_connection->Handle()->IsConnected(); + } + + inline void CheckForConnectState() { + if (RuleB(Logging, PlayerEventsQSProcess)) { + if (!m_connection) { + Connect(); + LogInfo("Starting QueryServ connection"); + } + } else { + if (HasConnection()) { + Disconnect(); + LogInfo("Stopping QueryServ connection"); + } + } + } + +private: + std::unique_ptr m_connection; + bool m_is_qs_connected = false; }; - -class QueryServ{ - public: - QueryServ(); - ~QueryServ(); - void SendQuery(std::string Query); - void PlayerLogEvent(int Event_Type, int Character_ID, std::string Event_Desc); +class QueryServConnection { +public: + QueryServConnection(); + void AddConnection(std::shared_ptr connection); + void RemoveConnection(std::shared_ptr connection); + void HandleGenericMessage(uint16_t opcode, EQ::Net::Packet &p); + void HandleLFGuildUpdateMessage(uint16_t opcode, EQ::Net::Packet &p); + bool SendPacket(ServerPacket *pack); +private: + std::map> m_streams; + std::unique_ptr m_keepalive; }; #endif /* QUERYSERV_ZONE_H */ diff --git a/zone/quest_interface.h b/zone/quest_interface.h index 687a90f58..f18e1dbec 100644 --- a/zone/quest_interface.h +++ b/zone/quest_interface.h @@ -163,6 +163,28 @@ public: return 0; } + virtual int EventZone( + QuestEventID event_id, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector* extra_pointers + ) + { + return 0; + } + + virtual int EventGlobalZone( + QuestEventID event_id, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector* extra_pointers + ) + { + return 0; + } + virtual bool HasQuestSub(uint32 npc_id, QuestEventID event_id) { return false; @@ -223,6 +245,16 @@ public: return false; } + virtual bool ZoneHasQuestSub(QuestEventID event_id) + { + return false; + } + + virtual bool GlobalZoneHasQuestSub(QuestEventID event_id) + { + return false; + } + virtual void LoadNPCScript(std::string filename, int npc_id) { } virtual void LoadGlobalNPCScript(std::string filename) { } virtual void LoadPlayerScript(std::string filename) { } @@ -234,6 +266,8 @@ public: virtual void LoadGlobalBotScript(std::string filename) { } virtual void LoadMercScript(std::string filename) { } virtual void LoadGlobalMercScript(std::string filename) { } + virtual void LoadZoneScript(std::string filename) { } + virtual void LoadGlobalZoneScript(std::string filename) { } virtual int DispatchEventNPC( QuestEventID event_id, @@ -308,6 +342,17 @@ public: return 0; } + virtual int DispatchEventZone( + QuestEventID event_id, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector* extra_pointers + ) + { + return 0; + } + virtual void AddVar(std::string name, std::string val) { } virtual std::string GetVar(std::string name) { diff --git a/zone/quest_parser_collection.cpp b/zone/quest_parser_collection.cpp index ba9b5745b..9535cb89a 100644 --- a/zone/quest_parser_collection.cpp +++ b/zone/quest_parser_collection.cpp @@ -49,6 +49,8 @@ QuestParserCollection::QuestParserCollection() _global_bot_quest_status = QuestUnloaded; _merc_quest_status = QuestUnloaded; _global_merc_quest_status = QuestUnloaded; + _zone_quest_status = QuestUnloaded; + _global_zone_quest_status = QuestUnloaded; } QuestParserCollection::~QuestParserCollection() { } @@ -85,6 +87,7 @@ void QuestParserCollection::ReloadQuests(bool reset_timers) { if (reset_timers) { quest_manager.ClearAllTimers(); + zone->StopAllTimers(); } MapOpcodes(); @@ -98,6 +101,8 @@ void QuestParserCollection::ReloadQuests(bool reset_timers) _global_bot_quest_status = QuestUnloaded; _merc_quest_status = QuestUnloaded; _global_merc_quest_status = QuestUnloaded; + _zone_quest_status = QuestUnloaded; + _global_zone_quest_status = QuestUnloaded; _spell_quest_status.clear(); _item_quest_status.clear(); @@ -426,6 +431,49 @@ bool QuestParserCollection::MercHasQuestSub(QuestEventID event_id) return MercHasQuestSubLocal(event_id) || MercHasQuestSubGlobal(event_id); } +bool QuestParserCollection::ZoneHasQuestSubLocal(QuestEventID event_id) +{ + if (_zone_quest_status == QuestUnloaded) { + std::string filename; + auto qi = GetQIByZoneQuest(filename); + + if (qi) { + _zone_quest_status = qi->GetIdentifier(); + qi->LoadZoneScript(filename); + return qi->ZoneHasQuestSub(event_id); + } + } else if (_zone_quest_status != QuestFailedToLoad) { + auto iter = _interfaces.find(_zone_quest_status); + return iter->second->ZoneHasQuestSub(event_id); + } + + return false; +} + +bool QuestParserCollection::ZoneHasQuestSubGlobal(QuestEventID event_id) +{ + if (_global_zone_quest_status == QuestUnloaded) { + std::string filename; + auto qi = GetQIByGlobalZoneQuest(filename); + + if (qi) { + _global_zone_quest_status = qi->GetIdentifier(); + qi->LoadGlobalZoneScript(filename); + return qi->GlobalZoneHasQuestSub(event_id); + } + } else if (_global_zone_quest_status != QuestFailedToLoad) { + auto iter = _interfaces.find(_global_zone_quest_status); + return iter->second->GlobalZoneHasQuestSub(event_id); + } + + return false; +} + +bool QuestParserCollection::ZoneHasQuestSub(QuestEventID event_id) +{ + return ZoneHasQuestSubLocal(event_id) || ZoneHasQuestSubGlobal(event_id); +} + int QuestParserCollection::EventNPC( QuestEventID event_id, NPC* npc, @@ -435,6 +483,10 @@ int QuestParserCollection::EventNPC( std::vector* extra_pointers ) { + if (npc->IsResumedFromZoneSuspend() && npc->IsQueuedForCorpse()) { + return 0; + } + const int local_return = EventNPCLocal(event_id, npc, init, data, extra_data, extra_pointers); const int global_return = EventNPCGlobal(event_id, npc, init, data, extra_data, extra_pointers); const int default_return = DispatchEventNPC(event_id, npc, init, data, extra_data, extra_pointers); @@ -920,6 +972,83 @@ int QuestParserCollection::EventMercGlobal( return 0; } +int QuestParserCollection::EventZone( + QuestEventID event_id, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector* extra_pointers +) +{ + const int local_return = EventZoneLocal(event_id, zone, data, extra_data, extra_pointers); + const int global_return = EventZoneGlobal(event_id, zone, data, extra_data, extra_pointers); + const int default_return = DispatchEventZone(event_id, zone, data, extra_data, extra_pointers); + + if (local_return != 0) { + return local_return; + } else if (global_return != 0) { + return global_return; + } else if (default_return != 0) { + return default_return; + } + + return 0; +} + +int QuestParserCollection::EventZoneLocal( + QuestEventID event_id, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector* extra_pointers +) +{ + if (_zone_quest_status == QuestUnloaded) { + std::string filename; + auto qi = GetQIByZoneQuest(filename); + + if (qi) { + _zone_quest_status = qi->GetIdentifier(); + qi->LoadZoneScript(filename); + return qi->EventZone(event_id, zone, data, extra_data, extra_pointers); + } + } else { + if (_zone_quest_status != QuestFailedToLoad) { + auto iter = _interfaces.find(_zone_quest_status); + return iter->second->EventZone(event_id, zone, data, extra_data, extra_pointers); + } + } + + return 0; +} + +int QuestParserCollection::EventZoneGlobal( + QuestEventID event_id, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector* extra_pointers +) +{ + if (_global_zone_quest_status == QuestUnloaded) { + std::string filename; + auto qi = GetQIByGlobalZoneQuest(filename); + + if (qi) { + _global_zone_quest_status = qi->GetIdentifier(); + qi->LoadGlobalZoneScript(filename); + return qi->EventGlobalZone(event_id, zone, data, extra_data, extra_pointers); + } + } else { + if (_global_zone_quest_status != QuestFailedToLoad) { + auto iter = _interfaces.find(_global_zone_quest_status); + return iter->second->EventGlobalZone(event_id, zone, data, extra_data, extra_pointers); + } + } + + return 0; +} + QuestInterface* QuestParserCollection::GetQIByNPCQuest(uint32 npc_id, std::string& filename) { if (!zone) { @@ -935,50 +1064,61 @@ QuestInterface* QuestParserCollection::GetQIByNPCQuest(uint32 npc_id, std::strin Strings::FindReplace(npc_name, "`", "-"); - const std::string& global_path = fmt::format( - "{}/{}", - path.GetQuestsPath(), - QUEST_GLOBAL_DIRECTORY - ); + for (auto & dir : PathManager::Instance()->GetQuestPaths()) { + const std::string& npc_id_and_name = fmt::format( + "{}_{}", + npc_name, + npc_id + ); - const std::string& zone_path = fmt::format( - "{}/{}", - path.GetQuestsPath(), - zone->GetShortName() - ); + const std::string& global_path = fmt::format( + "{}/{}", + dir, + QUEST_GLOBAL_DIRECTORY + ); - const std::string& zone_versioned_path = fmt::format( - "{}/{}/v{}", - path.GetQuestsPath(), - zone->GetShortName(), - zone->GetInstanceVersion() - ); + const std::string& zone_path = fmt::format( + "{}/{}", + dir, + zone->GetShortName() + ); - std::vector file_names = { - fmt::format("{}/{}", zone_versioned_path, npc_id), // Local versioned by NPC ID ./quests/zone/v0/10.ext - fmt::format("{}/{}", zone_versioned_path, npc_name), // Local versioned by NPC Name ./quests/zone/v0/npc.ext - fmt::format("{}/{}", zone_path, npc_id), // Local by NPC ID - fmt::format("{}/{}", zone_path, npc_name), // Local by NPC Name - fmt::format("{}/{}", global_path, npc_id), // Global by NPC ID - fmt::format("{}/{}", global_path, npc_name), // Global by NPC ID - fmt::format("{}/default", zone_versioned_path), // Zone Default ./quests/zone/v0/default.ext - fmt::format("{}/default", zone_path), // Zone Default - fmt::format("{}/default", global_path), // Global Default - }; + const std::string& zone_versioned_path = fmt::format( + "{}/{}/v{}", + dir, + zone->GetShortName(), + zone->GetInstanceVersion() + ); - std::string file_name; + std::vector file_names = { + fmt::format("{}/{}", zone_versioned_path, npc_id), // Local versioned by NPC ID (./quests/zone/v0/10.ext) + fmt::format("{}/{}", zone_versioned_path, npc_name), // Local versioned by NPC Name (./quests/zone/v0/name.ext) + fmt::format("{}/{}", zone_versioned_path, npc_id_and_name), // Local versioned by NPC ID and NPC Name (./quests/zone/v0/10_name.ext) + fmt::format("{}/{}", zone_path, npc_id), // Local by NPC ID + fmt::format("{}/{}", zone_path, npc_name), // Local by NPC Name + fmt::format("{}/{}", zone_path, npc_id_and_name), // Local by NPC ID and NPC Name + fmt::format("{}/{}", global_path, npc_id), // Global by NPC ID + fmt::format("{}/{}", global_path, npc_name), // Global by NPC ID + fmt::format("{}/{}", global_path, npc_id_and_name), // Global by NPC ID and NPC Name + fmt::format("{}/default", zone_versioned_path), // Zone Versioned Default (./quests/zone/v0/default.ext) + fmt::format("{}/default", zone_path), // Zone Default + fmt::format("{}/default", global_path), // Global Default + }; - for (auto & file : file_names) { - for (auto* e: _load_precedence) { - file_name = fmt::format( - "{}.{}", - file, - _extensions.find(e->GetIdentifier())->second - ); + std::string file_name; - if (File::Exists(file_name)) { - filename = file_name; - return e; + for (auto & file : file_names) { + for (auto* e: _load_precedence) { + file_name = fmt::format( + "{}.{}", + file, + _extensions.find(e->GetIdentifier())->second + ); + + if (File::Exists(file_name)) { + filename = file_name; + return e; + } } } } @@ -992,44 +1132,46 @@ QuestInterface* QuestParserCollection::GetQIByPlayerQuest(std::string& filename) return nullptr; } - const std::string& global_path = fmt::format( - "{}/{}", - path.GetQuestsPath(), - QUEST_GLOBAL_DIRECTORY - ); + for (auto & dir : PathManager::Instance()->GetQuestPaths()) { + const std::string& global_path = fmt::format( + "{}/{}", + dir, + QUEST_GLOBAL_DIRECTORY + ); - const std::string& zone_path = fmt::format( - "{}/{}", - path.GetQuestsPath(), - zone->GetShortName() - ); + const std::string& zone_path = fmt::format( + "{}/{}", + dir, + zone->GetShortName() + ); - const std::string& zone_versioned_path = fmt::format( - "{}/{}/v{}", - path.GetQuestsPath(), - zone->GetShortName(), - zone->GetInstanceVersion() - ); + const std::string& zone_versioned_path = fmt::format( + "{}/{}/v{}", + dir, + zone->GetShortName(), + zone->GetInstanceVersion() + ); - std::vector file_names = { - fmt::format("{}/player", zone_versioned_path), // Local by Instance Version ./quests/zone/v0/player.ext - fmt::format("{}/player_v{}", zone_path, zone->GetInstanceVersion()), // Local by Instance Version - fmt::format("{}/player", zone_path), // Local - fmt::format("{}/player", global_path) // Global - }; + std::vector file_names = { + fmt::format("{}/player", zone_versioned_path), // Local by Instance Version ./quests/zone/v0/player.ext + fmt::format("{}/player_v{}", zone_path, zone->GetInstanceVersion()), // Local by Instance Version + fmt::format("{}/player", zone_path), // Local + fmt::format("{}/player", global_path) // Global + }; - std::string file_name; - for (auto & file : file_names) { - for (auto* e: _load_precedence) { - file_name = fmt::format( - "{}.{}", - file, - _extensions.find(e->GetIdentifier())->second - ); + std::string file_name; + for (auto & file : file_names) { + for (auto* e: _load_precedence) { + file_name = fmt::format( + "{}.{}", + file, + _extensions.find(e->GetIdentifier())->second + ); - if (File::Exists(file_name)) { - filename = file_name; - return e; + if (File::Exists(file_name)) { + filename = file_name; + return e; + } } } } @@ -1045,17 +1187,19 @@ QuestInterface* QuestParserCollection::GetQIByGlobalNPCQuest(std::string& filena std::string file_name; - for (auto* e: _load_precedence) { - file_name = fmt::format( - "{}/{}/global_npc.{}", - path.GetQuestsPath(), - QUEST_GLOBAL_DIRECTORY, - _extensions.find(e->GetIdentifier())->second - ); + for (auto & dir : PathManager::Instance()->GetQuestPaths()) { + for (auto* e: _load_precedence) { + file_name = fmt::format( + "{}/{}/global_npc.{}", + dir, + QUEST_GLOBAL_DIRECTORY, + _extensions.find(e->GetIdentifier())->second + ); - if (File::Exists(file_name)) { - filename = file_name; - return e; + if (File::Exists(file_name)) { + filename = file_name; + return e; + } } } @@ -1069,17 +1213,19 @@ QuestInterface* QuestParserCollection::GetQIByGlobalPlayerQuest(std::string& fil } std::string file_name; - for (auto* e: _load_precedence) { - file_name = fmt::format( - "{}/{}/global_player.{}", - path.GetQuestsPath(), - QUEST_GLOBAL_DIRECTORY, - _extensions.find(e->GetIdentifier())->second - ); + for (auto & dir : PathManager::Instance()->GetQuestPaths()) { + for (auto* e: _load_precedence) { + file_name = fmt::format( + "{}/{}/global_player.{}", + dir, + QUEST_GLOBAL_DIRECTORY, + _extensions.find(e->GetIdentifier())->second + ); - if (File::Exists(file_name)) { - filename = file_name; - return e; + if (File::Exists(file_name)) { + filename = file_name; + return e; + } } } @@ -1092,45 +1238,47 @@ QuestInterface* QuestParserCollection::GetQIBySpellQuest(uint32 spell_id, std::s return nullptr; } - const std::string& global_path = fmt::format( - "{}/{}/spells", - path.GetQuestsPath(), - QUEST_GLOBAL_DIRECTORY - ); + for (auto & dir : PathManager::Instance()->GetQuestPaths()) { + const std::string& global_path = fmt::format( + "{}/{}/spells", + dir, + QUEST_GLOBAL_DIRECTORY + ); - const std::string& zone_path = fmt::format( - "{}/{}/spells", - path.GetQuestsPath(), - zone->GetShortName() - ); + const std::string& zone_path = fmt::format( + "{}/{}/spells", + dir, + zone->GetShortName() + ); - const std::string& zone_versioned_path = fmt::format( - "{}/{}/v{}/spells", - path.GetQuestsPath(), - zone->GetShortName(), - zone->GetInstanceVersion() - ); + const std::string& zone_versioned_path = fmt::format( + "{}/{}/v{}/spells", + dir, + zone->GetShortName(), + zone->GetInstanceVersion() + ); - std::vector file_names = { - fmt::format("{}/{}", zone_versioned_path, spell_id), // Local versioned by Spell ID ./quests/zone/v0/spells/10.ext - fmt::format("{}/{}", zone_path, spell_id), // Local - fmt::format("{}/{}", global_path, spell_id), // Global - fmt::format("{}/default", zone_path), // Local Default - fmt::format("{}/default", global_path) // Global Default - }; + std::vector file_names = { + fmt::format("{}/{}", zone_versioned_path, spell_id), // Local versioned by Spell ID ./quests/zone/v0/spells/10.ext + fmt::format("{}/{}", zone_path, spell_id), // Local + fmt::format("{}/{}", global_path, spell_id), // Global + fmt::format("{}/default", zone_path), // Local Default + fmt::format("{}/default", global_path) // Global Default + }; - std::string file_name; - for (auto & file : file_names) { - for (auto* e: _load_precedence) { - file_name = fmt::format( - "{}.{}", - file, - _extensions.find(e->GetIdentifier())->second - ); + std::string file_name; + for (auto & file : file_names) { + for (auto* e: _load_precedence) { + file_name = fmt::format( + "{}.{}", + file, + _extensions.find(e->GetIdentifier())->second + ); - if (File::Exists(file_name)) { - filename = file_name; - return e; + if (File::Exists(file_name)) { + filename = file_name; + return e; + } } } } @@ -1144,45 +1292,47 @@ QuestInterface* QuestParserCollection::GetQIByItemQuest(std::string item_script, return nullptr; } - const std::string& global_path = fmt::format( - "{}/{}/items", - path.GetQuestsPath(), - QUEST_GLOBAL_DIRECTORY - ); + for (auto & dir : PathManager::Instance()->GetQuestPaths()) { + const std::string& global_path = fmt::format( + "{}/{}/items", + dir, + QUEST_GLOBAL_DIRECTORY + ); - const std::string& zone_path = fmt::format( - "{}/{}/items", - path.GetQuestsPath(), - zone->GetShortName() - ); + const std::string& zone_path = fmt::format( + "{}/{}/items", + dir, + zone->GetShortName() + ); - const std::string& zone_versioned_path = fmt::format( - "{}/{}/v{}/items", - path.GetQuestsPath(), - zone->GetShortName(), - zone->GetInstanceVersion() - ); + const std::string& zone_versioned_path = fmt::format( + "{}/{}/v{}/items", + dir, + zone->GetShortName(), + zone->GetInstanceVersion() + ); - std::vector file_names = { - fmt::format("{}/{}", zone_versioned_path, item_script), // Local versioned by Item Script ./quests/zone/v0/items/10.ext - fmt::format("{}/{}", zone_path, item_script), // Local - fmt::format("{}/{}", global_path, item_script), // Global - fmt::format("{}/default", zone_path), // Local Default - fmt::format("{}/default", global_path) // Global Default - }; + std::vector file_names = { + fmt::format("{}/{}", zone_versioned_path, item_script), // Local versioned by Item Script ./quests/zone/v0/items/10.ext + fmt::format("{}/{}", zone_path, item_script), // Local + fmt::format("{}/{}", global_path, item_script), // Global + fmt::format("{}/default", zone_path), // Local Default + fmt::format("{}/default", global_path) // Global Default + }; - std::string file_name; - for (auto & file : file_names) { - for (auto* e: _load_precedence) { - file_name = fmt::format( - "{}.{}", - file, - _extensions.find(e->GetIdentifier())->second - ); + std::string file_name; + for (auto & file : file_names) { + for (auto* e: _load_precedence) { + file_name = fmt::format( + "{}.{}", + file, + _extensions.find(e->GetIdentifier())->second + ); - if (File::Exists(file_name)) { - filename = file_name; - return e; + if (File::Exists(file_name)) { + filename = file_name; + return e; + } } } } @@ -1196,43 +1346,45 @@ QuestInterface* QuestParserCollection::GetQIByEncounterQuest(std::string encount return nullptr; } - const std::string& global_path = fmt::format( - "{}/{}/encounters", - path.GetQuestsPath(), - QUEST_GLOBAL_DIRECTORY - ); + for (auto & dir : PathManager::Instance()->GetQuestPaths()) { + const std::string& global_path = fmt::format( + "{}/{}/encounters", + dir, + QUEST_GLOBAL_DIRECTORY + ); - const std::string& zone_path = fmt::format( - "{}/{}/encounters", - path.GetQuestsPath(), - zone->GetShortName() - ); + const std::string& zone_path = fmt::format( + "{}/{}/encounters", + dir, + zone->GetShortName() + ); - const std::string& zone_versioned_path = fmt::format( - "{}/{}/v{}/encounters", - path.GetQuestsPath(), - zone->GetShortName(), - zone->GetInstanceVersion() - ); + const std::string& zone_versioned_path = fmt::format( + "{}/{}/v{}/encounters", + dir, + zone->GetShortName(), + zone->GetInstanceVersion() + ); - std::vector file_names = { - fmt::format("{}/{}", zone_versioned_path, encounter_name), // Local versioned ./quests/zone/v0/encounters/name.ext - fmt::format("{}/{}", zone_path, encounter_name), // Local - fmt::format("{}/{}", global_path, encounter_name) // Global - }; + std::vector file_names = { + fmt::format("{}/{}", zone_versioned_path, encounter_name), // Local versioned ./quests/zone/v0/encounters/name.ext + fmt::format("{}/{}", zone_path, encounter_name), // Local + fmt::format("{}/{}", global_path, encounter_name) // Global + }; - std::string file_name; - for (auto & file : file_names) { - for (auto* e: _load_precedence) { - file_name = fmt::format( - "{}.{}", - file, - _extensions.find(e->GetIdentifier())->second - ); + std::string file_name; + for (auto & file : file_names) { + for (auto* e: _load_precedence) { + file_name = fmt::format( + "{}.{}", + file, + _extensions.find(e->GetIdentifier())->second + ); - if (File::Exists(file_name)) { - filename = file_name; - return e; + if (File::Exists(file_name)) { + filename = file_name; + return e; + } } } } @@ -1246,44 +1398,46 @@ QuestInterface* QuestParserCollection::GetQIByBotQuest(std::string& filename) return nullptr; } - const std::string& global_path = fmt::format( - "{}/{}", - path.GetQuestsPath(), - QUEST_GLOBAL_DIRECTORY - ); + for (auto & dir : PathManager::Instance()->GetQuestPaths()) { + const std::string& global_path = fmt::format( + "{}/{}", + dir, + QUEST_GLOBAL_DIRECTORY + ); - const std::string& zone_path = fmt::format( - "{}/{}", - path.GetQuestsPath(), - zone->GetShortName() - ); + const std::string& zone_path = fmt::format( + "{}/{}", + dir, + zone->GetShortName() + ); - const std::string& zone_versioned_path = fmt::format( - "{}/{}/v{}", - path.GetQuestsPath(), - zone->GetShortName(), - zone->GetInstanceVersion() - ); + const std::string& zone_versioned_path = fmt::format( + "{}/{}/v{}", + dir, + zone->GetShortName(), + zone->GetInstanceVersion() + ); - std::vector file_names = { - fmt::format("{}/bot", zone_versioned_path), // Local versioned by Instance Version ./quests/zone/v0/bot.ext - fmt::format("{}/bot_v{}", zone_path, zone->GetInstanceVersion()), // Local by Instance Version - fmt::format("{}/bot", zone_path), // Local - fmt::format("{}/bot", global_path) // Global - }; + std::vector file_names = { + fmt::format("{}/bot", zone_versioned_path), // Local versioned by Instance Version ./quests/zone/v0/bot.ext + fmt::format("{}/bot_v{}", zone_path, zone->GetInstanceVersion()), // Local by Instance Version + fmt::format("{}/bot", zone_path), // Local + fmt::format("{}/bot", global_path) // Global + }; - std::string file_name; - for (auto & file : file_names) { - for (auto* e: _load_precedence) { - file_name = fmt::format( - "{}.{}", - file, - _extensions.find(e->GetIdentifier())->second - ); + std::string file_name; + for (auto & file : file_names) { + for (auto* e: _load_precedence) { + file_name = fmt::format( + "{}.{}", + file, + _extensions.find(e->GetIdentifier())->second + ); - if (File::Exists(file_name)) { - filename = file_name; - return e; + if (File::Exists(file_name)) { + filename = file_name; + return e; + } } } } @@ -1298,17 +1452,19 @@ QuestInterface* QuestParserCollection::GetQIByGlobalBotQuest(std::string& filena } std::string file_name; - for (auto* e: _load_precedence) { - file_name = fmt::format( - "{}/{}/global_bot.{}", - path.GetQuestsPath(), - QUEST_GLOBAL_DIRECTORY, - _extensions.find(e->GetIdentifier())->second - ); + for (auto & dir : PathManager::Instance()->GetQuestPaths()) { + for (auto* e: _load_precedence) { + file_name = fmt::format( + "{}/{}/global_bot.{}", + dir, + QUEST_GLOBAL_DIRECTORY, + _extensions.find(e->GetIdentifier())->second + ); - if (File::Exists(file_name)) { - filename = file_name; - return e; + if (File::Exists(file_name)) { + filename = file_name; + return e; + } } } @@ -1321,44 +1477,46 @@ QuestInterface* QuestParserCollection::GetQIByMercQuest(std::string& filename) return nullptr; } - const std::string& global_path = fmt::format( - "{}/{}", - path.GetQuestsPath(), - QUEST_GLOBAL_DIRECTORY - ); + for (auto & dir : PathManager::Instance()->GetQuestPaths()) { + const std::string& global_path = fmt::format( + "{}/{}", + dir, + QUEST_GLOBAL_DIRECTORY + ); - const std::string& zone_path = fmt::format( - "{}/{}", - path.GetQuestsPath(), - zone->GetShortName() - ); + const std::string& zone_path = fmt::format( + "{}/{}", + dir, + zone->GetShortName() + ); - const std::string& zone_versioned_path = fmt::format( - "{}/{}/v{}", - path.GetQuestsPath(), - zone->GetShortName(), - zone->GetInstanceVersion() - ); + const std::string& zone_versioned_path = fmt::format( + "{}/{}/v{}", + dir, + zone->GetShortName(), + zone->GetInstanceVersion() + ); - std::vector file_names = { - fmt::format("{}/merc", zone_versioned_path), // Local versioned by Instance Version ./quests/zone/v0/merc.ext - fmt::format("{}/merc_v{}", zone_path, zone->GetInstanceVersion()), // Local by Instance Version - fmt::format("{}/merc", zone_path), // Local - fmt::format("{}/merc", global_path) // Global - }; + std::vector file_names = { + fmt::format("{}/merc", zone_versioned_path), // Local versioned by Instance Version ./quests/zone/v0/merc.ext + fmt::format("{}/merc_v{}", zone_path, zone->GetInstanceVersion()), // Local by Instance Version + fmt::format("{}/merc", zone_path), // Local + fmt::format("{}/merc", global_path) // Global + }; - std::string file_name; - for (auto & file : file_names) { - for (auto* e: _load_precedence) { - file_name = fmt::format( - "{}.{}", - file, - _extensions.find(e->GetIdentifier())->second - ); + std::string file_name; + for (auto & file : file_names) { + for (auto* e: _load_precedence) { + file_name = fmt::format( + "{}.{}", + file, + _extensions.find(e->GetIdentifier())->second + ); - if (File::Exists(file_name)) { - filename = file_name; - return e; + if (File::Exists(file_name)) { + filename = file_name; + return e; + } } } } @@ -1373,17 +1531,99 @@ QuestInterface* QuestParserCollection::GetQIByGlobalMercQuest(std::string& filen } std::string file_name; - for (auto* e: _load_precedence) { - file_name = fmt::format( - "{}/{}/global_merc.{}", - path.GetQuestsPath(), - QUEST_GLOBAL_DIRECTORY, - _extensions.find(e->GetIdentifier())->second + for (auto & dir : PathManager::Instance()->GetQuestPaths()) { + for (auto* e: _load_precedence) { + file_name = fmt::format( + "{}/{}/global_merc.{}", + dir, + QUEST_GLOBAL_DIRECTORY, + _extensions.find(e->GetIdentifier())->second + ); + + if (File::Exists(file_name)) { + filename = file_name; + return e; + } + } + } + + return nullptr; +} + +QuestInterface* QuestParserCollection::GetQIByZoneQuest(std::string& filename) +{ + if (!zone || !zone->IsLoaded()) { + return nullptr; + } + + std::string file_name; + for (auto& dir: PathManager::Instance()->GetQuestPaths()) { + const std::string& global_path = fmt::format( + "{}/{}", + dir, + QUEST_GLOBAL_DIRECTORY ); - if (File::Exists(file_name)) { - filename = file_name; - return e; + const std::string& zone_path = fmt::format( + "{}/{}", + dir, + zone->GetShortName() + ); + + const std::string& zone_versioned_path = fmt::format( + "{}/{}/v{}", + dir, + zone->GetShortName(), + zone->GetInstanceVersion() + ); + + std::vector file_names = { + fmt::format("{}/zone", zone_versioned_path), // Local versioned by Instance Version ./quests/zone/v0/zone.ext + fmt::format("{}/zone_v{}", zone_path, zone->GetInstanceVersion()), // Local by Instance Version + fmt::format("{}/zone", zone_path), // Local + fmt::format("{}/zone", global_path) // Global + }; + + for (auto& file: file_names) { + for (auto* e: _load_precedence) { + file_name = fmt::format( + "{}.{}", + file, + _extensions.find(e->GetIdentifier())->second + ); + + if (File::Exists(file_name)) { + filename = file_name; + return e; + } + } + } + } + + return nullptr; +} + +QuestInterface* QuestParserCollection::GetQIByGlobalZoneQuest(std::string& filename) +{ + if (!zone) { + return nullptr; + } + + std::string file_name; + + for (auto& dir: PathManager::Instance()->GetQuestPaths()) { + for (auto* e: _load_precedence) { + file_name = fmt::format( + "{}/{}/global_zone.{}", + dir, + QUEST_GLOBAL_DIRECTORY, + _extensions.find(e->GetIdentifier())->second + ); + + if (File::Exists(file_name)) { + filename = file_name; + return e; + } } } @@ -1526,6 +1766,26 @@ int QuestParserCollection::DispatchEventMerc( return ret; } +int QuestParserCollection::DispatchEventZone( + QuestEventID event_id, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector* extra_pointers +) +{ + int ret = 0; + + for (const auto& e: _load_precedence) { + int i = e->DispatchEventZone(event_id, zone, data, extra_data, extra_pointers); + if (i != 0) { + ret = i; + } + } + + return ret; +} + void QuestParserCollection::LoadPerlEventExportSettings(PerlEventExportSettings* s) { for (int i = 0; i < _LargestEventID; i++) { diff --git a/zone/quest_parser_collection.h b/zone/quest_parser_collection.h index cb0a5e8d1..5d7d1d3c0 100644 --- a/zone/quest_parser_collection.h +++ b/zone/quest_parser_collection.h @@ -72,6 +72,7 @@ public: bool ItemHasQuestSub(EQ::ItemInstance* inst, QuestEventID event_id); bool BotHasQuestSub(QuestEventID event_id); bool MercHasQuestSub(QuestEventID event_id); + bool ZoneHasQuestSub(QuestEventID event_id); int EventNPC( QuestEventID event_id, @@ -172,6 +173,14 @@ public: std::vector* extra_pointers = nullptr ); + int EventZone( + QuestEventID event_id, + Zone* zone, + std::string data, + uint32 extra_data = 0, + std::vector* extra_pointers = nullptr + ); + void GetErrors(std::list &quest_errors); /* @@ -209,6 +218,8 @@ private: bool BotHasQuestSubGlobal(QuestEventID event_id); bool MercHasQuestSubLocal(QuestEventID event_id); bool MercHasQuestSubGlobal(QuestEventID event_id); + bool ZoneHasQuestSubLocal(QuestEventID event_id); + bool ZoneHasQuestSubGlobal(QuestEventID event_id); int EventNPCLocal( QuestEventID event_id, @@ -280,6 +291,22 @@ private: std::vector* extra_pointers ); + int EventZoneLocal( + QuestEventID event_id, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector* extra_pointers + ); + + int EventZoneGlobal( + QuestEventID event_id, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector* extra_pointers + ); + QuestInterface* GetQIByNPCQuest(uint32 npc_id, std::string& filename); QuestInterface* GetQIByGlobalNPCQuest(std::string& filename); QuestInterface* GetQIByPlayerQuest(std::string& filename); @@ -291,6 +318,8 @@ private: QuestInterface* GetQIByGlobalBotQuest(std::string& filename); QuestInterface* GetQIByMercQuest(std::string& filename); QuestInterface* GetQIByGlobalMercQuest(std::string& filename); + QuestInterface* GetQIByZoneQuest(std::string& filename); + QuestInterface* GetQIByGlobalZoneQuest(std::string& filename); int DispatchEventNPC( QuestEventID event_id, @@ -347,6 +376,14 @@ private: std::vector* extra_pointers ); + int DispatchEventZone( + QuestEventID event_id, + Zone* zone, + std::string data, + uint32 extra_data, + std::vector* extra_pointers + ); + std::map _interfaces; std::map _extensions; std::list _load_precedence; @@ -359,6 +396,8 @@ private: uint32 _global_bot_quest_status; uint32 _merc_quest_status; uint32 _global_merc_quest_status; + uint32 _zone_quest_status; + uint32 _global_zone_quest_status; std::map _spell_quest_status; std::map _item_quest_status; std::map _encounter_quest_status; diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index 68bd859af..342955fff 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -40,6 +40,7 @@ #include "dialogue_window.h" #include "../common/repositories/account_repository.h" +#include "../common/repositories/completed_tasks_repository.h" #include "../common/repositories/tradeskill_recipe_repository.h" #include "../common/repositories/instance_list_repository.h" #include "../common/repositories/grid_entries_repository.h" @@ -63,10 +64,10 @@ QuestManager quest_manager; EQ::ItemInstance* questitem = nullptr; \ const SPDat_Spell_Struct* questspell = nullptr; \ bool depop_npc = false; \ - std::string encounter; \ + std::string encounter = ""; \ do { \ - if(!quests_running_.empty()) { \ - running_quest e = quests_running_.top(); \ + if(!m_running_quests.empty()) { \ + RunningQuest e = m_running_quests.top(); \ owner = e.owner; \ initiator = e.initiator; \ questitem = e.questitem; \ @@ -91,13 +92,7 @@ void QuestManager::Process() { if (cur->Timer_.Enabled() && cur->Timer_.Check()) { if (cur->mob) { if (cur->mob->IsEncounter()) { - parse->EventEncounter( - EVENT_TIMER, - cur->mob->CastToEncounter()->GetEncounterName(), - cur->name, - 0, - nullptr - ); + parse->EventEncounter(EVENT_TIMER, cur->mob->CastToEncounter()->GetEncounterName(), cur->name, 0, nullptr); } else { parse->EventMob(EVENT_TIMER, cur->mob, nullptr, [&]() { return cur->name; }, 0); } @@ -129,33 +124,30 @@ void QuestManager::Process() { } } -void QuestManager::StartQuest(Mob *_owner, Client *_initiator, EQ::ItemInstance* _questitem, const SPDat_Spell_Struct* _questspell, std::string encounter) { - running_quest run; - run.owner = _owner; - run.initiator = _initiator; - run.questitem = _questitem; - run.questspell = _questspell; - run.depop_npc = false; - run.encounter = encounter; - quests_running_.push(run); +void QuestManager::StartQuest(const RunningQuest& q) +{ + m_running_quests.push(q); } void QuestManager::EndQuest() { - running_quest run = quests_running_.top(); - if(run.depop_npc && run.owner->IsNPC()) { + RunningQuest run = m_running_quests.top(); + + if (run.depop_npc && run.owner->IsNPC()) { //clear out any timers for them... std::list::iterator cur = QTimerList.begin(), end; end = QTimerList.end(); while (cur != end) { - if (cur->mob == run.owner) + if (cur->mob == run.owner) { cur = QTimerList.erase(cur); - else + } else { ++cur; + } } run.owner->Depop(); } - quests_running_.pop(); + + m_running_quests.pop(); } void QuestManager::ClearAllTimers() { @@ -206,7 +198,7 @@ void QuestManager::summonitem(uint32 itemid, int16 charges) { void QuestManager::write(const char *file, const char *str) { FILE * pFile; - pFile = fopen (fmt::format("{}/{}", path.GetServerPath(), file).c_str(), "a"); + pFile = fopen (fmt::format("{}/{}", PathManager::Instance()->GetServerPath(), file).c_str(), "a"); if(!pFile) return; fprintf(pFile, "%s\n", str); @@ -214,6 +206,15 @@ void QuestManager::write(const char *file, const char *str) { } Mob* QuestManager::spawn2(int npc_id, int grid, int unused, const glm::vec4& position) { + QuestManagerCurrentQuestVars(); + if (owner && owner->IsNPC()) { + auto n = owner->CastToNPC(); + if (n->IsResumedFromZoneSuspend()) { + LogZoneState("NPC [{}] is resuming from zone suspend, skipping quest call", n->GetCleanName()); + return nullptr; + } + } + const NPCType* t = 0; if (t = content_db.LoadNPCTypesData(npc_id)) { auto npc = new NPC(t, nullptr, position, GravityBehavior::Water); @@ -234,6 +235,15 @@ Mob* QuestManager::spawn2(int npc_id, int grid, int unused, const glm::vec4& pos } Mob* QuestManager::unique_spawn(int npc_type, int grid, int unused, const glm::vec4& position) { + QuestManagerCurrentQuestVars(); + if (owner && owner->IsNPC()) { + auto n = owner->CastToNPC(); + if (n->IsResumedFromZoneSuspend()) { + LogZoneState("NPC [{}] is resuming from zone suspend, skipping quest call", n->GetCleanName()); + return nullptr; + } + } + Mob *other = entity_list.GetMobByNpcTypeID(npc_type); if(other != nullptr) { return other; @@ -661,14 +671,7 @@ void QuestManager::stoptimer(const std::string& timer_name) for (auto e = QTimerList.begin(); e != QTimerList.end(); ++e) { if (e->mob && e->mob == owner && e->name == timer_name) { - parse->EventMob( - EVENT_TIMER_STOP, - owner, - nullptr, - [&]() { - return timer_name; - } - ); + parse->EventMob(EVENT_TIMER_STOP, owner, nullptr, [&]() { return timer_name; }); QTimerList.erase(e); break; @@ -693,16 +696,9 @@ void QuestManager::stoptimer(const std::string& timer_name, Mob* m) return; } - for (auto e = QTimerList.begin(); e != QTimerList.end();) { - if (e->mob && e->mob == m) { - parse->EventMob( - EVENT_TIMER_STOP, - m, - nullptr, - [&]() { - return timer_name; - } - ); + for (auto e = QTimerList.begin(); e != QTimerList.end(); ++e) { + if (e->mob && e->mob == m && e->name == timer_name) { + parse->EventMob(EVENT_TIMER_STOP, m, nullptr, [&]() { return timer_name; }); QTimerList.erase(e); break; @@ -743,14 +739,7 @@ void QuestManager::stopalltimers() for (auto e = QTimerList.begin(); e != QTimerList.end();) { if (e->mob && e->mob == owner) { - parse->EventMob( - EVENT_TIMER_STOP, - owner, - nullptr, - [&]() { - return e->name; - } - ); + parse->EventMob(EVENT_TIMER_STOP, owner, nullptr, [&]() { return e->name; }); e = QTimerList.erase(e); } else { @@ -792,14 +781,7 @@ void QuestManager::stopalltimers(Mob* m) for (auto e = QTimerList.begin(); e != QTimerList.end();) { if (e->mob && e->mob == m) { - parse->EventMob( - EVENT_TIMER_STOP, - m, - nullptr, - [&]() { - return e->name; - } - ); + parse->EventMob(EVENT_TIMER_STOP, m, nullptr, [&]() { return e->name; }); e = QTimerList.erase(e); } else { @@ -853,18 +835,13 @@ void QuestManager::pausetimer(const std::string& timer_name, Mob* m) } ); - parse->EventMob( - EVENT_TIMER_PAUSE, - mob, - nullptr, - [&]() { - return fmt::format( - "{} {}", - timer_name, - milliseconds - ); - } - ); + parse->EventMob(EVENT_TIMER_PAUSE, mob, nullptr, [&]() { + return fmt::format( + "{} {}", + timer_name, + milliseconds + ); + }); LogQuests("Pausing timer [{}] for [{}] with [{}] ms remaining", timer_name, owner->GetName(), milliseconds); } @@ -929,7 +906,7 @@ void QuestManager::resumetimer(const std::string& timer_name, Mob* m) } } - QTimerList.emplace_back(QuestTimer(milliseconds, m, timer_name)); + QTimerList.emplace_back(QuestTimer(milliseconds, mob, timer_name)); parse->EventMob(EVENT_TIMER_RESUME, mob, nullptr, f); @@ -1118,18 +1095,18 @@ void QuestManager::depop(int npc_type) { tmp->CastToNPC()->Depop(); } else { - running_quest e = quests_running_.top(); + RunningQuest e = m_running_quests.top(); e.depop_npc = true; - quests_running_.pop(); - quests_running_.push(e); + m_running_quests.pop(); + m_running_quests.push(e); } } } else { //depop self - running_quest e = quests_running_.top(); + RunningQuest e = m_running_quests.top(); e.depop_npc = true; - quests_running_.pop(); - quests_running_.push(e); + m_running_quests.pop(); + m_running_quests.push(e); } } } @@ -1262,50 +1239,7 @@ bool QuestManager::isdisctome(uint32 item_id) { return false; } - if (!item->IsClassCommon() || item->ItemType != EQ::item::ItemTypeSpell) { - return false; - } - - //Need a way to determine the difference between a spell and a tome - //so they cant turn in a spell and get it as a discipline - //this is kinda a hack: - - const std::string item_name = item->Name; - - if ( - !Strings::BeginsWith(item_name, "Tome of ") && - !Strings::BeginsWith(item_name, "Skill: ") - ) { - return false; - } - - //we know for sure none of the int casters get disciplines - uint32 class_bit = 0; - class_bit |= 1 << (Class::Wizard - 1); - class_bit |= 1 << (Class::Enchanter - 1); - class_bit |= 1 << (Class::Magician - 1); - class_bit |= 1 << (Class::Necromancer - 1); - if (item->Classes & class_bit) { - return false; - } - - const auto& spell_id = static_cast(item->Scroll.Effect); - if (!IsValidSpell(spell_id)) { - return false; - } - - //we know for sure none of the int casters get disciplines - const auto& spell = spells[spell_id]; - if( - spell.classes[Class::Wizard - 1] != 255 && - spell.classes[Class::Enchanter - 1] != 255 && - spell.classes[Class::Magician - 1] != 255 && - spell.classes[Class::Necromancer - 1] != 255 - ) { - return false; - } - - return true; + return IsDisciplineTome(item); } std::string QuestManager::getracename(uint16 race_id) { @@ -1326,7 +1260,7 @@ std::string QuestManager::getskillname(int skill_id) { } std::string QuestManager::getldonthemename(uint32 theme_id) { - return EQ::constants::GetLDoNThemeName(theme_id); + return LDoNTheme::GetName(theme_id); } std::string QuestManager::getfactionname(int faction_id) { @@ -1374,15 +1308,14 @@ void QuestManager::rename(std::string name) { QuestManagerCurrentQuestVars(); if (initiator) { std::string current_name = initiator->GetName(); - if (initiator->ChangeFirstName(name.c_str(), current_name.c_str())) { + if (initiator->ChangeFirstName(name)) { initiator->Message( Chat::White, fmt::format( - "Successfully renamed to {}, kicking to character select.", + "Successfully renamed to {}.", name ).c_str() ); - initiator->Kick("Name was changed."); } else { initiator->Message( Chat::Red, @@ -1670,7 +1603,7 @@ void QuestManager::save() { void QuestManager::faction(int faction_id, int faction_value, int temp) { QuestManagerCurrentQuestVars(); - running_quest run = quests_running_.top(); + RunningQuest run = m_running_quests.top(); if(run.owner->IsCharmed() == false && initiator) { if(faction_id != 0 && faction_value != 0) { initiator->SetFactionLevel2( @@ -1705,10 +1638,10 @@ void QuestManager::setsky(uint8 new_sky) { safe_delete(outapp); } -void QuestManager::setguild(uint32 new_guild_id, uint8 new_rank) { +void QuestManager::SetGuild(uint32 new_guild_id, uint8 new_rank) { QuestManagerCurrentQuestVars(); if (initiator) { - guild_mgr.SetGuild(initiator->CharacterID(), new_guild_id, new_rank); + guild_mgr.SetGuild(initiator, new_guild_id, new_rank); } } @@ -1763,7 +1696,7 @@ void QuestManager::CreateGuild(const char *guild_name, const char *leader) { gid ).c_str() ); - if (!guild_mgr.SetGuild(character_id, gid, GUILD_LEADER)) { + if (!guild_mgr.SetGuild(initiator, gid, GUILD_LEADER)) { worldserver.SendEmoteMessage( 0, 0, @@ -1850,12 +1783,6 @@ void QuestManager::setglobal(const char *varname, const char *newvalue, int opti } InsertQuestGlobal(qgCharid, qgNpcid, qgZoneid, varname, newvalue, QGVarDuration(duration)); - - /* QS: PlayerLogQGlobalUpdate */ - if (RuleB(QueryServ, PlayerLogQGlobalUpdate) && qgCharid && qgCharid > 0 && initiator){ - std::string event_desc = StringFormat("Update :: qglobal:%s to qvalue:%s zoneid:%i instid:%i", varname, newvalue, initiator->GetZoneID(), initiator->GetInstanceID()); - QServ->PlayerLogEvent(Player_Log_QGlobal_Update, qgCharid, event_desc); - } } /* Inserts global variable into quest_globals table */ @@ -1942,12 +1869,6 @@ void QuestManager::delglobal(const char *varname) { else qgCharid=-qgNpcid; // make char id negative npc id as a fudge - /* QS: PlayerLogQGlobalUpdate */ - if (RuleB(QueryServ, PlayerLogQGlobalUpdate) && qgCharid && qgCharid > 0 && initiator){ - std::string event_desc = StringFormat("Deleted :: qglobal:%s zoneid:%i instid:%i", varname, initiator->GetZoneID(), initiator->GetInstanceID()); - QServ->PlayerLogEvent(Player_Log_QGlobal_Update, qgCharid, event_desc); - } - std::string query = StringFormat("DELETE FROM quest_globals " "WHERE name = '%s' " "&& (npcid=0 || npcid=%i) " @@ -2153,10 +2074,10 @@ void QuestManager::respawn(int npcTypeID, int grid) { if (!owner || !owner->IsNPC()) return; - running_quest e = quests_running_.top(); + RunningQuest e = m_running_quests.top(); e.depop_npc = true; - quests_running_.pop(); - quests_running_.push(e); + m_running_quests.pop(); + m_running_quests.push(e); const NPCType* npcType = nullptr; if ((npcType = content_db.LoadNPCTypesData(npcTypeID))) @@ -2863,7 +2784,7 @@ bool QuestManager::createBot(const char *name, const char *lastname, uint8 level std::string test_name = name; bool available_flag = false; - if (!database.botdb.QueryNameAvailablity(test_name, available_flag)) { + if (!database.botdb.QueryNameAvailability(test_name, available_flag)) { initiator->Message( Chat::White, fmt::format( @@ -2896,7 +2817,7 @@ bool QuestManager::createBot(const char *name, const char *lastname, uint8 level initiator->Message( Chat::White, fmt::format( - "{} has invalid characters. You can use only the A-Z, a-z and _ characters in a bot name.", + "{} has invalid characters. You can use only the A-Z, a-z and _ characters in a bot name and it must be between 4 and 15 characters long.", new_bot->GetCleanName() ).c_str() ); @@ -2945,27 +2866,27 @@ bool QuestManager::createBot(const char *name, const char *lastname, uint8 level void QuestManager::taskselector(const std::vector& tasks, bool ignore_cooldown) { QuestManagerCurrentQuestVars(); - if(RuleB(TaskSystem, EnableTaskSystem) && initiator && owner && task_manager) + if(RuleB(TaskSystem, EnableTaskSystem) && initiator && owner) initiator->TaskQuestSetSelector(owner, tasks, ignore_cooldown); } void QuestManager::enabletask(int taskcount, int *tasks) { QuestManagerCurrentQuestVars(); - if(RuleB(TaskSystem, EnableTaskSystem) && initiator && task_manager) + if(RuleB(TaskSystem, EnableTaskSystem) && initiator) initiator->EnableTask(taskcount, tasks); } void QuestManager::disabletask(int taskcount, int *tasks) { QuestManagerCurrentQuestVars(); - if(RuleB(TaskSystem, EnableTaskSystem) && initiator && task_manager) + if(RuleB(TaskSystem, EnableTaskSystem) && initiator) initiator->DisableTask(taskcount, tasks); } bool QuestManager::istaskenabled(int taskid) { QuestManagerCurrentQuestVars(); - if(RuleB(TaskSystem, EnableTaskSystem) && initiator && task_manager) + if(RuleB(TaskSystem, EnableTaskSystem) && initiator) return initiator->IsTaskEnabled(taskid); return false; @@ -2974,7 +2895,7 @@ bool QuestManager::istaskenabled(int taskid) { void QuestManager::tasksetselector(int tasksetid, bool ignore_cooldown) { QuestManagerCurrentQuestVars(); Log(Logs::General, Logs::Tasks, "[UPDATE] TaskSetSelector called for task set %i", tasksetid); - if(RuleB(TaskSystem, EnableTaskSystem) && initiator && owner && task_manager) + if(RuleB(TaskSystem, EnableTaskSystem) && initiator && owner) initiator->TaskSetSelector(owner, tasksetid, ignore_cooldown); } @@ -3035,6 +2956,27 @@ void QuestManager::failtask(int taskid) { initiator->FailTask(taskid); } +bool QuestManager::completetask(int task_id) { + QuestManagerCurrentQuestVars(); + + if (!RuleB(TaskSystem, EnableTaskSystem) || !initiator) { + return false; + + } + + return initiator->CompleteTask(task_id); +} + +bool QuestManager::uncompletetask(int task_id) { + QuestManagerCurrentQuestVars(); + + if (!RuleB(TaskSystem, EnableTaskSystem) || !initiator) { + return false; + } + + return initiator->UncompleteTask(task_id); +} + int QuestManager::tasktimeleft(int taskid) { QuestManagerCurrentQuestVars(); @@ -3056,8 +2998,8 @@ int QuestManager::enabledtaskcount(int taskset) { int QuestManager::firsttaskinset(int taskset) { QuestManagerCurrentQuestVars(); - if(RuleB(TaskSystem, EnableTaskSystem) && task_manager) - return task_manager->FirstTaskInSet(taskset); + if(RuleB(TaskSystem, EnableTaskSystem)) + return TaskManager::Instance()->FirstTaskInSet(taskset); return -1; } @@ -3065,8 +3007,8 @@ int QuestManager::firsttaskinset(int taskset) { int QuestManager::lasttaskinset(int taskset) { QuestManagerCurrentQuestVars(); - if(RuleB(TaskSystem, EnableTaskSystem) && task_manager) - return task_manager->LastTaskInSet(taskset); + if(RuleB(TaskSystem, EnableTaskSystem)) + return TaskManager::Instance()->LastTaskInSet(taskset); return -1; } @@ -3074,8 +3016,8 @@ int QuestManager::lasttaskinset(int taskset) { int QuestManager::nexttaskinset(int taskset, int taskid) { QuestManagerCurrentQuestVars(); - if(RuleB(TaskSystem, EnableTaskSystem) && task_manager) - return task_manager->NextTaskInSet(taskset, taskid); + if(RuleB(TaskSystem, EnableTaskSystem)) + return TaskManager::Instance()->NextTaskInSet(taskset, taskid); return -1; } @@ -3140,8 +3082,8 @@ int QuestManager::completedtasksinset(int taskset) { bool QuestManager::istaskappropriate(int task) { QuestManagerCurrentQuestVars(); - if(RuleB(TaskSystem, EnableTaskSystem) && initiator && task_manager) - return task_manager->ValidateLevel(task, initiator->GetLevel()); + if(RuleB(TaskSystem, EnableTaskSystem) && initiator) + return TaskManager::Instance()->ValidateLevel(task, initiator->GetLevel()); return false; } @@ -3150,7 +3092,7 @@ std::string QuestManager::gettaskname(uint32 task_id) { QuestManagerCurrentQuestVars(); if (RuleB(TaskSystem, EnableTaskSystem)) { - return task_manager->GetTaskName(task_id); + return TaskManager::Instance()->GetTaskName(task_id); } return std::string(); @@ -3159,8 +3101,8 @@ std::string QuestManager::gettaskname(uint32 task_id) { int QuestManager::GetCurrentDzTaskID() { QuestManagerCurrentQuestVars(); - if (RuleB(TaskSystem, EnableTaskSystem) && zone && task_manager) { - return task_manager->GetCurrentDzTaskID(); + if (RuleB(TaskSystem, EnableTaskSystem) && zone) { + return TaskManager::Instance()->GetCurrentDzTaskID(); } return 0; @@ -3169,8 +3111,8 @@ int QuestManager::GetCurrentDzTaskID() { void QuestManager::EndCurrentDzTask(bool send_fail) { QuestManagerCurrentQuestVars(); - if (RuleB(TaskSystem, EnableTaskSystem) && zone && task_manager) { - task_manager->EndCurrentDzTask(send_fail); + if (RuleB(TaskSystem, EnableTaskSystem) && zone) { + TaskManager::Instance()->EndCurrentDzTask(send_fail); } } @@ -3343,7 +3285,7 @@ int QuestManager::collectitems(uint32 item_id, bool remove) return quantity; } -int QuestManager::countitem(uint32 item_id) { +uint32 QuestManager::countitem(uint32 item_id) { QuestManagerCurrentQuestVars(); if (!initiator) { @@ -3488,7 +3430,9 @@ std::string QuestManager::varlink( linker.SetLinkType(EQ::saylink::SayLinkItemInst); linker.SetItemInst(item); - return linker.GenerateLink(); + auto link = linker.GenerateLink(); + safe_delete(item); + return link; } std::string QuestManager::getitemcomment(uint32 item_id) { @@ -3581,6 +3525,7 @@ void QuestManager::UpdateInstanceTimer(uint16 instance_id, uint32 new_duration) e.duration = new_duration; e.start_time = std::time(nullptr); + e.expire_at = e.start_time + e.duration; const int updated = InstanceListRepository::UpdateOne(database, e); @@ -4032,81 +3977,90 @@ void QuestManager::ReloadZoneStaticData() } } -Client *QuestManager::GetInitiator() const { - if(!quests_running_.empty()) { - running_quest e = quests_running_.top(); +Client* QuestManager::GetInitiator() const +{ + if (!m_running_quests.empty()) { + RunningQuest e = m_running_quests.top(); return e.initiator; } return nullptr; } -NPC *QuestManager::GetNPC() const { - if(!quests_running_.empty()) { - running_quest e = quests_running_.top(); +NPC* QuestManager::GetNPC() const +{ + if (!m_running_quests.empty()) { + RunningQuest e = m_running_quests.top(); return (e.owner && e.owner->IsNPC()) ? e.owner->CastToNPC() : nullptr; } return nullptr; } -Bot *QuestManager::GetBot() const { - if (!quests_running_.empty()) { - running_quest e = quests_running_.top(); +Bot* QuestManager::GetBot() const +{ + if (!m_running_quests.empty()) { + RunningQuest e = m_running_quests.top(); return (e.owner && e.owner->IsBot()) ? e.owner->CastToBot() : nullptr; } return nullptr; } -Merc *QuestManager::GetMerc() const { - if (!quests_running_.empty()) { - running_quest e = quests_running_.top(); +Merc* QuestManager::GetMerc() const +{ + if (!m_running_quests.empty()) { + RunningQuest e = m_running_quests.top(); return (e.owner && e.owner->IsMerc()) ? e.owner->CastToMerc() : nullptr; } return nullptr; } -Mob *QuestManager::GetOwner() const { - if(!quests_running_.empty()) { - running_quest e = quests_running_.top(); +Mob* QuestManager::GetOwner() const +{ + if (!m_running_quests.empty()) { + RunningQuest e = m_running_quests.top(); return e.owner; } return nullptr; } -EQ::InventoryProfile *QuestManager::GetInventory() const { - if(!quests_running_.empty()) { - running_quest e = quests_running_.top(); +EQ::InventoryProfile* QuestManager::GetInventory() const +{ + if (!m_running_quests.empty()) { + RunningQuest e = m_running_quests.top(); return &e.initiator->GetInv(); } return nullptr; } -EQ::ItemInstance *QuestManager::GetQuestItem() const { - if(!quests_running_.empty()) { - running_quest e = quests_running_.top(); +EQ::ItemInstance* QuestManager::GetQuestItem() const +{ + if (!m_running_quests.empty()) { + RunningQuest e = m_running_quests.top(); return e.questitem; } return nullptr; } -const SPDat_Spell_Struct *QuestManager::GetQuestSpell() { - if(!quests_running_.empty()) { - running_quest e = quests_running_.top(); +const SPDat_Spell_Struct* QuestManager::GetQuestSpell() +{ + if (!m_running_quests.empty()) { + RunningQuest e = m_running_quests.top(); return e.questspell; } return nullptr; } -std::string QuestManager::GetEncounter() const { - if(!quests_running_.empty()) { - running_quest e = quests_running_.top(); +std::string QuestManager::GetEncounter() const +{ + if (!m_running_quests.empty()) { + RunningQuest e = m_running_quests.top(); return e.encounter; } @@ -4522,7 +4476,7 @@ int QuestManager::GetRecipeMadeCount(uint32 recipe_id) { std::string QuestManager::GetRecipeName(uint32 recipe_id) { auto r = TradeskillRecipeRepository::GetWhere( - database, + content_db, fmt::format("id = {}", recipe_id) ); @@ -4698,3 +4652,48 @@ void QuestManager::SpawnGrid(uint32 npc_id, glm::vec4 position, float spacing, u } } } + +bool QuestManager::handin(std::map required) { + QuestManagerCurrentQuestVars(); + if (!owner || !initiator) { + LogQuests("QuestManager::handin called with nullptr owner. Probably syntax error in quest file"); + return false; + } + + if (owner && !owner->IsNPC()) { + LogQuests("QuestManager::handin called with non-NPC owner. Probably syntax error in quest file"); + return false; + } + + return owner->CastToNPC()->CheckHandin(initiator, {}, required, {}); +} + +std::vector QuestManager::GetPausedTimers(Mob* m) +{ + std::vector v; + + if (m && !PTimerList.empty()) { + for (auto e = PTimerList.begin(); e != PTimerList.end(); e++) { + if (e->owner == m) { + v.emplace_back(e->name); + } + } + } + + return v; +} + +std::vector QuestManager::GetTimers(Mob* m) +{ + std::vector v; + + if (m && !QTimerList.empty()) { + for (auto e = QTimerList.begin(); e != QTimerList.end(); e++) { + if (e->mob == m) { + v.emplace_back(e->name); + } + } + } + + return v; +} diff --git a/zone/questmgr.h b/zone/questmgr.h index 1f872aaf5..f58bb283a 100644 --- a/zone/questmgr.h +++ b/zone/questmgr.h @@ -33,15 +33,17 @@ namespace EQ class ItemInstance; } +struct RunningQuest { + Mob* owner = nullptr; + Client* initiator = nullptr; + EQ::ItemInstance* questitem = nullptr; + const SPDat_Spell_Struct* questspell = nullptr; + bool depop_npc = false; + std::string encounter = ""; + Zone* zone = nullptr; +}; + class QuestManager { - struct running_quest { - Mob *owner; - Client *initiator; - EQ::ItemInstance* questitem; - const SPDat_Spell_Struct* questspell; - bool depop_npc; - std::string encounter; - }; struct PausedTimer { Mob* owner; @@ -49,12 +51,13 @@ class QuestManager { uint32 time; }; public: + QuestManager(); virtual ~QuestManager(); - void StartQuest(Mob *_owner, Client *_initiator = nullptr, EQ::ItemInstance* _questitem = nullptr, const SPDat_Spell_Struct* _questspell = nullptr, std::string encounter = ""); + void StartQuest(const RunningQuest& q); void EndQuest(); - bool QuestsRunning() { return !quests_running_.empty(); } + bool QuestsRunning() { return !m_running_quests.empty(); } void Process(); @@ -152,7 +155,7 @@ public: void faction(int faction_id, int faction_value, int temp); void rewardfaction(int faction_id, int faction_value); void setsky(uint8 new_sky); - void setguild(uint32 new_guild_id, uint8 new_rank); + void SetGuild(uint32 new_guild_id, uint8 new_rank); void CreateGuild(const char *guild_name, const char *leader); void settime(uint8 new_hour, uint8 new_min, bool update_world = true); void itemlink(int item_id); @@ -224,6 +227,8 @@ public: void resettaskactivity(int task, int activity); void assigntask(int taskid, bool enforce_level_requirement = false); void failtask(int taskid); + bool completetask(int task_id); + bool uncompletetask(int task_id); int tasktimeleft(int taskid); bool istaskcompleted(int task_id); bool aretaskscompleted(const std::vector& task_ids); @@ -249,7 +254,7 @@ public: int getlevel(uint8 type); int collectitems(uint32 item_id, bool remove); int collectitems_processSlot(int16 slot_id, uint32 item_id, bool remove); - int countitem(uint32 item_id); + uint32 countitem(uint32 item_id); void removeitem(uint32 item_id, uint32 quantity = 1); std::string getitemcomment(uint32 item_id); std::string getitemlore(uint32 item_id); @@ -359,6 +364,8 @@ public: bool SetAutoLoginCharacterNameByAccountID(uint32 account_id, const std::string& character_name); void SpawnCircle(uint32 npc_id, glm::vec4 position, float radius, uint32 points); void SpawnGrid(uint32 npc_id, glm::vec4 position, float spacing, uint32 spawn_count); + std::vector GetPausedTimers(Mob* m); + std::vector GetTimers(Mob* m); Bot *GetBot() const; Client *GetInitiator() const; @@ -376,9 +383,10 @@ public: bool botquest(); bool createBot(const char *name, const char *lastname, uint8 level, uint16 race, uint8 botclass, uint8 gender); + bool handin(std::map required); private: - std::stack quests_running_; + std::stack m_running_quests; bool HaveProximitySays; diff --git a/zone/raids.cpp b/zone/raids.cpp index 4dd710e3a..ee7c69f0f 100644 --- a/zone/raids.cpp +++ b/zone/raids.cpp @@ -18,14 +18,15 @@ #include "../common/strings.h" #include "../common/events/player_event_logs.h" +#include "../common/repositories/character_expedition_lockouts_repository.h" #include "../common/repositories/raid_details_repository.h" #include "../common/repositories/raid_members_repository.h" #include "../common/raid.h" #include "client.h" +#include "dynamic_zone.h" #include "entity.h" -#include "expedition.h" #include "groups.h" #include "mob.h" #include "raids.h" @@ -33,9 +34,11 @@ #include "bot.h" #include "worldserver.h" +#include "queryserv.h" -extern EntityList entity_list; +extern EntityList entity_list; extern WorldServer worldserver; +extern QueryServ *QServ; Raid::Raid(uint32 raidID) : GroupIDConsumer(raidID) @@ -240,8 +243,6 @@ void Raid::AddBot(Bot* b, uint32 group, bool raid_leader, bool group_leader, boo SendRaidAddAll(b->GetName()); b->SetRaidGrouped(true); - b->p_raid_instance = this; - auto pack = new ServerPacket(ServerOP_RaidAdd, sizeof(ServerRaidGeneralAction_Struct)); auto* rga = (ServerRaidGeneralAction_Struct*) pack->pBuffer; @@ -267,6 +268,9 @@ void Raid::RemoveMember(const char *character_name) b->SetFollowID(b->GetOwner()->CastToClient()->GetID()); b->SetTarget(nullptr); b->SetRaidGrouped(false); + b->p_raid_instance = nullptr; + b->SetStoredRaid(nullptr); + b->SetVerifiedRaid(false); } disbandCheck = true; @@ -713,7 +717,7 @@ uint32 Raid::GetTotalRaidDamage(Mob* other) return total; } -void Raid::HealGroup(uint32 heal_amt, Mob* caster, uint32 gid, float range) +void Raid::HealGroup(uint32 heal_amount, Mob* caster, uint32 group_id, float range) { if (!caster) { return; @@ -724,26 +728,30 @@ void Raid::HealGroup(uint32 heal_amt, Mob* caster, uint32 gid, float range) } float distance; - float range2 = range*range; + float range_squared = range * range; + + int member_count = 0; - int numMem = 0; for (const auto& m : members) { - if (m.member && m.group_number == gid) { + if (m.member && m.group_number == group_id) { distance = DistanceSquared(caster->GetPosition(), m.member->GetPosition()); - if (distance <= range2) { - numMem += 1; + if (distance <= range_squared) { + member_count += 1; } } } - heal_amt /= numMem; + if (member_count > 0) { + heal_amount /= member_count; + } + for (const auto& m : members) { - if (m.member && m.group_number == gid) { + if (m.member && m.group_number == group_id) { distance = DistanceSquared(caster->GetPosition(), m.member->GetPosition()); - if (distance <= range2) { - m.member->SetHP(m.member->GetHP() + heal_amt); + if (distance <= range_squared) { + m.member->SetHP(m.member->GetHP() + heal_amount); m.member->SendHPUpdate(); } } @@ -751,7 +759,7 @@ void Raid::HealGroup(uint32 heal_amt, Mob* caster, uint32 gid, float range) } -void Raid::BalanceHP(int32 penalty, uint32 gid, float range, Mob* caster, int32 limit) +void Raid::BalanceHP(int32 penalty, uint32 group_id, float range, Mob* caster, int32 limit) { if (!caster) { return; @@ -761,44 +769,48 @@ void Raid::BalanceHP(int32 penalty, uint32 gid, float range, Mob* caster, int32 range = 200; } - int dmgtaken = 0, numMem = 0, dmgtaken_tmp = 0; + int damage_taken = 0; + int damage_taken_temporary = 0; + int member_count = 0; float distance; - float range2 = range*range; + float range_squared = range * range; for (const auto& m : members) { - if (m.member && m.group_number == gid) { + if (m.member && m.group_number == group_id) { distance = DistanceSquared(caster->GetPosition(), m.member->GetPosition()); - if (distance <= range2) { - dmgtaken_tmp = m.member->GetMaxHP() - m.member->GetHP(); + if (distance <= range_squared) { + damage_taken_temporary = m.member->GetMaxHP() - m.member->GetHP(); - if (limit && (dmgtaken_tmp > limit)) { - dmgtaken_tmp = limit; + if (limit && (damage_taken_temporary > limit)) { + damage_taken_temporary = limit; } - dmgtaken += dmgtaken_tmp; - numMem += 1; + damage_taken += damage_taken_temporary; + member_count += 1; } } } - dmgtaken += dmgtaken * penalty / 100; - dmgtaken /= numMem; + damage_taken += damage_taken * penalty / 100; + + if (member_count > 0) { + damage_taken /= member_count; + } + for (const auto& m : members) { - if (m.member && m.group_number == gid) { + if (m.member && m.group_number == group_id) { distance = DistanceSquared(caster->GetPosition(), m.member->GetPosition()); //this way the ability will never kill someone //but it will come darn close - if (distance <= range2) { - if ((m.member->GetMaxHP() - dmgtaken) < 1) { + if (distance <= range_squared) { + if ((m.member->GetMaxHP() - damage_taken) < 1) { m.member->SetHP(1); m.member->SendHPUpdate(); - } - - else { - m.member->SetHP(m.member->GetMaxHP() - dmgtaken); + } else { + m.member->SetHP(m.member->GetMaxHP() - damage_taken); m.member->SendHPUpdate(); } } @@ -806,7 +818,7 @@ void Raid::BalanceHP(int32 penalty, uint32 gid, float range, Mob* caster, int32 } } -void Raid::BalanceMana(int32 penalty, uint32 gid, float range, Mob* caster, int32 limit) +void Raid::BalanceMana(int32 penalty, uint32 group_id, float range, Mob* caster, int32 limit) { if (!caster) { return; @@ -817,54 +829,56 @@ void Raid::BalanceMana(int32 penalty, uint32 gid, float range, Mob* caster, int3 } float distance; - float range2 = range*range; + float range_squared = range * range; - int manataken = 0; - int numMem = 0; - int manataken_tmp = 0; + int mana_taken = 0; + int mana_taken_temporary = 0; + int member_count = 0; for (const auto& m : members) { if (m.is_bot) { continue; } - if (m.member && m.group_number == gid && m.member->GetMaxMana() > 0) { + if (m.member && m.group_number == group_id && m.member->GetMaxMana() > 0) { distance = DistanceSquared(caster->GetPosition(), m.member->GetPosition()); - if (distance <= range2) { - manataken_tmp = m.member->GetMaxMana() - m.member->GetMana(); + if (distance <= range_squared) { + mana_taken_temporary = m.member->GetMaxMana() - m.member->GetMana(); - if (limit && (manataken_tmp > limit)) { - manataken_tmp = limit; + if (limit && (mana_taken_temporary > limit)) { + mana_taken_temporary = limit; } - manataken += manataken_tmp; - numMem += 1; + mana_taken += mana_taken_temporary; + member_count += 1; } } } - manataken += manataken * penalty / 100; - manataken /= numMem; + mana_taken += mana_taken * penalty / 100; + + if (member_count > 0) { + mana_taken /= member_count; + } for (const auto& m : members) { if (m.is_bot) { continue; } - if (m.member && m.group_number == gid) { + if (m.member && m.group_number == group_id) { distance = DistanceSquared(caster->GetPosition(), m.member->GetPosition()); - if (distance <= range2) { - if ((m.member->GetMaxMana() - manataken) < 1) { + if (distance <= range_squared) { + if ((m.member->GetMaxMana() - mana_taken) < 1) { m.member->SetMana(1); if (m.member->IsClient()) { m.member->CastToClient()->SendManaUpdate(); } - } - else { - m.member->SetMana(m.member->GetMaxMana() - manataken); + } else { + m.member->SetMana(m.member->GetMaxMana() - mana_taken); if (m.member->IsClient()) { m.member->CastToClient()->SendManaUpdate(); @@ -951,7 +965,7 @@ void Raid::SplitMoney(uint32 gid, uint32 copper, uint32 silver, uint32 gold, uin true ); - if (player_event_logs.IsEventEnabled(PlayerEvent::SPLIT_MONEY)) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::SPLIT_MONEY)) { auto e = PlayerEvent::SplitMoneyEvent{ .copper = copper_split, .silver = silver_split, @@ -2135,28 +2149,21 @@ std::vector Raid::GetMembers() const return raid_members; } -bool Raid::DoesAnyMemberHaveExpeditionLockout(const std::string& expedition_name, const std::string& event_name, int max_check_count) +bool Raid::AnyMemberHasDzLockout(const std::string& expedition, const std::string& event) { - auto raid_members = GetMembers(); - - if (max_check_count > 0) { - // priority is leader, group number, then ungrouped members - std::sort(raid_members.begin(), raid_members.end(), - [&](const RaidMember& lhs, const RaidMember& rhs) { - if (lhs.is_raid_leader) { - return true; - } else if (rhs.is_raid_leader) { - return false; - } - return lhs.group_number < rhs.group_number; - }); - - raid_members.resize(max_check_count); + std::vector names; + for (const auto& mbr : members) + { + if (!mbr.member && !mbr.is_bot && mbr.member_name[0]) + { + names.emplace_back(mbr.member_name); // out of zone member + } + else if (mbr.member && !mbr.is_bot && mbr.member->HasDzLockout(expedition, event)) + { + return true; + } } - - return std::any_of(raid_members.begin(), raid_members.end(), [&](const RaidMember& raid_member) { - return Expedition::HasLockoutByCharacterName(raid_member.member_name, expedition_name, event_name); - }); + return !CharacterExpeditionLockoutsRepository::GetLockouts(database, names, expedition, event).empty(); } Mob* Raid::GetRaidMainAssistOne() @@ -2628,7 +2635,7 @@ void Raid::RaidClearNPCMarks(Client* c) Strings::EqualFold(main_marker_pcs[MAIN_MARKER_3_SLOT], c->GetCleanName())) { for (int i = 0; i < MAX_MARKED_NPCS; i++) { if (marked_npcs[i].entity_id > 0 && marked_npcs[i].zone_id == c->GetZoneID() - && marked_npcs[i].instance_id == c->GetInstanceID()) + && marked_npcs[i].instance_id == c->GetInstanceID()) { auto npc_name = entity_list.GetNPCByID(marked_npcs[i].entity_id)->GetCleanName(); RaidMessageString(nullptr, Chat::Cyan, RAID_NO_LONGER_MARKED, npc_name); @@ -2953,7 +2960,7 @@ void Raid::SendMarkTargets(Client* c) } for (int i = 0; i < MAX_MARKED_NPCS; i++) { - if (marked_npcs[i].entity_id > 0 && marked_npcs[i].zone_id == c->GetZoneID() + if (marked_npcs[i].entity_id > 0 && marked_npcs[i].zone_id == c->GetZoneID() && marked_npcs[i].instance_id == c->GetInstanceID()) { auto marked_mob = entity_list.GetMob(marked_npcs[i].entity_id); if (marked_mob) { @@ -2970,7 +2977,7 @@ void Raid::SendMarkTargets(Client* c) UpdateXtargetMarkedNPC(); } -void Raid::EmptyRaidMembers() +void Raid::EmptyRaidMembers() { for (int i = 0; i < MAX_RAID_MEMBERS; i++) { members[i].group_number = RAID_GROUPLESS; diff --git a/zone/raids.h b/zone/raids.h index 6fdf13206..05e232984 100644 --- a/zone/raids.h +++ b/zone/raids.h @@ -167,9 +167,9 @@ public: void CastGroupSpell(Mob* caster,uint16 spellid, uint32 gid); void SplitExp(ExpSource exp_source, const uint64 exp, Mob* other); uint32 GetTotalRaidDamage(Mob* other); - void BalanceHP(int32 penalty, uint32 gid, float range = 0, Mob* caster = nullptr, int32 limit = 0); - void BalanceMana(int32 penalty, uint32 gid, float range = 0, Mob* caster = nullptr, int32 limit = 0); - void HealGroup(uint32 heal_amt, Mob* caster, uint32 gid, float range = 0); + void BalanceHP(int32 penalty, uint32 group_id, float range = 0, Mob* caster = nullptr, int32 limit = 0); + void BalanceMana(int32 penalty, uint32 group_id, float range = 0, Mob* caster = nullptr, int32 limit = 0); + void HealGroup(uint32 heal_amount, Mob* caster, uint32 group_id, float range = 0); void SplitMoney(uint32 gid, uint32 copper, uint32 silver, uint32 gold, uint32 platinum, Client *splitter = nullptr); void TeleportGroup(Mob* sender, uint32 zoneID, uint16 instance_id, float x, float y, float z, float heading, uint32 gid); @@ -271,7 +271,7 @@ public: void QueueClients(Mob *sender, const EQApplicationPacket *app, bool ack_required = true, bool ignore_sender = true, float distance = 0, bool group_only = true); - bool DoesAnyMemberHaveExpeditionLockout(const std::string& expedition_name, const std::string& event_name, int max_check_count = 0); + bool AnyMemberHasDzLockout(const std::string& expedition, const std::string& event); std::vector GetMembers() const; std::vector GetRaidGroupMembers(uint32 gid); diff --git a/zone/raycast_mesh.cpp b/zone/raycast_mesh.cpp index 8873c293e..20c3f6b71 100644 --- a/zone/raycast_mesh.cpp +++ b/zone/raycast_mesh.cpp @@ -1,4 +1,6 @@ #include "raycast_mesh.h" +#include "../common/memory/ksm.hpp" +#include "../common/eqemu_logsys.h" #include #include #include @@ -9,7 +11,7 @@ // This code snippet allows you to create an axis aligned bounding volume tree for a triangle mesh so that you can do // high-speed raycasting. // -// There are much better implementations of this available on the internet. In particular I recommend that you use +// There are much better implementations of this available on the internet. In particular I recommend that you use // OPCODE written by Pierre Terdiman. // @see: http://www.codercorner.com/Opcode.htm // @@ -17,7 +19,7 @@ // // I am providing this code snippet for the use case where you *only* want to do quick and dirty optimized raycasting. // I have not done performance testing between this version and OPCODE; so I don't know how much slower it is. However, -// anytime you switch to using a spatial data structure for raycasting, you increase your performance by orders and orders +// anytime you switch to using a spatial data structure for raycasting, you increase your performance by orders and orders // of magnitude; so this implementation should work fine for simple tools and utilities. // // It also serves as a nice sample for people who are trying to learn the algorithm of how to implement AABB trees. @@ -32,14 +34,14 @@ // // The official source can be found at: http://code.google.com/p/raycastmesh/ // -// +// #pragma warning(disable:4100) namespace RAYCAST_MESH { -typedef std::vector< RmUint32 > TriVector; +typedef std::vector> TriVector; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** @@ -365,7 +367,7 @@ public: { RmUint32 ret = 0; - if ( p[0] < mMin[0] ) + if ( p[0] < mMin[0] ) { ret|=CC_MINX; } @@ -374,7 +376,7 @@ public: ret|=CC_MAXX; } - if ( p[1] < mMin[1] ) + if ( p[1] < mMin[1] ) { ret|=CC_MINY; } @@ -383,7 +385,7 @@ public: ret|=CC_MAXY; } - if ( p[2] < mMin[2] ) + if ( p[2] < mMin[2] ) { ret|=CC_MINZ; } @@ -514,7 +516,7 @@ public: // the width of the longest axis is less than the minimum axis size then... // we create the leaf node and copy the triangles into the leaf node triangle array. if ( count < minLeafSize || depth >= maxDepth || laxis < minAxisSize ) - { + { // Copy the triangle indices into the leaf triangles array mLeafTriangleIndex = leafTriangles.size(); // assign the array start location for these leaf triangles. leafTriangles.push_back(count); @@ -542,7 +544,7 @@ public: // and another array that includes all triangles which intersect the 'right' half of the bounding volume node. for (auto i = triangles.begin(); i != triangles.end(); ++i) { - RmUint32 tri = (*i); + RmUint32 tri = (*i); { RmUint32 i1 = indices[tri*3+0]; @@ -590,7 +592,7 @@ public: { leftBounds.clamp(b1); // we have to clamp the bounding volume so it stays inside the parent volume. mLeft = callback->getNode(); // get a new AABB node - new ( mLeft ) NodeAABB(leftBounds); // initialize it to default constructor values. + new ( mLeft ) NodeAABB(leftBounds); // initialize it to default constructor values. // Then recursively split this node. mLeft->split(leftTriangles,vcount,vertices,tcount,indices,depth+1,maxDepth,minLeafSize,minAxisSize,callback,leafTriangles); } @@ -662,7 +664,7 @@ public: RmReal nd = nearestDistance; if ( !intersectLineSegmentAABB(mBounds.mMin,mBounds.mMax,from,dir,nd,sect) ) { - return; + return; } if ( mLeafTriangleIndex != TRI_EOF ) { @@ -754,28 +756,60 @@ public: { mMaxNodeCount+=pow2Table[i]; } - mNodes = new NodeAABB[mMaxNodeCount]; + // Allocate page-aligned memory + mNodes = static_cast(KSM::AllocatePageAligned(sizeof(NodeAABB) * mMaxNodeCount)); + if (!mNodes) { + throw std::bad_alloc(); + } mNodeCount = 0; + KSM::CheckPageAlignment(mNodes); + + mVertices = static_cast(KSM::AllocatePageAligned(sizeof(RmReal) * 3 * vcount)); + if (!mVertices) { + throw std::bad_alloc(); + } + std::memcpy(mVertices, vertices, sizeof(RmReal) * 3 * vcount); mVcount = vcount; - mVertices = (RmReal *)::malloc(sizeof(RmReal)*3*vcount); - memcpy(mVertices,vertices,sizeof(RmReal)*3*vcount); + + mIndices = static_cast(KSM::AllocatePageAligned(sizeof(RmUint32) * 3 * tcount)); + if (!mIndices) { + throw std::bad_alloc(); + } + std::memcpy(mIndices, indices, sizeof(RmUint32) * 3 * tcount); mTcount = tcount; - mIndices = (RmUint32 *)::malloc(sizeof(RmUint32)*tcount*3); - memcpy(mIndices,indices,sizeof(RmUint32)*tcount*3); - mRaycastTriangles = (RmUint32 *)::malloc(tcount*sizeof(RmUint32)); - memset(mRaycastTriangles,0,tcount*sizeof(RmUint32)); + + mRaycastTriangles = static_cast(KSM::AllocatePageAligned(sizeof(RmUint32) * tcount)); + if (!mRaycastTriangles) { + throw std::bad_alloc(); + } + std::memset(mRaycastTriangles, 0, sizeof(RmUint32) * tcount); + + mFaceNormals = static_cast(KSM::AllocatePageAligned(sizeof(RmReal) * 3 * tcount)); + if (!mFaceNormals) { + throw std::bad_alloc(); + } + std::memset(mFaceNormals, 0, sizeof(RmReal) * 3 * tcount); + + // Mark memory as mergeable for KSM + KSM::MarkMemoryForKSM(mVertices, sizeof(RmReal) * 3 * vcount); + KSM::MarkMemoryForKSM(mIndices, sizeof(RmUint32) * 3 * tcount); + KSM::MarkMemoryForKSM(mRaycastTriangles, sizeof(RmUint32) * tcount); + KSM::MarkMemoryForKSM(mFaceNormals, sizeof(RmReal) * 3 * tcount); + mRoot = getNode(); mFaceNormals = NULL; new ( mRoot ) NodeAABB(mVcount,mVertices,mTcount,mIndices,maxDepth,minLeafSize,minAxisSize,this,mLeafTriangles); + + KSM::MarkMemoryForKSM(mLeafTriangles.data(), mLeafTriangles.size() * sizeof(RmUint32)); } ~MyRaycastMesh(void) { - delete []mNodes; - ::free(mVertices); - ::free(mIndices); - ::free(mFaceNormals); - ::free(mRaycastTriangles); + if (mNodes) { free(mNodes); } + if (mVertices) { free(mVertices); } + if (mIndices) { free(mIndices); } + if (mRaycastTriangles) { free(mRaycastTriangles); } + if (mFaceNormals) { free(mFaceNormals); } } virtual bool raycast(const RmReal *from,const RmReal *to,RmReal *hitLocation,RmReal *hitNormal,RmReal *hitDistance) @@ -812,7 +846,7 @@ public: return mRoot->mBounds.mMax; } - virtual NodeAABB * getNode(void) + virtual NodeAABB * getNode(void) { assert( mNodeCount < mMaxNodeCount ); NodeAABB *ret = &mNodes[mNodeCount]; @@ -820,7 +854,7 @@ public: return ret; } - virtual void getFaceNormal(RmUint32 tri,RmReal *faceNormal) + virtual void getFaceNormal(RmUint32 tri,RmReal *faceNormal) { if ( mFaceNormals == NULL ) { @@ -938,6 +972,29 @@ RaycastMesh * createRaycastMesh(RmUint32 vcount, // The number of vertices in t ) { auto m = new MyRaycastMesh(vcount, vertices, tcount, indices, maxDepth, minLeafSize, minAxisSize); + + // Calculate memory usage + size_t vertex_size = vcount * sizeof(RmReal) * 3; // Each vertex has 3 floats + size_t index_size = tcount * 3 * sizeof(RmUint32); // Each triangle has 3 indices + size_t bvh_node_size = m->mNodeCount * sizeof(NodeAABB); // BVH Node memory usage + size_t bvh_leaf_size = m->mLeafTriangles.size() * sizeof(RmUint32); // BVH leaf triangles + + size_t bvh_size = bvh_node_size + bvh_leaf_size; // Total BVH size + size_t total_size = vertex_size + index_size + bvh_size; + + KSM::CheckPageAlignment(m->mNodes); + KSM::CheckPageAlignment(m->mVertices); + + LogInfo( + "Map Raycast Memory Usage | Vertices [{:.2f}] MB Indices [{:.2f}] MB BVH Nodes [{:.2f}] MB BVH Leaves [{:.2f}] MB BVH Total [{:.2f}] MB", + vertex_size / (1024.0 * 1024.0), + index_size / (1024.0 * 1024.0), + bvh_node_size / (1024.0 * 1024.0), + bvh_leaf_size / (1024.0 * 1024.0), + bvh_size / (1024.0 * 1024.0) + ); + LogInfo("Total Raycast Memory [{:.2f}] MB", total_size / (1024.0 * 1024.0)); + return static_cast< RaycastMesh * >(m); } @@ -984,12 +1041,12 @@ MyRaycastMesh::MyRaycastMesh(std::vector& rm_buffer) return; char* buf = rm_buffer.data(); - + chunk_size = sizeof(RmUint32); memcpy(&mVcount, buf, chunk_size); buf += chunk_size; bytes_read += chunk_size; - + chunk_size = (sizeof(RmReal) * (3 * mVcount)); mVertices = (RmReal *)::malloc(chunk_size); memcpy(mVertices, buf, chunk_size); @@ -1037,7 +1094,7 @@ MyRaycastMesh::MyRaycastMesh(std::vector& rm_buffer) buf += chunk_size; bytes_read += chunk_size; } - + chunk_size = sizeof(RmUint32); memcpy(&mNodeCount, buf, chunk_size); buf += chunk_size; @@ -1071,7 +1128,7 @@ MyRaycastMesh::MyRaycastMesh(std::vector& rm_buffer) mNodes[index].mLeft = &mNodes[lNodeIndex]; buf += chunk_size; bytes_read += chunk_size; - + RmUint32 rNodeIndex; chunk_size = sizeof(RmUint32); memcpy(&rNodeIndex, buf, chunk_size); @@ -1106,7 +1163,7 @@ MyRaycastMesh::MyRaycastMesh(std::vector& rm_buffer) void MyRaycastMesh::serialize(std::vector& rm_buffer) { rm_buffer.clear(); - + size_t rm_buffer_size_ = 0; rm_buffer_size_ += sizeof(RmUint32); // mVcount diff --git a/zone/shared_task_zone_messaging.cpp b/zone/shared_task_zone_messaging.cpp index 8009dc944..f3cbe0983 100644 --- a/zone/shared_task_zone_messaging.cpp +++ b/zone/shared_task_zone_messaging.cpp @@ -20,7 +20,7 @@ void SharedTaskZoneMessaging::HandleWorldMessage(ServerPacket *pack) ->AcceptNewTask( c, (int) p->requested_task_id, - (int) p->requested_npc_type_id, + (int) p->requested_npc_entity_id, p->accept_time ); c->LoadClientTaskState(); @@ -157,7 +157,7 @@ void SharedTaskZoneMessaging::HandleWorldMessage(ServerPacket *pack) for (auto &client: entity_list.GetClientList()) { Client *c = client.second; - task_manager->SyncClientSharedTaskState(c, c->GetTaskState()); + TaskManager::Instance()->SyncClientSharedTaskState(c, c->GetTaskState()); c->RemoveClientTaskState(); c->LoadClientTaskState(); } diff --git a/zone/sidecar_api/loot_simulator_controller.cpp b/zone/sidecar_api/loot_simulator_controller.cpp index 1a91ab3fd..ad61157ef 100644 --- a/zone/sidecar_api/loot_simulator_controller.cpp +++ b/zone/sidecar_api/loot_simulator_controller.cpp @@ -11,7 +11,7 @@ void SidecarApi::LootSimulatorController(const httplib::Request &req, httplib::R auto iterations = 100; auto log_enabled = false; - LogSys.log_settings[Logs::Loot].log_to_console = 0; + EQEmuLogSys::Instance()->log_settings[Logs::Loot].log_to_console = 0; nlohmann::json j; diff --git a/zone/spawn2.cpp b/zone/spawn2.cpp index f71dbe31a..9127ab3e6 100644 --- a/zone/spawn2.cpp +++ b/zone/spawn2.cpp @@ -16,6 +16,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include #include "../common/global_define.h" #include "../common/strings.h" @@ -33,6 +34,7 @@ #include "../common/repositories/spawn2_repository.h" #include "../common/repositories/spawn2_disabled_repository.h" #include "../common/repositories/respawn_times_repository.h" +#include "../common/repositories/zone_state_spawns_repository.h" extern EntityList entity_list; extern Zone* zone; @@ -85,9 +87,9 @@ Spawn2::Spawn2(uint32 in_spawn2_id, uint32 spawngroup_id, x = in_x; y = in_y; z = in_z; - heading = in_heading; - respawn_ = respawn; - variance_ = variance; + heading = in_heading; + m_respawn_time = respawn; + variance_ = variance; grid_ = grid; path_when_zone_idle = in_path_when_zone_idle; condition_id = in_cond_id; @@ -95,6 +97,7 @@ Spawn2::Spawn2(uint32 in_spawn2_id, uint32 spawngroup_id, npcthis = nullptr; enabled = in_enabled; this->anim = anim; + currentnpcid = 0; if(timeleft == 0xFFFFFFFF) { //special disable timeleft @@ -115,7 +118,7 @@ Spawn2::~Spawn2() uint32 Spawn2::resetTimer() { - uint32 rspawn = respawn_ * 1000; + uint32 rspawn = m_respawn_time * 1000; if (variance_ != 0) { int var_over_2 = (variance_ * 1000) / 2; @@ -150,12 +153,12 @@ uint32 Spawn2::despawnTimer(uint32 despawn_timer) bool Spawn2::Process() { IsDespawned = false; - if (!Enabled()) + if (!Enabled()) { return true; + } //grab our spawn group SpawnGroup *spawn_group = zone->spawn_group_list.GetSpawnGroup(spawngroup_id_); - if (NPCPointerValid() && (spawn_group && spawn_group->despawn == 0 || condition_id != 0)) { return true; } @@ -188,14 +191,24 @@ bool Spawn2::Process() { return false; } - uint16 condition_value=1; - + uint16 condition_value = 1; if (condition_id > 0) { - condition_value = zone->spawn_conditions.GetCondition(zone->GetShortName(), zone->GetInstanceID(), condition_id); + condition_value = zone->spawn_conditions.GetCondition( + zone->GetShortName(), + zone->GetInstanceID(), + condition_id + ); } //have the spawn group pick an NPC for us - uint32 npcid = spawn_group->GetNPCType(condition_value); + uint32 npcid = 0; + if (m_resumed_npc_id > 0) { + npcid = m_resumed_npc_id; + m_resumed_npc_id = 0; + } else { + npcid = spawn_group->GetNPCType(condition_value); + } + if (npcid == 0) { LogSpawns("Spawn2 [{}]: Spawn group [{}] did not yeild an NPC! not spawning", spawn2_id, spawngroup_id_); @@ -264,13 +277,31 @@ bool Spawn2::Process() { } } - NPC *npc = new NPC(tmp, this, glm::vec4(x, y, z, heading), GravityBehavior::Water); + // zone state restore + if (m_stored_location != glm::vec4(0, 0, -1000, 0)) { + loc = m_stored_location; + m_stored_location = glm::vec4(0, 0, -1000, 0); + } + + NPC *npc = new NPC(tmp, this, loc, GravityBehavior::Water); npcthis = npc; + + if (!m_entity_variables.empty()) { + for (auto &var : m_entity_variables) { + npc->SetEntityVariable(var.first, var.second); + } + m_entity_variables = {}; + } + + npc->SetResumedFromZoneSuspend(m_resumed_from_zone_suspend); + m_resumed_from_zone_suspend = false; + npc->AddLootTable(); if (npc->DropsGlobalLoot()) { npc->CheckGlobalLootTables(); } + npc->SetSpawnGroupId(spawngroup_id_); npc->SaveGuardPointAnim(anim); npc->SetAppearance((EmuAppearance) anim); @@ -351,6 +382,7 @@ void Spawn2::LoadGrid(int start_wp) { void Spawn2::Reset() { timer.Start(resetTimer()); npcthis = nullptr; + currentnpcid = 0; LogSpawns("Spawn2 [{}]: Spawn reset, repop in [{}] ms", spawn2_id, timer.GetRemainingTime()); } @@ -358,6 +390,7 @@ void Spawn2::Depop() { timer.Disable(); LogSpawns("Spawn2 [{}]: Spawn reset, repop disabled", spawn2_id); npcthis = nullptr; + currentnpcid = 0; } void Spawn2::Repop(uint32 delay) { @@ -369,6 +402,7 @@ void Spawn2::Repop(uint32 delay) { timer.Start(delay); } npcthis = nullptr; + currentnpcid = 0; } void Spawn2::ForceDespawn() @@ -387,12 +421,14 @@ void Spawn2::ForceDespawn() npcthis->Depop(true); IsDespawned = true; npcthis = nullptr; + currentnpcid = 0; return; } else { npcthis->Depop(false); npcthis = nullptr; + currentnpcid = 0; } } } @@ -424,6 +460,7 @@ void Spawn2::DeathReset(bool realdeath) //zero out our NPC since he is now gone npcthis = nullptr; + currentnpcid = 0; if(realdeath) { killcount++; } @@ -498,6 +535,14 @@ bool ZoneDatabase::PopulateZoneSpawnList(uint32 zoneid, LinkedList &spa ); } + NPC::SpawnZoneController(); + + if (RuleB(Zone, StateSavingOnShutdown) && zone->LoadZoneState(spawn_times, disabled_spawns)) { + LogZoneState("Loaded zone state for zone [{}] instance_id [{}]", zone_name, zone->GetInstanceID()); + return true; + } + + // normal spawn2 loading for (auto &s: spawns) { uint32 spawn_time_left = 0; if (spawn_times.count(s.id) != 0) { @@ -533,12 +578,11 @@ bool ZoneDatabase::PopulateZoneSpawnList(uint32 zoneid, LinkedList &spa ); spawn2_list.Insert(new_spawn); + new_spawn->Process(); } LogInfo("Loaded [{}] spawn2 entries", Strings::Commify(l.size())); - NPC::SpawnZoneController(); - return true; } @@ -631,6 +675,7 @@ void Spawn2::SpawnConditionChanged(const SpawnCondition &c, int16 old_value) { LogSpawns("Spawn2 [{}]: Our npcthis is currently not null. The zone thinks it is [{}]. Forcing a depop", spawn2_id, npcthis->GetName()); npcthis->Depop(false); //remove the current mob npcthis = nullptr; + currentnpcid = 0; } if(new_state) { // only get repawn timer remaining when the SpawnCondition is enabled. timer_remaining = database.GetSpawnTimeLeft(spawn2_id,zone->GetInstanceID()); diff --git a/zone/spawn2.h b/zone/spawn2.h index e16fb4a72..f5b31022a 100644 --- a/zone/spawn2.h +++ b/zone/spawn2.h @@ -55,10 +55,10 @@ public: float GetZ() { return z; } float GetHeading() { return heading; } bool PathWhenZoneIdle() { return path_when_zone_idle; } - void SetRespawnTimer(uint32 newrespawntime) { respawn_ = newrespawntime; }; + void SetRespawnTimer(uint32 newrespawntime) { m_respawn_time = newrespawntime; }; void SetVariance(uint32 newvariance) { variance_ = newvariance; } const uint32 GetVariance() const { return variance_; } - uint32 RespawnTimer() { return respawn_; } + uint32 RespawnTimer() { return m_respawn_time; } uint32 SpawnGroupID() { return spawngroup_id_; } uint32 CurrentNPCID() { return currentnpcid; } void SetCurrentNPCID(uint32 nid) { currentnpcid = nid; } @@ -69,13 +69,24 @@ public: void SetNPCPointerNull() { npcthis = nullptr; } Timer GetTimer() { return timer; } void SetTimer(uint32 duration) { timer.Start(duration); } - uint32 GetKillCount() { return killcount; } + uint32 GetKillCount() { return killcount; } + uint32 GetGrid() const { return grid_; } + bool GetPathWhenZoneIdle() const { return path_when_zone_idle; } + int16 GetConditionMinValue() const { return condition_min_value; } + int16 GetAnimation () { return anim; } + inline NPC *GetNPC() const { return npcthis; } + inline bool IsResumedFromZoneSuspend() const { return m_resumed_from_zone_suspend; } + inline void SetResumedFromZoneSuspend(bool resumed) { m_resumed_from_zone_suspend = resumed; } + inline void SetEntityVariables(std::map vars) { m_entity_variables = vars; } + inline void SetResumedNPCID(uint32 npc_id) { m_resumed_npc_id = npc_id; } + inline void SetStoredLocation(const glm::vec4& loc) { m_stored_location = loc; } + protected: friend class Zone; Timer timer; private: - uint32 spawn2_id; - uint32 respawn_; + uint32 spawn2_id; + uint32 m_respawn_time; uint32 resetTimer(); uint32 despawnTimer(uint32 despawn_timer); @@ -95,6 +106,10 @@ private: EmuAppearance anim; bool IsDespawned; uint32 killcount; + bool m_resumed_from_zone_suspend = false; + uint32 m_resumed_npc_id = 0; + std::map m_entity_variables = {}; + glm::vec4 m_stored_location = {0, 0, -1000, 0}; // use -1000 to indicate unset/zero-state }; class SpawnCondition { diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index 5e9c6f792..57feb3f2e 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -25,6 +25,7 @@ #include "string_ids.h" #include "lua_parser.h" #include "npc.h" +#include "bot.h" #include @@ -351,9 +352,9 @@ void Client::OPCombatAbility(const CombatAbility_Struct *ca_atk) if (ca_atk->m_skill == EQ::skills::SkillBash) { // SLAM - Bash without a shield equipped switch (GetRace()) { - case OGRE: - case TROLL: - case BARBARIAN: + case Race::Ogre: + case Race::Troll: + case Race::Barbarian: bypass_skill_check = true; default: break; @@ -709,54 +710,74 @@ int Mob::MonkSpecialAttack(Mob *other, uint8 unchecked_type) } void Mob::TryBackstab(Mob *other, int ReuseTime) { - if(!other) + if (!other) { return; + } bool bIsBehind = false; bool bCanFrontalBS = false; //make sure we have a proper weapon if we are a client. - if(IsClient()) { + if (IsClient()) { const EQ::ItemInstance *wpn = CastToClient()->GetInv().GetItem(EQ::invslot::slotPrimary); + if (!wpn || (wpn->GetItem()->ItemType != EQ::item::ItemType1HPiercing)){ MessageString(Chat::Red, BACKSTAB_WEAPON); return; } } + else if (IsBot()) { + auto bot = CastToBot(); + auto inst = bot->GetBotItem(EQ::invslot::slotPrimary); + auto bot_piercer = inst ? inst->GetItem() : nullptr; + + if (!bot_piercer || bot_piercer->ItemType != EQ::item::ItemType1HPiercing) { + if (!bot->GetCombatRoundForAlerts()) { + bot->SetCombatRoundForAlerts(); + bot->RaidGroupSay("I can't backstab with this weapon!"); + } + return; + } + } //Live AA - Triple Backstab int tripleChance = itembonuses.TripleBackstab + spellbonuses.TripleBackstab + aabonuses.TripleBackstab; - if (BehindMob(other, GetX(), GetY())) + if (BehindMob(other, GetX(), GetY())) { bIsBehind = true; - + } else { //Live AA - Seized Opportunity int FrontalBSChance = itembonuses.FrontalBackstabChance + spellbonuses.FrontalBackstabChance + aabonuses.FrontalBackstabChance; - if (FrontalBSChance && zone->random.Roll(FrontalBSChance)) + if (FrontalBSChance && zone->random.Roll(FrontalBSChance)) { bCanFrontalBS = true; + } } if (bIsBehind || bCanFrontalBS || (IsNPC() && CanFacestab())) { // Player is behind other OR can do Frontal Backstab - if (bCanFrontalBS && IsClient()) // I don't think there is any message ... - CastToClient()->Message(Chat::White,"Your fierce attack is executed with such grace, your target did not see it coming!"); + if (bCanFrontalBS && IsClient()) { // I don't think there is any message ... + CastToClient()->Message(Chat::White, "Your fierce attack is executed with such grace, your target did not see it coming!"); + } RogueBackstab(other,false,ReuseTime); + if (level >= RuleI(Combat, DoubleBackstabLevelRequirement)) { // TODO: 55-59 doesn't appear to match just checking double attack, 60+ does though - if(IsClient() && CastToClient()->CheckDoubleAttack()) - { - if(other->GetHP() > 0) - RogueBackstab(other,false,ReuseTime); + if(IsOfClientBot() && CastToClient()->CheckDoubleAttack()) { + if (other->GetHP() > 0) { + RogueBackstab(other, false, ReuseTime); + } - if (tripleChance && other->GetHP() > 0 && zone->random.Roll(tripleChance)) - RogueBackstab(other,false,ReuseTime); + if (tripleChance && other->GetHP() > 0 && zone->random.Roll(tripleChance)) { + RogueBackstab(other, false, ReuseTime); + } } } - if(IsClient()) + if (IsClient()) { CastToClient()->CheckIncreaseSkill(EQ::skills::SkillBackstab, other, 10); + } } //Live AA - Chaotic Backstab @@ -766,14 +787,20 @@ void Mob::TryBackstab(Mob *other, int ReuseTime) { //we can stab from any angle, we do min damage though. // chaotic backstab can't double etc Seized can, but that's because it's a chance to do normal BS // Live actually added SPA 473 which grants chance to double here when they revamped chaotic/seized + RogueBackstab(other, true, ReuseTime); - if(IsClient()) + + if (IsClient()) { CastToClient()->CheckIncreaseSkill(EQ::skills::SkillBackstab, other, 10); + } + m_specialattacks = eSpecialAttacks::None; int double_bs_front = aabonuses.Double_Backstab_Front + itembonuses.Double_Backstab_Front + spellbonuses.Double_Backstab_Front; - if (double_bs_front && other->GetHP() > 0 && zone->random.Roll(double_bs_front)) + + if (double_bs_front && other->GetHP() > 0 && zone->random.Roll(double_bs_front)) { RogueBackstab(other, false, ReuseTime); + } } else { //We do a single regular attack if we attack from the front without chaotic stab Attack(other, EQ::invslot::slotPrimary); @@ -790,10 +817,25 @@ void Mob::RogueBackstab(Mob* other, bool min_damage, int ReuseTime) // make sure we can hit (bane, magical, etc) if (IsClient()) { - const EQ::ItemInstance *wpn = CastToClient()->GetInv().GetItem(EQ::invslot::slotPrimary); - if (!GetWeaponDamage(other, wpn)) + const EQ::ItemInstance* wpn = CastToClient()->GetInv().GetItem(EQ::invslot::slotPrimary); + + if (!GetWeaponDamage(other, wpn)) { return; - } else if (!GetWeaponDamage(other, (const EQ::ItemData*)nullptr)){ + } + } + else if (IsBot()) { + EQ::ItemInstance* botweaponInst = CastToBot()->GetBotItem(EQ::invslot::slotPrimary); + + if (botweaponInst) { + if (!GetWeaponDamage(other, botweaponInst)) { + return; + } + } + else if (!GetWeaponDamage(other, (const EQ::ItemData*)nullptr)) { + return; + } + } + else if (!GetWeaponDamage(other, (const EQ::ItemData*)nullptr)) { return; } @@ -1865,7 +1907,7 @@ void NPC::DoClassAttacks(Mob *target) { target->GetBodyType() != BodyType::Undead && taunt_time && type_of_pet && - type_of_pet != petTargetLock && + type_of_pet != PetType::TargetLock && DistanceSquared(GetPosition(), target->GetPosition()) <= (RuleI(Pets, PetTauntRange) * RuleI(Pets, PetTauntRange)) ) { GetOwner()->MessageString(Chat::PetResponse, PET_TAUNTING); @@ -2408,7 +2450,7 @@ int Mob::TryHeadShot(Mob *defender, EQ::skills::SkillType skillInUse) // Only works on YOUR target. if ( defender && - !defender->IsClient() && + !defender->IsOfClientBot() && skillInUse == EQ::skills::SkillArchery && GetTarget() == defender && (defender->GetBodyType() == BodyType::Humanoid || !RuleB(Combat, HeadshotOnlyHumanoids)) && @@ -2421,7 +2463,7 @@ int Mob::TryHeadShot(Mob *defender, EQ::skills::SkillType skillInUse) if (HeadShot_Dmg && HeadShot_Level && (defender->GetLevel() <= HeadShot_Level)) { int chance = GetDEX(); chance = 100 * chance / (chance + 3500); - if (IsClient() || IsBot()) { + if (IsOfClientBot()) { chance += GetHeroicDEX() / 25; } chance *= 10; @@ -2445,7 +2487,7 @@ int Mob::TryAssassinate(Mob *defender, EQ::skills::SkillType skillInUse) { if ( defender && - !defender->IsClient() && + !defender->IsOfClientBot() && GetLevel() >= RuleI(Combat, AssassinateLevelRequirement) && (skillInUse == EQ::skills::SkillBackstab || skillInUse == EQ::skills::SkillThrowing) && (defender->GetBodyType() == BodyType::Humanoid || !RuleB(Combat, AssassinateOnlyHumanoids)) && @@ -2454,7 +2496,7 @@ int Mob::TryAssassinate(Mob *defender, EQ::skills::SkillType skillInUse) int chance = GetDEX(); if (skillInUse == EQ::skills::SkillBackstab) { chance = 100 * chance / (chance + 3500); - if (IsClient() || IsBot()) { + if (IsOfClientBot()) { chance += GetHeroicDEX(); } chance *= 10; diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 7940a7c8e..42078e322 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -109,7 +109,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove else { if(IsBuffSpell(spell_id)){ - if(IsEffectInSpell(spell_id, SE_BindSight)) + if(IsEffectInSpell(spell_id, SpellEffect::BindSight)) { if(caster) { @@ -219,7 +219,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove if (!IsDistanceModifierSpell(spell_id)) SetSpellPowerDistanceMod(0); - bool spell_trigger_cast_complete = false; //Used with SE_Spell_Trigger and SE_Chance_Best_in_Spell_Grp, true when spell has been triggered. + bool spell_trigger_cast_complete = false; //Used with SpellEffect::Spell_Trigger and SpellEffect::Chance_Best_in_Spell_Grp, true when spell has been triggered. // if buff slot, use instrument mod there, otherwise calc it uint32 instrument_mod = buffslot > -1 ? buffs[buffslot].instrument_mod : caster ? caster->GetInstrumentMod(spell_id) : 10; @@ -241,7 +241,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove //Prevents effect from being applied if (spellbonuses.NegateEffects) { - if (effect != SE_NegateSpellEffect && NegateSpellEffect(spell_id, effect)) { + if (effect != SpellEffect::NegateSpellEffect && NegateSpellEffect(spell_id, effect)) { if (caster) { caster->Message(Chat::Red, "Part or all of this spell has lost its effectiveness."); //Placeholder msg, until live one is obtained. } @@ -254,12 +254,12 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove #endif switch(effect) { - case SE_CurrentHP: // nukes, heals; also regen/dot if a buff + case SpellEffect::CurrentHP: // nukes, heals; also regen/dot if a buff { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Current Hitpoints: %+i", effect_value); #endif - // SE_CurrentHP is calculated at first tick if its a dot/buff + // SpellEffect::CurrentHP is calculated at first tick if its a dot/buff if (buffslot >= 0) { //This is here so dots with hit counters tic down on initial cast. if (caster && effect_value < 0) { @@ -315,7 +315,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_CurrentHPOnce: // used in buffs usually, see Courage + case SpellEffect::CurrentHPOnce: // used in buffs usually, see Courage { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Current Hitpoints Once: %+i", effect_value); @@ -390,7 +390,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove } else if (dmg > 0) { //do not apply focus/critical to buff spells - if (caster && !IsEffectInSpell(spell_id, SE_TotalHP)) { + if (caster && !IsEffectInSpell(spell_id, SpellEffect::TotalHP)) { dmg = caster->GetActSpellHealing(spell_id, dmg, this); } HealDamage(dmg, caster); @@ -399,7 +399,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_PercentalHeal: + case SpellEffect::PercentalHeal: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Percental Heal: %+i (%d%% max)", spell.max_value[i], effect_value); @@ -434,7 +434,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_CompleteHeal: + case SpellEffect::CompleteHeal: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Complete Heal"); @@ -450,7 +450,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_CurrentMana: + case SpellEffect::CurrentMana: { // Bards don't get mana from effects, good or bad. if(GetClass() == Class::Bard) @@ -487,7 +487,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_CurrentManaOnce: + case SpellEffect::CurrentManaOnce: { // Bards don't get mana from effects, good or bad. if(GetClass() == Class::Bard) @@ -499,7 +499,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_Translocate: + case SpellEffect::Translocate: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Translocate: %s %d %d %d heading %d", @@ -517,7 +517,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_Succor: + case SpellEffect::Succor: { float x, y, z, heading; @@ -575,7 +575,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove if (IsClient()) { if (HasPet()) { if (RuleB(Spells, EvacClearCharmPet) && GetPet()->IsCharmed()) { - GetPet()->BuffFadeByEffect(SE_Charm); + GetPet()->BuffFadeByEffect(SpellEffect::Charm); } } @@ -598,9 +598,9 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_GateCastersBindpoint: // Used on Teleport Bind. - case SE_Teleport: // gates, rings, circles, etc - case SE_Teleport2: + case SpellEffect::GateCastersBindpoint: // Used on Teleport Bind. + case SpellEffect::Teleport: // gates, rings, circles, etc + case SpellEffect::Teleport2: { float x, y, z, heading; const char *target_zone = nullptr; @@ -630,7 +630,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove } } - if (effect == SE_GateCastersBindpoint && caster && caster->IsClient()) + if (effect == SpellEffect::GateCastersBindpoint && caster && caster->IsClient()) { // Teleport Bind uses caster's bind point int index = spells[spell_id].base_value[i] - 1; if (index < 0 || index > 4) @@ -646,11 +646,11 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove #ifdef SPELL_EFFECT_SPAM const char *efstr = "Teleport"; - if(effect == SE_Teleport) + if(effect == SpellEffect::Teleport) efstr = "Teleport v1"; - else if(effect == SE_Teleport2) + else if(effect == SpellEffect::Teleport2) efstr = "Teleport v2"; - else if(effect == SE_Succor) + else if(effect == SpellEffect::Succor) efstr = "Succor"; snprintf(effect_desc, _EDLEN, @@ -672,7 +672,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_FleshToBone: + case SpellEffect::FleshToBone: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Flesh To Bone"); @@ -701,7 +701,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_GroupFearImmunity:{ + case SpellEffect::GroupFearImmunity:{ #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Group Fear Immunity"); #endif @@ -726,7 +726,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_AddFaction: + case SpellEffect::AddFaction: { if (caster && !IsPet() && GetPrimaryFaction() > 0) { caster->AddFactionBonus(GetPrimaryFaction(),effect_value); @@ -734,7 +734,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_Stun: + case SpellEffect::Stun: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Stun: %d msec", effect_value); @@ -777,7 +777,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_Charm: + case SpellEffect::Charm: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Charm: %+i (up to lvl %d)", effect_value, spell.max_value[i]); @@ -803,19 +803,19 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove caster->SetPet(this); SetOwnerID(caster->GetID()); - SetPetOrder(SPO_Follow); + SetPetOrder(PetOrder::Follow); SetAppearance(eaStanding); // Client has saved previous pet sit/stand - make all new pets // stand and follow on charm. if (caster->IsClient()) { Client *cpet = caster->CastToClient(); - cpet->SetPetCommandState(PET_BUTTON_SIT,0); - cpet->SetPetCommandState(PET_BUTTON_FOLLOW, 1); - cpet->SetPetCommandState(PET_BUTTON_GUARD, 0); - cpet->SetPetCommandState(PET_BUTTON_STOP, 0); + cpet->SetPetCommandState(PetButton::Sit, PetButtonState::Off); + cpet->SetPetCommandState(PetButton::Follow, PetButtonState::On); + cpet->SetPetCommandState(PetButton::Guard, PetButtonState::Off); + cpet->SetPetCommandState(PetButton::Stop, PetButtonState::Off); } - SetPetType(petCharmed); + SetPetType(PetType::Charmed); // This was done in AddBuff, but we were not a pet yet, so // the target windows didn't get updated. @@ -835,12 +835,11 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove SendAppearancePacket(AppearanceType::Pet, caster->GetID(), true, true); } - if (IsClient()) - { + if (IsClient()) { CastToClient()->AI_Start(); - } else if(IsNPC()) { - CastToNPC()->SetPetSpellID(0); //not a pet spell. - CastToNPC()->ModifyStatsOnCharm(false); + } else if (IsNPC()) { + CastToNPC()->SetPetSpellID(0); //not a pet spell. + CastToNPC()->ModifyStatsOnCharm(false, caster); } bool bBreak = false; @@ -867,9 +866,9 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove } - case SE_SenseDead: - case SE_SenseSummoned: - case SE_SenseAnimals: + case SpellEffect::SenseDead: + case SpellEffect::SenseSummoned: + case SpellEffect::SenseAnimals: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Sense Target: %+i", effect_value); @@ -882,12 +881,12 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove int MessageID = SENSE_UNDEAD; - if(effect == SE_SenseSummoned) + if(effect == SpellEffect::SenseSummoned) { bt = BodyType::Summoned; MessageID = SENSE_SUMMONED; } - else if(effect == SE_SenseAnimals) + else if(effect == SpellEffect::SenseAnimals) { bt = BodyType::Animal; MessageID = SENSE_ANIMAL; @@ -910,7 +909,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_Fear: + case SpellEffect::Fear: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Fear: %+i", effect_value); @@ -939,7 +938,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_BindAffinity: //TO DO: Add support for secondary and tertiary gate abilities + case SpellEffect::BindAffinity: //TO DO: Add support for secondary and tertiary gate abilities { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Bind Affinity"); @@ -953,9 +952,9 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove auto action_packet = new EQApplicationPacket(OP_Action, sizeof(Action_Struct)); Action_Struct* action = (Action_Struct*) action_packet->pBuffer; - auto message_packet = - new EQApplicationPacket(OP_Damage, sizeof(CombatDamage_Struct)); - CombatDamage_Struct *cd = (CombatDamage_Struct *)message_packet->pBuffer; + + static EQApplicationPacket p(OP_Damage, sizeof(CombatDamage_Struct)); + auto cd = (CombatDamage_Struct *) p.pBuffer; action->target = GetID(); action->source = caster ? caster->GetID() : GetID(); @@ -978,16 +977,15 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove caster->CastToClient()->QueuePacket(action_packet); } - CastToClient()->QueuePacket(message_packet); + CastToClient()->QueuePacket(&p); if (caster && caster->IsClient() && caster != this) { - caster->CastToClient()->QueuePacket(message_packet); + caster->CastToClient()->QueuePacket(&p); } CastToClient()->SetBindPoint(spells[spell_id].base_value[i] - 1); Save(); safe_delete(action_packet); - safe_delete(message_packet); } else { if (!zone->CanBind()) { MessageString(Chat::SpellFailure, CANNOT_BIND); @@ -1002,9 +1000,9 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove auto action_packet = new EQApplicationPacket( OP_Action, sizeof(Action_Struct)); Action_Struct* action = (Action_Struct*) action_packet->pBuffer; - auto message_packet = new EQApplicationPacket( - OP_Damage, sizeof(CombatDamage_Struct)); - CombatDamage_Struct *cd = (CombatDamage_Struct *)message_packet->pBuffer; + + static EQApplicationPacket p(OP_Damage, sizeof(CombatDamage_Struct)); + auto cd = (CombatDamage_Struct *) p.pBuffer; action->target = GetID(); action->source = caster ? caster->GetID() : GetID(); @@ -1027,24 +1025,23 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove caster->CastToClient()->QueuePacket(action_packet); } - CastToClient()->QueuePacket(message_packet); + CastToClient()->QueuePacket(&p); if (caster->IsClient() && caster != this) { - caster->CastToClient()->QueuePacket(message_packet); + caster->CastToClient()->QueuePacket(&p); } CastToClient()->SetBindPoint(spells[spell_id].base_value[i] - 1); Save(); safe_delete(action_packet); - safe_delete(message_packet); } } else { auto action_packet = new EQApplicationPacket(OP_Action, sizeof(Action_Struct)); Action_Struct* action = (Action_Struct*) action_packet->pBuffer; - auto message_packet = new EQApplicationPacket( - OP_Damage, sizeof(CombatDamage_Struct)); - CombatDamage_Struct *cd = (CombatDamage_Struct *)message_packet->pBuffer; + + static EQApplicationPacket p(OP_Damage, sizeof(CombatDamage_Struct)); + auto cd = (CombatDamage_Struct *) p.pBuffer; action->target = GetID(); action->source = caster ? caster->GetID() : GetID(); @@ -1067,23 +1064,22 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove caster->CastToClient()->QueuePacket(action_packet); } - CastToClient()->QueuePacket(message_packet); + CastToClient()->QueuePacket(&p); if (caster->IsClient() && caster != this) { - caster->CastToClient()->QueuePacket(message_packet); + caster->CastToClient()->QueuePacket(&p); } CastToClient()->SetBindPoint(spells[spell_id].base_value[i] - 1); Save(); safe_delete(action_packet); - safe_delete(message_packet); } } } break; } - case SE_Gate: + case SpellEffect::Gate: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Gate"); @@ -1098,7 +1094,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_CancelMagic: + case SpellEffect::CancelMagic: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Cancel Magic: %d", effect_value); @@ -1114,7 +1110,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_DispelDetrimental: + case SpellEffect::DispelDetrimental: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Dispel Detrimental: %d", effect_value); @@ -1145,7 +1141,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_DispelBeneficial: + case SpellEffect::DispelBeneficial: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Dispel Beneficial: %d", effect_value); @@ -1172,7 +1168,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_Purify: + case SpellEffect::Purify: { //Attempt to remove up to base amount of detrimental effects (excluding charm, fear, resurrection, and revival sickness). int purify_count = spells[spell_id].base_value[i]; @@ -1183,8 +1179,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove for(int slot = 0; slot < purify_count; slot++) { if (IsValidSpell(buffs[slot].spellid) && IsDetrimentalSpell(buffs[slot].spellid)){ - if (!IsEffectInSpell(buffs[slot].spellid, SE_Charm) && - !IsEffectInSpell(buffs[slot].spellid, SE_Fear) && + if (!IsEffectInSpell(buffs[slot].spellid, SpellEffect::Charm) && + !IsEffectInSpell(buffs[slot].spellid, SpellEffect::Fear) && buffs[slot].spellid != SPELL_RESURRECTION_SICKNESS && buffs[slot].spellid != SPELL_RESURRECTION_SICKNESS2 && buffs[slot].spellid != SPELL_RESURRECTION_SICKNESS3 && @@ -1198,7 +1194,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_Mez: + case SpellEffect::Mez: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Mesmerize"); @@ -1207,7 +1203,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_SummonItem: + case SpellEffect::SummonItem: { const EQ::ItemData *item = database.GetItem(spell.base_value[i]); #ifdef SPELL_EFFECT_SPAM @@ -1243,9 +1239,14 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_SummonItemIntoBag: + case SpellEffect::SummonItemIntoBag: { const EQ::ItemData *item = database.GetItem(spell.base_value[i]); + if (!item) { + Message(Chat::Red, "Unable to summon item %d. Item not found.", spell.base_value[i]); + break; + } + #ifdef SPELL_EFFECT_SPAM const char *itemname = item ? item->Name : "*Unknown Item*"; snprintf(effect_desc, _EDLEN, "Summon Item In Bag: %s (id %d)", itemname, spell.base_value[i]); @@ -1285,10 +1286,10 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_SummonBSTPet: - case SE_NecPet: - case SE_SummonPet: - case SE_Familiar: + case SpellEffect::SummonBSTPet: + case SpellEffect::NecPet: + case SpellEffect::SummonPet: + case SpellEffect::Familiar: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Summon %s: %s", (effect==SE_Familiar)?"Familiar":"Pet", spell.teleport_zone); @@ -1306,25 +1307,25 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove if (IsClient() && pet) { auto c = CastToClient(); if (c->ClientVersionBit() & EQ::versions::maskUFAndLater) { - c->SetPetCommandState(PET_BUTTON_SIT, 0); - c->SetPetCommandState(PET_BUTTON_STOP, 0); - c->SetPetCommandState(PET_BUTTON_REGROUP, 0); - c->SetPetCommandState(PET_BUTTON_FOLLOW, 1); - c->SetPetCommandState(PET_BUTTON_GUARD, 0); + c->SetPetCommandState(PetButton::Sit, PetButtonState::Off); + c->SetPetCommandState(PetButton::Stop, PetButtonState::Off); + c->SetPetCommandState(PetButton::Regroup, PetButtonState::Off); + c->SetPetCommandState(PetButton::Follow, PetButtonState::On); + c->SetPetCommandState(PetButton::Guard, PetButtonState::Off); // Creating pet from spell - taunt always false // If suspended pet - that will be restore there // If logging in, client will send toggle - c->SetPetCommandState(PET_BUTTON_HOLD, 0); - c->SetPetCommandState(PET_BUTTON_GHOLD, 0); - c->SetPetCommandState(PET_BUTTON_FOCUS, 0); - c->SetPetCommandState(PET_BUTTON_SPELLHOLD, 0); + c->SetPetCommandState(PetButton::Hold, PetButtonState::Off); + c->SetPetCommandState(PetButton::GreaterHold, PetButtonState::Off); + c->SetPetCommandState(PetButton::Focus, PetButtonState::Off); + c->SetPetCommandState(PetButton::SpellHold, PetButtonState::Off); } } } break; } - case SE_DivineAura: + case SpellEffect::DivineAura: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Invulnerability"); @@ -1337,7 +1338,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_ShadowStep: + case SpellEffect::ShadowStep: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Shadow Step: %d", effect_value); @@ -1350,7 +1351,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_Blind: + case SpellEffect::Blind: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Blind: %+i", effect_value); @@ -1359,7 +1360,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove if (IsBeneficialSpell(spell_id) && spells[spell_id].buff_duration == 0) { int buff_count = GetMaxBuffSlots(); for (int slot = 0; slot < buff_count; slot++) { - if (IsValidSpell(buffs[slot].spellid) && IsEffectInSpell(buffs[slot].spellid, SE_Blind)) { + if (IsValidSpell(buffs[slot].spellid) && IsEffectInSpell(buffs[slot].spellid, SpellEffect::Blind)) { if (caster && TryDispel(caster->GetCasterLevel(spell_id), buffs[slot].casterlevel, 1)) { BuffFadeBySlot(slot); slot = buff_count; @@ -1373,7 +1374,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_Rune: + case SpellEffect::Rune: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Melee Absorb Rune: %+i", effect_value); @@ -1384,7 +1385,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_AbsorbMagicAtt: + case SpellEffect::AbsorbMagicAtt: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Spell Absorb Rune: %+i", effect_value); @@ -1396,7 +1397,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_MitigateMeleeDamage: + case SpellEffect::MitigateMeleeDamage: { if (buffslot > -1) { buffs[buffslot].melee_rune = spells[spell_id].max_value[i]; @@ -1404,7 +1405,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_MeleeThresholdGuard: + case SpellEffect::MeleeThresholdGuard: { if (buffslot > -1) { buffs[buffslot].melee_rune = spells[spell_id].max_value[i]; @@ -1412,7 +1413,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_SpellThresholdGuard: + case SpellEffect::SpellThresholdGuard: { if (buffslot > -1) { buffs[buffslot].magic_rune = spells[spell_id].max_value[i]; @@ -1420,7 +1421,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_MitigateSpellDamage: + case SpellEffect::MitigateSpellDamage: { if (buffslot > -1) { buffs[buffslot].magic_rune = spells[spell_id].max_value[i]; @@ -1428,7 +1429,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_MitigateDotDamage: + case SpellEffect::MitigateDotDamage: { if (buffslot > -1) { buffs[buffslot].dot_rune = spells[spell_id].max_value[i]; @@ -1436,7 +1437,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_DistanceRemoval: + case SpellEffect::DistanceRemoval: { if (buffslot > -1) { buffs[buffslot].caston_x = int(GetX()); @@ -1446,7 +1447,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_Levitate: + case SpellEffect::Levitate: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Levitate"); @@ -1462,7 +1463,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_DeathSave: { + case SpellEffect::DeathSave: { int16 mod = 0; @@ -1478,20 +1479,36 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_Illusion: + case SpellEffect::Illusion: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Illusion: race %d", effect_value); #endif + if (caster && caster->IsOfClientBot()) { + Mob* target = IsClient() ? static_cast(CastToClient()) : static_cast(CastToBot()); + + if (target && target->GetIllusionBlock()) { + break; + } + } + ApplySpellEffectIllusion(spell_id, caster, buffslot, spells[spell_id].base_value[i], spells[spell_id].limit_value[i], spells[spell_id].max_value[i]); break; } - case SE_IllusionCopy: + case SpellEffect::IllusionCopy: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Illusion Copy"); #endif + if (caster && caster->IsOfClientBot()) { + Mob* target = IsClient() ? static_cast(CastToClient()) : static_cast(CastToBot()); + + if (target && target->GetIllusionBlock()) { + break; + } + } + if(caster && caster->GetTarget()){ SendIllusionPacket ( @@ -1509,13 +1526,13 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_WipeHateList: + case SpellEffect::WipeHateList: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Memory Blur: %d", effect_value); #endif //Memory blur component of Mez spells is not checked again if Mez is recast on a target that is already mezed - if (!CanMemoryBlurFromMez && IsEffectInSpell(spell_id, SE_Mez)) { + if (!CanMemoryBlurFromMez && IsEffectInSpell(spell_id, SpellEffect::Mez)) { break; } @@ -1540,7 +1557,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_SpinTarget: + case SpellEffect::SpinTarget: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Spin: %d", effect_value); @@ -1571,7 +1588,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_EyeOfZomm: + case SpellEffect::EyeOfZomm: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Eye of Zomm"); @@ -1589,7 +1606,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_ReclaimPet: + case SpellEffect::ReclaimPet: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Reclaim Pet"); @@ -1600,7 +1617,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove GetOwnerID() && // I'm a pet caster && // there's a caster caster->GetID() == GetOwnerID() && // and it's my master - GetPetType() != petCharmed + GetPetType() != PetType::Charmed ) { uint16 pet_spellid = CastToNPC()->GetPetSpellID(); @@ -1624,7 +1641,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_BindSight: + case SpellEffect::BindSight: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Bind Sight"); @@ -1636,7 +1653,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_FeignDeath: + case SpellEffect::FeignDeath: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Feign Death"); @@ -1657,7 +1674,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_Sentinel: + case SpellEffect::Sentinel: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Sentinel"); @@ -1678,7 +1695,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_LocateCorpse: + case SpellEffect::LocateCorpse: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Locate Corpse"); @@ -1691,8 +1708,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_SummonToCorpse: - case SE_Revive: + case SpellEffect::SummonToCorpse: + case SpellEffect::Revive: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Revive"); // heh the corpse won't see this @@ -1708,8 +1725,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_ModelSize: - case SE_ChangeHeight: + case SpellEffect::ModelSize: + case SpellEffect::ChangeHeight: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Model Size: %d%%", effect_value); @@ -1727,14 +1744,14 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove } } //Only applies to SPA 89, max value also likely does something, but unknown. - else if (effect == SE_ModelSize && spells[spell_id].limit_value[i]) { + else if (effect == SpellEffect::ModelSize && spells[spell_id].limit_value[i]) { ChangeSize(spells[spell_id].limit_value[i]); } break; } - case SE_Root: + case SpellEffect::Root: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Root: %+i", effect_value); @@ -1750,7 +1767,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_SummonHorse: + case SpellEffect::SummonHorse: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Summon Mount: %s", spell.teleport_zone); @@ -1769,7 +1786,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_SummonCorpse: + case SpellEffect::SummonCorpse: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Summon Corpse: %d", effect_value); @@ -1839,13 +1856,13 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove } else { MessageString(Chat::LightBlue, TARGET_NOT_FOUND); - LogError("[{}] attempted to cast spell id [{}] with spell effect SE_SummonCorpse, but could not cast target into a Client object", GetCleanName(), spell_id); + LogError("[{}] attempted to cast spell id [{}] with spell effect SpellEffect::SummonCorpse, but could not cast target into a Client object", GetCleanName(), spell_id); } } break; } - case SE_SummonCorpseZone: + case SpellEffect::SummonCorpseZone: { if (IsClient()) { Client* client_target = CastToClient(); @@ -1892,13 +1909,13 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove } } else { MessageString(Chat::Spells, TARGET_NOT_FOUND); - LogError("[{}] attempted to cast spell id [{}] with spell effect SE_SummonCorpseZone, but could not cast target into a Client object", GetCleanName(), spell_id); + LogError("[{}] attempted to cast spell id [{}] with spell effect SpellEffect::SummonCorpseZone, but could not cast target into a Client object", GetCleanName(), spell_id); } } break; } - case SE_AddMeleeProc: - case SE_WeaponProc: + case SpellEffect::AddMeleeProc: + case SpellEffect::WeaponProc: { uint16 proc_id = GetProcID(spell_id, i); #ifdef SPELL_EFFECT_SPAM @@ -1913,7 +1930,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_RangedProc: + case SpellEffect::RangedProc: { uint16 procid = GetProcID(spell_id, i); #ifdef SPELL_EFFECT_SPAM @@ -1923,7 +1940,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_DefensiveProc: + case SpellEffect::DefensiveProc: { uint16 procid = GetProcID(spell_id, i); #ifdef SPELL_EFFECT_SPAM @@ -1933,7 +1950,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_NegateAttacks: + case SpellEffect::NegateAttacks: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Melee Negate Attack Rune: %+i", effect_value); @@ -1942,7 +1959,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove buffs[buffslot].hit_number = effect_value; break; } - case SE_AppraiseLDonChest: + case SpellEffect::AppraiseLDonChest: { if(IsNPC()) { @@ -1966,7 +1983,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_DisarmLDoNTrap: + case SpellEffect::DisarmLDoNTrap: { if(IsNPC()) { @@ -1990,7 +2007,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_UnlockLDoNChest: + case SpellEffect::UnlockLDoNChest: { if(IsNPC()) { @@ -2014,7 +2031,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_Lull: + case SpellEffect::Lull: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Lull"); @@ -2023,7 +2040,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_PoisonCounter: + case SpellEffect::PoisonCounter: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Poison Counter: %+i", effect_value); @@ -2056,7 +2073,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_DiseaseCounter: + case SpellEffect::DiseaseCounter: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Disease Counter: %+i", effect_value); @@ -2092,7 +2109,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_CurseCounter: + case SpellEffect::CurseCounter: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Curse Counter: %+i", effect_value); @@ -2128,7 +2145,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_CorruptionCounter: + case SpellEffect::CorruptionCounter: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Corruption Counter: %+i", effect_value); @@ -2161,7 +2178,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_Destroy: + case SpellEffect::Destroy: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Destroy"); @@ -2175,7 +2192,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_TossUp: + case SpellEffect::TossUp: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Toss Up: %d", effect_value); @@ -2186,7 +2203,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_StopRain: + case SpellEffect::StopRain: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Stop Rain"); @@ -2197,7 +2214,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_Sacrifice: + case SpellEffect::Sacrifice: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Sacrifice"); @@ -2209,7 +2226,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_SummonPC: + case SpellEffect::SummonPC: { if (!caster) { break; @@ -2244,7 +2261,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_Silence: + case SpellEffect::Silence: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Silence"); @@ -2253,7 +2270,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_Amnesia: + case SpellEffect::Amnesia: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Amnesia"); @@ -2262,7 +2279,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_CallPet: + case SpellEffect::CallPet: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Call Pet"); @@ -2278,15 +2295,15 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_StackingCommand_Block: - case SE_StackingCommand_Overwrite: + case SpellEffect::StackingCommand_Block: + case SpellEffect::StackingCommand_Overwrite: { // these are special effects used by the buff stuff break; } - case SE_TemporaryPets: //Dook- swarms and wards: + case SpellEffect::TemporaryPets: //Dook- swarms and wards: { if (!caster) break; @@ -2302,7 +2319,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_FadingMemories: //Dook- escape etc + case SpellEffect::FadingMemories: //Dook- escape etc { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Fading Memories"); @@ -2355,7 +2372,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_Rampage: + case SpellEffect::Rampage: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Rampage"); @@ -2373,7 +2390,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_AEMelee: + case SpellEffect::AEMelee: { //old aa @@ -2384,7 +2401,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_AETaunt: + case SpellEffect::AETaunt: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "AE Taunt"); @@ -2400,7 +2417,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_SkillAttack: + case SpellEffect::SkillAttack: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Skill Attack"); @@ -2432,7 +2449,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_WakeTheDead: + case SpellEffect::WakeTheDead: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Wake The Dead"); @@ -2452,7 +2469,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_ArmyOfTheDead: + case SpellEffect::ArmyOfTheDead: { if (caster && caster->IsClient()) { int dur = spells[spell_id].max_value[i]; @@ -2473,7 +2490,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_Doppelganger: + case SpellEffect::Doppelganger: { if(caster && caster->IsClient()) { char pet_name[64]; @@ -2485,12 +2502,12 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_BardAEDot: + case SpellEffect::BardAEDot: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Bard AE Dot: %+i", effect_value); #endif - // SE_CurrentHP is calculated at first tick if its a dot/buff + // SpellEffect::CurrentHP is calculated at first tick if its a dot/buff if (buffslot >= 0) break; //This effect does no damage if target is moving. @@ -2523,7 +2540,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_CurrentEndurance: { + case SpellEffect::CurrentEndurance: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Current Endurance: %+i", effect_value); #endif @@ -2536,7 +2553,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_CurrentEnduranceOnce: + case SpellEffect::CurrentEnduranceOnce: { #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Current Endurance Once: %+i", effect_value); @@ -2551,7 +2568,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_BalanceHP: { + case SpellEffect::BalanceHP: { if(!caster) break; @@ -2579,7 +2596,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_BalanceMana: { + case SpellEffect::BalanceMana: { if(!caster) break; @@ -2607,7 +2624,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_SummonAndResAllCorpses: + case SpellEffect::SummonAndResAllCorpses: { if(IsClient()) CastToClient()->SummonAndRezzAllCorpses(); @@ -2615,14 +2632,14 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_GateToHomeCity: + case SpellEffect::GateToHomeCity: { if(IsClient()) CastToClient()->GoToBind(4); break; } - case SE_SuspendPet: + case SpellEffect::SuspendPet: { if(IsClient()) CastToClient()->SuspendMinion(spell.base_value[i]); @@ -2630,7 +2647,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_FcTimerRefresh: + case SpellEffect::FcTimerRefresh: { if(IsClient()) { for (unsigned int i = 0; i < EQ::spells::SPELL_GEM_COUNT; ++i) { @@ -2649,7 +2666,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_FcTimerLockout: { + case SpellEffect::FcTimerLockout: { if (IsClient()) { for (unsigned int mem_spell : CastToClient()->m_pp.mem_spells) { if (IsValidSpell(mem_spell)) { @@ -2678,7 +2695,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_HealGroupFromMana: { + case SpellEffect::HealGroupFromMana: { if(!caster) break; @@ -2722,7 +2739,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_ManaDrainWithDmg: + case SpellEffect::ManaDrainWithDmg: { int mana_damage = 0; int32 mana_to_use = GetMana() - spell.base_value[i]; @@ -2741,7 +2758,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_EndDrainWithDmg: + case SpellEffect::EndDrainWithDmg: { if(IsClient()) { int end_damage = 0; @@ -2762,16 +2779,16 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_SetBodyType: + case SpellEffect::SetBodyType: { SetBodyType(spell.base_value[i], false); break; } - case SE_Leap: + case SpellEffect::Leap: { // These effects remove lev and only work a certain distance away. - BuffFadeByEffect(SE_Levitate); + BuffFadeByEffect(SpellEffect::Levitate); if (caster && caster->GetTarget()) { float my_x = caster->GetX(); float my_y = caster->GetY(); @@ -2807,7 +2824,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove } break; } - case SE_VoiceGraft: + case SpellEffect::VoiceGraft: { if(caster && caster->GetPet()) caster->spellbonuses.VoiceGraft = caster->GetPetID(); @@ -2815,7 +2832,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_ManaBurn: + case SpellEffect::ManaBurn: { int32 max_mana = spell.base_value[i]; int ratio = spell.limit_value[i]; @@ -2844,7 +2861,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_Taunt: + case SpellEffect::Taunt: { if (caster && IsNPC()){ caster->Taunt(CastToNPC(), false, spell.base_value[i], true, spell.limit_value[i]); @@ -2852,26 +2869,26 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_AttackSpeed: + case SpellEffect::AttackSpeed: if (spell.base_value[i] < 100) SlowMitigation(caster); break; - case SE_AttackSpeed2: + case SpellEffect::AttackSpeed2: if (spell.base_value[i] < 100) SlowMitigation(caster); break; - case SE_AttackSpeed3: + case SpellEffect::AttackSpeed3: if (spell.base_value[i] < 0) SlowMitigation(caster); break; - case SE_AttackSpeed4: + case SpellEffect::AttackSpeed4: SlowMitigation(caster); break; - case SE_AddHatePct: + case SpellEffect::AddHatePct: { if (IsNPC()){ int64 new_hate = CastToNPC()->GetHateAmount(caster) * (100 + spell.base_value[i]) / 100; @@ -2883,7 +2900,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_Hate:{ + case SpellEffect::Hate:{ if (buffslot >= 0) break; @@ -2907,7 +2924,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_InterruptCasting:{ + case SpellEffect::InterruptCasting:{ if (buffslot >= 0) break; @@ -2917,20 +2934,20 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_MassGroupBuff:{ + case SpellEffect::MassGroupBuff:{ SetMGB(true); MessageString(Chat::Disciplines, MGB_STRING); break; } - case SE_IllusionOther: { + case SpellEffect::IllusionOther: { SetProjectIllusion(true); Message(Chat::NPCQuestSay, "The power of your next illusion spell will flow to your grouped target in your place."); break; } - case SE_ApplyEffect: { + case SpellEffect::ApplyEffect: { if (caster && IsValidSpell(spells[spell_id].limit_value[i])){ if(zone->random.Roll(spells[spell_id].base_value[i])) @@ -2939,7 +2956,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_SpellTrigger: { + case SpellEffect::SpellTrigger: { if (!spell_trigger_cast_complete) { if (caster && caster->TrySpellTrigger(this, spell_id, i)) @@ -2949,7 +2966,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove } - case SE_Instant_Mana_Pct: { + case SpellEffect::Instant_Mana_Pct: { effect_value = spells[spell_id].base_value[i]; int64 amt = std::abs(GetMaxMana() * effect_value / 10000); if (spells[spell_id].max_value[i] && amt > spells[spell_id].max_value[i]) @@ -2964,7 +2981,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_Instant_Endurance_Pct: { + case SpellEffect::Instant_Endurance_Pct: { effect_value = spells[spell_id].base_value[i]; if (IsClient()) { int32 amt = std::abs(CastToClient()->GetMaxEndurance() * effect_value / 10000); @@ -2984,7 +3001,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove Calc for base1 is found in ApplyHealthTransferDamage() due to needing to account for AOE functionality since effect can potentially kill caster. */ - case SE_Health_Transfer: { + case SpellEffect::Health_Transfer: { if (caster) { effect_value = spells[spell_id].limit_value[i]; int64 amt = std::abs(caster->GetMaxHP() * effect_value / 1000); @@ -2999,7 +3016,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_Chance_Best_in_Spell_Grp: { + case SpellEffect::Chance_Best_in_Spell_Grp: { if (!spell_trigger_cast_complete) { if (caster && caster->TrySpellTrigger(this, spell_id, i)) spell_trigger_cast_complete = true; @@ -3007,7 +3024,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_Trigger_Best_in_Spell_Grp: { + case SpellEffect::Trigger_Best_in_Spell_Grp: { if (caster && !caster->IsClient()) break; @@ -3021,7 +3038,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_Trigger_Spell_Non_Item: { + case SpellEffect::Trigger_Spell_Non_Item: { //Only trigger if not from item if (caster && caster->IsClient() && GetCastedSpellInvSlot() > 0) break; @@ -3032,19 +3049,19 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_Hatelist_To_Tail_Index: { + case SpellEffect::Hatelist_To_Tail_Index: { if (caster && zone->random.Roll(spells[spell_id].base_value[i])) caster->SetBottomRampageList(); break; } - case SE_Hatelist_To_Top_Index: { + case SpellEffect::Hatelist_To_Top_Index: { if (caster && zone->random.Roll(spells[spell_id].base_value[i])) caster->SetTopRampageList(); break; } - case SE_Fearstun: { + case SpellEffect::Fearstun: { //Normal 'stun' restrictions do not apply. base1=duration, base2=PC duration, max =lv restrict if (!caster) break; @@ -3077,7 +3094,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_PetShield: { + case SpellEffect::PetShield: { if (IsPet()) { Mob* petowner = GetOwner(); if (petowner) { @@ -3090,316 +3107,316 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_Weapon_Stance: { + case SpellEffect::Weapon_Stance: { if (IsClient()) { CastToClient()->ApplyWeaponsStance(); } break; } - case SE_HealOverTime: { + case SpellEffect::HealOverTime: { //This is here so buffs with hit counters tic down on initial cast. caster->GetActSpellHealing(spell_id, effect_value, nullptr, false); break; } - case SE_PersistentEffect: + case SpellEffect::PersistentEffect: MakeAura(spell_id); break; // Handled Elsewhere - case SE_ImmuneFleeing: - case SE_NegateSpellEffect: - case SE_Knockdown: // handled by client - case SE_ShadowStepDirectional: // handled by client - case SE_SpellOnDeath: - case SE_BlockNextSpellFocus: - case SE_ReduceReuseTimer: - case SE_SwarmPetDuration: - case SE_LimitHPPercent: - case SE_LimitManaPercent: - case SE_LimitEndPercent: - case SE_ExtraAttackChance: - case SE_ProcChance: - case SE_StunResist: - case SE_MinDamageModifier: - case SE_DamageModifier: - case SE_DamageModifier2: - case SE_HitChance: - case SE_MeleeSkillCheck: - case SE_HundredHands: - case SE_ResistFearChance: - case SE_ResistSpellChance: - case SE_AllInstrumentMod: - case SE_MeleeLifetap: - case SE_DoubleAttackChance: - case SE_TripleAttackChance: - case SE_DualWieldChance: - case SE_ParryChance: - case SE_DodgeChance: - case SE_RiposteChance: - case SE_AvoidMeleeChance: - case SE_CrippBlowChance: - case SE_CriticalHitChance: - case SE_MeleeMitigation: - case SE_Reflect: - case SE_Screech: - case SE_Amplification: - case SE_MagicWeapon: - case SE_Hunger: - case SE_MagnifyVision: - case SE_Lycanthropy: - case SE_NegateIfCombat: - case SE_CastingLevel: - case SE_CastingLevel2: - case SE_RaiseStatCap: - case SE_ResistAll: - case SE_ResistMagic: - case SE_ResistDisease: - case SE_ResistPoison: - case SE_ResistCold: - case SE_ResistFire: - case SE_AllStats: - case SE_MakeDrunk: - case SE_CHA: - case SE_WIS: - case SE_INT: - case SE_STA: - case SE_AGI: - case SE_DEX: - case SE_STR: - case SE_ATK: - case SE_ArmorClass: - case SE_EndurancePool: - case SE_Stamina: - case SE_UltraVision: - case SE_InfraVision: - case SE_ManaPool: - case SE_TotalHP: - case SE_ChangeFrenzyRad: - case SE_Harmony: - case SE_ChangeAggro: - case SE_Identify: - case SE_InstantHate: - case SE_ReduceHate: - case SE_SpellDamageShield: - case SE_ReverseDS: - case SE_DamageShield: - case SE_TrueNorth: - case SE_WaterBreathing: - case SE_MovementSpeed: - case SE_PercentXPIncrease: - case SE_DivineSave: - case SE_Accuracy: - case SE_Flurry: - case SE_ImprovedDamage: - case SE_ImprovedDamage2: - case SE_ImprovedHeal: - case SE_IncreaseSpellHaste: - case SE_IncreaseSpellDuration: - case SE_IncreaseRange: - case SE_SpellHateMod: - case SE_ReduceReagentCost: - case SE_ReduceManaCost: - case SE_LimitMaxLevel: - case SE_LimitResist: - case SE_LimitTarget: - case SE_LimitEffect: - case SE_LimitSpellType: - case SE_LimitSpell: - case SE_LimitMinDur: - case SE_LimitInstant: - case SE_LimitMinLevel: - case SE_LimitCastTimeMin: - case SE_LimitManaMin: - case SE_LimitCombatSkills: - case SE_SpellDurationIncByTic: - case SE_TriggerOnCast: - case SE_HealRate: - case SE_SkillDamageTaken: - case SE_FcSpellVulnerability: - case SE_Fc_Spell_Damage_Pct_IncomingPC: - case SE_Fc_Spell_Damage_Amt_IncomingPC: - case SE_FcTwincast: - case SE_DelayDeath: - case SE_CastOnFadeEffect: - case SE_CastOnFadeEffectNPC: - case SE_CastOnFadeEffectAlways: - case SE_CastOnRuneFadeEffect: - case SE_MaxHPChange: - case SE_SympatheticProc: - case SE_FcDamageAmt: - case SE_FcDamageAmt2: - case SE_CriticalSpellChance: - case SE_SpellCritChance: - case SE_SpellCritDmgIncrease: - case SE_DotCritDmgIncrease: - case SE_CriticalHealChance: - case SE_CriticalHealOverTime: - case SE_CriticalDoTChance: - case SE_ProcOnKillShot: - case SE_ProcOnSpellKillShot: - case SE_CriticalDamageMob: - case SE_LimitSpellGroup: - case SE_ResistCorruption: - case SE_ReduceSkillTimer: - case SE_HPToMana: - case SE_ManaAbsorbPercentDamage: - case SE_SkillDamageAmount: - case SE_SkillDamageAmount2: - case SE_GravityEffect: - case SE_IncreaseBlockChance: - case SE_AntiGate: - case SE_Fearless: - case SE_FcDamageAmtCrit: - case SE_FcHealAmtCrit: - case SE_CastOnCurer: - case SE_CastOnCure: - case SE_CastonNumHitFade: - case SE_LimitToSkill: - case SE_SpellProcChance: - case SE_CharmBreakChance: - case SE_BardSongRange: - case SE_ACv2: - case SE_ManaRegen_v2: - case SE_FcDamagePctCrit: - case SE_FcHealAmt: - case SE_FcHealPctIncoming: - case SE_CriticalHealDecay: - case SE_CriticalRegenDecay: - case SE_FcDamageAmtIncoming: - case SE_LimitCastingSkill: - case SE_MitigateDamageShield: - case SE_FcBaseEffects: - case SE_LimitClass: - case SE_BlockBehind: - case SE_ShieldBlock: - case SE_PetCriticalHit: - case SE_SlayUndead: - case SE_GiveDoubleAttack: - case SE_StrikeThrough: - case SE_StrikeThrough2: - case SE_SecondaryDmgInc: - case SE_ArcheryDamageModifier: - case SE_ConsumeProjectile: - case SE_ForageAdditionalItems: - case SE_Salvage: - case SE_FrontalBackstabChance: - case SE_FrontalBackstabMinDmg: - case SE_TripleBackstab: - case SE_DoubleSpecialAttack: - case SE_IncreaseRunSpeedCap: - case SE_BaseMovementSpeed: - case SE_FrontalStunResist: - case SE_ImprovedBindWound: - case SE_MaxBindWound: - case SE_CombatStability: - case SE_AddSingingMod: - case SE_SongModCap: - case SE_HeadShot: - case SE_HeadShotLevel: - case SE_PetAvoidance: - case SE_GiveDoubleRiposte: - case SE_Ambidexterity: - case SE_PetMaxHP: - case SE_PetFlurry: - case SE_MasteryofPast: - case SE_GivePetGroupTarget: - case SE_RootBreakChance: - case SE_UnfailingDivinity: - case SE_ChannelChanceSpells: - case SE_ChannelChanceItems: - case SE_FcHealPctCritIncoming: - case SE_FcIncreaseNumHits: - case SE_CastonFocusEffect: - case SE_FcHealAmtIncoming: - case SE_LimitManaMax: - case SE_DoubleRangedAttack: - case SE_ShieldEquipDmgMod: - case SE_TriggerOnReqTarget: - case SE_LimitRace: - case SE_FcLimitUse: - case SE_FcMute: - case SE_LimitUseType: - case SE_FcStunTimeMod: - case SE_StunBashChance: - case SE_IncreaseChanceMemwipe: - case SE_CriticalMend: - case SE_LimitCastTimeMax: - case SE_TriggerOnReqCaster: - case SE_FrenziedDevastation: - case SE_AStacker: - case SE_BStacker: - case SE_CStacker: - case SE_DStacker: - case SE_DoubleRiposte: - case SE_Berserk: - case SE_Vampirism: - case SE_Metabolism: - case SE_FinishingBlow: - case SE_FinishingBlowLvl: - case SE_Assassinate: - case SE_AssassinateLevel: - case SE_FactionModPct: - case SE_LimitSpellClass: - case SE_Sanctuary: - case SE_PetMeleeMitigation: - case SE_SkillProcAttempt: - case SE_SkillProcSuccess: - case SE_SpellResistReduction: - case SE_IncreaseArchery: - case SE_Duration_HP_Pct: - case SE_Duration_Mana_Pct: - case SE_Duration_Endurance_Pct: - case SE_Endurance_Absorb_Pct_Damage: - case SE_AC_Mitigation_Max_Percent: - case SE_AC_Avoidance_Max_Percent: - case SE_Attack_Accuracy_Max_Percent: - case SE_Critical_Melee_Damage_Mod_Max: - case SE_Melee_Damage_Position_Mod: - case SE_Damage_Taken_Position_Mod: - case SE_Melee_Damage_Position_Amt: - case SE_Damage_Taken_Position_Amt: - case SE_DS_Mitigation_Amount: - case SE_DS_Mitigation_Percentage: - case SE_Double_Backstab_Front: - case SE_Pet_Crit_Melee_Damage_Pct_Owner: - case SE_Pet_Add_Atk: - case SE_TwinCastBlocker: - case SE_Fc_Cast_Spell_On_Land: - case SE_Ff_CasterClass: - case SE_Ff_Same_Caster: - case SE_Fc_ResistIncoming: - case SE_Fc_Amplify_Amt: - case SE_Fc_Amplify_Mod: - case SE_Fc_CastTimeAmt: - case SE_Fc_CastTimeMod2: - case SE_Ff_DurationMax: - case SE_Ff_Endurance_Max: - case SE_Ff_Endurance_Min: - case SE_Ff_ReuseTimeMin: - case SE_Ff_ReuseTimeMax: - case SE_Ff_Value_Min: - case SE_Ff_Value_Max: - case SE_AddExtraAttackPct_1h_Primary: - case SE_AddExtraAttackPct_1h_Secondary: - case SE_Double_Melee_Round: - case SE_Skill_Base_Damage_Mod: - case SE_Worn_Endurance_Regen_Cap: - case SE_Buy_AA_Rank: - case SE_Ff_FocusTimerMin: - case SE_Proc_Timer_Modifier: - case SE_FFItemClass: - case SE_SpellEffectResistChance: - case SE_SeeInvis: - case SE_Invisibility: - case SE_Invisibility2: - case SE_InvisVsAnimals: - case SE_ImprovedInvisAnimals: - case SE_InvisVsUndead: - case SE_InvisVsUndead2: - case SE_Shield_Target: - case SE_ReduceSkill: + case SpellEffect::ImmuneFleeing: + case SpellEffect::NegateSpellEffect: + case SpellEffect::Knockdown: // handled by client + case SpellEffect::ShadowStepDirectional: // handled by client + case SpellEffect::SpellOnDeath: + case SpellEffect::BlockNextSpellFocus: + case SpellEffect::ReduceReuseTimer: + case SpellEffect::SwarmPetDuration: + case SpellEffect::LimitHPPercent: + case SpellEffect::LimitManaPercent: + case SpellEffect::LimitEndPercent: + case SpellEffect::ExtraAttackChance: + case SpellEffect::ProcChance: + case SpellEffect::StunResist: + case SpellEffect::MinDamageModifier: + case SpellEffect::DamageModifier: + case SpellEffect::DamageModifier2: + case SpellEffect::HitChance: + case SpellEffect::MeleeSkillCheck: + case SpellEffect::HundredHands: + case SpellEffect::ResistFearChance: + case SpellEffect::ResistSpellChance: + case SpellEffect::AllInstrumentMod: + case SpellEffect::MeleeLifetap: + case SpellEffect::DoubleAttackChance: + case SpellEffect::TripleAttackChance: + case SpellEffect::DualWieldChance: + case SpellEffect::ParryChance: + case SpellEffect::DodgeChance: + case SpellEffect::RiposteChance: + case SpellEffect::AvoidMeleeChance: + case SpellEffect::CrippBlowChance: + case SpellEffect::CriticalHitChance: + case SpellEffect::MeleeMitigation: + case SpellEffect::Reflect: + case SpellEffect::Screech: + case SpellEffect::Amplification: + case SpellEffect::MagicWeapon: + case SpellEffect::Hunger: + case SpellEffect::MagnifyVision: + case SpellEffect::Lycanthropy: + case SpellEffect::NegateIfCombat: + case SpellEffect::CastingLevel: + case SpellEffect::CastingLevel2: + case SpellEffect::RaiseStatCap: + case SpellEffect::ResistAll: + case SpellEffect::ResistMagic: + case SpellEffect::ResistDisease: + case SpellEffect::ResistPoison: + case SpellEffect::ResistCold: + case SpellEffect::ResistFire: + case SpellEffect::AllStats: + case SpellEffect::MakeDrunk: + case SpellEffect::CHA: + case SpellEffect::WIS: + case SpellEffect::INT: + case SpellEffect::STA: + case SpellEffect::AGI: + case SpellEffect::DEX: + case SpellEffect::STR: + case SpellEffect::ATK: + case SpellEffect::ArmorClass: + case SpellEffect::EndurancePool: + case SpellEffect::Stamina: + case SpellEffect::UltraVision: + case SpellEffect::InfraVision: + case SpellEffect::ManaPool: + case SpellEffect::TotalHP: + case SpellEffect::ChangeFrenzyRad: + case SpellEffect::Harmony: + case SpellEffect::ChangeAggro: + case SpellEffect::Identify: + case SpellEffect::InstantHate: + case SpellEffect::ReduceHate: + case SpellEffect::SpellDamageShield: + case SpellEffect::ReverseDS: + case SpellEffect::DamageShield: + case SpellEffect::TrueNorth: + case SpellEffect::WaterBreathing: + case SpellEffect::MovementSpeed: + case SpellEffect::PercentXPIncrease: + case SpellEffect::DivineSave: + case SpellEffect::Accuracy: + case SpellEffect::Flurry: + case SpellEffect::ImprovedDamage: + case SpellEffect::ImprovedDamage2: + case SpellEffect::ImprovedHeal: + case SpellEffect::IncreaseSpellHaste: + case SpellEffect::IncreaseSpellDuration: + case SpellEffect::IncreaseRange: + case SpellEffect::SpellHateMod: + case SpellEffect::ReduceReagentCost: + case SpellEffect::ReduceManaCost: + case SpellEffect::LimitMaxLevel: + case SpellEffect::LimitResist: + case SpellEffect::LimitTarget: + case SpellEffect::LimitEffect: + case SpellEffect::LimitSpellType: + case SpellEffect::LimitSpell: + case SpellEffect::LimitMinDur: + case SpellEffect::LimitInstant: + case SpellEffect::LimitMinLevel: + case SpellEffect::LimitCastTimeMin: + case SpellEffect::LimitManaMin: + case SpellEffect::LimitCombatSkills: + case SpellEffect::SpellDurationIncByTic: + case SpellEffect::TriggerOnCast: + case SpellEffect::HealRate: + case SpellEffect::SkillDamageTaken: + case SpellEffect::FcSpellVulnerability: + case SpellEffect::Fc_Spell_Damage_Pct_IncomingPC: + case SpellEffect::Fc_Spell_Damage_Amt_IncomingPC: + case SpellEffect::FcTwincast: + case SpellEffect::DelayDeath: + case SpellEffect::CastOnFadeEffect: + case SpellEffect::CastOnFadeEffectNPC: + case SpellEffect::CastOnFadeEffectAlways: + case SpellEffect::CastOnRuneFadeEffect: + case SpellEffect::MaxHPChange: + case SpellEffect::SympatheticProc: + case SpellEffect::FcDamageAmt: + case SpellEffect::FcDamageAmt2: + case SpellEffect::CriticalSpellChance: + case SpellEffect::SpellCritChance: + case SpellEffect::SpellCritDmgIncrease: + case SpellEffect::DotCritDmgIncrease: + case SpellEffect::CriticalHealChance: + case SpellEffect::CriticalHealOverTime: + case SpellEffect::CriticalDoTChance: + case SpellEffect::ProcOnKillShot: + case SpellEffect::ProcOnSpellKillShot: + case SpellEffect::CriticalDamageMob: + case SpellEffect::LimitSpellGroup: + case SpellEffect::ResistCorruption: + case SpellEffect::ReduceSkillTimer: + case SpellEffect::HPToMana: + case SpellEffect::ManaAbsorbPercentDamage: + case SpellEffect::SkillDamageAmount: + case SpellEffect::SkillDamageAmount2: + case SpellEffect::GravityEffect: + case SpellEffect::IncreaseBlockChance: + case SpellEffect::AntiGate: + case SpellEffect::Fearless: + case SpellEffect::FcDamageAmtCrit: + case SpellEffect::FcHealAmtCrit: + case SpellEffect::CastOnCurer: + case SpellEffect::CastOnCure: + case SpellEffect::CastonNumHitFade: + case SpellEffect::LimitToSkill: + case SpellEffect::SpellProcChance: + case SpellEffect::CharmBreakChance: + case SpellEffect::BardSongRange: + case SpellEffect::ACv2: + case SpellEffect::ManaRegen_v2: + case SpellEffect::FcDamagePctCrit: + case SpellEffect::FcHealAmt: + case SpellEffect::FcHealPctIncoming: + case SpellEffect::CriticalHealDecay: + case SpellEffect::CriticalRegenDecay: + case SpellEffect::FcDamageAmtIncoming: + case SpellEffect::LimitCastingSkill: + case SpellEffect::MitigateDamageShield: + case SpellEffect::FcBaseEffects: + case SpellEffect::LimitClass: + case SpellEffect::BlockBehind: + case SpellEffect::ShieldBlock: + case SpellEffect::PetCriticalHit: + case SpellEffect::SlayUndead: + case SpellEffect::GiveDoubleAttack: + case SpellEffect::StrikeThrough: + case SpellEffect::StrikeThrough2: + case SpellEffect::SecondaryDmgInc: + case SpellEffect::ArcheryDamageModifier: + case SpellEffect::ConsumeProjectile: + case SpellEffect::ForageAdditionalItems: + case SpellEffect::Salvage: + case SpellEffect::FrontalBackstabChance: + case SpellEffect::FrontalBackstabMinDmg: + case SpellEffect::TripleBackstab: + case SpellEffect::DoubleSpecialAttack: + case SpellEffect::IncreaseRunSpeedCap: + case SpellEffect::BaseMovementSpeed: + case SpellEffect::FrontalStunResist: + case SpellEffect::ImprovedBindWound: + case SpellEffect::MaxBindWound: + case SpellEffect::CombatStability: + case SpellEffect::AddSingingMod: + case SpellEffect::SongModCap: + case SpellEffect::HeadShot: + case SpellEffect::HeadShotLevel: + case SpellEffect::PetAvoidance: + case SpellEffect::GiveDoubleRiposte: + case SpellEffect::Ambidexterity: + case SpellEffect::PetMaxHP: + case SpellEffect::PetFlurry: + case SpellEffect::MasteryofPast: + case SpellEffect::GivePetGroupTarget: + case SpellEffect::RootBreakChance: + case SpellEffect::UnfailingDivinity: + case SpellEffect::ChannelChanceSpells: + case SpellEffect::ChannelChanceItems: + case SpellEffect::FcHealPctCritIncoming: + case SpellEffect::FcIncreaseNumHits: + case SpellEffect::CastonFocusEffect: + case SpellEffect::FcHealAmtIncoming: + case SpellEffect::LimitManaMax: + case SpellEffect::DoubleRangedAttack: + case SpellEffect::ShieldEquipDmgMod: + case SpellEffect::TriggerOnReqTarget: + case SpellEffect::LimitRace: + case SpellEffect::FcLimitUse: + case SpellEffect::FcMute: + case SpellEffect::LimitUseType: + case SpellEffect::FcStunTimeMod: + case SpellEffect::StunBashChance: + case SpellEffect::IncreaseChanceMemwipe: + case SpellEffect::CriticalMend: + case SpellEffect::LimitCastTimeMax: + case SpellEffect::TriggerOnReqCaster: + case SpellEffect::FrenziedDevastation: + case SpellEffect::AStacker: + case SpellEffect::BStacker: + case SpellEffect::CStacker: + case SpellEffect::DStacker: + case SpellEffect::DoubleRiposte: + case SpellEffect::Berserk: + case SpellEffect::Vampirism: + case SpellEffect::Metabolism: + case SpellEffect::FinishingBlow: + case SpellEffect::FinishingBlowLvl: + case SpellEffect::Assassinate: + case SpellEffect::AssassinateLevel: + case SpellEffect::FactionModPct: + case SpellEffect::LimitSpellClass: + case SpellEffect::Sanctuary: + case SpellEffect::PetMeleeMitigation: + case SpellEffect::SkillProcAttempt: + case SpellEffect::SkillProcSuccess: + case SpellEffect::SpellResistReduction: + case SpellEffect::IncreaseArchery: + case SpellEffect::Duration_HP_Pct: + case SpellEffect::Duration_Mana_Pct: + case SpellEffect::Duration_Endurance_Pct: + case SpellEffect::Endurance_Absorb_Pct_Damage: + case SpellEffect::AC_Mitigation_Max_Percent: + case SpellEffect::AC_Avoidance_Max_Percent: + case SpellEffect::Attack_Accuracy_Max_Percent: + case SpellEffect::Critical_Melee_Damage_Mod_Max: + case SpellEffect::Melee_Damage_Position_Mod: + case SpellEffect::Damage_Taken_Position_Mod: + case SpellEffect::Melee_Damage_Position_Amt: + case SpellEffect::Damage_Taken_Position_Amt: + case SpellEffect::DS_Mitigation_Amount: + case SpellEffect::DS_Mitigation_Percentage: + case SpellEffect::Double_Backstab_Front: + case SpellEffect::Pet_Crit_Melee_Damage_Pct_Owner: + case SpellEffect::Pet_Add_Atk: + case SpellEffect::TwinCastBlocker: + case SpellEffect::Fc_Cast_Spell_On_Land: + case SpellEffect::Ff_CasterClass: + case SpellEffect::Ff_Same_Caster: + case SpellEffect::Fc_ResistIncoming: + case SpellEffect::Fc_Amplify_Amt: + case SpellEffect::Fc_Amplify_Mod: + case SpellEffect::Fc_CastTimeAmt: + case SpellEffect::Fc_CastTimeMod2: + case SpellEffect::Ff_DurationMax: + case SpellEffect::Ff_Endurance_Max: + case SpellEffect::Ff_Endurance_Min: + case SpellEffect::Ff_ReuseTimeMin: + case SpellEffect::Ff_ReuseTimeMax: + case SpellEffect::Ff_Value_Min: + case SpellEffect::Ff_Value_Max: + case SpellEffect::AddExtraAttackPct_1h_Primary: + case SpellEffect::AddExtraAttackPct_1h_Secondary: + case SpellEffect::Double_Melee_Round: + case SpellEffect::Skill_Base_Damage_Mod: + case SpellEffect::Worn_Endurance_Regen_Cap: + case SpellEffect::Buy_AA_Rank: + case SpellEffect::Ff_FocusTimerMin: + case SpellEffect::Proc_Timer_Modifier: + case SpellEffect::FFItemClass: + case SpellEffect::SpellEffectResistChance: + case SpellEffect::SeeInvis: + case SpellEffect::Invisibility: + case SpellEffect::Invisibility2: + case SpellEffect::InvisVsAnimals: + case SpellEffect::ImprovedInvisAnimals: + case SpellEffect::InvisVsUndead: + case SpellEffect::InvisVsUndead2: + case SpellEffect::Shield_Target: + case SpellEffect::ReduceSkill: { break; } @@ -3456,7 +3473,7 @@ int64 Mob::CalcSpellEffectValue(uint16 spell_id, int effect_id, int caster_level oval, instrument_mod, effect_value); } /* - SPA 413 SE_FcBaseEffects, modifies base value of a spell effect after formula calcultion, but before other focuses. + SPA 413 SpellEffect::FcBaseEffects, modifies base value of a spell effect after formula calcultion, but before other focuses. This is applied to non-Bards in Mob::GetInstrumentMod Like bard modifiers, this is sent in the action_struct using action->instrument_mod (which is a base effect modifier) @@ -3918,7 +3935,7 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster) // doing it every time up here, since most buff effects dont need it switch (effect) { - case SE_CurrentHP: { + case SpellEffect::CurrentHP: { if (spells[buff.spellid].limit_value[i] && !PassCastRestriction(spells[buff.spellid].limit_value[i])) { break; } @@ -3949,7 +3966,7 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster) } break; } - case SE_HealOverTime: { + case SpellEffect::HealOverTime: { effect_value = CalcSpellEffectValue(buff.spellid, i, buff.casterlevel, buff.instrument_mod); if (caster) { effect_value = caster->GetActSpellHealing(buff.spellid, effect_value, nullptr, true); @@ -3960,12 +3977,12 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster) break; } - case SE_CurrentEndurance: { + case SpellEffect::CurrentEndurance: { // Handled with bonuses break; } - case SE_BardAEDot: { + case SpellEffect::BardAEDot: { effect_value = CalcSpellEffectValue(buff.spellid, i, buff.casterlevel, buff.instrument_mod, caster); @@ -3990,7 +4007,7 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster) break; } - case SE_Hate: { + case SpellEffect::Hate: { effect_value = CalcSpellEffectValue(buff.spellid, i, buff.casterlevel, buff.instrument_mod); if (caster) { if (effect_value > 0) { @@ -4012,7 +4029,7 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster) break; } - case SE_WipeHateList: { + case SpellEffect::WipeHateList: { if (IsMesmerizeSpell(buff.spellid)) { break; } @@ -4033,9 +4050,9 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster) break; } - case SE_Charm: { + case SpellEffect::Charm: { if (!caster || !PassCharismaCheck(caster, buff.spellid)) { - BuffFadeByEffect(SE_Charm); + BuffFadeByEffect(SpellEffect::Charm); // Remove from hate list of any NPC's hate list and remove all NPCs this hate list if (IsNPC()) { @@ -4047,7 +4064,7 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster) break; } - case SE_Root: { + case SpellEffect::Root: { /* Root formula derived from extensive personal live parses - Kayen ROOT has a 70% chance to do a resist check to break. */ @@ -4065,7 +4082,7 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster) break; } - case SE_Fear: { + case SpellEffect::Fear: { if (zone->random.Roll(RuleI(Spells, FearBreakCheckChance))) { float resist_check = ResistSpell(spells[buff.spellid].resist_type, buff.spellid, caster,0,0,true); @@ -4078,9 +4095,9 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster) break; } - case SE_Invisibility: - case SE_InvisVsAnimals: - case SE_InvisVsUndead: { + case SpellEffect::Invisibility: + case SpellEffect::InvisVsAnimals: + case SpellEffect::InvisVsUndead: { if (buff.ticsremaining > 3) { if (!IsBardSong(buff.spellid)) { double break_chance = 2.0; @@ -4101,9 +4118,9 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster) } } } - case SE_ImprovedInvisAnimals: - case SE_Invisibility2: - case SE_InvisVsUndead2: { + case SpellEffect::ImprovedInvisAnimals: + case SpellEffect::Invisibility2: + case SpellEffect::InvisVsUndead2: { if (!IsBardSong(buff.spellid)) { if (buff.ticsremaining <= 3 && buff.ticsremaining > 1) { MessageString(Chat::Spells, INVIS_BEGIN_BREAK); @@ -4111,7 +4128,7 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster) } break; } - case SE_InterruptCasting: { + case SpellEffect::InterruptCasting: { if (IsCasting()) { const auto &spell = spells[casting_spell_id]; if (!IsCastNotStandingSpell(spell.id) && zone->random.Roll(spells[buff.spellid].base_value[i])) { @@ -4122,22 +4139,22 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster) } // These effects always trigger when they fade. // Should we have this triggered from else where? - case SE_CastOnFadeEffect: - case SE_CastOnFadeEffectNPC: - case SE_CastOnFadeEffectAlways: { + case SpellEffect::CastOnFadeEffect: + case SpellEffect::CastOnFadeEffectNPC: + case SpellEffect::CastOnFadeEffectAlways: { if (buff.ticsremaining == 0) { SpellFinished(spells[buff.spellid].base_value[i], this, EQ::spells::CastingSlot::Item, 0, -1, spells[spells[buff.spellid].base_value[i]].resist_difficulty); } break; } - case SE_LocateCorpse: { + case SpellEffect::LocateCorpse: { // This is handled by the client prior to SoD. if (IsClient() && (CastToClient()->ClientVersionBit() & EQ::versions::maskSoDAndLater)) CastToClient()->LocateCorpse(); } - case SE_DistanceRemoval: { + case SpellEffect::DistanceRemoval: { if (spellbonuses.DistanceRemoval) { int distance = @@ -4154,7 +4171,7 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster) } } - case SE_AddHateOverTimePct: { + case SpellEffect::AddHateOverTimePct: { if (IsNPC()) { int64 new_hate = CastToNPC()->GetHateAmount(caster) * (100 + spell.base_value[i]) / 100; if (new_hate <= 0) @@ -4165,7 +4182,7 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster) break; } - case SE_Duration_HP_Pct: { + case SpellEffect::Duration_HP_Pct: { effect_value = spells[buff.spellid].base_value[i]; int64 amt = std::abs(GetMaxHP() * effect_value / 100); if (spells[buff.spellid].max_value[i] && amt > spells[buff.spellid].max_value[i]) @@ -4180,7 +4197,7 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster) break; } - case SE_Duration_Mana_Pct: { + case SpellEffect::Duration_Mana_Pct: { effect_value = spells[buff.spellid].base_value[i]; int32 amt = std::abs(GetMaxMana() * effect_value / 100); if (spells[buff.spellid].max_value[i] && amt > spells[buff.spellid].max_value[i]) @@ -4196,7 +4213,7 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster) break; } - case SE_Duration_Endurance_Pct: { + case SpellEffect::Duration_Endurance_Pct: { effect_value = spells[buff.spellid].base_value[i]; if (IsClient()) { @@ -4284,8 +4301,8 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses) switch (spells[buffs[slot].spellid].effect_id[i]) { - case SE_AddMeleeProc: - case SE_WeaponProc: + case SpellEffect::AddMeleeProc: + case SpellEffect::WeaponProc: { uint16 proc_id = GetProcID(buffs[slot].spellid, i); @@ -4298,21 +4315,21 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses) break; } - case SE_DefensiveProc: + case SpellEffect::DefensiveProc: { uint16 procid = GetProcID(buffs[slot].spellid, i); RemoveDefensiveProc(procid); break; } - case SE_RangedProc: + case SpellEffect::RangedProc: { uint16 procid = GetProcID(buffs[slot].spellid, i); RemoveRangedProc(procid); break; } - case SE_SummonHorse: + case SpellEffect::SummonHorse: { if(IsClient()) { @@ -4324,12 +4341,17 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses) break; } - case SE_IllusionCopy: - case SE_Illusion: + case SpellEffect::IllusionCopy: + case SpellEffect::Illusion: { - SendIllusionPacket(AppearanceStruct{}); - // The GetSize below works because the above setting race to zero sets size back. - SendAppearancePacket(AppearanceType::Size, GetSize()); + SendIllusionPacket( + AppearanceStruct{ + .gender_id = GetBaseGender(), + .race_id = GetBaseRace(), + .size = GetDefaultRaceSize(GetBaseRace(), GetBaseGender()), + .texture = 0 + } + ); for (int x = EQ::textures::textureBegin; x <= EQ::textures::LastTintableTexture; x++) { SendWearChange(x); @@ -4338,44 +4360,44 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses) break; } - case SE_Levitate: + case SpellEffect::Levitate: { - if (!AffectedBySpellExcludingSlot(slot, SE_Levitate)) + if (!AffectedBySpellExcludingSlot(slot, SpellEffect::Levitate)) SendAppearancePacket(AppearanceType::FlyMode, 0); break; } - case SE_Silence: + case SpellEffect::Silence: { Silence(false); break; } - case SE_Amnesia: + case SpellEffect::Amnesia: { Amnesia(false); break; } - case SE_DivineAura: + case SpellEffect::DivineAura: { SetInvul(false); break; } - case SE_Rune: + case SpellEffect::Rune: { buffs[slot].melee_rune = 0; break; } - case SE_AbsorbMagicAtt: + case SpellEffect::AbsorbMagicAtt: { buffs[slot].magic_rune = 0; break; } - case SE_Familiar: + case SpellEffect::Familiar: { Mob *mypet = GetPet(); if (mypet){ @@ -4386,25 +4408,24 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses) break; } - case SE_Mez: + case SpellEffect::Mez: { SendAppearancePacket(AppearanceType::Animation, Animation::Standing); // unfreeze mezzed = false; break; } - case SE_Charm: + case SpellEffect::Charm: { - if(IsNPC()) - { + if (IsNPC()) { CastToNPC()->RestoreGuardSpotCharm(); - CastToNPC()->ModifyStatsOnCharm(true); + CastToNPC()->ModifyStatsOnCharm(true, GetOwner()); } SendAppearancePacket(AppearanceType::Pet, 0, true, true); Mob* owner = GetOwner(); SetOwnerID(0); - SetPetType(petNone); + SetPetType(PetType::None); SetHeld(false); SetGHeld(false); SetNoCast(false); @@ -4438,7 +4459,7 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses) for (unsigned int j = 0; j < buff_count; j++) { if (IsValidSpell(tar->GetBuffs()[j].spellid)) { auto spell = spells[tar->GetBuffs()[j].spellid]; - if (spell.good_effect == DETRIMENTAL_EFFECT && IsEffectInSpell(spell.id, SE_CurrentHP) && tar->GetBuffs()[j].casterid == GetID()) { + if (spell.good_effect == DETRIMENTAL_EFFECT && IsEffectInSpell(spell.id, SpellEffect::CurrentHP) && tar->GetBuffs()[j].casterid == GetID()) { tar->BuffFadeBySpellID(spell.id); } } @@ -4452,7 +4473,7 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses) for (unsigned int j = 0; j < buff_count; j++) { if (IsValidSpell(GetBuffs()[j].spellid )) { auto spell = spells[GetBuffs()[j].spellid]; - if (spell.good_effect == DETRIMENTAL_EFFECT && IsEffectInSpell(spell.id, SE_CurrentHP)) { + if (spell.good_effect == DETRIMENTAL_EFFECT && IsEffectInSpell(spell.id, SpellEffect::CurrentHP)) { BuffFadeBySpellID(spell.id); } } @@ -4507,7 +4528,7 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses) CastToClient()->AI_Start(CLIENT_LD_TIMEOUT); else { - bool feared = FindType(SE_Fear); + bool feared = FindType(SpellEffect::Fear); if(!feared) CastToClient()->AI_Stop(); } @@ -4515,24 +4536,24 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses) break; } - case SE_Root: + case SpellEffect::Root: { buffs[slot].RootBreakChance = 0; rooted = false; break; } - case SE_Blind: - if (currently_fleeing && !FindType(SE_Fear)) + case SpellEffect::Blind: + if (currently_fleeing && !FindType(SpellEffect::Fear)) currently_fleeing = false; break; - case SE_Fear: + case SpellEffect::Fear: { if(RuleB(Combat, EnableFearPathing)){ if(IsClient()) { - bool charmed = FindType(SE_Charm); + bool charmed = FindType(SpellEffect::Charm); if(!charmed) CastToClient()->AI_Stop(); } @@ -4549,7 +4570,7 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses) break; } - case SE_ImmuneFleeing: { + case SpellEffect::ImmuneFleeing: { if (RuleB(Combat, EnableFearPathing)) { if (flee_mode) { currently_fleeing = true; @@ -4559,7 +4580,7 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses) break; } - case SE_BindSight: + case SpellEffect::BindSight: { if(IsClient()) { @@ -4568,20 +4589,20 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses) break; } - case SE_SetBodyType: + case SpellEffect::SetBodyType: { SetBodyType(GetOrigBodyType(), false); break; } - case SE_AlterNPCLevel: + case SpellEffect::AlterNPCLevel: { if (IsNPC()) SetLevel(GetOrigLevel()); break; } - case SE_EyeOfZomm: + case SpellEffect::EyeOfZomm: { if (IsClient()) { @@ -4598,7 +4619,7 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses) break; } - case SE_Weapon_Stance: + case SpellEffect::Weapon_Stance: { /* If we click off the spell buff (or fades naturally) giving us @@ -4712,15 +4733,15 @@ int64 Mob::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) bool LimitInclude[MaxLimitInclude] = {false}; /* Certain limits require only one of several Include conditions to be true. Ie. Add damage to fire OR ice spells. - 0/1 SE_LimitResist - 2/3 SE_LimitSpell - 4/5 SE_LimitEffect - 6/7 SE_LimitTarget - 8/9 SE_LimitSpellGroup: - 10/11 SE_LimitCastingSkill: - 12/13 SE_LimitSpellClass: - 14/15 SE_LimitSpellSubClass: - 16/17 SE_FFItemCLass: + 0/1 SpellEffect::_LimitResist + 2/3 SpellEffect::_LimitSpell + 4/5 SpellEffect::_LimitEffect + 6/7 SpellEffect::_LimitTarget + 8/9 SpellEffect::_LimitSpellGroup: + 10/11 SpellEffect::LimitCastingSkill: + 12/13 SpellEffect::LimitSpellClass: + 14/15 SpellEffect::LimitSpellSubClass: + 16/17 SpellEffect::FFItemCLass: Remember: Update MaxLimitInclude in spdat.h if adding new limits that require Includes */ @@ -4766,11 +4787,11 @@ int64 Mob::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) } switch (effect) { - case SE_Blank: + case SpellEffect::Blank: break; // Handle Focus Limits - case SE_LimitResist: + case SpellEffect::LimitResist: if (base_value < 0) { if (spell.resist_type == -base_value) { // Exclude LimitFailure = true; @@ -4784,7 +4805,7 @@ int64 Mob::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) } break; - case SE_LimitInstant: + case SpellEffect::LimitInstant: if (base_value == 1 && spell.buff_duration) { // Fail if not instant LimitFailure = true; } @@ -4794,7 +4815,7 @@ int64 Mob::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) break; - case SE_LimitMaxLevel: + case SpellEffect::LimitMaxLevel: spell_level = spell.classes[(GetClass() % 17) - 1]; lvldiff = spell_level - base_value; // every level over cap reduces the effect by base2 percent unless from a clicky when @@ -4812,25 +4833,25 @@ int64 Mob::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) } break; - case SE_LimitMinLevel: + case SpellEffect::LimitMinLevel: if ((spell.classes[(GetClass() % 17) - 1]) < base_value) { LimitFailure = true; } break; - case SE_LimitCastTimeMin: + case SpellEffect::LimitCastTimeMin: if (static_cast(spell.cast_time) < base_value) { LimitFailure = true; } break; - case SE_LimitCastTimeMax: + case SpellEffect::LimitCastTimeMax: if (static_cast(spell.cast_time) > base_value) { LimitFailure = true; } break; - case SE_LimitSpell: + case SpellEffect::LimitSpell: if (base_value < 0) { // Exclude if (spell_id == -base_value) { LimitFailure = true; @@ -4844,13 +4865,13 @@ int64 Mob::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) } break; - case SE_LimitMinDur: + case SpellEffect::LimitMinDur: if (base_value > CalcBuffDuration_formula(GetLevel(), spell.buff_duration_formula, spell.buff_duration)) { LimitFailure = true; } break; - case SE_LimitEffect: + case SpellEffect::LimitEffect: if (base_value < 0) { if (IsEffectInSpell(spell_id, -base_value)) { // Exclude LimitFailure = true; @@ -4859,7 +4880,7 @@ int64 Mob::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) else { LimitInclude[IncludeExistsSELimitEffect] = true; // they use 33 here for all classes ... unsure if the type check is really needed - if (base_value == SE_SummonPet && type == focusReagentCost) { + if (base_value == SpellEffect::SummonPet && type == focusReagentCost) { if (IsPetSpell(spell_id)) { LimitInclude[IncludeFoundSELimitEffect] = true; } @@ -4872,7 +4893,7 @@ int64 Mob::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) } break; - case SE_LimitSpellType: + case SpellEffect::LimitSpellType: switch (base_value) { case 0: if (!IsDetrimentalSpell(spell_id)) { @@ -4887,19 +4908,19 @@ int64 Mob::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) } break; - case SE_LimitManaMin: + case SpellEffect::LimitManaMin: if (spell.mana < base_value) { LimitFailure = true; } break; - case SE_LimitManaMax: + case SpellEffect::LimitManaMax: if (spell.mana > base_value) { LimitFailure = true; } break; - case SE_LimitTarget: + case SpellEffect::LimitTarget: if (base_value < 0) { if (-base_value == spell.target_type) { // Exclude LimitFailure = true; @@ -4913,7 +4934,7 @@ int64 Mob::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) } break; - case SE_LimitCombatSkills: + case SpellEffect::LimitCombatSkills: if (base_value == 0 && (IsCombatSkill(spell_id) || IsCombatProc(spell_id))) { // Exclude Discs / Procs LimitFailure = true; } @@ -4923,7 +4944,7 @@ int64 Mob::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) break; - case SE_LimitSpellGroup: + case SpellEffect::LimitSpellGroup: if (base_value < 0) { if (-base_value == spell.spell_group) { // Exclude LimitFailure = true; @@ -4937,7 +4958,7 @@ int64 Mob::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) } break; - case SE_LimitCastingSkill: + case SpellEffect::LimitCastingSkill: if (base_value < 0) { if (-base_value == spell.skill) { LimitFailure = true; @@ -4951,7 +4972,7 @@ int64 Mob::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) } break; - case SE_LimitSpellClass: + case SpellEffect::LimitSpellClass: if (base_value < 0) { // Exclude if (-base_value == spell.spell_class) { LimitFailure = true; @@ -4965,7 +4986,7 @@ int64 Mob::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) } break; - case SE_LimitSpellSubclass: + case SpellEffect::LimitSpellSubclass: if (base_value < 0) { // Exclude if (-base_value == spell.spell_subclass) { LimitFailure = true; @@ -4979,7 +5000,7 @@ int64 Mob::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) } break; - case SE_LimitClass: + case SpellEffect::LimitClass: // Do not use this limit more then once per spell. If multiple class, treat value like items // would. if (!PassLimitClass(base_value, GetClass())) { @@ -4987,75 +5008,75 @@ int64 Mob::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) } break; - case SE_LimitRace: + case SpellEffect::LimitRace: if (base_value != GetRace()) { LimitFailure = true; } break; - case SE_LimitUseMin: + case SpellEffect::LimitUseMin: if (base_value > spell.hit_number) { LimitFailure = true; } break; - case SE_LimitUseType: + case SpellEffect::LimitUseType: if (base_value != spell.hit_number_type) { LimitFailure = true; } break; - case SE_Ff_DurationMax: + case SpellEffect::Ff_DurationMax: if (base_value > spell.buff_duration) { LimitFailure = true; } break; - case SE_Ff_Endurance_Min: + case SpellEffect::Ff_Endurance_Min: if (spell.endurance_cost < base_value) { LimitFailure = true; } break; - case SE_Ff_Endurance_Max: + case SpellEffect::Ff_Endurance_Max: if (spell.endurance_cost > base_value) { LimitFailure = true; } break; - case SE_Ff_ReuseTimeMin: + case SpellEffect::Ff_ReuseTimeMin: if (spell.recast_time < base_value) { LimitFailure = true; } break; - case SE_Ff_ReuseTimeMax: + case SpellEffect::Ff_ReuseTimeMax: if (spell.recast_time > base_value) { LimitFailure = true; } break; - case SE_Ff_Value_Min: + case SpellEffect::Ff_Value_Min: index_id = GetSpellEffectIndex(spell_id, limit_value); if (index_id >= 0 && spell.base_value[index_id] < base_value) { LimitFailure = true; } break; - case SE_Ff_Value_Max: + case SpellEffect::Ff_Value_Max: index_id = GetSpellEffectIndex(spell_id, limit_value); if (index_id >= 0 && spell.base_value[index_id] > base_value) { LimitFailure = true; } break; - case SE_Ff_Override_NotFocusable: + case SpellEffect::Ff_Override_NotFocusable: if (base_value == 1) { not_focusable = false; } break; - case SE_Ff_FocusTimerMin: + case SpellEffect::Ff_FocusTimerMin: if (IsFocusProcLimitTimerActive(-rank.id)) { LimitFailure = true; } @@ -5064,7 +5085,7 @@ int64 Mob::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) } break; - case SE_FFItemClass: + case SpellEffect::FFItemClass: has_item_limit_check = true; if (casting_spell_inventory_slot && casting_spell_inventory_slot != -1) { if (IsClient() && casting_spell_slot == EQ::spells::CastingSlot::Item && casting_spell_inventory_slot != 0xFFFFFFFF) { @@ -5120,111 +5141,111 @@ int64 Mob::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) break; /* These are not applicable to AA's because there is never a 'caster' of the 'buff' with the focus effect. - case SE_Ff_Same_Caster: - case SE_Ff_CasterClass: + case SpellEffect::Ff_Same_Caster: + case SpellEffect::Ff_CasterClass: */ // Handle Focus Effects - case SE_ImprovedDamage: + case SpellEffect::ImprovedDamage: if (type == focusImprovedDamage && base_value > value) { value = base_value; } break; - case SE_ImprovedDamage2: + case SpellEffect::ImprovedDamage2: if (type == focusImprovedDamage2 && base_value > value) { value = base_value; } break; - case SE_Fc_Amplify_Mod: + case SpellEffect::Fc_Amplify_Mod: if (type == focusFcAmplifyMod && base_value > value) { value = base_value; } break; - case SE_ImprovedHeal: + case SpellEffect::ImprovedHeal: if (type == focusImprovedHeal && base_value > value) { value = base_value; } break; - case SE_ReduceManaCost: + case SpellEffect::ReduceManaCost: if (type == focusManaCost) { value = base_value; } break; - case SE_IncreaseSpellHaste: + case SpellEffect::IncreaseSpellHaste: if (type == focusSpellHaste && base_value > value) { value = base_value; try_apply_to_item_click = is_from_item_click ? true : false; } break; - case SE_Fc_CastTimeMod2: + case SpellEffect::Fc_CastTimeMod2: if (type == focusFcCastTimeMod2 && base_value > value) { value = base_value; try_apply_to_item_click = is_from_item_click ? true : false; } break; - case SE_Fc_CastTimeAmt: + case SpellEffect::Fc_CastTimeAmt: if (type == focusFcCastTimeAmt && base_value > value) { value = base_value; try_apply_to_item_click = is_from_item_click ? true : false; } break; - case SE_IncreaseSpellDuration: + case SpellEffect::IncreaseSpellDuration: if (type == focusSpellDuration && base_value > value) { value = base_value; } break; - case SE_SpellDurationIncByTic: + case SpellEffect::SpellDurationIncByTic: if (type == focusSpellDurByTic && base_value > value) { value = base_value; } break; - case SE_SwarmPetDuration: + case SpellEffect::SwarmPetDuration: if (type == focusSwarmPetDuration && base_value > value) { value = base_value; } break; - case SE_IncreaseRange: + case SpellEffect::IncreaseRange: if (type == focusRange && base_value > value) { value = base_value; } break; - case SE_ReduceReagentCost: + case SpellEffect::ReduceReagentCost: if (type == focusReagentCost && base_value > value) { value = base_value; } break; - case SE_PetPowerIncrease: + case SpellEffect::PetPowerIncrease: if (type == focusPetPower && base_value > value) { value = base_value; } break; - case SE_SpellResistReduction: + case SpellEffect::SpellResistReduction: if (type == focusResistRate && base_value > value) { value = base_value; } break; - case SE_Fc_ResistIncoming: + case SpellEffect::Fc_ResistIncoming: if (type == focusFcResistIncoming && base_value > value) { value = base_value; } break; - case SE_SpellHateMod: + case SpellEffect::SpellHateMod: if (type == focusSpellHateMod) { if (value != 0) { if (value > 0) { @@ -5244,14 +5265,14 @@ int64 Mob::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) } break; - case SE_ReduceReuseTimer: + case SpellEffect::ReduceReuseTimer: if (type == focusReduceRecastTime) { value = base_value / 1000; try_apply_to_item_click = is_from_item_click ? true : false; } break; - case SE_TriggerOnCast: + case SpellEffect::TriggerOnCast: if (type == focusTriggerOnCast) { if (zone->random.Roll(base_value)) { value = limit_value; @@ -5263,19 +5284,19 @@ int64 Mob::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) } break; - case SE_FcSpellVulnerability: + case SpellEffect::FcSpellVulnerability: if (type == focusSpellVulnerability) { value = base_value; } break; - case SE_Fc_Spell_Damage_Pct_IncomingPC: + case SpellEffect::Fc_Spell_Damage_Pct_IncomingPC: if (type == focusFcSpellDamagePctIncomingPC) { value = base_value; } break; - case SE_BlockNextSpellFocus: + case SpellEffect::BlockNextSpellFocus: if (type == focusBlockNextSpell) { if (zone->random.Roll(base_value)) { value = 1; @@ -5283,122 +5304,122 @@ int64 Mob::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) } break; - case SE_FcTwincast: - if (type == focusTwincast && !IsEffectInSpell(spell_id, SE_TwinCastBlocker)) { + case SpellEffect::FcTwincast: + if (type == focusTwincast && !IsEffectInSpell(spell_id, SpellEffect::TwinCastBlocker)) { value = base_value; } break; // Note if using these as AA, make sure this is first focus used. - case SE_SympatheticProc: + case SpellEffect::SympatheticProc: if (type == focusSympatheticProc) { value = limit_value; } break; - case SE_FcDamageAmt: + case SpellEffect::FcDamageAmt: if (type == focusFcDamageAmt) { value = base_value; } break; - case SE_FcDamageAmt2: + case SpellEffect::FcDamageAmt2: if (type == focusFcDamageAmt2) { value = base_value; } break; - case SE_Fc_Amplify_Amt: + case SpellEffect::Fc_Amplify_Amt: if (type == focusFcAmplifyAmt) { value = base_value; } break; - case SE_FcDamageAmtCrit: + case SpellEffect::FcDamageAmtCrit: if (type == focusFcDamageAmtCrit) { value = base_value; } break; - case SE_FcDamageAmtIncoming: + case SpellEffect::FcDamageAmtIncoming: if (type == focusFcDamageAmtIncoming) { value = base_value; } break; - case SE_Fc_Spell_Damage_Amt_IncomingPC: + case SpellEffect::Fc_Spell_Damage_Amt_IncomingPC: if (type == focusFcSpellDamageAmtIncomingPC) { value = base_value; } break; - case SE_FcHealAmtIncoming: + case SpellEffect::FcHealAmtIncoming: if (type == focusFcHealAmtIncoming) { value = base_value; } break; - case SE_FcHealPctCritIncoming: + case SpellEffect::FcHealPctCritIncoming: if (type == focusFcHealPctCritIncoming) { value = base_value; } break; - case SE_FcHealAmtCrit: + case SpellEffect::FcHealAmtCrit: if (type == focusFcHealAmtCrit) { value = base_value; } break; - case SE_FcHealAmt: + case SpellEffect::FcHealAmt: if (type == focusFcHealAmt) { value = base_value; } break; - case SE_FcHealPctIncoming: + case SpellEffect::FcHealPctIncoming: if (type == focusFcHealPctIncoming) { value = base_value; } break; - case SE_FcBaseEffects: + case SpellEffect::FcBaseEffects: if (type == focusFcBaseEffects) { value = base_value; } break; - case SE_FcDamagePctCrit: + case SpellEffect::FcDamagePctCrit: if (type == focusFcDamagePctCrit) { value = base_value; } break; - case SE_FcIncreaseNumHits: + case SpellEffect::FcIncreaseNumHits: if (type == focusIncreaseNumHits) { value = base_value; } break; - case SE_FcLimitUse: + case SpellEffect::FcLimitUse: if (type == focusFcLimitUse) { value = base_value; } break; - case SE_FcMute: + case SpellEffect::FcMute: if (type == focusFcMute) { value = base_value; } break; - case SE_FcStunTimeMod: + case SpellEffect::FcStunTimeMod: if (type == focusFcStunTimeMod) { value = base_value; } break; - case SE_Fc_Cast_Spell_On_Land: + case SpellEffect::Fc_Cast_Spell_On_Land: if (type == focusFcCastSpellOnLand) { if (zone->random.Roll(base_value)) { value = limit_value; @@ -5446,7 +5467,7 @@ int64 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo } // No further checks if spell_id no_focusable, unless spell focus_id contains an override limiter. - if (spells[spell_id].not_focusable && !IsEffectInSpell(focus_id, SE_Ff_Override_NotFocusable)) { + if (spells[spell_id].not_focusable && !IsEffectInSpell(focus_id, SpellEffect::Ff_Override_NotFocusable)) { return 0; } @@ -5474,15 +5495,15 @@ int64 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo /* Certain limits require only one of several Include conditions to be true. Determined by limits being negative or positive Ie. Add damage to fire OR ice spells. If positive we 'Include', by checking each limit of same type to look for match until found. Opposed to just 'Excluding', where if set to negative, if we find that match then focus fails, ie Add damage to all spells BUT Fire. - 0/1 SE_LimitResist - 2/3 SE_LimitSpell - 4/5 SE_LimitEffect - 6/7 SE_LimitTarget - 8/9 SE_LimitSpellGroup: - 10/11 SE_LimitCastingSkill: - 12/13 SE_LimitSpellClass: - 14/15 SE_LimitSpellSubClass: - 16/17 SE_FFItemCLass: + 0/1 SpellEffect::_LimitResist + 2/3 SpellEffect::_LimitSpell + 4/5 SpellEffect::_LimitEffect + 6/7 SpellEffect::_LimitTarget + 8/9 SpellEffect::_LimitSpellGroup: + 10/11 SpellEffect::LimitCastingSkill: + 12/13 SpellEffect::LimitSpellClass: + 14/15 SpellEffect::LimitSpellSubClass: + 16/17 SpellEffect::FFItemCLass: Remember: Update MaxLimitInclude in spdat.h if adding new limits that require Includes */ @@ -5490,10 +5511,10 @@ int64 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo switch (focus_spell.effect_id[i]) { - case SE_Blank: + case SpellEffect::Blank: break; - case SE_LimitResist: + case SpellEffect::LimitResist: if (focus_spell.base_value[i] < 0) { if (spell.resist_type == -focus_spell.base_value[i]) { // Exclude return 0; @@ -5507,7 +5528,7 @@ int64 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo } break; - case SE_LimitInstant: + case SpellEffect::LimitInstant: if (focus_spell.base_value[i] == 1 && spell.buff_duration) { // Fail if not instant return 0; } @@ -5517,7 +5538,7 @@ int64 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo break; - case SE_LimitMaxLevel: + case SpellEffect::LimitMaxLevel: if (IsNPC()) { break; } @@ -5539,7 +5560,7 @@ int64 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo } break; - case SE_LimitMinLevel: + case SpellEffect::LimitMinLevel: if (IsNPC()) { break; } @@ -5548,19 +5569,19 @@ int64 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo } break; - case SE_LimitCastTimeMin: + case SpellEffect::LimitCastTimeMin: if (spells[spell_id].cast_time < (uint16) focus_spell.base_value[i]) { return (0); } break; - case SE_LimitCastTimeMax: + case SpellEffect::LimitCastTimeMax: if (spells[spell_id].cast_time > (uint16) focus_spell.base_value[i]) { return (0); } break; - case SE_LimitSpell: + case SpellEffect::LimitSpell: if (focus_spell.base_value[i] < 0) { // Exclude if (spell_id == -focus_spell.base_value[i]) { return (0); @@ -5574,14 +5595,14 @@ int64 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo } break; - case SE_LimitMinDur: + case SpellEffect::LimitMinDur: if (focus_spell.base_value[i] > CalcBuffDuration_formula(GetLevel(), spell.buff_duration_formula, spell.buff_duration)) { return (0); } break; - case SE_LimitEffect: + case SpellEffect::LimitEffect: if (focus_spell.base_value[i] < 0) { if (IsEffectInSpell(spell_id, -focus_spell.base_value[i])) { // Exclude return 0; @@ -5595,7 +5616,7 @@ int64 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo } break; - case SE_LimitSpellType: + case SpellEffect::LimitSpellType: switch (focus_spell.base_value[i]) { case 0: if (!IsDetrimentalSpell(spell_id)) { @@ -5613,19 +5634,19 @@ int64 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo } break; - case SE_LimitManaMin: + case SpellEffect::LimitManaMin: if (spell.mana < focus_spell.base_value[i]) { return 0; } break; - case SE_LimitManaMax: + case SpellEffect::LimitManaMax: if (spell.mana > focus_spell.base_value[i]) { return 0; } break; - case SE_LimitTarget: + case SpellEffect::LimitTarget: if (focus_spell.base_value[i] < 0) { if (-focus_spell.base_value[i] == spell.target_type) { // Exclude return 0; @@ -5639,7 +5660,7 @@ int64 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo } break; - case SE_LimitCombatSkills: + case SpellEffect::LimitCombatSkills: if (focus_spell.base_value[i] == 0 && (IsCombatSkill(spell_id) || IsCombatProc(spell_id))) { // Exclude Discs / Procs return 0; @@ -5650,7 +5671,7 @@ int64 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo break; - case SE_LimitSpellGroup: + case SpellEffect::LimitSpellGroup: if (focus_spell.base_value[i] < 0) { if (-focus_spell.base_value[i] == spell.spell_group) { // Exclude return 0; @@ -5664,7 +5685,7 @@ int64 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo } break; - case SE_LimitCastingSkill: + case SpellEffect::LimitCastingSkill: if (focus_spell.base_value[i] < 0) { if (-focus_spell.base_value[i] == spell.skill) { return 0; @@ -5678,7 +5699,7 @@ int64 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo } break; - case SE_LimitClass: + case SpellEffect::LimitClass: // Do not use this limit more then once per spell. If multiple class, treat value like items // would. if (!PassLimitClass(focus_spell.base_value[i], GetClass())) { @@ -5686,31 +5707,31 @@ int64 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo } break; - case SE_LimitRace: + case SpellEffect::LimitRace: if (focus_spell.base_value[i] != GetRace()) { return 0; } break; - case SE_LimitUseMin: + case SpellEffect::LimitUseMin: if (focus_spell.base_value[i] > spell.hit_number) { return 0; } break; - case SE_LimitUseType: + case SpellEffect::LimitUseType: if (focus_spell.base_value[i] != spell.hit_number_type) { return 0; } break; - case SE_CastonFocusEffect: + case SpellEffect::CastonFocusEffect: if (focus_spell.base_value[i] > 0) { Caston_spell_id = focus_spell.base_value[i]; } break; - case SE_LimitSpellClass: + case SpellEffect::LimitSpellClass: if (focus_spell.base_value[i] < 0) { // Exclude if (-focus_spell.base_value[i] == spell.spell_class) { return 0; @@ -5724,7 +5745,7 @@ int64 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo } break; - case SE_LimitSpellSubclass: + case SpellEffect::LimitSpellSubclass: if (focus_spell.base_value[i] < 0) { // Exclude if (-focus_spell.base_value[i] == spell.spell_subclass) { return 0; @@ -5738,7 +5759,7 @@ int64 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo } break; - case SE_Ff_Same_Caster://hmm do i need to pass casterid from buff slot here + case SpellEffect::Ff_Same_Caster://hmm do i need to pass casterid from buff slot here if (focus_spell.base_value[i] == 0) { if (caster && casterid == caster->GetID()) { return 0; @@ -5751,7 +5772,7 @@ int64 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo } break; - case SE_Ff_CasterClass: { + case SpellEffect::Ff_CasterClass: { // Do not use this limit more then once per spell. If multiple class, treat value like items would. if (caster && !PassLimitClass(focus_spell.base_value[i], caster->GetClass())) { @@ -5760,51 +5781,51 @@ int64 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo break; } - case SE_Ff_DurationMax: + case SpellEffect::Ff_DurationMax: if (focus_spell.base_value[i] > spell.buff_duration) { return 0; } break; - case SE_Ff_Endurance_Min: + case SpellEffect::Ff_Endurance_Min: if (spell.endurance_cost < focus_spell.base_value[i]) { return 0; } break; - case SE_Ff_Endurance_Max: + case SpellEffect::Ff_Endurance_Max: if (spell.endurance_cost > focus_spell.base_value[i]) { return 0; } break; - case SE_Ff_ReuseTimeMin: + case SpellEffect::Ff_ReuseTimeMin: if (spell.recast_time < focus_spell.base_value[i]) { return 0; } break; - case SE_Ff_ReuseTimeMax: + case SpellEffect::Ff_ReuseTimeMax: if (spell.recast_time > focus_spell.base_value[i]) { return 0; } break; - case SE_Ff_Value_Min: + case SpellEffect::Ff_Value_Min: index_id = GetSpellEffectIndex(spell_id, focus_spell.limit_value[i]); if (index_id >= 0 && spell.base_value[index_id] < focus_spell.base_value[i]) { return 0; } break; - case SE_Ff_Value_Max: + case SpellEffect::Ff_Value_Max: index_id = GetSpellEffectIndex(spell_id, focus_spell.limit_value[i]); if (index_id >= 0 && spell.base_value[index_id] > focus_spell.base_value[i]) { return 0; } break; - case SE_Ff_FocusTimerMin: + case SpellEffect::Ff_FocusTimerMin: if (IsFocusProcLimitTimerActive(focus_spell.id)) { return 0; } @@ -5813,7 +5834,7 @@ int64 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo } break; - case SE_FFItemClass: + case SpellEffect::FFItemClass: /* Limits focuses to check if cast from item clicks. Can be used to INCLUDE or EXCLUDE items by ItemType and/or SubType and/or Slots @@ -5904,8 +5925,8 @@ int64 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo break; // handle effects - case SE_ImprovedDamage: - case SE_ImprovedDamage2: + case SpellEffect::ImprovedDamage: + case SpellEffect::ImprovedDamage2: if (!RuleB(Spells, UseClassicSpellFocus)) { if (type == focusImprovedDamage || type == focusImprovedDamage2) { value = GetFocusRandomEffectivenessValue(focus_spell.base_value[i], focus_spell.limit_value[i], best_focus); @@ -5919,14 +5940,14 @@ int64 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo } } - case SE_Fc_Amplify_Mod: + case SpellEffect::Fc_Amplify_Mod: if (type == focusFcAmplifyMod && focus_spell.base_value[i] > value) { value = focus_spell.base_value[i]; } break; - case SE_ImprovedHeal: - case SE_ReduceManaCost: + case SpellEffect::ImprovedHeal: + case SpellEffect::ReduceManaCost: if (!RuleB(Spells, UseClassicSpellFocus)) { if (type == focusImprovedHeal || type == focusManaCost) { value = GetFocusRandomEffectivenessValue(focus_spell.base_value[i], focus_spell.limit_value[i], best_focus); @@ -5941,52 +5962,52 @@ int64 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo } break; - case SE_IncreaseSpellHaste: + case SpellEffect::IncreaseSpellHaste: if (type == focusSpellHaste && focus_spell.base_value[i] > value) { value = focus_spell.base_value[i]; try_apply_to_item_click = is_from_item_click ? true : false; } break; - case SE_Fc_CastTimeMod2: + case SpellEffect::Fc_CastTimeMod2: if (type == focusFcCastTimeMod2 && focus_spell.base_value[i] > value) { value = focus_spell.base_value[i]; try_apply_to_item_click = is_from_item_click ? true : false; } break; - case SE_Fc_CastTimeAmt: + case SpellEffect::Fc_CastTimeAmt: if (type == focusFcCastTimeAmt && focus_spell.base_value[i] > value) { value = focus_spell.base_value[i]; try_apply_to_item_click = is_from_item_click ? true : false; } break; - case SE_IncreaseSpellDuration: + case SpellEffect::IncreaseSpellDuration: if (type == focusSpellDuration && focus_spell.base_value[i] > value) { value = focus_spell.base_value[i]; } break; - case SE_SpellDurationIncByTic: + case SpellEffect::SpellDurationIncByTic: if (type == focusSpellDurByTic && focus_spell.base_value[i] > value) { value = focus_spell.base_value[i]; } break; - case SE_SwarmPetDuration: + case SpellEffect::SwarmPetDuration: if (type == focusSwarmPetDuration && focus_spell.base_value[i] > value) { value = focus_spell.base_value[i]; } break; - case SE_IncreaseRange: + case SpellEffect::IncreaseRange: if (type == focusRange && focus_spell.base_value[i] > value) { value = focus_spell.base_value[i]; } break; - case SE_ReduceReagentCost: + case SpellEffect::ReduceReagentCost: if (!RuleB(Spells, UseClassicSpellFocus)) { if (type == focusReagentCost) { value = GetFocusRandomEffectivenessValue(focus_spell.base_value[i], focus_spell.limit_value[i], best_focus); @@ -5999,25 +6020,25 @@ int64 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo } break; - case SE_PetPowerIncrease: + case SpellEffect::PetPowerIncrease: if (type == focusPetPower && focus_spell.base_value[i] > value) { value = focus_spell.base_value[i]; } break; - case SE_SpellResistReduction: + case SpellEffect::SpellResistReduction: if (type == focusResistRate) { value = GetFocusRandomEffectivenessValue(focus_spell.base_value[i], focus_spell.limit_value[i], best_focus); } break; - case SE_Fc_ResistIncoming: + case SpellEffect::Fc_ResistIncoming: if (type == focusFcResistIncoming && focus_spell.base_value[i] > value) { value = focus_spell.base_value[i]; } break; - case SE_SpellHateMod: + case SpellEffect::SpellHateMod: if (!RuleB(Spells, UseClassicSpellFocus)) { if (type == focusSpellHateMod) { value = GetFocusRandomEffectivenessValue(focus_spell.base_value[i], focus_spell.limit_value[i], best_focus); @@ -6034,14 +6055,14 @@ int64 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo } break; - case SE_ReduceReuseTimer: + case SpellEffect::ReduceReuseTimer: if (type == focusReduceRecastTime) { value = focus_spell.base_value[i] / 1000; try_apply_to_item_click = is_from_item_click ? true : false; } break; - case SE_TriggerOnCast: + case SpellEffect::TriggerOnCast: if (type == focusTriggerOnCast) { if (zone->random.Roll(focus_spell.base_value[i])) { value = focus_spell.limit_value[i]; @@ -6052,7 +6073,7 @@ int64 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo } break; - case SE_BlockNextSpellFocus: + case SpellEffect::BlockNextSpellFocus: if (type == focusBlockNextSpell) { if (zone->random.Roll(focus_spell.base_value[i])) { value = 1; @@ -6060,145 +6081,145 @@ int64 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo } break; - case SE_SympatheticProc: + case SpellEffect::SympatheticProc: if (type == focusSympatheticProc) { value = focus_id; } break; - case SE_FcSpellVulnerability: + case SpellEffect::FcSpellVulnerability: if (type == focusSpellVulnerability) { value = GetFocusRandomEffectivenessValue(focus_spell.base_value[i], focus_spell.limit_value[i], best_focus); } break; - case SE_Fc_Spell_Damage_Pct_IncomingPC: + case SpellEffect::Fc_Spell_Damage_Pct_IncomingPC: if (type == focusFcSpellDamagePctIncomingPC) { value = GetFocusRandomEffectivenessValue(focus_spell.base_value[i], focus_spell.limit_value[i], best_focus); } break; - case SE_FcTwincast: - if (type == focusTwincast && !IsEffectInSpell(spell_id, SE_TwinCastBlocker)) { + case SpellEffect::FcTwincast: + if (type == focusTwincast && !IsEffectInSpell(spell_id, SpellEffect::TwinCastBlocker)) { value = focus_spell.base_value[i]; } break; - case SE_FcDamageAmt: + case SpellEffect::FcDamageAmt: if (type == focusFcDamageAmt) { value = focus_spell.base_value[i]; } break; - case SE_FcDamageAmt2: + case SpellEffect::FcDamageAmt2: if (type == focusFcDamageAmt2) { value = focus_spell.base_value[i]; } break; - case SE_Fc_Amplify_Amt: + case SpellEffect::Fc_Amplify_Amt: if (type == focusFcAmplifyAmt) { value = focus_spell.base_value[i]; } break; - case SE_FcDamageAmtCrit: + case SpellEffect::FcDamageAmtCrit: if (type == focusFcDamageAmtCrit) { value = focus_spell.base_value[i]; } break; - case SE_FcDamageAmtIncoming: + case SpellEffect::FcDamageAmtIncoming: if (type == focusFcDamageAmtIncoming) { value = focus_spell.base_value[i]; } break; - case SE_Fc_Spell_Damage_Amt_IncomingPC: + case SpellEffect::Fc_Spell_Damage_Amt_IncomingPC: if (type == focusFcSpellDamageAmtIncomingPC) { value = focus_spell.base_value[i]; } break; - case SE_FcHealAmtIncoming: + case SpellEffect::FcHealAmtIncoming: if (type == focusFcHealAmtIncoming) { value = focus_spell.base_value[i]; } break; - case SE_FcDamagePctCrit: + case SpellEffect::FcDamagePctCrit: if (type == focusFcDamagePctCrit) { value = focus_spell.base_value[i]; } break; - case SE_FcHealPctCritIncoming: + case SpellEffect::FcHealPctCritIncoming: if (type == focusFcHealPctCritIncoming) { value = GetFocusRandomEffectivenessValue(focus_spell.base_value[i], focus_spell.limit_value[i], best_focus); } break; - case SE_FcHealAmtCrit: + case SpellEffect::FcHealAmtCrit: if (type == focusFcHealAmtCrit) { value = focus_spell.base_value[i]; } break; - case SE_FcHealAmt: + case SpellEffect::FcHealAmt: if (type == focusFcHealAmt) { value = focus_spell.base_value[i]; } break; - case SE_FcHealPctIncoming: + case SpellEffect::FcHealPctIncoming: if (type == focusFcHealPctIncoming) { value = GetFocusRandomEffectivenessValue(focus_spell.base_value[i], focus_spell.limit_value[i], best_focus); } break; - case SE_FcBaseEffects: + case SpellEffect::FcBaseEffects: if (type == focusFcBaseEffects) { value = focus_spell.base_value[i]; } break; - case SE_FcIncreaseNumHits: + case SpellEffect::FcIncreaseNumHits: if (type == focusIncreaseNumHits) { value = focus_spell.base_value[i]; } break; - case SE_FcLimitUse: + case SpellEffect::FcLimitUse: if (type == focusFcLimitUse) { value = focus_spell.base_value[i]; } break; - case SE_FcMute: + case SpellEffect::FcMute: if (type == focusFcMute) { value = focus_spell.base_value[i]; } break; - case SE_FcStunTimeMod: + case SpellEffect::FcStunTimeMod: if (type == focusFcStunTimeMod) { value = focus_spell.base_value[i]; } break; - case SE_FcTimerRefresh: + case SpellEffect::FcTimerRefresh: if (type == focusFcTimerRefresh) { value = focus_spell.base_value[i]; } break; - case SE_FcTimerLockout: + case SpellEffect::FcTimerLockout: if (type == focusFcTimerLockout) { value = focus_spell.base_value[i]; } break; - case SE_Fc_Cast_Spell_On_Land: + case SpellEffect::Fc_Cast_Spell_On_Land: if (type == focusFcCastSpellOnLand) { if (zone->random.Roll(focus_spell.base_value[i])) { value = focus_spell.limit_value[i]; @@ -6277,7 +6298,7 @@ void Mob::TryTriggerOnCastFocusEffect(focusType type, uint16 spell_id) temp_item = ins->GetItem(); if (temp_item && temp_item->Focus.Effect > 0 && IsValidSpell(temp_item->Focus.Effect)) { focus_spell_id = temp_item->Focus.Effect; - if (!IsEffectInSpell(focus_spell_id, SE_TriggerOnCast)) { + if (!IsEffectInSpell(focus_spell_id, SpellEffect::TriggerOnCast)) { continue; } @@ -6294,7 +6315,7 @@ void Mob::TryTriggerOnCastFocusEffect(focusType type, uint16 spell_id) if (temp_item_aug && temp_item_aug->Focus.Effect > 0 && IsValidSpell(temp_item_aug->Focus.Effect)) { focus_spell_id = temp_item_aug->Focus.Effect; - if (!IsEffectInSpell(focus_spell_id, SE_TriggerOnCast)) { + if (!IsEffectInSpell(focus_spell_id, SpellEffect::TriggerOnCast)) { continue; } @@ -6317,7 +6338,7 @@ void Mob::TryTriggerOnCastFocusEffect(focusType type, uint16 spell_id) continue; } - if (!IsEffectInSpell(focus_spell_id, SE_TriggerOnCast)) { + if (!IsEffectInSpell(focus_spell_id, SpellEffect::TriggerOnCast)) { continue; } @@ -6808,7 +6829,7 @@ int64 Mob::GetFocusEffect(focusType type, uint16 spell_id, Mob *caster, bool fro } } - if (type == focusReagentCost && (IsEffectInSpell(spell_id, SE_SummonItem) || IsSacrificeSpell(spell_id))) { + if (type == focusReagentCost && (IsEffectInSpell(spell_id, SpellEffect::SummonItem) || IsSacrificeSpell(spell_id))) { return 0; } //Summon Spells that require reagents are typically imbue type spells, enchant metal, sacrifice and shouldn't be affected @@ -6999,7 +7020,7 @@ void Mob::CheckNumHitsRemaining(NumHit type, int32 buff_slot, uint16 spell_id) 3: [Incoming Spells] (180=SE_ResistSpellChance, 296=SE_FcSpellVulnerability) //Note: Determinetal spells only unless proven otherwise 4: [Outgoing Spells] 5: [Outgoing Hit Successes] (220=SE_SkillDamageAmount, 178=SE_MeleeLifetap, 121=SE_ReverseDS, ?373=SE_CastOnFadeEffectAlways) - 6: [Incoming Hit Successes] (59=SE_DamageShield, 197=SE_SkillDamageTaken, 162=define SE_MitigateMeleeDamage) + 6: [Incoming Hit Successes] (59=SE_DamageShield, 197=SE_SkillDamageTaken, 162=define SpellEffect::MitigateMeleeDamage) 7: [Matching Spells] *When focus is triggered (focus effects) 8: [Incoming Hits or Spells] (329=SE_ManaAbsorbPercentDamage) 9: [Reflected Spells] If successful @@ -7141,8 +7162,8 @@ bool Mob::TryDivineSave() spellbonuses.DivineSaveChance[SBIndex::DIVINE_SAVE_SPELL_TRIGGER_ID] }; //Fade the divine save effect here after saving the old effects off. - //That way, if desired, the effect could apply SE_DivineSave again. - BuffFadeByEffect(SE_DivineSave); + //That way, if desired, the effect could apply SpellEffect::DivineSave again. + BuffFadeByEffect(SpellEffect::DivineSave); for(size_t i = 0; i < ( sizeof(EffectsToTry) / sizeof(EffectsToTry[0]) ); ++i) { if( EffectsToTry[i] ) @@ -7170,7 +7191,7 @@ bool Mob::TryDeathSave() { the a value of a heal modifier of the base effects heal. Ie. Divine Intervention is 8000 HP Max UD1=20, therefore heal is 8000*20/100 -No evidence of chance rate increasing between UD1-3, numbers indicate it uses same CHA rate as first DI. - -In later expansions this SE_DeathSave was given a level limit and a heal value in its effect data. + -In later expansions this SpellEffect::DeathSave was given a level limit and a heal value in its effect data. */ if (spellbonuses.DeathSave[SBIndex::DEATH_SAVE_TYPE]){ @@ -7314,7 +7335,7 @@ float Mob::GetSympatheticProcChances(uint16 spell_id, int16 ProcRateMod, int32 I int16 Mob::GetSympatheticSpellProcRate(uint16 spell_id) { for (int i = 0; i < EFFECT_COUNT; i++){ - if (spells[spell_id].effect_id[i] == SE_SympatheticProc) + if (spells[spell_id].effect_id[i] == SpellEffect::SympatheticProc) return spells[spell_id].base_value[i]; } @@ -7324,7 +7345,7 @@ int16 Mob::GetSympatheticSpellProcRate(uint16 spell_id) uint16 Mob::GetSympatheticSpellProcID(uint16 spell_id) { for (int i = 0; i < EFFECT_COUNT; i++){ - if (spells[spell_id].effect_id[i] == SE_SympatheticProc) + if (spells[spell_id].effect_id[i] == SpellEffect::SympatheticProc) return spells[spell_id].limit_value[i]; } @@ -7349,8 +7370,8 @@ int64 Mob::GetFcDamageAmtIncoming(Mob *caster, int32 spell_id, bool from_buff_ti { //THIS is target of spell cast int64 dmg = 0; - dmg += GetFocusEffect(focusFcDamageAmtIncoming, spell_id, caster, from_buff_tic); //SPA 297 SE_FcDamageAmtIncoming - dmg += GetFocusEffect(focusFcSpellDamageAmtIncomingPC, spell_id, caster, from_buff_tic); //SPA 484 SE_Fc_Spell_Damage_Amt_IncomingPC + dmg += GetFocusEffect(focusFcDamageAmtIncoming, spell_id, caster, from_buff_tic); //SPA 297 SpellEffect::FcDamageAmtIncoming + dmg += GetFocusEffect(focusFcSpellDamageAmtIncomingPC, spell_id, caster, from_buff_tic); //SPA 484 SpellEffect::Fc_Spell_Damage_Amt_IncomingPC return dmg; } @@ -7410,8 +7431,8 @@ int64 Mob::GetFocusIncoming(focusType type, int effect, Mob *caster, uint32 spel bool Mob::PassLimitClass(uint32 Classes_, uint16 Class_) { - //The class value for SE_LimitClass is +1 to its equivelent value in item dbase - //Example Bard on items is '128' while Bard on SE_LimitClass is '256', keep this in mind if making custom spells. + //The class value for SpellEffect::LimitClass is +1 to its equivelent value in item dbase + //Example Bard on items is '128' while Bard on SpellEffect::LimitClass is '256', keep this in mind if making custom spells. if (Class_ > 16) return false; @@ -7442,44 +7463,57 @@ void Mob::DispelMagic(Mob* caster, uint16 spell_id, int effect_value) } } -uint16 Mob::GetSpellEffectResistChance(uint16 spell_id) +bool Mob::TrySpellEffectResist(uint16 spell_id) { + /* + SEResist variable + 0 = spell effect id to be check if can resist + 1 = percent chance of resistance + */ - if(!IsValidSpell(spell_id)) - return 0; - if (!aabonuses.SEResist[1] && !spellbonuses.SEResist[1] && !itembonuses.SEResist[1]) - return 0; + if (!IsValidSpell(spell_id)) { + return false; + } - uint16 resist_chance = 0; + if (!aabonuses.SEResist[1] && !spellbonuses.SEResist[1] && !itembonuses.SEResist[1]) { + return false; + } + + int resist_chance = 0; for(int i = 0; i < EFFECT_COUNT; ++i) { - bool found = false; + if (spells[spell_id].effect_id[i] == SpellEffect::Blank) { + continue; + } for(int d = 0; d < MAX_RESISTABLE_EFFECTS*2; d+=2) { + resist_chance = 0; if (spells[spell_id].effect_id[i] == aabonuses.SEResist[d]){ resist_chance += aabonuses.SEResist[d+1]; - found = true; } if (spells[spell_id].effect_id[i] == itembonuses.SEResist[d]){ resist_chance += itembonuses.SEResist[d+1]; - found = true; } - if (spells[spell_id].effect_id[i] == spellbonuses.SEResist[d]){ resist_chance += spellbonuses.SEResist[d+1]; - found = true; } - if (found) - continue; + if (resist_chance) { + if (zone->random.Roll(resist_chance)) { + LogSpells("Resisted spell from Spell Effect Resistance, had [{}] chance to resist spell effect id [{}]", resist_chance, spells[spell_id].effect_id[i]); + return true; + } + + break; + } } } - return resist_chance; + return false; } bool Mob::TryDispel(uint8 caster_level, uint8 buff_level, int level_modifier){ @@ -8250,7 +8284,7 @@ bool Mob::PassCastRestriction(int value) case HAS_NO_MANA_BURN_BUFF: { bool has_effect = false; for (int i = 0; i < GetMaxTotalSlots(); i++) { - if (IsValidSpell(buffs[i].spellid) && IsEffectInSpell(buffs[i].spellid, SE_ManaBurn)) { + if (IsValidSpell(buffs[i].spellid) && IsEffectInSpell(buffs[i].spellid, SpellEffect::ManaBurn)) { has_effect = true; } } @@ -9701,7 +9735,7 @@ void Mob::ResourceTap(int64 damage, uint16 spellid) return; for (int i = 0; i < EFFECT_COUNT; i++) { - if (spells[spellid].effect_id[i] == SE_ResourceTap) { + if (spells[spellid].effect_id[i] == SpellEffect::ResourceTap) { damage = (damage * spells[spellid].base_value[i]) / 1000; if (damage) { @@ -9731,9 +9765,9 @@ void Mob::TryTriggerThreshHold(int64 damage, int effect_id, Mob* attacker){ if (damage <= 0) return; - if ((SE_TriggerMeleeThreshold == effect_id) && !spellbonuses.TriggerMeleeThreshold ) + if ((SpellEffect::TriggerMeleeThreshold == effect_id) && !spellbonuses.TriggerMeleeThreshold ) return; - else if ((SE_TriggerSpellThreshold == effect_id) && !spellbonuses.TriggerSpellThreshold) + else if ((SpellEffect::TriggerSpellThreshold == effect_id) && !spellbonuses.TriggerSpellThreshold) return; int buff_count = GetMaxTotalSlots(); @@ -9770,10 +9804,10 @@ void Mob::TryTriggerThreshHold(int64 damage, int effect_id, Mob* attacker){ void Mob::CastSpellOnLand(Mob* caster, int32 spell_id) { /* - This function checks for incoming spells on a mob, if they meet the criteria for focus SE_Fc_Cast_Spell_on_Land then + This function checks for incoming spells on a mob, if they meet the criteria for focus SpellEffect::Fc_Cast_Spell_on_Land then a new spell will be cast by THIS mob as specified by the focus effect. Note: Chance to cast the spell is determined in the CalcFocusEffect function if not 100pct. - ApplyFocusProcLimiter() function checks for SE_Proc_Timer_Modifier which allows for limiting how often a spell from effect can be triggered + ApplyFocusProcLimiter() function checks for SpellEffect::Proc_Timer_Modifier which allows for limiting how often a spell from effect can be triggered for example, if set to base=1 and base2= 1500, then for everyone 1 successful trigger, you will be unable to trigger again for 1.5 seconds. Live only has this focus in buffs/debuffs that can be placed on a target. TODO: Will consider adding support for it as AA and Item. @@ -9789,7 +9823,7 @@ void Mob::CastSpellOnLand(Mob* caster, int32 spell_id) int buff_count = GetMaxTotalSlots(); for (int i = 0; i < buff_count; i++) { - if ((IsValidSpell(buffs[i].spellid) && (buffs[i].spellid != spell_id) && IsEffectInSpell(buffs[i].spellid, SE_Fc_Cast_Spell_On_Land))) { + if ((IsValidSpell(buffs[i].spellid) && (buffs[i].spellid != spell_id) && IsEffectInSpell(buffs[i].spellid, SpellEffect::Fc_Cast_Spell_On_Land))) { //Step 2: Check if we pass all focus limiters and focus chance roll trigger_spell_id = CalcFocusEffect(focusFcCastSpellOnLand, buffs[i].spellid, spell_id, false, buffs[i].casterid, caster); @@ -9841,18 +9875,18 @@ void Mob::BreakInvisibleSpells() { if(invisible) { nobuff_invisible = 0; - BuffFadeByEffect(SE_Invisibility); - BuffFadeByEffect(SE_Invisibility2); + BuffFadeByEffect(SpellEffect::Invisibility); + BuffFadeByEffect(SpellEffect::Invisibility2); } if(invisible_undead) { ZeroInvisibleVars(InvisType::T_INVISIBLE_VERSE_UNDEAD); - BuffFadeByEffect(SE_InvisVsUndead); - BuffFadeByEffect(SE_InvisVsUndead2); + BuffFadeByEffect(SpellEffect::InvisVsUndead); + BuffFadeByEffect(SpellEffect::InvisVsUndead2); } if(invisible_animals){ ZeroInvisibleVars(InvisType::T_INVISIBLE_VERSE_ANIMAL); - BuffFadeByEffect(SE_ImprovedInvisAnimals); - BuffFadeByEffect(SE_InvisVsAnimals); + BuffFadeByEffect(SpellEffect::ImprovedInvisAnimals); + BuffFadeByEffect(SpellEffect::InvisVsAnimals); } } @@ -9908,8 +9942,8 @@ bool Mob::HarmonySpellLevelCheck(int32 spell_id, Mob *target) } for (int i = 0; i < EFFECT_COUNT; i++) { - // not important to check limit on SE_Lull as it doesnt have one and if the other components won't land, then SE_Lull wont either - if (spells[spell_id].effect_id[i] == SE_ChangeFrenzyRad || spells[spell_id].effect_id[i] == SE_Harmony) { + // not important to check limit on SpellEffect::Lull as it doesnt have one and if the other components won't land, then SpellEffect::Lull wont either + if (spells[spell_id].effect_id[i] == SpellEffect::ChangeFrenzyRad || spells[spell_id].effect_id[i] == SpellEffect::Harmony) { if ((spells[spell_id].max_value[i] != 0 && target->GetLevel() > spells[spell_id].max_value[i]) || target->GetSpecialAbility(SpecialAbility::PacifyImmunity)) { return false; } @@ -9950,14 +9984,14 @@ bool Mob::PassCharmTargetRestriction(Mob *target) { bool Mob::PassLimitToSkill(EQ::skills::SkillType skill, int32 spell_id, int proc_type, int aa_id) { /* - Check if SE_AddMeleProc or SE_RangedProc have a skill limiter. Passes automatically if no skill limiters present. + Check if SpellEffect::AddMeleProc or SpellEffect::RangedProc have a skill limiter. Passes automatically if no skill limiters present. */ int32 proc_type_spaid = 0; if (proc_type == ProcType::MELEE_PROC) { - proc_type_spaid = SE_AddMeleeProc; + proc_type_spaid = SpellEffect::AddMeleeProc; } if (proc_type == ProcType::RANGED_PROC) { - proc_type_spaid = SE_RangedProc; + proc_type_spaid = SpellEffect::RangedProc; } bool match_proc_type = false; @@ -9973,7 +10007,7 @@ bool Mob::PassLimitToSkill(EQ::skills::SkillType skill, int32 spell_id, int proc if (spells[spell_id].effect_id[i] == proc_type_spaid) { match_proc_type = true; } - if (match_proc_type && spells[spell_id].effect_id[i] == SE_LimitToSkill && spells[spell_id].base_value[i] <= EQ::skills::HIGHEST_SKILL) { + if (match_proc_type && spells[spell_id].effect_id[i] == SpellEffect::LimitToSkill && spells[spell_id].base_value[i] <= EQ::skills::HIGHEST_SKILL) { has_limit_check = true; if (spells[spell_id].base_value[i] == skill) { @@ -10010,7 +10044,7 @@ bool Mob::PassLimitToSkill(EQ::skills::SkillType skill, int32 spell_id, int proc match_proc_type = true; } - if (match_proc_type && effect.effect_id == SE_LimitToSkill && effect.base_value <= EQ::skills::HIGHEST_SKILL) { + if (match_proc_type && effect.effect_id == SpellEffect::LimitToSkill && effect.base_value <= EQ::skills::HIGHEST_SKILL) { has_limit_check = true; if (effect.base_value == skill) { return true; @@ -10078,11 +10112,11 @@ bool Mob::NegateSpellEffect(uint16 spell_id, int effect_id) for (int i = 0; i < GetMaxTotalSlots(); i++) { //Check for any buffs containing NegateEffect - if (IsValidSpell(buffs[i].spellid) && IsEffectInSpell(buffs[i].spellid, SE_NegateSpellEffect) && spell_id != buffs[i].spellid) { + if (IsValidSpell(buffs[i].spellid) && IsEffectInSpell(buffs[i].spellid, SpellEffect::NegateSpellEffect) && spell_id != buffs[i].spellid) { //Match each of the negate effects with the current spell effect, if found, that effect will not be applied. for (int j = 0; j < EFFECT_COUNT; j++) { - if (spells[buffs[i].spellid].effect_id[j] == SE_NegateSpellEffect && + if (spells[buffs[i].spellid].effect_id[j] == SpellEffect::NegateSpellEffect && spells[buffs[i].spellid].limit_value[j] == effect_id && (spells[buffs[i].spellid].base_value[j] == NEGATE_SPA_ALL_BONUSES || spells[buffs[i].spellid].base_value[j] == NEGATE_SPA_SPELLBONUS || @@ -10108,7 +10142,7 @@ int Mob::GetMemoryBlurChance(int base_chance) Memory blur is applied to mez on initial cast using same formula. However, recasting on a target that is already mezed will not give a chance to memory blur. The blur is not checked on buff ticks. - SPA 242 SE_IncreaseChanceMemwipe modifies the final chance after all bonuses are applied. + SPA 242 SpellEffect::IncreaseChanceMemwipe modifies the final chance after all bonuses are applied. This is also applied to memory blur from mez spells. this = caster @@ -10215,7 +10249,7 @@ void Mob::SpreadVirusEffect(int32 spell_id, uint32 caster_id, int32 buff_tics_re bool Mob::IsFocusProcLimitTimerActive(int32 focus_spell_id) { /* - Used with SPA 511 SE_Ff_FocusTimerMin to limit how often a focus effect can be applied. + Used with SPA 511 SpellEffect::Ff_FocusTimerMin to limit how often a focus effect can be applied. Ie. Can only have a spell trigger once every 15 seconds, or to be more creative can only have the fire spells received a very high special focused once every 30 seconds. Note, this stores timers for both spell, item and AA related focuses For AA the focus_spell_id @@ -10257,7 +10291,7 @@ void Mob::SetFocusProcLimitTimer(int32 focus_spell_id, uint32 focus_reuse_time) bool Mob::IsProcLimitTimerActive(int32 base_spell_id, uint32 proc_reuse_time, int proc_type) { /* - Used with SPA 512 SE_Proc_Timer_Modifier to limit how often a proc can be cast. + Used with SPA 512 SpellEffect::Proc_Timer_Modifier to limit how often a proc can be cast. If this effect exists it will prevent the next proc from firing until the timer defined in SPA 512 is finished. Ie. 1 proc every 55 seconds. Spell, Ranged, and Defensive procs all have their own timer array, therefore @@ -10392,7 +10426,7 @@ void Mob::ApplyIllusionToCorpse(int32 spell_id, Corpse* new_corpse) { } for (int i = 0; i < EFFECT_COUNT; i++){ - if (spells[spell_id].effect_id[i] == SE_Illusion) { + if (spells[spell_id].effect_id[i] == SpellEffect::Illusion) { new_corpse->ApplySpellEffectIllusion(spell_id, nullptr, -1, spells[spell_id].base_value[i], spells[spell_id].limit_value[i], spells[spell_id].max_value[i]); return; } @@ -10558,7 +10592,7 @@ bool Mob::HasPersistDeathIllusion(int32 spell_id) { if ( spell_id != SPELL_MINOR_ILLUSION && spell_id != SPELL_ILLUSION_TREE && - IsEffectInSpell(spell_id, SE_Illusion) && + IsEffectInSpell(spell_id, SpellEffect::Illusion) && IsBeneficialSpell(spell_id) ) { return true; @@ -10660,7 +10694,7 @@ int Mob::GetBuffStatValueBySlot(uint8 slot, const char* stat_identifier) if (id == "caster_level") { return buffs[slot].casterlevel; } else if (id == "spell_id") { return buffs[slot].spellid; } - else if (id == "caster_id") { return buffs[slot].spellid;; } + else if (id == "caster_id") { return buffs[slot].spellid; } else if (id == "ticsremaining") { return buffs[slot].ticsremaining; } else if (id == "counters") { return buffs[slot].counters; } else if (id == "hit_number") { return buffs[slot].hit_number; } diff --git a/zone/spells.cpp b/zone/spells.cpp index 7b644cd3a..c37fa4467 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -80,7 +80,7 @@ Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org) #include "../common/repositories/character_corpses_repository.h" #include "../common/repositories/spell_buckets_repository.h" -#include "data_bucket.h" +#include "../common/data_bucket.h" #include "quest_parser_collection.h" #include "string_ids.h" #include "worldserver.h" @@ -89,6 +89,7 @@ Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org) #include #include +#include "queryserv.h" #ifndef WIN32 #include @@ -106,10 +107,11 @@ Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org) #include "mob.h" #include "water_map.h" -extern Zone* zone; +extern Zone *zone; extern volatile bool is_zone_loaded; -extern WorldServer worldserver; -extern FastMath g_Math; +extern WorldServer worldserver; +extern FastMath g_Math; +extern QueryServ *QServ; using EQ::spells::CastingSlot; @@ -182,14 +184,14 @@ bool Mob::CastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, } //Goal of Spells:UseSpellImpliedTargeting is to replicate the EQ2 feature where spells will 'pass through' invalid targets to target's target to try to find a valid target. - if (RuleB(Spells,UseSpellImpliedTargeting) && IsClient()) { + if (RuleB(Spells,UseSpellImpliedTargeting) && IsOfClientBot()) { Mob* spell_target = entity_list.GetMobID(target_id); if (spell_target) { Mob* targets_target = spell_target->GetTarget(); if (targets_target) { // If either this is beneficial and the target is not a player or player's pet or vis versa - if ((IsBeneficialSpell(spell_id) && (!(spell_target->IsClient() || (spell_target->HasOwner() && spell_target->GetOwner()->IsClient())))) - || (IsDetrimentalSpell(spell_id) && (spell_target->IsClient() || (spell_target->HasOwner() && spell_target->GetOwner()->IsClient())))) { + if ((IsBeneficialSpell(spell_id) && (!(spell_target->IsOfClientBot() || (spell_target->HasOwner() && spell_target->GetOwner()->IsOfClientBot())))) + || (IsDetrimentalSpell(spell_id) && (spell_target->IsOfClientBot() || (spell_target->HasOwner() && spell_target->GetOwner()->IsOfClientBot())))) { //Check if the target's target is a valid target; we can use DoCastingChecksOnTarget() here because we can let it handle the failure as vanilla would if (DoCastingChecksOnTarget(true, spell_id, targets_target)) { target_id = targets_target->GetID(); @@ -216,11 +218,11 @@ bool Mob::CastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, //It appears that the Sanctuary effect is removed by a check on the client side (keep this however for redundancy) if (spellbonuses.Sanctuary && (spells[spell_id].target_type != ST_Self && GetTarget() != this) || IsDetrimentalSpell(spell_id)) { - BuffFadeByEffect(SE_Sanctuary); + BuffFadeByEffect(SpellEffect::Sanctuary); } if (spellbonuses.NegateIfCombat) { - BuffFadeByEffect(SE_NegateIfCombat); + BuffFadeByEffect(SpellEffect::NegateIfCombat); } if(GetTarget() && IsManaTapSpell(spell_id)) { @@ -256,22 +258,15 @@ bool Mob::CastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, Mob* spell_target = entity_list.GetMobID(target_id); std::vector args = { spell_target }; - int return_value = parse->EventMob( - EVENT_CAST_BEGIN, - this, - nullptr, - [&]() { - return fmt::format( - "{} {} {} {}", - spell_id, - GetID(), - GetCasterLevel(spell_id), - target_id - ); - }, - 0, - &args - ); + int return_value = parse->EventMob(EVENT_CAST_BEGIN, this, nullptr, [&]() { + return fmt::format( + "{} {} {} {}", + spell_id, + GetID(), + GetCasterLevel(spell_id), + target_id + ); + }, 0, &args); if (IsClient() && return_value != 0) { if (IsDiscipline(spell_id)) { @@ -473,7 +468,6 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, CastedSpellFinished(spell_id, target_id, slot, mana_cost, item_slot, resist_adjust); // return true; } - // ok we know it has a cast time so we can start the timer now spellend_timer.Start(cast_time); @@ -652,6 +646,11 @@ bool Mob::DoCastingChecksOnCaster(int32 spell_id, CastingSlot slot) { return true; } +struct SpellCheck { + std::function condition; // The condition to check + std::function action; // The action if the condition fails +}; + bool Mob::DoCastingChecksZoneRestrictions(bool check_on_casting, int32 spell_id) { /* @@ -659,114 +658,92 @@ bool Mob::DoCastingChecksZoneRestrictions(bool check_on_casting, int32 spell_id) - levitate zone restriction (client blocks) [cancel before begin cast message] - can not cast outdoor [cancels after spell finishes channeling] - If the spell is a casted spell, check on CastSpell and ignore on SpellFinished. - If the spell is a initiated from SpellFinished, then check at start of SpellFinished. + If the spell is a cast spell, check on CastSpell and ignore on SpellFinished. + If the spell is initiated from SpellFinished, then check at start of SpellFinished. */ - bool bypass_casting_restrictions = false; + bool bypass_casting_restrictions = !IsClient(); + glm::vec3 position = glm::vec3(GetPosition()); - if (!IsClient()) { - bypass_casting_restrictions = true; - } - - if (IsClient() && CastToClient()->GetGM()) { - bypass_casting_restrictions = true; - Message( - Chat::White, - fmt::format( - "Your GM flag allows you to bypass zone casting restrictions and cast {} in this zone.", - Saylink::Silent( - fmt::format( - "#castspell {}", - spell_id - ), - GetSpellName(spell_id) - ) - ).c_str() - ); - } - - /* - Zone ares that prevent blocked spells from being cast. - If on cast iniated then check any mob casting, if on spellfinished only check if is from client. - */ - if ((check_on_casting && !bypass_casting_restrictions) || (!check_on_casting && IsClient())) { - if (zone->IsSpellBlocked(spell_id, glm::vec3(GetPosition()))) { - if (IsClient()) { - if (!CastToClient()->GetGM()) { - const char *msg = zone->GetSpellBlockedMessage(spell_id, glm::vec3(GetPosition())); - if (msg) { - Message(Chat::Red, msg); - return false; - } - else { - Message(Chat::Red, "You can't cast this spell here."); - return false; - } - LogSpells("Spell casting canceled [{}] : can not cast in this zone location blocked spell.", spell_id); - } - else { - Message( - Chat::White, - fmt::format( - "Your GM flag allows you to bypass zone blocked spells and cast {} in this zone.", - Saylink::Silent( - fmt::format( - "#castspell {}", - spell_id - ), - GetSpellName(spell_id) - ) - ).c_str() - ); - LogSpells("GM Cast Blocked Spell: [{}] (ID [{}])", GetSpellName(spell_id), spell_id); - } - } - return false; + auto gm_bypass_message = [&](const std::string& restriction) { + if (CastToClient()->GetGM()) { + Message( + Chat::White, + fmt::format( + "Your GM flag allows you to bypass {} and cast {}.", + restriction, + Saylink::Silent( + fmt::format("#castspell {}", spell_id), + GetSpellName(spell_id) + ) + ).c_str() + ); + return true; } - } - /* - Zones where you can not use levitate spells. - */ - if (!bypass_casting_restrictions && !zone->CanLevitate() && IsEffectInSpell(spell_id, SE_Levitate)) { //check on spellfinished. - Message(Chat::Red, "You have entered an area where levitation effects do not function."); - LogSpells("Spell casting canceled [{}] : can not cast levitation in this zone.", spell_id); return false; - } - /* - Zones where you can not use detrimental spells. - */ - if (IsDetrimentalSpell(spell_id) && !zone->CanDoCombat()) { - Message(Chat::Red, "You cannot cast detrimental spells here."); - return false; - } - /* - Zones where you can not cast a spell that is for daytime or nighttime only - */ - if (spells[spell_id].time_of_day == SpellTimeRestrictions::Day && !zone->zone_time.IsDayTime()) { - MessageString(Chat::Red, CAST_DAYTIME); - return false; - } + }; - if (spells[spell_id].time_of_day == SpellTimeRestrictions::Night && !zone->zone_time.IsNightTime()) { - MessageString(Chat::Red, CAST_NIGHTTIME); - return false; - } - - if (check_on_casting) { - /* - Zones where you can not cast out door only spells. This is only checked when casting is completed. - */ - if (!bypass_casting_restrictions && spells[spell_id].zone_type == 1 && !zone->CanCastOutdoor()) { - if (IsClient()) { - if (!CastToClient()->GetGM()) { - MessageString(Chat::Red, CAST_OUTDOORS); - LogSpells("Spell casting canceled [{}] : can not cast outdoors.", spell_id); - return false; - } else { - Message(Chat::White, "Your GM flag allows you to cast outdoor spells when indoors."); - } + std::vector spell_checks = { + // Blocked spells + { + [&]() { return !bypass_casting_restrictions && zone->IsSpellBlocked(spell_id, position); }, + [&]() { + if (gm_bypass_message("zone blocked spells")) { return true; } + const char* msg = zone->GetSpellBlockedMessage(spell_id, position); + Message(Chat::Red, msg ? msg : "You can't cast this spell here."); + return false; } + }, + // Levitation restriction + { + [&]() { return !bypass_casting_restrictions && !zone->CanLevitate() && IsEffectInSpell(spell_id, SpellEffect::Levitate); }, + [&]() { + if (gm_bypass_message("zone levitation restrictions")) { return true; } + Message(Chat::Red, "You have entered an area where levitation effects do not function."); + return false; + } + }, + // Detrimental spells restriction + { + [&]() { return !bypass_casting_restrictions && IsDetrimentalSpell(spell_id) && !zone->CanDoCombat(); }, + [&]() { + if (gm_bypass_message("no combat zone restrictions")) { return true; } + Message(Chat::Red, "You cannot cast detrimental spells here."); + return false; + } + }, + // Daytime-only spells + { + [&]() { return !bypass_casting_restrictions && spells[spell_id].time_of_day == SpellTimeRestrictions::Day && !zone->zone_time.IsDayTime(); }, + [&]() { + if (gm_bypass_message("spell daytime restrictions")) { return true; } + MessageString(Chat::Red, CAST_DAYTIME); + return false; + } + }, + // Nighttime-only spells + { + [&]() { return !bypass_casting_restrictions && spells[spell_id].time_of_day == SpellTimeRestrictions::Night && !zone->zone_time.IsNightTime(); }, + [&]() { + if (gm_bypass_message("spell nighttime restrictions")) return true; + MessageString(Chat::Red, CAST_NIGHTTIME); + return false; + } + }, + // Outdoor-only spells + { + [&]() { return check_on_casting && !bypass_casting_restrictions && spells[spell_id].zone_type == 1 && !zone->CanCastOutdoor(); }, + [&]() { + if (gm_bypass_message("zone outdoor restrictions")) return true; + MessageString(Chat::Red, CAST_OUTDOORS); + return false; + } + } + }; + + for (const auto& check : spell_checks) { + if (check.condition() && !check.action()) { + return false; } } @@ -881,7 +858,7 @@ bool Mob::DoCastingChecksOnTarget(bool check_on_casting, int32 spell_id, Mob *sp /* Cannot cast shield target on self */ - if (this == spell_target && IsEffectInSpell(spell_id, SE_Shield_Target)) { + if (this == spell_target && IsEffectInSpell(spell_id, SpellEffect::Shield_Target)) { LogSpells("You cannot shield yourself"); Message(Chat::SpellFailure, "You cannot shield yourself."); return false; @@ -913,7 +890,7 @@ bool Mob::DoCastingChecksOnTarget(bool check_on_casting, int32 spell_id, Mob *sp /* Various charm related target restrictions */ - if (IsEffectInSpell(spell_id, SE_Charm) && !PassCharmTargetRestriction(spell_target)) { + if (IsEffectInSpell(spell_id, SpellEffect::Charm) && !PassCharmTargetRestriction(spell_target)) { LogSpells("Spell casting canceled [{}] : can not use charm on this target.", spell_id); return false; } @@ -1280,6 +1257,16 @@ void Mob::InterruptSpell(uint16 message, uint16 color, uint16 spellid) EQApplicationPacket *outapp = nullptr; uint16 message_other; bool bard_song_mode = false; //has the bard song gone to auto repeat mode + + if (IsBot()) { + auto bot = CastToBot(); + bot->SetCastedSpellType(UINT16_MAX); + + if (IsValidSpell(spellid) && bot->CheckSpellRecastTimer(spellid)) { + bot->ClearSpellRecastTimer(spellid); + } + } + if (!IsValidSpell(spellid)) { if (bardsong) { spellid = bardsong; @@ -1682,7 +1669,7 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo // handle the components for traditional casters else { - if (!RuleB(Character, PetsUseReagents) && (IsEffectInSpell(spell_id, SE_SummonPet) || IsEffectInSpell(spell_id, SE_NecPet)) || + if (!RuleB(Character, PetsUseReagents) && (IsEffectInSpell(spell_id, SpellEffect::SummonPet) || IsEffectInSpell(spell_id, SpellEffect::NecPet)) || (IsBardSong(spell_id) && (slot == CastingSlot::Item|| slot == CastingSlot::PotionBelt))) { //bypass reagent cost } @@ -1719,7 +1706,7 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo return; } } - else if (!RuleB(Character, PetsUseReagents) && (IsEffectInSpell(spell_id, SE_SummonPet) || IsEffectInSpell(spell_id, SE_NecPet))) { + else if (!RuleB(Character, PetsUseReagents) && (IsEffectInSpell(spell_id, SpellEffect::SummonPet) || IsEffectInSpell(spell_id, SpellEffect::NecPet))) { //bypass reagent cost } else if (!bard_song_mode) @@ -1804,22 +1791,15 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo std::vector args = { spell_target }; - parse->EventMob( - EVENT_CAST, - this, - nullptr, - [&]() { - return fmt::format( - "{} {} {} {}", - spell_id, - GetID(), - GetCasterLevel(spell_id), - target_id - ); - }, - 0, - &args - ); + parse->EventMob(EVENT_CAST, this, nullptr, [&]() { + return fmt::format( + "{} {} {} {}", + spell_id, + GetID(), + GetCasterLevel(spell_id), + target_id + ); + }, 0, &args); if(bard_song_mode) { @@ -1923,7 +1903,7 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce if(IsIllusionSpell(spell_id) && spell_target != nullptr // null ptr crash safeguard && !spell_target->IsNPC() // still self only if NPC targetted - && IsClient() + && IsOfClientBot() && (IsGrouped() // still self only if not grouped || IsRaidGrouped()) && (HasProjectIllusion())){ @@ -1941,7 +1921,14 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce // single target spells case ST_Self: { - spell_target = this; + bool bot_can_summon_corpse = IsBot() && + IsEffectInSpell(spell_id, SpellEffect::SummonCorpse) && + RuleB(Bots, AllowCommandedSummonCorpse); + + if (!bot_can_summon_corpse) { + spell_target = this; // Summon corpse spells are self-only; bots need a fallthrough + } + CastAction = SingleTarget; break; } @@ -2088,13 +2075,21 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce } case ST_Pet: { - spell_target = GetPet(); - if(!spell_target) - { - LogSpells("Spell [{}] canceled: invalid target (no pet)", spell_id); - MessageString(Chat::Red,NO_PET); - return false; // can't cast these unless we have a target + bool bot_casting_on_other_pet = IsBot() && + spell_target && + spell_target->GetOwner() != this && + RuleB(Bots, CanCastPetOnlyOnOthersPets); + + if (!bot_casting_on_other_pet) { + spell_target = GetPet(); } + + if (!spell_target) { + LogSpells("Spell [{}] canceled: invalid target (no pet)", spell_id); + MessageString(Chat::Red, NO_PET); + return false; // Can't cast these unless we have a target + } + CastAction = SingleTarget; break; } @@ -2176,19 +2171,26 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce case ST_Group: case ST_GroupNoPets: { - if(IsClient() && CastToClient()->TGB() && IsTGBCompatibleSpell(spell_id) && (slot != CastingSlot::Item || RuleB(Spells, AllowItemTGB))) { - if( (!target) || - (target->IsNPC() && !(target->GetOwner() && target->GetOwner()->IsClient())) || - (target->IsCorpse()) ) + bool tgb_enabled = (IsClient() && RuleB(Character, EnableTGB) && CastToClient()->TGB()) || + (IsBot() && RuleB(Bots, EnableBotTGB)); + bool tgb_compatible = IsTGBCompatibleSpell(spell_id); + bool item_tgb_allowed = (slot != CastingSlot::Item || RuleB(Spells, AllowItemTGB)); + + if (tgb_enabled && tgb_compatible && item_tgb_allowed) { + bool valid_target = spell_target && + !spell_target->IsCorpse() && + (!spell_target->IsNPC() || + (spell_target->GetOwner() && spell_target->IsPetOwnerOfClientBot())); + + if (!valid_target) { spell_target = this; - else - spell_target = target; + } } else { spell_target = this; } - if (spell_target && spell_target->IsPet() && spells[spell_id].target_type == ST_GroupNoPets){ - MessageString(Chat::Red,NO_CAST_ON_PET); + if (spell_target && spell_target->IsPet() && spells[spell_id].target_type == ST_GroupNoPets) { + MessageString(Chat::Red, NO_CAST_ON_PET); return false; } @@ -2253,14 +2255,15 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce } else if(IsBot()) { - if(IsGrouped()) - { - group_id_caster = GetGroup()->GetID(); - } - else if(IsRaidGrouped()) - { - if(GetOwner()) - group_id_caster = (GetRaid()->GetGroup(GetOwner()->CastToClient()) == 0xFFFF) ? 0 : (GetRaid()->GetGroup(GetOwner()->CastToClient()) + 1); + if (IsGrouped()) { + if (auto group = GetGroup()) { + group_id_caster = group->GetID(); + } + } else if (IsRaidGrouped()) { + if (auto raid = GetRaid()) { + uint32 group_id = raid->GetGroup(GetName()); + group_id_caster = (group_id == 0xFFFFFFFF) ? 0 : (group_id + 1); + } } } @@ -2423,8 +2426,9 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, in } } - if ((RuleB(Bots, CazicTouchBotsOwner) && spell_target && spell_target->IsBot()) && spell_id == (SPELL_CAZIC_TOUCH || spell_id == SPELL_TOUCH_OF_VINITRAS)) { + if ((RuleB(Bots, CazicTouchBotsOwner) && spell_target && spell_target->IsBot()) && (spell_id == SPELL_CAZIC_TOUCH || spell_id == SPELL_TOUCH_OF_VINITRAS)) { auto bot_owner = spell_target->GetOwner(); + if (bot_owner) { spell_target = bot_owner; } @@ -2510,11 +2514,16 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, in //range check our target, if we have one and it is not us float range = spells[spell_id].range + GetRangeDistTargetSizeMod(spell_target); - if(IsClient() && CastToClient()->TGB() && IsTGBCompatibleSpell(spell_id) && IsGroupSpell(spell_id)) + bool can_use_tgb = (IsClient() && CastToClient()->TGB()) || (IsBot() && RuleB(Bots, EnableBotTGB)); + bool is_tgb_compatible = IsTGBCompatibleSpell(spell_id); + bool is_group_spell = IsGroupSpell(spell_id); + + if (can_use_tgb && is_tgb_compatible && is_group_spell) { range = spells[spell_id].aoe_range; + } range = GetActSpellRange(spell_id, range); - if(IsClient() && IsIllusionSpell(spell_id) && (HasProjectIllusion())){ + if(IsOfClientBot() && IsIllusionSpell(spell_id) && (HasProjectIllusion())){ range = 100; } @@ -2599,7 +2608,7 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, in } if(IsIllusionSpell(spell_id) - && IsClient() + && IsOfClientBot() && (HasProjectIllusion())){ LogAA("Effect Project Illusion for [{}] on spell id: [{}] was ON", GetName(), spell_id); SetProjectIllusion(false); @@ -2660,7 +2669,7 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, in // it can affect up to 7 people if the targeted group is not our own // Allow pets who cast group spells to affect the group. - if (spell_target->IsPetOwnerClient() && IsPetOwnerClient()) { + if (spell_target->IsPetOwnerOfClientBot() && IsPetOwnerOfClientBot()) { Mob* owner = spell_target->GetOwner(); if (owner) { @@ -2676,8 +2685,12 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, in SpellOnTarget(spell_id, this); } } - } else if (spell_target->IsRaidGrouped() && spell_target->IsClient()) { + } else if (spell_target->IsRaidGrouped() && spell_target->IsOfClientBot()) { Raid *target_raid = entity_list.GetRaidByClient(spell_target->CastToClient()); + if (IsBot()) { + target_raid = entity_list.GetRaidByBot(spell_target->CastToBot()); + } + uint32 gid = 0xFFFFFFFF; if (target_raid) { gid = target_raid->GetGroup(spell_target->GetName()); @@ -2835,6 +2848,13 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, in } } } + + if (IsBot() && !isproc && !IsFromTriggeredSpell(slot, inventory_slot) && IsValidSpell(spell_id)) { + if (spells[spell_id].recast_time > 1000 && !spells[spell_id].is_discipline) { + CastToBot()->SetSpellRecastTimer(spell_id); + } + } + /* Set Recast Timer on item clicks, including augmenets. */ @@ -2858,7 +2878,7 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, in ApplyHealthTransferDamage(this, target, spell_id); //This needs to be here for bind sight to update correctly on client. - if (IsClient() && IsEffectInSpell(spell_id, SE_BindSight)) { + if (IsClient() && IsEffectInSpell(spell_id, SpellEffect::BindSight)) { for (int i = 0; i < GetMaxTotalSlots(); i++) { if (buffs[i].spellid == spell_id) { CastToClient()->SendBuffNumHitPacket(buffs[i], i);//its hack, it works. @@ -2891,7 +2911,7 @@ bool Mob::ApplyBardPulse(int32 spell_id, Mob *spell_target, CastingSlot slot) { Bard song charm that have no mana will continue to try and pulse on target, but will only reapply when charm fades. Live does not spam client with do not take hold messages. Checking here avoids that from happening. Only try to reapply if charm fades. */ - if (spell_target->IsCharmed() && spells[spell_id].mana == 0 && spell_target->GetOwner() == this && IsEffectInSpell(spell_id, SE_Charm)) { + if (spell_target->IsCharmed() && spells[spell_id].mana == 0 && spell_target->GetOwner() == this && IsEffectInSpell(spell_id, SpellEffect::Charm)) { if (IsClient()) { CastToClient()->CheckSongSkillIncrease(spell_id); } @@ -2972,7 +2992,7 @@ int Mob::CalcBuffDuration(Mob *caster, Mob *target, uint16 spell_id, int32 caste ) && spell_id != SPELL_MINOR_ILLUSION && spell_id != SPELL_ILLUSION_TREE && - IsEffectInSpell(spell_id, SE_Illusion) + IsEffectInSpell(spell_id, SpellEffect::Illusion) ) { res = 10000; // ~16h override } @@ -3084,7 +3104,7 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, return 0; } - if (spellbonuses.CompleteHealBuffBlocker && IsEffectInSpell(spellid2, SE_CompleteHeal)) { + if (spellbonuses.CompleteHealBuffBlocker && IsEffectInSpell(spellid2, SpellEffect::CompleteHeal)) { Message(0, "You must wait before you can be affected by this spell again."); return -1; } @@ -3096,9 +3116,9 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, return -1; } - if (!IsStackableDOT(spellid1) && !IsEffectInSpell(spellid1, SE_ManaBurn)) { // mana burn spells we need to use the stacking command blocks live actually checks those first, we should probably rework to that too + if (!IsStackableDOT(spellid1) && !IsEffectInSpell(spellid1, SpellEffect::ManaBurn)) { // mana burn spells we need to use the stacking command blocks live actually checks those first, we should probably rework to that too if (caster_level1 > caster_level2) { // cur buff higher level than new - if (IsEffectInSpell(spellid1, SE_ImprovedTaunt)) { + if (IsEffectInSpell(spellid1, SpellEffect::ImprovedTaunt)) { LogSpells("SE_ImprovedTaunt level exception, overwriting"); return 1; } else { @@ -3115,6 +3135,17 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, } } + const std::string& always_stack_spells = RuleS(Spells, AlwaysStackSpells); + if (spellid1 != spellid2 && !always_stack_spells.empty()) { + const auto& v = Strings::Split(always_stack_spells, ","); + if (Strings::Contains(v, std::to_string(spellid1))) { + return 0; + } + if (Strings::Contains(v, std::to_string(spellid2))) { + return 0; + } + } + /* One of these is a bard song and one isn't and they're both beneficial so they should stack. */ @@ -3131,12 +3162,12 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, if (spellid1 != spellid2) { for (i = 0; i < EFFECT_COUNT; i++) { // we don't want this optimization for mana burns - if (sp1.effect_id[i] != sp2.effect_id[i] || sp1.effect_id[i] == SE_ManaBurn) { + if (sp1.effect_id[i] != sp2.effect_id[i] || sp1.effect_id[i] == SpellEffect::ManaBurn) { effect_match = false; break; } } - } else if (IsEffectInSpell(spellid1, SE_ManaBurn)) { + } else if (IsEffectInSpell(spellid1, SpellEffect::ManaBurn)) { LogSpells("We have a Mana Burn spell that is the same, they won't stack"); return -1; } @@ -3150,7 +3181,7 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, effect2 = sp2.effect_id[i]; if (spellbonuses.Screech == 1) { - if (effect2 == SE_Screech && sp2.base_value[i] == -1) { + if (effect2 == SpellEffect::Screech && sp2.base_value[i] == -1) { MessageString(Chat::SpellFailure, SCREECH_BUFF_BLOCK, sp2.name); return -1; } @@ -3163,32 +3194,32 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, is not fully removed at the time of the trigger */ if (spellbonuses.AStacker[SBIndex::BUFFSTACKER_EXISTS]) { - if ((effect2 == SE_AStacker) && (sp2.effect_id[i] <= spellbonuses.AStacker[SBIndex::BUFFSTACKER_VALUE])) + if ((effect2 == SpellEffect::AStacker) && (sp2.effect_id[i] <= spellbonuses.AStacker[SBIndex::BUFFSTACKER_VALUE])) return -1; } if (spellbonuses.BStacker[SBIndex::BUFFSTACKER_EXISTS]) { - if ((effect2 == SE_BStacker) && (sp2.effect_id[i] <= spellbonuses.BStacker[SBIndex::BUFFSTACKER_VALUE])) + if ((effect2 == SpellEffect::BStacker) && (sp2.effect_id[i] <= spellbonuses.BStacker[SBIndex::BUFFSTACKER_VALUE])) return -1; - if ((effect2 == SE_AStacker) && (!IsCastOnFadeDurationSpell(spellid1) && buffs[buffslot].ticsremaining != 1 && IsEffectInSpell(spellid1, SE_BStacker))) + if ((effect2 == SpellEffect::AStacker) && (!IsCastOnFadeDurationSpell(spellid1) && buffs[buffslot].ticsremaining != 1 && IsEffectInSpell(spellid1, SpellEffect::BStacker))) return -1; } if (spellbonuses.CStacker[SBIndex::BUFFSTACKER_EXISTS]) { - if ((effect2 == SE_CStacker) && (sp2.effect_id[i] <= spellbonuses.CStacker[SBIndex::BUFFSTACKER_VALUE])) + if ((effect2 == SpellEffect::CStacker) && (sp2.effect_id[i] <= spellbonuses.CStacker[SBIndex::BUFFSTACKER_VALUE])) return -1; - if ((effect2 == SE_BStacker) && (!IsCastOnFadeDurationSpell(spellid1) && buffs[buffslot].ticsremaining != 1 && IsEffectInSpell(spellid1, SE_CStacker))) + if ((effect2 == SpellEffect::BStacker) && (!IsCastOnFadeDurationSpell(spellid1) && buffs[buffslot].ticsremaining != 1 && IsEffectInSpell(spellid1, SpellEffect::CStacker))) return -1; } if (spellbonuses.DStacker[SBIndex::BUFFSTACKER_EXISTS]) { - if ((effect2 == SE_DStacker) && (sp2.effect_id[i] <= spellbonuses.DStacker[SBIndex::BUFFSTACKER_VALUE])) + if ((effect2 == SpellEffect::DStacker) && (sp2.effect_id[i] <= spellbonuses.DStacker[SBIndex::BUFFSTACKER_VALUE])) return -1; - if ((effect2 == SE_CStacker) && (!IsCastOnFadeDurationSpell(spellid1) && buffs[buffslot].ticsremaining != 1 && IsEffectInSpell(spellid1, SE_DStacker))) + if ((effect2 == SpellEffect::CStacker) && (!IsCastOnFadeDurationSpell(spellid1) && buffs[buffslot].ticsremaining != 1 && IsEffectInSpell(spellid1, SpellEffect::DStacker))) return -1; } - if(effect2 == SE_StackingCommand_Overwrite) + if(effect2 == SpellEffect::StackingCommand_Overwrite) { overwrite_effect = sp2.base_value[i]; overwrite_slot = sp2.formula[i] - 201; //they use base 1 for slots, we use base 0 @@ -3216,7 +3247,7 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, sp2.name, spellid2, overwrite_effect, overwrite_slot, overwrite_below_value); } - } else if (effect1 == SE_StackingCommand_Block) + } else if (effect1 == SpellEffect::StackingCommand_Block) { blocked_effect = sp1.base_value[i]; blocked_slot = sp1.formula[i] - 201; @@ -3293,7 +3324,7 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, // negative AC affects are skipped. Ex. Sun's Corona and Glacier Breath should stack // There may be more SPAs we need to add here .... // The client does just check base rather than calculating the affect change value. - if ((effect1 == SE_ArmorClass || effect1 == SE_ACv2) && sp2.base_value[i] < 0) + if ((effect1 == SpellEffect::ArmorClass || effect1 == SpellEffect::ACv2) && sp2.base_value[i] < 0) continue; /* @@ -3301,13 +3332,13 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, If Caster1 isn't the same as Caster2 and the effect is a DoT then ignore it. */ if(IsNPC() && caster1 && caster2 && caster1 != caster2) { - if(effect1 == SE_CurrentHP && sp1_detrimental && sp2_detrimental) { + if(effect1 == SpellEffect::CurrentHP && sp1_detrimental && sp2_detrimental) { LogSpells("Both casters exist and are not the same, the effect is a detrimental dot, moving on"); continue; } } - if(effect1 == SE_CompleteHeal){ //SE_CompleteHeal never stacks or overwrites ever, always block. + if(effect1 == SpellEffect::CompleteHeal){ //SE_CompleteHeal never stacks or overwrites ever, always block. LogSpells("Blocking spell because complete heal never stacks or overwries"); return (-1); } @@ -3316,7 +3347,7 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, If the spells aren't the same and the effect is a dot we can go ahead and stack it */ - if(effect1 == SE_CurrentHP && spellid1 != spellid2 && sp1_detrimental && sp2_detrimental) { + if(effect1 == SpellEffect::CurrentHP && spellid1 != spellid2 && sp1_detrimental && sp2_detrimental) { LogSpells("The spells are not the same and it is a detrimental dot, passing"); continue; } @@ -3325,7 +3356,7 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, sp2_value = CalcSpellEffectValue(spellid2, i, caster_level2); // Spells like SoW won't stack if a snare effect is already in place. - if (effect2 == SE_MovementSpeed && effect1 == SE_MovementSpeed) { + if (effect2 == SpellEffect::MovementSpeed && effect1 == SpellEffect::MovementSpeed) { if (sp1_value < 0 && sp2_value > 0) { return -1; } else if (sp2_value < 0 && sp1_value > 0) { @@ -3335,7 +3366,7 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, // DoTs won't overwrite regeneration but will block regeneration spells. if (spells[spellid1].buff_duration > 0 && spells[spellid2].buff_duration > 0 && - effect1 == SE_CurrentHP && effect2 == SE_CurrentHP) { + effect1 == SpellEffect::CurrentHP && effect2 == SpellEffect::CurrentHP) { if (!sp1_detrimental && sp2_detrimental) { continue; } else if (sp1_detrimental && !sp2_detrimental) { @@ -3347,8 +3378,8 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, // have a value that's a percentage for instance if ( - effect1 == SE_AttackSpeed || - effect1 == SE_AttackSpeed2 + effect1 == SpellEffect::AttackSpeed || + effect1 == SpellEffect::AttackSpeed2 ) { sp1_value -= 100; @@ -3426,12 +3457,17 @@ bool Mob::CheckSpellLevelRestriction(Mob *caster, uint16 spell_id) bool can_cast = true; // NON GM clients might be restricted by rule setting - if (caster->IsClient()) { + if (caster->IsOfClientBot()) { if (IsClient()) { // Only restrict client on client for this rule if (RuleB(Spells, BuffLevelRestrictions)) { check_for_restrictions = true; } } + else if (IsBot()) { + if (RuleB(Bots, BotBuffLevelRestrictions)) { + check_for_restrictions = true; + } + } } // NPCS might be restricted by rule setting else if (RuleB(Spells, NPCBuffLevelRestrictions)) { @@ -3578,6 +3614,19 @@ int Mob::AddBuff(Mob *caster, uint16 spell_id, int duration, int32 level_overrid ); } + if (caster->IsBot() && RuleB(Bots, BotsUseLiveBlockedMessage) && caster->GetClass() != Class::Bard) { + caster->GetOwner()->Message( + Chat::SpellFailure, + fmt::format( + "{}'s {} did not take hold on {}. (Blocked by {}.)", + caster->GetCleanName(), + spells[spell_id].name, + GetName(), + spells[curbuf.spellid].name + ).c_str() + ); + } + std::function f = [&]() { return fmt::format( "{} {}", @@ -3767,35 +3816,56 @@ int Mob::CanBuffStack(uint16 spellid, uint8 caster_level, bool iFailIfOverwrite) if (!IsValidSpell(curbuf.spellid)) { // if we haven't found a free slot, this is the first one so save it - if(firstfree == -2) + if (firstfree == -2) { firstfree = i; + } + continue; } - if(curbuf.spellid == spellid) - return(-1); //do not recast a buff we already have on, we recast fast enough that we dont need to refresh our buffs + if ( + IsBot() && + GetClass() == Class::Bard && + curbuf.spellid == spellid && + curbuf.ticsremaining == 0 && + curbuf.casterid == GetID() + ) { + LogAI("Bard check for song, spell [{}] has [{}] ticks remaining.", spellid, curbuf.ticsremaining); + firstfree = i; + } + else { + if (curbuf.spellid == spellid) { + return(-1); //do not recast a buff we already have on, we recast fast enough that we dont need to refresh our buffs + } + } // there's a buff in this slot ret = CheckStackConflict(curbuf.spellid, curbuf.casterlevel, spellid, caster_level, nullptr, nullptr, i); - if(ret == 1) { + + if (ret == 1) { // should overwrite current slot - if(iFailIfOverwrite) { + if (iFailIfOverwrite) { LogAIDetail("Buff [{}] would overwrite [{}] in slot [{}], reporting stack failure", spellid, curbuf.spellid, i); return(-1); } - if(firstfree == -2) - firstfree = i; - } - if(ret == -1) { + if (firstfree == -2) { + firstfree = i; + } + } + + if(ret == -1) { LogAIDetail("Buff [{}] would conflict with [{}] in slot [{}], reporting stack failure", spellid, curbuf.spellid, i); return -1; // stop the spell, can't stack it } + if (ret == 2) { //ResurrectionEffectBlock handling to move potential overwrites to a new buff slock while keeping Res Sickness LogAIDetail("Adding buff [{}] will overwrite spell [{}] in slot [{}] with caster level [{}], but ResurrectionEffectBlock is set to 2. Attempting to move [{}] to an empty buff slot.", spellid, curbuf.spellid, i, curbuf.casterlevel, spellid); + for (int x = 0; x < buff_count; x++) { const Buffs_Struct& curbuf = buffs[x]; + if (IsValidSpell(curbuf.spellid)) { continue; } @@ -3859,7 +3929,7 @@ bool Mob::SpellOnTarget( IsDetrimentalSpell(spell_id) && !IsAttackAllowed(spelltar, true) && !IsResurrectionEffects(spell_id) && - !IsEffectInSpell(spell_id, SE_BindSight) + !IsEffectInSpell(spell_id, SpellEffect::BindSight) ) { if (!IsClient() || !CastToClient()->GetGM()) { MessageString(Chat::SpellFailure, SPELL_NO_HOLD); @@ -3927,7 +3997,7 @@ bool Mob::SpellOnTarget( // select target uint16 target_id = 0; - if (IsEffectInSpell(spell_id, SE_BindSight)) { + if (IsEffectInSpell(spell_id, SpellEffect::BindSight)) { action->target = GetID(); target_id = GetID(); } else { @@ -3965,10 +4035,7 @@ bool Mob::SpellOnTarget( std::vector args = { spelltar }; - parse->EventMob( - EVENT_CAST_ON, - spelltar, - this, + parse->EventMob(EVENT_CAST_ON, spelltar, this, [&]() { return fmt::format( "{} {} {} {}", @@ -3978,8 +4045,7 @@ bool Mob::SpellOnTarget( target_id ); }, - 0, - &args + 0, &args ); if (!DoCastingChecksOnTarget(false, spell_id, spelltar)) { @@ -3987,8 +4053,22 @@ bool Mob::SpellOnTarget( return false; } + bool client_blocked_buffs = + RuleB(Spells, EnableBlockedBuffs) && + ( + spelltar->IsClient() || + (spelltar->IsPet() && spelltar->IsPetOwnerClient()) + ); + + bool bot_blocked_buffs = + RuleB(Bots, AllowBotBlockedBuffs) && + ( + spelltar->IsBot() || + (spelltar->IsPet() && spelltar->IsPetOwnerBot()) + ); + // now check if the spell is allowed to land - if (RuleB(Spells, EnableBlockedBuffs)) { + if (client_blocked_buffs || bot_blocked_buffs) { // We return true here since the caster's client should act like normal if (spelltar->IsBlockedBuff(spell_id)) { LogSpells( @@ -4071,7 +4151,7 @@ bool Mob::SpellOnTarget( // Not sure if all 3 should be stacking //This is not live like behavior (~Kayen confirmed 2/2/22) if (!RuleB(Spells, AllowDoubleInvis) && !IsActiveBardSong(spell_id)) { - if (IsEffectInSpell(spell_id, SE_Invisibility)) { + if (IsEffectInSpell(spell_id, SpellEffect::Invisibility)) { if (spelltar->invisible) { spelltar->MessageString(Chat::SpellFailure, ALREADY_INVIS, GetCleanName()); safe_delete(action_packet); @@ -4079,7 +4159,7 @@ bool Mob::SpellOnTarget( } } - if (IsEffectInSpell(spell_id, SE_InvisVsUndead)) { + if (IsEffectInSpell(spell_id, SpellEffect::InvisVsUndead)) { if (spelltar->invisible_undead) { spelltar->MessageString(Chat::SpellFailure, ALREADY_INVIS, GetCleanName()); safe_delete(action_packet); @@ -4087,7 +4167,7 @@ bool Mob::SpellOnTarget( } } - if (IsEffectInSpell(spell_id, SE_InvisVsAnimals)) { + if (IsEffectInSpell(spell_id, SpellEffect::InvisVsAnimals)) { if (spelltar->invisible_animals) { spelltar->MessageString(Chat::SpellFailure, ALREADY_INVIS, GetCleanName()); safe_delete(action_packet); @@ -4196,7 +4276,7 @@ bool Mob::SpellOnTarget( } else if ( !IsAttackAllowed(spelltar, true) && !IsResurrectionEffects(spell_id) && - !IsEffectInSpell(spell_id, SE_BindSight) + !IsEffectInSpell(spell_id, SpellEffect::BindSight) ) { // Detrimental spells - PVP check LogSpells( "Detrimental spell [{}] can't take hold [{}] -> [{}]", @@ -4254,7 +4334,7 @@ bool Mob::SpellOnTarget( int buff_count = GetMaxTotalSlots(); int focus = 0; for (int b = 0; b < buff_count; b++) { - if (IsEffectInSpell(buffs[b].spellid, SE_BlockNextSpellFocus)) { + if (IsEffectInSpell(buffs[b].spellid, SpellEffect::BlockNextSpellFocus)) { focus = CalcFocusEffect(focusBlockNextSpell, buffs[b].spellid, spell_id); if (focus) { CheckNumHitsRemaining(NumHit::MatchingSpells, b); @@ -4424,6 +4504,18 @@ bool Mob::SpellOnTarget( } else { MessageString(Chat::SpellFailure, TARGET_RESISTED, spells[spell_id].name); spelltar->MessageString(Chat::SpellFailure, YOU_RESIST, spells[spell_id].name); + + if (IsBot() && RuleB(Bots, ShowResistMessagesToOwner)) { + CastToBot()->GetBotOwner()->Message + (Chat::SpellFailure, + fmt::format( + "{} resisted {}'s spell: {}.", + spelltar->GetCleanName(), + GetCleanName(), + spells[spell_id].name + ).c_str() + ); + } } if (spelltar->IsAIControlled()) { @@ -4537,7 +4629,7 @@ bool Mob::SpellOnTarget( return false; } - //Check SE_Fc_Cast_Spell_On_Land SPA 481 on target, if hit by this spell and Conditions are Met then target will cast the specified spell. + //Check SpellEffect::Fc_Cast_Spell_On_Land SPA 481 on target, if hit by this spell and Conditions are Met then target will cast the specified spell. if (spelltar) { spelltar->CastSpellOnLand(this, spell_id); } @@ -4585,11 +4677,11 @@ bool Mob::SpellOnTarget( } } - if (spelltar->IsClient() && IsEffectInSpell(spell_id, SE_ShadowStep)) { + if (spelltar->IsClient() && IsEffectInSpell(spell_id, SpellEffect::ShadowStep)) { spelltar->CastToClient()->cheat_manager.SetExemptStatus(ShadowStep, true); } - if (!IsEffectInSpell(spell_id, SE_BindAffinity)) { + if (!IsEffectInSpell(spell_id, SpellEffect::BindAffinity)) { if (spelltar != this && spelltar->IsClient()) {// send to target spelltar->CastToClient()->QueuePacket(action_packet); } @@ -4599,8 +4691,8 @@ bool Mob::SpellOnTarget( } } - message_packet = new EQApplicationPacket(OP_Damage, sizeof(CombatDamage_Struct)); - CombatDamage_Struct *cd = (CombatDamage_Struct *)message_packet->pBuffer; + static EQApplicationPacket p(OP_Damage, sizeof(CombatDamage_Struct)); + auto cd = (CombatDamage_Struct *) p.pBuffer; cd->target = action->target; cd->source = action->source; cd->type = action->type; @@ -4612,13 +4704,13 @@ bool Mob::SpellOnTarget( if ( !IsLifetapSpell(spell_id) && - !IsEffectInSpell(spell_id, SE_BindAffinity) && + !IsEffectInSpell(spell_id, SpellEffect::BindAffinity) && !IsAENukeSpell(spell_id) && !IsDamageSpell(spell_id) ) { entity_list.QueueCloseClients( spelltar, /* Sender */ - message_packet, /* Packet */ + &p, /* Packet */ false, /* Ignore Sender */ RuleI(Range, SpellMessages), 0, /* Skip this mob */ @@ -4628,7 +4720,6 @@ bool Mob::SpellOnTarget( } safe_delete(action_packet); - safe_delete(message_packet); /* Bug: When an HP buff with a heal effect is applied for first time, the heal portion of the effect heals the client and @@ -4636,12 +4727,20 @@ bool Mob::SpellOnTarget( However due to server thinking your healed, you are unable to correct it by healing. Solution: You need to resend the HP update after buff completed and action packet resent. */ - if ((IsEffectInSpell(spell_id, SE_TotalHP) || IsEffectInSpell(spell_id, SE_MaxHPChange)) && (IsEffectInSpell(spell_id, SE_CurrentHPOnce) || IsEffectInSpell(spell_id, SE_CurrentHP))) { + if ((IsEffectInSpell(spell_id, SpellEffect::TotalHP) || IsEffectInSpell(spell_id, SpellEffect::MaxHPChange)) && (IsEffectInSpell(spell_id, SpellEffect::CurrentHPOnce) || IsEffectInSpell(spell_id, SpellEffect::CurrentHP))) { SendHPUpdate(true); } LogSpells("Cast of [{}] by [{}] on [{}] complete successfully", spell_id, GetName(), spelltar->GetName()); + if (IsBot() && (CastToBot()->GetCastedSpellType() != UINT16_MAX)) { + if (!CastToBot()->IsCommandedSpell()) { + CastToBot()->SetBotSpellRecastTimer(CastToBot()->GetCastedSpellType(), spelltar); + } + + CastToBot()->SetCastedSpellType(UINT16_MAX); + } + return true; } @@ -4997,7 +5096,7 @@ bool Mob::IsImmuneToSpell(uint16 spell_id, Mob *caster) } // check max level for spell - effect_index = GetSpellEffectIndex(spell_id, SE_Mez); + effect_index = GetSpellEffectIndex(spell_id, SpellEffect::Mez); assert(effect_index >= 0); // NPCs get to ignore the max level if((GetLevel() > spells[spell_id].max_value[effect_index]) && @@ -5011,7 +5110,7 @@ bool Mob::IsImmuneToSpell(uint16 spell_id, Mob *caster) } // slow and haste spells - if(GetSpecialAbility(SpecialAbility::SlowImmunity) && IsEffectInSpell(spell_id, SE_AttackSpeed)) + if(GetSpecialAbility(SpecialAbility::SlowImmunity) && IsEffectInSpell(spell_id, SpellEffect::AttackSpeed)) { LogSpells("We are immune to Slow spells"); caster->MessageString(Chat::Red, IMMUNE_ATKSPEED); @@ -5025,9 +5124,9 @@ bool Mob::IsImmuneToSpell(uint16 spell_id, Mob *caster) } // client vs client fear - if(IsEffectInSpell(spell_id, SE_Fear)) + if(IsEffectInSpell(spell_id, SpellEffect::Fear)) { - effect_index = GetSpellEffectIndex(spell_id, SE_Fear); + effect_index = GetSpellEffectIndex(spell_id, SpellEffect::Fear); if(GetSpecialAbility(SpecialAbility::FearImmunity)) { LogSpells("We are immune to Fear spells"); caster->MessageString(Chat::Red, IMMUNE_FEAR); // need to verify message type, not in MQ2Cast for easy look up @@ -5091,7 +5190,7 @@ bool Mob::IsImmuneToSpell(uint16 spell_id, Mob *caster) if(!caster->IsNPC()) { // check level limit of charm spell - effect_index = GetSpellEffectIndex(spell_id, SE_Charm); + effect_index = GetSpellEffectIndex(spell_id, SpellEffect::Charm); assert(effect_index >= 0); if(GetLevel() > spells[spell_id].max_value[effect_index] && spells[spell_id].max_value[effect_index] != 0) { @@ -5112,8 +5211,8 @@ bool Mob::IsImmuneToSpell(uint16 spell_id, Mob *caster) if ( - IsEffectInSpell(spell_id, SE_Root) || - IsEffectInSpell(spell_id, SE_MovementSpeed) + IsEffectInSpell(spell_id, SpellEffect::Root) || + IsEffectInSpell(spell_id, SpellEffect::MovementSpeed) ) { if(GetSpecialAbility(SpecialAbility::SnareImmunity)) { @@ -5260,16 +5359,17 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use if (!CharmTick) { //Check for Spell Effect specific resistance chances (ie AA Mental Fortitude) - int se_resist_bonuses = GetSpellEffectResistChance(spell_id); - if (se_resist_bonuses && zone->random.Roll(se_resist_bonuses)) { + if (resist_type != RESIST_NONE && TrySpellEffectResist(spell_id)) { return 0; } // Check for Chance to Resist Spell bonuses (ie Sanctification Discipline) - int resist_bonuses = CalcResistChanceBonus(); - if (resist_bonuses && zone->random.Roll(resist_bonuses) && !IsResurrectionSicknessSpell(spell_id)) { - LogSpells("Resisted spell in sanctification, had [{}] chance to resist", resist_bonuses); - return 0; + if (!spells[spell_id].no_resist && resist_type != RESIST_NONE) { + int resist_bonuses = CalcResistChanceBonus(); + if (resist_bonuses && zone->random.Roll(resist_bonuses) && !IsResurrectionSicknessSpell(spell_id)) { + LogSpells("Resisted spell in sanctification, had [{}] chance to resist", resist_bonuses); + return 0; + } } } @@ -5289,7 +5389,7 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use // JULY 24, 2002 changes int level = GetLevel(); - if (RuleB(Spells,July242002PetResists) && IsPetOwnerClient() && caster->IsNPC() && !caster->IsPetOwnerClient()) { + if (RuleB(Spells,July242002PetResists) && IsPetOwnerOfClientBot() && caster->IsNPC() && !caster->IsPetOwnerOfClientBot()) { auto owner = GetOwner(); if (owner != nullptr) { target_resist = std::max(target_resist, owner->GetResist(resist_type)); @@ -5568,18 +5668,12 @@ int16 Mob::CalcResistChanceBonus() int16 Mob::CalcFearResistChance() { - int resistchance = spellbonuses.ResistFearChance + itembonuses.ResistFearChance; - if (IsOfClientBot()) { - resistchance += aabonuses.ResistFearChance; - if (aabonuses.Fearless == true) { - resistchance = 100; - } - } - if (spellbonuses.Fearless == true || itembonuses.Fearless == true) { - resistchance = 100; + int resist_chance = spellbonuses.ResistFearChance + itembonuses.ResistFearChance + aabonuses.ResistFearChance; + if (spellbonuses.Fearless || itembonuses.Fearless || aabonuses.Fearless) { + resist_chance = 100; } - return resistchance; + return resist_chance; } float Mob::GetAOERange(uint16 spell_id) @@ -6929,7 +7023,7 @@ void Mob::BeamDirectional(uint16 spell_id, int16 resist_adjust) float b = (GetY() * dX - dY * GetX()) / (dX - GetX()); while (iter != targets_in_range.end()) { - if (!(*iter) || (beneficial_targets && ((*iter)->IsNPC() && !(*iter)->IsPetOwnerClient())) || + if (!(*iter) || (beneficial_targets && ((*iter)->IsNPC() && !(*iter)->IsPetOwnerOfClientBot())) || (*iter)->BehindMob(this, (*iter)->GetX(), (*iter)->GetY())) { ++iter; continue; @@ -7013,7 +7107,7 @@ void Mob::ConeDirectional(uint16 spell_id, int16 resist_adjust) auto iter = targets_in_range.begin(); while (iter != targets_in_range.end()) { - if (!(*iter) || (beneficial_targets && ((*iter)->IsNPC() && !(*iter)->IsPetOwnerClient()))) { + if (!(*iter) || (beneficial_targets && ((*iter)->IsNPC() && !(*iter)->IsPetOwnerOfClientBot()))) { ++iter; continue; } @@ -7413,15 +7507,17 @@ void Mob::SetHP(int64 hp) void Mob::DrawDebugCoordinateNode(std::string node_name, const glm::vec4 vec) { - NPC *node = nullptr; + NPC *node = nullptr; + for (const auto &n: entity_list.GetNPCList()) { - if (n.second->GetCleanName() == node_name) { + if (n.second->GetEntityVariable("node_parent_id") == std::to_string(GetID())) { node = n.second; break; } } if (!node) { node = NPC::SpawnNodeNPC(node_name, "", GetPosition()); + node->SetEntityVariable("node_parent_id", std::to_string(GetID())); } } diff --git a/zone/string_ids.h b/zone/string_ids.h index b778170b6..5e929db02 100644 --- a/zone/string_ids.h +++ b/zone/string_ids.h @@ -286,6 +286,7 @@ #define BEGINS_TO_SHINE 1238 //Your %1 begins to shine. #define CANT_FIND_PLAYER 1276 //I can't find a player named %1! #define SURNAME_REJECTED 1374 //Your new surname was rejected. Please try a different name. +#define ALREADY_SOLD 1376 //The item you were interested in has already been sold. #define GUILD_DISBANDED 1377 //Your guild has been disbanded! You are no longer a member of any guild. #define DUEL_DECLINE 1383 //%1 has declined your challenge to duel to the death. #define DUEL_ACCEPTED 1384 //%1 has already accepted a duel with someone else. @@ -308,6 +309,7 @@ #define PLAYER_CHARMED 1461 //You lose control of yourself! #define TRADER_BUSY 1468 //That Trader is currently with a customer. Please wait until their transaction is finished. #define SENSE_CORPSE_DIRECTION 1563 //You sense a corpse in this direction. +#define DUPE_LORE_MERCHANT 1573 //%1 tells you, 'You already have the lore item, %2, on your person, on your shroud, in the bank, in a real estate, or as an augment in another item. You cannot have more than one of a particular lore item at a time.' #define QUEUED_TELL 2458 //[queued] #define QUEUE_TELL_FULL 2459 //[zoing and queue is full] #define TRADER_BUSY_TWO 3192 //Sorry, that action cannot be performed while trading. @@ -326,42 +328,42 @@ #define TRADESKILL_LEARN_RECIPE 3457 //You have learned the recipe %1! #define TASK_UPDATED 3471 //Your task '%1' has been updated. #define YOU_ASSIGNED_TASK 3472 //You have been assigned the task '%1'. -#define EXPEDITION_YOU_BELONG 3500 //You cannot create this expedition since you already belong to another. -#define EXPEDITION_YOU_PLAYED_HERE 3501 //You cannot create this expedition for another %1d:%2h:%3m since you have recently played here. -#define REQUIRED_PLAYER_COUNT 3503 //You do not meet the player count requirement. You have %1 players. You must have at least %2 and no more than %3. -#define EXPEDITION_REPLAY_TIMER 3504 //%1 cannot be added to this expedition for another %2D:%3H:%4M since they have recently played in this area. -#define EXPEDITION_AVAILABLE 3507 //%1 is now available to you. +#define DZ_YOU_BELONG 3500 //You cannot create this expedition since you already belong to another. +#define DZ_REPLAY_YOU 3501 //You cannot create this expedition for another %1d:%2h:%3m since you have recently played here. +#define DZ_PLAYER_COUNT 3503 //You do not meet the player count requirement. You have %1 players. You must have at least %2 and no more than %3. +#define DZ_REPLAY_OTHER 3504 //%1 cannot be added to this expedition for another %2D:%3H:%4M since they have recently played in this area. +#define DZ_AVAILABLE 3507 //%1 is now available to you. #define DZADD_INVITE 3508 //Sending an invitation to: %1. #define DZ_PREVENT_ENTERING 3510 //A strange magical presence prevents you from entering. It's too dangerous to enter at the moment. #define DZADD_INVITE_FAIL 3511 //%1 could not be invited to join you. -#define UNABLE_RETRIEVE_LEADER 3512 //Unable to retrieve information on the leader to check permissions. -#define EXPEDITION_NOT_LEADER 3513 //You are not the expedition leader, only %1 can issue this command. -#define EXPEDITION_NOT_MEMBER 3514 //%1 is not a member of this expedition. -#define EXPEDITION_REMOVED 3516 //%1 has been removed from %2. +#define DZ_NO_LEADER_INFO 3512 //Unable to retrieve information on the leader to check permissions. +#define DZ_NOT_LEADER 3513 //You are not the expedition leader, only %1 can issue this command. +#define DZ_NOT_MEMBER 3514 //%1 is not a member of this expedition. +#define DZ_REMOVED 3516 //%1 has been removed from %2. #define DZSWAP_INVITE 3517 //Sending an invitation to: %1. They must accept in order to swap party members. -#define DZMAKELEADER_NOT_ONLINE 3518 //%1 is not currently online. You can only transfer leadership to an online member of the expedition you are in. -#define DZLIST_REPLAY_TIMER 3519 //You have %1d:%2h:%3m remaining until you may enter %4. -#define DZMAKELEADER_NAME 3520 //%1 has been made the leader for this expedition. -#define DZMAKELEADER_YOU 3521 //You have been made the leader of this expedition. -#define EXPEDITION_INVITE_ACCEPTED 3522 //%1 has accepted your offer to join your expedition. -#define EXPEDITION_MEMBER_ADDED 3523 //%1 has been added to %2. -#define EXPEDITION_INVITE_ERROR 3524 //%1 accepted your offer to join your expedition but could not due to error(s). -#define EXPEDITION_INVITE_DECLINED 3525 //%1 has declined your offer to join your expedition. -#define EXPEDITION_ASKED_TO_JOIN 3527 //%1 has asked you to join the expedition: %2. Would you like to join? -#define DYNAMICZONE_WAY_IS_BLOCKED 3528 //The way is blocked to you. Perhaps you would be able to enter if there was a reason to come here. -#define EXPEDITION_NO_TIMERS 3529 //You have no outstanding timers. -#define EXPEDITION_MIN_REMAIN 3551 //You only have %1 minutes remaining before this expedition comes to an end. -#define EXPEDITION_LEADER 3552 //Expedition Leader: %1 -#define EXPEDITION_MEMBERS 3553 //Expedition Members: %1 -#define EXPEDITION_EVENT_TIMER 3561 //%1 cannot be added to this expedition since they have recently experienced %2. They must wait another %3D:%4H:%5M until they can experience it again. They may be added to the expedition later, once %2 has been completed. +#define DZ_LEADER_OFFLINE 3518 //%1 is not currently online. You can only transfer leadership to an online member of the expedition you are in. +#define DZ_TIMER 3519 //You have %1d:%2h:%3m remaining until you may enter %4. +#define DZ_LEADER_NAME 3520 //%1 has been made the leader for this expedition. +#define DZ_LEADER_YOU 3521 //You have been made the leader of this expedition. +#define DZ_INVITE_ACCEPTED 3522 //%1 has accepted your offer to join your expedition. +#define DZ_ADDED 3523 //%1 has been added to %2. +#define DZ_INVITE_ERROR 3524 //%1 accepted your offer to join your expedition but could not due to error(s). +#define DZ_INVITE_DECLINED 3525 //%1 has declined your offer to join your expedition. +#define DZ_INVITED 3527 //%1 has asked you to join the expedition: %2. Would you like to join? +#define DZ_WAY_IS_BLOCKED 3528 //The way is blocked to you. Perhaps you would be able to enter if there was a reason to come here. +#define DZ_NO_TIMERS 3529 //You have no outstanding timers. +#define DZ_MINUTES_REMAIN 3551 //You only have %1 minutes remaining before this expedition comes to an end. +#define DZ_LEADER 3552 //Expedition Leader: %1 +#define DZ_MEMBERS 3553 //Expedition Members: %1 +#define DZ_EVENT_TIMER 3561 //%1 cannot be added to this expedition since they have recently experienced %2. They must wait another %3D:%4H:%5M until they can experience it again. They may be added to the expedition later, once %2 has been completed. #define LOOT_NOT_ALLOWED 3562 //You are not allowed to loot the item: %1. #define DZ_UNABLE_RETRIEVE_LEADER 3583 //Unable to retrieve dynamic zone leader to check permissions. #define DZADD_NOT_ALLOWING 3585 //The expedition is not allowing players to be added. #define DZADD_NOT_ONLINE 3586 //%1 is not currently online. A player needs to be online to be added to a Dynamic Zone #define DZADD_EXCEED_MAX 3587 //You can not add another player since you currently have the maximum number of players allowed (%1) in this zone. #define DZADD_ALREADY_PART 3588 //You can not add %1 since they are already part of this zone. -#define DZADD_LEAVE_ZONE_FIRST 3589 //You can not add %1 since they first need to leave the zone before being allowed back in. -#define DZADD_ALREADY_ASSIGNED 3590 //%1 can not be added to this dynamic zone since they are already assigned to another dynamic zone. +#define DZADD_LEAVE_ZONE 3589 //You can not add %1 since they first need to leave the zone before being allowed back in. +#define DZADD_ALREADY_OTHER 3590 //%1 can not be added to this dynamic zone since they are already assigned to another dynamic zone. #define DZADD_REPLAY_TIMER 3591 //%1 can not be added to this dynamic zone for another %2D:%3H:%4M since they have recently played this zone. #define DZADD_EVENT_TIMER 3592 //%1 can not be added to this dynamic zone since they have recently experienced %2. They must wait for another %3D:%4H:%5M, or until event %2 has occurred. #define DZADD_PENDING 3593 //%1 currently has an outstanding invitation to join this Dynamic Zone. @@ -419,6 +421,11 @@ #define GUILD_BANK_FULL 6098 // There is no more room in the Guild Bank. #define GUILD_BANK_TRANSFERRED 6100 // '%1' transferred to Guild Bank from Deposits. #define GUILD_BANK_EMPTY_HANDS 6108 // You must empty your hands to withdraw from the Guild Bank. +#define EVOLVE_ITEM_EVOLVED 6145 //Your %1 has evolved! +#define EVOLVE_DETAILS 6146 //Evolving: Level %1/%2 %3%% %4 +#define EVOLVE_LEVEL_LIMIT 6147 //Your %1 can not evolve until you reach level %2. +#define EVOLVE_XP_TXFR_CONFIRM 6148 //Are you sure you want to transfer experience between these two items? +#define EVOLVE_XP_TXFRD 6149 //Your item's experience has been transferred! #define TRADESKILL_COMBINE_LORE 6199 // Combine would result in a LORE item (%1) you already possess. #define TRANSFORM_FAILED 6326 //This mold cannot be applied to your %1. #define TRANSFORM_COMPLETE 6327 //You have successfully transformed your %1. diff --git a/zone/task_client_state.cpp b/zone/task_client_state.cpp index 2b7f89ea9..af9525a60 100644 --- a/zone/task_client_state.cpp +++ b/zone/task_client_state.cpp @@ -66,7 +66,7 @@ void ClientTaskState::SendTaskHistory(Client *client, int task_index) return; } - const auto task_data = task_manager->GetTaskData(task_id); + const auto task_data = TaskManager::Instance()->GetTaskData(task_id); if (!task_data) { return; } @@ -252,16 +252,16 @@ int ClientTaskState::EnabledTaskCount(int task_set_id) if ((task_set_id <= 0) || (task_set_id >= MAXTASKSETS)) { return -1; } while ((enabled_task_index < m_enabled_tasks.size()) && - (task_set_index < task_manager->m_task_sets[task_set_id].size())) { + (task_set_index < TaskManager::Instance()->m_task_sets[task_set_id].size())) { - if (m_enabled_tasks[enabled_task_index] == task_manager->m_task_sets[task_set_id][task_set_index]) { + if (m_enabled_tasks[enabled_task_index] == TaskManager::Instance()->m_task_sets[task_set_id][task_set_index]) { enabled_task_count++; enabled_task_index++; task_set_index++; continue; } - if (m_enabled_tasks[enabled_task_index] < task_manager->m_task_sets[task_set_id][task_set_index]) { + if (m_enabled_tasks[enabled_task_index] < TaskManager::Instance()->m_task_sets[task_set_id][task_set_index]) { enabled_task_index++; } else { @@ -281,7 +281,7 @@ int ClientTaskState::ActiveTasksInSet(int task_set_id) int active_task_in_set_count = 0; - for (int task_id : task_manager->m_task_sets[task_set_id]) { + for (int task_id : TaskManager::Instance()->m_task_sets[task_set_id]) { if (IsTaskActive(task_id)) { active_task_in_set_count++; } @@ -298,7 +298,7 @@ int ClientTaskState::CompletedTasksInSet(int task_set_id) int completed_tasks_count = 0; - for (int i : task_manager->m_task_sets[task_set_id]) { + for (int i : TaskManager::Instance()->m_task_sets[task_set_id]) { if (IsTaskCompleted(i)) { completed_tasks_count++; } @@ -362,7 +362,7 @@ bool ClientTaskState::UnlockActivities(Client* client, ClientTaskInformation& ta task_info.updated ); - const auto task = task_manager->GetTaskData(task_info.task_id); + const auto task = TaskManager::Instance()->GetTaskData(task_info.task_id); if (!task) { return true; @@ -453,7 +453,7 @@ const TaskInformation* ClientTaskState::GetTaskData(const ClientTaskInformation& return nullptr; } - return task_manager->GetTaskData(client_task.task_id); + return TaskManager::Instance()->GetTaskData(client_task.task_id); } bool ClientTaskState::CanUpdate(Client* client, const TaskUpdateFilter& filter, int task_id, @@ -521,11 +521,6 @@ bool ClientTaskState::CanUpdate(Client* client, const TaskUpdateFilter& filter, int ClientTaskState::UpdateTasks(Client* client, const TaskUpdateFilter& filter, int count) { - if (!task_manager) - { - return 0; - } - int max_updated = 0; for (const auto& client_task : m_active_tasks) @@ -593,11 +588,6 @@ int ClientTaskState::UpdateTasks(Client* client, const TaskUpdateFilter& filter, std::pair ClientTaskState::FindTask(Client* client, const TaskUpdateFilter& filter) const { - if (!task_manager) - { - return std::make_pair(0, 0); - } - for (const auto& client_task : m_active_tasks) { const auto task = GetTaskData(client_task); @@ -901,7 +891,7 @@ int ClientTaskState::IncrementDoneCount( // and by the 'Task Stage Completed' message client->SendTaskActivityComplete(info->task_id, activity_id, task_index, task_data->type); // Send the updated task/activity_information list to the client - task_manager->SendSingleActiveTaskToClient(client, *info, task_complete, false); + TaskManager::Instance()->SendSingleActiveTaskToClient(client, *info, task_complete, false); if (!ignore_quest_update) { if (parse->PlayerHasQuestSub(EVENT_TASK_STAGE_COMPLETE)) { @@ -914,17 +904,6 @@ int ClientTaskState::IncrementDoneCount( parse->EventPlayer(EVENT_TASK_STAGE_COMPLETE, client, export_string, 0); } } - /* QS: PlayerLogTaskUpdates :: Update */ - if (RuleB(QueryServ, PlayerLogTaskUpdates)) { - std::string event_desc = StringFormat( - "Task Stage Complete :: taskid:%i activityid:%i donecount:%i in zoneid:%i instid:%i", - info->task_id, - info->activity[activity_id].activity_id, - info->activity[activity_id].done_count, - client->GetZoneID(), - client->GetInstanceID()); - QServ->PlayerLogEvent(Player_Log_Task_Updates, client->CharacterID(), event_desc); - } // If this task is now complete, the Completed tasks will have been // updated in UnlockActivities. Send the completed task list to the @@ -937,28 +916,16 @@ int ClientTaskState::IncrementDoneCount( int event_res = DispatchEventTaskComplete(client, *info, activity_id); - if (player_event_logs.IsEventEnabled(PlayerEvent::TASK_COMPLETE)) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::TASK_COMPLETE)) { auto e = PlayerEvent::TaskCompleteEvent{ .task_id = static_cast(info->task_id), - .task_name = task_manager->GetTaskName(static_cast(info->task_id)), + .task_name = TaskManager::Instance()->GetTaskName(static_cast(info->task_id)), .activity_id = static_cast(info->activity[activity_id].activity_id), .done_count = static_cast(info->activity[activity_id].done_count) }; RecordPlayerEventLogWithClient(client, PlayerEvent::TASK_COMPLETE, e); } - /* QS: PlayerLogTaskUpdates :: Complete */ - if (RuleB(QueryServ, PlayerLogTaskUpdates)) { - std::string event_desc = StringFormat( - "Task Complete :: taskid:%i activityid:%i donecount:%i in zoneid:%i instid:%i", - info->task_id, - info->activity[activity_id].activity_id, - info->activity[activity_id].done_count, - client->GetZoneID(), - client->GetInstanceID()); - QServ->PlayerLogEvent(Player_Log_Task_Updates, client->CharacterID(), event_desc); - } - client->SendTaskActivityComplete(info->task_id, 0, task_index, task_data->type, 0); // If Experience and/or cash rewards are set, reward them from the task even if reward_method is METHODQUEST @@ -971,25 +938,27 @@ int ClientTaskState::IncrementDoneCount( // shared tasks linger at the completion step and do not get removed from the task window unlike quests/task if (task_data->type != TaskType::Shared) { - task_manager->SendCompletedTasksToClient(client, this); + TaskManager::Instance()->SendCompletedTasksToClient(client, this); client->CancelTask(task_index, task_data->type); } + + client->LoadClientSharedCompletedTasks(); } } else { // Send an updated packet for this single activity_information - task_manager->SendTaskActivityLong( + TaskManager::Instance()->SendTaskActivityLong( client, info->task_id, activity_id, task_index ); - if (player_event_logs.IsEventEnabled(PlayerEvent::TASK_UPDATE)) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::TASK_UPDATE)) { auto e = PlayerEvent::TaskUpdateEvent{ .task_id = static_cast(info->task_id), - .task_name = task_manager->GetTaskName(static_cast(info->task_id)), + .task_name = TaskManager::Instance()->GetTaskName(static_cast(info->task_id)), .activity_id = static_cast(info->activity[activity_id].activity_id), .done_count = static_cast(info->activity[activity_id].done_count) }; @@ -997,7 +966,7 @@ int ClientTaskState::IncrementDoneCount( } } - task_manager->SaveClientState(client, this); + TaskManager::Instance()->SaveClientState(client, this); return count; } @@ -1049,6 +1018,7 @@ void ClientTaskState::RewardTask(Client *c, const TaskInformation *ti, ClientTas if (item_id > 0) { std::unique_ptr inst(database.CreateItem(item_id, charges)); if (inst && inst->GetItem()) { + c->CheckItemDiscoverability(item_id); bool stacked = c->TryStacking(inst.get()); if (!stacked) { int16_t slot = c->GetInv().FindFreeSlot(inst->IsClassBag(), true, inst->GetItem()->Size); @@ -1169,7 +1139,7 @@ void ClientTaskState::FailTask(Client *client, int task_id) // type: Shared Task (failed via world for all members) if (m_active_shared_task.task_id == task_id) { - task_manager->EndSharedTask(*client, task_id, true); + TaskManager::Instance()->EndSharedTask(*client, task_id, true); return; } @@ -1231,7 +1201,7 @@ bool ClientTaskState::IsTaskActivityActive(int task_id, int activity_id) return false; } - const auto task_data = task_manager->GetTaskData(info->task_id); + const auto task_data = TaskManager::Instance()->GetTaskData(info->task_id); if (!task_data) { return false; } @@ -1302,7 +1272,7 @@ void ClientTaskState::UpdateTaskActivity( return; } - const auto task_data = task_manager->GetTaskData(info->task_id); + const auto task_data = TaskManager::Instance()->GetTaskData(info->task_id); if (!task_data) { return; } @@ -1372,7 +1342,7 @@ void ClientTaskState::ResetTaskActivity(Client *client, int task_id, int activit return; } - const auto task_data = task_manager->GetTaskData(info->task_id); + const auto task_data = TaskManager::Instance()->GetTaskData(info->task_id); if (!task_data) { return; } @@ -1406,14 +1376,14 @@ void ClientTaskState::ResetTaskActivity(Client *client, int task_id, int activit bool ClientTaskState::CompleteTask(Client *c, uint32 task_id) { - const auto task_data = task_manager->GetTaskData(task_id); + const auto task_data = TaskManager::Instance()->GetTaskData(task_id); if (!task_data) { return false; } for ( int activity_id = 0; - activity_id < task_manager->GetActivityCount(task_id); + activity_id < TaskManager::Instance()->GetActivityCount(task_id); activity_id++ ) { c->UpdateTaskActivity( @@ -1426,19 +1396,37 @@ bool ClientTaskState::CompleteTask(Client *c, uint32 task_id) return true; } +bool ClientTaskState::UncompleteTask(int task_id) +{ + return std::erase_if( + m_completed_tasks, + [&](const CompletedTaskInformation& task) { + return task.task_id == task_id; + } + ); +} + void ClientTaskState::ShowClientTaskInfoMessage(ClientTaskInformation *task, Client *c) { - const auto task_data = task_manager->GetTaskData(task->task_id); + const auto task_data = TaskManager::Instance()->GetTaskData(task->task_id); c->Message(Chat::White, "------------------------------------------------"); c->Message( Chat::White, fmt::format( - "Task {} | Title: {} ID: {} Type: {}", + "Task {} | Title: {} ID: {} Type: {} | {}", task->slot, task_data->title, task->task_id, - Tasks::GetTaskTypeDescription(task_data->type) + Tasks::GetTaskTypeDescription(task_data->type), + Saylink::Create( + fmt::format( + "#task complete {}", + task->task_id + ), + false, + "Complete" + ) ).c_str() ); c->Message(Chat::White, "------------------------------------------------"); @@ -1450,7 +1438,7 @@ void ClientTaskState::ShowClientTaskInfoMessage(ClientTaskInformation *task, Cli ).c_str() ); - for (int activity_id = 0; activity_id < task_manager->GetActivityCount(task->task_id); activity_id++) { + for (int activity_id = 0; activity_id < TaskManager::Instance()->GetActivityCount(task->task_id); activity_id++) { std::vector update_increments = { "1", "5", "10", "20", "50" }; std::vector update_saylinks; @@ -1521,7 +1509,7 @@ int ClientTaskState::TaskTimeLeft(int task_id) if (m_active_task.task_id == task_id) { int time_now = time(nullptr); - const auto task_data = task_manager->GetTaskData(task_id); + const auto task_data = TaskManager::Instance()->GetTaskData(task_id); if (!task_data) { return -1; } @@ -1539,7 +1527,7 @@ int ClientTaskState::TaskTimeLeft(int task_id) if (m_active_shared_task.task_id == task_id) { int time_now = time(nullptr); - const auto task_data = task_manager->GetTaskData(task_id); + const auto task_data = TaskManager::Instance()->GetTaskData(task_id); if (!task_data) { return -1; } @@ -1564,7 +1552,7 @@ int ClientTaskState::TaskTimeLeft(int task_id) int time_now = time(nullptr); - const auto task_data = task_manager->GetTaskData(active_quest.task_id); + const auto task_data = TaskManager::Instance()->GetTaskData(active_quest.task_id); if (!task_data) { return -1; } @@ -1583,7 +1571,7 @@ int ClientTaskState::TaskTimeLeft(int task_id) return -1; } -bool ClientTaskState::IsTaskCompleted(int task_id) +bool ClientTaskState::IsTaskCompleted(int task_id, Client *c) { if (!RuleB(TaskSystem, RecordCompletedTasks)) { return false; @@ -1596,6 +1584,14 @@ bool ClientTaskState::IsTaskCompleted(int task_id) } } + if (c) { + for (auto &e: c->GetCompletedSharedTasks()) { + if (e == task_id) { + return true; + } + } + } + return false; } @@ -1628,7 +1624,7 @@ bool ClientTaskState::TaskOutOfTime(TaskType task_type, int index) } int time_now = time(nullptr); - const auto task_data = task_manager->GetTaskData(info->task_id); + const auto task_data = TaskManager::Instance()->GetTaskData(info->task_id); if (!task_data) { return false; } @@ -1948,7 +1944,7 @@ void ClientTaskState::RemoveTask(Client *client, int sequence_number, TaskType t void ClientTaskState::RemoveTaskByTaskID(Client *client, uint32 task_id) { - switch (task_manager->GetTaskType(task_id)) { + switch (TaskManager::Instance()->GetTaskType(task_id)) { case TaskType::Task: { if (m_active_task.task_id == task_id) { LogTasks("RemoveTaskByTaskID found Task [{}]", task_id); @@ -1980,17 +1976,17 @@ void ClientTaskState::RemoveTaskByTaskID(Client *client, uint32 task_id) void ClientTaskState::AcceptNewTask( Client *client, int task_id, - int npc_type_id, + int npc_entity_id, time_t accept_time, bool enforce_level_requirement ) { - if (!task_manager || task_id < 0) { + if (task_id < 0) { client->Message(Chat::Red, "Task system not functioning, or task_id %i out of range.", task_id); return; } - const auto task = task_manager->GetTaskData(task_id); + const auto task = TaskManager::Instance()->GetTaskData(task_id); if (!task) { client->Message(Chat::Red, "Invalid task_id %i", task_id); return; @@ -2013,7 +2009,7 @@ void ClientTaskState::AcceptNewTask( // fill r->requested_character_id = client->CharacterID(); r->requested_task_id = task_id; - r->requested_npc_type_id = npc_type_id; + r->requested_npc_entity_id = npc_entity_id; // send worldserver.SendPacket(pack); @@ -2060,12 +2056,12 @@ void ClientTaskState::AcceptNewTask( } } - if (enforce_level_requirement && !task_manager->ValidateLevel(task_id, client->GetLevel())) { + if (enforce_level_requirement && !TaskManager::Instance()->ValidateLevel(task_id, client->GetLevel())) { client->MessageString(Chat::Yellow, TASK_NOT_RIGHT_LEVEL); return; } - if (!task_manager->IsTaskRepeatable(task_id) && IsTaskCompleted(task_id)) { + if (!TaskManager::Instance()->IsTaskRepeatable(task_id) && IsTaskCompleted(task_id)) { return; } @@ -2196,20 +2192,20 @@ void ClientTaskState::AcceptNewTask( } } - task_manager->SendSingleActiveTaskToClient(client, *active_slot, false, true); + TaskManager::Instance()->SendSingleActiveTaskToClient(client, *active_slot, false, true); client->StartTaskRequestCooldownTimer(); client->MessageString(Chat::DefaultText, YOU_ASSIGNED_TASK, task->title.c_str()); - task_manager->SaveClientState(client, this); + TaskManager::Instance()->SaveClientState(client, this); - NPC *npc = entity_list.GetID(npc_type_id)->CastToNPC(); + NPC *npc = entity_list.GetNPCByID(npc_entity_id); if (npc) { - if (player_event_logs.IsEventEnabled(PlayerEvent::TASK_ACCEPT)) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::TASK_ACCEPT)) { auto e = PlayerEvent::TaskAcceptEvent{ - .npc_id = static_cast(npc_type_id), + .npc_id = npc->GetNPCTypeID(), .npc_name = npc->GetCleanName(), .task_id = static_cast(task_id), - .task_name = task_manager->GetTaskName(static_cast(task_id)), + .task_name = TaskManager::Instance()->GetTaskName(static_cast(task_id)), }; RecordPlayerEventLogWithClient(client, PlayerEvent::TASK_ACCEPT, e); } @@ -2218,12 +2214,12 @@ void ClientTaskState::AcceptNewTask( parse->EventNPC(EVENT_TASK_ACCEPTED, npc, client, std::to_string(task_id), 0); } } else { - if (player_event_logs.IsEventEnabled(PlayerEvent::TASK_ACCEPT)) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::TASK_ACCEPT)) { auto e = PlayerEvent::TaskAcceptEvent{ .npc_id = 0, .npc_name = "No NPC", .task_id = static_cast(task_id), - .task_name = task_manager->GetTaskName(static_cast(task_id)), + .task_name = TaskManager::Instance()->GetTaskName(static_cast(task_id)), }; RecordPlayerEventLogWithClient(client, PlayerEvent::TASK_ACCEPT, e); } @@ -2259,7 +2255,7 @@ void ClientTaskState::SharedTaskIncrementDoneCount( bool ignore_quest_update ) { - const auto t = task_manager->GetTaskData(task_id); + const auto t = TaskManager::Instance()->GetTaskData(task_id); auto info = GetClientTaskInfo(t->type, TASKSLOTSHAREDTASK); if (info == nullptr) { @@ -2298,24 +2294,22 @@ bool ClientTaskState::HasActiveSharedTask() void ClientTaskState::CreateTaskDynamicZone(Client* client, int task_id, DynamicZone& dz_request) { - const auto task = task_manager->GetTaskData(task_id); + const auto task = TaskManager::Instance()->GetTaskData(task_id); if (!task) { return; } // dz should be named the version-based zone name (used in choose zone window and dz window on live) - auto zone_info = zone_store.GetZoneWithFallback(dz_request.GetZoneID(), dz_request.GetZoneVersion()); + auto zone_info = ZoneStore::Instance()->GetZoneWithFallback(dz_request.GetZoneID(), dz_request.GetZoneVersion()); dz_request.SetName(zone_info && !zone_info->long_name.empty() ? zone_info->long_name : task->title); dz_request.SetMinPlayers(task->min_players); dz_request.SetMaxPlayers(task->max_players); - // a task might create a dz from an objective so override dz duration to time remaining - // live probably creates the dz with the shared task and just adds members for objectives + // a task might create a dz from an element so override dz duration to time remaining std::chrono::seconds seconds(TaskTimeLeft(task_id)); if (task->duration == 0 || seconds.count() < 0) { - // todo: maybe add a rule for duration // cap unlimited duration tasks so instance isn't held indefinitely // expected behavior is to re-acquire any unlimited tasks that have an expired dz seconds = std::chrono::duration_cast(std::chrono::hours(24)); @@ -2323,32 +2317,21 @@ void ClientTaskState::CreateTaskDynamicZone(Client* client, int task_id, Dynamic dz_request.SetDuration(static_cast(seconds.count())); - if (task->type == TaskType::Task || task->type == TaskType::Quest) - { - if (task->type == TaskType::Task) { - dz_request.SetType(DynamicZoneType::Task); - } else { - dz_request.SetType(DynamicZoneType::Quest); - } - - // todo: can enable non-shared task dz creation when dz ids are persisted for them (db also) - //DynamicZoneMember solo_member{ client->CharacterID(), client->GetCleanName() }; - //DynamicZone::CreateNew(dz_request, { solo_member }); - } - else if (task->type == TaskType::Shared) + if (task->type == TaskType::Shared) { dz_request.SetType(DynamicZoneType::Mission); // shared task missions are created in world - EQ::Net::DynamicPacket dyn_pack = dz_request.GetSerializedDzPacket(); + std::ostringstream ss = dz_request.GetSerialized(); + std::string_view sv = ss.view(); - auto pack_size = sizeof(ServerSharedTaskCreateDynamicZone_Struct) + dyn_pack.Length(); + auto pack_size = sizeof(ServerSharedTaskCreateDynamicZone_Struct) + sv.size(); auto pack = std::make_unique(ServerOP_SharedTaskCreateDynamicZone, static_cast(pack_size)); auto buf = reinterpret_cast(pack->pBuffer); buf->source_character_id = client->CharacterID(); buf->task_id = task_id; - buf->cereal_size = static_cast(dyn_pack.Length()); - memcpy(buf->cereal_data, dyn_pack.Data(), dyn_pack.Length()); + buf->cereal_size = static_cast(sv.size()); + memcpy(buf->cereal_data, sv.data(), sv.size()); worldserver.SendPacket(pack.get()); } @@ -2374,7 +2357,7 @@ void ClientTaskState::ListTaskTimers(Client* client) for (const auto& task_timer : character_task_timers) { - const auto task = task_manager->GetTaskData(task_timer.task_id); + const auto task = TaskManager::Instance()->GetTaskData(task_timer.task_id); if (task) { auto timer_type = static_cast(task_timer.timer_type); @@ -2480,10 +2463,6 @@ void ClientTaskState::SyncSharedTaskZoneClientDoneCountState( bool ClientTaskState::HasActiveTasks() { - if (!task_manager) { - return false; - } - if (m_active_task.task_id != TASKSLOTEMPTY) { return true; } @@ -2520,9 +2499,9 @@ void ClientTaskState::LockSharedTask(Client* client, bool lock) void ClientTaskState::EndSharedTask(Client* client, bool send_fail) { - if (task_manager && m_active_shared_task.task_id != TASKSLOTEMPTY) + if (m_active_shared_task.task_id != TASKSLOTEMPTY) { - task_manager->EndSharedTask(*client, m_active_shared_task.task_id, send_fail); + TaskManager::Instance()->EndSharedTask(*client, m_active_shared_task.task_id, send_fail); } } bool ClientTaskState::CanAcceptNewTask(Client* client, int task_id, int npc_entity_id) const diff --git a/zone/task_client_state.h b/zone/task_client_state.h index 6f22feb4a..30c5dd69c 100644 --- a/zone/task_client_state.h +++ b/zone/task_client_state.h @@ -42,10 +42,10 @@ public: int GetTaskActivityDoneCount(TaskType task_type, int index, int activity_id); int GetTaskActivityDoneCountFromTaskID(int task_id, int activity_id); int GetTaskStartTime(TaskType task_type, int index); - void AcceptNewTask(Client *client, int task_id, int npc_type_id, time_t accept_time, bool enforce_level_requirement = false); + void AcceptNewTask(Client *client, int task_id, int npc_entity_id, time_t accept_time, bool enforce_level_requirement = false); void FailTask(Client *client, int task_id); int TaskTimeLeft(int task_id); - bool IsTaskCompleted(int task_id); + bool IsTaskCompleted(int task_id, Client *c = nullptr); bool AreTasksCompleted(const std::vector& task_ids); bool IsTaskActive(int task_id); bool IsTaskActivityActive(int task_id, int activity_id); @@ -86,6 +86,7 @@ public: bool HasExploreTask(Client* client) const; void EndSharedTask(Client* client, bool send_fail); bool CompleteTask(Client *c, uint32 task_id); + bool UncompleteTask(int task_id); inline bool HasFreeTaskSlot() { return m_active_task.task_id == TASKSLOTEMPTY; } diff --git a/zone/task_manager.cpp b/zone/task_manager.cpp index 34cb4d6c4..d6df2ebb9 100644 --- a/zone/task_manager.cpp +++ b/zone/task_manager.cpp @@ -43,6 +43,8 @@ bool TaskManager::LoadTaskSets() bool TaskManager::LoadTasks(int single_task) { + m_task_data.clear(); + std::string task_query_filter = fmt::format("id = {}", single_task); if (single_task == 0) { if (!LoadTaskSets()) { @@ -198,9 +200,9 @@ bool TaskManager::LoadTasks(int single_task) ad->target_name = a.target_name; ad->item_list = a.item_list; ad->skill_list = a.skill_list; - ad->skill_id = Strings::IsNumber(a.skill_list) ? Strings::ToInt(a.skill_list) : 0; // for older clients + ad->skill_id = !a.skill_list.empty() && Strings::IsNumber(a.skill_list) ? Strings::ToInt(a.skill_list) : 0; // for older clients ad->spell_list = a.spell_list; - ad->spell_id = Strings::IsNumber(a.spell_list) ? Strings::ToInt(a.spell_list) : 0; // for older clients + ad->spell_id = !a.skill_list.empty() && Strings::IsNumber(a.spell_list) ? Strings::ToInt(a.spell_list) : 0; // for older clients ad->description_override = a.description_override; ad->npc_match_list = a.npc_match_list; ad->item_id_list = a.item_id_list; diff --git a/zone/task_manager.h b/zone/task_manager.h index e1f1a1154..abd1255a4 100644 --- a/zone/task_manager.h +++ b/zone/task_manager.h @@ -70,6 +70,12 @@ public: return it != m_task_data.end() ? &it->second : nullptr; } + static TaskManager* Instance() + { + static TaskManager instance; + return &instance; + } + private: std::vector m_task_sets[MAXTASKSETS]; std::unordered_map m_task_data; diff --git a/zone/tasks.cpp b/zone/tasks.cpp index f0619db9c..708a43e84 100644 --- a/zone/tasks.cpp +++ b/zone/tasks.cpp @@ -14,16 +14,17 @@ extern QueryServ *QServ; void Client::LoadClientTaskState() { - if (RuleB(TaskSystem, EnableTaskSystem) && task_manager) { - safe_delete(task_state); + if (RuleB(TaskSystem, EnableTaskSystem)) { + LoadClientSharedCompletedTasks(); + safe_delete(task_state); task_state = new ClientTaskState(); - if (!task_manager->LoadClientState(this, task_state)) { + if (!TaskManager::Instance()->LoadClientState(this, task_state)) { safe_delete(task_state); } else { - task_manager->SendActiveTasksToClient(this); - task_manager->SendCompletedTasksToClient(this, task_state); + TaskManager::Instance()->SendActiveTasksToClient(this); + TaskManager::Instance()->SendCompletedTasksToClient(this, task_state); } } } diff --git a/zone/titles.cpp b/zone/titles.cpp index 0f30bd27b..84ee2dd50 100644 --- a/zone/titles.cpp +++ b/zone/titles.cpp @@ -1,28 +1,9 @@ -/* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2005 EQEMu Development Team (http://eqemulator.net) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; version 2 of the License. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY except by those people which sell it, which - are required to give you total support for your newly bought product; - without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - #include "../common/eq_packet_structs.h" #include "../common/strings.h" #include "../common/misc_functions.h" #include "../common/repositories/player_titlesets_repository.h" #include "client.h" -#include "entity.h" #include "mob.h" #include "titles.h" @@ -37,60 +18,43 @@ bool TitleManager::LoadTitles() { titles.clear(); - std::string query = "SELECT `id`, `skill_id`, `min_skill_value`, `max_skill_value`, " - "`min_aa_points`, `max_aa_points`, `class`, `gender`, `char_id`, " - "`status`, `item_id`, `prefix`, `suffix`, `title_set` FROM titles"; - auto results = database.QueryDatabase(query); - if (!results.Success() || !results.RowCount()) { + const auto& l = TitlesRepository::All(database); + + if (l.empty()) { return false; } - for (auto row : results) { - TitleEntry title; - title.title_id = Strings::ToInt(row[0]); - title.skill_id = (EQ::skills::SkillType) Strings::ToInt(row[1]); - title.min_skill_value = Strings::ToInt(row[2]); - title.max_skill_value = Strings::ToInt(row[3]); - title.min_aa_points = Strings::ToInt(row[4]); - title.max_aa_points = Strings::ToInt(row[5]); - title.class_id = Strings::ToInt(row[6]); - title.gender_id = Strings::ToInt(row[7]); - title.character_id = Strings::ToInt(row[8]); - title.status = Strings::ToInt(row[9]); - title.item_id = Strings::ToInt(row[10]); - title.prefix = row[11]; - title.suffix = row[12]; - title.titleset = Strings::ToInt(row[13]); - titles.push_back(title); + for (const auto& e : l) { + titles.push_back(e); } - LogInfo("Loaded [{}] titles", Strings::Commify(std::to_string(results.RowCount()))); + LogInfo("Loaded [{}] Title{}", Strings::Commify(l.size()), l.size() != 1 ? "s" : ""); return true; } -EQApplicationPacket *TitleManager::MakeTitlesPacket(Client *client) +EQApplicationPacket* TitleManager::MakeTitlesPacket(Client* c) { - std::vector available_titles; - uint32 length = 4; - for (const auto& title : titles) { - if (!IsClientEligibleForTitle(client, title)) { - continue; - } + const auto& eligible_titles = GetEligibleTitles(c); - available_titles.push_back(title); - length += title.prefix.length() + title.suffix.length() + 6; + uint32 total_length = 4; + + for (const auto& e : eligible_titles) { + total_length += e.prefix.length() + e.suffix.length() + 6; } - auto outapp = new EQApplicationPacket(OP_SendTitleList, length); - char *buffer = (char *)outapp->pBuffer; - VARSTRUCT_ENCODE_TYPE(uint32, buffer, available_titles.size()); - for (const auto& available_title : available_titles) { - VARSTRUCT_ENCODE_TYPE(uint32, buffer, available_title.title_id); - VARSTRUCT_ENCODE_STRING(buffer, available_title.prefix.c_str()); - VARSTRUCT_ENCODE_STRING(buffer, available_title.suffix.c_str()); + auto outapp = new EQApplicationPacket(OP_SendTitleList, total_length); + char* buffer = (char*) outapp->pBuffer; + + VARSTRUCT_ENCODE_TYPE(uint32, buffer, eligible_titles.size()); + + for (const auto& t : eligible_titles) { + VARSTRUCT_ENCODE_TYPE(uint32, buffer, t.id); + VARSTRUCT_ENCODE_STRING(buffer, t.prefix.c_str()); + VARSTRUCT_ENCODE_STRING(buffer, t.suffix.c_str()); } - return(outapp); + + return outapp; } std::string TitleManager::GetPrefix(int title_id) @@ -99,13 +63,15 @@ std::string TitleManager::GetPrefix(int title_id) return ""; } - for (const auto& title : titles) { - if (title.title_id == title_id) { - return title.prefix; + auto e = std::find_if( + titles.begin(), + titles.end(), + [title_id](const auto& t) { + return t.id == title_id; } - } + ); - return ""; + return e != titles.end() ? e->prefix : ""; } std::string TitleManager::GetSuffix(int title_id) @@ -114,180 +80,215 @@ std::string TitleManager::GetSuffix(int title_id) return ""; } - for (const auto& title : titles) { - if (title.title_id == title_id) { - return title.suffix; + auto e = std::find_if( + titles.begin(), + titles.end(), + [title_id](const auto& t) { + return t.id == title_id; } - } + ); - return ""; + return e != titles.end() ? e->suffix : ""; } -bool TitleManager::HasTitle(Client* client, uint32 title_id) +bool TitleManager::HasTitle(Client* c, uint32 title_id) { - if (!client || !title_id) { + if (!c || !title_id) { return false; } - for (const auto& title : titles) { - if (title.title_id == title_id) { - return IsClientEligibleForTitle(client, title); - } - } + const auto& eligible_titles = GetEligibleTitles(c); - return false; + return std::any_of( + eligible_titles.begin(), + eligible_titles.end(), + [title_id](const auto& t) { + return t.id == title_id; + } + ); } -bool TitleManager::IsClientEligibleForTitle(Client *client, TitleEntry title) +std::vector TitleManager::GetEligibleTitles(Client* c) { - if (!client) { - return false; + std::vector eligible_titles = {}; + if (!c) { + return eligible_titles; } - if (title.character_id >= 0 && client->CharacterID() != static_cast(title.character_id)) { - return false; - } + const auto& player_title_sets = PlayerTitlesetsRepository::GetWhere( + database, + fmt::format( + "`char_id` = {}", + c->CharacterID() + ) + ); - if (title.status >= 0 && client->Admin() < title.status) { - return false; - } - - if (title.gender_id >= Gender::Male && client->GetBaseGender() != title.gender_id) { - return false; - } - - if (title.class_id >= Class::None && client->GetBaseClass() != title.class_id) { - return false; - } - - if (title.min_aa_points >= 0 && client->GetSpentAA() < title.min_aa_points) { - return false; - } - - if (title.max_aa_points >= 0 && client->GetSpentAA() > title.max_aa_points) { - return false; - } - - if (title.skill_id >= 0) { - auto skill_id = static_cast(title.skill_id); - if (title.min_skill_value >= 0 && client->GetRawSkill(skill_id) < static_cast(title.min_skill_value)) { - return false; + for (auto t : titles) { + if (t.char_id >= 0 && c->CharacterID() != static_cast(t.char_id)) { + continue; } - if (title.max_skill_value >= 0 && client->GetRawSkill(skill_id) > static_cast(title.max_skill_value)) { - return false; + if (t.status >= 0 && c->Admin() < t.status) { + continue; } + + if (t.gender >= Gender::Male && c->GetBaseGender() != t.gender) { + continue; + } + + if (t.class_ >= Class::None && c->GetBaseClass() != t.class_) { + continue; + } + + if (t.min_aa_points >= 0 && c->GetSpentAA() < t.min_aa_points) { + continue; + } + + if (t.max_aa_points >= 0 && c->GetSpentAA() > t.max_aa_points) { + continue; + } + + if (t.skill_id >= 0) { + auto skill_id = static_cast(t.skill_id); + if ( + t.min_skill_value >= 0 && + c->GetRawSkill(skill_id) < static_cast(t.min_skill_value) + ) { + continue; + } + + if ( + t.max_skill_value >= 0 && + c->GetRawSkill(skill_id) > static_cast(t.max_skill_value) + ) { + continue; + } + } + + if (t.item_id >= 1 && c->GetInv().HasItem(t.item_id) == INVALID_INDEX) { + continue; + } + + if ( + t.title_set > 0 && + !std::any_of( + player_title_sets.begin(), + player_title_sets.end(), + [t](const auto& e) { + return e.title_set == t.title_set; + } + ) + ) { + continue; + } + + eligible_titles.emplace_back(t); } - if (title.item_id >= 1 && client->GetInv().HasItem(title.item_id) == INVALID_INDEX) { - return false; - } - - if (title.titleset > 0 && !client->CheckTitle(title.titleset)) { - return false; - } - - return true; + return eligible_titles; } bool TitleManager::IsNewAATitleAvailable(int aa_points, int class_id) { - for (const auto& title : titles) { - if ( - (title.class_id == -1 || title.class_id == class_id) && - title.min_aa_points == aa_points - ) { - return true; + return std::any_of( + titles.begin(), + titles.end(), + [class_id, aa_points](const auto& t) { + return ( + (t.class_ == -1 || t.class_ == class_id) && + t.min_aa_points == aa_points + ); } - } - - return false; + ); } bool TitleManager::IsNewTradeSkillTitleAvailable(int skill_id, int skill_value) { - for (const auto& title : titles) { - if (title.skill_id == skill_id && title.min_skill_value == skill_value) { - return true; + return std::any_of( + titles.begin(), + titles.end(), + [skill_id, skill_value](const auto& t) { + return t.skill_id == skill_id && t.min_skill_value == skill_value; } - } - - return false; + ); } -void TitleManager::CreateNewPlayerTitle(Client *client, std::string title) +void TitleManager::CreateNewPlayerTitle(Client* c, std::string title) { - if (!client || title.empty()) { + if (!c || title.empty()) { return; } - client->SetAATitle(title); + c->SetAATitle(title); - auto query = fmt::format( - "SELECT `id` FROM titles WHERE `prefix` = '{}' AND char_id = {}", - Strings::Escape(title), - client->CharacterID() + const auto& l = TitlesRepository::GetWhere( + database, + fmt::format( + "`char_id` = {} AND `prefix` = '{}'", + c->CharacterID(), + Strings::Escape(title) + ) ); - auto results = database.QueryDatabase(query); - if (results.Success() && results.RowCount()){ + + if (!l.empty()) { return; } - query = fmt::format( - "INSERT INTO titles (`char_id`, `prefix`) VALUES ({}, '{}')", - client->CharacterID(), - Strings::Escape(title) - ); - results = database.QueryDatabase(query); - if (!results.Success()) { + auto e = TitlesRepository::NewEntity(); + + e.char_id = c->CharacterID(); + e.prefix = title; + + if (!TitlesRepository::InsertOne(database, e).id) { return; } - auto pack = new ServerPacket(ServerOP_ReloadTitles, 0); - worldserver.SendPacket(pack); - safe_delete(pack); + worldserver.SendReload(ServerReload::Type::Titles); } -void TitleManager::CreateNewPlayerSuffix(Client *client, std::string suffix) +void TitleManager::CreateNewPlayerSuffix(Client* c, std::string suffix) { - if (!client || suffix.empty()) { + if (!c || suffix.empty()) { return; } - client->SetTitleSuffix(suffix); + c->SetTitleSuffix(suffix); - auto query = fmt::format( - "SELECT `id` FROM titles WHERE `suffix` = '{}' AND char_id = {}", - Strings::Escape(suffix), - client->CharacterID() + const auto& l = TitlesRepository::GetWhere( + database, + fmt::format( + "`char_id` = {} AND `suffix` = '{}'", + c->CharacterID(), + Strings::Escape(suffix) + ) ); - auto results = database.QueryDatabase(query); - if (results.Success() && results.RowCount()) { + + if (!l.empty()) { return; } - query = fmt::format( - "INSERT INTO titles (`char_id`, `suffix`) VALUES ({}, '{}')", - client->CharacterID(), - Strings::Escape(suffix) - ); - results = database.QueryDatabase(query); - if (!results.Success()) { + auto e = TitlesRepository::NewEntity(); + + e.char_id = c->CharacterID(); + e.suffix = suffix; + + if (!TitlesRepository::InsertOne(database, e).id) { return; } - auto pack = new ServerPacket(ServerOP_ReloadTitles, 0); - worldserver.SendPacket(pack); - safe_delete(pack); + worldserver.SendReload(ServerReload::Type::Titles); } void Client::SetAATitle(std::string title) { strn0cpy(m_pp.title, title.c_str(), sizeof(m_pp.title)); + auto outapp = new EQApplicationPacket(OP_SetTitleReply, sizeof(SetTitleReply_Struct)); - auto strs = (SetTitleReply_Struct *) outapp->pBuffer; + auto strs = (SetTitleReply_Struct*) outapp->pBuffer; + strn0cpy(strs->title, title.c_str(), sizeof(strs->title)); strs->entity_id = GetID(); + entity_list.QueueClients(this, outapp, false); safe_delete(outapp); } @@ -295,46 +296,49 @@ void Client::SetAATitle(std::string title) void Client::SetTitleSuffix(std::string suffix) { strn0cpy(m_pp.suffix, suffix.c_str(), sizeof(m_pp.suffix)); + auto outapp = new EQApplicationPacket(OP_SetTitleReply, sizeof(SetTitleReply_Struct)); - auto strs = (SetTitleReply_Struct *) outapp->pBuffer; + auto strs = (SetTitleReply_Struct*) outapp->pBuffer; + strs->is_suffix = 1; strn0cpy(strs->title, suffix.c_str(), sizeof(strs->title)); strs->entity_id = GetID(); + entity_list.QueueClients(this, outapp, false); safe_delete(outapp); } -void Client::EnableTitle(int title_set) +void Client::EnableTitle(int title_set, bool insert) { if (CheckTitle(title_set)) { return; } - std::string query = fmt::format( - "INSERT INTO player_titlesets (char_id, title_set) VALUES ({}, {})", - CharacterID(), - title_set - ); - auto results = database.QueryDatabase(query); - if (!results.Success()) { - LogError("Error in EnableTitle query for titleset [{}] and charid [{}]", title_set, CharacterID()); + auto e = PlayerTitlesetsRepository::NewEntity(); + + e.char_id = CharacterID(); + e.title_set = title_set; + + if (insert) { + e = PlayerTitlesetsRepository::InsertOne(database, e); + if (!e.id) { + LogError("Error in EnableTitle query for titleset [{}] and charid [{}]", title_set, CharacterID()); + return; + } } + m_player_title_sets.emplace_back(e); } bool Client::CheckTitle(int title_set) { - std::string query = fmt::format( - "SELECT `id` FROM player_titlesets WHERE `title_set` = {} AND `char_id` = {} LIMIT 1", - title_set, - CharacterID() - ); - auto results = database.QueryDatabase(query); - if (!results.Success() || !results.RowCount()) { - return false; + for (const auto& e : m_player_title_sets) { + if (e.title_set == title_set) { + return true; + } } - return true; + return false; } void Client::RemoveTitle(int title_set) @@ -343,13 +347,13 @@ void Client::RemoveTitle(int title_set) return; } - for (const auto& title : title_manager.GetTitles()) { - if (title.titleset == title_set) { - if (std::string(m_pp.title) == title.prefix) { + for (const auto& t : title_manager.GetTitles()) { + if (t.title_set == title_set) { + if (std::string(m_pp.title) == t.prefix) { SetAATitle(""); } - if (std::string(m_pp.suffix) == title.suffix) { + if (std::string(m_pp.suffix) == t.suffix) { SetTitleSuffix(""); } @@ -357,6 +361,14 @@ void Client::RemoveTitle(int title_set) } } + auto& titles = m_player_title_sets; + for (auto e = titles.begin(); e != titles.end(); e++) { + if (e->title_set == title_set) { + titles.erase(e); + break; + } + } + PlayerTitlesetsRepository::DeleteWhere( database, fmt::format( diff --git a/zone/titles.h b/zone/titles.h index 32909cb3f..5486e49c5 100644 --- a/zone/titles.h +++ b/zone/titles.h @@ -1,46 +1,12 @@ -/* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2005 EQEMu Development Team (http://eqemulator.net) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; version 2 of the License. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY except by those people which sell it, which - are required to give you total support for your newly bought product; - without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ #ifndef TITLES_H #define TITLES_H #include +#include "../common/repositories/titles_repository.h" class Client; class EQApplicationPacket; -struct TitleEntry -{ - int title_id; - int skill_id; - int min_skill_value; - int max_skill_value; - int min_aa_points; - int max_aa_points; - int class_id; - int gender_id; - int character_id; - int status; - int item_id; - std::string prefix; - std::string suffix; - int titleset; -}; - class TitleManager { public: @@ -48,19 +14,19 @@ public: bool LoadTitles(); - EQApplicationPacket *MakeTitlesPacket(Client *client); + EQApplicationPacket* MakeTitlesPacket(Client* c); std::string GetPrefix(int title_id); std::string GetSuffix(int title_id); - bool IsClientEligibleForTitle(Client *client, TitleEntry title); + std::vector GetEligibleTitles(Client* c); bool IsNewAATitleAvailable(int aa_points, int class_id); - bool IsNewTradeSkillTitleAvailable(int skill_id, int skill_value); - void CreateNewPlayerTitle(Client *client, std::string title); - void CreateNewPlayerSuffix(Client *client, std::string suffix); - bool HasTitle(Client* client, uint32 title_id); - inline const std::vector& GetTitles() { return titles; } + bool IsNewTradeSkillTitleAvailable(int t, int skill_value); + void CreateNewPlayerTitle(Client* c, std::string title); + void CreateNewPlayerSuffix(Client* c, std::string suffix); + bool HasTitle(Client* c, uint32 title_id); + inline const std::vector& GetTitles() { return titles; } protected: - std::vector titles; + std::vector titles; }; extern TitleManager title_manager; diff --git a/zone/tradeskills.cpp b/zone/tradeskills.cpp index d0abc1b5f..d307ca418 100644 --- a/zone/tradeskills.cpp +++ b/zone/tradeskills.cpp @@ -19,6 +19,7 @@ #include "../common/global_define.h" #include "../common/events/player_event_logs.h" +#include #include #ifndef WIN32 @@ -465,7 +466,7 @@ void Object::HandleCombine(Client* user, const NewCombine_Struct* in_combine, Ob } } else if (spec.tradeskill == EQ::skills::SkillTinkering) { - if (user_pp.race != GNOME) { + if (user_pp.race != Race::Gnome) { user->Message(Chat::Red, "Only gnomes can tinker."); auto outapp = new EQApplicationPacket(OP_TradeSkillCombine, 0); user->QueuePacket(outapp); @@ -554,7 +555,7 @@ void Object::HandleCombine(Client* user, const NewCombine_Struct* in_combine, Ob } if (success) { - if (player_event_logs.IsEventEnabled(PlayerEvent::COMBINE_SUCCESS)) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::COMBINE_SUCCESS)) { auto e = PlayerEvent::CombineEvent{ .recipe_id = spec.recipe_id, .recipe_name = spec.name, @@ -569,7 +570,7 @@ void Object::HandleCombine(Client* user, const NewCombine_Struct* in_combine, Ob } } else { - if (player_event_logs.IsEventEnabled(PlayerEvent::COMBINE_FAILURE)) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::COMBINE_FAILURE)) { auto e = PlayerEvent::CombineEvent{ .recipe_id = spec.recipe_id, .recipe_name = spec.name, @@ -642,7 +643,7 @@ void Object::HandleAutoCombine(Client* user, const RecipeAutoCombine_Struct* rac } } else if (spec.tradeskill == EQ::skills::SkillTinkering) { - if (user->GetRace() != GNOME) { + if (user->GetRace() != Race::Gnome) { user->Message(Chat::Red, "Only gnomes can tinker."); auto outapp = new EQApplicationPacket(OP_TradeSkillCombine, 0); user->QueuePacket(outapp); @@ -1131,7 +1132,7 @@ bool Client::TradeskillExecute(DBTradeskillRecipe_Struct *spec) { zone->random.Roll(aa_chance) ) { if (GetGM()) { - Message(Chat::White, "Your GM flag gives you a 100% chance to succeed in combining this tradeskill."); + Message(Chat::White, "Your GM flag gives you a 100%% chance to succeed in combining this tradeskill."); } success_modifier = 1; @@ -1149,6 +1150,7 @@ bool Client::TradeskillExecute(DBTradeskillRecipe_Struct *spec) { item = database.GetItem(itr->first); if (item) { + CheckItemDiscoverability(itr->first); SummonItem(itr->first, itr->second); if (GetGroup()) { entity_list.MessageGroup(this, true, Chat::Skills, "%s has successfully fashioned %s!", GetName(), item->Name); @@ -1172,13 +1174,6 @@ bool Client::TradeskillExecute(DBTradeskillRecipe_Struct *spec) { ); } - /* QS: Player_Log_Trade_Skill_Events */ - if (RuleB(QueryServ, PlayerLogTradeSkillEvents)) { - - std::string event_desc = StringFormat("Success :: fashioned recipe_id:%i tskillid:%i trivial:%i chance:%4.2f in zoneid:%i instid:%i", spec->recipe_id, spec->tradeskill, spec->trivial, chance, GetZoneID(), GetInstanceID()); - QServ->PlayerLogEvent(Player_Log_Trade_Skill_Events, CharacterID(), event_desc); - } - if (RuleB(TaskSystem, EnableTaskSystem)) { UpdateTasksForItem(TaskActivityType::TradeSkill, itr->first, itr->second); } @@ -1203,12 +1198,6 @@ bool Client::TradeskillExecute(DBTradeskillRecipe_Struct *spec) { } - /* QS: Player_Log_Trade_Skill_Events */ - if (RuleB(QueryServ, PlayerLogTradeSkillEvents)){ - std::string event_desc = StringFormat("Failed :: recipe_id:%i tskillid:%i trivial:%i chance:%4.2f in zoneid:%i instid:%i", spec->recipe_id, spec->tradeskill, spec->trivial, chance, GetZoneID(), GetInstanceID()); - QServ->PlayerLogEvent(Player_Log_Trade_Skill_Events, CharacterID(), event_desc); - } - itr = spec->onfail.begin(); while(itr != spec->onfail.end()) { //should we check these arguments? @@ -1246,15 +1235,18 @@ void Client::CheckIncreaseTradeskill(int16 bonusstat, int16 stat_modifier, float return; //not allowed to go higher. uint16 maxskill = MaxSkill(tradeskill); + float min_skill_up_chance = RuleR(Character, TradeskillUpMinChance); + min_skill_up_chance = std::max(min_skill_up_chance, 2.5f); + float chance_stage2 = 0; //A successfull combine doubles the stage1 chance for an skillup //Some tradeskill are harder than others. See above for more. float chance_stage1 = (bonusstat - stat_modifier) / (skillup_modifier * success_modifier); + chance_stage1 = std::max(min_skill_up_chance, chance_stage1); - //In stage2 the only thing that matters is your current unmodified skill. - //If you want to customize here you probbably need to implement your own - //formula instead of tweaking the below one. + //In stage2 the only thing that matters is your current unmodified skill + //and the Character:TradeskillUpMinChance rule. if (chance_stage1 > zone->random.Real(0, 99)) { if (current_raw_skill < 15) { //Always succeed @@ -1266,6 +1258,7 @@ void Client::CheckIncreaseTradeskill(int16 bonusstat, int16 stat_modifier, float //At skill 175, your chance of success falls linearly from 12.5% to 2.5% at skill 300. chance_stage2 = 12.5 - (.08 * (current_raw_skill - 175)); } + chance_stage2 = std::max(min_skill_up_chance, chance_stage2); } if (chance_stage2 > zone->random.Real(0, 99)) { diff --git a/zone/trading.cpp b/zone/trading.cpp index f963f7ca1..9415c9e01 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -320,40 +320,18 @@ void Client::ResetTrade() { } void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, std::list* event_details) { - if(tradingWith && tradingWith->IsClient()) { + if (!tradingWith) { + return; + } + + if (tradingWith->IsClient()) { Client * other = tradingWith->CastToClient(); - PlayerLogTrade_Struct * qs_audit = nullptr; - bool qs_log = false; if(other) { LogTrading("Finishing trade with client [{}]", other->GetName()); AddMoneyToPP(other->trade->cp, other->trade->sp, other->trade->gp, other->trade->pp, true); - // step 0: pre-processing - // QS code - if (RuleB(QueryServ, PlayerLogTrades) && event_entry && event_details) { - qs_audit = (PlayerLogTrade_Struct*)event_entry; - qs_log = true; - - if (finalizer) { - qs_audit->character_2_id = character_id; - - qs_audit->character_2_money.platinum = trade->pp; - qs_audit->character_2_money.gold = trade->gp; - qs_audit->character_2_money.silver = trade->sp; - qs_audit->character_2_money.copper = trade->cp; - } - else { - qs_audit->character_1_id = character_id; - - qs_audit->character_1_money.platinum = trade->pp; - qs_audit->character_1_money.gold = trade->gp; - qs_audit->character_1_money.silver = trade->sp; - qs_audit->character_1_money.copper = trade->cp; - } - } - // step 1: process bags for (int16 trade_slot = EQ::invslot::TRADE_BEGIN; trade_slot <= EQ::invslot::TRADE_END; ++trade_slot) { const EQ::ItemInstance* inst = m_inv[trade_slot]; @@ -366,61 +344,13 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st inst->GetItem()->NoDrop != 0 || CanTradeFVNoDropItem() || other == this - ) { + ) { int16 free_slot = other->GetInv().FindFreeSlotForTradeItem(inst); if (free_slot != INVALID_INDEX) { if (other->PutItemInInventory(free_slot, *inst, true)) { + inst->TransferOwnership(database, other->CharacterID()); LogTrading("Container [{}] ([{}]) successfully transferred, deleting from trade slot", inst->GetItem()->Name, inst->GetItem()->ID); - if (qs_log) { - auto detail = new PlayerLogTradeItemsEntry_Struct; - - detail->from_character_id = character_id; - detail->from_slot = trade_slot; - detail->to_character_id = other->CharacterID(); - detail->to_slot = free_slot; - detail->item_id = inst->GetID(); - detail->charges = 1; - detail->aug_1 = inst->GetAugmentItemID(1); - detail->aug_2 = inst->GetAugmentItemID(2); - detail->aug_3 = inst->GetAugmentItemID(3); - detail->aug_4 = inst->GetAugmentItemID(4); - detail->aug_5 = inst->GetAugmentItemID(5); - - event_details->push_back(detail); - - if (finalizer) - qs_audit->character_2_item_count += detail->charges; - else - qs_audit->character_1_item_count += detail->charges; - - for (uint8 sub_slot = EQ::invbag::SLOT_BEGIN; (sub_slot <= EQ::invbag::SLOT_END); ++sub_slot) { // this is to catch ALL items - const EQ::ItemInstance* bag_inst = inst->GetItem(sub_slot); - - if (bag_inst) { - detail = new PlayerLogTradeItemsEntry_Struct; - - detail->from_character_id = character_id; - detail->from_slot = EQ::InventoryProfile::CalcSlotId(trade_slot, sub_slot); - detail->to_character_id = other->CharacterID(); - detail->to_slot = EQ::InventoryProfile::CalcSlotId(free_slot, sub_slot); - detail->item_id = bag_inst->GetID(); - detail->charges = (!bag_inst->IsStackable() ? 1 : bag_inst->GetCharges()); - detail->aug_1 = bag_inst->GetAugmentItemID(1); - detail->aug_2 = bag_inst->GetAugmentItemID(2); - detail->aug_3 = bag_inst->GetAugmentItemID(3); - detail->aug_4 = bag_inst->GetAugmentItemID(4); - detail->aug_5 = bag_inst->GetAugmentItemID(5); - - event_details->push_back(detail); - - if (finalizer) - qs_audit->character_2_item_count += detail->charges; - else - qs_audit->character_1_item_count += detail->charges; - } - } - } } else { LogTrading("Transfer of container [{}] ([{}]) to [{}] failed, returning to giver", inst->GetItem()->Name, inst->GetItem()->ID, other->GetName()); @@ -480,34 +410,17 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st LogTrading("Transferring partial stack [{}] ([{}]) in slot [{}] to [{}]", inst->GetItem()->Name, inst->GetItem()->ID, trade_slot, other->GetName()); if (other->PutItemInInventory(partial_slot, *partial_inst, true)) { - LogTrading("Partial stack [{}] ([{}]) successfully transferred, deleting [{}] charges from trade slot", - inst->GetItem()->Name, inst->GetItem()->ID, (old_charges - inst->GetCharges())); - if (qs_log) { - auto detail = new PlayerLogTradeItemsEntry_Struct; - - detail->from_character_id = character_id; - detail->from_slot = trade_slot; - detail->to_character_id = other->CharacterID(); - detail->to_slot = partial_slot; - detail->item_id = inst->GetID(); - detail->charges = (old_charges - inst->GetCharges()); - detail->aug_1 = 0; - detail->aug_2 = 0; - detail->aug_3 = 0; - detail->aug_4 = 0; - detail->aug_5 = 0; - - event_details->push_back(detail); - - if (finalizer) - qs_audit->character_2_item_count += detail->charges; - else - qs_audit->character_1_item_count += detail->charges; - } + LogTrading( + "Partial stack [{}] ([{}]) successfully transferred, deleting [{}] charges from trade slot", + inst->GetItem()->Name, + inst->GetItem()->ID, + (old_charges - inst->GetCharges()) + ); + inst->TransferOwnership(database, other->CharacterID()); } else { LogTrading("Transfer of partial stack [{}] ([{}]) to [{}] failed, returning [{}] charges to trade slot", - inst->GetItem()->Name, inst->GetItem()->ID, other->GetName(), (old_charges - inst->GetCharges())); + inst->GetItem()->Name, inst->GetItem()->ID, other->GetName(), (old_charges - inst->GetCharges())); inst->SetCharges(old_charges); partial_inst->SetCharges(partial_charges); @@ -550,24 +463,6 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st inst->SetCharges(0); } - if (qs_log) { - auto detail = new PlayerLogTradeItemsEntry_Struct; - - detail->from_character_id = character_id; - detail->from_slot = trade_slot; - detail->to_character_id = character_id; - detail->to_slot = bias_slot; - detail->item_id = inst->GetID(); - detail->charges = (old_charges - inst->GetCharges()); - detail->aug_1 = 0; - detail->aug_2 = 0; - detail->aug_3 = 0; - detail->aug_4 = 0; - detail->aug_5 = 0; - - event_details->push_back(detail); - } - if (inst->GetCharges() == 0) { DeleteItemInInventory(trade_slot); break; @@ -589,57 +484,8 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st if (free_slot != INVALID_INDEX) { if (other->PutItemInInventory(free_slot, *inst, true)) { + inst->TransferOwnership(database, other->CharacterID()); LogTrading("Item [{}] ([{}]) successfully transferred, deleting from trade slot", inst->GetItem()->Name, inst->GetItem()->ID); - if (qs_log) { - auto detail = new PlayerLogTradeItemsEntry_Struct; - - detail->from_character_id = character_id; - detail->from_slot = trade_slot; - detail->to_character_id = other->CharacterID(); - detail->to_slot = free_slot; - detail->item_id = inst->GetID(); - detail->charges = (!inst->IsStackable() ? 1 : inst->GetCharges()); - detail->aug_1 = inst->GetAugmentItemID(1); - detail->aug_2 = inst->GetAugmentItemID(2); - detail->aug_3 = inst->GetAugmentItemID(3); - detail->aug_4 = inst->GetAugmentItemID(4); - detail->aug_5 = inst->GetAugmentItemID(5); - - event_details->push_back(detail); - - if (finalizer) - qs_audit->character_2_item_count += detail->charges; - else - qs_audit->character_1_item_count += detail->charges; - - // 'step 3' should never really see containers..but, just in case... - for (uint8 sub_slot = EQ::invbag::SLOT_BEGIN; (sub_slot <= EQ::invbag::SLOT_END); ++sub_slot) { // this is to catch ALL items - const EQ::ItemInstance* bag_inst = inst->GetItem(sub_slot); - - if (bag_inst) { - detail = new PlayerLogTradeItemsEntry_Struct; - - detail->from_character_id = character_id; - detail->from_slot = trade_slot; - detail->to_character_id = other->CharacterID(); - detail->to_slot = free_slot; - detail->item_id = bag_inst->GetID(); - detail->charges = (!bag_inst->IsStackable() ? 1 : bag_inst->GetCharges()); - detail->aug_1 = bag_inst->GetAugmentItemID(1); - detail->aug_2 = bag_inst->GetAugmentItemID(2); - detail->aug_3 = bag_inst->GetAugmentItemID(3); - detail->aug_4 = bag_inst->GetAugmentItemID(4); - detail->aug_5 = bag_inst->GetAugmentItemID(5); - - event_details->push_back(detail); - - if (finalizer) - qs_audit->character_2_item_count += detail->charges; - else - qs_audit->character_1_item_count += detail->charges; - } - } - } } else { LogTrading("Transfer of Item [{}] ([{}]) to [{}] failed, returning to giver", inst->GetItem()->Name, inst->GetItem()->ID, other->GetName()); @@ -663,85 +509,10 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st //Do not reset the trade here, done by the caller. } } - else if(tradingWith && tradingWith->IsNPC()) { - NPCHandinEventLog(trade, tradingWith->CastToNPC()); - - QSPlayerLogHandin_Struct* qs_audit = nullptr; - bool qs_log = false; - - // QS code - if(RuleB(QueryServ, PlayerLogTrades) && event_entry && event_details) { - // Currently provides only basic functionality. Calling method will also - // need to be modified before item returns and rewards can be logged. - qs_audit = (QSPlayerLogHandin_Struct*)event_entry; - qs_log = true; - - qs_audit->quest_id = 0; - qs_audit->char_id = character_id; - qs_audit->char_money.platinum = trade->pp; - qs_audit->char_money.gold = trade->gp; - qs_audit->char_money.silver = trade->sp; - qs_audit->char_money.copper = trade->cp; - qs_audit->char_count = 0; - qs_audit->npc_id = tradingWith->GetNPCTypeID(); - qs_audit->npc_money.platinum = 0; - qs_audit->npc_money.gold = 0; - qs_audit->npc_money.silver = 0; - qs_audit->npc_money.copper = 0; - qs_audit->npc_count = 0; - } - - if(qs_log) { // This can be incorporated below when revisions are made - for (int16 trade_slot = EQ::invslot::TRADE_BEGIN; trade_slot <= EQ::invslot::TRADE_NPC_END; ++trade_slot) { - const EQ::ItemInstance* trade_inst = m_inv[trade_slot]; - - if(trade_inst) { - auto detail = new QSHandinItems_Struct; - - strcpy(detail->action_type, "HANDIN"); - - detail->char_slot = trade_slot; - detail->item_id = trade_inst->GetID(); - detail->charges = (!trade_inst->IsStackable() ? 1 : trade_inst->GetCharges()); - detail->aug_1 = trade_inst->GetAugmentItemID(1); - detail->aug_2 = trade_inst->GetAugmentItemID(2); - detail->aug_3 = trade_inst->GetAugmentItemID(3); - detail->aug_4 = trade_inst->GetAugmentItemID(4); - detail->aug_5 = trade_inst->GetAugmentItemID(5); - - event_details->push_back(detail); - qs_audit->char_count += detail->charges; - - if (trade_inst->IsClassBag()) { - for (uint8 sub_slot = EQ::invbag::SLOT_BEGIN; sub_slot < trade_inst->GetItem()->BagSlots; ++sub_slot) { - const EQ::ItemInstance* trade_baginst = trade_inst->GetItem(sub_slot); - - if(trade_baginst) { - detail = new QSHandinItems_Struct; - - strcpy(detail->action_type, "HANDIN"); - - detail->char_slot = EQ::InventoryProfile::CalcSlotId(trade_slot, sub_slot); - detail->item_id = trade_baginst->GetID(); - detail->charges = (!trade_inst->IsStackable() ? 1 : trade_inst->GetCharges()); - detail->aug_1 = trade_baginst->GetAugmentItemID(1); - detail->aug_2 = trade_baginst->GetAugmentItemID(2); - detail->aug_3 = trade_baginst->GetAugmentItemID(3); - detail->aug_4 = trade_baginst->GetAugmentItemID(4); - detail->aug_5 = trade_baginst->GetAugmentItemID(5); - - event_details->push_back(detail); - qs_audit->char_count += detail->charges; - } - } - } - } - } - } + else if(tradingWith->IsNPC()) { bool quest_npc = false; if (parse->HasQuestSub(tradingWith->GetNPCTypeID(), EVENT_TRADE)) { - // This is a quest NPC quest_npc = true; } @@ -757,34 +528,14 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st if (RuleB(TaskSystem, EnableTaskSystem)) { if (UpdateTasksOnDeliver(items, *trade, tradingWith->CastToNPC())) { - if (!tradingWith->IsMoving()) + if (!tradingWith->IsMoving()) { tradingWith->FaceTarget(this); - - EVENT_ITEM_ScriptStopReturn(); - - } - } - - // Regardless of quest or non-quest NPC - No in combat trade completion - // is allowed. - if (tradingWith->CheckAggro(this)) - { - for (EQ::ItemInstance* inst : items) { - if (!inst || !inst->GetItem()) { - continue; } - - tradingWith->SayString(TRADE_BACK, GetCleanName()); - PushItemOnCursor(*inst, true); } - - items.clear(); } - // Only enforce trade rules if the NPC doesn't have an EVENT_TRADE - // subroutine. That overrides all. - else if (!quest_npc) - { - for (EQ::ItemInstance* inst : items) { + + if (!quest_npc) { + for (auto &inst: items) { if (!inst || !inst->GetItem()) { continue; } @@ -798,128 +549,141 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st } } - const EQ::ItemData* item = inst->GetItem(); - const bool is_pet = _CLIENTPET(tradingWith) && tradingWith->GetPetType()<=petOther; - const bool is_quest_npc = tradingWith->CastToNPC()->IsQuestNPC(); - const bool restrict_quest_items_to_quest_npc = RuleB(NPC, ReturnQuestItemsFromNonQuestNPCs); - const bool pets_can_take_quest_items = RuleB(Pets, CanTakeQuestItems); - const bool is_pet_and_can_have_nodrop_items = (RuleB(Pets, CanTakeNoDrop) && is_pet); - const bool is_pet_and_can_have_quest_items = (pets_can_take_quest_items && is_pet); - // if it was not a NO DROP or Attuned item (or if a GM is trading), let the NPC have it - if (GetGM() || - (!restrict_quest_items_to_quest_npc || (is_quest_npc && item->IsQuestItem()) || !item->IsQuestItem()) && // If rule is enabled, return any quest items given to non-quest NPCs - (((item->NoDrop != 0 && !inst->IsAttuned()) || is_pet_and_can_have_nodrop_items) && - ((!item->IsQuestItem() || is_pet_and_can_have_quest_items || !is_pet)))) { + auto with = tradingWith->CastToNPC(); + const EQ::ItemData *item = inst->GetItem(); + const bool is_pet = with->IsPetOwnerOfClientBot() || with->IsCharmedPet(); + if (is_pet && with->CanPetTakeItem(inst)) { // pets need to look inside bags and try to equip items found there if (item->IsClassBag() && item->BagSlots > 0) { - for (int16 bslot = EQ::invbag::SLOT_BEGIN; bslot < item->BagSlots; bslot++) { + // if an item inside the bag can't be given to the pet, keep the bag + bool keep_bag = false; + int item_count = 0; + for (int16 bslot = EQ::invbag::SLOT_BEGIN; bslot < item->BagSlots; bslot++) { const EQ::ItemInstance *baginst = inst->GetItem(bslot); - if (baginst) { - const EQ::ItemData *bagitem = baginst->GetItem(); - if (bagitem && (GetGM() || - (!restrict_quest_items_to_quest_npc || - (is_quest_npc && bagitem->IsQuestItem()) || !bagitem->IsQuestItem()) && - // If rule is enabled, return any quest items given to non-quest NPCs (inside bags) - (bagitem->NoDrop != 0 && !baginst->IsAttuned()) && - ((is_pet && (!bagitem->IsQuestItem() || pets_can_take_quest_items) || - !is_pet)))) { - - if (GetGM()) { - const std::string& item_link = database.CreateItemLink(bagitem->ID); - Message( - Chat::White, - fmt::format( - "Your GM flag allows you to give {} to {}.", - item_link, - GetTargetDescription(tradingWith) - ).c_str() - ); - } - - auto lde = LootdropEntriesRepository::NewNpcEntity(); - lde.equip_item = 1; - lde.item_charges = static_cast(baginst->GetCharges()); - - tradingWith->CastToNPC()->AddLootDrop( - bagitem, - lde, - true - ); - // Return quest items being traded to non-quest NPC when the rule is true - } else if (restrict_quest_items_to_quest_npc && (!is_quest_npc && bagitem->IsQuestItem())) { - tradingWith->SayString(TRADE_BACK, GetCleanName()); - PushItemOnCursor(*baginst, true); - Message(Chat::Red, "You can only trade quest items to quest NPCs."); - // Return quest items being traded to player pet when not allowed - } else if (is_pet && bagitem->IsQuestItem() && !pets_can_take_quest_items) { - tradingWith->SayString(TRADE_BACK, GetCleanName()); - PushItemOnCursor(*baginst, true); - Message(Chat::Red, "You cannot trade quest items with your pet."); - } else if (RuleB(NPC, ReturnNonQuestNoDropItems)) { - tradingWith->SayString(TRADE_BACK, GetCleanName()); - PushItemOnCursor(*baginst, true); - } + if (baginst && baginst->GetItem() && with->CanPetTakeItem(baginst)) { + // add item to pet's inventory + auto lde = LootdropEntriesRepository::NewNpcEntity(); + lde.equip_item = 1; + lde.item_charges = static_cast(baginst->GetCharges()); + with->AddLootDrop(baginst->GetItem(), lde, true); + inst->DeleteItem(bslot); + item_count++; + } + else { + keep_bag = true; } } - } else { + + // add item to pet's inventory + if (!keep_bag || item_count == 0) { + auto lde = LootdropEntriesRepository::NewNpcEntity(); + lde.equip_item = 1; + lde.item_charges = static_cast(inst->GetCharges()); + with->AddLootDrop(item, lde, true); + inst = nullptr; + } + } + else { + // add item to pet's inventory auto lde = LootdropEntriesRepository::NewNpcEntity(); lde.equip_item = 1; lde.item_charges = static_cast(inst->GetCharges()); - - tradingWith->CastToNPC()->AddLootDrop( - item, - lde, - true - ); + with->AddLootDrop(item, lde, true); + inst = nullptr; } } - // Return quest items being traded to non-quest NPC when the rule is true - else if (restrict_quest_items_to_quest_npc && (!is_quest_npc && item->IsQuestItem())) { - tradingWith->SayString(TRADE_BACK, GetCleanName()); - PushItemOnCursor(*inst, true); - Message(Chat::Red, "You can only trade quest items to quest NPCs."); - } - // Return quest items being traded to player pet when not allowed - else if (is_pet && item->IsQuestItem()) { - tradingWith->SayString(TRADE_BACK, GetCleanName()); - PushItemOnCursor(*inst, true); - Message(Chat::Red, "You cannot trade quest items with your pet."); - } - // Return NO DROP and Attuned items being handed into a non-quest NPC if the rule is true - else if (RuleB(NPC, ReturnNonQuestNoDropItems)) { - tradingWith->SayString(TRADE_BACK, GetCleanName()); - PushItemOnCursor(*inst, true); - } } } - char temp1[100] = { 0 }; - char temp2[100] = { 0 }; - snprintf(temp1, 100, "copper.%d", tradingWith->GetNPCTypeID()); - snprintf(temp2, 100, "%u", trade->cp); - parse->AddVar(temp1, temp2); - snprintf(temp1, 100, "silver.%d", tradingWith->GetNPCTypeID()); - snprintf(temp2, 100, "%u", trade->sp); - parse->AddVar(temp1, temp2); - snprintf(temp1, 100, "gold.%d", tradingWith->GetNPCTypeID()); - snprintf(temp2, 100, "%u", trade->gp); - parse->AddVar(temp1, temp2); - snprintf(temp1, 100, "platinum.%d", tradingWith->GetNPCTypeID()); - snprintf(temp2, 100, "%u", trade->pp); - parse->AddVar(temp1, temp2); + std::string currencies[] = {"copper", "silver", "gold", "platinum"}; + int32 amounts[] = {trade->cp, trade->sp, trade->gp, trade->pp}; - if(tradingWith->GetAppearance() != eaDead) { + for (int i = 0; i < 4; ++i) { + parse->AddVar( + fmt::format("{}.{}", currencies[i], tradingWith->GetNPCTypeID()), + fmt::format("{}", amounts[i]) + ); + } + + if (tradingWith->GetAppearance() != eaDead) { tradingWith->FaceTarget(this); } - if (parse->HasQuestSub(tradingWith->GetNPCTypeID(), EVENT_TRADE)) { - std::vector item_list(items.begin(), items.end()); - parse->EventNPC(EVENT_TRADE, tradingWith->CastToNPC(), this, "", 0, &item_list); + // we cast to any to pass through the quest event system + std::vector item_list(items.begin(), items.end()); + for (EQ::ItemInstance *inst: items) { + if (!inst || !inst->GetItem()) { + continue; + } + item_list.emplace_back(inst); } - for(int i = 0; i < 4; ++i) { - if(insts[i]) { - safe_delete(insts[i]); + auto handin_npc = tradingWith->CastToNPC(); + + m_external_handin_money_returned = {}; + m_external_handin_items_returned = {}; + bool has_aggro = tradingWith->CheckAggro(this); + if (parse->HasQuestSub(tradingWith->GetNPCTypeID(), EVENT_TRADE) && !has_aggro) { + // This CheckHandin call enables eq.handin and quest::handin to recognize the hand-in context. + // It initializes the first hand-in bucket, which is then reused for the EVENT_TRADE subroutine. + std::map handin = { + {"copper", trade->cp}, + {"silver", trade->sp}, + {"gold", trade->gp}, + {"platinum", trade->pp} + }; + + for (EQ::ItemInstance *inst: items) { + if (!inst || !inst->GetItem()) { + continue; + } + + std::string item_id = fmt::format("{}", inst->GetItem()->ID); + handin[item_id] += inst->GetCharges(); + } + + handin_npc->CheckHandin(this, handin, {}, items); + + parse->EventNPC(EVENT_TRADE, tradingWith->CastToNPC(), this, "", 0, &item_list); + LogNpcHandinDetail("EVENT_TRADE triggered for NPC [{}]", tradingWith->GetNPCTypeID()); + } + + // this is a catch-all return for items that weren't consumed by the EVENT_TRADE subroutine + // it's possible we have a quest NPC that doesn't have an EVENT_TRADE subroutine + // we can't double fire the ReturnHandinItems() event, so we need to check if it's already been processed from EVENT_TRADE + if (!handin_npc->HasProcessedHandinReturn()) { + if (!handin_npc->HandinStarted()) { + LogNpcHandinDetail("EVENT_TRADE did not process handin, calling ReturnHandinItems() for NPC [{}]", tradingWith->GetNPCTypeID()); + std::map handin = { + {"copper", trade->cp}, + {"silver", trade->sp}, + {"gold", trade->gp}, + {"platinum", trade->pp} + }; + + for (EQ::ItemInstance *inst: items) { + if (!inst || !inst->GetItem()) { + continue; + } + + std::string item_id = fmt::format("{}", inst->GetItem()->ID); + handin[item_id] += inst->GetCharges(); + } + + handin_npc->CheckHandin(this, handin, {}, items); + } + + if (RuleB(Items, AlwaysReturnHandins)) { + handin_npc->ReturnHandinItems(this); + LogNpcHandin("ReturnHandinItems called for NPC [{}]", handin_npc->GetNPCTypeID()); + } + } + + handin_npc->ResetHandin(); + + for (auto &inst: insts) { + if (inst) { + safe_delete(inst); } } } @@ -1093,15 +857,17 @@ void Client::TraderStartTrader(const EQApplicationPacket *app) inst->SetPrice(in->item_cost[i]); TraderRepository::Trader trader_item{}; - trader_item.id = 0; - trader_item.char_entity_id = GetID(); - trader_item.char_id = CharacterID(); - trader_item.char_zone_id = GetZoneID(); - trader_item.item_charges = inst->GetCharges() == 0 ? 1 : inst->GetCharges(); - trader_item.item_cost = inst->GetPrice(); - trader_item.item_id = inst->GetID(); - trader_item.item_sn = in->serial_number[i]; - trader_item.slot_id = i; + trader_item.id = 0; + trader_item.char_entity_id = GetID(); + trader_item.char_id = CharacterID(); + trader_item.char_zone_id = GetZoneID(); + trader_item.char_zone_instance_id = GetInstanceID(); + trader_item.item_charges = inst->GetCharges() == 0 ? 1 : inst->GetCharges(); + trader_item.item_cost = inst->GetPrice(); + trader_item.item_id = inst->GetID(); + trader_item.item_sn = in->serial_number[i]; + trader_item.slot_id = i; + trader_item.listing_date = time(nullptr); if (inst->IsAugmented()) { auto augs = inst->GetAugmentIDs(); trader_item.aug_slot_1 = augs.at(0); @@ -1585,12 +1351,12 @@ void Client::BuyTraderItem(TraderBuy_Struct *tbs, Client *Trader, const EQApplic return; } - auto in = (TraderBuy_Struct *) app->pBuffer; - auto outapp = std::make_unique(OP_Trader, sizeof(TraderBuy_Struct)); - auto outtbs = (TraderBuy_Struct *) outapp->pBuffer; - outtbs->item_id = tbs->item_id; + auto outapp = std::make_unique(OP_Trader, static_cast(sizeof(TraderBuy_Struct))); + auto outtbs = (TraderBuy_Struct *) outapp->pBuffer; + outtbs->item_id = tbs->item_id; + const EQ::ItemInstance *buy_item = nullptr; - uint32 item_id = 0; + uint32 item_id = 0; if (ClientVersion() >= EQ::versions::ClientVersion::RoF) { tbs->item_id = Strings::ToUnsignedBigInt(tbs->serial_number); @@ -1699,35 +1465,49 @@ void Client::BuyTraderItem(TraderBuy_Struct *tbs, Client *Trader, const EQApplic Trader->AddMoneyToPP(copper, silver, gold, platinum, true); - if (player_event_logs.IsEventEnabled(PlayerEvent::TRADER_PURCHASE)) { - auto e = PlayerEvent::TraderPurchaseEvent{ - .item_id = buy_item->GetID(), - .item_name = buy_item->GetItem()->Name, - .trader_id = Trader->CharacterID(), - .trader_name = Trader->GetCleanName(), - .price = tbs->price, - .charges = outtbs->quantity, - .total_cost = (tbs->price * outtbs->quantity), - .player_money_balance = GetCarriedMoney(), - }; + if (buy_item && PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::TRADER_PURCHASE)) { + auto e = PlayerEvent::TraderPurchaseEvent{ + .item_id = buy_item->GetID(), + .augment_1_id = buy_item->GetAugmentItemID(0), + .augment_2_id = buy_item->GetAugmentItemID(1), + .augment_3_id = buy_item->GetAugmentItemID(2), + .augment_4_id = buy_item->GetAugmentItemID(3), + .augment_5_id = buy_item->GetAugmentItemID(4), + .augment_6_id = buy_item->GetAugmentItemID(5), + .item_name = buy_item->GetItem()->Name, + .trader_id = Trader->CharacterID(), + .trader_name = Trader->GetCleanName(), + .price = tbs->price, + .quantity = outtbs->quantity, + .charges = buy_item->GetCharges(), + .total_cost = (tbs->price * outtbs->quantity), + .player_money_balance = GetCarriedMoney(), + }; - RecordPlayerEventLog(PlayerEvent::TRADER_PURCHASE, e); - } + RecordPlayerEventLog(PlayerEvent::TRADER_PURCHASE, e); + } - if (player_event_logs.IsEventEnabled(PlayerEvent::TRADER_SELL)) { - auto e = PlayerEvent::TraderSellEvent{ - .item_id = buy_item->GetID(), - .item_name = buy_item->GetItem()->Name, - .buyer_id = CharacterID(), - .buyer_name = GetCleanName(), - .price = tbs->price, - .charges = outtbs->quantity, - .total_cost = (tbs->price * outtbs->quantity), - .player_money_balance = Trader->GetCarriedMoney(), - }; + if (buy_item && PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::TRADER_SELL)) { + auto e = PlayerEvent::TraderSellEvent{ + .item_id = buy_item->GetID(), + .augment_1_id = buy_item->GetAugmentItemID(0), + .augment_2_id = buy_item->GetAugmentItemID(1), + .augment_3_id = buy_item->GetAugmentItemID(2), + .augment_4_id = buy_item->GetAugmentItemID(3), + .augment_5_id = buy_item->GetAugmentItemID(4), + .augment_6_id = buy_item->GetAugmentItemID(5), + .item_name = buy_item->GetItem()->Name, + .buyer_id = CharacterID(), + .buyer_name = GetCleanName(), + .price = tbs->price, + .quantity = outtbs->quantity, + .charges = buy_item->GetCharges(), + .total_cost = (tbs->price * outtbs->quantity), + .player_money_balance = Trader->GetCarriedMoney(), + }; - RecordPlayerEventLogWithClient(Trader, PlayerEvent::TRADER_SELL, e); - } + RecordPlayerEventLogWithClient(Trader, PlayerEvent::TRADER_SELL, e); + } LogTrading("Trader Received: [{}] Platinum, [{}] Gold, [{}] Silver, [{}] Copper", platinum, gold, silver, copper); ReturnTraderReq(app, outtbs->quantity, item_id); @@ -1777,15 +1557,15 @@ void Client::BuyTraderItem(TraderBuy_Struct *tbs, Client *Trader, const EQApplic void Client::SendBazaarWelcome() { - const auto results = TraderRepository::GetWelcomeData(database); - auto outapp = std::make_unique(OP_BazaarSearch, sizeof(BazaarWelcome_Struct)); - auto data = (BazaarWelcome_Struct *) outapp->pBuffer; + const auto results = TraderRepository::GetWelcomeData(database); + EQApplicationPacket outapp(OP_BazaarSearch, static_cast(sizeof(BazaarWelcome_Struct))); + auto data = (BazaarWelcome_Struct *) outapp.pBuffer; - data->action = BazaarWelcome; - data->traders = results.count_of_traders; - data->items = results.count_of_items; + data->action = BazaarWelcome; + data->traders = results.count_of_traders; + data->items = results.count_of_items; - QueuePacket(outapp.get()); + QueuePacket(&outapp); } void Client::SendBarterWelcome() @@ -1796,12 +1576,19 @@ void Client::SendBarterWelcome() void Client::DoBazaarSearch(BazaarSearchCriteria_Struct search_criteria) { - auto results = Bazaar::GetSearchResults(database, search_criteria, GetZoneID()); + std::vector results = Bazaar::GetSearchResults( + database, + content_db, + search_criteria, + GetZoneID(), + GetInstanceID() + ); if (results.empty()) { SendBazaarDone(GetID()); return; } + SetTraderTransactionDate(); std::stringstream ss{}; cereal::BinaryOutputArchive ar(ss); ar(results); @@ -1818,317 +1605,6 @@ void Client::DoBazaarSearch(BazaarSearchCriteria_Struct search_criteria) SendBazaarDeliveryCosts(); } -void Client::SendBazaarResults( - uint32 trader_id, - uint32 in_class, - uint32 in_race, - uint32 item_stat, - uint32 item_slot, - uint32 item_type, - char item_name[64], - uint32 min_price, - uint32 max_price -) -{ - std::string search_values = " COUNT(item_id), trader.*, items.name "; - std::string search_criteria = " WHERE trader.item_id = items.id "; - - if (trader_id > 0) { - Client *trader = entity_list.GetClientByID(trader_id); - - if (trader) { - search_criteria.append(StringFormat(" AND trader.char_id = %i", trader->CharacterID())); - } - } - - if (min_price != 0) { - search_criteria.append(StringFormat(" AND trader.item_cost >= %i", min_price)); - } - - if (max_price != 0) { - search_criteria.append(StringFormat(" AND trader.item_cost <= %i", max_price)); - } - - if (strlen(item_name) > 0) { - char *safeName = RemoveApostrophes(item_name); - search_criteria.append(StringFormat(" AND items.name LIKE '%%%s%%'", safeName)); - safe_delete_array(safeName); - } - - if (in_class != 0xFFFFFFFF) { - search_criteria.append(StringFormat(" AND MID(REVERSE(BIN(items.classes)), %i, 1) = 1", in_class)); - } - - if (in_race != 0xFFFFFFFF) { - search_criteria.append(StringFormat(" AND MID(REVERSE(BIN(items.races)), %i, 1) = 1", in_race)); - } - - if (item_slot != 0xFFFFFFFF) { - search_criteria.append(StringFormat(" AND MID(REVERSE(BIN(items.slots)), %i, 1) = 1", item_slot + 1)); - } - - switch (item_type) { - case 0xFFFFFFFF: - break; - case 0: - // 1H Slashing - search_criteria.append(" AND items.itemtype = 0 AND damage > 0"); - break; - case 31: - search_criteria.append(" AND items.itemclass = 2"); - break; - case 46: - search_criteria.append(" AND items.scrolleffect > 0 AND items.scrolleffect < 65000"); - break; - case 47: - search_criteria.append(" AND items.worneffect = 998"); - break; - case 48: - search_criteria.append(" AND items.worneffect >= 1298 AND items.worneffect <= 1307"); - break; - case 49: - search_criteria.append(" AND items.focuseffect > 0"); - break; - - default: - search_criteria.append(StringFormat(" AND items.itemtype = %i", item_type)); - } - - switch (item_stat) { - - case STAT_AC: - search_criteria.append(" AND items.ac > 0"); - search_values.append(", items.ac"); - break; - - case STAT_AGI: - search_criteria.append(" AND items.aagi > 0"); - search_values.append(", items.aagi"); - break; - - case STAT_CHA: - search_criteria.append(" AND items.acha > 0"); - search_values.append(", items.acha"); - break; - - case STAT_DEX: - search_criteria.append(" AND items.adex > 0"); - search_values.append(", items.adex"); - break; - - case STAT_INT: - search_criteria.append(" AND items.aint > 0"); - search_values.append(", items.aint"); - break; - - case STAT_STA: - search_criteria.append(" AND items.asta > 0"); - search_values.append(", items.asta"); - break; - - case STAT_STR: - search_criteria.append(" AND items.astr > 0"); - search_values.append(", items.astr"); - break; - - case STAT_WIS: - search_criteria.append(" AND items.awis > 0"); - search_values.append(", items.awis"); - break; - - case STAT_COLD: - search_criteria.append(" AND items.cr > 0"); - search_values.append(", items.cr"); - break; - - case STAT_DISEASE: - search_criteria.append(" AND items.dr > 0"); - search_values.append(", items.dr"); - break; - - case STAT_FIRE: - search_criteria.append(" AND items.fr > 0"); - search_values.append(", items.fr"); - break; - - case STAT_MAGIC: - search_criteria.append(" AND items.mr > 0"); - search_values.append(", items.mr"); - break; - - case STAT_POISON: - search_criteria.append(" AND items.pr > 0"); - search_values.append(", items.pr"); - break; - - case STAT_HP: - search_criteria.append(" AND items.hp > 0"); - search_values.append(", items.hp"); - break; - - case STAT_MANA: - search_criteria.append(" AND items.mana > 0"); - search_values.append(", items.mana"); - break; - - case STAT_ENDURANCE: - search_criteria.append(" AND items.endur > 0"); - search_values.append(", items.endur"); - break; - - case STAT_ATTACK: - search_criteria.append(" AND items.attack > 0"); - search_values.append(", items.attack"); - break; - - case STAT_HP_REGEN: - search_criteria.append(" AND items.regen > 0"); - search_values.append(", items.regen"); - break; - - case STAT_MANA_REGEN: - search_criteria.append(" AND items.manaregen > 0"); - search_values.append(", items.manaregen"); - break; - - case STAT_HASTE: - search_criteria.append(" AND items.haste > 0"); - search_values.append(", items.haste"); - break; - - case STAT_DAMAGE_SHIELD: - search_criteria.append(" AND items.damageshield > 0"); - search_values.append(", items.damageshield"); - break; - - default: - search_values.append(", 0"); - break; - } - - std::string query = StringFormat( - "SELECT %s, SUM(charges), items.stackable " - "FROM trader, items %s GROUP BY items.id, charges, char_id LIMIT %i", - search_values.c_str(), - search_criteria.c_str(), - RuleI(Bazaar, MaxSearchResults) - ); - - auto results = database.QueryDatabase(query); - - if (!results.Success()) { - return; - } - - LogTrading("SRCH: [{}]", query.c_str()); - - int Size = 0; - uint32 ID = 0; - - if (results.RowCount() == static_cast(RuleI(Bazaar, MaxSearchResults))) { - Message( - Chat::Yellow, - "Your search reached the limit of %i results. Please narrow your search down by selecting more options.", - RuleI(Bazaar, MaxSearchResults)); - } - - if (results.RowCount() == 0) { - auto outapp2 = new EQApplicationPacket(OP_BazaarSearch, sizeof(BazaarReturnDone_Struct)); - BazaarReturnDone_Struct *brds = (BazaarReturnDone_Struct *) outapp2->pBuffer; - brds->TraderID = ID; - brds->Type = BazaarSearchDone; - brds->Unknown008 = 0xFFFFFFFF; - brds->Unknown012 = 0xFFFFFFFF; - brds->Unknown016 = 0xFFFFFFFF; - QueuePacket(outapp2); - safe_delete(outapp2); - return; - } - - Size = results.RowCount() * sizeof(BazaarSearchResults_Struct); - auto buffer = new uchar[Size]; - uchar *bufptr = buffer; - memset(buffer, 0, Size); - - int Action = BazaarSearchResults; - uint32 Cost = 0; - int32 SerialNumber = 0; - char temp_buffer[64] = {0}; - int Count = 0; - uint32 StatValue = 0; - - for (auto &row = results.begin(); row != results.end(); ++row) { - VARSTRUCT_ENCODE_TYPE(uint32, bufptr, Action); - Count = Strings::ToInt(row[0]); - VARSTRUCT_ENCODE_TYPE(uint32, bufptr, Count); - SerialNumber = Strings::ToInt(row[3]); - VARSTRUCT_ENCODE_TYPE(int32, bufptr, SerialNumber); - Client *Trader2 = entity_list.GetClientByCharID(Strings::ToInt(row[1])); - if (Trader2) { - ID = Trader2->GetID(); - VARSTRUCT_ENCODE_TYPE(uint32, bufptr, ID); - } - else { - LogTrading("Unable to find trader: [{}]\n", Strings::ToInt(row[1])); - VARSTRUCT_ENCODE_TYPE(uint32, bufptr, 0); - } - Cost = Strings::ToInt(row[5]); - VARSTRUCT_ENCODE_TYPE(uint32, bufptr, Cost); - StatValue = Strings::ToInt(row[8]); - VARSTRUCT_ENCODE_TYPE(uint32, bufptr, StatValue); - bool Stackable = Strings::ToInt(row[10]); - if (Stackable) { - int Charges = Strings::ToInt(row[9]); - sprintf(temp_buffer, "%s(%i)", row[7], Charges); - } - else { - sprintf(temp_buffer, "%s(%i)", row[7], Count); - } - - memcpy(bufptr, &temp_buffer, strlen(temp_buffer)); - - bufptr += 64; - - // Extra fields for SoD+ - // - if (Trader2) { - sprintf(temp_buffer, "%s", Trader2->GetName()); - } - else { - sprintf(temp_buffer, "Unknown"); - } - - memcpy(bufptr, &temp_buffer, strlen(temp_buffer)); - - bufptr += 64; - - VARSTRUCT_ENCODE_TYPE(uint32, bufptr, Strings::ToInt(row[1])); // ItemID - } - - auto outapp = new EQApplicationPacket(OP_BazaarSearch, Size); - - memcpy(outapp->pBuffer, buffer, Size); - - QueuePacket(outapp); - - safe_delete(outapp); - safe_delete_array(buffer); - - auto outapp2 = new EQApplicationPacket(OP_BazaarSearch, sizeof(BazaarReturnDone_Struct)); - BazaarReturnDone_Struct *brds = (BazaarReturnDone_Struct *) outapp2->pBuffer; - - brds->TraderID = ID; - brds->Type = BazaarSearchDone; - - brds->Unknown008 = 0xFFFFFFFF; - brds->Unknown012 = 0xFFFFFFFF; - brds->Unknown016 = 0xFFFFFFFF; - - QueuePacket(outapp2); - - safe_delete(outapp2); -} - static void UpdateTraderCustomerItemsAdded( uint32 customer_id, std::vector trader_items, @@ -2322,7 +1798,10 @@ void Client::SendBuyerResults(BarterSearchRequest_Struct& bsr) { ar(results); } - auto packet = std::make_unique(OP_BuyerItems, ss.str().length() + sizeof(BuyerGeneric_Struct)); + auto packet = std::make_unique( + OP_BuyerItems, + static_cast(ss.str().length()) + static_cast(sizeof(BuyerGeneric_Struct)) + ); auto emu = (BuyerGeneric_Struct *) packet->pBuffer; emu->action = Barter_BuyerSearch; @@ -2375,7 +1854,10 @@ void Client::ShowBuyLines(const EQApplicationPacket *app) { ar(l); } - auto packet = std::make_unique(OP_BuyerItems, ss.str().length() + sizeof(BuyerGeneric_Struct)); + auto packet = std::make_unique( + OP_BuyerItems, + static_cast(ss.str().length()) + static_cast(sizeof(BuyerGeneric_Struct)) + ); auto emu = (BuyerGeneric_Struct *) packet->pBuffer; emu->action = Barter_BuyerInspectBegin; @@ -2418,6 +1900,13 @@ void Client::SellToBuyer(const EQApplicationPacket *app) break; } + if (sell_line.purchase_method == BarterInBazaar && buyer->IsThereACustomer()) { + auto customer = entity_list.GetClientByID(buyer->GetCustomerID()); + if (customer) { + customer->CancelBuyerTradeWindow(); + } + } + if (!DoBarterBuyerChecks(sell_line)) { return; }; @@ -2506,10 +1995,10 @@ void Client::SellToBuyer(const EQApplicationPacket *app) } uint64 total_cost = (uint64) sell_line.item_cost * (uint64) sell_line.seller_quantity; - AddMoneyToPPWithOverflow(total_cost, false); - buyer->TakeMoneyFromPPWithOverFlow(total_cost, false); + AddMoneyToPP(total_cost, false); + buyer->TakeMoneyFromPP(total_cost, false); - if (player_event_logs.IsEventEnabled(PlayerEvent::BARTER_TRANSACTION)) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::BARTER_TRANSACTION)) { PlayerEvent::BarterTransaction e{}; e.status = "Successful Barter Transaction"; e.item_id = sell_line.item_id; @@ -2592,7 +2081,7 @@ void Client::SellToBuyer(const EQApplicationPacket *app) auto server_packet = std::make_unique( ServerOP_BuyerMessaging, - sizeof(BuyerMessaging_Struct) + static_cast(sizeof(BuyerMessaging_Struct)) ); auto data = (BuyerMessaging_Struct *) server_packet->pBuffer; @@ -2640,7 +2129,10 @@ void Client::SendBuyerPacket(Client* Buyer) { void Client::ToggleBuyerMode(bool status) { - auto outapp = std::make_unique(OP_Barter, sizeof(BuyerSetAppearance_Struct)); + auto outapp = std::make_unique( + OP_Barter, + static_cast(sizeof(BuyerSetAppearance_Struct)) + ); auto data = (BuyerSetAppearance_Struct *) outapp->pBuffer; data->action = Barter_BuyerAppearance; @@ -2836,8 +2328,7 @@ void Client::ModifyBuyLine(const EQApplicationPacket *app) auto packet = std::make_unique( OP_BuyerItems, - ss_customer.str().length() + - sizeof(BuyerGeneric_Struct) + static_cast(ss_customer.str().length()) + static_cast(sizeof(BuyerGeneric_Struct)) ); auto emu = (BuyerGeneric_Struct *) packet->pBuffer; @@ -2940,9 +2431,11 @@ void Client::SendBecomeTrader(BazaarTraderBarterActions action, uint32 entity_id auto outapp = new EQApplicationPacket(OP_BecomeTrader, sizeof(BecomeTrader_Struct)); auto data = (BecomeTrader_Struct *) outapp->pBuffer; - data->action = action; - data->entity_id = trader->GetID(); - data->trader_id = trader->CharacterID(); + data->action = action; + data->entity_id = trader->GetID(); + data->trader_id = trader->CharacterID(); + data->zone_id = trader->GetZoneID(); + data->zone_instance_id = trader->GetInstanceID(); strn0cpy(data->trader_name, trader->GetCleanName(), sizeof(data->trader_name)); QueuePacket(outapp); @@ -3088,14 +2581,16 @@ void Client::TraderPriceUpdate(const EQApplicationPacket *app) auto item_detail = FindTraderItemBySerialNumber(newgis->serial_number[i]); TraderRepository::Trader trader_item{}; - trader_item.id = 0; - trader_item.char_entity_id = GetID(); - trader_item.char_id = CharacterID(); - trader_item.char_zone_id = GetZoneID(); - trader_item.item_charges = newgis->charges[i]; - trader_item.item_cost = tpus->NewPrice; - trader_item.item_id = newgis->items[i]; - trader_item.item_sn = newgis->serial_number[i]; + trader_item.id = 0; + trader_item.char_entity_id = GetID(); + trader_item.char_id = CharacterID(); + trader_item.char_zone_id = GetZoneID(); + trader_item.char_zone_instance_id = GetInstanceID(); + trader_item.item_charges = newgis->charges[i]; + trader_item.item_cost = tpus->NewPrice; + trader_item.item_id = newgis->items[i]; + trader_item.item_sn = newgis->serial_number[i]; + trader_item.listing_date = time(nullptr); if (item_detail->IsAugmented()) { auto augs = item_detail->GetAugmentIDs(); trader_item.aug_slot_1 = augs.at(0); @@ -3215,7 +2710,43 @@ void Client::SendBulkBazaarTraders() return; } - auto results = TraderRepository::GetDistinctTraders(database); + TraderRepository::BulkTraders_Struct results{}; + + if (RuleB(Bazaar, UseAlternateBazaarSearch)) + { + if (GetZoneID() == Zones::BAZAAR) { + results = TraderRepository::GetDistinctTraders(database, GetInstanceID()); + } + + uint32 number = 1; + auto shards = CharacterDataRepository::GetInstanceZonePlayerCounts(database, Zones::BAZAAR); + for (auto const &shard: shards) { + if (GetZoneID() != Zones::BAZAAR || (GetZoneID() == Zones::BAZAAR && GetInstanceID() != shard.instance_id)) { + + TraderRepository::DistinctTraders_Struct t{}; + t.entity_id = 0; + t.trader_id = TraderRepository::TRADER_CONVERT_ID + shard.instance_id; + t.trader_name = fmt::format("Bazaar Shard {}", number); + t.zone_id = Zones::BAZAAR; + t.zone_instance_id = shard.instance_id; + results.count += 1; + results.name_length += t.trader_name.length() + 1; + results.traders.push_back(t); + } + + number++; + } + } + else { + results = TraderRepository::GetDistinctTraders( + database, + GetInstanceID(), + EQ::constants::StaticLookup(ClientVersion())->BazaarTraderLimit + ); + } + + SetTraderCount(results.count); + auto p_size = 4 + 12 * results.count + results.name_length; auto buffer = std::make_unique(p_size); memset(buffer.get(), 0, p_size); @@ -3224,7 +2755,8 @@ void Client::SendBulkBazaarTraders() VARSTRUCT_ENCODE_TYPE(uint32, bufptr, results.count); for (auto t : results.traders) { - VARSTRUCT_ENCODE_TYPE(uint32, bufptr, t.zone_id); + VARSTRUCT_ENCODE_TYPE(uint16, bufptr, t.zone_id); + VARSTRUCT_ENCODE_TYPE(uint16, bufptr, t.zone_instance_id); VARSTRUCT_ENCODE_TYPE(uint32, bufptr, t.trader_id); VARSTRUCT_ENCODE_TYPE(uint32, bufptr, t.entity_id); VARSTRUCT_ENCODE_STRING(bufptr, t.trader_name.c_str()); @@ -3236,8 +2768,28 @@ void Client::SendBulkBazaarTraders() QueuePacket(outapp.get()); } -void Client::DoBazaarInspect(const BazaarInspect_Struct &in) +void Client::DoBazaarInspect(BazaarInspect_Struct &in) { + if (RuleB(Bazaar, UseAlternateBazaarSearch)) { + if (in.trader_id >= TraderRepository::TRADER_CONVERT_ID) { + auto trader = TraderRepository::GetTraderByInstanceAndSerialnumber( + database, + in.trader_id - TraderRepository::TRADER_CONVERT_ID, + fmt::format("{}", in.serial_number).c_str() + ); + + if (!trader.trader_id) { + LogTrading("Unable to convert trader id for {} and serial number {}. Trader Buy aborted.", + in.trader_id - TraderRepository::TRADER_CONVERT_ID, + in.serial_number + ); + return; + } + + in.trader_id = trader.trader_id; + } + } + auto items = TraderRepository::GetWhere( database, fmt::format("`char_id` = '{}' AND `item_sn` = '{}'", in.trader_id, in.serial_number) ); @@ -3269,7 +2821,10 @@ void Client::DoBazaarInspect(const BazaarInspect_Struct &in) void Client::SendBazaarDeliveryCosts() { - auto outapp = std::make_unique(OP_BazaarSearch, sizeof(BazaarDeliveryCost_Struct)); + auto outapp = std::make_unique( + OP_BazaarSearch, + static_cast(sizeof(BazaarDeliveryCost_Struct)) + ); auto data = (BazaarDeliveryCost_Struct *) outapp->pBuffer; data->action = DeliveryCostUpdate; @@ -3311,7 +2866,7 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati { auto in = (TraderBuy_Struct *) app->pBuffer; auto trader_item = TraderRepository::GetItemBySerialNumber(database, tbs->serial_number, tbs->trader_id); - if (!trader_item.id) { + if (!trader_item.id || GetTraderTransactionDate() < trader_item.listing_date) { LogTrading("Attempt to purchase an item outside of the Bazaar trader_id [{}] item serial_number " "[{}] The Traders data was outdated.", tbs->trader_id, @@ -3362,6 +2917,21 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati return; } + auto next_slot = FindNextFreeParcelSlot(CharacterID()); + if (next_slot == INVALID_INDEX) { + LogTrading( + "{} attempted to purchase {} from the bazaar with parcel delivery. Unfortunately their parcel limit was reached. " + "Purchase unsuccessful.", + GetCleanName(), + buy_item->GetItem()->Name + ); + in->method = BazaarByParcel; + in->sub_action = TooManyParcels; + TraderRepository::UpdateActiveTransaction(database, trader_item.id, false); + TradeRequestFailed(app); + return; + } + LogTrading( "Name: [{}] IsStackable: [{}] Requested Quantity: [{}] Charges on Item [{}]", buy_item->GetItem()->Name, @@ -3371,23 +2941,24 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati ); // Determine the actual quantity for the purchase + int32 charges = static_cast(tbs->quantity); if (!buy_item->IsStackable()) { - tbs->quantity = 1; - } - else { - int32 item_charges = buy_item->GetCharges(); - if (item_charges <= 0) { - tbs->quantity = 1; + if (buy_item->GetCharges() <= 0) { + charges = 1; } - else if (static_cast(item_charges) < tbs->quantity) { - tbs->quantity = item_charges; + else { + charges = buy_item->GetCharges(); } } - LogTrading("Actual quantity that will be traded is [{}]", tbs->quantity); + LogTrading( + "Actual quantity that will be traded is [{}] {}", + tbs->quantity, + buy_item->GetCharges() ? fmt::format("with {} charges", buy_item->GetCharges()) : "" + ); - uint64 total_transaction_value = static_cast(tbs->price) * static_cast(tbs->quantity); - if (total_transaction_value > MAX_TRANSACTION_VALUE) { + uint64 total_cost = static_cast(tbs->price) * static_cast(tbs->quantity); + if (total_cost > MAX_TRANSACTION_VALUE) { Message( Chat::Red, "That would exceed the single transaction limit of %u platinum.", @@ -3398,9 +2969,8 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati return; } - uint32 total_cost = tbs->price * tbs->quantity; - uint32 fee = static_cast(std::round((uint32) total_cost * RuleR(Bazaar, ParcelDeliveryCostMod))); - if (!TakeMoneyFromPP(total_cost + fee)) { + uint64 fee = std::round(total_cost * RuleR(Bazaar, ParcelDeliveryCostMod)); + if (!TakeMoneyFromPP(total_cost + fee, false)) { RecordPlayerEventLog( PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{ @@ -3421,14 +2991,21 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati Message(Chat::Red, fmt::format("You paid {} for the parcel delivery.", DetermineMoneyString(fee)).c_str()); LogTrading("Customer [{}] Paid: [{}] in Copper", CharacterID(), total_cost); - if (player_event_logs.IsEventEnabled(PlayerEvent::TRADER_PURCHASE)) { + if (buy_item && PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::TRADER_PURCHASE)) { auto e = PlayerEvent::TraderPurchaseEvent{ .item_id = buy_item->GetID(), + .augment_1_id = buy_item->GetAugmentItemID(0), + .augment_2_id = buy_item->GetAugmentItemID(1), + .augment_3_id = buy_item->GetAugmentItemID(2), + .augment_4_id = buy_item->GetAugmentItemID(3), + .augment_5_id = buy_item->GetAugmentItemID(4), + .augment_6_id = buy_item->GetAugmentItemID(5), .item_name = buy_item->GetItem()->Name, .trader_id = tbs->trader_id, .trader_name = tbs->seller_name, .price = tbs->price, - .charges = tbs->quantity, + .quantity = tbs->quantity, + .charges = buy_item->IsStackable() ? 1 : charges, .total_cost = total_cost, .player_money_balance = GetCarriedMoney(), }; @@ -3437,24 +3014,10 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati } CharacterParcelsRepository::CharacterParcels parcel_out{}; - auto next_slot = FindNextFreeParcelSlot(CharacterID()); - if (next_slot == INVALID_INDEX) { - LogTrading( - "{} attempted to purchase {} from the bazaar with parcel delivery. Unfortunately their parcel limit was reached. " - "Purchase unsuccessful.", - GetCleanName(), - buy_item->GetItem()->Name - ); - in->method = BazaarByParcel; - in->sub_action = TooManyParcels; - TraderRepository::UpdateActiveTransaction(database, trader_item.id, false); - TradeRequestFailed(app); - return; - } parcel_out.from_name = tbs->seller_name; parcel_out.note = "Delivered from a Bazaar Purchase"; parcel_out.sent_date = time(nullptr); - parcel_out.quantity = buy_item->IsStackable() ? tbs->quantity : buy_item->GetCharges(); + parcel_out.quantity = charges; parcel_out.item_id = buy_item->GetItem()->ID; parcel_out.aug_slot_1 = buy_item->GetAugmentItemID(0); parcel_out.aug_slot_2 = buy_item->GetAugmentItemID(1); @@ -3483,18 +3046,19 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati } ReturnTraderReq(app, tbs->quantity, buy_item->GetID()); - if (player_event_logs.IsEventEnabled(PlayerEvent::PARCEL_SEND)) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::PARCEL_SEND)) { PlayerEvent::ParcelSend e{}; e.from_player_name = parcel_out.from_name; e.to_player_name = GetCleanName(); e.item_id = parcel_out.item_id; - e.aug_slot_1 = parcel_out.aug_slot_1; - e.aug_slot_2 = parcel_out.aug_slot_2; - e.aug_slot_3 = parcel_out.aug_slot_3; - e.aug_slot_4 = parcel_out.aug_slot_4; - e.aug_slot_5 = parcel_out.aug_slot_5; - e.aug_slot_6 = parcel_out.aug_slot_6; - e.quantity = parcel_out.quantity; + e.augment_1_id = parcel_out.aug_slot_1; + e.augment_2_id = parcel_out.aug_slot_2; + e.augment_3_id = parcel_out.aug_slot_3; + e.augment_4_id = parcel_out.aug_slot_4; + e.augment_5_id = parcel_out.aug_slot_5; + e.augment_6_id = parcel_out.aug_slot_6; + e.quantity = tbs->quantity; + e.charges = buy_item->IsStackable() ? 1 : charges; e.sent_date = parcel_out.sent_date; RecordPlayerEventLog(PlayerEvent::PARCEL_SEND, e); @@ -3521,7 +3085,9 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati BazaarAuditTrail(tbs->seller_name, GetName(), buy_item->GetItem()->Name, tbs->quantity, tbs->price, 0); } - auto out_server = std::make_unique(ServerOP_BazaarPurchase, sizeof(BazaarPurchaseMessaging_Struct)); + auto out_server = std::make_unique( + ServerOP_BazaarPurchase, static_cast(sizeof(BazaarPurchaseMessaging_Struct)) + ); auto out_data = (BazaarPurchaseMessaging_Struct *) out_server->pBuffer; out_data->trader_buy_struct = *tbs; @@ -3536,7 +3102,9 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati out_data->id = trader_item.id; strn0cpy(out_data->trader_buy_struct.buyer_name, GetCleanName(), sizeof(out_data->trader_buy_struct.buyer_name)); - worldserver.SendPacket(out_server.release()); + worldserver.SendPacket(out_server.get()); + + SendMoneyUpdate(); } void Client::SetBuyerWelcomeMessage(const char *welcome_message) @@ -3556,7 +3124,7 @@ void Client::SendBuyerGreeting(uint32 buyer_id) void Client::SendSellerBrowsing(const std::string &browser) { - auto outapp = std::make_unique(OP_Barter, sizeof(BuyerBrowsing_Struct)); + auto outapp = std::make_unique(OP_Barter, static_cast(sizeof(BuyerBrowsing_Struct))); auto eq = (BuyerBrowsing_Struct *) outapp->pBuffer; eq->action = Barter_SellerBrowsing; @@ -3754,7 +3322,7 @@ void Client::SendWindowUpdatesToSellerAndBuyer(BuyerLineSellItem_Struct &blsi) if (blsi.item_quantity - blsi.seller_quantity <= 0) { auto outapp = std::make_unique( OP_BuyerItems, - sizeof(BuyerRemoveItemFromMerchantWindow_Struct) + static_cast(sizeof(BuyerRemoveItemFromMerchantWindow_Struct)) ); auto data = (BuyerRemoveItemFromMerchantWindow_Struct *) outapp->pBuffer; @@ -3844,7 +3412,7 @@ void Client::SendBuyerToBarterWindow(Client *buyer, uint32 action) { auto server_packet = std::make_unique( ServerOP_BuyerMessaging, - sizeof(BuyerMessaging_Struct) + static_cast(sizeof(BuyerMessaging_Struct)) ); auto data = (BuyerMessaging_Struct *) server_packet->pBuffer; @@ -3865,7 +3433,10 @@ void Client::SendBulkBazaarBuyers() return; } - auto outapp = std::make_unique(OP_Barter, sizeof(BuyerAddBuyertoBarterWindow_Struct)); + auto outapp = std::make_unique( + OP_Barter, + static_cast(sizeof(BuyerAddBuyertoBarterWindow_Struct)) + ); auto emu = (BuyerAddBuyertoBarterWindow_Struct *) outapp->pBuffer; for (auto const &b: results) { @@ -4108,11 +3679,11 @@ bool Client::ValidateBuyLineItems(std::map &i int64 Client::ValidateBuyLineCost(std::map &item_map) { - int64 proposed_total_cost = std::accumulate( + uint64 proposed_total_cost = std::accumulate( item_map.cbegin(), item_map.cend(), - 0, - [](auto prev_sum, const std::pair &x) { + static_cast(0), + [](uint64 prev_sum, const std::pair &x) { return prev_sum + x.second.item_cost; } ); @@ -4275,3 +3846,18 @@ bool Client::DoBarterSellerChecks(BuyerLineSellItem_Struct &sell_line) return true; } + +void Client::CancelBuyerTradeWindow() +{ + auto end_session = new EQApplicationPacket(OP_Barter, sizeof(BuyerRemoveItemFromMerchantWindow_Struct)); + auto data = reinterpret_cast(end_session->pBuffer); + data->action = Barter_BuyerInspectBegin; + + FastQueuePacket(&end_session); +} + +void Client::CancelTraderTradeWindow() +{ + auto end_session = new EQApplicationPacket(OP_ShopEnd); + FastQueuePacket(&end_session); +} diff --git a/zone/trap.cpp b/zone/trap.cpp index ba0dfb4ea..7377d5897 100644 --- a/zone/trap.cpp +++ b/zone/trap.cpp @@ -462,10 +462,6 @@ bool ZoneDatabase::LoadTraps(const std::string& zone_short_name, int16 instance_ ) ); - if (l.empty()) { - return false; - } - for (const auto& e : l) { if (e.group_) { if (entity_list.IsTrapGroupSpawned(e.id, e.group_)) { diff --git a/zone/tune.cpp b/zone/tune.cpp index e46a8b32f..6f94d7bbd 100644 --- a/zone/tune.cpp +++ b/zone/tune.cpp @@ -466,12 +466,12 @@ void Mob::TuneGetAvoidanceByHitChance(Mob* defender, Mob *attacker, float hit_ch if (loop_add_avoid >= 0) { Message(0, "[#Tune] OPTION1: MODIFY Client Heroic AGI or Avoidance Mod2 stat by [+ %i ]", loop_add_avoid); - Message(0, "[#Tune] OPTION2: Give CLIENT an evasion bonus using SPA 172 Evasion SE_AvoidMeleeChance from (spells/items/aa) of [+ %i pct ]", evasion_bonus); + Message(0, "[#Tune] OPTION2: Give CLIENT an evasion bonus using SPA 172 Evasion SpellEffect::AvoidMeleeChance from (spells/items/aa) of [+ %i pct ]", evasion_bonus); } else { Message(0, "[#Tune] OPTION1: MODIFY Client Heroic AGI or Avoidance Mod2 stat by [ %i ]", loop_add_avoid); - Message(0, "[#Tune] OPTION2: Give CLIENT an evasion bonus using SPA 172 Evasion SE_AvoidMeleeChance from (spells/items/aa) of [ %i pct ]", evasion_bonus); + Message(0, "[#Tune] OPTION2: Give CLIENT an evasion bonus using SPA 172 Evasion SpellEffect::AvoidMeleeChance from (spells/items/aa) of [ %i pct ]", evasion_bonus); } Message(0, "###################COMPLETE###################"); @@ -853,7 +853,7 @@ int64 Mob::TuneNPCAttack(Mob* other, bool no_avoid, bool no_hit_chance, int hit_ TuneDoAttack(other, my_hit, nullptr, no_avoid, no_hit_chance, ac_override, add_ac, avoidance_override, accuracy_override, add_avoidance, add_accuracy); LogCombat("Final damage against [{}]: [{}]", other->GetName(), my_hit.damage_done); - if (other->IsClient() && IsPet() && GetOwner()->IsClient()) { + if (other->IsClient() && IsPet() && GetOwner()->IsOfClientBot()) { //pets do half damage to clients in pvp my_hit.damage_done /= 2; if (my_hit.damage_done < 1) @@ -1283,7 +1283,7 @@ int64 Mob::Tunecompute_tohit(EQ::skills::SkillType skillinuse, int accuracy_over // return -1 in cases that always hit int64 Mob::TuneGetTotalToHit(EQ::skills::SkillType skill, int chance_mod, int accuracy_override, int add_accuracy) { - if (chance_mod >= 10000) // override for stuff like SE_SkillAttack + if (chance_mod >= 10000) // override for stuff like SpellEffect::SkillAttack return -1; // calculate attacker's accuracy @@ -1313,7 +1313,7 @@ int64 Mob::TuneGetTotalToHit(EQ::skills::SkillType skill, int chance_mod, int ac if (atkhit_bonus) accuracy += round(static_cast(accuracy) * static_cast(atkhit_bonus) * 0.0001); - // 216 Melee Accuracy Amt aka SE_Accuracy -- flat bonus + // 216 Melee Accuracy Amt aka SpellEffect::Accuracy -- flat bonus accuracy += itembonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1] + aabonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1] + spellbonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1] + @@ -1335,7 +1335,7 @@ int64 Mob::TuneGetTotalToHit(EQ::skills::SkillType skill, int chance_mod, int ac if (spellbonuses.HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1] >= 10000) return -1; - // 184 Accuracy % aka SE_HitChance -- percentage increase + // 184 Accuracy % aka SpellEffect::HitChance -- percentage increase auto hit_bonus = itembonuses.HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1] + aabonuses.HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1] + spellbonuses.HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1] + @@ -1360,15 +1360,15 @@ int64 Mob::TuneGetTotalDefense(int avoidance_override, int add_avoidance) if (evasion_bonus >= 10000) return -1; - // 515 SE_AC_Avoidance_Max_Percent + // 515 SpellEffect::AC_Avoidance_Max_Percent auto ac_aviodance_bonus = itembonuses.AC_Avoidance_Max_Percent + aabonuses.AC_Avoidance_Max_Percent + spellbonuses.AC_Avoidance_Max_Percent; if (ac_aviodance_bonus) avoidance += round(static_cast(avoidance) * static_cast(ac_aviodance_bonus) * 0.0001); - // 172 Evasion aka SE_AvoidMeleeChance + // 172 Evasion aka SpellEffect::AvoidMeleeChance evasion_bonus += itembonuses.AvoidMeleeChanceEffect + aabonuses.AvoidMeleeChanceEffect; // item bonus here isn't mod2 avoidance - // 215 Pet Avoidance % aka SE_PetAvoidance + // 215 Pet Avoidance % aka SpellEffect::PetAvoidance evasion_bonus += GetPetAvoidanceBonusFromOwner(); // Evasion is a percentage bonus according to AA descriptions @@ -1410,7 +1410,7 @@ int64 Mob::Tunecompute_defense(int avoidance_override, int add_avoidance) } - //516 SE_AC_Mitigation_Max_Percent + //516 SpellEffect::AC_Mitigation_Max_Percent auto ac_bonus = itembonuses.AC_Mitigation_Max_Percent + aabonuses.AC_Mitigation_Max_Percent + spellbonuses.AC_Mitigation_Max_Percent; if (ac_bonus) { defense += round(static_cast(defense) * static_cast(ac_bonus) * 0.0001); @@ -1556,7 +1556,7 @@ void Mob::TuneCommonOutgoingHitSuccess(Mob* defender, DamageHitInfo &hit, ExtraA int mod = GetSpecialAbilityParam(SpecialAbility::Rampage, 2); if (mod > 0) spec_mod = mod; - if ((IsPet() || IsTempPet()) && IsPetOwnerClient()) { + if ((IsPet() || IsTempPet()) && IsPetOwnerOfClientBot()) { //SE_PC_Pet_Rampage SPA 464 on pet, damage modifier int spell_mod = spellbonuses.PC_Pet_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] + itembonuses.PC_Pet_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] + aabonuses.PC_Pet_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD]; if (spell_mod > spec_mod) @@ -1567,7 +1567,7 @@ void Mob::TuneCommonOutgoingHitSuccess(Mob* defender, DamageHitInfo &hit, ExtraA int mod = GetSpecialAbilityParam(SpecialAbility::AreaRampage, 2); if (mod > 0) spec_mod = mod; - if ((IsPet() || IsTempPet()) && IsPetOwnerClient()) { + if ((IsPet() || IsTempPet()) && IsPetOwnerOfClientBot()) { //SE_PC_Pet_AE_Rampage SPA 465 on pet, damage modifier int spell_mod = spellbonuses.PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] + itembonuses.PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] + aabonuses.PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD]; if (spell_mod > spec_mod) diff --git a/zone/water_map.cpp b/zone/water_map.cpp index 27289abd6..e31bb0c75 100644 --- a/zone/water_map.cpp +++ b/zone/water_map.cpp @@ -17,7 +17,7 @@ WaterMap* WaterMap::LoadWaterMapfile(std::string zone_name) { std::transform(zone_name.begin(), zone_name.end(), zone_name.begin(), ::tolower); - std::string file_path = fmt::format("{}/water/{}.wtr", path.GetMapsPath(), zone_name); + std::string file_path = fmt::format("{}/water/{}.wtr", PathManager::Instance()->GetMapsPath(), zone_name); LogDebug("Attempting to load water map with path [{}]", file_path.c_str()); FILE *f = fopen(file_path.c_str(), "rb"); if(f) { diff --git a/zone/waypoints.cpp b/zone/waypoints.cpp index bb071ed1a..5904c83ad 100644 --- a/zone/waypoints.cpp +++ b/zone/waypoints.cpp @@ -206,6 +206,9 @@ void NPC::PauseWandering(int pausetime) void NPC::MoveTo(const glm::vec4 &position, bool saveguardspot) { // makes mob walk to specified location + if (!AI_walking_timer) { + return; + } if (IsNPC() && GetGrid() != 0) { // he is on a grid if (GetGrid() < 0) { // currently stopped by a quest command SetGrid(0 - GetGrid()); // get him moving again diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index afff2a534..747302e24 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -40,8 +40,8 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "client.h" #include "command.h" #include "corpse.h" +#include "dynamic_zone.h" #include "entity.h" -#include "expedition.h" #include "quest_parser_collection.h" #include "guild_mgr.h" #include "mob.h" @@ -60,16 +60,18 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "../common/repositories/guild_tributes_repository.h" #include "../common/patches/patches.h" #include "../common/skill_caps.h" +#include "../common/server_reload_types.h" +#include "queryserv.h" -extern EntityList entity_list; -extern Zone* zone; -extern volatile bool is_zone_loaded; -extern void Shutdown(); -extern WorldServer worldserver; -extern PetitionList petition_list; -extern uint32 numclients; -extern volatile bool RunLoops; +extern EntityList entity_list; +extern Zone *zone; +extern volatile bool is_zone_loaded; +extern void Shutdown(); +extern WorldServer worldserver; +extern uint32 numclients; +extern volatile bool RunLoops; extern QuestParserCollection *parse; +extern QueryServ *QServ; // QuestParserCollection *parse = 0; @@ -83,6 +85,23 @@ WorldServer::WorldServer() WorldServer::~WorldServer() { } +void WorldServer::Process() +{ + if (!m_reload_queue.empty()) { + m_reload_mutex.lock(); + for (auto it = m_reload_queue.begin(); it != m_reload_queue.end(); ) { + if (it->second.reload_at_unix < std::time(nullptr)) { + ProcessReload(it->second); + it = m_reload_queue.erase(it); + break; + } else { + ++it; + } + } + m_reload_mutex.unlock(); + } +} + void WorldServer::Connect() { m_connection = std::make_unique(Config->WorldIP, Config->WorldTCPPort, false, "Zone", Config->SharedKey); @@ -91,8 +110,6 @@ void WorldServer::Connect() }); m_connection->OnMessage(std::bind(&WorldServer::HandleMessage, this, std::placeholders::_1, std::placeholders::_2)); - - m_keepalive = std::make_unique(1000, true, std::bind(&WorldServer::OnKeepAlive, this, std::placeholders::_1)); } bool WorldServer::SendPacket(ServerPacket *pack) @@ -212,7 +229,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) LogInfo("World assigned Port [{}] for this zone", sci->port); ZoneConfig::SetZonePort(sci->port); - LogSys.SetDiscordHandler(&Zone::DiscordWebhookMessageHandler); + EQEmuLogSys::Instance()->SetDiscordHandler(&Zone::DiscordWebhookMessageHandler); } break; } @@ -573,7 +590,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) auto *s = (ServerZoneStateChange_Struct *) pack->pBuffer; LogInfo("Zone shutdown by {}.", s->admin_name); - Zone::Shutdown(); + zone->Shutdown(); } break; } @@ -605,6 +622,10 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) } Zone::Bootup(s->zone_id, s->instance_id, s->is_static); + if (zone) { + zone->SetZoneServerId(s->zone_server_id); + } + break; } case ServerOP_ZoneIncClient: { @@ -889,8 +910,8 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) std::cout << "Got Server Requested Petition List Refresh" << std::endl; ServerPetitionUpdate_Struct* sus = (ServerPetitionUpdate_Struct*)pack->pBuffer; // this was typoed to = instead of ==, not that it acts any different now though.. - if (sus->status == 0) petition_list.ReadDatabase(); - else if (sus->status == 1) petition_list.ReadDatabase(); // Until I fix this to be better.... + if (sus->status == 0) PetitionList::Instance()->ReadDatabase(); + else if (sus->status == 1) PetitionList::Instance()->ReadDatabase(); // Until I fix this to be better.... break; } case ServerOP_RezzPlayer: { @@ -936,7 +957,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) LogSpells("[WorldServer::HandleMessage] Found corpse. Marking corpse as rezzed if needed"); // I don't know why Rezzed is not set to true in CompleteRezz(). - if (!IsEffectInSpell(srs->rez.spellid, SE_SummonToCorpse)) { + if (!IsEffectInSpell(srs->rez.spellid, SpellEffect::SummonToCorpse)) { corpse->IsRezzed(true); corpse->CompleteResurrection(); } @@ -1973,232 +1994,10 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) } break; } - case ServerOP_ReloadAAData: + case ServerOP_ServerReloadRequest: { - if (zone && zone->IsLoaded()) { - zone->SendReloadMessage("Alternate Advancement Data"); - zone->LoadAlternateAdvancement(); - entity_list.SendAlternateAdvancementStats(); - } - break; - } - case ServerOP_ReloadOpcodes: - { - zone->SendReloadMessage("Opcodes"); - ReloadAllPatches(); - break; - } - case ServerOP_ReloadAlternateCurrencies: - { - if (zone && zone->IsLoaded()) { - zone->SendReloadMessage("Alternate Currencies"); - zone->LoadAlternateCurrencies(); - } - break; - } - case ServerOP_ReloadBaseData: - { - if (zone && zone->IsLoaded()) { - zone->SendReloadMessage("Base Data"); - zone->ReloadBaseData(); - } - - break; - } - case ServerOP_ReloadBlockedSpells: - { - if (zone && zone->IsLoaded()) { - zone->SendReloadMessage("Blocked Spells"); - zone->LoadZoneBlockedSpells(); - } - break; - } - case ServerOP_ReloadCommands: - { - zone->SendReloadMessage("Commands"); - command_init(); - if (RuleB(Bots, Enabled) && database.DoesTableExist("bot_command_settings")) { - bot_command_init(); - } - break; - } - case ServerOP_ReloadContentFlags: - { - zone->SendReloadMessage("Content Flags"); - content_service.SetExpansionContext()->ReloadContentFlags(); - break; - } - case ServerOP_ReloadDzTemplates: - { - if (zone) - { - zone->SendReloadMessage("Dynamic Zone Templates"); - zone->LoadDynamicZoneTemplates(); - } - break; - } - case ServerOP_ReloadFactions: - { - if (zone && zone->IsLoaded()) { - zone->SendReloadMessage("Factions"); - content_db.LoadFactionData(); - zone->ReloadNPCFactions(); - zone->ReloadFactionAssociations(); - } - - break; - } - case ServerOP_ReloadLevelEXPMods: - { - if (zone && zone->IsLoaded()) { - zone->SendReloadMessage("Level Based Experience Modifiers"); - zone->LoadLevelEXPMods(); - } - break; - } - case ServerOP_ReloadLogs: - { - zone->SendReloadMessage("Log Settings"); - LogSys.LoadLogDatabaseSettings(); - player_event_logs.ReloadSettings(); - break; - } - case ServerOP_ReloadLoot: - { - if (zone && zone->IsLoaded()) { - zone->SendReloadMessage("Loot"); - zone->ReloadLootTables(); - } - break; - } - case ServerOP_ReloadMerchants: { - if (zone && zone->IsLoaded()) { - zone->SendReloadMessage("Merchants"); - entity_list.ReloadMerchants(); - } - break; - } - case ServerOP_ReloadNPCEmotes: - { - if (zone && zone->IsLoaded()) { - zone->SendReloadMessage("NPC Emotes"); - zone->LoadNPCEmotes(&zone->npc_emote_list); - } - break; - } - case ServerOP_ReloadNPCSpells: - { - if (zone && zone->IsLoaded()) { - zone->SendReloadMessage("NPC Spells"); - content_db.ClearNPCSpells(); - for (auto& e : entity_list.GetNPCList()) { - e.second->ReloadSpells(); - } - } - break; - } - case ServerOP_ReloadPerlExportSettings: - { - zone->SendReloadMessage("Perl Event Export Settings"); - parse->LoadPerlEventExportSettings(parse->perl_event_export_settings); - break; - } - case ServerOP_ReloadRules: - { - zone->SendReloadMessage("Rules"); - RuleManager::Instance()->LoadRules(&database, RuleManager::Instance()->GetActiveRuleset(), true); - break; - } - case ServerOP_ReloadSkillCaps: - { - if (zone && zone->IsLoaded()) { - zone->SendReloadMessage("Skill Caps"); - skill_caps.ReloadSkillCaps(); - } - - break; - } - case ServerOP_ReloadDataBucketsCache: - { - zone->SendReloadMessage("Data buckets cache"); - DataBucket::ClearCache(); - break; - } - case ServerOP_ReloadDoors: - case ServerOP_ReloadGroundSpawns: - case ServerOP_ReloadObjects: - case ServerOP_ReloadStaticZoneData: { - if (zone && zone->IsLoaded()) { - zone->SendReloadMessage("Static Zone Data"); - zone->ReloadStaticData(); - } - break; - } - case ServerOP_ReloadTasks: - { - if (RuleB(Tasks, EnableTaskSystem) && zone && zone->IsLoaded()) { - zone->SendReloadMessage("Tasks"); - HandleReloadTasks(pack); - } - - break; - } - case ServerOP_ReloadTitles: - { - if (zone && zone->IsLoaded()) { - zone->SendReloadMessage("Titles"); - title_manager.LoadTitles(); - } - break; - } - case ServerOP_ReloadTraps: - { - if (zone && zone->IsLoaded()) { - zone->SendReloadMessage("Traps"); - entity_list.UpdateAllTraps(true, true); - } - - break; - } - case ServerOP_ReloadVariables: - { - if (zone && zone->IsLoaded()) { - zone->SendReloadMessage("Variables"); - database.LoadVariables(); - } - break; - } - case ServerOP_ReloadVeteranRewards: - { - if (zone && zone->IsLoaded()) { - zone->SendReloadMessage("Veteran Rewards"); - zone->LoadVeteranRewards(); - } - break; - } - case ServerOP_ReloadWorld: - { - auto* reload_world = (ReloadWorld_Struct*)pack->pBuffer; - if (zone) { - zone->ReloadWorld(reload_world->global_repop); - } - break; - } - case ServerOP_ReloadZonePoints: - { - if (zone && zone->IsLoaded()) { - zone->SendReloadMessage("Zone Points"); - content_db.LoadStaticZonePoints(&zone->zone_point_list, zone->GetShortName(), zone->GetInstanceVersion()); - } - break; - } - case ServerOP_ReloadZoneData: - { - zone_store.LoadZones(content_db); - if (zone && zone->IsLoaded()) { - zone->LoadZoneCFG(zone->GetShortName(), zone->GetInstanceVersion()); - zone->SendReloadMessage("Zone Data"); - } + auto o = (ServerReload::Request*) pack->pBuffer; + QueueReload(*o); break; } case ServerOP_CameraShake: @@ -3340,7 +3139,6 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) case ServerOP_WWMarquee: { auto s = (WWMarquee_Struct*) pack->pBuffer; - for (const auto& c : entity_list.GetClientList()) { if ( c.second->Admin() >= s->min_status && @@ -3601,25 +3399,16 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) break; } - case ServerOP_ExpeditionCreate: - case ServerOP_ExpeditionLockout: - case ServerOP_ExpeditionLockoutDuration: - case ServerOP_ExpeditionLockState: - case ServerOP_ExpeditionReplayOnJoin: - case ServerOP_ExpeditionDzAddPlayer: - case ServerOP_ExpeditionDzMakeLeader: - case ServerOP_ExpeditionCharacterLockout: - { - Expedition::HandleWorldMessage(pack); - break; - } case ServerOP_DzCreated: case ServerOP_DzDeleted: + case ServerOP_DzAddPlayer: + case ServerOP_DzMakeLeader: case ServerOP_DzAddRemoveMember: case ServerOP_DzSwapMembers: case ServerOP_DzRemoveAllMembers: case ServerOP_DzDurationUpdate: case ServerOP_DzGetMemberStatuses: + case ServerOP_DzGetBulkMemberStatuses: case ServerOP_DzSetCompass: case ServerOP_DzSetSafeReturn: case ServerOP_DzSetZoneIn: @@ -3628,6 +3417,11 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) case ServerOP_DzLeaderChanged: case ServerOP_DzExpireWarning: case ServerOP_DzMovePC: + case ServerOP_DzLock: + case ServerOP_DzReplayOnJoin: + case ServerOP_DzLockout: + case ServerOP_DzLockoutDuration: + case ServerOP_DzCharacterLockout: { DynamicZone::HandleWorldMessage(pack); break; @@ -3644,11 +3438,6 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) SharedTaskZoneMessaging::HandleWorldMessage(pack); break; } - case ServerOP_DataBucketCacheUpdate: - { - DataBucket::HandleWorldMessage(pack); - break; - } case ServerOP_GuildTributeUpdate: { GuildTributeUpdate* in = (GuildTributeUpdate*)pack->pBuffer; @@ -3919,27 +3708,58 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) auto in = (TraderMessaging_Struct *) pack->pBuffer; for (auto const &c: entity_list.GetClientList()) { if (c.second->ClientVersion() >= EQ::versions::ClientVersion::RoF2) { - auto outapp = new EQApplicationPacket(OP_BecomeTrader, sizeof(BecomeTrader_Struct)); - auto out = (BecomeTrader_Struct *) outapp->pBuffer; + auto outapp = new EQApplicationPacket(OP_BecomeTrader, sizeof(BecomeTrader_Struct)); + auto out = (BecomeTrader_Struct *) outapp->pBuffer; + + out->entity_id = in->entity_id; + out->zone_id = in->zone_id; + out->zone_instance_id = in->instance_id; + out->trader_id = in->trader_id; + strn0cpy(out->trader_name, in->trader_name, sizeof(out->trader_name)); + switch (in->action) { case TraderOn: { out->action = AddTraderToBazaarWindow; + if (c.second->GetTraderCount() < + EQ::constants::StaticLookup(c.second->ClientVersion())->BazaarTraderLimit) { + if (RuleB(Bazaar, UseAlternateBazaarSearch)) { + if (out->zone_id == Zones::BAZAAR && + out->zone_instance_id == c.second->GetInstanceID()) { + c.second->IncrementTraderCount(); + c.second->QueuePacket(outapp, true, Mob::CLIENT_CONNECTED); + } + } + else { + c.second->IncrementTraderCount(); + c.second->QueuePacket(outapp, true, Mob::CLIENT_CONNECTED); + } + } break; } case TraderOff: { out->action = RemoveTraderFromBazaarWindow; + if (c.second->GetTraderCount() <= + EQ::constants::StaticLookup(c.second->ClientVersion())->BazaarTraderLimit) { + if (RuleB(Bazaar, UseAlternateBazaarSearch)) { + if (out->zone_id == Zones::BAZAAR && + out->zone_instance_id == c.second->GetInstanceID()) { + c.second->DecrementTraderCount(); + c.second->QueuePacket(outapp, true, Mob::CLIENT_CONNECTED); + } + } + else { + c.second->DecrementTraderCount(); + c.second->QueuePacket(outapp, true, Mob::CLIENT_CONNECTED); + } + } break; } default: { out->action = 0; + c.second->QueuePacket(outapp, true, Mob::CLIENT_CONNECTED); } } - out->entity_id = in->entity_id; - out->zone_id = in->zone_id; - out->trader_id = in->trader_id; - strn0cpy(out->trader_name, in->trader_name, sizeof(out->trader_name)); - c.second->QueuePacket(outapp); safe_delete(outapp); } if (zone && zone->GetZoneID() == Zones::BAZAAR && in->instance_id == zone->GetInstanceID()) { @@ -3964,8 +3784,15 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) return; } + if (trader_pc->IsThereACustomer()) { + auto customer = entity_list.GetClientByID(trader_pc->GetCustomerID()); + if (customer) { + customer->CancelTraderTradeWindow(); + } + } + auto item_sn = Strings::ToUnsignedBigInt(in->trader_buy_struct.serial_number); - auto outapp = std::make_unique(OP_Trader, sizeof(TraderBuy_Struct)); + auto outapp = std::make_unique(OP_Trader, static_cast(sizeof(TraderBuy_Struct))); auto data = (TraderBuy_Struct *) outapp->pBuffer; memcpy(data, &in->trader_buy_struct, sizeof(TraderBuy_Struct)); @@ -3976,25 +3803,33 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) TraderRepository::UpdateActiveTransaction(database, in->id, false); - trader_pc->RemoveItemBySerialNumber(item_sn, in->trader_buy_struct.quantity); - trader_pc->AddMoneyToPP(in->trader_buy_struct.price * in->trader_buy_struct.quantity, true); - trader_pc->QueuePacket(outapp.get()); + auto item = trader_pc->FindTraderItemBySerialNumber(item_sn); - if (player_event_logs.IsEventEnabled(PlayerEvent::TRADER_SELL)) { + if (item && PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::TRADER_SELL)) { auto e = PlayerEvent::TraderSellEvent{ - .item_id = in->trader_buy_struct.item_id, + .item_id = item ? item->GetID() : 0, + .augment_1_id = item->GetAugmentItemID(0), + .augment_2_id = item->GetAugmentItemID(1), + .augment_3_id = item->GetAugmentItemID(2), + .augment_4_id = item->GetAugmentItemID(3), + .augment_5_id = item->GetAugmentItemID(4), + .augment_6_id = item->GetAugmentItemID(5), .item_name = in->trader_buy_struct.item_name, .buyer_id = in->buyer_id, .buyer_name = in->trader_buy_struct.buyer_name, .price = in->trader_buy_struct.price, - .charges = in->trader_buy_struct.quantity, + .quantity = in->trader_buy_struct.quantity, + .charges = item ? item->IsStackable() ? 1 : item->GetCharges() : 0, .total_cost = (in->trader_buy_struct.price * in->trader_buy_struct.quantity), .player_money_balance = trader_pc->GetCarriedMoney(), }; - RecordPlayerEventLogWithClient(trader_pc, PlayerEvent::TRADER_SELL, e); } + trader_pc->RemoveItemBySerialNumber(item_sn, in->trader_buy_struct.quantity); + trader_pc->AddMoneyToPP(in->trader_buy_struct.price * in->trader_buy_struct.quantity, true); + trader_pc->QueuePacket(outapp.get()); + break; } case ServerOP_BuyerMessaging: { @@ -4004,7 +3839,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) case Barter_AddToBarterWindow: { auto outapp = std::make_unique( OP_Barter, - sizeof(BuyerAddBuyertoBarterWindow_Struct) + static_cast(sizeof(BuyerAddBuyertoBarterWindow_Struct)) ); auto emu = (BuyerAddBuyertoBarterWindow_Struct *) outapp->pBuffer; @@ -4021,7 +3856,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) case Barter_RemoveFromBarterWindow: { auto outapp = std::make_unique( OP_Barter, - sizeof(BuyerRemoveBuyerFromBarterWindow_Struct) + static_cast(sizeof(BuyerRemoveBuyerFromBarterWindow_Struct)) ); auto emu = (BuyerRemoveBuyerFromBarterWindow_Struct *) outapp->pBuffer; @@ -4066,7 +3901,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) Barter_Failure ); - if (player_event_logs.IsEventEnabled(PlayerEvent::BARTER_TRANSACTION)) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::BARTER_TRANSACTION)) { PlayerEvent::BarterTransaction e{}; e.status = "Failed Barter Transaction"; e.item_id = sell_line.item_id; @@ -4089,7 +3924,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) sell_line.seller_quantity, sell_line.item_name, buyer->GetCleanName()); - buyer->AddMoneyToPPWithOverflow(total_cost, true); + buyer->AddMoneyToPP(total_cost, true); buyer->RemoveItem(sell_line.item_id, sell_line.seller_quantity); buyer->Message( @@ -4103,7 +3938,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) Barter_Failure ); - if (player_event_logs.IsEventEnabled(PlayerEvent::BARTER_TRANSACTION)) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::BARTER_TRANSACTION)) { PlayerEvent::BarterTransaction e{}; e.status = "Failed Barter Transaction"; e.item_id = sell_line.item_id; @@ -4151,6 +3986,12 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) worldserver.SendPacket(pack); return; } + if (buyer->IsThereACustomer()) { + auto customer = entity_list.GetClientByID(buyer->GetCustomerID()); + if (customer) { + customer->CancelBuyerTradeWindow(); + } + } BuyerLineSellItem_Struct sell_line{}; sell_line.item_id = in->buy_item_id; @@ -4188,7 +4029,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) if (inst->IsStackable()) { if (!buyer->PutItemInInventoryWithStacking(inst.get())) { buyer->Message(Chat::Red, "Error putting item in your inventory."); - buyer->AddMoneyToPPWithOverflow(total_cost, true); + buyer->AddMoneyToPP(total_cost, true); in->action = Barter_FailedTransaction; in->sub_action = Barter_FailedBuyerChecks; worldserver.SendPacket(pack); @@ -4200,7 +4041,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) inst->SetCharges(1); if (!buyer->PutItemInInventoryWithStacking(inst.get())) { buyer->Message(Chat::Red, "Error putting item in your inventory."); - buyer->AddMoneyToPPWithOverflow(total_cost, true); + buyer->AddMoneyToPP(total_cost, true); in->action = Barter_FailedTransaction; in->sub_action = Barter_FailedBuyerChecks; worldserver.SendPacket(pack); @@ -4209,7 +4050,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) } } - if (!buyer->TakeMoneyFromPPWithOverFlow(total_cost, false)) { + if (!buyer->TakeMoneyFromPP(total_cost, false)) { in->action = Barter_FailedTransaction; in->sub_action = Barter_FailedBuyerChecks; worldserver.SendPacket(pack); @@ -4224,7 +4065,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) Barter_Success ); - if (player_event_logs.IsEventEnabled(PlayerEvent::BARTER_TRANSACTION)) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::BARTER_TRANSACTION)) { PlayerEvent::BarterTransaction e{}; e.status = "Successful Barter Transaction"; e.item_id = sell_line.item_id; @@ -4274,7 +4115,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) uint64 total_cost = (uint64) sell_line.item_cost * (uint64) sell_line.seller_quantity; seller->RemoveItem(in->buy_item_id, in->seller_quantity); - seller->AddMoneyToPPWithOverflow(total_cost, false); + seller->AddMoneyToPP(total_cost, false); seller->SendBarterBuyerClientMessage( sell_line, Barter_SellerTransactionComplete, @@ -4282,7 +4123,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) Barter_Success ); - if (player_event_logs.IsEventEnabled(PlayerEvent::BARTER_TRANSACTION)) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::BARTER_TRANSACTION)) { PlayerEvent::BarterTransaction e{}; e.status = "Successful Barter Transaction"; e.item_id = sell_line.item_id; @@ -4435,7 +4276,7 @@ bool WorldServer::SendVoiceMacro(Client* From, uint32 Type, char* Target, uint32 uint16 player_race = GetPlayerRaceValue(From->GetRace()); - if (player_race == PLAYER_RACE_UNKNOWN) { + if (player_race == Race::Doug) { player_race = From->GetBaseRace(); } @@ -4470,59 +4311,9 @@ bool WorldServer::RezzPlayer(EQApplicationPacket* rpack, uint32 rezzexp, uint32 } void WorldServer::SendReloadTasks(uint8 reload_type, uint32 task_id) { - auto pack = new ServerPacket(ServerOP_ReloadTasks, sizeof(ReloadTasks_Struct)); - auto rts = (ReloadTasks_Struct*) pack->pBuffer; - - rts->reload_type = reload_type; - rts->task_id = task_id; - - SendPacket(pack); - safe_delete(pack); + SendReload(ServerReload::Type::Tasks); } -void WorldServer::HandleReloadTasks(ServerPacket *pack) -{ - auto rts = (ReloadTasks_Struct*) pack->pBuffer; - - LogTasks("Global reload of tasks received with Reload Type [{}] Task ID [{}]", rts->reload_type, rts->task_id); - - switch (rts->reload_type) { - case RELOADTASKS: - { - entity_list.SaveAllClientsTaskState(); - - // TODO: Reload at the world level for shared tasks - - if (!rts->task_id) { - LogTasks("Global reload of all Tasks"); - safe_delete(task_manager); - task_manager = new TaskManager; - task_manager->LoadTasks(); - - entity_list.ReloadAllClientsTaskState(); - } else { - LogTasks("Global reload of Task ID [{}]", rts->task_id); - task_manager->LoadTasks(rts->task_id); - entity_list.ReloadAllClientsTaskState(rts->task_id); - } - - break; - } - case RELOADTASKSETS: - { - LogTasks("Global reload of all Task Sets"); - task_manager->LoadTaskSets(); - break; - } - default: - { - LogTasks("Unhandled global reload of Tasks Reload Type [{}] Task ID [{}]", rts->reload_type, rts->task_id); - break; - } - } -} - - uint32 WorldServer::NextGroupID() { //this system wastes a lot of potential group IDs (~5%), but //if you are creating 2 billion groups in 1 run of the emu, @@ -4693,12 +4484,6 @@ void WorldServer::RequestTellQueue(const char *who) return; } -void WorldServer::OnKeepAlive(EQ::Timer *t) -{ - ServerPacket pack(ServerOP_KeepAlive, 0); - SendPacket(&pack); -} - ZoneEventScheduler *WorldServer::GetScheduler() const { return m_zone_scheduler; @@ -4709,3 +4494,215 @@ void WorldServer::SetScheduler(ZoneEventScheduler *scheduler) WorldServer::m_zone_scheduler = scheduler; } +void WorldServer::SendReload(ServerReload::Type type, bool is_global) +{ + static auto pack = ServerPacket(ServerOP_ServerReloadRequest, sizeof(ServerReload::Request)); + auto reload = (ServerReload::Request*) pack.pBuffer; + reload->type = type; + reload->zone_server_id = 0; + if (!is_global && zone && zone->IsLoaded()) { + reload->zone_server_id = zone->GetZoneServerId(); + } + + SendPacket(&pack); +} + +void WorldServer::QueueReload(ServerReload::Request r) +{ + m_reload_mutex.lock(); + int64_t reload_at = r.reload_at_unix - std::time(nullptr); + + LogInfo( + "Queuing reload for [{}] ({}) to reload in [{}]", + ServerReload::GetName(r.type), + r.type, + reload_at > 0 ? Strings::SecondsToTime(reload_at) : "Now" + ); + + m_reload_queue[r.type] = r; + m_reload_mutex.unlock(); +} + +void WorldServer::ProcessReload(const ServerReload::Request& request) +{ + LogInfo( + "Reloading [{}] ({}) zone booted required [{}]", + ServerReload::GetName(request.type), + request.type, + request.requires_zone_booted + ); + + if (request.requires_zone_booted) { + if (!zone || (zone && !zone->IsLoaded())) { + LogInfo("Zone not booted, skipping reload for [{}] ({})", ServerReload::GetName(request.type), request.type); + return; + } + } + + zone->SendReloadMessage(ServerReload::GetName(request.type)); + + switch (request.type) { + case ServerReload::Type::AAData: + zone->LoadAlternateAdvancement(); + entity_list.SendAlternateAdvancementStats(); + break; + + case ServerReload::Type::Opcodes: + ReloadAllPatches(); + break; + + case ServerReload::Type::AlternateCurrencies: + zone->LoadAlternateCurrencies(); + break; + + case ServerReload::Type::BaseData: + zone->ReloadBaseData(); + break; + + case ServerReload::Type::BlockedSpells: + zone->LoadZoneBlockedSpells(); + break; + + case ServerReload::Type::Commands: + command_init(); + if (RuleB(Bots, Enabled) && database.DoesTableExist("bot_command_settings")) { + bot_command_init(); + } + break; + + case ServerReload::Type::ContentFlags: + WorldContentService::Instance()->SetExpansionContext()->ReloadContentFlags(); + break; + + case ServerReload::Type::DzTemplates: + zone->LoadDynamicZoneTemplates(); + break; + + case ServerReload::Type::Factions: + content_db.LoadFactionData(); + zone->ReloadNPCFactions(); + zone->ReloadFactionAssociations(); + break; + + case ServerReload::Type::LevelEXPMods: + zone->LoadLevelEXPMods(); + break; + + case ServerReload::Type::Logs: + EQEmuLogSys::Instance()->LoadLogDatabaseSettings(); + PlayerEventLogs::Instance()->ReloadSettings(); + break; + + case ServerReload::Type::Loot: + zone->ReloadLootTables(); + break; + + case ServerReload::Type::Maps: + zone->ReloadMaps(); + break; + + case ServerReload::Type::Merchants: + entity_list.ReloadMerchants(); + break; + + case ServerReload::Type::NPCEmotes: + zone->LoadNPCEmotes(&zone->npc_emote_list); + break; + + case ServerReload::Type::NPCSpells: + content_db.ClearNPCSpells(); + for (auto &e: entity_list.GetNPCList()) { + e.second->ReloadSpells(); + } + break; + + case ServerReload::Type::PerlExportSettings: + parse->LoadPerlEventExportSettings(parse->perl_event_export_settings); + break; + + case ServerReload::Type::Rules: + RuleManager::Instance()->LoadRules(&database, RuleManager::Instance()->GetActiveRuleset(), true); + break; + + case ServerReload::Type::SkillCaps: + SkillCaps::Instance()->ReloadSkillCaps(); + break; + + case ServerReload::Type::DataBucketsCache: + DataBucket::ClearCache(); + break; + + case ServerReload::Type::StaticZoneData: + case ServerReload::Type::Doors: + case ServerReload::Type::GroundSpawns: + case ServerReload::Type::Objects: + zone->ReloadStaticData(); + break; + + case ServerReload::Type::Tasks: + if (RuleB(Tasks, EnableTaskSystem)) { + entity_list.SaveAllClientsTaskState(); + TaskManager::Instance()->LoadTasks(); + entity_list.ReloadAllClientsTaskState(); + TaskManager::Instance()->LoadTaskSets(); + } + break; + + case ServerReload::Type::Quests: + entity_list.ClearAreas(); + parse->ReloadQuests(false); + break; + + case ServerReload::Type::QuestsTimerReset: + entity_list.ClearAreas(); + parse->ReloadQuests(true); + break; + + case ServerReload::Type::Titles: + title_manager.LoadTitles(); + break; + + case ServerReload::Type::Traps: + entity_list.UpdateAllTraps(true, true); + break; + + case ServerReload::Type::Variables: + database.LoadVariables(); + break; + + case ServerReload::Type::VeteranRewards: + zone->LoadVeteranRewards(); + break; + + case ServerReload::Type::WorldRepop: + parse->ReloadQuests(); + if (zone && zone->IsLoaded()) { + entity_list.ClearAreas(); + zone->Repop(); + } + break; + + case ServerReload::Type::WorldWithRespawn: + parse->ReloadQuests(); + if (zone && zone->IsLoaded()) { + entity_list.ClearAreas(); + zone->Repop(); + zone->ClearSpawnTimers(); + } + break; + + case ServerReload::Type::ZonePoints: + content_db.LoadStaticZonePoints(&zone->zone_point_list, zone->GetShortName(), zone->GetInstanceVersion()); + break; + + case ServerReload::Type::ZoneData: + ZoneStore::Instance()->LoadZones(content_db); + zone->LoadZoneCFG(zone->GetShortName(), zone->GetInstanceVersion()); + break; + + default: + break; + } + + LogInfo("Reloaded [{}] ({})", ServerReload::GetName(request.type), request.type); +} diff --git a/zone/worldserver.h b/zone/worldserver.h index 49b151400..2e92169a5 100644 --- a/zone/worldserver.h +++ b/zone/worldserver.h @@ -21,6 +21,7 @@ #include "../common/eq_packet_structs.h" #include "../common/net/servertalk_client_connection.h" #include "zone_event_scheduler.h" +#include "../common/server_reload_types.h" class ServerPacket; class EQApplicationPacket; @@ -31,6 +32,7 @@ public: WorldServer(); ~WorldServer(); + void Process(); void Connect(); bool SendPacket(ServerPacket* pack); std::string GetIP() const; @@ -52,7 +54,6 @@ public: void SetLaunchedName(const char *n) { m_launchedName = n; } void SetLauncherName(const char *n) { m_launcherName = n; } void SendReloadTasks(uint8 reload_type, uint32 task_id = 0); - void HandleReloadTasks(ServerPacket *pack); void UpdateLFP(uint32 LeaderID, uint8 Action, uint8 MatchFilter, uint32 FromLevel, uint32 ToLevel, uint32 Classes, const char *Comments, GroupLFPMemberEntry *LFPMembers); void UpdateLFP(uint32 LeaderID, GroupLFPMemberEntry *LFPMembers); @@ -61,7 +62,8 @@ public: void HandleLFPMatches(ServerPacket *pack); void RequestTellQueue(const char *who); - + void QueueReload(ServerReload::Request r); + void ProcessReload(const ServerReload::Request &request); private: virtual void OnConnected(); @@ -73,15 +75,18 @@ private: uint32 cur_groupid; uint32 last_groupid; - void OnKeepAlive(EQ::Timer *t); - std::unique_ptr m_connection; std::unique_ptr m_keepalive; ZoneEventScheduler *m_zone_scheduler; + + // server reload queue + std::mutex m_reload_mutex = {}; + std::map m_reload_queue = {}; public: ZoneEventScheduler *GetScheduler() const; void SetScheduler(ZoneEventScheduler *scheduler); + void SendReload(ServerReload::Type type, bool is_global = true); }; #endif diff --git a/zone/zone.cpp b/zone/zone.cpp index 0280ee8f5..eea9d8a97 100644 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -36,7 +36,7 @@ #include "../common/strings.h" #include "../common/eqemu_logsys.h" -#include "expedition.h" +#include "dynamic_zone.h" #include "guild_mgr.h" #include "map.h" #include "npc.h" @@ -64,6 +64,7 @@ #include "../common/repositories/ldon_trap_templates_repository.h" #include "../common/repositories/respawn_times_repository.h" #include "../common/repositories/npc_emotes_repository.h" +#include "../common/repositories/zone_state_spawns_repository.h" #include "../common/serverinfo.h" #include "../common/repositories/merc_stance_entries_repository.h" #include "../common/repositories/alternate_currency_repository.h" @@ -80,15 +81,12 @@ #endif extern bool staticzone; -extern PetitionList petition_list; extern QuestParserCollection* parse; extern uint32 numclients; extern WorldServer worldserver; extern Zone* zone; extern NpcScaleManager* npc_scale_manager; -Mutex MZoneShutdown; - volatile bool is_zone_loaded = false; Zone* zone = 0; @@ -172,10 +170,12 @@ bool Zone::Bootup(uint32 iZoneID, uint32 iInstanceID, bool is_static) { zone->RequestUCSServerStatus(); zone->StartShutdownTimer(); + DataBucket::LoadZoneCache(iZoneID, iInstanceID); + /* * Set Logging */ - LogSys.StartFileLogs(StringFormat("%s_version_%u_inst_id_%u_port_%u", zone->GetShortName(), zone->GetInstanceVersion(), zone->GetInstanceID(), ZoneConfig::get()->ZonePort)); + EQEmuLogSys::Instance()->StartFileLogs(StringFormat("%s_version_%u_inst_id_%u_port_%u", zone->GetShortName(), zone->GetInstanceVersion(), zone->GetInstanceID(), ZoneConfig::get()->ZonePort)); return true; } @@ -877,62 +877,73 @@ void Zone::Shutdown(bool quiet) return; } + DataBucket::DeleteCachedBuckets(DataBucketLoadType::Zone, zone->GetZoneID(), zone->GetInstanceID()); + // save and kick all clients + for (auto c : entity_list.GetClientList()) { + c.second->Save(); + c.second->WorldKick(); + } + + if (m_save_zone_state && RuleB(Zone, StateSavingOnShutdown) && zone && zone->IsLoaded()) { + SaveZoneState(); + } + entity_list.StopMobAI(); std::map::iterator itr; - while (!zone->npctable.empty()) { - itr = zone->npctable.begin(); + while (!npctable.empty()) { + itr = npctable.begin(); delete itr->second; itr->second = nullptr; - zone->npctable.erase(itr); + npctable.erase(itr); } - while (!zone->merctable.empty()) { - itr = zone->merctable.begin(); + while (!merctable.empty()) { + itr = merctable.begin(); delete itr->second; itr->second = nullptr; - zone->merctable.erase(itr); + merctable.erase(itr); } - zone->adventure_entry_list_flavor.clear(); + adventure_entry_list_flavor.clear(); std::map::iterator itr4; - while (!zone->ldon_trap_list.empty()) { - itr4 = zone->ldon_trap_list.begin(); + while (!ldon_trap_list.empty()) { + itr4 = ldon_trap_list.begin(); delete itr4->second; itr4->second = nullptr; - zone->ldon_trap_list.erase(itr4); + ldon_trap_list.erase(itr4); } - zone->ldon_trap_entry_list.clear(); + ldon_trap_entry_list.clear(); LogInfo( "Zone [{}] zone_id [{}] version [{}] instance_id [{}]", - zone->GetShortName(), - zone->GetZoneID(), - zone->GetInstanceVersion(), - zone->GetInstanceID() + GetShortName(), + GetZoneID(), + GetInstanceVersion(), + GetInstanceID() ); - petition_list.ClearPetitions(); - zone->SetZoneHasCurrentTime(false); + PetitionList::Instance()->ClearPetitions(); + SetZoneHasCurrentTime(false); if (!quiet) { LogInfo( "Zone [{}] zone_id [{}] version [{}] instance_id [{}] Going to sleep", - zone->GetShortName(), - zone->GetZoneID(), - zone->GetInstanceVersion(), - zone->GetInstanceID() + GetShortName(), + GetZoneID(), + GetInstanceVersion(), + GetInstanceID() ); } is_zone_loaded = false; - zone->ResetAuth(); + ResetAuth(); safe_delete(zone); entity_list.ClearAreas(); parse->ReloadQuests(true); UpdateWindowTitle(nullptr); - LogSys.CloseFileLogs(); + EQEmuLogSys::Instance()->CloseFileLogs(); if (RuleB(Zone, KillProcessOnDynamicShutdown)) { LogInfo("Shutting down"); @@ -996,6 +1007,7 @@ Zone::Zone(uint32 in_zoneid, uint32 in_instanceid, const char* in_short_name) SetIdleWhenEmpty(true); SetSecondsBeforeIdle(60); + SetSaveZoneState(false); if (database.GetServerType() == 1) { pvpzone = true; @@ -1091,16 +1103,22 @@ Zone::Zone(uint32 in_zoneid, uint32 in_instanceid, const char* in_short_name) mMovementManager = &MobMovementManager::Get(); - SetNpcPositionUpdateDistance(0); SetQuestHotReloadQueued(false); } Zone::~Zone() { + LogInfo("Zone destructor called for zone [{}]", short_name); + spawn2_list.Clear(); if (worldserver.Connected()) { worldserver.SetZoneData(0); } + + for (auto &e: npc_emote_list) { + safe_delete(e); + } npc_emote_list.clear(); + zone_point_list.Clear(); entity_list.Clear(); parse->ReloadQuests(); @@ -1157,7 +1175,6 @@ bool Zone::Init(bool is_static) { LoadDynamicZoneTemplates(); DynamicZone::CacheAllFromDatabase(); - Expedition::CacheAllFromDatabase(); content_db.LoadGlobalLoot(); @@ -1171,6 +1188,11 @@ bool Zone::Init(bool is_static) { RespawnTimesRepository::ClearExpiredRespawnTimers(database); + // Loading zone variables so they're available for things like encounter_load + if (RuleB(Zone, StateSavingOnShutdown)) { + zone->LoadZoneVariablesState(); + } + // make sure that anything that needs to be loaded prior to scripts is loaded before here // this is to ensure that the scripts have access to the data they need parse->ReloadQuests(true); @@ -1185,6 +1207,8 @@ bool Zone::Init(bool is_static) { } content_db.PopulateZoneSpawnList(zoneid, spawn2_list, GetInstanceVersion()); + SetSaveZoneState(true); + database.LoadCharacterCorpses(zoneid, instanceid); content_db.LoadTraps(short_name, GetInstanceVersion()); @@ -1209,17 +1233,17 @@ bool Zone::Init(bool is_static) { LoadMercenarySpells(); } - petition_list.ClearPetitions(); - petition_list.ReadDatabase(); + PetitionList::Instance()->ClearPetitions(); + PetitionList::Instance()->ReadDatabase(); guild_mgr.LoadGuilds(); LogInfo("Zone booted successfully zone_id [{}] time_offset [{}]", zoneid, zone_time.getEQTimeZone()); // logging origination information - LogSys.origination_info.zone_short_name = zone->short_name; - LogSys.origination_info.zone_long_name = zone->long_name; - LogSys.origination_info.instance_id = zone->instanceid; + EQEmuLogSys::Instance()->origination_info.zone_short_name = zone->short_name; + EQEmuLogSys::Instance()->origination_info.zone_long_name = zone->long_name; + EQEmuLogSys::Instance()->origination_info.instance_id = zone->instanceid; return true; } @@ -1257,7 +1281,6 @@ void Zone::ReloadStaticData() { LoadVeteranRewards(); LoadAlternateCurrencies(); - npc_emote_list.clear(); LoadNPCEmotes(&npc_emote_list); //load the zone config file. @@ -1268,7 +1291,7 @@ void Zone::ReloadStaticData() { ); } // if that fails, try the file name, then load defaults - content_service.SetExpansionContext()->ReloadContentFlags(); + WorldContentService::Instance()->SetExpansionContext()->ReloadContentFlags(); LogInfo("Zone Static Data Reloaded"); @@ -1276,7 +1299,7 @@ void Zone::ReloadStaticData() { bool Zone::LoadZoneCFG(const char* filename, uint16 instance_version) { - auto z = zone_store.GetZoneWithFallback(ZoneID(filename), instance_version); + auto z = ZoneStore::Instance()->GetZoneWithFallback(ZoneID(filename), instance_version); if (!z) { LogError("Failed to load zone data for [{}] instance_version [{}]", filename, instance_version); @@ -1370,17 +1393,17 @@ bool Zone::LoadZoneCFG(const char* filename, uint16 instance_version) newzone_data.suspend_buffs = z->suspendbuffs; // local attributes - can_bind = z->canbind != 0; - is_city = z->canbind == 2; - can_combat = z->cancombat != 0; - can_levitate = z->canlevitate != 0; - can_castoutdoor = z->castoutdoor != 0; - is_hotzone = z->hotzone != 0; - max_movement_update_range = z->max_movement_update_range; - default_ruleset = z->ruleset; - allow_mercs = true; - m_graveyard_id = z->graveyard_id; - m_max_clients = z->maxclients; + can_bind = z->canbind != 0; + is_city = z->canbind == 2; + can_combat = z->cancombat != 0; + can_levitate = z->canlevitate != 0; + can_castoutdoor = z->castoutdoor != 0; + is_hotzone = z->hotzone != 0; + m_client_update_range = z->client_update_range; + default_ruleset = z->ruleset; + allow_mercs = true; + m_graveyard_id = z->graveyard_id; + m_max_clients = z->maxclients; SetIdleWhenEmpty(z->idle_when_empty); SetSecondsBeforeIdle(z->seconds_before_idle); @@ -1516,7 +1539,6 @@ bool Zone::Process() { spawn_conditions.Process(); if (spawn2_timer.Check()) { - LinkedListIterator iterator(spawn2_list); EQ::InventoryProfile::CleanDirty(); @@ -1534,10 +1556,6 @@ bool Zone::Process() { if (adv_data && !did_adventure_actions) { DoAdventureActions(); } - - if (GetNpcPositionUpdateDistance() == 0) { - CalculateNpcUpdateDistanceSpread(); - } } if (hot_reload_timer.Check() && IsQuestHotReloadQueued()) { @@ -1623,7 +1641,7 @@ bool Zone::Process() { if (minutes_warning > 0) { // expedition expire warnings are handled by world - auto expedition = Expedition::FindCachedExpeditionByZoneInstance(GetZoneID(), GetInstanceID()); + auto expedition = DynamicZone::FindExpeditionByZone(GetZoneID(), GetInstanceID()); if (!expedition) { entity_list.ExpeditionWarning(minutes_warning); @@ -1675,6 +1693,25 @@ bool Zone::Process() { } } + const bool has_timer_event = parse->ZoneHasQuestSub(EVENT_TIMER); + + for (auto e : zone_timers) { + if (e.timer_.Enabled() && e.timer_.Check()) { + if (has_timer_event) { + parse->EventZone(EVENT_TIMER, this, e.name); + } + } + } + + if (!m_zone_signals.empty()) { + int signal_id = m_zone_signals.front(); + m_zone_signals.pop_front(); + + if (parse->ZoneHasQuestSub(EVENT_SIGNAL)) { + parse->EventZone(EVENT_SIGNAL, this, std::to_string(signal_id), 0); + } + } + mMovementManager->Process(); return true; @@ -1917,6 +1954,8 @@ void Zone::Repop(bool is_forced) quest_manager.ClearAllTimers(); + StopAllTimers(); + LogInfo("Loading spawn groups"); if (!content_db.LoadSpawnGroups(short_name, GetInstanceVersion(), &spawn_group_list)) { LogError("Loading spawn groups failed"); @@ -1924,6 +1963,10 @@ void Zone::Repop(bool is_forced) spawn_conditions.LoadSpawnConditions(short_name, instanceid); + if (RuleB(Zone, StateSavingOnShutdown)) { + ClearZoneState(zoneid, instanceid); + } + if (!content_db.PopulateZoneSpawnList(zoneid, spawn2_list, GetInstanceVersion())) { LogDebug("Error in Zone::Repop: database.PopulateZoneSpawnList failed"); } @@ -2228,7 +2271,7 @@ void Zone::LoadZoneBlockedSpells() if (!content_db.LoadBlockedSpells(zone_total_blocked_spells, blocked_spells, GetZoneID())) { LogError( "Failed to load blocked spells for {} ({}).", - zone_store.GetZoneName(GetZoneID(), true), + ZoneStore::Instance()->GetZoneName(GetZoneID(), true), GetZoneID() ); ClearBlockedSpells(); @@ -2238,7 +2281,7 @@ void Zone::LoadZoneBlockedSpells() LogInfo( "Loaded [{}] blocked spells(s) for {} ({}).", Strings::Commify(zone_total_blocked_spells), - zone_store.GetZoneName(GetZoneID(), true), + ZoneStore::Instance()->GetZoneName(GetZoneID(), true), GetZoneID() ); } @@ -2575,6 +2618,10 @@ void Zone::DoAdventureActions() void Zone::LoadNPCEmotes(std::vector* v) { + for (auto &e: *v) { + safe_delete(e); + } + v->clear(); const auto& l = NpcEmotesRepository::All(content_db); @@ -2599,52 +2646,6 @@ void Zone::LoadNPCEmotes(std::vector* v) } -void Zone::ReloadWorld(uint8 global_repop) -{ - entity_list.ClearAreas(); - parse->ReloadQuests(); - - if (global_repop) { - if (global_repop == ReloadWorld::ForceRepop) { - zone->ClearSpawnTimers(); - } - - zone->Repop(); - } - - worldserver.SendEmoteMessage( - 0, - 0, - AccountStatus::GMAdmin, - Chat::Yellow, - fmt::format( - "Quests reloaded {}for {}{}.", - ( - global_repop ? - ( - global_repop == ReloadWorld::Repop ? - "and repopped NPCs " : - "and forcefully repopped NPCs " - ) : - "" - ), - fmt::format( - "{} ({})", - GetLongName(), - GetZoneID() - ), - ( - GetInstanceID() ? - fmt::format( - " (Instance ID {})", - GetInstanceID() - ) : - "" - ) - ).c_str() - ); -} - void Zone::ClearSpawnTimers() { LinkedListIterator iterator(spawn2_list); @@ -2727,62 +2728,6 @@ void Zone::SetUCSServerAvailable(bool ucss_available, uint32 update_timestamp) { m_ucss_available = ucss_available; } -int Zone::GetNpcPositionUpdateDistance() const -{ - return npc_position_update_distance; -} - -void Zone::SetNpcPositionUpdateDistance(int in_npc_position_update_distance) -{ - Zone::npc_position_update_distance = in_npc_position_update_distance; -} - -void Zone::CalculateNpcUpdateDistanceSpread() -{ - float max_x = 0; - float max_y = 0; - float min_x = 0; - float min_y = 0; - - auto &mob_list = entity_list.GetMobList(); - - for (auto &it : mob_list) { - Mob *entity = it.second; - if (!entity->IsNPC()) { - continue; - } - - if (entity->GetX() <= min_x) { - min_x = entity->GetX(); - } - - if (entity->GetY() <= min_y) { - min_y = entity->GetY(); - } - - if (entity->GetX() >= max_x) { - max_x = entity->GetX(); - } - - if (entity->GetY() >= max_y) { - max_y = entity->GetY(); - } - } - - int x_spread = int(std::abs(max_x - min_x)); - int y_spread = int(std::abs(max_y - min_y)); - int combined_spread = int(std::abs((x_spread + y_spread) / 2)); - int update_distance = EQ::ClampLower(int(combined_spread / 4), int(zone->GetMaxMovementUpdateRange())); - - SetNpcPositionUpdateDistance(update_distance); - - Log(Logs::General, Logs::Debug, - "NPC update spread distance set to [%i] combined_spread [%i]", - update_distance, - combined_spread - ); -} - bool Zone::IsQuestHotReloadQueued() const { return quest_hot_reload_queued; @@ -2909,8 +2854,6 @@ std::string Zone::GetZoneDescription() void Zone::SendReloadMessage(std::string reload_type) { - LogInfo("Reloaded [{}]", reload_type); - worldserver.SendEmoteMessage( 0, 0, @@ -2943,7 +2886,7 @@ void Zone::SendDiscordMessage(const std::string& webhook_name, const std::string bool not_found = true; for (int i= 0; i < MAX_DISCORD_WEBHOOK_ID; i++) { - auto &w = LogSys.GetDiscordWebhooks()[i]; + auto &w = EQEmuLogSys::Instance()->GetDiscordWebhooks()[i]; if (w.webhook_name == webhook_name) { SendDiscordMessage(w.id, message + "\n"); not_found = false; @@ -3064,12 +3007,7 @@ bool Zone::CompareDataBucket(uint8 comparison_type, const std::string& bucket, c void Zone::ReloadContentFlags() { - auto pack = new ServerPacket(ServerOP_ReloadContentFlags, 0); - if (pack) { - worldserver.SendPacket(pack); - } - - safe_delete(pack); + worldserver.SendReload(ServerReload::Type::ContentFlags); } void Zone::ClearEXPModifier(Client* c) @@ -3241,4 +3179,410 @@ bool Zone::DoesAlternateCurrencyExist(uint32 currency_id) ); } +std::string Zone::GetBucket(const std::string& bucket_name) +{ + DataBucketKey k = {}; + k.zone_id = zoneid; + k.instance_id = instanceid; + k.key = bucket_name; + + return DataBucket::GetData(k).value; +} + +void Zone::SetBucket(const std::string& bucket_name, const std::string& bucket_value, const std::string& expiration) +{ + DataBucketKey k = {}; + k.zone_id = zoneid; + k.instance_id = instanceid; + k.key = bucket_name; + k.expires = expiration; + k.value = bucket_value; + + DataBucket::SetData(k); +} + +void Zone::DeleteBucket(const std::string& bucket_name) +{ + DataBucketKey k = {}; + k.zone_id = zoneid; + k.instance_id = instanceid; + k.key = bucket_name; + + DataBucket::DeleteData(k); +} + +std::string Zone::GetBucketExpires(const std::string& bucket_name) +{ + DataBucketKey k = {}; + k.zone_id = zoneid; + k.instance_id = instanceid; + k.key = bucket_name; + + return DataBucket::GetDataExpires(k); +} + +std::string Zone::GetBucketRemaining(const std::string& bucket_name) +{ + DataBucketKey k = {}; + k.zone_id = zoneid; + k.instance_id = instanceid; + k.key = bucket_name; + + return DataBucket::GetDataRemaining(k); +} + +void Zone::DisableRespawnTimers() +{ + LinkedListIterator e(spawn2_list); + + e.Reset(); + + while (e.MoreElements()) { + e.GetData()->SetRespawnTimer(std::numeric_limits::max()); + e.Advance(); + } +} + +bool Zone::ClearVariables() +{ + if (m_zone_variables.empty()) { + return false; + } + + m_zone_variables.clear(); + return true; +} + +bool Zone::DeleteVariable(const std::string& variable_name) +{ + if (m_zone_variables.empty() || variable_name.empty()) { + return false; + } + + auto v = m_zone_variables.find(variable_name); + if (v == m_zone_variables.end()) { + return false; + } + + m_zone_variables.erase(v); + + return true; +} + +std::string Zone::GetVariable(const std::string& variable_name) +{ + if (m_zone_variables.empty() || variable_name.empty()) { + return std::string(); + } + + const auto& v = m_zone_variables.find(variable_name); + + return v != m_zone_variables.end() ? v->second : std::string(); +} + +std::vector Zone::GetVariables() +{ + std::vector l; + + if (m_zone_variables.empty()) { + return l; + } + + l.reserve(m_zone_variables.size()); + + for (const auto& v : m_zone_variables) { + l.emplace_back(v.first); + } + + return l; +} + +void Zone::SetVariable(const std::string& variable_name, const std::string& variable_value) +{ + if (variable_name.empty()) { + return; + } + + m_zone_variables[variable_name] = variable_value; +} + +bool Zone::VariableExists(const std::string& variable_name) +{ + if (m_zone_variables.empty() || variable_name.empty()) { + return false; + } + + return m_zone_variables.find(variable_name) != m_zone_variables.end(); +} + +void Zone::ReloadMaps() +{ + zonemap = Map::LoadMapFile(map_name); + watermap = WaterMap::LoadWaterMapfile(map_name); + pathing = IPathfinder::Load(map_name); +} + +uint32 Zone::GetTimerDuration(std::string name) +{ + if (!IsLoaded() || zone_timers.empty()) { + return 0; + } + + const auto& e = std::find_if( + zone_timers.begin(), + zone_timers.end(), + [&name](ZoneTimer e) { + return e.name == name; + } + ); + + return e != zone_timers.end() ? e->timer_.GetDuration() : 0; +} + +uint32 Zone::GetTimerRemainingTime(std::string name) +{ + if (!IsLoaded() || zone_timers.empty()) { + return 0; + } + + const auto& e = std::find_if( + zone_timers.begin(), + zone_timers.end(), + [&name](ZoneTimer e) { + return e.name == name; + } + ); + + return e != zone_timers.end() ? e->timer_.GetRemainingTime() : 0; +} + +bool Zone::HasTimer(std::string name) +{ + if (!IsLoaded() || zone_timers.empty()) { + return false; + } + + const auto& e = std::find_if( + zone_timers.begin(), + zone_timers.end(), + [&name](ZoneTimer e) { + return e.name == name; + } + ); + + return e != zone_timers.end(); +} + +bool Zone::IsPausedTimer(std::string name) +{ + if (!IsLoaded() || paused_zone_timers.empty()) { + return false; + } + + const auto& e = std::find_if( + paused_zone_timers.begin(), + paused_zone_timers.end(), + [&name](PausedZoneTimer e) { + return e.name == name; + } + ); + + return e != paused_zone_timers.end(); +} + +void Zone::PauseTimer(std::string name) +{ + if ( + !IsLoaded() || + zone_timers.empty() || + !HasTimer(name) || + IsPausedTimer(name) + ) { + return; + } + + uint32 remaining_time = 0; + + const bool has_pause_event = parse->ZoneHasQuestSub(EVENT_TIMER_PAUSE); + + if (!zone_timers.empty()) { + for (auto e = zone_timers.begin(); e != zone_timers.end(); e++) { + if (e->name == name) { + remaining_time = e->timer_.GetRemainingTime(); + + zone_timers.erase(e); + + const std::string& export_string = fmt::format( + "{} {}", + name, + remaining_time + ); + + LogQuests( + "Pausing timer [{}] with [{}] ms remaining", + name, + remaining_time + ); + + paused_zone_timers.emplace_back( + PausedZoneTimer{ + .name = name, + .remaining_time = remaining_time + } + ); + + if (has_pause_event) { + parse->EventZone(EVENT_TIMER_PAUSE, this, export_string); + } + + break; + } + } + } +} + +void Zone::ResumeTimer(std::string name) +{ + if ( + !IsLoaded() || + paused_zone_timers.empty() || + !IsPausedTimer(name) + ) { + return; + } + + uint32 remaining_time = 0; + + if (!paused_zone_timers.empty()) { + for (auto e = paused_zone_timers.begin(); e != paused_zone_timers.end(); e++) { + if (e->name == name) { + remaining_time = e->remaining_time; + + paused_zone_timers.erase(e); + + if (!remaining_time) { + LogQuests("Paused timer [{}] not found or has expired.", name); + return; + } + + const std::string& export_string = fmt::format( + "{} {}", + name, + remaining_time + ); + + LogQuests( + "Creating a new timer and resuming [{}] with [{}] ms remaining", + name, + remaining_time + ); + + zone_timers.emplace_back(ZoneTimer(name, remaining_time)); + + if (parse->ZoneHasQuestSub(EVENT_TIMER_RESUME)) { + parse->EventZone(EVENT_TIMER_RESUME, this, export_string); + } + + break; + } + } + } +} + +void Zone::SetTimer(std::string name, uint32 duration) +{ + if (!IsLoaded() || HasTimer(name)) { + return; + } + + zone_timers.emplace_back(ZoneTimer(name, duration)); + + if (parse->ZoneHasQuestSub(EVENT_TIMER_START)) { + const std::string& export_string = fmt::format("{} {}", name, duration); + parse->EventZone(EVENT_TIMER_START, this, export_string); + } +} + +void Zone::StopTimer(std::string name) +{ + if ( + !IsLoaded() || + zone_timers.empty() || + !HasTimer(name) || + IsPausedTimer(name) + ) { + return; + } + + const bool has_stop_event = parse->ZoneHasQuestSub(EVENT_TIMER_STOP); + + for (auto e = zone_timers.begin(); e != zone_timers.end(); e++) { + if (e->name == name) { + if (has_stop_event) { + parse->EventZone(EVENT_TIMER_STOP, this, name); + } + + zone_timers.erase(e); + break; + } + } +} + +void Zone::StopAllTimers() +{ + if (!IsLoaded() || zone_timers.empty()) { + return; + } + + const bool has_stop_event = parse->ZoneHasQuestSub(EVENT_TIMER_STOP); + + for (auto e = zone_timers.begin(); e != zone_timers.end();) { + if (has_stop_event) { + parse->EventZone(EVENT_TIMER_STOP, this, e->name); + } + + e = zone_timers.erase(e); + } +} + +void Zone::Signal(int signal_id) +{ + m_zone_signals.push_back(signal_id); +} + +void Zone::SendPayload(int payload_id, std::string payload_value) +{ + if (parse->ZoneHasQuestSub(EVENT_PAYLOAD)) { + const auto& export_string = fmt::format("{} {}", payload_id, payload_value); + + parse->EventZone(EVENT_PAYLOAD, this, export_string, 0); + } +} + +std::vector Zone::GetPausedTimers() +{ + std::vector v; + + if (!paused_zone_timers.empty()) { + for (auto e = paused_zone_timers.begin(); e != paused_zone_timers.end(); e++) { + v.emplace_back(e->name); + } + } + + return v; +} + +std::vector Zone::GetTimers() +{ + std::vector v; + + if (!zone_timers.empty()) { + for (auto e = zone_timers.begin(); e != zone_timers.end(); e++) { + v.emplace_back(e->name); + } + } + + return v; +} + #include "zone_loot.cpp" diff --git a/zone/zone.h b/zone/zone.h index c29a89b29..fe9d39650 100755 --- a/zone/zone.h +++ b/zone/zone.h @@ -47,6 +47,9 @@ #include "../common/repositories/lootdrop_entries_repository.h" #include "../common/repositories/base_data_repository.h" #include "../common/repositories/skill_caps_repository.h" +#include "../common/repositories/zone_state_spawns_repository.h" +#include "../common/repositories/spawn2_disabled_repository.h" +#include "../common/repositories/player_titlesets_repository.h" struct EXPModifier { @@ -93,7 +96,6 @@ struct ZoneEXPModInfo { }; class Client; -class Expedition; class Map; class Mob; class WaterMap; @@ -105,7 +107,7 @@ class MobMovementManager; class Zone { public: static bool Bootup(uint32 iZoneID, uint32 iInstanceID, bool is_static = false); - static void Shutdown(bool quiet = false); + void Shutdown(bool quiet = false); Zone(uint32 in_zoneid, uint32 in_instanceid, const char *in_short_name); ~Zone(); @@ -155,9 +157,7 @@ public: bool Process(); bool SaveZoneCFG(); bool DoesAlternateCurrencyExist(uint32 currency_id); - - int GetNpcPositionUpdateDistance() const; - void SetNpcPositionUpdateDistance(int in_npc_position_update_distance); + void DisableRespawnTimers(); char *adv_data; @@ -198,6 +198,13 @@ public: int32 MobsAggroCount() { return aggroedmobs; } DynamicZone *GetDynamicZone(); + bool ClearVariables(); + bool DeleteVariable(const std::string& variable_name); + std::string GetVariable(const std::string& variable_name); + std::vector GetVariables(); + void SetVariable(const std::string& variable_name, const std::string& variable_value); + bool VariableExists(const std::string& variable_name); + IPathfinder *pathing; std::vector npc_emote_list; LinkedList spawn2_list; @@ -239,11 +246,14 @@ public: std::vector zone_grid_entries; std::unordered_map> dynamic_zone_cache; - std::unordered_map> expedition_cache; std::unordered_map dz_template_cache; std::unordered_map exp_modifiers; + std::vector discovered_items; + + std::map m_zone_variables; + time_t weather_timer; Timer spawn2_timer; Timer hot_reload_timer; @@ -313,7 +323,6 @@ public: void LoadVeteranRewards(); void LoadZoneDoors(); void ReloadStaticData(); - void ReloadWorld(uint8 global_repop); void RemoveAuth(const char *iCharName, const char *iLSKey); void RemoveAuth(uint32 lsid); void Repop(bool is_forced = false); @@ -345,6 +354,9 @@ public: uint32 GetInstanceTimeRemaining() const; void SetInstanceTimeRemaining(uint32 instance_time_remaining); + inline bool GetSaveZoneState() const { return m_save_zone_state; } + inline void SetSaveZoneState(bool save_state) { m_save_zone_state = save_state; } + /** * GMSay Callback for LogSys * @@ -375,7 +387,7 @@ public: entity_list.MessageStatus( 0, AccountStatus::QuestTroupe, - LogSys.GetGMSayColorFromCategory(log_category), + EQEmuLogSys::Instance()->GetGMSayColorFromCategory(log_category), message_split[0].c_str() ); @@ -383,7 +395,7 @@ public: entity_list.MessageStatus( 0, AccountStatus::QuestTroupe, - LogSys.GetGMSayColorFromCategory(log_category), + EQEmuLogSys::Instance()->GetGMSayColorFromCategory(log_category), fmt::format( "--- {}", message_split[iter] @@ -395,7 +407,7 @@ public: entity_list.MessageStatus( 0, AccountStatus::QuestTroupe, - LogSys.GetGMSayColorFromCategory(log_category), + EQEmuLogSys::Instance()->GetGMSayColorFromCategory(log_category), fmt::format("[{}] [{}] {}", Logs::LogCategoryName[log_category], func, message).c_str() ); } @@ -406,18 +418,18 @@ public: static void DiscordWebhookMessageHandler(uint16 log_category, int webhook_id, const std::string &message) { std::string message_prefix; - if (!LogSys.origination_info.zone_short_name.empty()) { + if (!EQEmuLogSys::Instance()->origination_info.zone_short_name.empty()) { message_prefix = fmt::format( "[**{}**] **Zone** [**{}**] ", Logs::LogCategoryName[log_category], - LogSys.origination_info.zone_short_name + EQEmuLogSys::Instance()->origination_info.zone_short_name ); } SendDiscordMessage(webhook_id, message_prefix + Discord::FormatDiscordMessage(log_category, message)); }; - double GetMaxMovementUpdateRange() const { return max_movement_update_range; } + double GetClientUpdateRange() const { return m_client_update_range; } void SetIsHotzone(bool is_hotzone); @@ -441,6 +453,7 @@ public: // loot void LoadLootTable(const uint32 loottable_id); void LoadLootTables(const std::vector in_loottable_ids); + void LoadLootDrops(const std::vector in_lootdrop_ids); void ClearLootTables(); void ReloadLootTables(); LoottableRepository::Loottable *GetLootTable(const uint32 loottable_id); @@ -454,6 +467,44 @@ public: void LoadBaseData(); void ReloadBaseData(); + // data buckets + std::string GetBucket(const std::string& bucket_name); + void SetBucket(const std::string& bucket_name, const std::string& bucket_value, const std::string& expiration = ""); + void DeleteBucket(const std::string& bucket_name); + std::string GetBucketExpires(const std::string& bucket_name); + std::string GetBucketRemaining(const std::string& bucket_name); + inline void SetZoneServerId(uint32 id) { m_zone_server_id = id; } + inline uint32 GetZoneServerId() const { return m_zone_server_id; } + + // zone state + bool LoadZoneVariablesState(); + bool LoadZoneState( + std::unordered_map spawn_times, + std::vector disabled_spawns + ); + void SaveZoneState(); + static void ClearZoneState(uint32 zone_id, uint32 instance_id); + void ReloadMaps(); + + void Signal(int signal_id); + void SendPayload(int payload_id, std::string payload_value); + + struct PausedZoneTimer { + std::string name; + uint32 remaining_time; + }; + + uint32 GetTimerDuration(std::string name); + uint32 GetTimerRemainingTime(std::string name); + bool HasTimer(std::string name); + bool IsPausedTimer(std::string name); + void PauseTimer(std::string name); + void ResumeTimer(std::string name); + void SetTimer(std::string name, uint32 duration); + void StopTimer(std::string name); + void StopAllTimers(); + std::vector GetPausedTimers(); + std::vector GetTimers(); private: bool allow_mercs; @@ -469,7 +520,7 @@ private: bool staticzone; bool zone_has_current_time; bool quest_hot_reload_queued; - double max_movement_update_range; + double m_client_update_range; char *long_name; char *map_name; char *short_name; @@ -490,6 +541,7 @@ private: uint32 m_last_ucss_update; bool m_idle_when_empty; uint32 m_seconds_before_idle; + bool m_save_zone_state; GlobalLootManager m_global_loot; LinkedList client_auth_list; @@ -518,6 +570,20 @@ private: // Base Data std::vector m_base_data = { }; + + uint32_t m_zone_server_id = 0; + + class ZoneTimer { + public: + inline ZoneTimer(std::string _name, uint32 duration) + : name(_name), timer_(duration) { timer_.Start(duration, false); } + std::string name; + Timer timer_; + }; + + std::vector zone_timers; + std::vector paused_zone_timers; + std::deque m_zone_signals; }; #endif diff --git a/zone/zone_cli.cpp b/zone/zone_cli.cpp index 5739aa6ca..33177e8f6 100644 --- a/zone/zone_cli.cpp +++ b/zone/zone_cli.cpp @@ -12,6 +12,11 @@ bool ZoneCLI::RanSidecarCommand(int argc, char **argv) return argc > 1 && (strstr(argv[1], "sidecar:") != nullptr); } +bool ZoneCLI::RanTestCommand(int argc, char **argv) +{ + return argc > 1 && (strstr(argv[1], "tests:") != nullptr); +} + void ZoneCLI::CommandHandler(int argc, char **argv) { if (argc == 1) { return; } @@ -24,9 +29,23 @@ void ZoneCLI::CommandHandler(int argc, char **argv) auto function_map = EQEmuCommand::function_map; // Register commands - function_map["sidecar:serve-http"] = &ZoneCLI::SidecarServeHttp; + function_map["benchmark:databuckets"] = &ZoneCLI::BenchmarkDatabuckets; + function_map["sidecar:serve-http"] = &ZoneCLI::SidecarServeHttp; + function_map["tests:databuckets"] = &ZoneCLI::TestDataBuckets; + function_map["tests:npc-handins"] = &ZoneCLI::TestNpcHandins; + function_map["tests:npc-handins-multiquest"] = &ZoneCLI::TestNpcHandinsMultiQuest; + function_map["tests:zone-state"] = &ZoneCLI::TestZoneState; EQEmuCommand::HandleMenu(function_map, cmd, argc, argv); } +// cli +#include "cli/benchmark_databuckets.cpp" #include "cli/sidecar_serve_http.cpp" + +// tests +#include "cli/tests/_test_util.cpp" +#include "cli/tests/databuckets.cpp" +#include "cli/tests/npc_handins.cpp" +#include "cli/tests/npc_handins_multiquest.cpp" +#include "cli/tests/zone_state.cpp" diff --git a/zone/zone_cli.h b/zone/zone_cli.h index e9ed18367..7e5e6e81c 100644 --- a/zone/zone_cli.h +++ b/zone/zone_cli.h @@ -1,15 +1,21 @@ #ifndef EQEMU_ZONE_CLI_H #define EQEMU_ZONE_CLI_H +#include #include "../common/cli/argh.h" class ZoneCLI { public: static void CommandHandler(int argc, char **argv); + static void BenchmarkDatabuckets(int argc, char **argv, argh::parser &cmd, std::string &description); static void SidecarServeHttp(int argc, char **argv, argh::parser &cmd, std::string &description); static bool RanConsoleCommand(int argc, char **argv); static bool RanSidecarCommand(int argc, char **argv); + static bool RanTestCommand(int argc, char **argv); + static void TestDataBuckets(int argc, char **argv, argh::parser &cmd, std::string &description); + static void TestNpcHandins(int argc, char **argv, argh::parser &cmd, std::string &description); + static void TestNpcHandinsMultiQuest(int argc, char **argv, argh::parser &cmd, std::string &description); + static void TestZoneState(int argc, char **argv, argh::parser &cmd, std::string &description); }; - #endif //EQEMU_ZONE_CLI_H diff --git a/zone/zone_config.h b/zone/zone_config.h index a8e7401b6..e527e0601 100644 --- a/zone/zone_config.h +++ b/zone/zone_config.h @@ -43,11 +43,13 @@ class ZoneConfig : public EQEmuConfig { } // Load the config - static bool LoadConfig(const std::string& path = "") { - if (_zone_config != nullptr) - delete _zone_config; - _zone_config=new ZoneConfig; - _config=_zone_config; + static bool LoadConfig(const std::string &path = "") + { + safe_delete(_zone_config); + safe_delete(_config); + + _zone_config = new ZoneConfig; + _config = _zone_config; return _config->parseFile(path); } diff --git a/zone/zone_event_scheduler.h b/zone/zone_event_scheduler.h index a2e6c67ad..f92442402 100644 --- a/zone/zone_event_scheduler.h +++ b/zone/zone_event_scheduler.h @@ -9,6 +9,12 @@ class ZoneEventScheduler : public ServerEventScheduler { public: void Process(Zone *zone, WorldContentService *content_service); void SyncEventDataWithActiveEvents(); + + static ZoneEventScheduler* Instance() + { + static ZoneEventScheduler instance; + return &instance; + } }; #endif //EQEMU_ZONE_EVENT_SCHEDULER_H diff --git a/zone/zone_loot.cpp b/zone/zone_loot.cpp index 697a2634f..9a09bfcad 100644 --- a/zone/zone_loot.cpp +++ b/zone/zone_loot.cpp @@ -211,7 +211,7 @@ LoottableRepository::Loottable *Zone::GetLootTable(const uint32 loottable_id) { for (auto &e: m_loottables) { if (e.id == loottable_id) { - if (!content_service.DoesPassContentFiltering( + if (!WorldContentService::Instance()->DoesPassContentFiltering( ContentFlags{ .min_expansion = e.min_expansion, .max_expansion = e.max_expansion, @@ -249,7 +249,7 @@ LootdropRepository::Lootdrop Zone::GetLootdrop(const uint32 lootdrop_id) const { for (const auto &e: m_lootdrops) { if (e.id == lootdrop_id) { - if (!content_service.DoesPassContentFiltering( + if (!WorldContentService::Instance()->DoesPassContentFiltering( ContentFlags{ .min_expansion = e.min_expansion, .max_expansion = e.max_expansion, @@ -276,7 +276,7 @@ std::vector Zone::GetLootdropEntries std::vector entries = {}; for (const auto &e: m_lootdrop_entries) { if (e.lootdrop_id == lootdrop_id) { - if (!content_service.DoesPassContentFiltering( + if (!WorldContentService::Instance()->DoesPassContentFiltering( ContentFlags{ .min_expansion = e.min_expansion, .max_expansion = e.max_expansion, @@ -300,3 +300,96 @@ std::vector Zone::GetLootdropEntries return entries; } +void Zone::LoadLootDrops(const std::vector in_lootdrop_ids) +{ + BenchTimer timer; + + // copy lootdrop_ids + std::vector lootdrop_ids = in_lootdrop_ids; + + // check if lootdrop is already loaded + std::vector loaded_drops = {}; + for (const auto &e: lootdrop_ids) { + for (const auto &f: m_lootdrops) { + if (e == f.id) { + LogLootDetail("Lootdrop [{}] already loaded", e); + loaded_drops.push_back(e); + } + } + } + + // remove loaded drops from lootdrop_ids + for (const auto &e: loaded_drops) { + lootdrop_ids.erase( + std::remove( + lootdrop_ids.begin(), + lootdrop_ids.end(), + e + ), + lootdrop_ids.end() + ); + } + + if (lootdrop_ids.empty()) { + LogLootDetail("No lootdrops to load"); + return; + } + + auto lootdrops = LootdropRepository::GetWhere( + content_db, + fmt::format( + "id IN ({})", + Strings::Join(lootdrop_ids, ",") + ) + ); + + auto lootdrop_entries = LootdropEntriesRepository::GetWhere( + content_db, + fmt::format( + "lootdrop_id IN ({})", + Strings::Join(lootdrop_ids, ",") + ) + ); + + // emplace back drops to m_lootdrops if not exists + for (const auto &e: lootdrops) { + bool has_drop = false; + + for (const auto &l: m_lootdrops) { + if (e.id == l.id) { + has_drop = true; + break; + } + } + + bool has_entry = false; + if (!has_drop) { + // add lootdrop + m_lootdrops.emplace_back(e); + + // add lootdrop entries + // add lootdrop entries + for (const auto &h: lootdrop_entries) { + if (e.id == h.lootdrop_id) { + + // check if lootdrop entry already exists in memory + has_entry = false; + for (const auto &i: m_lootdrop_entries) { + if (h.lootdrop_id == i.lootdrop_id && h.item_id == i.item_id) { + has_entry = true; + break; + } + } + + if (!has_entry) { + m_lootdrop_entries.emplace_back(h); + } + } + } + } + } + + if (!lootdrop_ids.empty()) { + LogInfo("Loaded [{}] lootdrops ({}s)", m_lootdrops.size(), std::to_string(timer.elapsed())); + } +} diff --git a/zone/zone_save_state.cpp b/zone/zone_save_state.cpp new file mode 100644 index 000000000..f36f3ee1c --- /dev/null +++ b/zone/zone_save_state.cpp @@ -0,0 +1,829 @@ +#include +#include +#include +#include "npc.h" +#include "corpse.h" +#include "zone.h" +#include "zone_save_state.h" +#include "../common/repositories/spawn2_repository.h" +#include "../common/repositories/criteria/content_filter_criteria.h" + +// IsZoneStateValid checks if the zone state is valid +// if these fields are all empty or zero value for an entire zone state, it's considered invalid +inline bool IsZoneStateValid(std::vector &spawns) +{ + return std::any_of( + spawns.begin(), spawns.end(), [](const auto &s) { + return !( + s.hp == 0 && + s.mana == 0 && + s.endurance == 0 && + s.loot_data.empty() && + s.entity_variables.empty() && + s.buffs.empty() + ); + } + ); +} + +inline void LoadLootStateData(Zone *zone, NPC *npc, const std::string &loot_data) +{ + LootStateData l{}; + + // in the event that should never happen, we roll loot from the NPC's table + if (loot_data.empty()) { + LogZoneState("No loot state data found for NPC [{}], re-rolling", npc->GetNPCTypeID()); + npc->ClearLootItems(); + npc->AddLootTable(); + if (npc->DropsGlobalLoot()) { + npc->CheckGlobalLootTables(); + } + + return; + } + + if (!Strings::IsValidJson(loot_data)) { + LogZoneState("Invalid JSON data for NPC [{}]", npc->GetNPCTypeID()); + return; + } + + try { + std::stringstream ss; + { + ss << loot_data; + cereal::JSONInputArchive ar(ss); + l.serialize(ar); + } + } catch (const std::exception &e) { + LogZoneState("Failed to load loot state data for NPC [{}] [{}]", npc->GetNPCTypeID(), e.what()); + return; + } + + // reset + npc->RemoveLootCash(); + npc->ClearLootItems(); + + // add loot + npc->AddLootCash(l.copper, l.silver, l.gold, l.platinum); + + for (auto &e: l.entries) { + const auto *db_item = database.GetItem(e.item_id); + if (!db_item) { + continue; + } + + // dynamically added via AddItem + if (e.lootdrop_id == 0) { + npc->AddItem(e.item_id, e.charges, true); + continue; + } + + const auto entries = zone->GetLootdropEntries(e.lootdrop_id); + if (entries.empty()) { + continue; + } + + LootdropEntriesRepository::LootdropEntries lootdrop_entry; + + for (auto &le: entries) { + if (e.item_id == le.item_id) { + lootdrop_entry = le; + break; + } + } + + npc->AddLootDrop(db_item, lootdrop_entry); + } +} + +inline std::string GetLootSerialized(NPC *npc) +{ + LootStateData ls = {}; + auto loot_items = npc->GetLootItems(); // Assuming this returns a list of loot items + ls.copper = npc->GetCopper(); + ls.silver = npc->GetSilver(); + ls.gold = npc->GetGold(); + ls.platinum = npc->GetPlatinum(); + ls.entries.reserve(loot_items.size()); + + for (auto &l: loot_items) { + ls.entries.emplace_back( + LootEntryStateData{ + .item_id = l->item_id, + .lootdrop_id = l->lootdrop_id, + .charges = l->charges, + } + ); + } + + try { + std::stringstream ss; + { + cereal::JSONOutputArchiveSingleLine ar(ss); + ls.serialize(ar); + } + + return ss.str(); + } catch (const std::exception &e) { + LogZoneState("Failed to serialize loot data for NPC [{}] [{}]", npc->GetNPCTypeID(), e.what()); + return ""; + } + + return ""; +} + +inline std::string GetLootSerialized(Corpse *c) +{ + LootStateData ls = {}; + auto loot_items = c->GetLootItems(); // Assuming this returns a list of loot items + ls.copper = c->GetCopper(); + ls.silver = c->GetSilver(); + ls.gold = c->GetGold(); + ls.platinum = c->GetPlatinum(); + ls.entries.reserve(loot_items.size()); + + for (auto &l: loot_items) { + ls.entries.emplace_back( + LootEntryStateData{ + .item_id = l->item_id, + .lootdrop_id = l->lootdrop_id, + } + ); + } + + try { + std::stringstream ss; + { + cereal::JSONOutputArchiveSingleLine ar(ss); + ls.serialize(ar); + } + + return ss.str(); + } catch (const std::exception &e) { + LogZoneState("Failed to serialize loot data for Corpse [{}] [{}]", c->GetID(), e.what()); + return ""; + } + + return ""; +} + +inline std::map GetVariablesDeserialized(const std::string &entity_variables) +{ + std::map deserialized_map; + + if (entity_variables.empty()) { + return deserialized_map; + } + + if (!Strings::IsValidJson(entity_variables)) { + LogZoneState("Invalid JSON data for entity variables"); + return deserialized_map; + } + + try { + std::stringstream ss; + { + ss << entity_variables; + cereal::JSONInputArchive ar(ss); + ar(deserialized_map); + } + } catch (const std::exception &e) { + LogZoneState("Failed to load entity variables [{}]", e.what()); + } + + return deserialized_map; +} + +inline void LoadNPCEntityVariables(NPC *n, const std::string &entity_variables) +{ + if (!RuleB(Zone, StateSaveEntityVariables)) { + return; + } + + if (entity_variables.empty()) { + return; + } + + for (const auto &[key, value]: GetVariablesDeserialized(entity_variables)) { + n->SetEntityVariable(key, value); + } +} + +inline void LoadNPCBuffs(NPC *n, const std::string &buffs) +{ + if (!RuleB(Zone, StateSaveBuffs)) { + return; + } + + if (buffs.empty()) { + return; + } + + if (!Strings::IsValidJson(buffs)) { + LogZoneState("Invalid JSON data for NPC [{}]", n->GetNPCTypeID()); + return; + } + + std::vector valid_buffs; + try { + std::istringstream is(buffs); + { + cereal::JSONInputArchive archive(is); + archive(cereal::make_nvp("buffs", valid_buffs)); + } + } + catch (const std::exception &e) { + LogZoneState("Failed to load entity variables for NPC [{}] [{}]", n->GetNPCTypeID(), e.what()); + return; + } + + n->LoadBuffsFromState(valid_buffs); +} + +inline std::vector GetLootdropIds(const std::vector &spawn_states) +{ + LogInfo("Loading lootdrop ids for zone state spawns"); + + std::vector lootdrop_ids; + + for (auto &s: spawn_states) { + if (s.loot_data.empty()) { + continue; + } + + if (!Strings::IsValidJson(s.loot_data)) { + continue; + } + + LootStateData l{}; + try { + std::stringstream ss; + { + ss << s.loot_data; + cereal::JSONInputArchive ar(ss); + l.serialize(ar); + } + } + catch (const std::exception &e) { + LogZoneState("Failed to load loot state data for spawn2 [{}] [{}]", s.id, e.what()); + continue; + } + + for (auto &e: l.entries) { + // make sure it isn't already in the list + if (std::find(lootdrop_ids.begin(), lootdrop_ids.end(), e.lootdrop_id) == lootdrop_ids.end()) { + lootdrop_ids.push_back(e.lootdrop_id); + } + } + } + + LogInfo("Loaded [{}] lootdrop id(s)", lootdrop_ids.size()); + + return lootdrop_ids; +} + +inline void LoadNPCStatePreSpawn(Zone *zone, NPC *n, ZoneStateSpawnsRepository::ZoneStateSpawns &s) +{ + LoadNPCEntityVariables(n, s.entity_variables); +} + +inline void LoadNPCState(Zone *zone, NPC *n, ZoneStateSpawnsRepository::ZoneStateSpawns &s) +{ + if (s.hp > 0) { + n->SetHP(s.hp); + } + if (s.mana > 0) { + n->SetMana(s.mana); + } + if (s.endurance > 0) { + n->SetEndurance(s.endurance); + } + + // if these are zero for some reason, we need to reset the max hp + if (!s.is_corpse) { + if (s.hp == 0 || n->GetHP() == 0) { + n->SetMaxHP(); + } + if (s.mana == 0 || n->GetMana() == 0) { + n->RestoreMana(); + } + } + + if (s.grid) { + n->AssignWaypoints(s.grid, s.current_waypoint); + } + + n->SetResumedFromZoneSuspend(false); + LoadLootStateData(zone, n, s.loot_data); + n->SetResumedFromZoneSuspend(true); + LoadNPCBuffs(n, s.buffs); + + if (s.is_corpse) { + auto decay_time = s.decay_in_seconds * 1000; + if (decay_time > 0) { + n->SetQueuedToCorpse(); + entity_list.RestoreCorpse(n, decay_time); + } + else { + n->Depop(); + } + } + + n->SetPosition(s.x, s.y, s.z); + n->SetHeading(s.heading); + n->SetResumedFromZoneSuspend(true); +} + +inline std::string GetZoneVariablesSerialized(Zone *z) +{ + std::map variables; + + for (const auto &k: z->GetVariables()) { + variables[k] = z->GetVariable(k); + } + + try { + std::ostringstream os; + { + cereal::JSONOutputArchiveSingleLine archive(os); + archive(variables); + } + return os.str(); + } + catch (const std::exception &e) { + LogZoneState("Failed to serialize variables for zone [{}]", e.what()); + return ""; + } + + return ""; +} + +inline void LoadZoneVariables(Zone *z, const std::string &variables) +{ + if (!Strings::IsValidJson(variables)) { + LogZoneState("Invalid JSON data for zone [{}]", variables); + return; + } + + std::map deserialized_map; + try { + std::istringstream is(variables); + { + cereal::JSONInputArchive archive(is); + archive(deserialized_map); + } + } + catch (const std::exception &e) { + LogZoneState("Failed to load zone variables [{}]", e.what()); + return; + } + + for (const auto &[key, value]: deserialized_map) { + z->SetVariable(key, value); + } +} + +bool Zone::LoadZoneVariablesState() +{ + auto spawn_states = ZoneStateSpawnsRepository::GetWhere( + database, + fmt::format( + "zone_id = {} AND instance_id = {} AND is_zone = 1 ORDER BY spawn2_id", + zoneid, + zone->GetInstanceID() + ) + ); + + if (spawn_states.empty()) { + return false; + } + + for (auto &s: spawn_states) { + if (s.is_zone) { + LoadZoneVariables(zone, s.entity_variables); + return true; + } + } + + return false; +} + +bool Zone::LoadZoneState( + std::unordered_map spawn_times, + std::vector disabled_spawns +) +{ + auto spawn_states = ZoneStateSpawnsRepository::GetWhere( + database, + fmt::format( + "zone_id = {} AND instance_id = {} AND is_zone = 0 ORDER BY spawn2_id", + zoneid, + zone->GetInstanceID() + ) + ); + + if (spawn_states.empty()) { + LogInfo("No zone state spawns found for zone [{}] instance [{}]", GetShortName(), zone->GetInstanceID()); + return false; + } + + LogInfo("Loading zone state spawns for zone [{}] instance [{}] spawns [{}]", GetShortName(), zone->GetInstanceID(), spawn_states.size()); + + if (!IsZoneStateValid(spawn_states)) { + LogZoneState("Invalid zone state data for zone [{}]", GetShortName()); + ClearZoneState(zoneid, zone->GetInstanceID()); + return false; + } + + std::vector lootdrop_ids = GetLootdropIds(spawn_states); + zone->LoadLootDrops(lootdrop_ids); + + // we have to load grids first otherwise setting grid/wp will not work + zone->initgrids_timer.Trigger(); + zone->Process(); + + // load base spawn2 data for spawn locations + std::vector spawn2_ids; + for (auto &s: spawn_states) { + if (s.spawn2_id > 0) { + spawn2_ids.push_back(std::to_string(s.spawn2_id)); + } + } + + std::vector spawn2s; + if (!spawn2_ids.empty()) { + spawn2s = Spawn2Repository::GetWhere( + content_db, + fmt::format( + "id IN ({})", + Strings::Join(spawn2_ids, ",") + ) + ); + + LogZoneState("Loaded [{}] spawn2s", spawn2s.size()); + } + + // spawn2 + for (auto &s: spawn_states) { + if (s.spawngroup_id == 0 || s.is_corpse || s.is_zone) { + continue; + } + + uint32 spawn_time_left = 0; + if (spawn_times.count(s.spawn2_id) != 0) { + spawn_time_left = spawn_times[s.spawn2_id]; + LogInfo("Spawn2 [{}] Respawn time left [{}]", s.spawn2_id, spawn_time_left); + } + + // load from spawn2_disabled + bool spawn_enabled = true; + + // check if spawn is disabled + for (auto &ds: disabled_spawns) { + if (ds.spawn2_id == s.spawn2_id) { + spawn_enabled = !ds.disabled; + } + } + + // find spawn 2 by id + Spawn2Repository::Spawn2 spawn2; + for (auto &sp: spawn2s) { + if (sp.id == s.spawn2_id) { + spawn2 = sp; + break; + } + } + + if (!spawn2.id) { + LogZoneState("Failed to load spawn2 data for spawn2_id [{}]", s.spawn2_id); + } + + auto new_spawn = new Spawn2( + s.spawn2_id, + s.spawngroup_id, + spawn2.id > 0 ? spawn2.x : s.x, + spawn2.id > 0 ? spawn2.y : s.y, + spawn2.id > 0 ? spawn2.z : s.z, + spawn2.id > 0 ? spawn2.heading : s.heading, + s.respawn_time, + s.variance, + spawn_time_left, + s.grid, + (bool) s.path_when_zone_idle, + s.condition_id, + s.condition_min_value, + (s.enabled && spawn_enabled), + (EmuAppearance) s.anim + ); + + new_spawn->SetStoredLocation(glm::vec4(s.x, s.y, s.z, s.heading)); + + if (spawn_time_left == 0 && s.npc_id > 0) { + new_spawn->SetResumedNPCID(s.npc_id); + new_spawn->SetResumedFromZoneSuspend(true); + new_spawn->SetEntityVariables(GetVariablesDeserialized(s.entity_variables)); + } + + spawn2_list.Insert(new_spawn); + new_spawn->Process(); + auto n = new_spawn->GetNPC(); + if (n) { + LoadNPCState(zone, n, s); + } + } + + // compare state spawns to spawn2 list, if there are any missing, we need to add them + // this is to cover the rare case where a spawn2 is created in the database but not in the zone state + auto zone_spawns = Spawn2Repository::GetWhere( + content_db, fmt::format( + "TRUE {} AND zone = '{}' AND (version = {} OR version = -1) ", + ContentFilterCriteria::apply(), + zone->GetShortName(), + zone->GetInstanceVersion() + ) + ); + + for (auto &s: zone_spawns) { + bool found = false; + for (auto &ss: spawn_states) { + if (ss.spawn2_id == 0 || ss.spawngroup_id == 0 || ss.is_corpse || ss.is_zone) { + continue; + } + if (s.id == ss.spawn2_id) { + found = true; + break; + } + } + + if (!found) { + bool spawn_enabled = true; + + for (auto &ds: disabled_spawns) { + if (ds.spawn2_id == s.id) { + spawn_enabled = !ds.disabled; + } + } + + LogZoneState("Missing spawn2 [{}] in zone state, this NPC spawn was newly created", s.id); + uint32 spawn_time_left = 0; + if (spawn_times.count(s.id) != 0) { + spawn_time_left = spawn_times[s.id]; + LogInfo("Spawn2 [{}] Respawn time left [{}]", s.id, spawn_time_left); + } + + auto new_spawn = new Spawn2( + s.id, + s.spawngroupID, + s.x, + s.y, + s.z, + s.heading, + s.respawntime, + s.variance, + spawn_time_left, + s.pathgrid, + (bool) s.path_when_zone_idle, + s._condition, + (int16) s.cond_value, + spawn_enabled, + (EmuAppearance) s.animation + ); + + new_spawn->SetStoredLocation(glm::vec4(s.x, s.y, s.z, s.heading)); + spawn2_list.Insert(new_spawn); + new_spawn->Process(); + } + } + + // dynamic spawns, quest spawns, triggers etc. + for (auto &s: spawn_states) { + if (s.spawngroup_id > 0 || s.is_zone) { + continue; + } + + auto npc_type = content_db.LoadNPCTypesData(s.npc_id); + if (!npc_type) { + LogZoneState("Failed to load NPC type data for npc_id [{}]", s.npc_id); + continue; + } + + auto npc = new NPC( + npc_type, + nullptr, + glm::vec4(s.x, s.y, s.z, s.heading), + GravityBehavior::Water + ); + + npc->SetResumedFromZoneSuspend(true); + + // tag as corpse before we add to entity list to prevent quest triggers + if (s.is_corpse) { + npc->SetQueuedToCorpse(); + } + + LoadNPCStatePreSpawn(zone, npc, s); + + entity_list.AddNPC(npc, true, true); + + LoadNPCState(zone, npc, s); + } + + return !spawn_states.empty(); +} + +inline void SaveNPCState(NPC *n, ZoneStateSpawnsRepository::ZoneStateSpawns &s) +{ + // entity variables + std::map variables; + + for (const auto &k: n->GetEntityVariables()) { + variables[k] = n->GetEntityVariable(k); + } + + if (!variables.empty()) { + try { + std::ostringstream os; + { + cereal::JSONOutputArchiveSingleLine archive(os); + archive(variables); + } + s.entity_variables = os.str(); + } + catch (const std::exception &e) { + LogZoneState("Failed to serialize entity variables for NPC [{}] [{}]", n->GetNPCTypeID(), e.what()); + } + } + + // buffs + auto buffs = n->GetBuffs(); + if (buffs) { + std::vector valid_buffs; + for (int index = 0; index < n->GetMaxBuffSlots(); index++) { + if (buffs[index].spellid != 0 && buffs[index].spellid != 65535) { + valid_buffs.push_back(buffs[index]); + } + } + + if (!valid_buffs.empty()) { + try { + std::ostringstream os = std::ostringstream(); + { + cereal::JSONOutputArchiveSingleLine archive(os); + archive(cereal::make_nvp("buffs", valid_buffs)); + } + s.buffs = os.str(); + } + catch (const std::exception &e) { + LogZoneState("Failed to serialize buffs for NPC [{}] [{}]", n->GetNPCTypeID(), e.what()); + } + } + } + + // rest + s.npc_id = n->GetNPCTypeID(); + s.loot_data = GetLootSerialized(n); + s.hp = n->GetHP(); + s.mana = n->GetMana(); + s.endurance = n->GetEndurance(); + s.grid = n->GetGrid(); + s.current_waypoint = n->GetGrid() > 0 ? n->GetCWP() : 0; + s.x = n->GetX(); + s.y = n->GetY(); + s.z = n->GetZ(); + s.heading = n->GetHeading(); + s.created_at = std::time(nullptr); +} + +void Zone::SaveZoneState() +{ + // spawns + std::vector spawns = {}; + LinkedListIterator iterator(spawn2_list); + iterator.Reset(); + int count = 0; + while (iterator.MoreElements()) { + Spawn2 *sp = iterator.GetData(); + auto s = ZoneStateSpawnsRepository::NewEntity(); + s.zone_id = GetZoneID(); + s.instance_id = GetInstanceID(); + s.npc_id = sp->CurrentNPCID(); + s.spawn2_id = sp->GetID(); + s.spawngroup_id = sp->SpawnGroupID(); + s.x = sp->GetX(); + s.y = sp->GetY(); + s.z = sp->GetZ(); + s.heading = sp->GetHeading(); + s.respawn_time = sp->RespawnTimer(); + s.variance = sp->GetVariance(); + s.grid = sp->GetGrid(); + s.path_when_zone_idle = sp->GetPathWhenZoneIdle() ? 1 : 0; + s.condition_id = sp->GetSpawnCondition(); + s.condition_min_value = sp->GetConditionMinValue(); + s.enabled = sp->Enabled() ? 1 : 0; + s.anim = sp->GetAnimation(); + s.created_at = std::time(nullptr); + + auto n = sp->GetNPC(); + if (n && entity_list.GetNPCByID(n->GetID())) { + SaveNPCState(n, s); + } + + spawns.emplace_back(s); + iterator.Advance(); + count++; + } + + // npc's that are not in the spawn2 list + for (auto &n: entity_list.GetNPCList()) { + // everything below here is dynamically spawned + bool ignore_npcs = + n.second->GetSpawnGroupId() > 0 || + n.second->GetNPCTypeID() < 100 || + n.second->GetNPCTypeID() == 500 || // Trap::CreateHiddenTrigger + n.second->IsAura() || + n.second->IsBot() || + n.second->IsMerc() || + n.second->IsTrap() || + n.second->GetSwarmOwner() || + n.second->IsPet(); + if (ignore_npcs) { + continue; + } + + auto s = ZoneStateSpawnsRepository::NewEntity(); + s.zone_id = GetZoneID(); + s.instance_id = GetInstanceID(); + + SaveNPCState(n.second, s); + + spawns.emplace_back(s); + } + + // corpses + for (auto &n: entity_list.GetCorpseList()) { + if (!n.second->IsNPCCorpse()) { + continue; + } + + auto s = ZoneStateSpawnsRepository::NewEntity(); + s.zone_id = GetZoneID(); + s.instance_id = GetInstanceID(); + s.npc_id = n.second->GetNPCTypeID(); + s.is_corpse = 1; + s.x = n.second->GetX(); + s.y = n.second->GetY(); + s.z = n.second->GetZ(); + s.heading = n.second->GetHeading(); + s.created_at = std::time(nullptr); + s.loot_data = GetLootSerialized(n.second); + s.decay_in_seconds = (int) (n.second->GetDecayTime() / 1000); + + spawns.emplace_back(s); + } + + // zone state variables + if (!GetVariables().empty()) { + ZoneStateSpawnsRepository::ZoneStateSpawns z{}; + z.zone_id = GetZoneID(); + z.instance_id = GetInstanceID(); + z.is_zone = 1; + z.entity_variables = GetZoneVariablesSerialized(this); + + spawns.emplace_back(z); + } + + ZoneStateSpawnsRepository::DeleteWhere( + database, + fmt::format( + "`zone_id` = {} AND `instance_id` = {}", + GetZoneID(), + GetInstanceID() + ) + ); + + if (!IsZoneStateValid(spawns)) { + LogInfo("No valid zone state data to save"); + return; + } + + if (spawns.empty()) { + LogInfo("No zone state data to save"); + return; + } + + ZoneStateSpawnsRepository::InsertMany(database, spawns); + + LogInfo("Saved [{}] zone state spawns", Strings::Commify(spawns.size())); +} + +void Zone::ClearZoneState(uint32 zone_id, uint32 instance_id) +{ + ZoneStateSpawnsRepository::DeleteWhere( + database, + fmt::format( + "`zone_id` = {} AND `instance_id` = {}", + zone_id, + instance_id + ) + ); +} diff --git a/zone/zone_save_state.h b/zone/zone_save_state.h new file mode 100644 index 000000000..8d124240b --- /dev/null +++ b/zone/zone_save_state.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include +#include "npc.h" +#include "corpse.h" +#include "zone.h" +#include "../common/repositories/zone_state_spawns_repository.h" +#include "../common/repositories/spawn2_disabled_repository.h" + +struct LootEntryStateData { + uint32 item_id = 0; + uint32_t lootdrop_id = 0; + uint16 charges = 0; // used in dynamically added loot (AddItem) + + // cereal + template + void serialize(Archive &ar) + { + ar( + CEREAL_NVP(item_id), + CEREAL_NVP(lootdrop_id), + CEREAL_NVP(charges) + ); + } +}; + +struct LootStateData { + uint32 copper = 0; + uint32 silver = 0; + uint32 gold = 0; + uint32 platinum = 0; + std::vector entries = {}; + + // cereal + template + void serialize(Archive &ar) + { + ar( + CEREAL_NVP(copper), + CEREAL_NVP(silver), + CEREAL_NVP(gold), + CEREAL_NVP(platinum), + CEREAL_NVP(entries) + ); + } +}; diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index 3f0ac20e8..0dc143675 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -52,7 +52,7 @@ #include "../common/repositories/zone_repository.h" #include "../common/repositories/trader_repository.h" - +#include "../common/repositories/character_evolving_items_repository.h" #include #include @@ -164,6 +164,7 @@ void ZoneDatabase::UpdateRespawnTime(uint32 spawn2_id, uint16 instance_id, uint3 .id = static_cast(spawn2_id), .start = static_cast(current_time), .duration = static_cast(time_left), + .expire_at = current_time + time_left, .instance_id = static_cast(instance_id) } ); @@ -1158,6 +1159,7 @@ bool ZoneDatabase::SaveCharacterData( e.e_expended_aa_spent = m_epp->expended_aa; e.e_last_invsnapshot = m_epp->last_invsnapshot_time; e.mailkey = c->GetMailKeyFull(); + e.illusion_block = c->GetIllusionBlock(); const int replaced = CharacterDataRepository::ReplaceOne(database, e); @@ -1338,27 +1340,27 @@ bool ZoneDatabase::SaveCharacterInvSnapshot(uint32 character_id) { ") " "SELECT" " %u," - " `charid`," - " `slotid`," - " `itemid`," + " `character_id`," + " `slot_id`," + " `item_id`," " `charges`," " `color`," - " `augslot1`," - " `augslot2`," - " `augslot3`," - " `augslot4`," - " `augslot5`," - " `augslot6`," + " `augment_one`," + " `augment_two`," + " `augment_three`," + " `augment_four`," + " `augment_five`," + " `augment_six`," " `instnodrop`," " `custom_data`," - " `ornamenticon`," - " `ornamentidfile`," + " `ornament_icon`," + " `ornament_idfile`," " `ornament_hero_model`," " `guid` " "FROM" " `inventory` " "WHERE" - " `charid` = %u", + " `character_id` = %u", time_index, character_id ); @@ -1512,13 +1514,13 @@ void ZoneDatabase::DivergeCharacterInvSnapshotFromInventory(uint32 character_id, "JOIN" " `inventory` b " "USING" - " (`slotid`, `itemid`) " + " (`slot_id`, `item_id`) " "WHERE" " a.`time_index` = %u " "AND" " a.`charid` = %u " "AND" - " b.`charid` = %u" + " b.`character_id` = %u" ")", timestamp, character_id, @@ -1543,7 +1545,7 @@ void ZoneDatabase::DivergeCharacterInventoryFromInvSnapshot(uint32 character_id, "FROM" " `inventory` " "WHERE" - " `charid` = %u " + " `character_id` = %u " "AND" " `slotid` NOT IN " "(" @@ -1560,7 +1562,7 @@ void ZoneDatabase::DivergeCharacterInventoryFromInvSnapshot(uint32 character_id, "AND" " b.`charid` = %u " "AND" - " a.`charid` = %u" + " a.`character_id` = %u" ")", character_id, timestamp, @@ -1589,7 +1591,7 @@ bool ZoneDatabase::RestoreCharacterInvSnapshot(uint32 character_id, uint32 times "FROM" " `inventory` " "WHERE" - " `charid` = %u", + " `character_id` = %u", character_id ); auto results = database.QueryDatabase(query); @@ -1600,23 +1602,23 @@ bool ZoneDatabase::RestoreCharacterInvSnapshot(uint32 character_id, uint32 times "INSERT " "INTO" " `inventory` " - "(`charid`," - " `slotid`," - " `itemid`," + "(`character_id`," + " `slot_id`," + " `item_id`," " `charges`," " `color`," - " `augslot1`," - " `augslot2`," - " `augslot3`," - " `augslot4`," - " `augslot5`," - " `augslot6`," + " `augment_one`," + " `augment_two`," + " `augment_three`," + " `augment_four`," + " `augment_five`," + " `augment_six`," " `instnodrop`," " `custom_data`," - " `ornamenticon`," - " `ornamentidfile`," + " `ornament_icon`," + " `ornament_idfile`," " `ornament_hero_model`," - " `guid`" + " `guid` " ") " "SELECT" " `charid`," @@ -1730,6 +1732,7 @@ const NPCType *ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load t->attack_count = n.attack_count; t->is_parcel_merchant = n.is_parcel_merchant ? true : false; t->greed = n.greed; + t->m_npc_tint_id = n.npc_tint_id; if (!n.special_abilities.empty()) { strn0cpy(t->special_abilities, n.special_abilities.c_str(), 512); @@ -1905,6 +1908,7 @@ const NPCType *ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load t->heroic_strikethrough = n.heroic_strikethrough; t->faction_amount = n.faction_amount; t->keeps_sold_items = n.keeps_sold_items; + t->multiquest_enabled = n.multiquest_enabled != 0; // If NPC with duplicate NPC id already in table, // free item we attempted to add. @@ -1923,8 +1927,6 @@ const NPCType *ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load } } - DataBucket::BulkLoadEntities(DataBucketLoadType::NPC, npc_ids); - if (!npc_faction_ids.empty()) { zone->LoadNPCFactions(npc_faction_ids); zone->LoadNPCFactionAssociations(npc_faction_ids); @@ -2637,7 +2639,7 @@ int64 ZoneDatabase::GetBlockedSpellsCount(uint32 zone_id) bool ZoneDatabase::LoadBlockedSpells(int64 blocked_spells_count, ZoneSpellsBlocked* into, uint32 zone_id) { - LogInfo("Loading Blocked Spells from database for {} ({}).", zone_store.GetZoneName(zone_id, true), zone_id); + LogInfo("Loading Blocked Spells from database for {} ({}).", ZoneStore::Instance()->GetZoneName(zone_id, true), zone_id); const auto& l = BlockedSpellsRepository::GetWhere( *this, @@ -2659,7 +2661,7 @@ bool ZoneDatabase::LoadBlockedSpells(int64 blocked_spells_count, ZoneSpellsBlock LogError( "Blocked spells count of {} exceeded for {} ({}).", blocked_spells_count, - zone_store.GetZoneName(zone_id, true), + ZoneStore::Instance()->GetZoneName(zone_id, true), zone_id ); break; @@ -3007,12 +3009,12 @@ void ZoneDatabase::LoadBuffs(Client *client) continue; } - if (IsEffectInSpell(buffs[slot_id].spellid, SE_Charm)) { + if (IsEffectInSpell(buffs[slot_id].spellid, SpellEffect::Charm)) { buffs[slot_id].spellid = SPELL_UNKNOWN; break; } - if (IsEffectInSpell(buffs[slot_id].spellid, SE_Illusion)) { + if (IsEffectInSpell(buffs[slot_id].spellid, SpellEffect::Illusion)) { if (buffs[slot_id].persistant_buff) { break; } @@ -4270,3 +4272,28 @@ void ZoneDatabase::SaveCharacterEXPModifier(Client* c) } ); } + +void ZoneDatabase::LoadCharacterTitleSets(Client* c) +{ + if (!zone || !c) { + return; + } + + const auto& l = PlayerTitlesetsRepository::GetWhere( + *this, + fmt::format( + "`char_id` = {}", + c->CharacterID() + ) + ); + + if (l.empty()) { + return; + } + + const uint32 character_id = c->CharacterID(); + + for (const auto& e : l) { + c->EnableTitle(e.title_set, false); + } +} diff --git a/zone/zonedb.h b/zone/zonedb.h index ca0447758..cda0a5ab2 100644 --- a/zone/zonedb.h +++ b/zone/zonedb.h @@ -208,10 +208,10 @@ struct ZoneSpellsBlocked { }; struct TraderCharges_Struct { - uint32 ItemID[80]; - int32 SerialNumber[80]; - uint32 ItemCost[80]; - int32 Charges[80]; + uint32 ItemID[EQ::invtype::BAZAAR_SIZE]; + int32 SerialNumber[EQ::invtype::BAZAAR_SIZE]; + uint32 ItemCost[EQ::invtype::BAZAAR_SIZE]; + int32 Charges[EQ::invtype::BAZAAR_SIZE]; }; const int MaxMercStanceID = 9; @@ -336,6 +336,7 @@ struct CharacterCorpseEntry uint32 drakkin_tattoo; uint32 drakkin_details; std::vector items; + std::string entity_variables; }; namespace BeastlordPetData { @@ -464,6 +465,9 @@ public: void LoadCharacterEXPModifier(Client* c); void SaveCharacterEXPModifier(Client *c); + /* Player Title Sets */ + void LoadCharacterTitleSets(Client* c); + float GetAAEXPModifierByCharID(uint32 character_id, uint32 zone_id, int16 instance_version = -1); float GetEXPModifierByCharID(uint32 character_id, uint32 zone_id, int16 instance_version = -1); void SetAAEXPModifierByCharID(uint32 character_id, uint32 zone_id, float aa_modifier, int16 instance_version = -1); diff --git a/zone/zonedump.h b/zone/zonedump.h index 52dbb5b3e..5d29a252b 100644 --- a/zone/zonedump.h +++ b/zone/zonedump.h @@ -156,6 +156,8 @@ struct NPCType bool keeps_sold_items; bool is_parcel_merchant; uint8 greed; + bool multiquest_enabled; + uint32 m_npc_tint_id; }; #pragma pack() diff --git a/zone/zoning.cpp b/zone/zoning.cpp index 225d21a91..96ad15dbc 100644 --- a/zone/zoning.cpp +++ b/zone/zoning.cpp @@ -21,7 +21,7 @@ #include "../common/rulesys.h" #include "../common/strings.h" -#include "expedition.h" +#include "dynamic_zone.h" #include "queryserv.h" #include "quest_parser_collection.h" #include "string_ids.h" @@ -72,7 +72,7 @@ void Client::Handle_OP_ZoneChange(const EQApplicationPacket *app) { int(zone_mode) ); - content_service.HandleZoneRoutingMiddleware(zc); + WorldContentService::Instance()->HandleZoneRoutingMiddleware(zc); uint16 target_zone_id = 0; auto target_instance_id = zc->instanceID; @@ -241,7 +241,7 @@ void Client::Handle_OP_ZoneChange(const EQApplicationPacket *app) { } } - if (player_event_logs.IsEventEnabled(PlayerEvent::ZONING)) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::ZONING)) { auto e = PlayerEvent::ZoningEvent{}; e.from_zone_long_name = zone->GetLongName(); e.from_zone_short_name = zone->GetShortName(); @@ -349,19 +349,19 @@ void Client::Handle_OP_ZoneChange(const EQApplicationPacket *app) { //TODO: ADVENTURE ENTRANCE CHECK // Expansion checks and routing - if (content_service.GetCurrentExpansion() >= Expansion::Classic && !GetGM()) { + if (WorldContentService::Instance()->GetCurrentExpansion() >= Expansion::Classic && !GetGM()) { bool meets_zone_expansion_check = false; - auto z = zone_store.GetZoneWithFallback(ZoneID(target_zone_name), 0); - if (z->expansion <= content_service.GetCurrentExpansion() || z->bypass_expansion_check) { + auto z = ZoneStore::Instance()->GetZoneWithFallback(ZoneID(target_zone_name), 0); + if (z->expansion <= WorldContentService::Instance()->GetCurrentExpansion() || z->bypass_expansion_check) { meets_zone_expansion_check = true; } LogZoning( "Checking zone request [{}] for expansion [{}] ({}) success [{}]", target_zone_name, - (content_service.GetCurrentExpansion()), - content_service.GetCurrentExpansionName(), + (WorldContentService::Instance()->GetCurrentExpansion()), + WorldContentService::Instance()->GetCurrentExpansionName(), meets_zone_expansion_check ? "true" : "false" ); @@ -370,7 +370,7 @@ void Client::Handle_OP_ZoneChange(const EQApplicationPacket *app) { } } - if (content_service.GetCurrentExpansion() >= Expansion::Classic && GetGM()) { + if (WorldContentService::Instance()->GetCurrentExpansion() >= Expansion::Classic && GetGM()) { LogInfo("[{}] Bypassing zone expansion checks because GM flag is set", GetCleanName()); Message(Chat::White, "Your GM flag allows you to bypass zone expansion checks."); } @@ -489,25 +489,18 @@ void Client::DoZoneSuccess(ZoneChange_Struct *zc, uint16 zone_id, uint32 instanc SendLogoutPackets(); - /* QS: PlayerLogZone */ - if (RuleB(QueryServ, PlayerLogZone)){ - std::string event_desc = StringFormat("Zoning :: zoneid:%u instid:%u x:%4.2f y:%4.2f z:%4.2f h:%4.2f zonemode:%d from zoneid:%u instid:%i", zone_id, instance_id, dest_x, dest_y, dest_z, dest_h, zone_mode, GetZoneID(), GetInstanceID()); - QServ->PlayerLogEvent(Player_Log_Zoning, CharacterID(), event_desc); - } - /* Dont clear aggro until the zone is successful */ entity_list.RemoveFromHateLists(this); if(GetPet()) entity_list.RemoveFromHateLists(GetPet()); - if (GetPendingExpeditionInviteID() != 0) + if (GetPendingDzInviteID() != 0) { // live re-invites if client zoned with a pending invite, save pending invite info in world - auto expedition = Expedition::FindCachedExpeditionByID(GetPendingExpeditionInviteID()); - if (expedition) + if (auto dz = DynamicZone::FindDynamicZoneByID(GetPendingDzInviteID(), DynamicZoneType::Expedition)) { - expedition->SendWorldPendingInvite(m_pending_expedition_invite, GetName()); + dz->SendWorldPendingInvite(m_dz_invite, GetName()); } } @@ -535,8 +528,12 @@ void Client::DoZoneSuccess(ZoneChange_Struct *zc, uint16 zone_id, uint32 instanc m_pp.zone_id = zone_id; m_pp.zoneInstance = instance_id; - //Force a save so its waiting for them when they zone - Save(2); + // save character position + m_pp.x = m_Position.x; + m_pp.y = m_Position.y; + m_pp.z = m_Position.z; + m_pp.heading = m_Position.w; + SaveCharacterData(); m_lock_save_position = true; @@ -684,9 +681,20 @@ void Client::MoveZoneInstanceRaid(uint16 instance_id, const glm::vec4 &location) void Client::ProcessMovePC(uint32 zoneID, uint32 instance_id, float x, float y, float z, float heading, uint8 ignorerestrictions, ZoneMode zm) { - // From what I have read, dragged corpses should stay with the player for Intra-zone summons etc, but we can implement that later. + // From what I have read, dragged corpses should stay with the player for Intra-zone summons etc, but we can + // implement that later. ClearDraggedCorpses(); + // Added to ensure that if a player is moved (ported, gmmove, etc) and they are an active trader or buyer, they will + // be removed from future transactions. + if (IsTrader()) { + TraderEndTrader(); + } + + if (IsBuyer()) { + ToggleBuyerMode(false); + } + if(zoneID == 0) zoneID = zone->GetZoneID(); @@ -701,7 +709,7 @@ void Client::ProcessMovePC(uint32 zoneID, uint32 instance_id, float x, float y, //if they have a pet and they are staying in zone, move with them Mob *p = GetPet(); if(p != nullptr){ - p->SetPetOrder(SPO_Follow); + p->SetPetOrder(PetOrder::Follow); p->GMMove(x+15, y, z); //so it dosent have to run across the map. } } @@ -755,7 +763,7 @@ void Client::ZonePC(uint32 zoneID, uint32 instance_id, float x, float y, float z pZoneName = strcpy(new char[zd->long_name.length() + 1], zd->long_name.c_str()); } - auto r = content_service.FindZone(zoneID, instance_id); + auto r = WorldContentService::Instance()->FindZone(zoneID, instance_id); if (r.zone_id) { zoneID = r.zone_id; instance_id = r.instance.id; @@ -773,11 +781,71 @@ void Client::ZonePC(uint32 zoneID, uint32 instance_id, float x, float y, float z ); // If we are zoning to the same zone, we need to use the current instance ID if it is not specified. - if (content_service.IsInPublicStaticInstance(instance_id) && zoneID == zone->GetZoneID() && instance_id == 0) { + if (WorldContentService::Instance()->IsInPublicStaticInstance(instance_id) && zoneID == zone->GetZoneID() && instance_id == 0) { instance_id = zone->GetInstanceID(); } } + // zone sharding + if (zoneID == zd->zoneidnumber && + instance_id == 0 && + zd->shard_at_player_count > 0) { + + bool found_shard = false; + auto results = CharacterDataRepository::GetInstanceZonePlayerCounts(database, zoneID); + + LogZoning("Zone sharding results count [{}]", results.size()); + + uint64_t shard_instance_duration = 3155760000; + + for (auto &e: results) { + LogZoning( + "Zone sharding results [{}] ({}) instance_id [{}] player_count [{}]", + ZoneName(e.zone_id) ? ZoneName(e.zone_id) : "Unknown", + e.zone_id, + e.instance_id, + e.player_count + ); + + if (e.player_count < zd->shard_at_player_count) { + instance_id = e.instance_id; + + database.AddClientToInstance(instance_id, CharacterID()); + + LogZoning( + "Client [{}] attempting zone to sharded zone > instance_id [{}] zone [{}] ({})", + GetCleanName(), + instance_id, + ZoneName(zoneID) ? ZoneName(zoneID) : "Unknown", + zoneID + ); + + found_shard = true; + break; + } + } + + if (!found_shard) { + uint16 new_instance_id = 0; + database.GetUnusedInstanceID(new_instance_id); + database.CreateInstance(new_instance_id, zoneID, zd->version, shard_instance_duration); + database.AddClientToInstance(new_instance_id, CharacterID()); + instance_id = new_instance_id; + LogZoning( + "Client [{}] creating new sharded zone > instance_id [{}] zone [{}] ({})", + GetCleanName(), + new_instance_id, + ZoneName(zoneID) ? ZoneName(zoneID) : "Unknown", + zoneID + ); + } + } + + // passed from zone shard request to normal zone + if (instance_id == -1) { + instance_id = 0; + } + LogInfo( "Client [{}] zone_id [{}] instance_id [{}] x [{}] y [{}] z [{}] heading [{}] ignorerestrictions [{}] zone_mode [{}]", GetCleanName(),