mirror of
https://github.com/EQEmu/Server.git
synced 2026-05-27 17:12:28 +00:00
Compare commits
136 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1ce51ca3b0 | |||
| 25ef3d2cdb | |||
| 95249889a6 | |||
| 428cccfa50 | |||
| 41dd8a5754 | |||
| d02d766563 | |||
| dfd2729b28 | |||
| b92eafd21b | |||
| d6d5d992cb | |||
| d524cb6a5a | |||
| e6469878ce | |||
| 9583099ace | |||
| cf3483b402 | |||
| 311af7bbe9 | |||
| be42b73f5c | |||
| f76c798910 | |||
| ae198ae043 | |||
| 520943ebf1 | |||
| 9ac306fe67 | |||
| 7a1d69d0d4 | |||
| c873fe5a22 | |||
| e06b0c4b0c | |||
| ed2130f649 | |||
| 448a33a60c | |||
| 8f86cb353e | |||
| 178129443f | |||
| a7c3b41afc | |||
| a5a568d548 | |||
| e3198edb86 | |||
| 8568cf7d49 | |||
| 1fb7a860a1 | |||
| 7eaee2649e | |||
| a17f467b98 | |||
| 3359839a9b | |||
| 7e51e629f9 | |||
| dc6c28a52d | |||
| 78aee0780a | |||
| bcd943a964 | |||
| 56608e84bd | |||
| 8d23e710ce | |||
| 4d11077b21 | |||
| 5c0bdfdc4c | |||
| 6130e10831 | |||
| c3e1c531d2 | |||
| b52719a535 | |||
| 1af252466f | |||
| 699d22fc28 | |||
| 5d1fe68906 | |||
| 52dcf35425 | |||
| a7550fbd9e | |||
| cc0171dfe1 | |||
| 913c5da70f | |||
| 40fecbfaf5 | |||
| b1646381b0 | |||
| bb1578796b | |||
| 0e5a38f072 | |||
| 39876ab858 | |||
| ff16a76481 | |||
| ffd68eb63d | |||
| 76c1da1aad | |||
| a91e03fa43 | |||
| 453106439f | |||
| 3da24fffa4 | |||
| 8d8ef6d480 | |||
| 1f9c4b3a22 | |||
| 7dfda95d86 | |||
| 40738b29e3 | |||
| 080865faa2 | |||
| e2b545991a | |||
| b7f8d0f179 | |||
| e3588781aa | |||
| e9b84f4d11 | |||
| 4f03970fd1 | |||
| 4979da6932 | |||
| 9987029791 | |||
| eece0a92e3 | |||
| 057f96796a | |||
| f475cecdb1 | |||
| 6296ed6d41 | |||
| ac0f729aa2 | |||
| 2937852cf9 | |||
| 2cf5bae571 | |||
| 2feb05be18 | |||
| 421767e1e5 | |||
| 6e9ff52dce | |||
| aa700f8960 | |||
| 2ef959c5ed | |||
| e49ab924cc | |||
| fc3c691588 | |||
| d465a3deba | |||
| 40c9c8044b | |||
| 70a96ea098 | |||
| d5cbec714e | |||
| 6903205484 | |||
| 4c81321847 | |||
| e5cea73e0c | |||
| 23308192b5 | |||
| 29fdf7e2ae | |||
| 098498dedd | |||
| b6fb8daae8 | |||
| 563f7d5564 | |||
| 1e5abc456b | |||
| 3b0fa015a7 | |||
| c73a1e8bea | |||
| 3bfdc0cf71 | |||
| a23ac4628f | |||
| 5ef4612249 | |||
| 17f66c5d60 | |||
| 51eb95ed31 | |||
| 080abaede1 | |||
| 97e332819d | |||
| 1e41c5517e | |||
| c7a88af11a | |||
| d8ddd0aab9 | |||
| 95cbadade5 | |||
| a85f4fb703 | |||
| e63f34638b | |||
| 7918fed81c | |||
| ac24c9bf5a | |||
| 7b914c731b | |||
| 7362c0ebb5 | |||
| ae213a4e4b | |||
| 187288f3aa | |||
| abc8c3d886 | |||
| 0b2493beb8 | |||
| 9cebba5911 | |||
| 4478328b2a | |||
| 55a7e1646d | |||
| b6b8491060 | |||
| 850053a136 | |||
| 1aa8758b0a | |||
| b1aa087b9f | |||
| 1e57a0372f | |||
| 9614ea59ec | |||
| 7a648cce16 | |||
| 8640776a21 |
+274
@@ -1,3 +1,277 @@
|
||||
## [22.58.0] 11/5/2024
|
||||
|
||||
### Code
|
||||
|
||||
* Add mysql prepared statement support ([#4530](https://github.com/EQEmu/Server/pull/4530)) @hgtw 2024-11-06
|
||||
* Update perlbind to 1.1.0 ([#4529](https://github.com/EQEmu/Server/pull/4529)) @hgtw 2024-11-06
|
||||
|
||||
### Feature
|
||||
|
||||
* Focus Skill Attack Spells ([#4528](https://github.com/EQEmu/Server/pull/4528)) @mmcgarvey 2024-10-31
|
||||
|
||||
### Fixes
|
||||
|
||||
* Add Missing Lua Registers ([#4525](https://github.com/EQEmu/Server/pull/4525)) @Kinglykrab 2024-10-24
|
||||
* Fix cross_zone_set_entity_variable_by_char_id in Lua ([#4526](https://github.com/EQEmu/Server/pull/4526)) @Kinglykrab 2024-10-24
|
||||
|
||||
### Loginserver
|
||||
|
||||
* Automatifc Opcode File Creation ([#4521](https://github.com/EQEmu/Server/pull/4521)) @KimLS 2024-10-22
|
||||
|
||||
### Quest API
|
||||
|
||||
* Add Spawn Circle/Grid Methods to Perl/Lua ([#4524](https://github.com/EQEmu/Server/pull/4524)) @Kinglykrab 2024-10-24
|
||||
|
||||
## [22.57.1] 10/22/2024
|
||||
|
||||
### Bots
|
||||
|
||||
* Enable Bot Commands Only if Rule Enabled ([#4519](https://github.com/EQEmu/Server/pull/4519)) @Kinglykrab 2024-10-22
|
||||
* Fix pet buffs from saving duplicates every save ([#4520](https://github.com/EQEmu/Server/pull/4520)) @nytmyr 2024-10-22
|
||||
|
||||
### Loginserver
|
||||
|
||||
* Automatic Opcode File Creation ([#4521](https://github.com/EQEmu/Server/pull/4521)) @KimLS 2024-10-22
|
||||
|
||||
## [22.57.0] 10/20/2024
|
||||
|
||||
### Bots
|
||||
|
||||
* Add "silent" option to ^spawn and mute raid spawn ([#4494](https://github.com/EQEmu/Server/pull/4494)) @nytmyr 2024-10-05
|
||||
* Add attack flag when told to attack ([#4490](https://github.com/EQEmu/Server/pull/4490)) @nytmyr 2024-09-29
|
||||
* Fix timers loading on spawn and zone ([#4516](https://github.com/EQEmu/Server/pull/4516)) @nytmyr 2024-10-20
|
||||
|
||||
### Code
|
||||
|
||||
* Fixed a typo in Zoning.cpp ([#4515](https://github.com/EQEmu/Server/pull/4515)) @carolus21rex 2024-10-20
|
||||
* Optimization Code Cleanup ([#4489](https://github.com/EQEmu/Server/pull/4489)) @Akkadius 2024-09-30
|
||||
* Remove Extra Skill in EQ::skills::GetExtraDamageSkills() ([#4486](https://github.com/EQEmu/Server/pull/4486)) @Kinglykrab 2024-10-03
|
||||
|
||||
### Crash
|
||||
|
||||
* Fixes a crash when the faction_list db table is empty. ([#4511](https://github.com/EQEmu/Server/pull/4511)) @KimLS 2024-10-14
|
||||
|
||||
### Fixes
|
||||
|
||||
* Add character_instance_safereturns to tables_to_zero_id ([#4485](https://github.com/EQEmu/Server/pull/4485)) @Morzain 2024-09-26
|
||||
* Correctly limit max targets of PBAOE ([#4507](https://github.com/EQEmu/Server/pull/4507)) @catapultam-habeo 2024-10-11
|
||||
* FindBestZ selecting false zone floor as bestz - Results in roambox failures ([#4504](https://github.com/EQEmu/Server/pull/4504)) @fryguy503 2024-10-13
|
||||
* Fix #set motd Crash ([#4495](https://github.com/EQEmu/Server/pull/4495)) @Kinglykrab 2024-10-05
|
||||
* Fix `character_exp_modifiers` Default Values ([#4502](https://github.com/EQEmu/Server/pull/4502)) @Kinglykrab 2024-10-09
|
||||
* Fix a display error regarding a few trader/buyer query errors ([#4514](https://github.com/EQEmu/Server/pull/4514)) @neckkola 2024-10-17
|
||||
* Fix Group ID 0 in Group::SaveGroupLeaderAA() ([#4487](https://github.com/EQEmu/Server/pull/4487)) @Kinglykrab 2024-10-03
|
||||
* Fix Mercenary Encounter Crash ([#4509](https://github.com/EQEmu/Server/pull/4509)) @Kinglykrab 2024-10-12
|
||||
* Fix NPC::CanTalk() Crash ([#4499](https://github.com/EQEmu/Server/pull/4499)) @Kinglykrab 2024-10-07
|
||||
* Fix Spells:DefaultAOEMaxTargets Default Value ([#4508](https://github.com/EQEmu/Server/pull/4508)) @Kinglykrab 2024-10-12
|
||||
* Fix Targeted AOE Max Targets Rule ([#4488](https://github.com/EQEmu/Server/pull/4488)) @Kinglykrab 2024-10-03
|
||||
* fixed a bug where it would use npc value instead of faction value in the database. ([#4491](https://github.com/EQEmu/Server/pull/4491)) @regneq 2024-09-29
|
||||
* Master of Disguise should apply to illusions casted by others. ([#4506](https://github.com/EQEmu/Server/pull/4506)) @fryguy503 2024-10-11
|
||||
* Spells - Self Only (Yellow) cast when non group member is targeted ([#4503](https://github.com/EQEmu/Server/pull/4503)) @fryguy503 2024-10-11
|
||||
|
||||
### Loginserver
|
||||
|
||||
* Larion loginserver support ([#4492](https://github.com/EQEmu/Server/pull/4492)) @KimLS 2024-10-03
|
||||
* Login Fatal Error Spamming ([#4476](https://github.com/EQEmu/Server/pull/4476)) @KimLS 2024-10-09
|
||||
|
||||
### Logs
|
||||
|
||||
* Add NPC Trades to Player Events ([#4505](https://github.com/EQEmu/Server/pull/4505)) @Kinglykrab 2024-10-13
|
||||
|
||||
### Quest API
|
||||
|
||||
* Add Buff Fade Methods to Perl/Lua ([#4501](https://github.com/EQEmu/Server/pull/4501)) @Kinglykrab 2024-10-09
|
||||
* Add EVENT_READ_ITEM to Perl/Lua ([#4497](https://github.com/EQEmu/Server/pull/4497)) @Kinglykrab 2024-10-08
|
||||
* Add NPC List Filter Methods to Perl/Lua ([#4493](https://github.com/EQEmu/Server/pull/4493)) @Kinglykrab 2024-10-04
|
||||
* Add Scripting Support to Mercenaries ([#4500](https://github.com/EQEmu/Server/pull/4500)) @Kinglykrab 2024-10-11
|
||||
|
||||
### Rules
|
||||
|
||||
* Add Rule to disable PVP Regions ([#4513](https://github.com/EQEmu/Server/pull/4513)) @Kinglykrab 2024-10-17
|
||||
|
||||
## [22.56.3] 9/23/2024
|
||||
|
||||
### Fixes
|
||||
|
||||
* Fix issue with Client::SaveDisciplines() not specifying character ID ([#4481](https://github.com/EQEmu/Server/pull/4477)) @Kinglykrab 2024-09-23
|
||||
|
||||
## [22.56.2] 9/20/2024
|
||||
|
||||
### Fixes
|
||||
|
||||
* Fix Issue with Database::ReserveName ([#4477](https://github.com/EQEmu/Server/pull/4477)) @Kinglykrab 2024-09-20
|
||||
|
||||
### Quest API
|
||||
|
||||
* Add GrantAllAAPoints() Overload To Perl/Lua ([#4474](https://github.com/EQEmu/Server/pull/4474)) @Kinglykrab 2024-09-20
|
||||
|
||||
## [22.56.1] 9/20/2024
|
||||
|
||||
### Fixes
|
||||
|
||||
* Fix Untrained Disciplines in Client::SaveDisciplines() ([#4472](https://github.com/EQEmu/Server/pull/4472)) @Kinglykrab 2024-09-13
|
||||
* Fix Infinite Loop in Adventure::Finished() ([#4473](https://github.com/EQEmu/Server/pull/4473)) @oddx2k 2024-09-13
|
||||
|
||||
## [22.56.0] 9/12/2024
|
||||
|
||||
### Code
|
||||
|
||||
* Add IsCloseToBanker method ([#4462](https://github.com/EQEmu/Server/pull/4462)) @Akkadius 2024-08-27
|
||||
|
||||
### Feature
|
||||
|
||||
* Add Rule to Limit Task Update Messages ([#4459](https://github.com/EQEmu/Server/pull/4459)) @Kinglykrab 2024-08-28
|
||||
* Allow NPCs to cast Sacrifice ([#4470](https://github.com/EQEmu/Server/pull/4470)) @fuzzlecutter 2024-09-12
|
||||
* Lazy Load Bank Contents ([#4453](https://github.com/EQEmu/Server/pull/4453)) @catapultam-habeo 2024-08-27
|
||||
|
||||
### Fixes
|
||||
|
||||
* Add RULE_STRING to RuleManager::ResetRules ([#4467](https://github.com/EQEmu/Server/pull/4467)) @Kinglykrab 2024-09-07
|
||||
* Fix Bard Effect in Migration 9237 ([#4468](https://github.com/EQEmu/Server/pull/4468)) @Kinglykrab 2024-09-09
|
||||
* ModernAAScalingEnabled() Calculation Error ([#4469](https://github.com/EQEmu/Server/pull/4469)) @carolus21rex 2024-09-11
|
||||
|
||||
### Performance
|
||||
|
||||
* Move Discipline Loading to Client::CompleteConnect() ([#4466](https://github.com/EQEmu/Server/pull/4466)) @Kinglykrab 2024-09-09
|
||||
|
||||
### Rules
|
||||
|
||||
* Add a Bandolier Swap Delay Rule ([#4465](https://github.com/EQEmu/Server/pull/4465)) @Kinglykrab 2024-09-08
|
||||
|
||||
## [22.55.1] 8/26/2024
|
||||
|
||||
### Code
|
||||
|
||||
* Remove unused methods ([#4449](https://github.com/EQEmu/Server/pull/4449)) @Kinglykrab 2024-08-22
|
||||
|
||||
### Feature
|
||||
|
||||
* Add Character:DefaultGuildRank Rule ([#4438](https://github.com/EQEmu/Server/pull/4438)) @Kinglykrab 2024-08-04
|
||||
* Add Optional Return to EVENT_DAMAGE_TAKEN ([#4454](https://github.com/EQEmu/Server/pull/4454)) @Kinglykrab 2024-08-27
|
||||
* Extend Spell Buckets Functionality ([#4441](https://github.com/EQEmu/Server/pull/4441)) @Kinglykrab 2024-08-22
|
||||
|
||||
### Fixes
|
||||
|
||||
* Apply Race & Class restrictions to Auto-Combines ([#4452](https://github.com/EQEmu/Server/pull/4452)) @catapultam-habeo 2024-08-20
|
||||
* Attune Augments when Equipped ([#4446](https://github.com/EQEmu/Server/pull/4446)) @fryguy503 2024-08-10
|
||||
* Correct missed maxlevel reference in exp.cpp ([#4463](https://github.com/EQEmu/Server/pull/4463)) @N0ctrnl 2024-08-27
|
||||
* Ensure close of Tribute Item search ([#4439](https://github.com/EQEmu/Server/pull/4439)) @joligario 2024-08-04
|
||||
* Fix AddCrystals() in Perl/Lua ([#4445](https://github.com/EQEmu/Server/pull/4445)) @Kinglykrab 2024-08-10
|
||||
* Fix Bot Spell Entries IDs Capping at 32,767 ([#4444](https://github.com/EQEmu/Server/pull/4444)) @Kinglykrab 2024-08-27
|
||||
* Fix Character ID of 0 being inserted into character_stats_record ([#4458](https://github.com/EQEmu/Server/pull/4458)) @Kinglykrab 2024-08-22
|
||||
* Fix Issue with Removed #setfaction Command ([#4448](https://github.com/EQEmu/Server/pull/4448)) @Kinglykrab 2024-08-11
|
||||
* Fix Lua Client FilteredMessage ([#4437](https://github.com/EQEmu/Server/pull/4437)) @Kinglykrab 2024-07-31
|
||||
* Fix client hotbar exchanging items when zoning ([#4460](https://github.com/EQEmu/Server/pull/4460)) @neckkola 2024-08-27
|
||||
* Fix issue with killed mob coordinates ([#4457](https://github.com/EQEmu/Server/pull/4457)) @Kinglykrab 2024-08-22
|
||||
* Imitate Death should also clear zone feign aggro ([#4436](https://github.com/EQEmu/Server/pull/4436)) @fryguy503 2024-07-31
|
||||
* client_max_level allow leveling to end of level ([#4455](https://github.com/EQEmu/Server/pull/4455)) @fryguy503 2024-08-20
|
||||
|
||||
### Improvement
|
||||
|
||||
* Filtered Messages Extension ([#4435](https://github.com/EQEmu/Server/pull/4435)) @fryguy503 2024-07-31
|
||||
|
||||
### Quest API
|
||||
|
||||
* Add AreTasksCompleted() to Perl/Lua. ([#4456](https://github.com/EQEmu/Server/pull/4456)) @Kinglykrab 2024-08-23
|
||||
* Add Area-Based Quest Methods to Perl/Lua ([#4447](https://github.com/EQEmu/Server/pull/4447)) @Kinglykrab 2024-08-27
|
||||
* Add Several Door Methods to Perl/Lua ([#4451](https://github.com/EQEmu/Server/pull/4451)) @Kinglykrab 2024-08-16
|
||||
|
||||
### World
|
||||
|
||||
* Fix slow world bootup bug ([#4461](https://github.com/EQEmu/Server/pull/4461)) @Akkadius 2024-08-27
|
||||
|
||||
## [22.54.0] 7/30/2024
|
||||
|
||||
### Code
|
||||
|
||||
* Cleanup Client File Exporting ([#4348](https://github.com/EQEmu/Server/pull/4348)) @Kinglykrab 2024-07-31
|
||||
* Cleanup Stance Code ([#4368](https://github.com/EQEmu/Server/pull/4368)) @Kinglykrab 2024-07-03
|
||||
* Mask GM Show Buff message behind EntityVariable ([#4419](https://github.com/EQEmu/Server/pull/4419)) @nytmyr 2024-07-22
|
||||
|
||||
### Commands
|
||||
|
||||
* Extend #devtools Functionality ([#4425](https://github.com/EQEmu/Server/pull/4425)) @Kinglykrab 2024-07-23
|
||||
|
||||
### Databuckets
|
||||
|
||||
* Remove memory reserve from bulk load ([#4427](https://github.com/EQEmu/Server/pull/4427)) @Akkadius 2024-07-23
|
||||
|
||||
### Feature
|
||||
|
||||
* Add Barter/Buyer Features ([#4405](https://github.com/EQEmu/Server/pull/4405)) @neckkola 2024-07-30
|
||||
* Add Parcel notification for online players when using the Quest API ([#4418](https://github.com/EQEmu/Server/pull/4418)) @neckkola 2024-07-22
|
||||
* Implement Move Multiple Items ([#4259](https://github.com/EQEmu/Server/pull/4259)) @catapultam-habeo 2024-07-30
|
||||
|
||||
### Fixes
|
||||
|
||||
* Aegolism Spell line stacking ([#4399](https://github.com/EQEmu/Server/pull/4399)) @KayenEQ 2024-07-07
|
||||
* AllowRaidTargetBlind logic backwards ([#4400](https://github.com/EQEmu/Server/pull/4400)) @fryguy503 2024-07-01
|
||||
* AutoSplit unknown bug and cleanup. ([#4401](https://github.com/EQEmu/Server/pull/4401)) @fryguy503 2024-07-07
|
||||
* Corpse Call removing Resurrection Effects ([#4410](https://github.com/EQEmu/Server/pull/4410)) @fryguy503 2024-07-22
|
||||
* Fix #parcels add subcommand ([#4431](https://github.com/EQEmu/Server/pull/4431)) @neckkola 2024-07-29
|
||||
* Fix #setlevel Allowing Skills Above Max ([#4423](https://github.com/EQEmu/Server/pull/4423)) @Kinglykrab 2024-07-23
|
||||
* Fix Bot::SetBotStance ([#4426](https://github.com/EQEmu/Server/pull/4426)) @Kinglykrab 2024-07-23
|
||||
* Fix Client::RemoveTitle ([#4421](https://github.com/EQEmu/Server/pull/4421)) @Kinglykrab 2024-07-23
|
||||
* Fix EVENT_USE_SKILL with Sense Heading ([#4424](https://github.com/EQEmu/Server/pull/4424)) @Kinglykrab 2024-07-23
|
||||
* Fix for random disconnects when a large number of guild members zone or disconnect ([#4402](https://github.com/EQEmu/Server/pull/4402)) @neckkola 2024-07-10
|
||||
* Fix issue with quest::echo and quest::me ([#4433](https://github.com/EQEmu/Server/pull/4433)) @Kinglykrab 2024-07-30
|
||||
* Personal tributes for bard items were not applying correctly ([#4416](https://github.com/EQEmu/Server/pull/4416)) @neckkola 2024-07-16
|
||||
* Potential fix for some undesired ranged explotative behavior. ([#4413](https://github.com/EQEmu/Server/pull/4413)) @fryguy503 2024-07-22
|
||||
* Proximity Aggro for Frustrated and Undead ([#4411](https://github.com/EQEmu/Server/pull/4411)) @fryguy503 2024-07-22
|
||||
* Slay Adjustments ([#4389](https://github.com/EQEmu/Server/pull/4389)) @fryguy503 2024-07-07
|
||||
* Stop DOSing ourselves with OP_WearChange ([#4432](https://github.com/EQEmu/Server/pull/4432)) @catapultam-habeo 2024-07-30
|
||||
* [Quest API] Fix getraididbycharid and getgroupidbycharid ([#4417](https://github.com/EQEmu/Server/pull/4417)) @nytmyr 2024-07-16
|
||||
|
||||
### Improvement
|
||||
|
||||
* Flee Overhaul ([#4407](https://github.com/EQEmu/Server/pull/4407)) @fryguy503 2024-07-30
|
||||
|
||||
### Rules
|
||||
|
||||
* Add HasteCap and Hastev3Cap rules for NPCs, Bots and Mercs ([#4406](https://github.com/EQEmu/Server/pull/4406)) @nytmyr 2024-07-22
|
||||
|
||||
### Zone Instances
|
||||
|
||||
* Revert " Handle routing to instances when using evac/succor " (#4429) ([#4297](https://github.com/EQEmu/Server/pull/4297)) @Akkadius 2024-07-30
|
||||
|
||||
### Zoning
|
||||
|
||||
* Improve zone routing ([#4428](https://github.com/EQEmu/Server/pull/4428)) @Akkadius 2024-07-30
|
||||
|
||||
## [22.53.1] 6/16/2024
|
||||
|
||||
### Fixes
|
||||
|
||||
* Fix trader mode ([#4397](https://github.com/EQEmu/Server/pull/4397)) @joligario 2024-06-17
|
||||
|
||||
## [22.53.0] 6/14/2024
|
||||
|
||||
### Bug
|
||||
|
||||
* Anon players should not show in /who all ([#4392](https://github.com/EQEmu/Server/pull/4392)) @fryguy503 2024-06-14
|
||||
* Escape should put player into SOS if owned. ([#4388](https://github.com/EQEmu/Server/pull/4388)) @fryguy503 2024-06-07
|
||||
* Prevent Resurrection Spells from being resisted ([#4393](https://github.com/EQEmu/Server/pull/4393)) @fryguy503 2024-06-14
|
||||
|
||||
### Code
|
||||
|
||||
* Cleanup Account Status Code ([#4376](https://github.com/EQEmu/Server/pull/4376)) @Kinglykrab 2024-06-02
|
||||
* Cleanup Body Type Code ([#4366](https://github.com/EQEmu/Server/pull/4366)) @Kinglykrab 2024-06-02
|
||||
* Cleanup Object Type Code ([#4375](https://github.com/EQEmu/Server/pull/4375)) @Kinglykrab 2024-06-14
|
||||
* Remove unused code in emu_constants.h ([#4384](https://github.com/EQEmu/Server/pull/4384)) @Kinglykrab 2024-06-14
|
||||
|
||||
### Fixes
|
||||
|
||||
* Fix #goto Target ([#4382](https://github.com/EQEmu/Server/pull/4382)) @Kinglykrab 2024-06-03
|
||||
* Fix Swarm Pet Damage Messages ([#4383](https://github.com/EQEmu/Server/pull/4383)) @Kinglykrab 2024-06-04
|
||||
* Fix for players having empty bazaar window dropdown list, even though trader is tagged as a trader. ([#4391](https://github.com/EQEmu/Server/pull/4391)) @neckkola 2024-06-14
|
||||
* Fix potential trader crash when serialized item not found ([#4386](https://github.com/EQEmu/Server/pull/4386)) @joligario 2024-06-14
|
||||
|
||||
### Rules
|
||||
|
||||
* Add Invisible Augment Rules ([#4385](https://github.com/EQEmu/Server/pull/4385)) @Kinglykrab 2024-06-14
|
||||
* Classic Harm Touch Formula ([#4394](https://github.com/EQEmu/Server/pull/4394)) @fryguy503 2024-06-14
|
||||
* Mend/Sneak allow success tuning ([#4390](https://github.com/EQEmu/Server/pull/4390)) @fryguy503 2024-06-14
|
||||
* Snare Override Movement Bonus ([#4381](https://github.com/EQEmu/Server/pull/4381)) @fryguy503 2024-06-02
|
||||
|
||||
## [22.52.0] 6/1/2024
|
||||
|
||||
### Code
|
||||
|
||||
+38
-139
@@ -29,7 +29,10 @@
|
||||
#include "../../common/content/world_content_service.h"
|
||||
#include "../../common/zone_store.h"
|
||||
#include "../../common/path_manager.h"
|
||||
#include "../../common/repositories/base_data_repository.h"
|
||||
#include "../../common/repositories/db_str_repository.h"
|
||||
#include "../../common/repositories/skill_caps_repository.h"
|
||||
#include "../../common/repositories/spells_new_repository.h"
|
||||
#include "../../common/file.h"
|
||||
#include "../../common/events/player_event_logs.h"
|
||||
#include "../../common/skill_caps.h"
|
||||
@@ -99,25 +102,22 @@ int main(int argc, char **argv)
|
||||
->LoadLogDatabaseSettings()
|
||||
->StartFileLogs();
|
||||
|
||||
std::string arg_1;
|
||||
std::string export_type;
|
||||
|
||||
if (argv[1]) {
|
||||
arg_1 = argv[1];
|
||||
export_type = argv[1];
|
||||
}
|
||||
|
||||
if (arg_1 == "spells") {
|
||||
if (Strings::EqualFold(export_type, "spells")) {
|
||||
ExportSpells(&content_db);
|
||||
return 0;
|
||||
}
|
||||
if (arg_1 == "skills") {
|
||||
} else if (Strings::EqualFold(export_type, "skills")) {
|
||||
ExportSkillCaps(&content_db);
|
||||
return 0;
|
||||
}
|
||||
if (arg_1 == "basedata") {
|
||||
} else if (Strings::EqualFold(export_type, "basedata") || Strings::EqualFold(export_type, "base_data")) {
|
||||
ExportBaseData(&content_db);
|
||||
return 0;
|
||||
}
|
||||
if (arg_1 == "dbstring") {
|
||||
} else if (Strings::EqualFold(export_type, "dbstr") || Strings::EqualFold(export_type, "dbstring")) {
|
||||
ExportDBStrings(&database);
|
||||
return 0;
|
||||
}
|
||||
@@ -132,180 +132,79 @@ int main(int argc, char **argv)
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ExportSpells(SharedDatabase *db)
|
||||
void ExportSpells(SharedDatabase* db)
|
||||
{
|
||||
LogInfo("Exporting Spells");
|
||||
|
||||
std::string file = fmt::format("{}/export/spells_us.txt", path.GetServerPath());
|
||||
FILE *f = fopen(file.c_str(), "w");
|
||||
if (!f) {
|
||||
std::ofstream file(fmt::format("{}/export/spells_us.txt", path.GetServerPath()));
|
||||
if (!file || !file.is_open()) {
|
||||
LogError("Unable to open export/spells_us.txt to write, skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string query = "SELECT * FROM spells_new ORDER BY id";
|
||||
auto results = db->QueryDatabase(query);
|
||||
const auto& lines = SpellsNewRepository::GetSpellFileLines(*db);
|
||||
|
||||
if (results.Success()) {
|
||||
for (auto row = results.begin(); row != results.end(); ++row) {
|
||||
std::string line;
|
||||
unsigned int fields = results.ColumnCount();
|
||||
for (unsigned int i = 0; i < fields; ++i) {
|
||||
if (i != 0) {
|
||||
line.push_back('^');
|
||||
}
|
||||
const std::string& file_string = Strings::Implode("\n", lines);
|
||||
|
||||
if (row[i] != nullptr) {
|
||||
line += row[i];
|
||||
}
|
||||
}
|
||||
file << file_string;
|
||||
|
||||
fprintf(f, "%s\n", line.c_str());
|
||||
}
|
||||
}
|
||||
else {
|
||||
}
|
||||
file.close();
|
||||
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
bool SkillUsable(SharedDatabase* db, int skill_id, int class_id)
|
||||
{
|
||||
const auto& l = SkillCapsRepository::GetWhere(
|
||||
*db,
|
||||
fmt::format(
|
||||
"`class_id` = {} AND `skill_id` = {} ORDER BY `cap` DESC LIMIT 1",
|
||||
class_id,
|
||||
skill_id
|
||||
)
|
||||
);
|
||||
|
||||
return !l.empty();
|
||||
}
|
||||
|
||||
uint32 GetSkill(SharedDatabase* db, int skill_id, int class_id, int level)
|
||||
{
|
||||
const auto& l = SkillCapsRepository::GetWhere(
|
||||
*db,
|
||||
fmt::format(
|
||||
"`class_id` = {} AND `skill_id` = {} AND `level` = {}",
|
||||
class_id,
|
||||
skill_id,
|
||||
level
|
||||
)
|
||||
);
|
||||
|
||||
if (l.empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto e = l.front();
|
||||
|
||||
return e.cap;
|
||||
LogInfo("Exported [{}] Spell{}", lines.size(), lines.size() != 1 ? "s" : "");
|
||||
}
|
||||
|
||||
void ExportSkillCaps(SharedDatabase* db)
|
||||
{
|
||||
LogInfo("Exporting Skill Caps");
|
||||
|
||||
std::ofstream file(fmt::format("{}/export/SkillCaps.txt", path.GetServerPath()));
|
||||
if (!file || !file.is_open()) {
|
||||
LogError("Unable to open export/SkillCaps.txt to write, skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint8 class_id = Class::Warrior; class_id <= Class::Berserker; class_id++) {
|
||||
for (uint8 skill_id = EQ::skills::Skill1HBlunt; skill_id <= EQ::skills::Skill2HPiercing; skill_id++) {
|
||||
if (SkillUsable(db, skill_id, class_id)) {
|
||||
uint32 previous_cap = 0;
|
||||
const auto& lines = SkillCapsRepository::GetSkillCapFileLines(*db);
|
||||
|
||||
for (
|
||||
uint8 level = 1;
|
||||
level <= SkillCaps::GetSkillCapMaxLevel(class_id, static_cast<EQ::skills::SkillType>(skill_id));
|
||||
level++
|
||||
) {
|
||||
uint32 cap = GetSkill(db, skill_id, class_id, level);
|
||||
if (cap < previous_cap) {
|
||||
cap = previous_cap;
|
||||
}
|
||||
const std::string& file_string = Strings::Implode("\n", lines);
|
||||
|
||||
file << fmt::format("{}^{}^{}^{}^0", class_id, skill_id, level, cap) << std::endl;
|
||||
|
||||
previous_cap = cap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
file << file_string;
|
||||
|
||||
file.close();
|
||||
|
||||
LogInfo("Exported [{}] Skill Cap{}", lines.size(), lines.size() != 1 ? "s" : "");
|
||||
}
|
||||
|
||||
void ExportBaseData(SharedDatabase *db)
|
||||
{
|
||||
LogInfo("Exporting Base Data");
|
||||
|
||||
std::string file = fmt::format("{}/export/BaseData.txt", path.GetServerPath());
|
||||
FILE *f = fopen(file.c_str(), "w");
|
||||
if (!f) {
|
||||
std::ofstream file(fmt::format("{}/export/BaseData.txt", path.GetServerPath()));
|
||||
if (!file || !file.is_open()) {
|
||||
LogError("Unable to open export/BaseData.txt to write, skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string query = "SELECT * FROM base_data ORDER BY level, class";
|
||||
auto results = db->QueryDatabase(query);
|
||||
if (results.Success()) {
|
||||
for (auto row = results.begin(); row != results.end(); ++row) {
|
||||
std::string line;
|
||||
unsigned int fields = results.ColumnCount();
|
||||
for (unsigned int rowIndex = 0; rowIndex < fields; ++rowIndex) {
|
||||
if (rowIndex != 0) {
|
||||
line.push_back('^');
|
||||
}
|
||||
const auto& lines = BaseDataRepository::GetBaseDataFileLines(*db);
|
||||
|
||||
if (row[rowIndex] != nullptr) {
|
||||
line += row[rowIndex];
|
||||
}
|
||||
}
|
||||
const std::string& file_string = Strings::Implode("\n", lines);
|
||||
|
||||
fprintf(f, "%s\n", line.c_str());
|
||||
}
|
||||
}
|
||||
file << file_string;
|
||||
|
||||
fclose(f);
|
||||
file.close();
|
||||
|
||||
LogInfo("Exported [{}] Base Data Entr{}", lines.size(), lines.size() != 1 ? "ies" : "y");
|
||||
}
|
||||
|
||||
void ExportDBStrings(SharedDatabase *db)
|
||||
{
|
||||
LogInfo("Exporting DB Strings");
|
||||
|
||||
std::string file = fmt::format("{}/export/dbstr_us.txt", path.GetServerPath());
|
||||
FILE *f = fopen(file.c_str(), "w");
|
||||
if (!f) {
|
||||
std::ofstream file(fmt::format("{}/export/dbstr_us.txt", path.GetServerPath()));
|
||||
if (!file || !file.is_open()) {
|
||||
LogError("Unable to open export/dbstr_us.txt to write, skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(f, "Major^Minor^String(New)\n");
|
||||
const std::string query = "SELECT * FROM db_str ORDER BY id, type";
|
||||
auto results = db->QueryDatabase(query);
|
||||
if (results.Success()) {
|
||||
for (auto row = results.begin(); row != results.end(); ++row) {
|
||||
std::string line;
|
||||
unsigned int fields = results.ColumnCount();
|
||||
for (unsigned int rowIndex = 0; rowIndex < fields; ++rowIndex) {
|
||||
if (rowIndex != 0) {
|
||||
line.push_back('^');
|
||||
}
|
||||
const auto& lines = DbStrRepository::GetDBStrFileLines(*db);
|
||||
|
||||
if (row[rowIndex] != nullptr) {
|
||||
line += row[rowIndex];
|
||||
}
|
||||
}
|
||||
const std::string& file_string = Strings::Implode("\n", lines);
|
||||
|
||||
fprintf(f, "%s\n", line.c_str());
|
||||
}
|
||||
}
|
||||
file << file_string;
|
||||
|
||||
fclose(f);
|
||||
file.close();
|
||||
|
||||
LogInfo("Exported [{}] Database String{}", lines.size(), lines.size() != 1 ? "s" : "");
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.12)
|
||||
SET(common_sources
|
||||
base_packet.cpp
|
||||
bazaar.cpp
|
||||
bodytypes.cpp
|
||||
classes.cpp
|
||||
cli/eqemu_command_handler.cpp
|
||||
compression.cpp
|
||||
@@ -61,6 +62,7 @@ SET(common_sources
|
||||
mutex.cpp
|
||||
mysql_request_result.cpp
|
||||
mysql_request_row.cpp
|
||||
mysql_stmt.cpp
|
||||
opcode_map.cpp
|
||||
opcodemgr.cpp
|
||||
packet_dump.cpp
|
||||
@@ -157,6 +159,7 @@ SET(repositories
|
||||
repositories/base/base_bugs_repository.h
|
||||
repositories/base/base_bug_reports_repository.h
|
||||
repositories/base/base_buyer_repository.h
|
||||
repositories/base/base_buyer_trade_items_repository.h
|
||||
repositories/base/base_character_activities_repository.h
|
||||
repositories/base/base_character_alternate_abilities_repository.h
|
||||
repositories/base/base_character_alt_currency_repository.h
|
||||
@@ -338,7 +341,8 @@ SET(repositories
|
||||
repositories/books_repository.h
|
||||
repositories/bugs_repository.h
|
||||
repositories/bug_reports_repository.h
|
||||
repositories/buyer_repository.h
|
||||
repositories/buyer_buy_lines_repository.h
|
||||
repositories/buyer_trade_items_repository.h
|
||||
repositories/character_activities_repository.h
|
||||
repositories/character_alternate_abilities_repository.h
|
||||
repositories/character_alt_currency_repository.h
|
||||
@@ -583,6 +587,7 @@ SET(common_headers
|
||||
mutex.h
|
||||
mysql_request_result.h
|
||||
mysql_request_row.h
|
||||
mysql_stmt.h
|
||||
op_codes.h
|
||||
opcode_dispatch.h
|
||||
opcodemgr.h
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ Bazaar::GetSearchResults(
|
||||
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));
|
||||
search_criteria_trader.append(fmt::format(" AND trader.item_cost >= {}", search.min_cost * 1000));
|
||||
}
|
||||
if (search.max_cost != 0) {
|
||||
search_criteria_trader.append(fmt::format(" AND trader.item_cost <= {}", (uint64) search.max_cost * 1000));
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
#include "../common/global_define.h"
|
||||
#include "../common/bodytypes.h"
|
||||
|
||||
std::string BodyType::GetName(uint8 body_type_id)
|
||||
{
|
||||
return IsValid(body_type_id) ? body_type_names[body_type_id] : "UNKNOWN BODY TYPE";
|
||||
}
|
||||
|
||||
bool BodyType::IsValid(uint8 body_type_id)
|
||||
{
|
||||
return body_type_names.find(body_type_id) != body_type_names.end();
|
||||
}
|
||||
+90
-46
@@ -18,52 +18,96 @@
|
||||
#ifndef BODYTYPES_H
|
||||
#define BODYTYPES_H
|
||||
|
||||
typedef enum {
|
||||
BT_Humanoid = 1,
|
||||
BT_Lycanthrope = 2,
|
||||
BT_Undead = 3,
|
||||
BT_Giant = 4,
|
||||
BT_Construct = 5,
|
||||
BT_Extraplanar = 6,
|
||||
BT_Magical = 7, //this name might be a bit off,
|
||||
BT_SummonedUndead = 8,
|
||||
BT_RaidGiant = 9, //Velious era Raid Giant
|
||||
BT_RaidColdain = 10, //Velious era Raid Coldain
|
||||
BT_NoTarget = 11, //no name, can't target this bodytype
|
||||
BT_Vampire = 12,
|
||||
BT_Atenha_Ra = 13,
|
||||
BT_Greater_Akheva = 14,
|
||||
BT_Khati_Sha = 15,
|
||||
BT_Seru = 16,
|
||||
BT_Grieg_Veneficus = 17,
|
||||
BT_Draz_Nurakk = 18,
|
||||
BT_Zek = 19, //"creatures from the Plane of War."
|
||||
BT_Luggald = 20,
|
||||
BT_Animal = 21,
|
||||
BT_Insect = 22,
|
||||
BT_Monster = 23,
|
||||
BT_Summoned = 24, //Elemental?
|
||||
BT_Plant = 25,
|
||||
BT_Dragon = 26,
|
||||
BT_Summoned2 = 27,
|
||||
BT_Summoned3 = 28,
|
||||
BT_Dragon2 = 29, //database data indicates this is a dragon type (kunark and DoN?)
|
||||
BT_VeliousDragon = 30, //might not be a tight set
|
||||
BT_Familiar = 31,
|
||||
BT_Dragon3 = 32,
|
||||
BT_Boxes = 33,
|
||||
BT_Muramite = 34, //tribal dudes
|
||||
// ...
|
||||
BT_NoTarget2 = 60,
|
||||
// ...
|
||||
BT_SwarmPet = 63, //Looks like weapon proc related temp pets and few misc pets, should not be used for checking swarm pets in general.
|
||||
BT_MonsterSummon = 64,
|
||||
// 65, trap or effect related?
|
||||
BT_InvisMan = 66, //no name, seen on 'InvisMan', can be /targeted
|
||||
BT_Special = 67
|
||||
} bodyType;
|
||||
/* bodytypes above 64 make the mob not show up */
|
||||
#include "types.h"
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
constexpr int format_as(bodyType type) { return static_cast<int>(type); }
|
||||
// body types above 64 make the mob invisible
|
||||
namespace BodyType {
|
||||
constexpr uint8 Humanoid = 1;
|
||||
constexpr uint8 Lycanthrope = 2;
|
||||
constexpr uint8 Undead = 3;
|
||||
constexpr uint8 Giant = 4;
|
||||
constexpr uint8 Construct = 5;
|
||||
constexpr uint8 Extraplanar = 6;
|
||||
constexpr uint8 Magical = 7; // this name might be a bit off,
|
||||
constexpr uint8 SummonedUndead = 8;
|
||||
constexpr uint8 RaidGiant = 9; // Velious era Raid Giant
|
||||
constexpr uint8 RaidColdain = 10; // Velious era Raid Coldain
|
||||
constexpr uint8 NoTarget = 11; // no name, can't target this bodytype
|
||||
constexpr uint8 Vampire = 12;
|
||||
constexpr uint8 AtenHaRa = 13;
|
||||
constexpr uint8 GreaterAkheva = 14;
|
||||
constexpr uint8 KhatiSha = 15;
|
||||
constexpr uint8 Seru = 16;
|
||||
constexpr uint8 GriegVeneficus = 17;
|
||||
constexpr uint8 DrazNurakk = 18;
|
||||
constexpr uint8 Zek = 19; //"creatures from the Plane of War."
|
||||
constexpr uint8 Luggald = 20;
|
||||
constexpr uint8 Animal = 21;
|
||||
constexpr uint8 Insect = 22;
|
||||
constexpr uint8 Monster = 23;
|
||||
constexpr uint8 Summoned = 24; // Elemental?
|
||||
constexpr uint8 Plant = 25;
|
||||
constexpr uint8 Dragon = 26;
|
||||
constexpr uint8 Summoned2 = 27;
|
||||
constexpr uint8 Summoned3 = 28;
|
||||
constexpr uint8 Dragon2 = 29; // database data indicates this is a dragon type (Kunark and DoN?)
|
||||
constexpr uint8 VeliousDragon = 30; // might not be a tight set
|
||||
constexpr uint8 Familiar = 31;
|
||||
constexpr uint8 Dragon3 = 32;
|
||||
constexpr uint8 Boxes = 33;
|
||||
constexpr uint8 Muramite = 34; // tribal dudes
|
||||
constexpr uint8 NoTarget2 = 60;
|
||||
constexpr uint8 SwarmPet = 63; // Looks like weapon proc related temp pets and few misc pets, should not be used for checking swarm pets in general.
|
||||
constexpr uint8 MonsterSummon = 64;
|
||||
constexpr uint8 InvisibleMan = 66; // no name, seen on 'InvisMan', can be /targeted
|
||||
constexpr uint8 Special = 67;
|
||||
|
||||
std::string GetName(uint8 body_type_id);
|
||||
bool IsValid(uint8 body_type_id);
|
||||
}
|
||||
|
||||
static std::map<uint8, std::string> body_type_names = {
|
||||
{ BodyType::Humanoid, "Humanoid" },
|
||||
{ BodyType::Lycanthrope, "Lycanthrope" },
|
||||
{ BodyType::Undead, "Undead" },
|
||||
{ BodyType::Giant, "Giant" },
|
||||
{ BodyType::Construct, "Construct" },
|
||||
{ BodyType::Extraplanar, "Extraplanar" },
|
||||
{ BodyType::Magical, "Magical" },
|
||||
{ BodyType::SummonedUndead, "Summoned Undead" },
|
||||
{ BodyType::RaidGiant, "Raid Giant" },
|
||||
{ BodyType::RaidColdain, "Raid Coldain" },
|
||||
{ BodyType::NoTarget, "Untargetable" },
|
||||
{ BodyType::Vampire, "Vampire" },
|
||||
{ BodyType::AtenHaRa, "Aten Ha Ra" },
|
||||
{ BodyType::GreaterAkheva, "Greater Akheva" },
|
||||
{ BodyType::KhatiSha, "Khati Sha" },
|
||||
{ BodyType::Seru, "Seru" },
|
||||
{ BodyType::GriegVeneficus, "Grieg Veneficus" },
|
||||
{ BodyType::DrazNurakk, "Draz Nurakk" },
|
||||
{ BodyType::Zek, "Zek" },
|
||||
{ BodyType::Luggald, "Luggald" },
|
||||
{ BodyType::Animal, "Animal" },
|
||||
{ BodyType::Insect, "Insect" },
|
||||
{ BodyType::Monster, "Monster" },
|
||||
{ BodyType::Summoned, "Summoned" },
|
||||
{ BodyType::Plant, "Plant" },
|
||||
{ BodyType::Dragon, "Dragon" },
|
||||
{ BodyType::Summoned2, "Summoned 2" },
|
||||
{ BodyType::Summoned3, "Summoned 3" },
|
||||
{ BodyType::Dragon2, "Dragon 2" },
|
||||
{ BodyType::VeliousDragon, "Velious Dragon" },
|
||||
{ BodyType::Familiar, "Familiar" },
|
||||
{ BodyType::Dragon3, "Dragon 3" },
|
||||
{ BodyType::Boxes, "Boxes" },
|
||||
{ BodyType::Muramite, "Muramite" },
|
||||
{ BodyType::NoTarget2, "Untargetable 2" },
|
||||
{ BodyType::SwarmPet, "Swarm Pet" },
|
||||
{ BodyType::MonsterSummon, "Monster Summon" },
|
||||
{ BodyType::InvisibleMan, "Invisible Man" },
|
||||
{ BodyType::Special, "Special" },
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "../rulesys.h"
|
||||
#include "../eqemu_logsys.h"
|
||||
#include "../repositories/instance_list_repository.h"
|
||||
#include "../zone_store.h"
|
||||
|
||||
|
||||
WorldContentService::WorldContentService()
|
||||
@@ -183,8 +184,8 @@ void WorldContentService::ReloadContentFlags()
|
||||
}
|
||||
|
||||
SetContentFlags(set_content_flags);
|
||||
LoadZones();
|
||||
LoadStaticGlobalZoneInstances();
|
||||
zone_store.LoadZones(*m_content_database);
|
||||
}
|
||||
|
||||
Database *WorldContentService::GetDatabase() const
|
||||
@@ -236,18 +237,6 @@ void WorldContentService::SetContentFlag(const std::string &content_flag_name, b
|
||||
ReloadContentFlags();
|
||||
}
|
||||
|
||||
// HandleZoneRoutingMiddleware is meant to handle content and context aware zone routing
|
||||
//
|
||||
// example # 1
|
||||
// lavastorm (pre-don) version 0 (classic)
|
||||
// lavastorm (don) version 1
|
||||
// we want to route players to the correct version of lavastorm based on the current server side expansion
|
||||
// in order to do that the simplest and cleanest way we intercept the zoning process and route players to an "instance" of the zone
|
||||
// the reason why we're doing this is because all of the zoning logic already is handled by two keys "zone_id" and "instance_id"
|
||||
// we can leverage static, never expires instances to handle this but to the client they don't see it any other way than a public normal zone
|
||||
// scripts handle all the same way, you don't have to think about instances, the middleware will handle the magic
|
||||
// the versions of zones are represented by two zone entries that have potentially different min/max expansion and/or different content flags
|
||||
// we decide to route the client to the correct version of the zone based on the current server side expansion
|
||||
void WorldContentService::HandleZoneRoutingMiddleware(ZoneChange_Struct *zc)
|
||||
{
|
||||
auto r = FindZone(zc->zoneID, zc->instanceID);
|
||||
@@ -261,93 +250,72 @@ void WorldContentService::HandleZoneRoutingMiddleware(ZoneChange_Struct *zc)
|
||||
// LoadStaticGlobalZoneInstances loads all static global zone instances
|
||||
// these are zones that are never set to expire and are global
|
||||
// these are used commonly in v1/v2/v3 versions of the same zone for expansion routing
|
||||
WorldContentService * WorldContentService::LoadStaticGlobalZoneInstances()
|
||||
WorldContentService *WorldContentService::LoadStaticGlobalZoneInstances()
|
||||
{
|
||||
m_zone_instances = InstanceListRepository::GetWhere(*GetDatabase(), fmt::format("never_expires = 1 AND is_global = 1"));
|
||||
m_zone_static_instances = InstanceListRepository::GetWhere(
|
||||
*GetDatabase(),
|
||||
fmt::format("never_expires = 1 AND is_global = 1")
|
||||
);
|
||||
|
||||
LogInfo("Loaded [{}] zone_instances", m_zone_instances.size());
|
||||
LogInfo("Loaded [{}] zone_instances", m_zone_static_instances.size());
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
// LoadZones sets the zones for the world content service
|
||||
// this is used for zone routing middleware
|
||||
// we pull the zone list from the zone repository and feed from the zone store for now
|
||||
// we're holding a copy in the content service - but we're talking 250kb of data in memory to handle routing of zoning
|
||||
WorldContentService * WorldContentService::LoadZones()
|
||||
{
|
||||
m_zones = ZoneRepository::All(*GetContentDatabase());
|
||||
|
||||
LogInfo("Loaded [{}] zones", m_zones.size());
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
// FindZone is critical to the zone routing middleware and any logic that needs to route players to the correct zone
|
||||
// era contextual routing, multiple version of zones, etc
|
||||
// FindZone handles content and context aware zone routing (middleware)
|
||||
//
|
||||
// this is a middleware function that is meant to be used in the zone change process
|
||||
// this hooks all core zone changes within the server and routes the player to the correct zone
|
||||
// returning a zone_id of non-zero means the middleware will route the player
|
||||
// returning a zone_id of 0 means the middleware will not route the player
|
||||
// this is useful for handling multiple versions of the same zone
|
||||
//
|
||||
// implementation >
|
||||
// the zoning and process spawning logic already is handled by two keys "zone_id" and "instance_id"
|
||||
// we leverage static, never expires instances to handle this and client still sees it as a normal zone
|
||||
//
|
||||
// content awareness >
|
||||
// simply use the zone_id, server content settings and the middleware will handle the rest
|
||||
// you don't have to think about instances in any data tables (use instance_id 0)
|
||||
// you don't have to keep track of instance ids in scripts (use instance_id 0)
|
||||
// the versions of zones are represented by two zone entries that have potentially different min/max expansion and/or different content flags
|
||||
// we decide to route the client to the correct version of the zone based on the current server side expansion
|
||||
//
|
||||
// example >
|
||||
// we want to route players to the correct version of lavastorm based on the current server side expansion (DoesZonePassContentFiltering)
|
||||
// lavastorm (pre-don) version 0 (classic)
|
||||
// zone table entry for version = 0, min_expansion = 0, max_expansion = 8
|
||||
// instance_list table entry for lavastorm has version = 0, is_global = 1, never_expires = 1
|
||||
// lavastorm (don) version 1
|
||||
// zone table entry for version = 1, min_expansion = 9, max_expansion = 99
|
||||
// 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)
|
||||
{
|
||||
// if there's an active dynamic instance, we don't need to route
|
||||
if (instance_id > 0) {
|
||||
auto inst = InstanceListRepository::FindOne(*GetDatabase(), instance_id);
|
||||
if (inst.id != 0 && !inst.is_global && !inst.never_expires) {
|
||||
return WorldContentService::FindZoneResult{
|
||||
.zone_id = 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
for (const auto &z: zone_store.GetZones()) {
|
||||
for (auto &i: m_zone_static_instances) {
|
||||
if (
|
||||
z.zoneidnumber == zone_id &&
|
||||
DoesZonePassContentFiltering(z) &&
|
||||
i.zone == zone_id &&
|
||||
i.version == z.version) {
|
||||
|
||||
for (auto &z: m_zones) {
|
||||
if (z.zoneidnumber == zone_id) {
|
||||
auto f = ContentFlags{
|
||||
.min_expansion = z.min_expansion,
|
||||
.max_expansion = z.max_expansion,
|
||||
.content_flags = z.content_flags,
|
||||
.content_flags_disabled = z.content_flags_disabled
|
||||
};
|
||||
|
||||
if (DoesPassContentFiltering(f)) {
|
||||
LogInfo(
|
||||
"Attempting to route player to zone [{}] ({}) version [{}] long_name [{}]",
|
||||
z.short_name,
|
||||
z.zoneidnumber,
|
||||
z.version,
|
||||
z.long_name
|
||||
);
|
||||
|
||||
// first pass, explicit match on public static global zone instances
|
||||
for (auto &i: m_zone_instances) {
|
||||
if (i.zone == zone_id && i.version == z.version) {
|
||||
LogInfo(
|
||||
"Routed player to instance [{}] of zone [{}] ({}) version [{}] long_name [{}] notes [{}]",
|
||||
i.id,
|
||||
z.short_name,
|
||||
z.zoneidnumber,
|
||||
z.version,
|
||||
z.long_name,
|
||||
i.notes
|
||||
);
|
||||
|
||||
return WorldContentService::FindZoneResult{
|
||||
.zone_id = static_cast<uint32>(z.zoneidnumber),
|
||||
.instance = i,
|
||||
.zone = z
|
||||
};
|
||||
}
|
||||
if (instance_id > 0 && i.id != instance_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
LogInfo(
|
||||
"Routed player to non-instance zone [{}] ({}) version [{}] long_name [{}] notes [{}]",
|
||||
"Routed player to public static instance [{}] of zone [{}] ({}) version [{}] long_name [{}] notes [{}]",
|
||||
i.id,
|
||||
z.short_name,
|
||||
z.zoneidnumber,
|
||||
z.version,
|
||||
z.long_name,
|
||||
z.note
|
||||
i.notes
|
||||
);
|
||||
|
||||
return WorldContentService::FindZoneResult{
|
||||
.zone_id = static_cast<uint32>(z.zoneidnumber),
|
||||
.instance = InstanceListRepository::NewEntity(),
|
||||
.instance = i,
|
||||
.zone = z
|
||||
};
|
||||
}
|
||||
@@ -359,7 +327,7 @@ WorldContentService::FindZoneResult WorldContentService::FindZone(uint32 zone_id
|
||||
|
||||
bool WorldContentService::IsInPublicStaticInstance(uint32 instance_id)
|
||||
{
|
||||
for (auto &i: m_zone_instances) {
|
||||
for (auto &i: m_zone_static_instances) {
|
||||
if (i.id == instance_id) {
|
||||
return true;
|
||||
}
|
||||
@@ -367,3 +335,15 @@ bool WorldContentService::IsInPublicStaticInstance(uint32 instance_id)
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WorldContentService::DoesZonePassContentFiltering(const ZoneRepository::Zone &z)
|
||||
{
|
||||
auto f = ContentFlags{
|
||||
.min_expansion = z.min_expansion,
|
||||
.max_expansion = z.max_expansion,
|
||||
.content_flags = z.content_flags,
|
||||
.content_flags_disabled = z.content_flags_disabled
|
||||
};
|
||||
|
||||
return DoesPassContentFiltering(f);
|
||||
}
|
||||
|
||||
@@ -160,6 +160,7 @@ public:
|
||||
WorldContentService * SetExpansionContext();
|
||||
|
||||
bool DoesPassContentFiltering(const ContentFlags& f);
|
||||
bool DoesZonePassContentFiltering(const ZoneRepository::Zone& z);
|
||||
|
||||
WorldContentService * SetDatabase(Database *database);
|
||||
Database *GetDatabase() const;
|
||||
@@ -189,10 +190,8 @@ private:
|
||||
Database *m_content_database;
|
||||
|
||||
// holds a record of the zone table from the database
|
||||
std::vector<ZoneRepository::Zone> m_zones = {};
|
||||
WorldContentService *LoadStaticGlobalZoneInstances();
|
||||
std::vector<InstanceListRepository::InstanceList> m_zone_instances;
|
||||
WorldContentService * LoadZones();
|
||||
std::vector<InstanceListRepository::InstanceList> m_zone_static_instances;
|
||||
};
|
||||
|
||||
extern WorldContentService content_service;
|
||||
|
||||
+57
-14
@@ -66,6 +66,7 @@
|
||||
#endif
|
||||
|
||||
#include "database.h"
|
||||
#include "data_verification.h"
|
||||
#include "eq_packet_structs.h"
|
||||
#include "extprofile.h"
|
||||
#include "strings.h"
|
||||
@@ -77,6 +78,8 @@
|
||||
#include "zone_store.h"
|
||||
#include "repositories/merchantlist_temp_repository.h"
|
||||
#include "repositories/bot_data_repository.h"
|
||||
#include "repositories/trader_repository.h"
|
||||
#include "repositories/buyer_repository.h"
|
||||
|
||||
extern Client client;
|
||||
|
||||
@@ -282,16 +285,31 @@ bool Database::SetAccountStatus(const std::string& account_name, int16 status)
|
||||
|
||||
bool Database::ReserveName(uint32 account_id, const std::string& name)
|
||||
{
|
||||
const auto& l = CharacterDataRepository::GetWhere(
|
||||
*this,
|
||||
fmt::format(
|
||||
"`name` = '{}'",
|
||||
Strings::Escape(name)
|
||||
)
|
||||
const std::string& where_filter = fmt::format(
|
||||
"`name` = '{}'",
|
||||
Strings::Escape(name)
|
||||
);
|
||||
|
||||
if (!l.empty()) {
|
||||
LogInfo("Account: [{}] tried to request name: [{}], but it is already taken", account_id, name);
|
||||
if (RuleB(Bots, Enabled)) {
|
||||
const auto& b = BotDataRepository::GetWhere(*this, where_filter);
|
||||
|
||||
if (!b.empty()) {
|
||||
LogInfo("Account [{}] requested name [{}] but name is already taken by a bot", account_id, name);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const auto& c = CharacterDataRepository::GetWhere(*this, where_filter);
|
||||
|
||||
if (!c.empty()) {
|
||||
LogInfo("Account [{}] requested name [{}] but name is already taken by a character", account_id, name);
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& n = NpcTypesRepository::GetWhere(*this, where_filter);
|
||||
|
||||
if (!n.empty()) {
|
||||
LogInfo("Account [{}] requested name [{}] but name is already taken by an NPC", account_id, name);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -306,13 +324,15 @@ bool Database::ReserveName(uint32 account_id, const std::string& name)
|
||||
return false;
|
||||
}
|
||||
|
||||
const int guild_id = RuleI(Character, DefaultGuild);
|
||||
const uint32 guild_id = RuleI(Character, DefaultGuild);
|
||||
const uint8 guild_rank = EQ::Clamp(RuleI(Character, DefaultGuildRank), 0, 8);
|
||||
if (guild_id != 0) {
|
||||
if (e.id) {
|
||||
auto g = GuildMembersRepository::NewEntity();
|
||||
|
||||
g.char_id = e.id;
|
||||
g.guild_id = guild_id;
|
||||
g.rank_ = guild_rank;
|
||||
|
||||
GuildMembersRepository::InsertOne(*this, g);
|
||||
}
|
||||
@@ -1628,16 +1648,29 @@ uint32 Database::GetGuildIDByCharID(uint32 character_id)
|
||||
|
||||
uint32 Database::GetGroupIDByCharID(uint32 character_id)
|
||||
{
|
||||
const auto& e = GroupIdRepository::FindOne(*this, character_id);
|
||||
const auto& e = GroupIdRepository::GetWhere(
|
||||
*this,
|
||||
fmt::format(
|
||||
"`character_id` = {}",
|
||||
character_id
|
||||
)
|
||||
);
|
||||
|
||||
return e.character_id ? e.group_id : 0;
|
||||
return e.size() == 1 ? e.front().group_id : 0;
|
||||
}
|
||||
|
||||
uint32 Database::GetRaidIDByCharID(uint32 character_id)
|
||||
{
|
||||
const auto& e = RaidMembersRepository::FindOne(*this, character_id);
|
||||
|
||||
return e.charid ? e.raidid : 0;
|
||||
const auto& e = RaidMembersRepository::GetWhere(
|
||||
*this,
|
||||
fmt::format(
|
||||
"`charid` = {}",
|
||||
character_id
|
||||
)
|
||||
);
|
||||
|
||||
return e.size() == 1 ? e.front().raidid : 0;
|
||||
}
|
||||
|
||||
int64 Database::CountInvSnapshots()
|
||||
@@ -1827,7 +1860,7 @@ bool Database::CopyCharacter(
|
||||
|
||||
const int64 new_character_id = (CharacterDataRepository::GetMaxId(*this) + 1);
|
||||
|
||||
std::vector<std::string> tables_to_zero_id = { "keyring", "data_buckets" };
|
||||
std::vector<std::string> tables_to_zero_id = { "keyring", "data_buckets", "character_instance_safereturns" };
|
||||
|
||||
TransactionBegin();
|
||||
|
||||
@@ -2104,3 +2137,13 @@ void Database::ClearGuildOnlineStatus()
|
||||
{
|
||||
GuildMembersRepository::ClearOnlineStatus(*this);
|
||||
}
|
||||
|
||||
void Database::ClearTraderDetails()
|
||||
{
|
||||
TraderRepository::Truncate(*this);
|
||||
}
|
||||
|
||||
void Database::ClearBuyerDetails()
|
||||
{
|
||||
BuyerRepository::DeleteBuyer(*this, 0);
|
||||
}
|
||||
|
||||
@@ -244,6 +244,8 @@ public:
|
||||
|
||||
void PurgeAllDeletedDataBuckets();
|
||||
void ClearGuildOnlineStatus();
|
||||
void ClearTraderDetails();
|
||||
void ClearBuyerDetails();
|
||||
|
||||
|
||||
/* Database Variables */
|
||||
|
||||
@@ -4947,7 +4947,7 @@ UPDATE `aa_ability` SET `auto_grant_enabled` = 1 WHERE `grant_only` = 0 AND `cha
|
||||
.version = 9237,
|
||||
.description = "2023_10_15_import_13th_floor.sql",
|
||||
.check = "SHOW COLUMNS FROM `items` LIKE 'bardeffect';",
|
||||
.condition = "contains",
|
||||
.condition = "missing",
|
||||
.match = "mediumint",
|
||||
.sql = R"(
|
||||
ALTER TABLE `items`
|
||||
@@ -5660,6 +5660,104 @@ ALTER TABLE `trader`
|
||||
DROP PRIMARY KEY,
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD INDEX `charid_slotid` (`char_id`, `slot_id`);
|
||||
)"
|
||||
},
|
||||
ManifestEntry{
|
||||
.version = 9281,
|
||||
.description = "2024_06_24_update_buyer_support.sql",
|
||||
.check = "SHOW COLUMNS FROM `buyer` LIKE 'id'",
|
||||
.condition = "empty",
|
||||
.match = "",
|
||||
.sql = R"(
|
||||
ALTER TABLE `buyer`
|
||||
ADD COLUMN `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT FIRST,
|
||||
CHANGE COLUMN `charid` `char_id` INT(11) UNSIGNED NOT NULL DEFAULT '0' AFTER `id`,
|
||||
ADD COLUMN `char_entity_id` INT(11) UNSIGNED NOT NULL DEFAULT '0' AFTER `char_id`,
|
||||
ADD COLUMN `char_name` VARCHAR(64) NULL DEFAULT NULL AFTER `char_entity_id`,
|
||||
ADD COLUMN `char_zone_id` INT(11) UNSIGNED NOT NULL DEFAULT '0' AFTER `char_name`,
|
||||
ADD COLUMN `char_zone_instance_id` INT(11) UNSIGNED NOT NULL DEFAULT '0' AFTER `char_zone_id`,
|
||||
ADD COLUMN `transaction_date` DATETIME NULL DEFAULT NULL AFTER `char_zone_instance_id`,
|
||||
ADD COLUMN `welcome_message` VARCHAR(256) NULL DEFAULT NULL AFTER `transaction_date`,
|
||||
DROP COLUMN `buyslot`,
|
||||
DROP COLUMN `itemid`,
|
||||
DROP COLUMN `itemname`,
|
||||
DROP COLUMN `quantity`,
|
||||
DROP COLUMN `price`,
|
||||
DROP PRIMARY KEY,
|
||||
ADD PRIMARY KEY (`id`) USING BTREE,
|
||||
ADD INDEX `charid` (`char_id`);
|
||||
|
||||
CREATE TABLE `buyer_buy_lines` (
|
||||
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`buyer_id` BIGINT(20) UNSIGNED NOT NULL DEFAULT '0',
|
||||
`char_id` INT(11) UNSIGNED NOT NULL DEFAULT '0',
|
||||
`buy_slot_id` INT(11) NOT NULL DEFAULT '0',
|
||||
`item_id` INT(11) NOT NULL DEFAULT '0',
|
||||
`item_qty` INT(11) NOT NULL DEFAULT '0',
|
||||
`item_price` INT(11) NOT NULL DEFAULT '0',
|
||||
`item_icon` INT(11) UNSIGNED NOT NULL DEFAULT '0',
|
||||
`item_name` VARCHAR(64) NOT NULL DEFAULT '' COLLATE 'latin1_swedish_ci',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `buyerid_charid_buyslotid` (`buyer_id`, `char_id`, `buy_slot_id`) USING BTREE
|
||||
)
|
||||
COLLATE='latin1_swedish_ci'
|
||||
ENGINE=InnoDB
|
||||
AUTO_INCREMENT=1;
|
||||
|
||||
CREATE TABLE `buyer_trade_items` (
|
||||
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`buyer_buy_lines_id` BIGINT(20) UNSIGNED NOT NULL DEFAULT '0',
|
||||
`item_id` INT(11) NOT NULL DEFAULT '0',
|
||||
`item_qty` INT(11) NOT NULL DEFAULT '0',
|
||||
`item_icon` INT(11) NOT NULL DEFAULT '0',
|
||||
`item_name` VARCHAR(64) NOT NULL DEFAULT '0' COLLATE 'latin1_swedish_ci',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `buyerbuylinesid` (`buyer_buy_lines_id`) USING BTREE
|
||||
)
|
||||
COLLATE='latin1_swedish_ci'
|
||||
ENGINE=InnoDB
|
||||
AUTO_INCREMENT=1;
|
||||
)"
|
||||
},
|
||||
ManifestEntry{
|
||||
.version = 9282,
|
||||
.description = "2024_08_02_spell_buckets_comparison.sql",
|
||||
.check = "SHOW COLUMNS FROM `spell_buckets` LIKE 'bucket_comparison'",
|
||||
.condition = "empty",
|
||||
.match = "",
|
||||
.sql = R"(
|
||||
ALTER TABLE `spell_buckets`
|
||||
CHANGE COLUMN `spellid` `spell_id` int UNSIGNED NOT NULL FIRST,
|
||||
CHANGE COLUMN `key` `bucket_name` varchar(100) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL DEFAULT '' AFTER `spell_id`,
|
||||
CHANGE COLUMN `value` `bucket_value` varchar(100) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL DEFAULT '' AFTER `bucket_name`,
|
||||
ADD COLUMN `bucket_comparison` tinyint UNSIGNED NOT NULL DEFAULT 0 AFTER `bucket_value`,
|
||||
DROP PRIMARY KEY,
|
||||
ADD PRIMARY KEY (`spell_id`) USING BTREE;
|
||||
)"
|
||||
},
|
||||
ManifestEntry{
|
||||
.version = 9283,
|
||||
.description = "2024_08_05_fix_client_hotbar",
|
||||
.check = "SHOW COLUMNS FROM `inventory` LIKE 'guid'",
|
||||
.condition = "empty",
|
||||
.match = "",
|
||||
.sql = R"(
|
||||
ALTER TABLE `inventory`
|
||||
ADD COLUMN `guid` BIGINT UNSIGNED NULL DEFAULT '0' AFTER `ornament_hero_model`;
|
||||
ALTER TABLE `inventory_snapshots`
|
||||
ADD COLUMN `guid` BIGINT UNSIGNED NULL DEFAULT '0' AFTER `ornament_hero_model`;
|
||||
)"
|
||||
},
|
||||
ManifestEntry{
|
||||
.version = 9284,
|
||||
.description = "2024_10_08_character_exp_modifiers_default.sql",
|
||||
.check = "SHOW CREATE TABLE `character_exp_modifiers`",
|
||||
.condition = "contains",
|
||||
.match = "`exp_modifier` float NOT NULL,",
|
||||
.sql = R"(
|
||||
ALTER TABLE `character_exp_modifiers`
|
||||
MODIFY COLUMN `aa_modifier` float NOT NULL DEFAULT 1.0 AFTER `instance_version`,
|
||||
MODIFY COLUMN `exp_modifier` float NOT NULL DEFAULT 1.0 AFTER `aa_modifier`;
|
||||
)"
|
||||
}
|
||||
// -- template; copy/paste this when you need to create a new entry
|
||||
|
||||
@@ -150,6 +150,17 @@ 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,
|
||||
.description = "2024_08_05_bot_spells_entries_unsigned_spell_id.sql",
|
||||
.check = "SHOW COLUMNS FROM `bot_spells_entries` LIKE 'spell_id'",
|
||||
.condition = "empty",
|
||||
.match = "",
|
||||
.sql = R"(
|
||||
ALTER TABLE `bot_spells_entries`
|
||||
CHANGE COLUMN `spellid` `spell_id` smallint(5) UNSIGNED NOT NULL DEFAULT 0 AFTER `npc_spells_id`;
|
||||
)"
|
||||
}
|
||||
// -- template; copy/paste this when you need to create a new entry
|
||||
|
||||
@@ -36,7 +36,6 @@ namespace DatabaseSchema {
|
||||
{
|
||||
return {
|
||||
{"adventure_stats", "player_id"},
|
||||
{"buyer", "charid"},
|
||||
{"char_recipe_list", "char_id"},
|
||||
{"character_activities", "charid"},
|
||||
{"character_alt_currency", "char_id"},
|
||||
@@ -107,6 +106,8 @@ namespace DatabaseSchema {
|
||||
"adventure_details",
|
||||
"adventure_stats",
|
||||
"buyer",
|
||||
"buyer_buy_lines",
|
||||
"buyer_trade_items",
|
||||
"char_recipe_list",
|
||||
"character_activities",
|
||||
"character_alt_currency",
|
||||
@@ -325,6 +326,9 @@ namespace DatabaseSchema {
|
||||
"banned_ips",
|
||||
"bug_reports",
|
||||
"bugs",
|
||||
"buyer",
|
||||
"buyer_buy_lines",
|
||||
"buyer_trade_items",
|
||||
"completed_shared_task_activity_state",
|
||||
"completed_shared_task_members",
|
||||
"completed_shared_tasks",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "timer.h"
|
||||
|
||||
#include "dbcore.h"
|
||||
#include "mysql_stmt.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
@@ -436,3 +437,8 @@ MySQLRequestResult DBcore::QueryDatabaseMulti(const std::string &query)
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
mysql::PreparedStmt DBcore::Prepare(std::string query)
|
||||
{
|
||||
return mysql::PreparedStmt(*mysql, std::move(query), m_mutex);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
#define CR_SERVER_GONE_ERROR 2006
|
||||
#define CR_SERVER_LOST 2013
|
||||
|
||||
namespace mysql { class PreparedStmt; }
|
||||
|
||||
class DBcore {
|
||||
public:
|
||||
enum eStatus {
|
||||
@@ -48,6 +50,11 @@ public:
|
||||
}
|
||||
void SetMutex(Mutex *mutex);
|
||||
|
||||
// only safe on connections shared with other threads if results buffered
|
||||
// unsafe to use off main thread due to internal server logging
|
||||
// throws std::runtime_error on failure
|
||||
mysql::PreparedStmt Prepare(std::string query);
|
||||
|
||||
protected:
|
||||
bool Open(
|
||||
const char *iHost,
|
||||
|
||||
+25
-204
@@ -80,39 +80,19 @@ bool Bug::IsValid(uint32 category_id)
|
||||
return bug_category_names.find(category_id) != bug_category_names.end();
|
||||
}
|
||||
|
||||
const char *EQ::constants::GetStanceName(StanceType stance_type) {
|
||||
switch (stance_type) {
|
||||
case stanceUnknown:
|
||||
return "Unknown";
|
||||
case stancePassive:
|
||||
return "Passive";
|
||||
case stanceBalanced:
|
||||
return "Balanced";
|
||||
case stanceEfficient:
|
||||
return "Efficient";
|
||||
case stanceReactive:
|
||||
return "Reactive";
|
||||
case stanceAggressive:
|
||||
return "Aggressive";
|
||||
case stanceAssist:
|
||||
return "Assist";
|
||||
case stanceBurn:
|
||||
return "Burn";
|
||||
case stanceEfficient2:
|
||||
return "Efficient2";
|
||||
case stanceBurnAE:
|
||||
return "BurnAE";
|
||||
default:
|
||||
return "Invalid";
|
||||
}
|
||||
std::string Stance::GetName(uint8 stance_id)
|
||||
{
|
||||
return IsValid(stance_id) ? stance_names[stance_id] : "UNKNOWN STANCE";
|
||||
}
|
||||
|
||||
int EQ::constants::ConvertStanceTypeToIndex(StanceType stance_type) {
|
||||
if (EQ::ValueWithin(stance_type, EQ::constants::stancePassive, EQ::constants::stanceBurnAE)) {
|
||||
return (stance_type - EQ::constants::stancePassive);
|
||||
}
|
||||
bool Stance::IsValid(uint8 stance_id)
|
||||
{
|
||||
return stance_names.find(stance_id) != stance_names.end();
|
||||
}
|
||||
|
||||
return 0;
|
||||
uint8 Stance::GetIndex(uint8 stance_id)
|
||||
{
|
||||
return IsValid(stance_id) ? (stance_id - Stance::Passive) : 0;
|
||||
}
|
||||
|
||||
const std::map<uint8, std::string>& EQ::constants::GetLanguageMap()
|
||||
@@ -206,102 +186,6 @@ std::string EQ::constants::GetFlyModeName(int8 flymode_id)
|
||||
return EQ::constants::GetFlyModeMap().find(flymode_id)->second;
|
||||
}
|
||||
|
||||
const std::map<bodyType, std::string>& EQ::constants::GetBodyTypeMap()
|
||||
{
|
||||
static const std::map<bodyType, std::string> bodytype_map = {
|
||||
{ BT_Humanoid, "Humanoid" },
|
||||
{ BT_Lycanthrope, "Lycanthrope" },
|
||||
{ BT_Undead, "Undead" },
|
||||
{ BT_Giant, "Giant" },
|
||||
{ BT_Construct, "Construct" },
|
||||
{ BT_Extraplanar, "Extraplanar" },
|
||||
{ BT_Magical, "Magical" },
|
||||
{ BT_SummonedUndead, "Summoned Undead" },
|
||||
{ BT_RaidGiant, "Raid Giant" },
|
||||
{ BT_RaidColdain, "Raid Coldain" },
|
||||
{ BT_NoTarget, "Untargetable" },
|
||||
{ BT_Vampire, "Vampire" },
|
||||
{ BT_Atenha_Ra, "Aten Ha Ra" },
|
||||
{ BT_Greater_Akheva, "Greater Akheva" },
|
||||
{ BT_Khati_Sha, "Khati Sha" },
|
||||
{ BT_Seru, "Seru" },
|
||||
{ BT_Grieg_Veneficus, "Grieg Veneficus" },
|
||||
{ BT_Draz_Nurakk, "Draz Nurakk" },
|
||||
{ BT_Zek, "Zek" },
|
||||
{ BT_Luggald, "Luggald" },
|
||||
{ BT_Animal, "Animal" },
|
||||
{ BT_Insect, "Insect" },
|
||||
{ BT_Monster, "Monster" },
|
||||
{ BT_Summoned, "Summoned" },
|
||||
{ BT_Plant, "Plant" },
|
||||
{ BT_Dragon, "Dragon" },
|
||||
{ BT_Summoned2, "Summoned 2" },
|
||||
{ BT_Summoned3, "Summoned 3" },
|
||||
{ BT_Dragon2, "Dragon 2" },
|
||||
{ BT_VeliousDragon, "Velious Dragon" },
|
||||
{ BT_Familiar, "Familiar" },
|
||||
{ BT_Dragon3, "Dragon 3" },
|
||||
{ BT_Boxes, "Boxes" },
|
||||
{ BT_Muramite, "Muramite" },
|
||||
{ BT_NoTarget2, "Untargetable 2" },
|
||||
{ BT_SwarmPet, "Swarm Pet" },
|
||||
{ BT_MonsterSummon, "Monster Summon" },
|
||||
{ BT_InvisMan, "Invisible Man" },
|
||||
{ BT_Special, "Special" },
|
||||
};
|
||||
|
||||
return bodytype_map;
|
||||
}
|
||||
|
||||
std::string EQ::constants::GetBodyTypeName(bodyType bodytype_id)
|
||||
{
|
||||
if (EQ::constants::GetBodyTypeMap().find(bodytype_id) != EQ::constants::GetBodyTypeMap().end()) {
|
||||
return EQ::constants::GetBodyTypeMap().find(bodytype_id)->second;
|
||||
}
|
||||
|
||||
return std::string();
|
||||
}
|
||||
|
||||
const std::map<uint8, std::string>& EQ::constants::GetAccountStatusMap()
|
||||
{
|
||||
static const std::map<uint8, std::string> account_status_map = {
|
||||
{ AccountStatus::Player, "Player" },
|
||||
{ AccountStatus::Steward, "Steward" },
|
||||
{ AccountStatus::ApprenticeGuide, "Apprentice Guide" },
|
||||
{ AccountStatus::Guide, "Guide" },
|
||||
{ AccountStatus::QuestTroupe, "Quest Troupe" },
|
||||
{ AccountStatus::SeniorGuide, "Senior Guide" },
|
||||
{ AccountStatus::GMTester, "GM Tester" },
|
||||
{ AccountStatus::EQSupport, "EQ Support" },
|
||||
{ AccountStatus::GMStaff, "GM Staff" },
|
||||
{ AccountStatus::GMAdmin, "GM Admin" },
|
||||
{ AccountStatus::GMLeadAdmin, "GM Lead Admin" },
|
||||
{ AccountStatus::QuestMaster, "Quest Master" },
|
||||
{ AccountStatus::GMAreas, "GM Areas" },
|
||||
{ AccountStatus::GMCoder, "GM Coder" },
|
||||
{ AccountStatus::GMMgmt, "GM Mgmt" },
|
||||
{ AccountStatus::GMImpossible, "GM Impossible" },
|
||||
{ AccountStatus::Max, "GM Max" }
|
||||
};
|
||||
|
||||
return account_status_map;
|
||||
}
|
||||
|
||||
std::string EQ::constants::GetAccountStatusName(uint8 account_status)
|
||||
{
|
||||
for (
|
||||
auto status_level = EQ::constants::GetAccountStatusMap().rbegin();
|
||||
status_level != EQ::constants::GetAccountStatusMap().rend();
|
||||
++status_level
|
||||
) {
|
||||
if (account_status >= status_level->first) {
|
||||
return status_level->second;
|
||||
}
|
||||
}
|
||||
|
||||
return std::string();
|
||||
}
|
||||
|
||||
const std::map<uint8, std::string>& EQ::constants::GetConsiderLevelMap()
|
||||
{
|
||||
static const std::map<uint8, std::string> consider_level_map = {
|
||||
@@ -392,84 +276,6 @@ std::string EQ::constants::GetSpawnAnimationName(uint8 animation_id)
|
||||
return EQ::constants::GetSpawnAnimationMap().find(animation_id)->second;
|
||||
}
|
||||
|
||||
const std::map<int, std::string>& EQ::constants::GetObjectTypeMap()
|
||||
{
|
||||
static const std::map<int, std::string> object_type_map = {
|
||||
{ ObjectTypes::SmallBag, "Small Bag" },
|
||||
{ ObjectTypes::LargeBag, "Large Bag" },
|
||||
{ ObjectTypes::Quiver, "Quiver" },
|
||||
{ ObjectTypes::BeltPouch, "Belt Pouch" },
|
||||
{ ObjectTypes::WristPouch, "Wrist Pouch" },
|
||||
{ ObjectTypes::Backpack, "Backpack" },
|
||||
{ ObjectTypes::SmallChest, "Small Chest" },
|
||||
{ ObjectTypes::LargeChest, "Large Chest" },
|
||||
{ ObjectTypes::Bandolier, "Bandolier" },
|
||||
{ ObjectTypes::Medicine, "Medicine" },
|
||||
{ ObjectTypes::Tinkering, "Tinkering" },
|
||||
{ ObjectTypes::Lexicon, "Lexicon" },
|
||||
{ ObjectTypes::PoisonMaking, "Mortar and Pestle" },
|
||||
{ ObjectTypes::Quest, "Quest" },
|
||||
{ ObjectTypes::MixingBowl, "Mixing Bowl" },
|
||||
{ ObjectTypes::Baking, "Baking" },
|
||||
{ ObjectTypes::Tailoring, "Tailoring" },
|
||||
{ ObjectTypes::Blacksmithing, "Blacksmithing" },
|
||||
{ ObjectTypes::Fletching, "Fletching" },
|
||||
{ ObjectTypes::Brewing, "Brewing" },
|
||||
{ ObjectTypes::JewelryMaking, "Jewelry Making" },
|
||||
{ ObjectTypes::Pottery, "Pottery" },
|
||||
{ ObjectTypes::Kiln, "Kiln" },
|
||||
{ ObjectTypes::KeyMaker, "Key Maker" },
|
||||
{ ObjectTypes::ResearchWIZ, "Lexicon" },
|
||||
{ ObjectTypes::ResearchMAG, "Lexicon" },
|
||||
{ ObjectTypes::ResearchNEC, "Lexicon" },
|
||||
{ ObjectTypes::ResearchENC, "Lexicon" },
|
||||
{ ObjectTypes::Unknown, "Unknown" },
|
||||
{ ObjectTypes::ResearchPractice, "Lexicon" },
|
||||
{ ObjectTypes::Alchemy, "Alchemy" },
|
||||
{ ObjectTypes::HighElfForge, "High Elf Forge" },
|
||||
{ ObjectTypes::DarkElfForge, "Dark Elf Forge" },
|
||||
{ ObjectTypes::OgreForge, "Ogre Forge" },
|
||||
{ ObjectTypes::DwarfForge, "Dwarf Forge" },
|
||||
{ ObjectTypes::GnomeForge, "Gnome Forge" },
|
||||
{ ObjectTypes::BarbarianForge, "Barbarian Forge" },
|
||||
{ ObjectTypes::IksarForge, "Iksar Forge" },
|
||||
{ ObjectTypes::HumanForgeOne, "Human Forge" },
|
||||
{ ObjectTypes::HumanForgeTwo, "Human Forge" },
|
||||
{ ObjectTypes::HalflingTailoringOne, "Halfling Tailoring" },
|
||||
{ ObjectTypes::HalflingTailoringTwo, "Halfling Tailoring" },
|
||||
{ ObjectTypes::EruditeTailoring, "Erudite Tailoring" },
|
||||
{ ObjectTypes::WoodElfTailoring, "Wood Elf Tailoring" },
|
||||
{ ObjectTypes::WoodElfFletching, "Wood Elf Fletching" },
|
||||
{ ObjectTypes::IksarPottery, "Iksar Pottery" },
|
||||
{ ObjectTypes::Fishing, "Fishing" },
|
||||
{ ObjectTypes::TrollForge, "Troll Forge" },
|
||||
{ ObjectTypes::WoodElfForge, "Wood Elf Forge" },
|
||||
{ ObjectTypes::HalflingForge, "Halfling Forge" },
|
||||
{ ObjectTypes::EruditeForge, "Erudite Forge" },
|
||||
{ ObjectTypes::Merchant, "Merchant" },
|
||||
{ ObjectTypes::FroglokForge, "Froglok Forge" },
|
||||
{ ObjectTypes::Augmenter, "Augmenter" },
|
||||
{ ObjectTypes::Churn, "Churn" },
|
||||
{ ObjectTypes::TransformationMold, "Transformation Mold" },
|
||||
{ ObjectTypes::DetransformationMold, "Detransformation Mold" },
|
||||
{ ObjectTypes::Unattuner, "Unattuner" },
|
||||
{ ObjectTypes::TradeskillBag, "Tradeskill Bag" },
|
||||
{ ObjectTypes::CollectibleBag, "Collectible Bag" },
|
||||
{ ObjectTypes::NoDeposit, "No Deposit" }
|
||||
};
|
||||
|
||||
return object_type_map;
|
||||
}
|
||||
|
||||
std::string EQ::constants::GetObjectTypeName(int object_type)
|
||||
{
|
||||
if (!EQ::ValueWithin(object_type, ObjectTypes::SmallBag, ObjectTypes::NoDeposit)) {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
return EQ::constants::GetObjectTypeMap().find(object_type)->second;
|
||||
}
|
||||
|
||||
const std::map<uint8, std::string> &EQ::constants::GetWeatherTypeMap()
|
||||
{
|
||||
static const std::map<uint8, std::string> weather_type_map = {
|
||||
@@ -629,6 +435,21 @@ std::string EQ::constants::GetConsiderColorName(uint32 consider_color)
|
||||
return c != EQ::constants::GetConsiderColorMap().end() ? c->second : std::string();
|
||||
}
|
||||
|
||||
std::string AccountStatus::GetName(uint8 account_status)
|
||||
{
|
||||
for (
|
||||
auto e = account_status_names.rbegin();
|
||||
e != account_status_names.rend();
|
||||
++e
|
||||
) {
|
||||
if (account_status >= e->first) {
|
||||
return e->second;
|
||||
}
|
||||
}
|
||||
|
||||
return "UNKNOWN ACCOUNT STATUS";
|
||||
}
|
||||
|
||||
std::string ComparisonType::GetName(uint8 type)
|
||||
{
|
||||
return IsValid(type) ? comparison_types[type] : "UNKNOWN COMPARISON TYPE";
|
||||
|
||||
+84
-115
@@ -26,6 +26,48 @@
|
||||
|
||||
#include <string.h>
|
||||
|
||||
namespace AccountStatus {
|
||||
constexpr uint8 Player = 0;
|
||||
constexpr uint8 Steward = 10;
|
||||
constexpr uint8 ApprenticeGuide = 20;
|
||||
constexpr uint8 Guide = 50;
|
||||
constexpr uint8 QuestTroupe = 80;
|
||||
constexpr uint8 SeniorGuide = 81;
|
||||
constexpr uint8 GMTester = 85;
|
||||
constexpr uint8 EQSupport = 90;
|
||||
constexpr uint8 GMStaff = 95;
|
||||
constexpr uint8 GMAdmin = 100;
|
||||
constexpr uint8 GMLeadAdmin = 150;
|
||||
constexpr uint8 QuestMaster = 160;
|
||||
constexpr uint8 GMAreas = 170;
|
||||
constexpr uint8 GMCoder = 180;
|
||||
constexpr uint8 GMMgmt = 200;
|
||||
constexpr uint8 GMImpossible = 250;
|
||||
constexpr uint8 Max = 255;
|
||||
|
||||
std::string GetName(uint8 account_status);
|
||||
}
|
||||
|
||||
static std::map<uint8, std::string> account_status_names = {
|
||||
{ AccountStatus::Player, "Player" },
|
||||
{ AccountStatus::Steward, "Steward" },
|
||||
{ AccountStatus::ApprenticeGuide, "Apprentice Guide" },
|
||||
{ AccountStatus::Guide, "Guide" },
|
||||
{ AccountStatus::QuestTroupe, "Quest Troupe" },
|
||||
{ AccountStatus::SeniorGuide, "Senior Guide" },
|
||||
{ AccountStatus::GMTester, "GM Tester" },
|
||||
{ AccountStatus::EQSupport, "EQ Support" },
|
||||
{ AccountStatus::GMStaff, "GM Staff" },
|
||||
{ AccountStatus::GMAdmin, "GM Admin" },
|
||||
{ AccountStatus::GMLeadAdmin, "GM Lead Admin" },
|
||||
{ AccountStatus::QuestMaster, "Quest Master" },
|
||||
{ AccountStatus::GMAreas, "GM Areas" },
|
||||
{ AccountStatus::GMCoder, "GM Coder" },
|
||||
{ AccountStatus::GMMgmt, "GM Mgmt" },
|
||||
{ AccountStatus::GMImpossible, "GM Impossible" },
|
||||
{ AccountStatus::Max, "GM Max" }
|
||||
};
|
||||
|
||||
namespace ComparisonType {
|
||||
constexpr uint8 Equal = 0;
|
||||
constexpr uint8 NotEqual = 1;
|
||||
@@ -55,7 +97,6 @@ static std::map<uint8, std::string> comparison_types = {
|
||||
{ ComparisonType::NotBetween, "Not Between" },
|
||||
};
|
||||
|
||||
|
||||
// local definitions are the result of using hybrid-client or server-only values and methods
|
||||
namespace EQ
|
||||
{
|
||||
@@ -233,19 +274,6 @@ namespace EQ
|
||||
const size_t SAY_LINK_CLOSER_SIZE = 1;
|
||||
const size_t SAY_LINK_MAXIMUM_SIZE = (SAY_LINK_OPENER_SIZE + SAY_LINK_BODY_SIZE + SAY_LINK_TEXT_SIZE + SAY_LINK_CLOSER_SIZE);
|
||||
|
||||
enum StanceType : int {
|
||||
stanceUnknown = 0,
|
||||
stancePassive,
|
||||
stanceBalanced,
|
||||
stanceEfficient,
|
||||
stanceReactive,
|
||||
stanceAggressive,
|
||||
stanceAssist,
|
||||
stanceBurn,
|
||||
stanceEfficient2,
|
||||
stanceBurnAE
|
||||
};
|
||||
|
||||
enum BotSpellIDs : int {
|
||||
Warrior = 3001,
|
||||
Cleric,
|
||||
@@ -296,70 +324,6 @@ namespace EQ
|
||||
Looting
|
||||
};
|
||||
|
||||
enum ObjectTypes : int {
|
||||
SmallBag,
|
||||
LargeBag,
|
||||
Quiver,
|
||||
BeltPouch,
|
||||
WristPouch,
|
||||
Backpack,
|
||||
SmallChest,
|
||||
LargeChest,
|
||||
Bandolier,
|
||||
Medicine,
|
||||
Tinkering,
|
||||
Lexicon,
|
||||
PoisonMaking,
|
||||
Quest,
|
||||
MixingBowl,
|
||||
Baking,
|
||||
Tailoring,
|
||||
Blacksmithing,
|
||||
Fletching,
|
||||
Brewing,
|
||||
JewelryMaking,
|
||||
Pottery,
|
||||
Kiln,
|
||||
KeyMaker,
|
||||
ResearchWIZ,
|
||||
ResearchMAG,
|
||||
ResearchNEC,
|
||||
ResearchENC,
|
||||
Unknown,
|
||||
ResearchPractice,
|
||||
Alchemy,
|
||||
HighElfForge,
|
||||
DarkElfForge,
|
||||
OgreForge,
|
||||
DwarfForge,
|
||||
GnomeForge,
|
||||
BarbarianForge,
|
||||
IksarForge,
|
||||
HumanForgeOne,
|
||||
HumanForgeTwo,
|
||||
HalflingTailoringOne,
|
||||
HalflingTailoringTwo,
|
||||
EruditeTailoring,
|
||||
WoodElfTailoring,
|
||||
WoodElfFletching,
|
||||
IksarPottery,
|
||||
Fishing,
|
||||
TrollForge,
|
||||
WoodElfForge,
|
||||
HalflingForge,
|
||||
EruditeForge,
|
||||
Merchant,
|
||||
FroglokForge,
|
||||
Augmenter,
|
||||
Churn,
|
||||
TransformationMold,
|
||||
DetransformationMold,
|
||||
Unattuner,
|
||||
TradeskillBag,
|
||||
CollectibleBag,
|
||||
NoDeposit
|
||||
};
|
||||
|
||||
enum WeatherTypes : uint8 {
|
||||
None,
|
||||
Raining,
|
||||
@@ -385,9 +349,6 @@ namespace EQ
|
||||
Proximity
|
||||
};
|
||||
|
||||
const char *GetStanceName(StanceType stance_type);
|
||||
int ConvertStanceTypeToIndex(StanceType stance_type);
|
||||
|
||||
extern const std::map<uint8, std::string>& GetLanguageMap();
|
||||
std::string GetLanguageName(uint8 language_id);
|
||||
|
||||
@@ -397,12 +358,6 @@ namespace EQ
|
||||
extern const std::map<int8, std::string>& GetFlyModeMap();
|
||||
std::string GetFlyModeName(int8 flymode_id);
|
||||
|
||||
extern const std::map<bodyType, std::string>& GetBodyTypeMap();
|
||||
std::string GetBodyTypeName(bodyType bodytype_id);
|
||||
|
||||
extern const std::map<uint8, std::string>& GetAccountStatusMap();
|
||||
std::string GetAccountStatusName(uint8 account_status);
|
||||
|
||||
extern const std::map<uint8, std::string>& GetConsiderLevelMap();
|
||||
std::string GetConsiderLevelName(uint8 consider_level);
|
||||
|
||||
@@ -415,9 +370,6 @@ namespace EQ
|
||||
extern const std::map<uint8, std::string>& GetSpawnAnimationMap();
|
||||
std::string GetSpawnAnimationName(uint8 animation_id);
|
||||
|
||||
extern const std::map<int, std::string>& GetObjectTypeMap();
|
||||
std::string GetObjectTypeName(int object_type);
|
||||
|
||||
extern const std::map<uint8, std::string>& GetWeatherTypeMap();
|
||||
std::string GetWeatherTypeName(uint8 weather_type);
|
||||
|
||||
@@ -433,10 +385,6 @@ namespace EQ
|
||||
extern const std::map<uint32, std::string>& GetConsiderColorMap();
|
||||
std::string GetConsiderColorName(uint32 consider_color);
|
||||
|
||||
const int STANCE_TYPE_FIRST = stancePassive;
|
||||
const int STANCE_TYPE_LAST = stanceBurnAE;
|
||||
const int STANCE_TYPE_COUNT = stanceBurnAE;
|
||||
|
||||
} /*constants*/
|
||||
|
||||
namespace profile {
|
||||
@@ -503,7 +451,7 @@ namespace EQ
|
||||
Raid,
|
||||
Guild
|
||||
};
|
||||
}; // namespace consent
|
||||
};
|
||||
} /*EQEmu*/
|
||||
|
||||
enum ServerLockType : int {
|
||||
@@ -512,26 +460,6 @@ enum ServerLockType : int {
|
||||
Unlock
|
||||
};
|
||||
|
||||
enum AccountStatus : uint8 {
|
||||
Player = 0,
|
||||
Steward = 10,
|
||||
ApprenticeGuide = 20,
|
||||
Guide = 50,
|
||||
QuestTroupe = 80,
|
||||
SeniorGuide = 81,
|
||||
GMTester = 85,
|
||||
EQSupport = 90,
|
||||
GMStaff = 95,
|
||||
GMAdmin = 100,
|
||||
GMLeadAdmin = 150,
|
||||
QuestMaster = 160,
|
||||
GMAreas = 170,
|
||||
GMCoder = 180,
|
||||
GMMgmt = 200,
|
||||
GMImpossible = 250,
|
||||
Max = 255
|
||||
};
|
||||
|
||||
enum Invisibility : uint8 {
|
||||
Visible,
|
||||
Invisible,
|
||||
@@ -793,4 +721,45 @@ static std::map<uint32, std::string> bug_category_names = {
|
||||
{ Bug::Category::Mercenaries, "Mercenaries" }
|
||||
};
|
||||
|
||||
namespace Stance {
|
||||
constexpr uint32 Unknown = 0;
|
||||
constexpr uint32 Passive = 1;
|
||||
constexpr uint32 Balanced = 2;
|
||||
constexpr uint32 Efficient = 3;
|
||||
constexpr uint32 Reactive = 4;
|
||||
constexpr uint32 Aggressive = 5;
|
||||
constexpr uint32 Assist = 6;
|
||||
constexpr uint32 Burn = 7;
|
||||
constexpr uint32 Efficient2 = 8;
|
||||
constexpr uint32 AEBurn = 9;
|
||||
|
||||
std::string GetName(uint8 stance_id);
|
||||
uint8 GetIndex(uint8 stance_id);
|
||||
bool IsValid(uint8 stance_id);
|
||||
}
|
||||
|
||||
static std::map<uint32, std::string> stance_names = {
|
||||
{ Stance::Unknown, "Unknown" },
|
||||
{ Stance::Passive, "Passive" },
|
||||
{ Stance::Balanced, "Balanced" },
|
||||
{ Stance::Efficient, "Efficient" },
|
||||
{ Stance::Reactive, "Reactive" },
|
||||
{ Stance::Aggressive, "Aggressive" },
|
||||
{ Stance::Assist, "Assist" },
|
||||
{ Stance::Burn, "Burn" },
|
||||
{ Stance::Efficient2, "Efficient" },
|
||||
{ Stance::AEBurn, "AE Burn" }
|
||||
};
|
||||
|
||||
namespace PCNPCOnlyFlagType {
|
||||
constexpr int PC = 1;
|
||||
constexpr int NPC = 2;
|
||||
}
|
||||
|
||||
namespace BookType {
|
||||
constexpr uint8 Scroll = 0;
|
||||
constexpr uint8 Book = 1;
|
||||
constexpr uint8 ItemInfo = 2;
|
||||
}
|
||||
|
||||
#endif /*COMMON_EMU_CONSTANTS_H*/
|
||||
|
||||
@@ -67,6 +67,7 @@ N(OP_Buff),
|
||||
N(OP_BuffCreate),
|
||||
N(OP_BuffRemoveRequest),
|
||||
N(OP_Bug),
|
||||
N(OP_BuyerItems),
|
||||
N(OP_CameraEffect),
|
||||
N(OP_Camp),
|
||||
N(OP_CancelSneakHide),
|
||||
@@ -533,6 +534,7 @@ N(OP_Stamina),
|
||||
N(OP_Stun),
|
||||
N(OP_Surname),
|
||||
N(OP_SwapSpell),
|
||||
N(OP_SystemFingerprint),
|
||||
N(OP_TargetBuffs),
|
||||
N(OP_TargetCommand),
|
||||
N(OP_TargetHoTT),
|
||||
|
||||
@@ -758,10 +758,10 @@ typedef enum {
|
||||
FilterFocusEffects = 22, //0=show, 1=hide
|
||||
FilterPetSpells = 23, //0=show, 1=hide
|
||||
FilterHealOverTime = 24, //0=show, 1=mine only, 2=hide
|
||||
FilterUnknown25 = 25,
|
||||
FilterUnknown26 = 26,
|
||||
FilterUnknown27 = 27,
|
||||
FilterUnknown28 = 28,
|
||||
FilterItemSpeech = 25, //0=show, 1=hide // RoF2 Confirmed
|
||||
FilterStrikethrough = 26, //0=show, 1=hide // RoF2 Confirmed
|
||||
FilterStuns = 27, //0=show, 1=hide // RoF2 Confirmed
|
||||
FilterBardSongsOnPets = 28, //0=show, 1=hide // RoF2 Confirmed
|
||||
_FilterCount
|
||||
} eqFilterType;
|
||||
|
||||
@@ -1129,4 +1129,7 @@ enum ExpSource
|
||||
#define PARCEL_LIMIT 5
|
||||
#define PARCEL_BEGIN_SLOT 1
|
||||
|
||||
namespace DoorType {
|
||||
constexpr uint32 BuyerStall = 155;
|
||||
}
|
||||
#endif /*COMMON_EQ_CONSTANTS_H*/
|
||||
|
||||
+385
-66
@@ -323,6 +323,7 @@ union
|
||||
bool show_name;
|
||||
bool guild_show;
|
||||
bool trader;
|
||||
bool buyer;
|
||||
};
|
||||
|
||||
struct PlayerState_Struct {
|
||||
@@ -1620,6 +1621,32 @@ struct MoveItem_Struct
|
||||
/*0012*/
|
||||
};
|
||||
|
||||
// New for RoF2 - Size: 12
|
||||
struct InventorySlot_Struct
|
||||
{
|
||||
/*000*/ int16 Type; // Worn and Normal inventory = 0, Bank = 1, Shared Bank = 2, Delete Item = -1
|
||||
/*002*/ int16 Unknown02;
|
||||
/*004*/ int16 Slot;
|
||||
/*006*/ int16 SubIndex;
|
||||
/*008*/ int16 AugIndex; // Guessing - Seen 0xffff
|
||||
/*010*/ int16 Unknown01; // Normally 0 - Seen 13262 when deleting an item, but didn't match item ID
|
||||
/*012*/
|
||||
};
|
||||
|
||||
struct MultiMoveItemSub_Struct
|
||||
{
|
||||
/*0000*/ InventorySlot_Struct from_slot;
|
||||
/*0012*/ InventorySlot_Struct to_slot;
|
||||
/*0024*/ uint32 number_in_stack;
|
||||
/*0028*/ uint8 unknown[8];
|
||||
};
|
||||
|
||||
struct MultiMoveItem_Struct
|
||||
{
|
||||
/*0000*/ uint32 count;
|
||||
/*0004*/ MultiMoveItemSub_Struct moves[0];
|
||||
};
|
||||
|
||||
// both MoveItem_Struct/DeleteItem_Struct server structures will be changing to a structure-based slot format..this will
|
||||
// be used for handling SoF/SoD/etc... time stamps sent using the MoveItem_Struct format. (nothing will be done with this
|
||||
// info at the moment..but, it is forwarded on to the server for handling/future use)
|
||||
@@ -3113,60 +3140,369 @@ struct BazaarSearchResults_Struct {
|
||||
// Barter/Buyer
|
||||
//
|
||||
//
|
||||
enum {
|
||||
Barter_BuyerSearch = 0,
|
||||
Barter_SellerSearch = 1,
|
||||
Barter_BuyerModeOn = 2,
|
||||
Barter_BuyerModeOff = 3,
|
||||
Barter_BuyerItemUpdate = 5,
|
||||
Barter_BuyerItemRemove = 6,
|
||||
Barter_SellItem = 7,
|
||||
#define MAX_BUYER_COMPENSATION_ITEMS 10
|
||||
|
||||
enum BarterBuyerActions {
|
||||
Barter_BuyerSearch = 0,
|
||||
Barter_SellerSearch = 1,
|
||||
Barter_BuyerModeOn = 2,
|
||||
Barter_BuyerModeOff = 3,
|
||||
Barter_BuyerItemStart = 4,
|
||||
Barter_BuyerItemUpdate = 5,
|
||||
Barter_BuyerItemRemove = 6,
|
||||
Barter_SellItem = 7,
|
||||
Barter_SellerTransactionComplete = 8,
|
||||
Barter_BuyerTransactionComplete = 9,
|
||||
Barter_BuyerInspectBegin = 10,
|
||||
Barter_BuyerInspectEnd = 11,
|
||||
Barter_BuyerAppearance = 12,
|
||||
Barter_BuyerInspectWindow = 13,
|
||||
Barter_BarterItemInspect = 14,
|
||||
Barter_SellerBrowsing = 15,
|
||||
Barter_BuyerSearchResults = 16,
|
||||
Barter_Welcome = 17,
|
||||
Barter_WelcomeMessageUpdate = 19,
|
||||
Barter_BuyerItemInspect = 21,
|
||||
Barter_Unknown23 = 23
|
||||
Barter_BuyerTransactionComplete = 9,
|
||||
Barter_BuyerInspectBegin = 10,
|
||||
Barter_BuyerInspectEnd = 11,
|
||||
Barter_BuyerAppearance = 12,
|
||||
Barter_BuyerInspectWindow = 13,
|
||||
Barter_BarterItemInspect = 14,
|
||||
Barter_SellerBrowsing = 15,
|
||||
Barter_BuyerSearchResults = 16,
|
||||
Barter_Welcome = 17,
|
||||
Barter_WelcomeMessageUpdate = 19,
|
||||
Barter_Greeting = 20,
|
||||
Barter_BuyerItemInspect = 21,
|
||||
Barter_OpenBarterWindow = 23,
|
||||
Barter_AddToBarterWindow = 26,
|
||||
Barter_RemoveFromBarterWindow = 27,
|
||||
Barter_RemoveFromMerchantWindow = 50, //Not a client item. Used for internal communications.
|
||||
Barter_FailedTransaction = 51,
|
||||
Barter_BuyerCouldNotBeFound = 52,
|
||||
Barter_FailedBuyerChecks = 53,
|
||||
Barter_SellerCouldNotBeFound = 54,
|
||||
Barter_FailedSellerChecks = 55
|
||||
};
|
||||
|
||||
enum BarterBuyerSubActions {
|
||||
Barter_Success = 0,
|
||||
Barter_Failure = 1,
|
||||
Barter_DataOutOfDate = 4,
|
||||
Barter_SellerDoesNotHaveItem = 6,
|
||||
Barter_SameZone = 8
|
||||
};
|
||||
|
||||
enum BuyerBarter {
|
||||
Off = 0,
|
||||
On = 1
|
||||
};
|
||||
|
||||
struct BuyerRemoveItem_Struct {
|
||||
uint32 action;
|
||||
uint32 buy_slot_id;
|
||||
};
|
||||
|
||||
struct BuyerRemoveItemFromMerchantWindow_Struct {
|
||||
uint32 action;
|
||||
uint32 unknown_004;
|
||||
uint32 buy_slot_id;
|
||||
uint32 unknown_012;
|
||||
};
|
||||
|
||||
struct BuyerGeneric_Struct {
|
||||
uint32 action;
|
||||
char payload[];
|
||||
};
|
||||
|
||||
struct BuyerMessaging_Struct {
|
||||
uint32 action;
|
||||
uint32 sub_action;
|
||||
uint32 zone_id;
|
||||
uint32 buyer_id;
|
||||
uint32 buyer_entity_id;
|
||||
char buyer_name[64];
|
||||
uint32 buy_item_id;
|
||||
uint32 buy_item_qty;
|
||||
uint64 buy_item_cost;
|
||||
uint32 buy_item_icon;
|
||||
uint32 seller_entity_id;
|
||||
char seller_name[64];
|
||||
char item_name[64];
|
||||
uint32 slot;
|
||||
uint32 seller_quantity;
|
||||
};
|
||||
|
||||
struct BuyerAddBuyertoBarterWindow_Struct {
|
||||
uint32 action;
|
||||
uint32 zone_id;
|
||||
uint32 buyer_id;
|
||||
uint32 buyer_entity_id;
|
||||
char buyer_name[64];
|
||||
};
|
||||
|
||||
struct BuyerRemoveBuyerFromBarterWindow_Struct {
|
||||
uint32 action;
|
||||
uint32 buyer_id;
|
||||
};
|
||||
|
||||
struct BuyerBrowsing_Struct {
|
||||
uint32 action;
|
||||
char char_name[64];
|
||||
};
|
||||
|
||||
struct BuyerGreeting_Struct {
|
||||
uint32 action;
|
||||
uint32 buyer_id;
|
||||
};
|
||||
|
||||
struct BuyerWelcomeMessageUpdate_Struct {
|
||||
/*000*/ uint32 Action;
|
||||
/*004*/ char WelcomeMessage[256];
|
||||
uint32 action;
|
||||
char welcome_message[256];
|
||||
};
|
||||
|
||||
struct BuyerItemSearch_Struct {
|
||||
/*000*/ uint32 Unknown000;
|
||||
/*004*/ char SearchString[64];
|
||||
struct BuyerLineTradeItems_Struct {
|
||||
uint32 item_id;
|
||||
uint32 item_quantity;
|
||||
uint32 item_icon;
|
||||
std::string item_name;
|
||||
|
||||
void operator*=(uint32 multiplier)
|
||||
{
|
||||
this->item_quantity *= multiplier;
|
||||
}
|
||||
|
||||
template<class Archive>
|
||||
void serialize(Archive &archive)
|
||||
{
|
||||
archive(
|
||||
CEREAL_NVP(item_id),
|
||||
CEREAL_NVP(item_quantity),
|
||||
CEREAL_NVP(item_icon),
|
||||
CEREAL_NVP(item_name)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
struct BuyerItemSearchResultEntry_Struct {
|
||||
/*000*/ char ItemName[64];
|
||||
/*064*/ uint32 ItemID;
|
||||
/*068*/ uint32 Unknown068;
|
||||
/*072*/ uint32 Unknown072;
|
||||
struct BuyerLineItems_Struct {
|
||||
uint32 slot;
|
||||
uint8 enabled;
|
||||
uint32 item_id;
|
||||
std::string item_name;
|
||||
uint32 item_icon;
|
||||
uint32 item_quantity;
|
||||
uint8 item_toggle;
|
||||
uint32 item_cost;
|
||||
std::vector<BuyerLineTradeItems_Struct> trade_items;
|
||||
|
||||
template<class Archive>
|
||||
void serialize(Archive &archive)
|
||||
{
|
||||
archive(
|
||||
CEREAL_NVP(slot),
|
||||
CEREAL_NVP(enabled),
|
||||
CEREAL_NVP(item_id),
|
||||
CEREAL_NVP(item_name),
|
||||
CEREAL_NVP(item_icon),
|
||||
CEREAL_NVP(item_quantity),
|
||||
CEREAL_NVP(item_toggle),
|
||||
CEREAL_NVP(item_cost),
|
||||
CEREAL_NVP(trade_items)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
#define MAX_BUYER_ITEMSEARCH_RESULTS 200
|
||||
struct BuyerBuyLines_Struct {
|
||||
uint32 action;
|
||||
union {
|
||||
uint32 no_items;
|
||||
uint32 string_length;
|
||||
};
|
||||
std::vector<BuyerLineItems_Struct> buy_lines;
|
||||
|
||||
struct BuyerItemSearchResults_Struct {
|
||||
uint32 Action;
|
||||
uint32 ResultCount;
|
||||
BuyerItemSearchResultEntry_Struct Results[MAX_BUYER_ITEMSEARCH_RESULTS];
|
||||
template<class Archive>
|
||||
void serialize(Archive &archive)
|
||||
{
|
||||
archive(
|
||||
CEREAL_NVP(action),
|
||||
CEREAL_NVP(no_items),
|
||||
CEREAL_NVP(buy_lines)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
struct BuyerLineSellItem_Struct {
|
||||
uint32 action;
|
||||
uint32 sub_action;
|
||||
uint32 error_code;
|
||||
uint32 purchase_method; // 0 direct merchant, 1 via /barter window
|
||||
uint32 buyer_entity_id;
|
||||
uint32 buyer_id;
|
||||
std::string buyer_name;
|
||||
uint32 seller_entity_id;
|
||||
std::string seller_name;
|
||||
uint32 slot;
|
||||
uint8 enabled;
|
||||
uint32 item_id;
|
||||
char item_name[64];
|
||||
uint32 item_icon;
|
||||
uint32 item_quantity;
|
||||
uint8 item_toggle;
|
||||
uint32 item_cost;
|
||||
uint32 no_trade_items;
|
||||
std::vector<BuyerLineTradeItems_Struct> trade_items;
|
||||
uint32 seller_quantity;
|
||||
uint32 zone_id;
|
||||
|
||||
template<class Archive>
|
||||
void serialize(Archive &archive)
|
||||
{
|
||||
archive(
|
||||
CEREAL_NVP(action),
|
||||
CEREAL_NVP(sub_action),
|
||||
CEREAL_NVP(error_code),
|
||||
CEREAL_NVP(purchase_method),
|
||||
CEREAL_NVP(buyer_entity_id),
|
||||
CEREAL_NVP(buyer_id),
|
||||
CEREAL_NVP(buyer_name),
|
||||
CEREAL_NVP(seller_entity_id),
|
||||
CEREAL_NVP(seller_name),
|
||||
CEREAL_NVP(slot),
|
||||
CEREAL_NVP(enabled),
|
||||
CEREAL_NVP(item_id),
|
||||
CEREAL_NVP(item_name),
|
||||
CEREAL_NVP(item_icon),
|
||||
CEREAL_NVP(item_quantity),
|
||||
CEREAL_NVP(item_toggle),
|
||||
CEREAL_NVP(item_cost),
|
||||
CEREAL_NVP(no_trade_items),
|
||||
CEREAL_NVP(trade_items),
|
||||
CEREAL_NVP(seller_quantity),
|
||||
CEREAL_NVP(zone_id)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
struct BuyerLineItemsSearch_Struct {
|
||||
uint32 slot;
|
||||
uint8 enabled;
|
||||
uint32 item_id;
|
||||
char item_name[64];
|
||||
uint32 item_icon;
|
||||
uint32 item_quantity;
|
||||
uint8 item_toggle;
|
||||
uint32 item_cost;
|
||||
uint32 buyer_id;
|
||||
uint32 buyer_entity_id;
|
||||
uint32 buyer_zone_id;
|
||||
uint32 buyer_zone_instance_id;
|
||||
std::string buyer_name;
|
||||
std::vector<BuyerLineTradeItems_Struct> trade_items;
|
||||
|
||||
template<class Archive>
|
||||
void serialize(Archive &archive)
|
||||
{
|
||||
archive(
|
||||
CEREAL_NVP(slot),
|
||||
CEREAL_NVP(enabled),
|
||||
CEREAL_NVP(item_id),
|
||||
CEREAL_NVP(item_name),
|
||||
CEREAL_NVP(item_icon),
|
||||
CEREAL_NVP(item_quantity),
|
||||
CEREAL_NVP(item_toggle),
|
||||
CEREAL_NVP(item_cost),
|
||||
CEREAL_NVP(buyer_id),
|
||||
CEREAL_NVP(buyer_entity_id),
|
||||
CEREAL_NVP(buyer_zone_id),
|
||||
CEREAL_NVP(buyer_zone_instance_id),
|
||||
CEREAL_NVP(buyer_name),
|
||||
CEREAL_NVP(trade_items)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
struct BuyerLineSearch_Struct {
|
||||
uint32 action;
|
||||
uint32 no_items;
|
||||
std::string search_string;
|
||||
uint32 transaction_id;
|
||||
std::vector<BuyerLineItemsSearch_Struct> buy_line;
|
||||
|
||||
template<class Archive>
|
||||
void serialize(Archive &archive)
|
||||
{
|
||||
archive(
|
||||
CEREAL_NVP(action),
|
||||
CEREAL_NVP(no_items),
|
||||
CEREAL_NVP(search_string),
|
||||
CEREAL_NVP(transaction_id),
|
||||
CEREAL_NVP(buy_line)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
struct BuyerSetAppearance_Struct {
|
||||
uint32 action;
|
||||
uint32 entity_id;
|
||||
uint32 status; // 0 off 1 on
|
||||
char buyer_name[64];
|
||||
};
|
||||
|
||||
struct BarterItemSearchLinkRequest_Struct {
|
||||
uint32 action;
|
||||
uint32 searcher_id;
|
||||
uint32 unknown_008;
|
||||
uint32 unknown_012;
|
||||
uint32 item_id;
|
||||
uint32 unknown_020;
|
||||
};
|
||||
|
||||
struct BuyerInspectRequest_Struct {
|
||||
uint32 action;
|
||||
uint32 buyer_id;
|
||||
uint32 approval;
|
||||
};
|
||||
|
||||
struct BarterSearchRequest_Struct {
|
||||
uint32 Action;
|
||||
char SearchString[64];
|
||||
uint32 SearchID;
|
||||
uint32 action;
|
||||
char search_string[64];
|
||||
uint32 transaction_id;
|
||||
uint32 unknown_072;
|
||||
uint32 buyer_id;
|
||||
uint8 search_scope; //0 All Buyers, 1 Local Buyers
|
||||
uint16 zone_id;
|
||||
};
|
||||
|
||||
struct BuyerItemSearch_Struct {
|
||||
uint32 action;
|
||||
char search_string[64];
|
||||
};
|
||||
|
||||
struct BuyerItemSearchResultEntry_Struct {
|
||||
char item_name[64];
|
||||
uint32 item_id;
|
||||
uint32 item_icon;
|
||||
uint32 unknown_072;
|
||||
|
||||
template<class Archive>
|
||||
void serialize(Archive &archive)
|
||||
{
|
||||
archive(
|
||||
CEREAL_NVP(item_name),
|
||||
CEREAL_NVP(item_id),
|
||||
CEREAL_NVP(item_icon),
|
||||
CEREAL_NVP(unknown_072)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
struct BuyerItemSearchResults_Struct {
|
||||
uint32 action;
|
||||
uint32 result_count;
|
||||
std::vector<BuyerItemSearchResultEntry_Struct> results;
|
||||
|
||||
template<class Archive>
|
||||
void serialize(Archive &archive)
|
||||
{
|
||||
archive(
|
||||
CEREAL_NVP(action),
|
||||
CEREAL_NVP(result_count),
|
||||
CEREAL_NVP(results)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
//old below here
|
||||
struct BuyerItemSearchLinkRequest_Struct {
|
||||
/*000*/ uint32 Action; // 0x00000015
|
||||
/*004*/ uint32 ItemID;
|
||||
@@ -3174,31 +3510,6 @@ struct BuyerItemSearchLinkRequest_Struct {
|
||||
/*012*/ uint32 Unknown012;
|
||||
};
|
||||
|
||||
struct BarterItemSearchLinkRequest_Struct {
|
||||
/*000*/ uint32 Action; // 0x0000000E
|
||||
/*004*/ uint32 SearcherID;
|
||||
/*008*/ uint32 Unknown008;
|
||||
/*012*/ uint32 Unknown012;
|
||||
/*016*/ uint32 ItemID;
|
||||
/*020*/ uint32 Unknown020;
|
||||
};
|
||||
|
||||
struct BuyerInspectRequest_Struct {
|
||||
uint32 Action;
|
||||
uint32 BuyerID;
|
||||
uint32 Approval;
|
||||
};
|
||||
|
||||
struct BuyerBrowsing_Struct {
|
||||
uint32 Action;
|
||||
char PlayerName[64];
|
||||
};
|
||||
|
||||
struct BuyerRemoveItem_Struct {
|
||||
uint32 Action;
|
||||
uint32 BuySlot;
|
||||
};
|
||||
|
||||
struct ServerSideFilters_Struct {
|
||||
uint8 clientattackfilters; // 0) No, 1) All (players) but self, 2) All (players) but group
|
||||
uint8 npcattackfilters; // 0) No, 1) Ignore NPC misses (all), 2) Ignore NPC Misses + Attacks (all but self), 3) Ignores NPC Misses + Attacks (all but group)
|
||||
@@ -6040,9 +6351,12 @@ enum BazaarTraderBarterActions {
|
||||
};
|
||||
|
||||
enum BazaarPurchaseActions {
|
||||
ByVendor = 0,
|
||||
ByParcel = 1,
|
||||
ByDirectToInventory = 2
|
||||
BazaarByVendor = 0,
|
||||
BazaarByParcel = 1,
|
||||
BazaarByDirectToInventory = 2,
|
||||
BarterByVendor = 0,
|
||||
BarterInBazaar = 1,
|
||||
BarterOutsideBazaar = 2
|
||||
};
|
||||
|
||||
enum BazaarPurchaseSubActions {
|
||||
@@ -6116,6 +6430,11 @@ struct BazaarSearchMessaging_Struct {
|
||||
}
|
||||
};
|
||||
|
||||
struct BuylineItemDetails_Struct {
|
||||
uint64 item_cost;
|
||||
uint32 item_quantity;
|
||||
};
|
||||
|
||||
// Restore structure packing to default
|
||||
#pragma pack()
|
||||
|
||||
|
||||
@@ -171,6 +171,7 @@ void EQEmuConfig::parse_config()
|
||||
PluginDir = _root["server"]["directories"].get("plugins", "plugins/").asString();
|
||||
LuaModuleDir = _root["server"]["directories"].get("lua_modules", "lua_modules/").asString();
|
||||
PatchDir = _root["server"]["directories"].get("patches", "./").asString();
|
||||
OpcodeDir = _root["server"]["directories"].get("opcodes", "./").asString();
|
||||
SharedMemDir = _root["server"]["directories"].get("shared_memory", "shared/").asString();
|
||||
LogDir = _root["server"]["directories"].get("logs", "logs/").asString();
|
||||
|
||||
|
||||
@@ -95,6 +95,7 @@ class EQEmuConfig
|
||||
std::string PluginDir;
|
||||
std::string LuaModuleDir;
|
||||
std::string PatchDir;
|
||||
std::string OpcodeDir;
|
||||
std::string SharedMemDir;
|
||||
std::string LogDir;
|
||||
|
||||
|
||||
@@ -789,50 +789,36 @@ std::string PlayerEventDiscordFormatter::FormatNPCHandinEvent(
|
||||
);
|
||||
}
|
||||
|
||||
std::string npc_info = fmt::format(
|
||||
"{} ({})\n",
|
||||
e.npc_name,
|
||||
e.npc_id
|
||||
);
|
||||
|
||||
npc_info += fmt::format(
|
||||
"Is Quest Handin: {}",
|
||||
e.is_quest_handin ? "Yes" : "No"
|
||||
);
|
||||
|
||||
std::vector<DiscordField> f = {};
|
||||
|
||||
|
||||
BuildDiscordField(&f, "NPC", npc_info);
|
||||
|
||||
if (!handin_items_info.empty()) {
|
||||
BuildDiscordField(
|
||||
&f,
|
||||
"Handin Items",
|
||||
fmt::format(
|
||||
"{}",
|
||||
handin_items_info
|
||||
)
|
||||
);
|
||||
BuildDiscordField(&f, "Handin Items", handin_items_info);
|
||||
}
|
||||
|
||||
if (!handin_money_info.empty()) {
|
||||
BuildDiscordField(
|
||||
&f,
|
||||
"Handin Money",
|
||||
fmt::format(
|
||||
"{}",
|
||||
handin_money_info
|
||||
)
|
||||
);
|
||||
BuildDiscordField(&f, "Handin Money", handin_money_info);
|
||||
}
|
||||
|
||||
if (!return_items_info.empty()) {
|
||||
BuildDiscordField(
|
||||
&f,
|
||||
"Return Items",
|
||||
fmt::format(
|
||||
"{}",
|
||||
return_items_info
|
||||
)
|
||||
);
|
||||
BuildDiscordField(&f, "Return Items", return_items_info);
|
||||
}
|
||||
|
||||
if (!return_money_info.empty()) {
|
||||
BuildDiscordField(
|
||||
&f,
|
||||
"Return Money",
|
||||
fmt::format(
|
||||
"{}",
|
||||
return_money_info
|
||||
)
|
||||
);
|
||||
BuildDiscordField(&f, "Return Money", return_money_info);
|
||||
}
|
||||
|
||||
std::vector<DiscordEmbed> embeds = {};
|
||||
|
||||
@@ -654,58 +654,59 @@ const int32_t RETENTION_DAYS_DEFAULT = 7;
|
||||
|
||||
void PlayerEventLogs::SetSettingsDefaults()
|
||||
{
|
||||
m_settings[PlayerEvent::GM_COMMAND].event_enabled = 1;
|
||||
m_settings[PlayerEvent::ZONING].event_enabled = 1;
|
||||
m_settings[PlayerEvent::AA_GAIN].event_enabled = 1;
|
||||
m_settings[PlayerEvent::AA_PURCHASE].event_enabled = 1;
|
||||
m_settings[PlayerEvent::FORAGE_SUCCESS].event_enabled = 0;
|
||||
m_settings[PlayerEvent::FORAGE_FAILURE].event_enabled = 0;
|
||||
m_settings[PlayerEvent::FISH_SUCCESS].event_enabled = 0;
|
||||
m_settings[PlayerEvent::FISH_FAILURE].event_enabled = 0;
|
||||
m_settings[PlayerEvent::ITEM_DESTROY].event_enabled = 1;
|
||||
m_settings[PlayerEvent::WENT_ONLINE].event_enabled = 0;
|
||||
m_settings[PlayerEvent::WENT_OFFLINE].event_enabled = 0;
|
||||
m_settings[PlayerEvent::LEVEL_GAIN].event_enabled = 1;
|
||||
m_settings[PlayerEvent::LEVEL_LOSS].event_enabled = 1;
|
||||
m_settings[PlayerEvent::LOOT_ITEM].event_enabled = 1;
|
||||
m_settings[PlayerEvent::MERCHANT_PURCHASE].event_enabled = 1;
|
||||
m_settings[PlayerEvent::MERCHANT_SELL].event_enabled = 1;
|
||||
m_settings[PlayerEvent::GROUP_JOIN].event_enabled = 0;
|
||||
m_settings[PlayerEvent::GROUP_LEAVE].event_enabled = 0;
|
||||
m_settings[PlayerEvent::RAID_JOIN].event_enabled = 0;
|
||||
m_settings[PlayerEvent::RAID_LEAVE].event_enabled = 0;
|
||||
m_settings[PlayerEvent::GROUNDSPAWN_PICKUP].event_enabled = 1;
|
||||
m_settings[PlayerEvent::NPC_HANDIN].event_enabled = 1;
|
||||
m_settings[PlayerEvent::SKILL_UP].event_enabled = 0;
|
||||
m_settings[PlayerEvent::TASK_ACCEPT].event_enabled = 1;
|
||||
m_settings[PlayerEvent::TASK_UPDATE].event_enabled = 1;
|
||||
m_settings[PlayerEvent::TASK_COMPLETE].event_enabled = 1;
|
||||
m_settings[PlayerEvent::TRADE].event_enabled = 1;
|
||||
m_settings[PlayerEvent::GIVE_ITEM].event_enabled = 1;
|
||||
m_settings[PlayerEvent::SAY].event_enabled = 0;
|
||||
m_settings[PlayerEvent::REZ_ACCEPTED].event_enabled = 1;
|
||||
m_settings[PlayerEvent::DEATH].event_enabled = 1;
|
||||
m_settings[PlayerEvent::COMBINE_FAILURE].event_enabled = 1;
|
||||
m_settings[PlayerEvent::COMBINE_SUCCESS].event_enabled = 1;
|
||||
m_settings[PlayerEvent::DROPPED_ITEM].event_enabled = 1;
|
||||
m_settings[PlayerEvent::SPLIT_MONEY].event_enabled = 1;
|
||||
m_settings[PlayerEvent::DZ_JOIN].event_enabled = 1;
|
||||
m_settings[PlayerEvent::DZ_LEAVE].event_enabled = 1;
|
||||
m_settings[PlayerEvent::TRADER_PURCHASE].event_enabled = 1;
|
||||
m_settings[PlayerEvent::TRADER_SELL].event_enabled = 1;
|
||||
m_settings[PlayerEvent::BANDOLIER_CREATE].event_enabled = 0;
|
||||
m_settings[PlayerEvent::BANDOLIER_SWAP].event_enabled = 0;
|
||||
m_settings[PlayerEvent::DISCOVER_ITEM].event_enabled = 1;
|
||||
m_settings[PlayerEvent::POSSIBLE_HACK].event_enabled = 1;
|
||||
m_settings[PlayerEvent::KILLED_NPC].event_enabled = 0;
|
||||
m_settings[PlayerEvent::KILLED_NAMED_NPC].event_enabled = 1;
|
||||
m_settings[PlayerEvent::KILLED_RAID_NPC].event_enabled = 1;
|
||||
m_settings[PlayerEvent::ITEM_CREATION].event_enabled = 1;
|
||||
m_settings[PlayerEvent::GM_COMMAND].event_enabled = 1;
|
||||
m_settings[PlayerEvent::ZONING].event_enabled = 1;
|
||||
m_settings[PlayerEvent::AA_GAIN].event_enabled = 1;
|
||||
m_settings[PlayerEvent::AA_PURCHASE].event_enabled = 1;
|
||||
m_settings[PlayerEvent::FORAGE_SUCCESS].event_enabled = 0;
|
||||
m_settings[PlayerEvent::FORAGE_FAILURE].event_enabled = 0;
|
||||
m_settings[PlayerEvent::FISH_SUCCESS].event_enabled = 0;
|
||||
m_settings[PlayerEvent::FISH_FAILURE].event_enabled = 0;
|
||||
m_settings[PlayerEvent::ITEM_DESTROY].event_enabled = 1;
|
||||
m_settings[PlayerEvent::WENT_ONLINE].event_enabled = 0;
|
||||
m_settings[PlayerEvent::WENT_OFFLINE].event_enabled = 0;
|
||||
m_settings[PlayerEvent::LEVEL_GAIN].event_enabled = 1;
|
||||
m_settings[PlayerEvent::LEVEL_LOSS].event_enabled = 1;
|
||||
m_settings[PlayerEvent::LOOT_ITEM].event_enabled = 1;
|
||||
m_settings[PlayerEvent::MERCHANT_PURCHASE].event_enabled = 1;
|
||||
m_settings[PlayerEvent::MERCHANT_SELL].event_enabled = 1;
|
||||
m_settings[PlayerEvent::GROUP_JOIN].event_enabled = 0;
|
||||
m_settings[PlayerEvent::GROUP_LEAVE].event_enabled = 0;
|
||||
m_settings[PlayerEvent::RAID_JOIN].event_enabled = 0;
|
||||
m_settings[PlayerEvent::RAID_LEAVE].event_enabled = 0;
|
||||
m_settings[PlayerEvent::GROUNDSPAWN_PICKUP].event_enabled = 1;
|
||||
m_settings[PlayerEvent::NPC_HANDIN].event_enabled = 1;
|
||||
m_settings[PlayerEvent::SKILL_UP].event_enabled = 0;
|
||||
m_settings[PlayerEvent::TASK_ACCEPT].event_enabled = 1;
|
||||
m_settings[PlayerEvent::TASK_UPDATE].event_enabled = 1;
|
||||
m_settings[PlayerEvent::TASK_COMPLETE].event_enabled = 1;
|
||||
m_settings[PlayerEvent::TRADE].event_enabled = 1;
|
||||
m_settings[PlayerEvent::GIVE_ITEM].event_enabled = 1;
|
||||
m_settings[PlayerEvent::SAY].event_enabled = 0;
|
||||
m_settings[PlayerEvent::REZ_ACCEPTED].event_enabled = 1;
|
||||
m_settings[PlayerEvent::DEATH].event_enabled = 1;
|
||||
m_settings[PlayerEvent::COMBINE_FAILURE].event_enabled = 1;
|
||||
m_settings[PlayerEvent::COMBINE_SUCCESS].event_enabled = 1;
|
||||
m_settings[PlayerEvent::DROPPED_ITEM].event_enabled = 1;
|
||||
m_settings[PlayerEvent::SPLIT_MONEY].event_enabled = 1;
|
||||
m_settings[PlayerEvent::DZ_JOIN].event_enabled = 1;
|
||||
m_settings[PlayerEvent::DZ_LEAVE].event_enabled = 1;
|
||||
m_settings[PlayerEvent::TRADER_PURCHASE].event_enabled = 1;
|
||||
m_settings[PlayerEvent::TRADER_SELL].event_enabled = 1;
|
||||
m_settings[PlayerEvent::BANDOLIER_CREATE].event_enabled = 0;
|
||||
m_settings[PlayerEvent::BANDOLIER_SWAP].event_enabled = 0;
|
||||
m_settings[PlayerEvent::DISCOVER_ITEM].event_enabled = 1;
|
||||
m_settings[PlayerEvent::POSSIBLE_HACK].event_enabled = 1;
|
||||
m_settings[PlayerEvent::KILLED_NPC].event_enabled = 0;
|
||||
m_settings[PlayerEvent::KILLED_NAMED_NPC].event_enabled = 1;
|
||||
m_settings[PlayerEvent::KILLED_RAID_NPC].event_enabled = 1;
|
||||
m_settings[PlayerEvent::ITEM_CREATION].event_enabled = 1;
|
||||
m_settings[PlayerEvent::GUILD_TRIBUTE_DONATE_ITEM].event_enabled = 1;
|
||||
m_settings[PlayerEvent::GUILD_TRIBUTE_DONATE_PLAT].event_enabled = 1;
|
||||
m_settings[PlayerEvent::PARCEL_SEND].event_enabled = 1;
|
||||
m_settings[PlayerEvent::PARCEL_RETRIEVE].event_enabled = 1;
|
||||
m_settings[PlayerEvent::PARCEL_DELETE].event_enabled = 1;
|
||||
m_settings[PlayerEvent::BARTER_TRANSACTION].event_enabled = 1;
|
||||
|
||||
for (int i = PlayerEvent::GM_COMMAND; i != PlayerEvent::MAX; i++) {
|
||||
m_settings[i].retention_days = RETENTION_DAYS_DEFAULT;
|
||||
|
||||
@@ -60,7 +60,8 @@ namespace PlayerEvent {
|
||||
GUILD_TRIBUTE_DONATE_PLAT,
|
||||
PARCEL_SEND,
|
||||
PARCEL_RETRIEVE,
|
||||
PARCEL_DELETE,
|
||||
PARCEL_DELETE,
|
||||
BARTER_TRANSACTION,
|
||||
MAX // dont remove
|
||||
};
|
||||
|
||||
@@ -122,7 +123,8 @@ namespace PlayerEvent {
|
||||
"Guild Tribute Donate Platinum",
|
||||
"Parcel Item Sent",
|
||||
"Parcel Item Retrieved",
|
||||
"Parcel Prune Routine"
|
||||
"Parcel Prune Routine",
|
||||
"Barter Transaction"
|
||||
};
|
||||
|
||||
// Generic struct used by all events
|
||||
@@ -858,10 +860,12 @@ namespace PlayerEvent {
|
||||
|
||||
class HandinEntry {
|
||||
public:
|
||||
uint32 item_id;
|
||||
std::string item_name;
|
||||
uint16 charges;
|
||||
bool attuned;
|
||||
uint32 item_id;
|
||||
std::string item_name;
|
||||
std::vector<uint32> augment_ids;
|
||||
std::vector<std::string> augment_names;
|
||||
uint16 charges;
|
||||
bool attuned;
|
||||
|
||||
// cereal
|
||||
template<class Archive>
|
||||
@@ -870,6 +874,8 @@ namespace PlayerEvent {
|
||||
ar(
|
||||
CEREAL_NVP(item_id),
|
||||
CEREAL_NVP(item_name),
|
||||
CEREAL_NVP(augment_ids),
|
||||
CEREAL_NVP(augment_names),
|
||||
CEREAL_NVP(charges),
|
||||
CEREAL_NVP(attuned)
|
||||
);
|
||||
@@ -903,6 +909,7 @@ namespace PlayerEvent {
|
||||
HandinMoney handin_money;
|
||||
std::vector<HandinEntry> return_items;
|
||||
HandinMoney return_money;
|
||||
bool is_quest_handin;
|
||||
|
||||
// cereal
|
||||
template<class Archive>
|
||||
@@ -914,7 +921,8 @@ namespace PlayerEvent {
|
||||
CEREAL_NVP(handin_items),
|
||||
CEREAL_NVP(handin_money),
|
||||
CEREAL_NVP(return_items),
|
||||
CEREAL_NVP(return_money)
|
||||
CEREAL_NVP(return_money),
|
||||
CEREAL_NVP(is_quest_handin)
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -1081,6 +1089,32 @@ namespace PlayerEvent {
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
struct BarterTransaction {
|
||||
std::string status;
|
||||
uint32 item_id;
|
||||
uint32 item_quantity;
|
||||
std::string item_name;
|
||||
std::vector<BuyerLineTradeItems_Struct> trade_items;
|
||||
std::string buyer_name;
|
||||
std::string seller_name;
|
||||
uint64 total_cost;
|
||||
// cereal
|
||||
template<class Archive>
|
||||
void serialize(Archive &ar)
|
||||
{
|
||||
ar(
|
||||
CEREAL_NVP(status),
|
||||
CEREAL_NVP(item_id),
|
||||
CEREAL_NVP(item_quantity),
|
||||
CEREAL_NVP(item_name),
|
||||
CEREAL_NVP(trade_items),
|
||||
CEREAL_NVP(buyer_name),
|
||||
CEREAL_NVP(seller_name),
|
||||
CEREAL_NVP(total_cost)
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif //EQEMU_PLAYER_EVENTS_H
|
||||
|
||||
@@ -1743,3 +1743,68 @@ std::vector<uint32> EQ::InventoryProfile::GetAugmentIDsBySlotID(int16 slot_id)
|
||||
|
||||
return augments;
|
||||
}
|
||||
|
||||
std::vector<int16> EQ::InventoryProfile::FindAllFreeSlotsThatFitItem(const EQ::ItemData *item_data)
|
||||
{
|
||||
std::vector<int16> 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++) {
|
||||
if ((((uint64) 1 << i) & GetLookup()->PossessionsBitmask) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
EQ::ItemInstance *inv_item = GetItem(i);
|
||||
|
||||
if (!inv_item) {
|
||||
// Found available slot in personal inventory
|
||||
return 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
|
||||
return base_slot_id + bag_slot;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -176,6 +176,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<int16> FindAllFreeSlotsThatFitItem(const EQ::ItemData *inst);
|
||||
int16 FindFirstFreeSlotThatFitsItem(const EQ::ItemData *inst);
|
||||
|
||||
// Calculate slot_id for an item within a bag
|
||||
static int16 CalcSlotId(int16 slot_id); // Calc parent bag's slot_id
|
||||
|
||||
+55
-21
@@ -81,45 +81,79 @@ bool IpUtil::IsIpInPrivateRfc1918(const std::string &ip)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets local address - pings google to inspect what interface was used locally
|
||||
* @return
|
||||
*/
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#pragma comment(lib, "Ws2_32.lib")
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#endif
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
|
||||
std::string IpUtil::GetLocalIPAddress()
|
||||
{
|
||||
char my_ip_address[16];
|
||||
unsigned int my_port;
|
||||
#ifdef _WIN32
|
||||
WSADATA wsaData;
|
||||
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
|
||||
return "";
|
||||
}
|
||||
#endif
|
||||
|
||||
char my_ip_address[INET_ADDRSTRLEN];
|
||||
struct sockaddr_in server_address{};
|
||||
struct sockaddr_in my_address{};
|
||||
int sockfd;
|
||||
int sockfd;
|
||||
|
||||
// Connect to server
|
||||
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
|
||||
// Create a UDP socket
|
||||
#ifdef _WIN32
|
||||
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (sockfd == INVALID_SOCKET) {
|
||||
WSACleanup();
|
||||
return "";
|
||||
}
|
||||
#else
|
||||
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (sockfd < 0) {
|
||||
return "";
|
||||
}
|
||||
#endif
|
||||
|
||||
// Set server_addr
|
||||
// Set server_addr (dummy address)
|
||||
memset(&server_address, 0, sizeof(server_address));
|
||||
server_address.sin_family = AF_INET;
|
||||
server_address.sin_addr.s_addr = inet_addr("172.217.160.99");
|
||||
server_address.sin_port = htons(80);
|
||||
server_address.sin_family = AF_INET;
|
||||
server_address.sin_addr.s_addr = inet_addr("8.8.8.8"); // Google DNS
|
||||
server_address.sin_port = htons(53); // DNS port
|
||||
|
||||
// Connect to server
|
||||
if (connect(sockfd, (struct sockaddr *) &server_address, sizeof(server_address)) < 0) {
|
||||
close(sockfd);
|
||||
return "";
|
||||
}
|
||||
// Perform a dummy connection to the server (UDP)
|
||||
connect(sockfd, (struct sockaddr *) &server_address, sizeof(server_address));
|
||||
|
||||
// Get my ip address and port
|
||||
// Get my IP address
|
||||
memset(&my_address, 0, sizeof(my_address));
|
||||
socklen_t len = sizeof(my_address);
|
||||
getsockname(sockfd, (struct sockaddr *) &my_address, &len);
|
||||
inet_ntop(AF_INET, &my_address.sin_addr, my_ip_address, sizeof(my_ip_address));
|
||||
my_port = ntohs(my_address.sin_port);
|
||||
|
||||
return fmt::format("{}", my_ip_address);
|
||||
#ifdef _WIN32
|
||||
closesocket(sockfd);
|
||||
WSACleanup();
|
||||
#else
|
||||
close(sockfd);
|
||||
#endif
|
||||
|
||||
LogInfo("Local IP Address [{}]", my_ip_address);
|
||||
|
||||
return std::string(my_ip_address);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets public address
|
||||
* Uses various websites as options to return raw public IP back to the client
|
||||
|
||||
+39
-36
@@ -32,10 +32,11 @@
|
||||
|
||||
//#include <iostream>
|
||||
|
||||
int32 NextItemInstSerialNumber = 1;
|
||||
|
||||
static inline int32 GetNextItemInstSerialNumber() {
|
||||
int32 next_item_serial_number = 1;
|
||||
std::unordered_set<uint64> guids{};
|
||||
|
||||
static inline int32 GetNextItemInstSerialNumber()
|
||||
{
|
||||
// The Bazaar relies on each item a client has up for Trade having a unique
|
||||
// identifier. This 'SerialNumber' is sent in Serialized item packets and
|
||||
// is used in Bazaar packets to identify the item a player is buying or inspecting.
|
||||
@@ -46,12 +47,18 @@ static inline int32 GetNextItemInstSerialNumber() {
|
||||
// NextItemInstSerialNumber is the next one to hand out.
|
||||
//
|
||||
// It is very unlikely to reach 2,147,483,647. Maybe we should call abort(), rather than wrapping back to 1.
|
||||
if(NextItemInstSerialNumber >= INT_MAX)
|
||||
NextItemInstSerialNumber = 1;
|
||||
else
|
||||
NextItemInstSerialNumber++;
|
||||
if (next_item_serial_number >= INT32_MAX) {
|
||||
next_item_serial_number = 1;
|
||||
}
|
||||
else {
|
||||
next_item_serial_number++;
|
||||
}
|
||||
|
||||
return NextItemInstSerialNumber;
|
||||
while (guids.contains(next_item_serial_number)) {
|
||||
next_item_serial_number++;
|
||||
}
|
||||
|
||||
return next_item_serial_number;
|
||||
}
|
||||
|
||||
//
|
||||
@@ -303,47 +310,34 @@ int8 EQ::ItemInstance::AvailableAugmentSlot(int32 augment_type) const
|
||||
return INVALID_INDEX;
|
||||
}
|
||||
|
||||
auto i = invaug::SOCKET_BEGIN;
|
||||
for (; i <= invaug::SOCKET_END; ++i) {
|
||||
if (GetItem(i)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
augment_type == -1 ||
|
||||
(
|
||||
m_item->AugSlotType[i] &&
|
||||
((1 << (m_item->AugSlotType[i] - 1)) & augment_type)
|
||||
)
|
||||
) {
|
||||
break;
|
||||
for (int16 slot_id = invaug::SOCKET_BEGIN; slot_id <= invaug::SOCKET_END; ++slot_id) {
|
||||
if (IsAugmentSlotAvailable(augment_type, slot_id)) {
|
||||
return slot_id;
|
||||
}
|
||||
}
|
||||
|
||||
return (i <= invaug::SOCKET_END) ? i : INVALID_INDEX;
|
||||
return INVALID_INDEX;
|
||||
}
|
||||
|
||||
bool EQ::ItemInstance::IsAugmentSlotAvailable(int32 augment_type, uint8 slot) const
|
||||
{
|
||||
if (!m_item || !m_item->IsClassCommon()) {
|
||||
if (!m_item || !m_item->IsClassCommon() || GetItem(slot)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
return (
|
||||
(
|
||||
!GetItem(slot) &&
|
||||
m_item->AugSlotVisible[slot]
|
||||
augment_type == -1 ||
|
||||
(
|
||||
m_item->AugSlotType[slot] &&
|
||||
((1 << (m_item->AugSlotType[slot] - 1)) & augment_type)
|
||||
)
|
||||
) &&
|
||||
augment_type == -1 ||
|
||||
(
|
||||
m_item->AugSlotType[slot] &&
|
||||
((1 << (m_item->AugSlotType[slot] - 1)) & augment_type)
|
||||
RuleB(Items, AugmentItemAllowInvisibleAugments) ||
|
||||
m_item->AugSlotVisible[slot]
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
);
|
||||
}
|
||||
|
||||
// Retrieve item inside container
|
||||
@@ -1292,7 +1286,7 @@ int EQ::ItemInstance::GetItemBaneDamageRace(bool augments) const
|
||||
return race;
|
||||
}
|
||||
|
||||
int EQ::ItemInstance::GetItemBaneDamageBody(bodyType against, bool augments) const
|
||||
int EQ::ItemInstance::GetItemBaneDamageBody(uint8 against, bool augments) const
|
||||
{
|
||||
int64 damage = 0;
|
||||
const auto item = GetItem();
|
||||
@@ -1948,6 +1942,15 @@ int EQ::ItemInstance::GetItemSkillsStat(EQ::skills::SkillType skill, bool augmen
|
||||
return stat;
|
||||
}
|
||||
|
||||
void EQ::ItemInstance::AddGUIDToMap(uint64 existing_serial_number)
|
||||
{
|
||||
guids.emplace(existing_serial_number);
|
||||
}
|
||||
|
||||
void EQ::ItemInstance::ClearGUIDMap()
|
||||
{
|
||||
guids.clear();
|
||||
}
|
||||
//
|
||||
// class EvolveInfo
|
||||
//
|
||||
|
||||
@@ -265,7 +265,7 @@ namespace EQ
|
||||
// these two are just quick checks
|
||||
int GetItemBaneDamageBody(bool augments = false) const;
|
||||
int GetItemBaneDamageRace(bool augments = false) const;
|
||||
int GetItemBaneDamageBody(bodyType against, bool augments = false) const;
|
||||
int GetItemBaneDamageBody(uint8 against, bool augments = false) const;
|
||||
int GetItemBaneDamageRace(uint16 against, bool augments = false) const;
|
||||
int GetItemMagical(bool augments = false) const;
|
||||
int GetItemHP(bool augments = false) const;
|
||||
@@ -309,6 +309,8 @@ namespace EQ
|
||||
int GetItemSkillsStat(EQ::skills::SkillType skill, bool augments = false) const;
|
||||
uint32 GetItemGuildFavor() const;
|
||||
std::vector<uint32> GetAugmentIDs() const;
|
||||
static void AddGUIDToMap(uint64 existing_serial_number);
|
||||
static void ClearGUIDMap();
|
||||
|
||||
protected:
|
||||
//////////////////////////
|
||||
|
||||
@@ -0,0 +1,586 @@
|
||||
#include "mysql_stmt.h"
|
||||
#include "eqemu_logsys.h"
|
||||
#include "mutex.h"
|
||||
#include "timer.h"
|
||||
#include <charconv>
|
||||
|
||||
namespace mysql
|
||||
{
|
||||
|
||||
void PreparedStmt::StmtDeleter::operator()(MYSQL_STMT* stmt) noexcept
|
||||
{
|
||||
// The connection must be locked when closing the stmt to avoid mysql errors
|
||||
// in case another thread tries to use it during the close. If the mutex is
|
||||
// changed to one that throws then exceptions need to be caught here.
|
||||
LockMutex lock(mutex);
|
||||
mysql_stmt_close(stmt);
|
||||
}
|
||||
|
||||
PreparedStmt::PreparedStmt(MYSQL& mysql, std::string query, Mutex* mutex, StmtOptions opts)
|
||||
: m_stmt(mysql_stmt_init(&mysql), { mutex }), m_query(std::move(query)), m_mutex(mutex), m_options(opts)
|
||||
{
|
||||
LockMutex lock(m_mutex);
|
||||
if (mysql_stmt_prepare(m_stmt.get(), m_query.c_str(), static_cast<unsigned long>(m_query.size())) != 0)
|
||||
{
|
||||
ThrowError(fmt::format("Prepare error: {}", GetStmtError()));
|
||||
}
|
||||
|
||||
m_params.resize(mysql_stmt_param_count(m_stmt.get()));
|
||||
m_inputs.resize(m_params.size());
|
||||
}
|
||||
|
||||
void PreparedStmt::ThrowError(const std::string& error)
|
||||
{
|
||||
LogMySQLError("{}", error);
|
||||
throw std::runtime_error(error);
|
||||
}
|
||||
|
||||
std::string PreparedStmt::GetStmtError()
|
||||
{
|
||||
auto err = mysql_stmt_errno(m_stmt.get());
|
||||
auto str = mysql_stmt_error(m_stmt.get());
|
||||
return fmt::format("({}) [{}] for query [{}]", err, str, m_query);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void PreparedStmt::BindInput(size_t index, T value)
|
||||
{
|
||||
if (index >= m_inputs.size())
|
||||
{
|
||||
ThrowError(fmt::format("Cannot bind input, index {} out of range", index));
|
||||
}
|
||||
|
||||
impl::Bind& arg = m_inputs[index];
|
||||
arg.is_null = std::is_same_v<T, std::nullptr_t>;
|
||||
|
||||
MYSQL_BIND& bind = m_params[index];
|
||||
bind.is_unsigned = std::is_unsigned_v<T>;
|
||||
bind.is_null = &arg.is_null;
|
||||
bind.length = &arg.length;
|
||||
|
||||
auto old_type = bind.buffer_type;
|
||||
|
||||
if constexpr (std::is_arithmetic_v<T>)
|
||||
{
|
||||
if (arg.buffer.size() < sizeof(T))
|
||||
{
|
||||
arg.buffer.resize(std::max(sizeof(T), sizeof(int64_t)));
|
||||
bind.buffer = arg.buffer.data();
|
||||
m_need_bind = true;
|
||||
}
|
||||
memcpy(arg.buffer.data(), &value, sizeof(T));
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<T, int8_t> || std::is_same_v<T, uint8_t> || std::is_same_v<T, bool>)
|
||||
{
|
||||
bind.buffer_type = MYSQL_TYPE_TINY;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, int16_t> || std::is_same_v<T, uint16_t>)
|
||||
{
|
||||
bind.buffer_type = MYSQL_TYPE_SHORT;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, int32_t> || std::is_same_v<T, uint32_t>)
|
||||
{
|
||||
bind.buffer_type = MYSQL_TYPE_LONG;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, int64_t> || std::is_same_v<T, uint64_t>)
|
||||
{
|
||||
bind.buffer_type = MYSQL_TYPE_LONGLONG;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, float>)
|
||||
{
|
||||
bind.buffer_type = MYSQL_TYPE_FLOAT;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, double>)
|
||||
{
|
||||
bind.buffer_type = MYSQL_TYPE_DOUBLE;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, std::string_view>)
|
||||
{
|
||||
bind.buffer_type = MYSQL_TYPE_STRING;
|
||||
if (arg.buffer.empty() || arg.buffer.size() < value.size())
|
||||
{
|
||||
arg.buffer.resize(static_cast<size_t>((value.size() + 1) * 1.5));
|
||||
bind.buffer = arg.buffer.data();
|
||||
bind.buffer_length = static_cast<unsigned long>(arg.buffer.size());
|
||||
m_need_bind = true;
|
||||
}
|
||||
std::copy(value.begin(), value.end(), arg.buffer.begin());
|
||||
arg.length = static_cast<unsigned long>(value.size());
|
||||
}
|
||||
else if constexpr (!std::is_same_v<T, std::nullptr_t>)
|
||||
{
|
||||
static_assert(false_v<T>, "Cannot bind unsupported type");
|
||||
}
|
||||
|
||||
if (old_type != bind.buffer_type)
|
||||
{
|
||||
m_need_bind = true;
|
||||
}
|
||||
}
|
||||
|
||||
void PreparedStmt::BindInput(size_t index, const char* str)
|
||||
{
|
||||
BindInput(index, std::string_view(str));
|
||||
}
|
||||
|
||||
void PreparedStmt::BindInput(size_t index, const std::string& str)
|
||||
{
|
||||
BindInput(index, std::string_view(str));
|
||||
}
|
||||
|
||||
StmtResult PreparedStmt::Execute()
|
||||
{
|
||||
CheckArgs(0);
|
||||
return DoExecute();
|
||||
}
|
||||
|
||||
StmtResult PreparedStmt::Execute(const std::vector<param_t>& args)
|
||||
{
|
||||
CheckArgs(args.size());
|
||||
for (size_t i = 0; i < args.size(); ++i)
|
||||
{
|
||||
std::visit([&](const auto& arg) { BindInput(i, arg); }, args[i]);
|
||||
}
|
||||
return DoExecute();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
StmtResult PreparedStmt::Execute(const std::vector<T>& args)
|
||||
{
|
||||
CheckArgs(args.size());
|
||||
for (size_t i = 0; i < args.size(); ++i)
|
||||
{
|
||||
BindInput(i, args[i]);
|
||||
}
|
||||
return DoExecute();
|
||||
}
|
||||
|
||||
void PreparedStmt::CheckArgs(size_t argc)
|
||||
{
|
||||
if (argc != m_params.size())
|
||||
{
|
||||
ThrowError(fmt::format("Bad arg count (got {}, expected {}) for [{}]", argc, m_params.size(), m_query));
|
||||
}
|
||||
}
|
||||
|
||||
StmtResult PreparedStmt::DoExecute()
|
||||
{
|
||||
BenchTimer timer;
|
||||
LockMutex lock(m_mutex);
|
||||
|
||||
if (m_need_bind && mysql_stmt_bind_param(m_stmt.get(), m_params.data()) != 0)
|
||||
{
|
||||
ThrowError(fmt::format("Bind param error: {}", GetStmtError()));
|
||||
}
|
||||
|
||||
m_need_bind = false;
|
||||
|
||||
if (mysql_stmt_execute(m_stmt.get()) != 0)
|
||||
{
|
||||
ThrowError(fmt::format("Execute error: {}", GetStmtError()));
|
||||
}
|
||||
|
||||
my_bool attr = m_options.use_max_length;
|
||||
mysql_stmt_attr_set(m_stmt.get(), STMT_ATTR_UPDATE_MAX_LENGTH, &attr);
|
||||
|
||||
if (m_options.buffer_results && mysql_stmt_store_result(m_stmt.get()) != 0)
|
||||
{
|
||||
ThrowError(fmt::format("Store result error: {}", GetStmtError()));
|
||||
}
|
||||
|
||||
// Result buffers are bound on first execute and re-used if needed
|
||||
if (m_results.empty())
|
||||
{
|
||||
BindResults();
|
||||
}
|
||||
|
||||
StmtResult res(m_stmt.get(), m_results.size());
|
||||
|
||||
if (m_results.empty())
|
||||
{
|
||||
LogMySQLQuery("{} -- ({} row(s) affected) ({:.6f}s)", m_query, res.RowsAffected(), timer.elapsed());
|
||||
}
|
||||
else
|
||||
{
|
||||
LogMySQLQuery("{} -- ({} row(s) returned) ({:.6f}s)", m_query, res.RowCount(), timer.elapsed());
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void PreparedStmt::BindResults()
|
||||
{
|
||||
MYSQL_RES* res = mysql_stmt_result_metadata(m_stmt.get());
|
||||
if (!res)
|
||||
{
|
||||
return; // did not produce a result set
|
||||
}
|
||||
|
||||
MYSQL_FIELD* fields = mysql_fetch_fields(res);
|
||||
m_columns.resize(mysql_num_fields(res));
|
||||
m_results.resize(m_columns.size());
|
||||
|
||||
for (int i = 0; i < static_cast<int>(m_columns.size()); ++i)
|
||||
{
|
||||
impl::BindColumn& col = m_columns[i].m_col;
|
||||
MYSQL_BIND& bind = m_results[i];
|
||||
|
||||
col.index = i;
|
||||
col.name = fields[i].name;
|
||||
col.buffer_type = fields[i].type;
|
||||
col.is_unsigned = (fields[i].flags & UNSIGNED_FLAG) != 0;
|
||||
col.buffer.resize(GetResultBufferSize(fields[i]));
|
||||
|
||||
bind.buffer_type = col.buffer_type;
|
||||
bind.buffer = col.buffer.data();
|
||||
bind.buffer_length = static_cast<unsigned long>(col.buffer.size());
|
||||
bind.is_unsigned = col.is_unsigned;
|
||||
bind.is_null = &col.is_null;
|
||||
bind.length = &col.length;
|
||||
bind.error = &col.error;
|
||||
}
|
||||
|
||||
mysql_free_result(res);
|
||||
|
||||
if (!m_results.empty() && mysql_stmt_bind_result(m_stmt.get(), m_results.data()) != 0)
|
||||
{
|
||||
ThrowError(fmt::format("Bind result error: {}", GetStmtError()));
|
||||
}
|
||||
}
|
||||
|
||||
int PreparedStmt::GetResultBufferSize(const MYSQL_FIELD& field) const
|
||||
{
|
||||
switch (field.type)
|
||||
{
|
||||
case MYSQL_TYPE_TINY:
|
||||
return sizeof(int8_t);
|
||||
case MYSQL_TYPE_SHORT:
|
||||
return sizeof(int16_t);
|
||||
case MYSQL_TYPE_INT24:
|
||||
case MYSQL_TYPE_LONG:
|
||||
return sizeof(int32_t);
|
||||
case MYSQL_TYPE_LONGLONG:
|
||||
return sizeof(int64_t);
|
||||
case MYSQL_TYPE_FLOAT:
|
||||
return sizeof(float);
|
||||
case MYSQL_TYPE_DOUBLE:
|
||||
return sizeof(double);
|
||||
case MYSQL_TYPE_TIME:
|
||||
case MYSQL_TYPE_DATE:
|
||||
case MYSQL_TYPE_DATETIME:
|
||||
case MYSQL_TYPE_TIMESTAMP:
|
||||
return sizeof(MYSQL_TIME);
|
||||
default: // if max_length is unavailable for strings buffers are resized on fetch
|
||||
return field.max_length + 1; // ensure valid buffer created
|
||||
}
|
||||
}
|
||||
|
||||
StmtRow PreparedStmt::Fetch()
|
||||
{
|
||||
StmtRow row;
|
||||
if (!m_columns.empty())
|
||||
{
|
||||
int rc = mysql_stmt_fetch(m_stmt.get());
|
||||
if (rc == 1)
|
||||
{
|
||||
ThrowError(fmt::format("Fetch error: {}", GetStmtError()));
|
||||
}
|
||||
|
||||
if (rc != MYSQL_NO_DATA)
|
||||
{
|
||||
if (rc == MYSQL_DATA_TRUNCATED)
|
||||
{
|
||||
FetchTruncated();
|
||||
}
|
||||
row = StmtRow(m_columns);
|
||||
}
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
void PreparedStmt::FetchTruncated()
|
||||
{
|
||||
for (int i = 0; i < static_cast<int>(m_columns.size()); ++i)
|
||||
{
|
||||
impl::BindColumn& col = m_columns[i].m_col;
|
||||
if (col.error)
|
||||
{
|
||||
MYSQL_BIND& bind = m_results[i];
|
||||
col.buffer.resize(static_cast<size_t>(col.length * 1.5));
|
||||
bind.buffer = col.buffer.data();
|
||||
bind.buffer_length = static_cast<unsigned long>(col.buffer.size());
|
||||
|
||||
mysql_stmt_fetch_column(m_stmt.get(), &bind, i, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (mysql_stmt_bind_result(m_stmt.get(), m_results.data()) != 0)
|
||||
{
|
||||
ThrowError(fmt::format("Fetch rebind result error: {}", GetStmtError()));
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
StmtResult::StmtResult(MYSQL_STMT* stmt, size_t columns)
|
||||
{
|
||||
m_num_cols = static_cast<int>(columns);
|
||||
m_num_rows = mysql_stmt_num_rows(stmt); // requires buffered results
|
||||
m_affected = mysql_stmt_affected_rows(stmt);
|
||||
m_insert_id = mysql_stmt_insert_id(stmt);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const StmtColumn* StmtRow::GetColumn(size_t index) const
|
||||
{
|
||||
return index < m_columns.size() ? &m_columns[index] : nullptr;
|
||||
}
|
||||
|
||||
const StmtColumn* StmtRow::GetColumn(std::string_view name) const
|
||||
{
|
||||
auto it = std::ranges::find_if(m_columns,
|
||||
[name](const StmtColumn& col) { return col.Name() == name; });
|
||||
|
||||
return it != m_columns.end() ? &(*it) : nullptr;
|
||||
}
|
||||
|
||||
std::optional<std::string> StmtRow::operator[](size_t index) const
|
||||
{
|
||||
return GetStr(index);
|
||||
}
|
||||
|
||||
std::optional<std::string> StmtRow::operator[](std::string_view name) const
|
||||
{
|
||||
return GetStr(name);
|
||||
}
|
||||
|
||||
std::optional<std::string> StmtRow::GetStr(size_t index) const
|
||||
{
|
||||
const StmtColumn* col = GetColumn(index);
|
||||
return col ? col->GetStr() : std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::string> StmtRow::GetStr(std::string_view name) const
|
||||
{
|
||||
const StmtColumn* col = GetColumn(name);
|
||||
return col ? col->GetStr() : std::nullopt;
|
||||
}
|
||||
|
||||
template <typename T> requires std::is_arithmetic_v<T>
|
||||
std::optional<T> StmtRow::Get(size_t index) const
|
||||
{
|
||||
const StmtColumn* col = GetColumn(index);
|
||||
return col ? col->Get<T>() : std::nullopt;
|
||||
}
|
||||
|
||||
template <typename T> requires std::is_arithmetic_v<T>
|
||||
std::optional<T> StmtRow::Get(std::string_view name) const
|
||||
{
|
||||
const StmtColumn* col = GetColumn(name);
|
||||
return col ? col->Get<T>() : std::nullopt;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static time_t MakeTime(const MYSQL_TIME& mt)
|
||||
{
|
||||
// buffer mt given in mysql session time zone (assumes local)
|
||||
std::tm tm{};
|
||||
tm.tm_year = mt.year - 1900;
|
||||
tm.tm_mon = mt.month - 1;
|
||||
tm.tm_mday = mt.day;
|
||||
tm.tm_hour = mt.hour;
|
||||
tm.tm_min = mt.minute;
|
||||
tm.tm_sec = mt.second;
|
||||
tm.tm_isdst = -1;
|
||||
return std::mktime(&tm);
|
||||
}
|
||||
|
||||
static int MakeSeconds(const MYSQL_TIME& mt)
|
||||
{
|
||||
return (mt.neg ? -1 : 1) * static_cast<int>(mt.hour * 3600 + mt.minute * 60 + mt.second);
|
||||
}
|
||||
|
||||
static uint64_t MakeBits(std::span<const uint8_t> data)
|
||||
{
|
||||
// byte stream for bits is in big endian
|
||||
uint64_t bits = 0;
|
||||
for (size_t i = 0; i < data.size() && i < sizeof(uint64_t); ++i)
|
||||
{
|
||||
bits |= static_cast<uint64_t>(data[data.size() - i - 1] & 0xff) << (i * 8);
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static T FromString(std::string_view sv)
|
||||
{
|
||||
if constexpr (std::is_same_v<T, bool>)
|
||||
{
|
||||
// return false for empty (zero-length) strings
|
||||
return !sv.empty();
|
||||
}
|
||||
else
|
||||
{
|
||||
// non numbers return a zero initialized T (could return nullopt instead)
|
||||
T value = {};
|
||||
std::from_chars(sv.data(), sv.data() + sv.size(), value);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
static std::string FormatTime(enum_field_types type, const MYSQL_TIME& mt)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case MYSQL_TYPE_TIME: // hhh:mm:ss '-838:59:59' to '838:59:59'
|
||||
return fmt::format("{}{:02d}:{:02d}:{:02d}", mt.neg ? "-" : "", mt.hour, mt.minute, mt.second);
|
||||
case MYSQL_TYPE_DATE: // YYYY-MM-DD '1000-01-01' to '9999-12-31'
|
||||
return fmt::format("{}-{:02d}-{:02d}", mt.year, mt.month, mt.day);
|
||||
case MYSQL_TYPE_DATETIME: // YYYY-MM-DD hh:mm:ss '1000-01-01 00:00:00' to '9999-12-31 23:59:59'
|
||||
case MYSQL_TYPE_TIMESTAMP: // YYYY-MM-DD hh:mm:ss '1970-01-01 00:00:01' UTC to '2038-01-19 03:14:07' UTC
|
||||
return fmt::format("{}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}", mt.year, mt.month, mt.day, mt.hour, mt.minute, mt.second);
|
||||
default:
|
||||
return std::string();
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::string_view> StmtColumn::GetStrView() const
|
||||
{
|
||||
if (m_col.is_null)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
switch (m_col.buffer_type)
|
||||
{
|
||||
case MYSQL_TYPE_NEWDECIMAL:
|
||||
case MYSQL_TYPE_TINY_BLOB:
|
||||
case MYSQL_TYPE_MEDIUM_BLOB:
|
||||
case MYSQL_TYPE_LONG_BLOB:
|
||||
case MYSQL_TYPE_BLOB:
|
||||
case MYSQL_TYPE_VAR_STRING:
|
||||
case MYSQL_TYPE_STRING:
|
||||
return std::make_optional<std::string_view>(reinterpret_cast<const char*>(m_col.buffer.data()), m_col.length);
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::string> StmtColumn::GetStr() const
|
||||
{
|
||||
if (m_col.is_null)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
switch (m_col.buffer_type)
|
||||
{
|
||||
case MYSQL_TYPE_TINY:
|
||||
return m_col.is_unsigned ? fmt::format_int(BitCast<uint8_t>()).c_str() : fmt::format_int(BitCast<int8_t>()).c_str();
|
||||
case MYSQL_TYPE_SHORT:
|
||||
return m_col.is_unsigned ? fmt::format_int(BitCast<uint16_t>()).c_str() : fmt::format_int(BitCast<int16_t>()).c_str();
|
||||
case MYSQL_TYPE_INT24:
|
||||
case MYSQL_TYPE_LONG:
|
||||
return m_col.is_unsigned ? fmt::format_int(BitCast<uint32_t>()).c_str() : fmt::format_int(BitCast<int32_t>()).c_str();
|
||||
case MYSQL_TYPE_LONGLONG:
|
||||
return m_col.is_unsigned ? fmt::format_int(BitCast<uint64_t>()).c_str() : fmt::format_int(BitCast<int64_t>()).c_str();
|
||||
case MYSQL_TYPE_FLOAT:
|
||||
return fmt::format("{}", BitCast<float>());
|
||||
case MYSQL_TYPE_DOUBLE:
|
||||
return fmt::format("{}", BitCast<double>());
|
||||
case MYSQL_TYPE_TIME:
|
||||
case MYSQL_TYPE_DATE:
|
||||
case MYSQL_TYPE_DATETIME:
|
||||
case MYSQL_TYPE_TIMESTAMP:
|
||||
return FormatTime(m_col.buffer_type, BitCast<MYSQL_TIME>());
|
||||
case MYSQL_TYPE_BIT:
|
||||
return fmt::format_int(*Get<uint64_t>()).c_str();
|
||||
case MYSQL_TYPE_NEWDECIMAL:
|
||||
case MYSQL_TYPE_TINY_BLOB:
|
||||
case MYSQL_TYPE_MEDIUM_BLOB:
|
||||
case MYSQL_TYPE_LONG_BLOB:
|
||||
case MYSQL_TYPE_BLOB:
|
||||
case MYSQL_TYPE_VAR_STRING:
|
||||
case MYSQL_TYPE_STRING:
|
||||
return std::make_optional<std::string>(reinterpret_cast<const char*>(m_col.buffer.data()), m_col.length);
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> requires std::is_arithmetic_v<T>
|
||||
std::optional<T> StmtColumn::Get() const
|
||||
{
|
||||
if (m_col.is_null)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
switch (m_col.buffer_type)
|
||||
{
|
||||
case MYSQL_TYPE_TINY:
|
||||
return m_col.is_unsigned ? static_cast<T>(BitCast<uint8_t>()) : static_cast<T>(BitCast<int8_t>());
|
||||
case MYSQL_TYPE_SHORT:
|
||||
return m_col.is_unsigned ? static_cast<T>(BitCast<uint16_t>()) : static_cast<T>(BitCast<int16_t>());
|
||||
case MYSQL_TYPE_INT24:
|
||||
case MYSQL_TYPE_LONG:
|
||||
return m_col.is_unsigned ? static_cast<T>(BitCast<uint32_t>()) : static_cast<T>(BitCast<int32_t>());
|
||||
case MYSQL_TYPE_LONGLONG:
|
||||
return m_col.is_unsigned ? static_cast<T>(BitCast<uint64_t>()) : static_cast<T>(BitCast<int64_t>());
|
||||
case MYSQL_TYPE_FLOAT:
|
||||
return static_cast<T>(BitCast<float>());
|
||||
case MYSQL_TYPE_DOUBLE:
|
||||
return static_cast<T>(BitCast<double>());
|
||||
case MYSQL_TYPE_TIME: // return as total seconds
|
||||
return static_cast<T>(MakeSeconds(BitCast<MYSQL_TIME>()));
|
||||
case MYSQL_TYPE_DATE:
|
||||
case MYSQL_TYPE_DATETIME:
|
||||
case MYSQL_TYPE_TIMESTAMP: // return as epoch timestamp
|
||||
return static_cast<T>(MakeTime(BitCast<MYSQL_TIME>()));
|
||||
case MYSQL_TYPE_BIT:
|
||||
return static_cast<T>(MakeBits({ m_col.buffer.data(), m_col.length }));
|
||||
case MYSQL_TYPE_NEWDECIMAL:
|
||||
case MYSQL_TYPE_TINY_BLOB:
|
||||
case MYSQL_TYPE_MEDIUM_BLOB:
|
||||
case MYSQL_TYPE_LONG_BLOB:
|
||||
case MYSQL_TYPE_BLOB:
|
||||
case MYSQL_TYPE_VAR_STRING:
|
||||
case MYSQL_TYPE_STRING:
|
||||
return FromString<T>({ reinterpret_cast<const char*>(m_col.buffer.data()), m_col.length });
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// explicit template instantiations for supported types
|
||||
template void PreparedStmt::BindInput(size_t, std::string_view);
|
||||
template void PreparedStmt::BindInput(size_t, std::nullptr_t);
|
||||
template StmtResult PreparedStmt::Execute(const std::vector<std::string_view>&);
|
||||
template StmtResult PreparedStmt::Execute(const std::vector<std::string>&);
|
||||
template StmtResult PreparedStmt::Execute(const std::vector<const char*>&);
|
||||
|
||||
#define INSTANTIATE(T) \
|
||||
template void PreparedStmt::BindInput(size_t, T); \
|
||||
template StmtResult PreparedStmt::Execute(const std::vector<T>&); \
|
||||
template std::optional<T> StmtRow::Get(size_t) const; \
|
||||
template std::optional<T> StmtRow::Get(std::string_view) const; \
|
||||
template std::optional<T> StmtColumn::Get() const;
|
||||
|
||||
INSTANTIATE(bool);
|
||||
INSTANTIATE(int8_t);
|
||||
INSTANTIATE(uint8_t);
|
||||
INSTANTIATE(int16_t);
|
||||
INSTANTIATE(uint16_t);
|
||||
INSTANTIATE(int32_t);
|
||||
INSTANTIATE(uint32_t);
|
||||
INSTANTIATE(int64_t);
|
||||
INSTANTIATE(uint64_t);
|
||||
INSTANTIATE(float);
|
||||
INSTANTIATE(double);
|
||||
|
||||
} // namespace mysql
|
||||
@@ -0,0 +1,221 @@
|
||||
#pragma once
|
||||
|
||||
#include "mysql.h"
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
class Mutex;
|
||||
|
||||
namespace mysql
|
||||
{
|
||||
|
||||
// support MySQL 8.0.1+ API which removed the my_bool type
|
||||
#if !defined(MARIADB_VERSION_ID) && MYSQL_VERSION_ID >= 80001
|
||||
using my_bool = bool;
|
||||
#endif
|
||||
|
||||
template <typename>
|
||||
inline constexpr bool false_v = false;
|
||||
|
||||
namespace impl
|
||||
{
|
||||
|
||||
struct Bind
|
||||
{
|
||||
std::vector<uint8_t> buffer;
|
||||
unsigned long length = 0;
|
||||
my_bool is_null = false;
|
||||
my_bool error = false;
|
||||
};
|
||||
|
||||
struct BindColumn : Bind
|
||||
{
|
||||
int index = 0;
|
||||
std::string name;
|
||||
bool is_unsigned = false;
|
||||
enum_field_types buffer_type = {};
|
||||
};
|
||||
|
||||
} // namespace impl
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
struct StmtOptions
|
||||
{
|
||||
// Enable buffering (storing) entire result set after executing a statement
|
||||
bool buffer_results = true;
|
||||
|
||||
// Enable MySQL to update max_length of fields in execute result set (requires buffering)
|
||||
bool use_max_length = true;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Holds ownership of bound column value buffer
|
||||
class StmtColumn
|
||||
{
|
||||
public:
|
||||
int Index() const { return m_col.index; }
|
||||
bool IsNull() const { return m_col.is_null; }
|
||||
bool IsUnsigned() const { return m_col.is_unsigned; }
|
||||
enum_field_types Type() const { return m_col.buffer_type; }
|
||||
const std::string& Name() const { return m_col.name; }
|
||||
|
||||
// Get view of column value buffer
|
||||
std::span<const uint8_t> GetBuf() const { return { m_col.buffer.data(), m_col.length }; }
|
||||
|
||||
// Get view of column string value. Returns nullopt if value is NULL or not a string
|
||||
std::optional<std::string_view> GetStrView() const;
|
||||
|
||||
// Get column value as string. Returns nullopt if value is NULL or field type unsupported
|
||||
std::optional<std::string> GetStr() const;
|
||||
|
||||
// Get column value as numeric T. Returns nullopt if value NULL or field type unsupported
|
||||
template <typename T> requires std::is_arithmetic_v<T>
|
||||
std::optional<T> Get() const;
|
||||
|
||||
private:
|
||||
// uses memcpy for type punning buffer data to avoid UB with strict aliasing
|
||||
template <typename T>
|
||||
T BitCast() const
|
||||
{
|
||||
T val;
|
||||
assert(sizeof(T) == m_col.length);
|
||||
memcpy(&val, m_col.buffer.data(), sizeof(T));
|
||||
return val;
|
||||
}
|
||||
|
||||
friend class PreparedStmt; // access to allocate and bind buffers
|
||||
friend class StmtResult; // access to resize truncated buffers
|
||||
impl::BindColumn m_col;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Provides a non-owning view of PreparedStmt column value buffers
|
||||
// Evaluates false if it does not contain a valid row
|
||||
class StmtRow
|
||||
{
|
||||
public:
|
||||
StmtRow() = default;
|
||||
StmtRow(std::span<const StmtColumn> columns) : m_columns(columns) {};
|
||||
|
||||
explicit operator bool() const noexcept { return !m_columns.empty(); }
|
||||
|
||||
int ColumnCount() const { return static_cast<int>(m_columns.size()); }
|
||||
const StmtColumn* GetColumn(size_t index) const;
|
||||
const StmtColumn* GetColumn(std::string_view name) const;
|
||||
|
||||
// Get specified column value as string
|
||||
// Returns nullopt if column invalid, value is NULL, or field type unsupported
|
||||
std::optional<std::string> operator[](size_t index) const;
|
||||
std::optional<std::string> operator[](std::string_view name) const;
|
||||
std::optional<std::string> GetStr(size_t index) const;
|
||||
std::optional<std::string> GetStr(std::string_view name) const;
|
||||
|
||||
// Get specified column value as numeric T
|
||||
// Returns nullopt if column invalid, value is NULL, or field type unsupported
|
||||
template <typename T> requires std::is_arithmetic_v<T>
|
||||
std::optional<T> Get(size_t index) const;
|
||||
|
||||
template <typename T> requires std::is_arithmetic_v<T>
|
||||
std::optional<T> Get(std::string_view name) const;
|
||||
|
||||
auto begin() const { return m_columns.begin(); }
|
||||
auto end() const { return m_columns.end(); }
|
||||
|
||||
private:
|
||||
std::span<const StmtColumn> m_columns;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Result meta data for an executed prepared statement
|
||||
class StmtResult
|
||||
{
|
||||
public:
|
||||
StmtResult() = default;
|
||||
StmtResult(MYSQL_STMT* stmt, size_t columns);
|
||||
|
||||
int ColumnCount() const { return m_num_cols; }
|
||||
uint64_t RowCount() const { return m_num_rows; }
|
||||
uint64_t RowsAffected() const { return m_affected; }
|
||||
uint64_t LastInsertID() const { return m_insert_id; }
|
||||
|
||||
private:
|
||||
int m_num_cols = 0;
|
||||
uint64_t m_num_rows = 0;
|
||||
uint64_t m_affected = 0;
|
||||
uint64_t m_insert_id = 0;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class PreparedStmt
|
||||
{
|
||||
public:
|
||||
// Supported argument types for execute
|
||||
using param_t = std::variant<int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t,
|
||||
int64_t, uint64_t, float, double, bool, std::string_view, std::nullptr_t>;
|
||||
|
||||
PreparedStmt() = delete;
|
||||
PreparedStmt(MYSQL& mysql, std::string query, Mutex* mutex, StmtOptions opts = {});
|
||||
|
||||
const std::string& GetQuery() const { return m_query; }
|
||||
StmtOptions GetOptions() const { return m_options; }
|
||||
void SetOptions(StmtOptions options) { m_options = options; }
|
||||
void FreeResult() { mysql_stmt_free_result(m_stmt.get()); }
|
||||
|
||||
// Execute the prepared statement with specified arguments
|
||||
// Throws exception on error
|
||||
template <typename T>
|
||||
StmtResult Execute(const std::vector<T>& args);
|
||||
StmtResult Execute(const std::vector<param_t>& args);
|
||||
StmtResult Execute();
|
||||
|
||||
// Fetch the next row into column buffers (overwrites previous row values)
|
||||
// Return value evaluates false if no more rows to fetch
|
||||
// Throws exception on error
|
||||
StmtRow Fetch();
|
||||
|
||||
private:
|
||||
void CheckArgs(size_t argc);
|
||||
StmtResult DoExecute();
|
||||
void BindResults();
|
||||
void FetchTruncated();
|
||||
int GetResultBufferSize(const MYSQL_FIELD& field) const;
|
||||
void ThrowError(const std::string& error);
|
||||
std::string GetStmtError();
|
||||
|
||||
// bind an input value to a query parameter by index
|
||||
template <typename T>
|
||||
void BindInput(size_t index, T value);
|
||||
void BindInput(size_t index, const char* str);
|
||||
void BindInput(size_t index, const std::string& str);
|
||||
|
||||
struct StmtDeleter
|
||||
{
|
||||
Mutex* mutex = nullptr;
|
||||
void operator()(MYSQL_STMT* stmt) noexcept;
|
||||
};
|
||||
|
||||
private:
|
||||
std::unique_ptr<MYSQL_STMT, StmtDeleter> m_stmt;
|
||||
std::vector<MYSQL_BIND> m_params; // input binds
|
||||
std::vector<MYSQL_BIND> m_results; // result binds
|
||||
std::vector<impl::Bind> m_inputs; // execute buffers (addresses bound)
|
||||
std::vector<StmtColumn> m_columns; // fetch buffers (addresses bound)
|
||||
std::string m_query;
|
||||
StmtOptions m_options = {};
|
||||
bool m_need_bind = true;
|
||||
Mutex* m_mutex = nullptr; // connection mutex
|
||||
};
|
||||
|
||||
} // namespace mysql
|
||||
+523
-32
@@ -356,41 +356,91 @@ namespace RoF2
|
||||
|
||||
ENCODE(OP_Barter)
|
||||
{
|
||||
EQApplicationPacket *in = *p;
|
||||
EQApplicationPacket *in = *p;
|
||||
*p = nullptr;
|
||||
|
||||
char *Buffer = (char *)in->pBuffer;
|
||||
char *buffer = (char *) in->pBuffer;
|
||||
uint32 sub_action = VARSTRUCT_DECODE_TYPE(uint32, buffer);
|
||||
|
||||
uint32 SubAction = VARSTRUCT_DECODE_TYPE(uint32, Buffer);
|
||||
switch (sub_action) {
|
||||
case Barter_BuyerAppearance: {
|
||||
auto emu = (BuyerInspectRequest_Struct *) in->pBuffer;
|
||||
|
||||
if (SubAction != Barter_BuyerAppearance)
|
||||
{
|
||||
dest->FastQueuePacket(&in, ack_req);
|
||||
auto outapp = new EQApplicationPacket(OP_Barter, sizeof(structs::Buyer_SetAppearance_Struct));
|
||||
auto eq = (structs::Buyer_SetAppearance_Struct *) outapp->pBuffer;
|
||||
|
||||
return;
|
||||
eq->action = structs::RoF2BuyerActions::BuyerAppearance;
|
||||
eq->entity_id = emu->buyer_id;
|
||||
eq->enabled = emu->approval;
|
||||
|
||||
dest->FastQueuePacket(&outapp);
|
||||
safe_delete(in);
|
||||
|
||||
break;
|
||||
}
|
||||
case Barter_BuyerItemRemove: {
|
||||
auto emu = (BuyerRemoveItem_Struct *) in->pBuffer;
|
||||
|
||||
auto outapp = new EQApplicationPacket(OP_BuyerItems, sizeof(structs::BuyerRemoveItem_Struct));
|
||||
auto eq = (structs::BuyerRemoveItem_Struct *) outapp->pBuffer;
|
||||
|
||||
eq->action = structs::RoF2BuyerActions::BuyerModifyBuyLine;
|
||||
eq->slot_id = emu->buy_slot_id;
|
||||
eq->toggle = 0;
|
||||
|
||||
dest->FastQueuePacket(&outapp);
|
||||
safe_delete(in);
|
||||
|
||||
break;
|
||||
}
|
||||
case Barter_BuyerInspectBegin: {
|
||||
*(uint32 *) in->pBuffer = structs::RoF2BuyerActions::BuyerInspectBegin;
|
||||
dest->FastQueuePacket(&in);
|
||||
break;
|
||||
}
|
||||
case Barter_BuyerInspectEnd: {
|
||||
*(uint32 *) in->pBuffer = structs::RoF2BuyerActions::BuyerInspectEnd;
|
||||
dest->FastQueuePacket(&in);
|
||||
break;
|
||||
}
|
||||
case Barter_SellerBrowsing: {
|
||||
*(uint32 *) in->pBuffer = structs::RoF2BuyerActions::BuyerBrowsingBuyLine;
|
||||
dest->FastQueuePacket(&in);
|
||||
break;
|
||||
}
|
||||
case Barter_BuyerSearchResults: {
|
||||
BuyerItemSearchResults_Struct bisr{};
|
||||
auto emu = (BuyerGeneric_Struct *) in->pBuffer;
|
||||
EQ::Util::MemoryStreamReader ss(
|
||||
reinterpret_cast<char *>(emu->payload),
|
||||
in->size - sizeof(BuyerGeneric_Struct)
|
||||
);
|
||||
cereal::BinaryInputArchive ar(ss);
|
||||
ar(bisr);
|
||||
|
||||
LogTradingDetail("Sending item search results <green>[{}]", bisr.result_count);
|
||||
|
||||
uint32 packet_size = bisr.result_count * sizeof(structs::BuyerItemSearchResultEntry_Struct) + 8;
|
||||
auto outapp = std::make_unique<EQApplicationPacket>(OP_Barter, packet_size);
|
||||
auto eq = (char *) outapp->pBuffer;
|
||||
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, structs::RoF2BuyerActions::BuyerSearchResults);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, bisr.result_count);
|
||||
for (auto const &i: bisr.results) {
|
||||
strn0cpy(eq, i.item_name, 64);
|
||||
eq += 64;
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, i.item_id);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, i.item_icon);
|
||||
VARSTRUCT_SKIP_TYPE(uint32, eq);
|
||||
}
|
||||
dest->QueuePacket(outapp.get());
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
LogTradingDetail("Unhandled action <red>[{}]", sub_action);
|
||||
dest->FastQueuePacket(&in);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned char *__emu_buffer = in->pBuffer;
|
||||
|
||||
in->size = 80;
|
||||
|
||||
in->pBuffer = new unsigned char[in->size];
|
||||
|
||||
char *OutBuffer = (char *)in->pBuffer;
|
||||
|
||||
char Name[64];
|
||||
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, SubAction);
|
||||
uint32 EntityID = VARSTRUCT_DECODE_TYPE(uint32, Buffer);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, EntityID);
|
||||
uint8 Toggle = VARSTRUCT_DECODE_TYPE(uint8, Buffer);
|
||||
VARSTRUCT_DECODE_STRING(Name, Buffer);
|
||||
VARSTRUCT_ENCODE_STRING(OutBuffer, Name);
|
||||
OutBuffer = (char *)in->pBuffer + 72;
|
||||
VARSTRUCT_ENCODE_TYPE(uint8, OutBuffer, Toggle);
|
||||
|
||||
delete[] __emu_buffer;
|
||||
dest->FastQueuePacket(&in, ack_req);
|
||||
}
|
||||
|
||||
ENCODE(OP_BazaarSearch)
|
||||
@@ -542,8 +592,8 @@ namespace RoF2
|
||||
LogTrading(
|
||||
"(RoF2) AddTraderToBazaarWindow action <green>[{}] trader_id <green>[{}] entity_id <green>[{}] zone_id <green>[{}]",
|
||||
eq->action,
|
||||
eq->entity_id,
|
||||
eq->trader_id,
|
||||
eq->entity_id,
|
||||
eq->zone_id
|
||||
);
|
||||
dest->FastQueuePacket(&outapp);
|
||||
@@ -682,6 +732,243 @@ namespace RoF2
|
||||
FINISH_ENCODE();
|
||||
}
|
||||
|
||||
ENCODE(OP_BuyerItems)
|
||||
{
|
||||
EQApplicationPacket *inapp = *p;
|
||||
*p = nullptr;
|
||||
|
||||
auto action = *(uint32 *) inapp->pBuffer;
|
||||
|
||||
switch (action) {
|
||||
case Barter_BuyerItemUpdate: {
|
||||
BuyerLineItems_Struct bl{};
|
||||
auto emu = (BuyerGeneric_Struct *) inapp->pBuffer;
|
||||
EQ::Util::MemoryStreamReader ss(
|
||||
reinterpret_cast<char *>(emu->payload),
|
||||
inapp->size - sizeof(BuyerGeneric_Struct)
|
||||
);
|
||||
cereal::BinaryInputArchive ar(ss);
|
||||
ar(bl);
|
||||
|
||||
//packet size
|
||||
auto packet_size = bl.item_name.length() + 1 + 34;
|
||||
for (auto const &b: bl.trade_items) {
|
||||
packet_size += b.item_name.length() + 1;
|
||||
packet_size += 12;
|
||||
}
|
||||
|
||||
auto outapp = std::make_unique<EQApplicationPacket>(OP_BuyerItems, packet_size);
|
||||
char *eq = (char *) outapp->pBuffer;
|
||||
auto no_trade_items = bl.trade_items.size();
|
||||
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, structs::RoF2BuyerActions::BuyerModifyBuyLine);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, 0);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, bl.slot);
|
||||
VARSTRUCT_ENCODE_TYPE(uint8, eq, bl.enabled ? 1 : 0);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, bl.item_id);
|
||||
VARSTRUCT_ENCODE_STRING(eq, bl.item_name.c_str());
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, bl.item_icon);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, bl.item_quantity);
|
||||
VARSTRUCT_ENCODE_TYPE(uint8, eq, bl.item_toggle ? 1 : 0);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, bl.item_cost);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, no_trade_items);
|
||||
|
||||
for (int i = 0; i < no_trade_items; i++) {
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, bl.trade_items[i].item_id);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, bl.trade_items[i].item_quantity);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, bl.trade_items[i].item_icon);
|
||||
VARSTRUCT_ENCODE_STRING(eq, bl.trade_items[i].item_name.c_str());
|
||||
}
|
||||
dest->QueuePacket(outapp.get());
|
||||
safe_delete(inapp);
|
||||
break;
|
||||
}
|
||||
case Barter_BuyerInspectBegin: {
|
||||
auto emu = (BuyerGeneric_Struct *) inapp->pBuffer;
|
||||
|
||||
BuyerLineItems_Struct bli{};
|
||||
EQ::Util::MemoryStreamReader ss(
|
||||
reinterpret_cast<char *>(emu->payload),
|
||||
inapp->size - sizeof(BuyerGeneric_Struct)
|
||||
);
|
||||
cereal::BinaryInputArchive ar(ss);
|
||||
ar(bli);
|
||||
|
||||
//packet size
|
||||
auto packet_size = bli.item_name.length() + 1 + 34;
|
||||
for (auto const &b: bli.trade_items) {
|
||||
packet_size += b.item_name.length() + 1;
|
||||
packet_size += 12;
|
||||
}
|
||||
|
||||
auto packet = std::make_unique<EQApplicationPacket>(OP_BuyerItems, packet_size);
|
||||
char *eq = (char *) packet->pBuffer;
|
||||
auto no_trade_items = bli.trade_items.size();
|
||||
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, structs::RoF2BuyerActions::BuyerSendBuyLine);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, bli.slot);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, bli.slot);
|
||||
VARSTRUCT_ENCODE_TYPE(uint8, eq, bli.enabled ? 1 : 0);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, bli.item_id);
|
||||
VARSTRUCT_ENCODE_STRING(eq, bli.item_name.c_str());
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, bli.item_icon);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, bli.item_quantity);
|
||||
VARSTRUCT_ENCODE_TYPE(uint8, eq, bli.item_toggle ? 1 : 0);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, bli.item_cost);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, no_trade_items);
|
||||
|
||||
for (auto const &i: bli.trade_items) {
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, i.item_id);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, i.item_quantity);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, i.item_icon);
|
||||
VARSTRUCT_ENCODE_STRING(eq, i.item_name.c_str());
|
||||
}
|
||||
dest->QueuePacket(packet.get());
|
||||
safe_delete(inapp);
|
||||
|
||||
break;
|
||||
}
|
||||
case Barter_BuyerSearch: {
|
||||
BuyerLineSearch_Struct bls{};
|
||||
auto emu = (BuyerGeneric_Struct *) inapp->pBuffer;
|
||||
EQ::Util::MemoryStreamReader ss(
|
||||
reinterpret_cast<char *>(emu->payload),
|
||||
inapp->size - sizeof(BuyerGeneric_Struct)
|
||||
);
|
||||
cereal::BinaryInputArchive ar(ss);
|
||||
ar(bls);
|
||||
LogTrading("(RoF2) Barter_BuyerSearch action <green>[{}]", emu->action);
|
||||
|
||||
//Calculate size of packet
|
||||
auto p_size = 0;
|
||||
p_size += 5 * sizeof(uint32) + 1 * sizeof(uint8);
|
||||
p_size += bls.search_string.length() + 1;
|
||||
for (auto const &b: bls.buy_line) {
|
||||
p_size += 6 * sizeof(uint32) + 2 * sizeof(uint8);
|
||||
p_size += strlen(b.item_name) + 1;
|
||||
p_size += b.buyer_name.length() + 1;
|
||||
for (auto const &d: b.trade_items) {
|
||||
if (d.item_id != 0) {
|
||||
p_size += d.item_name.length() + 1;
|
||||
p_size += 3 * sizeof(uint32);
|
||||
}
|
||||
}
|
||||
p_size += 3 * sizeof(uint32);
|
||||
}
|
||||
|
||||
BuyerBuyLines_Struct bl{};
|
||||
auto outapp = std::make_unique<EQApplicationPacket>(OP_BuyerItems, p_size);
|
||||
auto eq = (char *) outapp->pBuffer;
|
||||
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, 1);
|
||||
VARSTRUCT_ENCODE_STRING(eq, bls.search_string.c_str());
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, bls.transaction_id);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, 0);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, 0);
|
||||
VARSTRUCT_ENCODE_TYPE(uint8, eq, 1);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, bls.no_items);
|
||||
for (auto const &b: bls.buy_line) {
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, b.slot);
|
||||
VARSTRUCT_ENCODE_TYPE(uint8, eq, 1);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, b.item_id);
|
||||
VARSTRUCT_ENCODE_STRING(eq, b.item_name);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, b.item_icon);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, b.item_quantity);
|
||||
VARSTRUCT_ENCODE_TYPE(uint8, eq, 1);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, b.item_cost);
|
||||
auto no_sub_items = b.trade_items.size();
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, no_sub_items);
|
||||
for (auto const &i: b.trade_items) {
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, i.item_id);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, i.item_quantity);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, i.item_icon);
|
||||
VARSTRUCT_ENCODE_STRING(eq, i.item_name.c_str());
|
||||
}
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, b.buyer_entity_id);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, b.buyer_id);
|
||||
VARSTRUCT_ENCODE_TYPE(uint16, eq, b.buyer_zone_id);
|
||||
VARSTRUCT_ENCODE_TYPE(uint16, eq, b.buyer_zone_instance_id);
|
||||
VARSTRUCT_ENCODE_STRING(eq, b.buyer_name.c_str());
|
||||
}
|
||||
dest->QueuePacket(outapp.get());
|
||||
break;
|
||||
}
|
||||
case Barter_RemoveFromMerchantWindow: {
|
||||
auto emu = (BuyerRemoveItemFromMerchantWindow_Struct *) inapp->pBuffer;
|
||||
|
||||
emu->action = structs::RoF2BuyerActions::BuyerSendBuyLine;
|
||||
dest->FastQueuePacket(&inapp);
|
||||
break;
|
||||
}
|
||||
case Barter_BuyerTransactionComplete:
|
||||
case Barter_SellerTransactionComplete: {
|
||||
BuyerLineSellItem_Struct blsi{};
|
||||
auto emu = (BuyerGeneric_Struct *) inapp->pBuffer;
|
||||
EQ::Util::MemoryStreamReader ss(
|
||||
reinterpret_cast<char *>(emu->payload),
|
||||
inapp->size - sizeof(BuyerGeneric_Struct)
|
||||
);
|
||||
cereal::BinaryInputArchive ar(ss);
|
||||
ar(blsi);
|
||||
|
||||
//packet size
|
||||
auto packet_size = strlen(blsi.item_name) * 2 + 2 + 48 + 30 + blsi.seller_name.length() + 1 +
|
||||
blsi.buyer_name.length() + 1;
|
||||
for (auto const &b: blsi.trade_items) {
|
||||
packet_size += b.item_name.length() + 1;
|
||||
packet_size += 12;
|
||||
}
|
||||
|
||||
auto outapp = std::make_unique<EQApplicationPacket>(OP_BuyerItems, packet_size);
|
||||
auto eq = (char *) outapp->pBuffer;
|
||||
|
||||
switch (action) {
|
||||
case Barter_BuyerTransactionComplete: {
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, structs::RoF2BuyerActions::BuyerBuyItem);
|
||||
break;
|
||||
}
|
||||
case Barter_SellerTransactionComplete: {
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, structs::RoF2BuyerActions::BuyerSellItem);
|
||||
break;
|
||||
}
|
||||
}
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, blsi.sub_action);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, blsi.error_code);
|
||||
eq += 16;
|
||||
VARSTRUCT_ENCODE_STRING(eq, blsi.buyer_name.c_str());
|
||||
VARSTRUCT_ENCODE_STRING(eq, blsi.item_name);
|
||||
VARSTRUCT_ENCODE_STRING(eq, blsi.seller_name.c_str());
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, 0xFFFFFFFF);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, 0xFFFFFFFF);
|
||||
eq += 1;
|
||||
VARSTRUCT_ENCODE_STRING(eq, blsi.item_name);
|
||||
eq += 9;
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, blsi.item_cost);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, blsi.trade_items.size());
|
||||
|
||||
for (auto const &i: blsi.trade_items) {
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, 0);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, i.item_quantity);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, 0);
|
||||
VARSTRUCT_ENCODE_STRING(eq, i.item_name.c_str());
|
||||
}
|
||||
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, 0);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, 0);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, 0);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, 0xFFFFFF);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, 0);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, eq, blsi.seller_quantity);
|
||||
|
||||
dest->QueuePacket(outapp.get());
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
dest->FastQueuePacket(&inapp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ENCODE(OP_CancelTrade)
|
||||
{
|
||||
ENCODE_LENGTH_EXACT(CancelTrade_Struct);
|
||||
@@ -1690,7 +1977,7 @@ namespace RoF2
|
||||
uchar *__emu_buffer = in->pBuffer;
|
||||
ItemPacket_Struct *old_item_pkt = (ItemPacket_Struct *) __emu_buffer;
|
||||
|
||||
switch(old_item_pkt->PacketType)
|
||||
switch(old_item_pkt->PacketType)
|
||||
{
|
||||
case ItemPacketParcel: {
|
||||
ParcelMessaging_Struct pms{};
|
||||
@@ -4348,6 +4635,9 @@ namespace RoF2
|
||||
if (emu->DestructibleObject) {
|
||||
OtherData = OtherData | 0xe1; // Live has 0xe1 for OtherData
|
||||
}
|
||||
if (emu->buyer) {
|
||||
OtherData = OtherData | 0x01;
|
||||
}
|
||||
|
||||
VARSTRUCT_ENCODE_TYPE(uint8, Buffer, OtherData);
|
||||
// float EmitterScalingRadius
|
||||
@@ -4459,7 +4749,7 @@ namespace RoF2
|
||||
VARSTRUCT_ENCODE_STRING(Buffer, emu->lastName);
|
||||
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // aatitle
|
||||
VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->guild_show);
|
||||
VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->guild_show);
|
||||
VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // TempPet
|
||||
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->petOwnerId);
|
||||
@@ -4664,6 +4954,66 @@ namespace RoF2
|
||||
FINISH_DIRECT_DECODE();
|
||||
}
|
||||
|
||||
DECODE(OP_Barter)
|
||||
{
|
||||
auto action = *(uint32 *) __packet->pBuffer;
|
||||
|
||||
switch (action) {
|
||||
case structs::RoF2BuyerActions::BuyerRemoveItem: {
|
||||
auto emu = (BuyerGeneric_Struct *) __packet->pBuffer;
|
||||
emu->action = Barter_BuyerItemRemove;
|
||||
LogTradingDetail("(RoF2) Buyer Remove Item");
|
||||
|
||||
break;
|
||||
}
|
||||
case structs::RoF2BuyerActions::BuyerInspectBegin: {
|
||||
LogTradingDetail("(RoF2) Buyer Inspect Begin Item");
|
||||
|
||||
auto emu = (BuyerGeneric_Struct *) __packet->pBuffer;
|
||||
emu->action = Barter_BuyerInspectBegin;
|
||||
|
||||
break;
|
||||
}
|
||||
case structs::RoF2BuyerActions::BuyerInspectEnd: {
|
||||
LogTradingDetail("(RoF2) Buyer Inspect End Item ");
|
||||
|
||||
auto emu = (BuyerGeneric_Struct *) __packet->pBuffer;
|
||||
emu->action = Barter_BuyerInspectEnd;
|
||||
|
||||
break;
|
||||
}
|
||||
case structs::RoF2BuyerActions::BuyerWelcomeMessage: {
|
||||
LogTradingDetail("(RoF2) Buyer Welcome Message Update");
|
||||
SETUP_DIRECT_DECODE(BuyerWelcomeMessageUpdate_Struct, structs::BuyerWelcomeMessageUpdate_Struct);
|
||||
|
||||
emu->action = Barter_WelcomeMessageUpdate;
|
||||
strn0cpy(emu->welcome_message, eq->welcome_message, sizeof(emu->welcome_message));
|
||||
|
||||
FINISH_DIRECT_DECODE();
|
||||
break;
|
||||
}
|
||||
case structs::RoF2BuyerActions::BuyerItemInspect: {
|
||||
SETUP_DIRECT_DECODE(BarterItemSearchLinkRequest_Struct, structs::BarterItemSearchLinkRequest_Struct);
|
||||
LogTradingDetail("(RoF2) Seller ID <green>[{}] Inspecting Item <green>[{}] from Buyer ID <green>[{}] ",
|
||||
eq->seller_id,
|
||||
eq->item_id,
|
||||
eq->buyer_id
|
||||
);
|
||||
|
||||
emu->action = Barter_BarterItemInspect;
|
||||
emu->item_id = eq->item_id;
|
||||
emu->searcher_id = eq->seller_id;
|
||||
|
||||
FINISH_DIRECT_DECODE();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
auto emu = (BuyerGeneric_Struct *) __packet->pBuffer;
|
||||
LogTradingDetail("(RoF2) Pass thru OP_Barter packet action <red>[{}]", emu->action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DECODE(OP_BazaarSearch)
|
||||
{
|
||||
char *Buffer = (char *)__packet->pBuffer;
|
||||
@@ -4742,6 +5092,147 @@ namespace RoF2
|
||||
FINISH_DIRECT_DECODE();
|
||||
}
|
||||
|
||||
DECODE(OP_BuyerItems)
|
||||
{
|
||||
auto action = *(uint32 *) __packet->pBuffer;
|
||||
|
||||
switch (action) {
|
||||
case structs::RoF2BuyerActions::BuyerModifyBuyLine:
|
||||
case structs::RoF2BuyerActions::BuyerBuyLine: {
|
||||
BuyerBuyLines_Struct buyer_buy_lines{};
|
||||
auto buffer = (char *) __packet->pBuffer;
|
||||
|
||||
buyer_buy_lines.action = VARSTRUCT_DECODE_TYPE(uint32, buffer);
|
||||
|
||||
buyer_buy_lines.no_items = 1;
|
||||
if (action == structs::RoF2BuyerActions::BuyerBuyLine) {
|
||||
buyer_buy_lines.no_items = VARSTRUCT_DECODE_TYPE(uint16, buffer);
|
||||
}
|
||||
|
||||
buyer_buy_lines.buy_lines.reserve(buyer_buy_lines.no_items);
|
||||
for (int i = 0; i < buyer_buy_lines.no_items; i++) {
|
||||
BuyerLineItems_Struct b{};
|
||||
b.slot = VARSTRUCT_DECODE_TYPE(uint32, buffer);
|
||||
b.enabled = VARSTRUCT_DECODE_TYPE(uint8, buffer);
|
||||
b.item_id = VARSTRUCT_DECODE_TYPE(uint32, buffer);
|
||||
b.item_name = std::string(buffer, strlen(buffer));
|
||||
buffer += strlen(buffer) + 1;
|
||||
b.item_icon = VARSTRUCT_DECODE_TYPE(uint32, buffer);
|
||||
b.item_quantity = VARSTRUCT_DECODE_TYPE(uint32, buffer);
|
||||
b.item_toggle = VARSTRUCT_DECODE_TYPE(uint8, buffer);
|
||||
b.item_cost = VARSTRUCT_DECODE_TYPE(uint32, buffer);
|
||||
auto trade_items = VARSTRUCT_DECODE_TYPE(uint32, buffer);
|
||||
buyer_buy_lines.buy_lines.push_back(b);
|
||||
|
||||
if (trade_items > 0) {
|
||||
buyer_buy_lines.buy_lines[i].trade_items.reserve(trade_items);
|
||||
for (int x = 0; x < trade_items; x++) {
|
||||
BuyerLineTradeItems_Struct blti{};
|
||||
blti.item_id = VARSTRUCT_DECODE_TYPE(uint32, buffer);
|
||||
blti.item_quantity = VARSTRUCT_DECODE_TYPE(uint32, buffer);
|
||||
blti.item_icon = VARSTRUCT_DECODE_TYPE(uint32, buffer);
|
||||
blti.item_name = std::string(buffer, strlen(buffer));
|
||||
buffer += strlen(buffer) + 1;
|
||||
buyer_buy_lines.buy_lines[i].trade_items.push_back(blti);
|
||||
}
|
||||
}
|
||||
buffer += 13;
|
||||
}
|
||||
|
||||
buffer = nullptr;
|
||||
std::stringstream ss{};
|
||||
cereal::BinaryOutputArchive ar(ss);
|
||||
{
|
||||
ar(buyer_buy_lines);
|
||||
}
|
||||
|
||||
auto new_size = sizeof(BuyerGeneric_Struct) + ss.str().length();
|
||||
auto new_packet = new unsigned char[new_size];
|
||||
__packet->size = new_size;
|
||||
__packet->pBuffer = new_packet;
|
||||
auto emu = (BuyerGeneric_Struct *) __packet->pBuffer;
|
||||
emu->action = Barter_BuyerItemUpdate;
|
||||
|
||||
if (action == structs::RoF2BuyerActions::BuyerBuyLine) {
|
||||
emu->action = Barter_BuyerItemStart;
|
||||
}
|
||||
|
||||
memcpy(emu->payload, ss.str().data(), ss.str().length());
|
||||
__packet->SetOpcode(OP_Barter);
|
||||
|
||||
break;
|
||||
}
|
||||
case structs::RoF2BuyerActions::BuyerSellItem: {
|
||||
BuyerLineSellItem_Struct sell_item{};
|
||||
|
||||
char *buffer = (char *) __packet->pBuffer;
|
||||
|
||||
sell_item.action = VARSTRUCT_DECODE_TYPE(uint32, buffer);
|
||||
sell_item.purchase_method = VARSTRUCT_DECODE_TYPE(uint32, buffer);
|
||||
buffer += 4;
|
||||
sell_item.buyer_entity_id = VARSTRUCT_DECODE_TYPE(uint32, buffer);
|
||||
sell_item.buyer_id = VARSTRUCT_DECODE_TYPE(uint32, buffer);
|
||||
buffer += 11;
|
||||
sell_item.slot = VARSTRUCT_DECODE_TYPE(uint32, buffer);
|
||||
sell_item.enabled = VARSTRUCT_DECODE_TYPE(uint8, buffer);
|
||||
sell_item.item_id = VARSTRUCT_DECODE_TYPE(uint32, buffer);
|
||||
VARSTRUCT_DECODE_STRING(sell_item.item_name, buffer);
|
||||
sell_item.item_icon = VARSTRUCT_DECODE_TYPE(uint32, buffer);
|
||||
sell_item.item_quantity = VARSTRUCT_DECODE_TYPE(uint32, buffer);
|
||||
sell_item.item_toggle = VARSTRUCT_DECODE_TYPE(uint8, buffer);
|
||||
sell_item.item_cost = VARSTRUCT_DECODE_TYPE(uint32, buffer);
|
||||
sell_item.no_trade_items = VARSTRUCT_DECODE_TYPE(uint32, buffer);
|
||||
|
||||
if (sell_item.no_trade_items > 0) {
|
||||
sell_item.trade_items.reserve(sell_item.no_trade_items);
|
||||
for (int x = 0; x < sell_item.no_trade_items; x++) {
|
||||
BuyerLineTradeItems_Struct blti{};
|
||||
blti.item_id = VARSTRUCT_DECODE_TYPE(uint32, buffer);
|
||||
blti.item_quantity = VARSTRUCT_DECODE_TYPE(uint32, buffer);
|
||||
blti.item_icon = VARSTRUCT_DECODE_TYPE(uint32, buffer);
|
||||
blti.item_name = std::string(buffer, strlen(buffer));
|
||||
buffer += strlen(buffer) + 1;
|
||||
sell_item.trade_items.push_back(blti);
|
||||
}
|
||||
}
|
||||
|
||||
if (sell_item.purchase_method) {
|
||||
sell_item.buyer_entity_id = VARSTRUCT_DECODE_TYPE(uint32, buffer);
|
||||
sell_item.buyer_id = VARSTRUCT_DECODE_TYPE(uint32, buffer);
|
||||
sell_item.zone_id = VARSTRUCT_DECODE_TYPE(uint32, buffer);
|
||||
sell_item.buyer_name = std::string(buffer, strlen(buffer));
|
||||
buffer += sell_item.buyer_name.length() + 1;
|
||||
}
|
||||
else {
|
||||
buffer += 13;
|
||||
}
|
||||
|
||||
sell_item.seller_quantity = VARSTRUCT_DECODE_TYPE(uint32, buffer);
|
||||
|
||||
buffer += 4;
|
||||
|
||||
buffer = nullptr;
|
||||
std::stringstream ss{};
|
||||
cereal::BinaryOutputArchive ar(ss);
|
||||
{
|
||||
ar(sell_item);
|
||||
}
|
||||
|
||||
auto new_size = sizeof(BuyerGeneric_Struct) + ss.str().length();
|
||||
auto new_packet = new unsigned char[new_size];
|
||||
__packet->size = new_size;
|
||||
__packet->pBuffer = new_packet;
|
||||
auto emu = (BuyerGeneric_Struct *) __packet->pBuffer;
|
||||
emu->action = Barter_SellItem;
|
||||
|
||||
memcpy(emu->payload, ss.str().data(), ss.str().length());
|
||||
__packet->SetOpcode(OP_Barter);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DECODE(OP_CastSpell)
|
||||
{
|
||||
DECODE_LENGTH_EXACT(structs::CastSpell_Struct);
|
||||
|
||||
@@ -48,6 +48,7 @@ E(OP_BeginCast)
|
||||
E(OP_BlockedBuffs)
|
||||
E(OP_Buff)
|
||||
E(OP_BuffCreate)
|
||||
E(OP_BuyerItems)
|
||||
E(OP_CancelTrade)
|
||||
E(OP_CastSpell)
|
||||
E(OP_ChannelMessage)
|
||||
@@ -149,11 +150,13 @@ D(OP_Animation)
|
||||
D(OP_ApplyPoison)
|
||||
D(OP_AugmentInfo)
|
||||
D(OP_AugmentItem)
|
||||
D(OP_Barter)
|
||||
D(OP_BazaarSearch)
|
||||
D(OP_BlockedBuffs)
|
||||
D(OP_BookButton)
|
||||
D(OP_Buff)
|
||||
D(OP_BuffRemoveRequest)
|
||||
D(OP_BuyerItems)
|
||||
D(OP_CastSpell)
|
||||
D(OP_ChannelMessage)
|
||||
D(OP_CharacterCreate)
|
||||
|
||||
+142
-22
@@ -354,15 +354,15 @@ struct Spawn_Struct_Bitfields
|
||||
/*29*/ unsigned showname:1;
|
||||
/*30*/ unsigned idleanimationsoff:1; // what we called statue?
|
||||
/*31*/ unsigned untargetable:1; // bClickThrough
|
||||
/* do these later
|
||||
32 unsigned buyer:1;
|
||||
33 unsigned offline:1;
|
||||
34 unsigned interactiveobject:1;
|
||||
35 unsigned flung:1; // hmm this vfunc appears to do stuff with leve and flung variables
|
||||
36 unsigned title:1;
|
||||
37 unsigned suffix:1;
|
||||
38 unsigned padding1:1;
|
||||
39 unsigned padding2:1;
|
||||
// byte 5
|
||||
/*32 unsigned buyer:1;
|
||||
/*33 unsigned offline:1;
|
||||
/*34 unsigned interactiveobject:1;
|
||||
/*35 unsigned flung:1; // hmm this vfunc appears to do stuff with leve and flung variables
|
||||
/*36 unsigned title:1;
|
||||
/*37 unsigned suffix:1;
|
||||
/*38 unsigned padding1:1;
|
||||
/*39 unsigned padding2:1;
|
||||
40 unsinged padding3:1;
|
||||
*/
|
||||
/*
|
||||
@@ -3107,19 +3107,139 @@ struct EnvDamage2_Struct {
|
||||
|
||||
//Bazaar Stuff
|
||||
enum RoF2BazaarTraderBuyerActions {
|
||||
Zero = 0,
|
||||
BeginTraderMode = 1,
|
||||
EndTraderMode = 2,
|
||||
PriceUpdate = 3,
|
||||
EndTransaction = 4,
|
||||
BazaarSearch = 7,
|
||||
WelcomeMessage = 9,
|
||||
BuyTraderItem = 10,
|
||||
ListTraderItems = 11,
|
||||
BazaarInspect = 18,
|
||||
ClickTrader = 28,
|
||||
ItemMove = 19,
|
||||
ReconcileItems = 20
|
||||
Zero = 0,
|
||||
BeginTraderMode = 1,
|
||||
EndTraderMode = 2,
|
||||
PriceUpdate = 3,
|
||||
EndTransaction = 4,
|
||||
BazaarSearch = 7,
|
||||
WelcomeMessage = 9,
|
||||
BuyTraderItem = 10,
|
||||
ListTraderItems = 11,
|
||||
BazaarInspect = 18,
|
||||
ClickTrader = 28,
|
||||
ItemMove = 19,
|
||||
ReconcileItems = 20
|
||||
};
|
||||
|
||||
enum RoF2BuyerActions {
|
||||
BuyerSearchResults = 0x00,
|
||||
BuyerBuyLine = 0x06,
|
||||
BuyerModifyBuyLine = 0x07,
|
||||
BuyerRemoveItem = 0x08,
|
||||
BuyerSellItem = 0x09,
|
||||
BuyerBuyItem = 0x0a,
|
||||
BuyerInspectBegin = 0x0b,
|
||||
BuyerInspectEnd = 0x0c,
|
||||
BuyerAppearance = 0x0d,
|
||||
BuyerSendBuyLine = 0x0e,
|
||||
BuyerItemInspect = 0x0f,
|
||||
BuyerBrowsingBuyLine = 0x10,
|
||||
BarterWelcomeMessage = 0x11,
|
||||
BuyerWelcomeMessage = 0x13,
|
||||
BuyerGreeting = 0x14,
|
||||
BuyerInventoryFull = 0x16
|
||||
};
|
||||
|
||||
struct BarterItemSearchLinkRequest_Struct {
|
||||
uint32 action;
|
||||
uint32 unknown_004;
|
||||
uint32 seller_id;
|
||||
uint32 buyer_id;
|
||||
uint32 unknown_016;
|
||||
uint32 slot_id; // 0xffffffff main buy line 0x0 trade_item_1, 0x1 trade_item_2
|
||||
uint32 item_id;
|
||||
uint32 unknown_028;
|
||||
};
|
||||
|
||||
struct BuyerWelcomeMessageUpdate_Struct {
|
||||
uint32 action;
|
||||
char unknown_004[64];
|
||||
uint32 unknown_068;
|
||||
char welcome_message[256];
|
||||
};
|
||||
|
||||
struct Buyer_SetAppearance_Struct {
|
||||
uint32 action;
|
||||
uint32 entity_id;
|
||||
char unknown[64];
|
||||
uint32 enabled;
|
||||
};
|
||||
|
||||
struct BuyerRemoveItem_Struct {
|
||||
uint32 action;
|
||||
uint32 unknown004;
|
||||
uint32 slot_id;
|
||||
uint32 toggle;
|
||||
};
|
||||
|
||||
struct BuyerLineSellItem_Struct {
|
||||
uint32 action;
|
||||
uint32 purchase_method; // 0 direct merchant, 1 via /barter window
|
||||
uint32 unknown008;
|
||||
uint32 buyer_entity_id;
|
||||
uint32 seller_entity_id;
|
||||
char unknown[15];
|
||||
uint32 slot;
|
||||
uint8 enabled;
|
||||
uint32 item_id;
|
||||
char item_name[64];
|
||||
uint32 item_icon;
|
||||
uint32 item_quantity;
|
||||
uint8 item_toggle;
|
||||
uint32 item_cost;
|
||||
uint32 no_trade_items;
|
||||
BuyerLineTradeItems_Struct trade_items[10];
|
||||
char unknown2[13];
|
||||
uint32 seller_quantity;
|
||||
};
|
||||
|
||||
struct BuyerLineItemsSearch_Struct {
|
||||
uint32 slot;
|
||||
uint8 enabled;
|
||||
uint32 item_id;
|
||||
char item_name[64];
|
||||
uint32 item_icon;
|
||||
uint32 item_quantity;
|
||||
uint8 item_toggle;
|
||||
uint32 item_cost;
|
||||
uint32 buyer_id;
|
||||
BuyerLineTradeItems_Struct trade_items[MAX_BUYER_COMPENSATION_ITEMS];
|
||||
};
|
||||
|
||||
struct BuyerLineSearch_Struct {
|
||||
uint32 action;
|
||||
uint32 no_items;
|
||||
std::vector<BuyerLineItemsSearch_Struct> buy_line;
|
||||
};
|
||||
|
||||
struct BuyerStart_Struct {
|
||||
uint32 action;
|
||||
uint16 no_buyer_lines;
|
||||
uint32 slot;
|
||||
uint8 enabled;
|
||||
uint32 item_id;
|
||||
char item_name[1]; // vary length
|
||||
uint32 item_icon;
|
||||
uint32 item_quantity;
|
||||
uint8 toggle;
|
||||
uint32 item_cost;
|
||||
uint32 no_trade_items;
|
||||
BuyerLineTradeItems_Struct trade_items[1]; // size is actually no_trade_items. If 0, then this is not in packet
|
||||
char unknown[13];
|
||||
};
|
||||
|
||||
struct BuyerItemSearchResultEntry_Struct {
|
||||
char item_name[64];
|
||||
uint32 item_id;
|
||||
uint32 item_icon;
|
||||
uint32 unknown_072;
|
||||
};
|
||||
|
||||
struct BuyerItemSearchResults_Struct {
|
||||
uint32 action;
|
||||
uint32 result_count;
|
||||
BuyerItemSearchResultEntry_Struct results[];
|
||||
};
|
||||
|
||||
enum {
|
||||
|
||||
@@ -74,6 +74,11 @@ void PathManager::LoadPaths()
|
||||
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();
|
||||
@@ -89,6 +94,7 @@ void PathManager::LoadPaths()
|
||||
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);
|
||||
@@ -129,6 +135,11 @@ const std::string &PathManager::GetPatchPath() const
|
||||
return m_patch_path;
|
||||
}
|
||||
|
||||
const std::string &PathManager::GetOpcodePath() const
|
||||
{
|
||||
return m_opcode_path;
|
||||
}
|
||||
|
||||
const std::string &PathManager::GetLuaModulesPath() const
|
||||
{
|
||||
return m_lua_modules_path;
|
||||
|
||||
@@ -13,6 +13,7 @@ public:
|
||||
[[nodiscard]] const std::string &GetLuaModulesPath() const;
|
||||
[[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;
|
||||
@@ -24,6 +25,7 @@ private:
|
||||
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;
|
||||
|
||||
@@ -21,7 +21,7 @@ public:
|
||||
struct BotSpellsEntries {
|
||||
uint32_t id;
|
||||
int32_t npc_spells_id;
|
||||
int16_t spellid;
|
||||
uint16_t spell_id;
|
||||
uint32_t type;
|
||||
uint8_t minlevel;
|
||||
uint8_t maxlevel;
|
||||
@@ -46,7 +46,7 @@ public:
|
||||
return {
|
||||
"id",
|
||||
"npc_spells_id",
|
||||
"spellid",
|
||||
"spell_id",
|
||||
"type",
|
||||
"minlevel",
|
||||
"maxlevel",
|
||||
@@ -67,7 +67,7 @@ public:
|
||||
return {
|
||||
"id",
|
||||
"npc_spells_id",
|
||||
"spellid",
|
||||
"spell_id",
|
||||
"type",
|
||||
"minlevel",
|
||||
"maxlevel",
|
||||
@@ -122,7 +122,7 @@ public:
|
||||
|
||||
e.id = 0;
|
||||
e.npc_spells_id = 0;
|
||||
e.spellid = 0;
|
||||
e.spell_id = 0;
|
||||
e.type = 0;
|
||||
e.minlevel = 0;
|
||||
e.maxlevel = 255;
|
||||
@@ -173,7 +173,7 @@ public:
|
||||
|
||||
e.id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
|
||||
e.npc_spells_id = row[1] ? static_cast<int32_t>(atoi(row[1])) : 0;
|
||||
e.spellid = row[2] ? static_cast<int16_t>(atoi(row[2])) : 0;
|
||||
e.spell_id = row[2] ? static_cast<uint16_t>(strtoul(row[2], nullptr, 10)) : 0;
|
||||
e.type = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
|
||||
e.minlevel = row[4] ? static_cast<uint8_t>(strtoul(row[4], nullptr, 10)) : 0;
|
||||
e.maxlevel = row[5] ? static_cast<uint8_t>(strtoul(row[5], nullptr, 10)) : 255;
|
||||
@@ -220,7 +220,7 @@ public:
|
||||
auto columns = Columns();
|
||||
|
||||
v.push_back(columns[1] + " = " + std::to_string(e.npc_spells_id));
|
||||
v.push_back(columns[2] + " = " + std::to_string(e.spellid));
|
||||
v.push_back(columns[2] + " = " + std::to_string(e.spell_id));
|
||||
v.push_back(columns[3] + " = " + std::to_string(e.type));
|
||||
v.push_back(columns[4] + " = " + std::to_string(e.minlevel));
|
||||
v.push_back(columns[5] + " = " + std::to_string(e.maxlevel));
|
||||
@@ -256,7 +256,7 @@ public:
|
||||
|
||||
v.push_back(std::to_string(e.id));
|
||||
v.push_back(std::to_string(e.npc_spells_id));
|
||||
v.push_back(std::to_string(e.spellid));
|
||||
v.push_back(std::to_string(e.spell_id));
|
||||
v.push_back(std::to_string(e.type));
|
||||
v.push_back(std::to_string(e.minlevel));
|
||||
v.push_back(std::to_string(e.maxlevel));
|
||||
@@ -300,7 +300,7 @@ public:
|
||||
|
||||
v.push_back(std::to_string(e.id));
|
||||
v.push_back(std::to_string(e.npc_spells_id));
|
||||
v.push_back(std::to_string(e.spellid));
|
||||
v.push_back(std::to_string(e.spell_id));
|
||||
v.push_back(std::to_string(e.type));
|
||||
v.push_back(std::to_string(e.minlevel));
|
||||
v.push_back(std::to_string(e.maxlevel));
|
||||
@@ -348,7 +348,7 @@ public:
|
||||
|
||||
e.id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
|
||||
e.npc_spells_id = row[1] ? static_cast<int32_t>(atoi(row[1])) : 0;
|
||||
e.spellid = row[2] ? static_cast<int16_t>(atoi(row[2])) : 0;
|
||||
e.spell_id = row[2] ? static_cast<uint16_t>(strtoul(row[2], nullptr, 10)) : 0;
|
||||
e.type = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
|
||||
e.minlevel = row[4] ? static_cast<uint8_t>(strtoul(row[4], nullptr, 10)) : 0;
|
||||
e.maxlevel = row[5] ? static_cast<uint8_t>(strtoul(row[5], nullptr, 10)) : 255;
|
||||
@@ -387,7 +387,7 @@ public:
|
||||
|
||||
e.id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
|
||||
e.npc_spells_id = row[1] ? static_cast<int32_t>(atoi(row[1])) : 0;
|
||||
e.spellid = row[2] ? static_cast<int16_t>(atoi(row[2])) : 0;
|
||||
e.spell_id = row[2] ? static_cast<uint16_t>(strtoul(row[2], nullptr, 10)) : 0;
|
||||
e.type = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
|
||||
e.minlevel = row[4] ? static_cast<uint8_t>(strtoul(row[4], nullptr, 10)) : 0;
|
||||
e.maxlevel = row[5] ? static_cast<uint8_t>(strtoul(row[5], nullptr, 10)) : 255;
|
||||
@@ -476,7 +476,7 @@ public:
|
||||
|
||||
v.push_back(std::to_string(e.id));
|
||||
v.push_back(std::to_string(e.npc_spells_id));
|
||||
v.push_back(std::to_string(e.spellid));
|
||||
v.push_back(std::to_string(e.spell_id));
|
||||
v.push_back(std::to_string(e.type));
|
||||
v.push_back(std::to_string(e.minlevel));
|
||||
v.push_back(std::to_string(e.maxlevel));
|
||||
@@ -513,7 +513,7 @@ public:
|
||||
|
||||
v.push_back(std::to_string(e.id));
|
||||
v.push_back(std::to_string(e.npc_spells_id));
|
||||
v.push_back(std::to_string(e.spellid));
|
||||
v.push_back(std::to_string(e.spell_id));
|
||||
v.push_back(std::to_string(e.type));
|
||||
v.push_back(std::to_string(e.minlevel));
|
||||
v.push_back(std::to_string(e.maxlevel));
|
||||
|
||||
@@ -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_BUYER_BUY_LINES_REPOSITORY_H
|
||||
#define EQEMU_BASE_BUYER_BUY_LINES_REPOSITORY_H
|
||||
|
||||
#include "../../database.h"
|
||||
#include "../../strings.h"
|
||||
#include <ctime>
|
||||
|
||||
class BaseBuyerBuyLinesRepository {
|
||||
public:
|
||||
struct BuyerBuyLines {
|
||||
uint64_t id;
|
||||
uint64_t buyer_id;
|
||||
uint32_t char_id;
|
||||
int32_t buy_slot_id;
|
||||
int32_t item_id;
|
||||
int32_t item_qty;
|
||||
int32_t item_price;
|
||||
uint32_t item_icon;
|
||||
std::string item_name;
|
||||
};
|
||||
|
||||
static std::string PrimaryKey()
|
||||
{
|
||||
return std::string("id");
|
||||
}
|
||||
|
||||
static std::vector<std::string> Columns()
|
||||
{
|
||||
return {
|
||||
"id",
|
||||
"buyer_id",
|
||||
"char_id",
|
||||
"buy_slot_id",
|
||||
"item_id",
|
||||
"item_qty",
|
||||
"item_price",
|
||||
"item_icon",
|
||||
"item_name",
|
||||
};
|
||||
}
|
||||
|
||||
static std::vector<std::string> SelectColumns()
|
||||
{
|
||||
return {
|
||||
"id",
|
||||
"buyer_id",
|
||||
"char_id",
|
||||
"buy_slot_id",
|
||||
"item_id",
|
||||
"item_qty",
|
||||
"item_price",
|
||||
"item_icon",
|
||||
"item_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("buyer_buy_lines");
|
||||
}
|
||||
|
||||
static std::string BaseSelect()
|
||||
{
|
||||
return fmt::format(
|
||||
"SELECT {} FROM {}",
|
||||
SelectColumnsRaw(),
|
||||
TableName()
|
||||
);
|
||||
}
|
||||
|
||||
static std::string BaseInsert()
|
||||
{
|
||||
return fmt::format(
|
||||
"INSERT INTO {} ({}) ",
|
||||
TableName(),
|
||||
ColumnsRaw()
|
||||
);
|
||||
}
|
||||
|
||||
static BuyerBuyLines NewEntity()
|
||||
{
|
||||
BuyerBuyLines e{};
|
||||
|
||||
e.id = 0;
|
||||
e.buyer_id = 0;
|
||||
e.char_id = 0;
|
||||
e.buy_slot_id = 0;
|
||||
e.item_id = 0;
|
||||
e.item_qty = 0;
|
||||
e.item_price = 0;
|
||||
e.item_icon = 0;
|
||||
e.item_name = "";
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
static BuyerBuyLines GetBuyerBuyLines(
|
||||
const std::vector<BuyerBuyLines> &buyer_buy_liness,
|
||||
int buyer_buy_lines_id
|
||||
)
|
||||
{
|
||||
for (auto &buyer_buy_lines : buyer_buy_liness) {
|
||||
if (buyer_buy_lines.id == buyer_buy_lines_id) {
|
||||
return buyer_buy_lines;
|
||||
}
|
||||
}
|
||||
|
||||
return NewEntity();
|
||||
}
|
||||
|
||||
static BuyerBuyLines FindOne(
|
||||
Database& db,
|
||||
int buyer_buy_lines_id
|
||||
)
|
||||
{
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"{} WHERE {} = {} LIMIT 1",
|
||||
BaseSelect(),
|
||||
PrimaryKey(),
|
||||
buyer_buy_lines_id
|
||||
)
|
||||
);
|
||||
|
||||
auto row = results.begin();
|
||||
if (results.RowCount() == 1) {
|
||||
BuyerBuyLines e{};
|
||||
|
||||
e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0;
|
||||
e.buyer_id = row[1] ? strtoull(row[1], nullptr, 10) : 0;
|
||||
e.char_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
|
||||
e.buy_slot_id = row[3] ? static_cast<int32_t>(atoi(row[3])) : 0;
|
||||
e.item_id = row[4] ? static_cast<int32_t>(atoi(row[4])) : 0;
|
||||
e.item_qty = row[5] ? static_cast<int32_t>(atoi(row[5])) : 0;
|
||||
e.item_price = row[6] ? static_cast<int32_t>(atoi(row[6])) : 0;
|
||||
e.item_icon = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
|
||||
e.item_name = row[8] ? row[8] : "";
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
return NewEntity();
|
||||
}
|
||||
|
||||
static int DeleteOne(
|
||||
Database& db,
|
||||
int buyer_buy_lines_id
|
||||
)
|
||||
{
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"DELETE FROM {} WHERE {} = {}",
|
||||
TableName(),
|
||||
PrimaryKey(),
|
||||
buyer_buy_lines_id
|
||||
)
|
||||
);
|
||||
|
||||
return (results.Success() ? results.RowsAffected() : 0);
|
||||
}
|
||||
|
||||
static int UpdateOne(
|
||||
Database& db,
|
||||
const BuyerBuyLines &e
|
||||
)
|
||||
{
|
||||
std::vector<std::string> v;
|
||||
|
||||
auto columns = Columns();
|
||||
|
||||
v.push_back(columns[1] + " = " + std::to_string(e.buyer_id));
|
||||
v.push_back(columns[2] + " = " + std::to_string(e.char_id));
|
||||
v.push_back(columns[3] + " = " + std::to_string(e.buy_slot_id));
|
||||
v.push_back(columns[4] + " = " + std::to_string(e.item_id));
|
||||
v.push_back(columns[5] + " = " + std::to_string(e.item_qty));
|
||||
v.push_back(columns[6] + " = " + std::to_string(e.item_price));
|
||||
v.push_back(columns[7] + " = " + std::to_string(e.item_icon));
|
||||
v.push_back(columns[8] + " = '" + Strings::Escape(e.item_name) + "'");
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"UPDATE {} SET {} WHERE {} = {}",
|
||||
TableName(),
|
||||
Strings::Implode(", ", v),
|
||||
PrimaryKey(),
|
||||
e.id
|
||||
)
|
||||
);
|
||||
|
||||
return (results.Success() ? results.RowsAffected() : 0);
|
||||
}
|
||||
|
||||
static BuyerBuyLines InsertOne(
|
||||
Database& db,
|
||||
BuyerBuyLines e
|
||||
)
|
||||
{
|
||||
std::vector<std::string> v;
|
||||
|
||||
v.push_back(std::to_string(e.id));
|
||||
v.push_back(std::to_string(e.buyer_id));
|
||||
v.push_back(std::to_string(e.char_id));
|
||||
v.push_back(std::to_string(e.buy_slot_id));
|
||||
v.push_back(std::to_string(e.item_id));
|
||||
v.push_back(std::to_string(e.item_qty));
|
||||
v.push_back(std::to_string(e.item_price));
|
||||
v.push_back(std::to_string(e.item_icon));
|
||||
v.push_back("'" + Strings::Escape(e.item_name) + "'");
|
||||
|
||||
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<BuyerBuyLines> &entries
|
||||
)
|
||||
{
|
||||
std::vector<std::string> insert_chunks;
|
||||
|
||||
for (auto &e: entries) {
|
||||
std::vector<std::string> v;
|
||||
|
||||
v.push_back(std::to_string(e.id));
|
||||
v.push_back(std::to_string(e.buyer_id));
|
||||
v.push_back(std::to_string(e.char_id));
|
||||
v.push_back(std::to_string(e.buy_slot_id));
|
||||
v.push_back(std::to_string(e.item_id));
|
||||
v.push_back(std::to_string(e.item_qty));
|
||||
v.push_back(std::to_string(e.item_price));
|
||||
v.push_back(std::to_string(e.item_icon));
|
||||
v.push_back("'" + Strings::Escape(e.item_name) + "'");
|
||||
|
||||
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
|
||||
}
|
||||
|
||||
std::vector<std::string> v;
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"{} VALUES {}",
|
||||
BaseInsert(),
|
||||
Strings::Implode(",", insert_chunks)
|
||||
)
|
||||
);
|
||||
|
||||
return (results.Success() ? results.RowsAffected() : 0);
|
||||
}
|
||||
|
||||
static std::vector<BuyerBuyLines> All(Database& db)
|
||||
{
|
||||
std::vector<BuyerBuyLines> all_entries;
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"{}",
|
||||
BaseSelect()
|
||||
)
|
||||
);
|
||||
|
||||
all_entries.reserve(results.RowCount());
|
||||
|
||||
for (auto row = results.begin(); row != results.end(); ++row) {
|
||||
BuyerBuyLines e{};
|
||||
|
||||
e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0;
|
||||
e.buyer_id = row[1] ? strtoull(row[1], nullptr, 10) : 0;
|
||||
e.char_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
|
||||
e.buy_slot_id = row[3] ? static_cast<int32_t>(atoi(row[3])) : 0;
|
||||
e.item_id = row[4] ? static_cast<int32_t>(atoi(row[4])) : 0;
|
||||
e.item_qty = row[5] ? static_cast<int32_t>(atoi(row[5])) : 0;
|
||||
e.item_price = row[6] ? static_cast<int32_t>(atoi(row[6])) : 0;
|
||||
e.item_icon = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
|
||||
e.item_name = row[8] ? row[8] : "";
|
||||
|
||||
all_entries.push_back(e);
|
||||
}
|
||||
|
||||
return all_entries;
|
||||
}
|
||||
|
||||
static std::vector<BuyerBuyLines> GetWhere(Database& db, const std::string &where_filter)
|
||||
{
|
||||
std::vector<BuyerBuyLines> 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) {
|
||||
BuyerBuyLines e{};
|
||||
|
||||
e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0;
|
||||
e.buyer_id = row[1] ? strtoull(row[1], nullptr, 10) : 0;
|
||||
e.char_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
|
||||
e.buy_slot_id = row[3] ? static_cast<int32_t>(atoi(row[3])) : 0;
|
||||
e.item_id = row[4] ? static_cast<int32_t>(atoi(row[4])) : 0;
|
||||
e.item_qty = row[5] ? static_cast<int32_t>(atoi(row[5])) : 0;
|
||||
e.item_price = row[6] ? static_cast<int32_t>(atoi(row[6])) : 0;
|
||||
e.item_icon = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
|
||||
e.item_name = row[8] ? row[8] : "";
|
||||
|
||||
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 BuyerBuyLines &e
|
||||
)
|
||||
{
|
||||
std::vector<std::string> v;
|
||||
|
||||
v.push_back(std::to_string(e.id));
|
||||
v.push_back(std::to_string(e.buyer_id));
|
||||
v.push_back(std::to_string(e.char_id));
|
||||
v.push_back(std::to_string(e.buy_slot_id));
|
||||
v.push_back(std::to_string(e.item_id));
|
||||
v.push_back(std::to_string(e.item_qty));
|
||||
v.push_back(std::to_string(e.item_price));
|
||||
v.push_back(std::to_string(e.item_icon));
|
||||
v.push_back("'" + Strings::Escape(e.item_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<BuyerBuyLines> &entries
|
||||
)
|
||||
{
|
||||
std::vector<std::string> insert_chunks;
|
||||
|
||||
for (auto &e: entries) {
|
||||
std::vector<std::string> v;
|
||||
|
||||
v.push_back(std::to_string(e.id));
|
||||
v.push_back(std::to_string(e.buyer_id));
|
||||
v.push_back(std::to_string(e.char_id));
|
||||
v.push_back(std::to_string(e.buy_slot_id));
|
||||
v.push_back(std::to_string(e.item_id));
|
||||
v.push_back(std::to_string(e.item_qty));
|
||||
v.push_back(std::to_string(e.item_price));
|
||||
v.push_back(std::to_string(e.item_icon));
|
||||
v.push_back("'" + Strings::Escape(e.item_name) + "'");
|
||||
|
||||
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
|
||||
}
|
||||
|
||||
std::vector<std::string> v;
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"{} VALUES {}",
|
||||
BaseReplace(),
|
||||
Strings::Implode(",", insert_chunks)
|
||||
)
|
||||
);
|
||||
|
||||
return (results.Success() ? results.RowsAffected() : 0);
|
||||
}
|
||||
};
|
||||
|
||||
#endif //EQEMU_BASE_BUYER_BUY_LINES_REPOSITORY_H
|
||||
@@ -19,40 +19,46 @@
|
||||
class BaseBuyerRepository {
|
||||
public:
|
||||
struct Buyer {
|
||||
int32_t charid;
|
||||
int32_t buyslot;
|
||||
int32_t itemid;
|
||||
std::string itemname;
|
||||
int32_t quantity;
|
||||
int32_t price;
|
||||
uint64_t id;
|
||||
uint32_t char_id;
|
||||
uint32_t char_entity_id;
|
||||
std::string char_name;
|
||||
uint32_t char_zone_id;
|
||||
uint32_t char_zone_instance_id;
|
||||
time_t transaction_date;
|
||||
std::string welcome_message;
|
||||
};
|
||||
|
||||
static std::string PrimaryKey()
|
||||
{
|
||||
return std::string("charid");
|
||||
return std::string("id");
|
||||
}
|
||||
|
||||
static std::vector<std::string> Columns()
|
||||
{
|
||||
return {
|
||||
"charid",
|
||||
"buyslot",
|
||||
"itemid",
|
||||
"itemname",
|
||||
"quantity",
|
||||
"price",
|
||||
"id",
|
||||
"char_id",
|
||||
"char_entity_id",
|
||||
"char_name",
|
||||
"char_zone_id",
|
||||
"char_zone_instance_id",
|
||||
"transaction_date",
|
||||
"welcome_message",
|
||||
};
|
||||
}
|
||||
|
||||
static std::vector<std::string> SelectColumns()
|
||||
{
|
||||
return {
|
||||
"charid",
|
||||
"buyslot",
|
||||
"itemid",
|
||||
"itemname",
|
||||
"quantity",
|
||||
"price",
|
||||
"id",
|
||||
"char_id",
|
||||
"char_entity_id",
|
||||
"char_name",
|
||||
"char_zone_id",
|
||||
"char_zone_instance_id",
|
||||
"UNIX_TIMESTAMP(transaction_date)",
|
||||
"welcome_message",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -93,12 +99,14 @@ public:
|
||||
{
|
||||
Buyer e{};
|
||||
|
||||
e.charid = 0;
|
||||
e.buyslot = 0;
|
||||
e.itemid = 0;
|
||||
e.itemname = "";
|
||||
e.quantity = 0;
|
||||
e.price = 0;
|
||||
e.id = 0;
|
||||
e.char_id = 0;
|
||||
e.char_entity_id = 0;
|
||||
e.char_name = "";
|
||||
e.char_zone_id = 0;
|
||||
e.char_zone_instance_id = 0;
|
||||
e.transaction_date = 0;
|
||||
e.welcome_message = "";
|
||||
|
||||
return e;
|
||||
}
|
||||
@@ -109,7 +117,7 @@ public:
|
||||
)
|
||||
{
|
||||
for (auto &buyer : buyers) {
|
||||
if (buyer.charid == buyer_id) {
|
||||
if (buyer.id == buyer_id) {
|
||||
return buyer;
|
||||
}
|
||||
}
|
||||
@@ -135,12 +143,14 @@ public:
|
||||
if (results.RowCount() == 1) {
|
||||
Buyer e{};
|
||||
|
||||
e.charid = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
|
||||
e.buyslot = row[1] ? static_cast<int32_t>(atoi(row[1])) : 0;
|
||||
e.itemid = row[2] ? static_cast<int32_t>(atoi(row[2])) : 0;
|
||||
e.itemname = row[3] ? row[3] : "";
|
||||
e.quantity = row[4] ? static_cast<int32_t>(atoi(row[4])) : 0;
|
||||
e.price = row[5] ? static_cast<int32_t>(atoi(row[5])) : 0;
|
||||
e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0;
|
||||
e.char_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
|
||||
e.char_entity_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
|
||||
e.char_name = row[3] ? row[3] : "";
|
||||
e.char_zone_id = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
|
||||
e.char_zone_instance_id = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
|
||||
e.transaction_date = strtoll(row[6] ? row[6] : "-1", nullptr, 10);
|
||||
e.welcome_message = row[7] ? row[7] : "";
|
||||
|
||||
return e;
|
||||
}
|
||||
@@ -174,12 +184,13 @@ public:
|
||||
|
||||
auto columns = Columns();
|
||||
|
||||
v.push_back(columns[0] + " = " + std::to_string(e.charid));
|
||||
v.push_back(columns[1] + " = " + std::to_string(e.buyslot));
|
||||
v.push_back(columns[2] + " = " + std::to_string(e.itemid));
|
||||
v.push_back(columns[3] + " = '" + Strings::Escape(e.itemname) + "'");
|
||||
v.push_back(columns[4] + " = " + std::to_string(e.quantity));
|
||||
v.push_back(columns[5] + " = " + std::to_string(e.price));
|
||||
v.push_back(columns[1] + " = " + std::to_string(e.char_id));
|
||||
v.push_back(columns[2] + " = " + std::to_string(e.char_entity_id));
|
||||
v.push_back(columns[3] + " = '" + Strings::Escape(e.char_name) + "'");
|
||||
v.push_back(columns[4] + " = " + std::to_string(e.char_zone_id));
|
||||
v.push_back(columns[5] + " = " + std::to_string(e.char_zone_instance_id));
|
||||
v.push_back(columns[6] + " = FROM_UNIXTIME(" + (e.transaction_date > 0 ? std::to_string(e.transaction_date) : "null") + ")");
|
||||
v.push_back(columns[7] + " = '" + Strings::Escape(e.welcome_message) + "'");
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
@@ -187,7 +198,7 @@ public:
|
||||
TableName(),
|
||||
Strings::Implode(", ", v),
|
||||
PrimaryKey(),
|
||||
e.charid
|
||||
e.id
|
||||
)
|
||||
);
|
||||
|
||||
@@ -201,12 +212,14 @@ public:
|
||||
{
|
||||
std::vector<std::string> v;
|
||||
|
||||
v.push_back(std::to_string(e.charid));
|
||||
v.push_back(std::to_string(e.buyslot));
|
||||
v.push_back(std::to_string(e.itemid));
|
||||
v.push_back("'" + Strings::Escape(e.itemname) + "'");
|
||||
v.push_back(std::to_string(e.quantity));
|
||||
v.push_back(std::to_string(e.price));
|
||||
v.push_back(std::to_string(e.id));
|
||||
v.push_back(std::to_string(e.char_id));
|
||||
v.push_back(std::to_string(e.char_entity_id));
|
||||
v.push_back("'" + Strings::Escape(e.char_name) + "'");
|
||||
v.push_back(std::to_string(e.char_zone_id));
|
||||
v.push_back(std::to_string(e.char_zone_instance_id));
|
||||
v.push_back("FROM_UNIXTIME(" + (e.transaction_date > 0 ? std::to_string(e.transaction_date) : "null") + ")");
|
||||
v.push_back("'" + Strings::Escape(e.welcome_message) + "'");
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
@@ -217,7 +230,7 @@ public:
|
||||
);
|
||||
|
||||
if (results.Success()) {
|
||||
e.charid = results.LastInsertedID();
|
||||
e.id = results.LastInsertedID();
|
||||
return e;
|
||||
}
|
||||
|
||||
@@ -236,12 +249,14 @@ public:
|
||||
for (auto &e: entries) {
|
||||
std::vector<std::string> v;
|
||||
|
||||
v.push_back(std::to_string(e.charid));
|
||||
v.push_back(std::to_string(e.buyslot));
|
||||
v.push_back(std::to_string(e.itemid));
|
||||
v.push_back("'" + Strings::Escape(e.itemname) + "'");
|
||||
v.push_back(std::to_string(e.quantity));
|
||||
v.push_back(std::to_string(e.price));
|
||||
v.push_back(std::to_string(e.id));
|
||||
v.push_back(std::to_string(e.char_id));
|
||||
v.push_back(std::to_string(e.char_entity_id));
|
||||
v.push_back("'" + Strings::Escape(e.char_name) + "'");
|
||||
v.push_back(std::to_string(e.char_zone_id));
|
||||
v.push_back(std::to_string(e.char_zone_instance_id));
|
||||
v.push_back("FROM_UNIXTIME(" + (e.transaction_date > 0 ? std::to_string(e.transaction_date) : "null") + ")");
|
||||
v.push_back("'" + Strings::Escape(e.welcome_message) + "'");
|
||||
|
||||
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
|
||||
}
|
||||
@@ -275,12 +290,14 @@ public:
|
||||
for (auto row = results.begin(); row != results.end(); ++row) {
|
||||
Buyer e{};
|
||||
|
||||
e.charid = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
|
||||
e.buyslot = row[1] ? static_cast<int32_t>(atoi(row[1])) : 0;
|
||||
e.itemid = row[2] ? static_cast<int32_t>(atoi(row[2])) : 0;
|
||||
e.itemname = row[3] ? row[3] : "";
|
||||
e.quantity = row[4] ? static_cast<int32_t>(atoi(row[4])) : 0;
|
||||
e.price = row[5] ? static_cast<int32_t>(atoi(row[5])) : 0;
|
||||
e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0;
|
||||
e.char_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
|
||||
e.char_entity_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
|
||||
e.char_name = row[3] ? row[3] : "";
|
||||
e.char_zone_id = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
|
||||
e.char_zone_instance_id = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
|
||||
e.transaction_date = strtoll(row[6] ? row[6] : "-1", nullptr, 10);
|
||||
e.welcome_message = row[7] ? row[7] : "";
|
||||
|
||||
all_entries.push_back(e);
|
||||
}
|
||||
@@ -305,12 +322,14 @@ public:
|
||||
for (auto row = results.begin(); row != results.end(); ++row) {
|
||||
Buyer e{};
|
||||
|
||||
e.charid = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
|
||||
e.buyslot = row[1] ? static_cast<int32_t>(atoi(row[1])) : 0;
|
||||
e.itemid = row[2] ? static_cast<int32_t>(atoi(row[2])) : 0;
|
||||
e.itemname = row[3] ? row[3] : "";
|
||||
e.quantity = row[4] ? static_cast<int32_t>(atoi(row[4])) : 0;
|
||||
e.price = row[5] ? static_cast<int32_t>(atoi(row[5])) : 0;
|
||||
e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0;
|
||||
e.char_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
|
||||
e.char_entity_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
|
||||
e.char_name = row[3] ? row[3] : "";
|
||||
e.char_zone_id = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
|
||||
e.char_zone_instance_id = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
|
||||
e.transaction_date = strtoll(row[6] ? row[6] : "-1", nullptr, 10);
|
||||
e.welcome_message = row[7] ? row[7] : "";
|
||||
|
||||
all_entries.push_back(e);
|
||||
}
|
||||
@@ -385,12 +404,14 @@ public:
|
||||
{
|
||||
std::vector<std::string> v;
|
||||
|
||||
v.push_back(std::to_string(e.charid));
|
||||
v.push_back(std::to_string(e.buyslot));
|
||||
v.push_back(std::to_string(e.itemid));
|
||||
v.push_back("'" + Strings::Escape(e.itemname) + "'");
|
||||
v.push_back(std::to_string(e.quantity));
|
||||
v.push_back(std::to_string(e.price));
|
||||
v.push_back(std::to_string(e.id));
|
||||
v.push_back(std::to_string(e.char_id));
|
||||
v.push_back(std::to_string(e.char_entity_id));
|
||||
v.push_back("'" + Strings::Escape(e.char_name) + "'");
|
||||
v.push_back(std::to_string(e.char_zone_id));
|
||||
v.push_back(std::to_string(e.char_zone_instance_id));
|
||||
v.push_back("FROM_UNIXTIME(" + (e.transaction_date > 0 ? std::to_string(e.transaction_date) : "null") + ")");
|
||||
v.push_back("'" + Strings::Escape(e.welcome_message) + "'");
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
@@ -413,12 +434,14 @@ public:
|
||||
for (auto &e: entries) {
|
||||
std::vector<std::string> v;
|
||||
|
||||
v.push_back(std::to_string(e.charid));
|
||||
v.push_back(std::to_string(e.buyslot));
|
||||
v.push_back(std::to_string(e.itemid));
|
||||
v.push_back("'" + Strings::Escape(e.itemname) + "'");
|
||||
v.push_back(std::to_string(e.quantity));
|
||||
v.push_back(std::to_string(e.price));
|
||||
v.push_back(std::to_string(e.id));
|
||||
v.push_back(std::to_string(e.char_id));
|
||||
v.push_back(std::to_string(e.char_entity_id));
|
||||
v.push_back("'" + Strings::Escape(e.char_name) + "'");
|
||||
v.push_back(std::to_string(e.char_zone_id));
|
||||
v.push_back(std::to_string(e.char_zone_instance_id));
|
||||
v.push_back("FROM_UNIXTIME(" + (e.transaction_date > 0 ? std::to_string(e.transaction_date) : "null") + ")");
|
||||
v.push_back("'" + Strings::Escape(e.welcome_message) + "'");
|
||||
|
||||
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
|
||||
}
|
||||
|
||||
@@ -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_BUYER_TRADE_ITEMS_REPOSITORY_H
|
||||
#define EQEMU_BASE_BUYER_TRADE_ITEMS_REPOSITORY_H
|
||||
|
||||
#include "../../database.h"
|
||||
#include "../../strings.h"
|
||||
#include <ctime>
|
||||
|
||||
class BaseBuyerTradeItemsRepository {
|
||||
public:
|
||||
struct BuyerTradeItems {
|
||||
uint64_t id;
|
||||
uint64_t buyer_buy_lines_id;
|
||||
int32_t item_id;
|
||||
int32_t item_qty;
|
||||
int32_t item_icon;
|
||||
std::string item_name;
|
||||
};
|
||||
|
||||
static std::string PrimaryKey()
|
||||
{
|
||||
return std::string("id");
|
||||
}
|
||||
|
||||
static std::vector<std::string> Columns()
|
||||
{
|
||||
return {
|
||||
"id",
|
||||
"buyer_buy_lines_id",
|
||||
"item_id",
|
||||
"item_qty",
|
||||
"item_icon",
|
||||
"item_name",
|
||||
};
|
||||
}
|
||||
|
||||
static std::vector<std::string> SelectColumns()
|
||||
{
|
||||
return {
|
||||
"id",
|
||||
"buyer_buy_lines_id",
|
||||
"item_id",
|
||||
"item_qty",
|
||||
"item_icon",
|
||||
"item_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("buyer_trade_items");
|
||||
}
|
||||
|
||||
static std::string BaseSelect()
|
||||
{
|
||||
return fmt::format(
|
||||
"SELECT {} FROM {}",
|
||||
SelectColumnsRaw(),
|
||||
TableName()
|
||||
);
|
||||
}
|
||||
|
||||
static std::string BaseInsert()
|
||||
{
|
||||
return fmt::format(
|
||||
"INSERT INTO {} ({}) ",
|
||||
TableName(),
|
||||
ColumnsRaw()
|
||||
);
|
||||
}
|
||||
|
||||
static BuyerTradeItems NewEntity()
|
||||
{
|
||||
BuyerTradeItems e{};
|
||||
|
||||
e.id = 0;
|
||||
e.buyer_buy_lines_id = 0;
|
||||
e.item_id = 0;
|
||||
e.item_qty = 0;
|
||||
e.item_icon = 0;
|
||||
e.item_name = "0";
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
static BuyerTradeItems GetBuyerTradeItems(
|
||||
const std::vector<BuyerTradeItems> &buyer_trade_itemss,
|
||||
int buyer_trade_items_id
|
||||
)
|
||||
{
|
||||
for (auto &buyer_trade_items : buyer_trade_itemss) {
|
||||
if (buyer_trade_items.id == buyer_trade_items_id) {
|
||||
return buyer_trade_items;
|
||||
}
|
||||
}
|
||||
|
||||
return NewEntity();
|
||||
}
|
||||
|
||||
static BuyerTradeItems FindOne(
|
||||
Database& db,
|
||||
int buyer_trade_items_id
|
||||
)
|
||||
{
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"{} WHERE {} = {} LIMIT 1",
|
||||
BaseSelect(),
|
||||
PrimaryKey(),
|
||||
buyer_trade_items_id
|
||||
)
|
||||
);
|
||||
|
||||
auto row = results.begin();
|
||||
if (results.RowCount() == 1) {
|
||||
BuyerTradeItems e{};
|
||||
|
||||
e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0;
|
||||
e.buyer_buy_lines_id = row[1] ? strtoull(row[1], nullptr, 10) : 0;
|
||||
e.item_id = row[2] ? static_cast<int32_t>(atoi(row[2])) : 0;
|
||||
e.item_qty = row[3] ? static_cast<int32_t>(atoi(row[3])) : 0;
|
||||
e.item_icon = row[4] ? static_cast<int32_t>(atoi(row[4])) : 0;
|
||||
e.item_name = row[5] ? row[5] : "0";
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
return NewEntity();
|
||||
}
|
||||
|
||||
static int DeleteOne(
|
||||
Database& db,
|
||||
int buyer_trade_items_id
|
||||
)
|
||||
{
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"DELETE FROM {} WHERE {} = {}",
|
||||
TableName(),
|
||||
PrimaryKey(),
|
||||
buyer_trade_items_id
|
||||
)
|
||||
);
|
||||
|
||||
return (results.Success() ? results.RowsAffected() : 0);
|
||||
}
|
||||
|
||||
static int UpdateOne(
|
||||
Database& db,
|
||||
const BuyerTradeItems &e
|
||||
)
|
||||
{
|
||||
std::vector<std::string> v;
|
||||
|
||||
auto columns = Columns();
|
||||
|
||||
v.push_back(columns[1] + " = " + std::to_string(e.buyer_buy_lines_id));
|
||||
v.push_back(columns[2] + " = " + std::to_string(e.item_id));
|
||||
v.push_back(columns[3] + " = " + std::to_string(e.item_qty));
|
||||
v.push_back(columns[4] + " = " + std::to_string(e.item_icon));
|
||||
v.push_back(columns[5] + " = '" + Strings::Escape(e.item_name) + "'");
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"UPDATE {} SET {} WHERE {} = {}",
|
||||
TableName(),
|
||||
Strings::Implode(", ", v),
|
||||
PrimaryKey(),
|
||||
e.id
|
||||
)
|
||||
);
|
||||
|
||||
return (results.Success() ? results.RowsAffected() : 0);
|
||||
}
|
||||
|
||||
static BuyerTradeItems InsertOne(
|
||||
Database& db,
|
||||
BuyerTradeItems e
|
||||
)
|
||||
{
|
||||
std::vector<std::string> v;
|
||||
|
||||
v.push_back(std::to_string(e.id));
|
||||
v.push_back(std::to_string(e.buyer_buy_lines_id));
|
||||
v.push_back(std::to_string(e.item_id));
|
||||
v.push_back(std::to_string(e.item_qty));
|
||||
v.push_back(std::to_string(e.item_icon));
|
||||
v.push_back("'" + Strings::Escape(e.item_name) + "'");
|
||||
|
||||
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<BuyerTradeItems> &entries
|
||||
)
|
||||
{
|
||||
std::vector<std::string> insert_chunks;
|
||||
|
||||
for (auto &e: entries) {
|
||||
std::vector<std::string> v;
|
||||
|
||||
v.push_back(std::to_string(e.id));
|
||||
v.push_back(std::to_string(e.buyer_buy_lines_id));
|
||||
v.push_back(std::to_string(e.item_id));
|
||||
v.push_back(std::to_string(e.item_qty));
|
||||
v.push_back(std::to_string(e.item_icon));
|
||||
v.push_back("'" + Strings::Escape(e.item_name) + "'");
|
||||
|
||||
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
|
||||
}
|
||||
|
||||
std::vector<std::string> v;
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"{} VALUES {}",
|
||||
BaseInsert(),
|
||||
Strings::Implode(",", insert_chunks)
|
||||
)
|
||||
);
|
||||
|
||||
return (results.Success() ? results.RowsAffected() : 0);
|
||||
}
|
||||
|
||||
static std::vector<BuyerTradeItems> All(Database& db)
|
||||
{
|
||||
std::vector<BuyerTradeItems> all_entries;
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"{}",
|
||||
BaseSelect()
|
||||
)
|
||||
);
|
||||
|
||||
all_entries.reserve(results.RowCount());
|
||||
|
||||
for (auto row = results.begin(); row != results.end(); ++row) {
|
||||
BuyerTradeItems e{};
|
||||
|
||||
e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0;
|
||||
e.buyer_buy_lines_id = row[1] ? strtoull(row[1], nullptr, 10) : 0;
|
||||
e.item_id = row[2] ? static_cast<int32_t>(atoi(row[2])) : 0;
|
||||
e.item_qty = row[3] ? static_cast<int32_t>(atoi(row[3])) : 0;
|
||||
e.item_icon = row[4] ? static_cast<int32_t>(atoi(row[4])) : 0;
|
||||
e.item_name = row[5] ? row[5] : "0";
|
||||
|
||||
all_entries.push_back(e);
|
||||
}
|
||||
|
||||
return all_entries;
|
||||
}
|
||||
|
||||
static std::vector<BuyerTradeItems> GetWhere(Database& db, const std::string &where_filter)
|
||||
{
|
||||
std::vector<BuyerTradeItems> 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) {
|
||||
BuyerTradeItems e{};
|
||||
|
||||
e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0;
|
||||
e.buyer_buy_lines_id = row[1] ? strtoull(row[1], nullptr, 10) : 0;
|
||||
e.item_id = row[2] ? static_cast<int32_t>(atoi(row[2])) : 0;
|
||||
e.item_qty = row[3] ? static_cast<int32_t>(atoi(row[3])) : 0;
|
||||
e.item_icon = row[4] ? static_cast<int32_t>(atoi(row[4])) : 0;
|
||||
e.item_name = row[5] ? row[5] : "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 BuyerTradeItems &e
|
||||
)
|
||||
{
|
||||
std::vector<std::string> v;
|
||||
|
||||
v.push_back(std::to_string(e.id));
|
||||
v.push_back(std::to_string(e.buyer_buy_lines_id));
|
||||
v.push_back(std::to_string(e.item_id));
|
||||
v.push_back(std::to_string(e.item_qty));
|
||||
v.push_back(std::to_string(e.item_icon));
|
||||
v.push_back("'" + Strings::Escape(e.item_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<BuyerTradeItems> &entries
|
||||
)
|
||||
{
|
||||
std::vector<std::string> insert_chunks;
|
||||
|
||||
for (auto &e: entries) {
|
||||
std::vector<std::string> v;
|
||||
|
||||
v.push_back(std::to_string(e.id));
|
||||
v.push_back(std::to_string(e.buyer_buy_lines_id));
|
||||
v.push_back(std::to_string(e.item_id));
|
||||
v.push_back(std::to_string(e.item_qty));
|
||||
v.push_back(std::to_string(e.item_icon));
|
||||
v.push_back("'" + Strings::Escape(e.item_name) + "'");
|
||||
|
||||
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
|
||||
}
|
||||
|
||||
std::vector<std::string> v;
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"{} VALUES {}",
|
||||
BaseReplace(),
|
||||
Strings::Implode(",", insert_chunks)
|
||||
)
|
||||
);
|
||||
|
||||
return (results.Success() ? results.RowsAffected() : 0);
|
||||
}
|
||||
};
|
||||
|
||||
#endif //EQEMU_BASE_BUYER_TRADE_ITEMS_REPOSITORY_H
|
||||
@@ -35,6 +35,7 @@ public:
|
||||
uint32_t ornamenticon;
|
||||
uint32_t ornamentidfile;
|
||||
int32_t ornament_hero_model;
|
||||
uint64_t guid;
|
||||
};
|
||||
|
||||
static std::string PrimaryKey()
|
||||
@@ -61,6 +62,7 @@ public:
|
||||
"ornamenticon",
|
||||
"ornamentidfile",
|
||||
"ornament_hero_model",
|
||||
"guid",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -83,6 +85,7 @@ public:
|
||||
"ornamenticon",
|
||||
"ornamentidfile",
|
||||
"ornament_hero_model",
|
||||
"guid",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -139,6 +142,7 @@ public:
|
||||
e.ornamenticon = 0;
|
||||
e.ornamentidfile = 0;
|
||||
e.ornament_hero_model = 0;
|
||||
e.guid = 0;
|
||||
|
||||
return e;
|
||||
}
|
||||
@@ -191,6 +195,7 @@ public:
|
||||
e.ornamenticon = row[13] ? static_cast<uint32_t>(strtoul(row[13], nullptr, 10)) : 0;
|
||||
e.ornamentidfile = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
|
||||
e.ornament_hero_model = row[15] ? static_cast<int32_t>(atoi(row[15])) : 0;
|
||||
e.guid = row[16] ? strtoull(row[16], nullptr, 10) : 0;
|
||||
|
||||
return e;
|
||||
}
|
||||
@@ -240,6 +245,7 @@ public:
|
||||
v.push_back(columns[13] + " = " + std::to_string(e.ornamenticon));
|
||||
v.push_back(columns[14] + " = " + std::to_string(e.ornamentidfile));
|
||||
v.push_back(columns[15] + " = " + std::to_string(e.ornament_hero_model));
|
||||
v.push_back(columns[16] + " = " + std::to_string(e.guid));
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
@@ -277,6 +283,7 @@ public:
|
||||
v.push_back(std::to_string(e.ornamenticon));
|
||||
v.push_back(std::to_string(e.ornamentidfile));
|
||||
v.push_back(std::to_string(e.ornament_hero_model));
|
||||
v.push_back(std::to_string(e.guid));
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
@@ -322,6 +329,7 @@ public:
|
||||
v.push_back(std::to_string(e.ornamenticon));
|
||||
v.push_back(std::to_string(e.ornamentidfile));
|
||||
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) + ")");
|
||||
}
|
||||
@@ -371,6 +379,7 @@ public:
|
||||
e.ornamenticon = row[13] ? static_cast<uint32_t>(strtoul(row[13], nullptr, 10)) : 0;
|
||||
e.ornamentidfile = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
|
||||
e.ornament_hero_model = row[15] ? static_cast<int32_t>(atoi(row[15])) : 0;
|
||||
e.guid = row[16] ? strtoull(row[16], nullptr, 10) : 0;
|
||||
|
||||
all_entries.push_back(e);
|
||||
}
|
||||
@@ -411,6 +420,7 @@ public:
|
||||
e.ornamenticon = row[13] ? static_cast<uint32_t>(strtoul(row[13], nullptr, 10)) : 0;
|
||||
e.ornamentidfile = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
|
||||
e.ornament_hero_model = row[15] ? static_cast<int32_t>(atoi(row[15])) : 0;
|
||||
e.guid = row[16] ? strtoull(row[16], nullptr, 10) : 0;
|
||||
|
||||
all_entries.push_back(e);
|
||||
}
|
||||
@@ -501,6 +511,7 @@ public:
|
||||
v.push_back(std::to_string(e.ornamenticon));
|
||||
v.push_back(std::to_string(e.ornamentidfile));
|
||||
v.push_back(std::to_string(e.ornament_hero_model));
|
||||
v.push_back(std::to_string(e.guid));
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
@@ -539,6 +550,7 @@ public:
|
||||
v.push_back(std::to_string(e.ornamenticon));
|
||||
v.push_back(std::to_string(e.ornamentidfile));
|
||||
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) + ")");
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ public:
|
||||
uint32_t ornamenticon;
|
||||
uint32_t ornamentidfile;
|
||||
int32_t ornament_hero_model;
|
||||
uint64_t guid;
|
||||
};
|
||||
|
||||
static std::string PrimaryKey()
|
||||
@@ -63,6 +64,7 @@ public:
|
||||
"ornamenticon",
|
||||
"ornamentidfile",
|
||||
"ornament_hero_model",
|
||||
"guid",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -86,6 +88,7 @@ public:
|
||||
"ornamenticon",
|
||||
"ornamentidfile",
|
||||
"ornament_hero_model",
|
||||
"guid",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -143,6 +146,7 @@ public:
|
||||
e.ornamenticon = 0;
|
||||
e.ornamentidfile = 0;
|
||||
e.ornament_hero_model = 0;
|
||||
e.guid = 0;
|
||||
|
||||
return e;
|
||||
}
|
||||
@@ -196,6 +200,7 @@ public:
|
||||
e.ornamenticon = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
|
||||
e.ornamentidfile = row[15] ? static_cast<uint32_t>(strtoul(row[15], nullptr, 10)) : 0;
|
||||
e.ornament_hero_model = row[16] ? static_cast<int32_t>(atoi(row[16])) : 0;
|
||||
e.guid = row[17] ? strtoull(row[17], nullptr, 10) : 0;
|
||||
|
||||
return e;
|
||||
}
|
||||
@@ -246,6 +251,7 @@ public:
|
||||
v.push_back(columns[14] + " = " + std::to_string(e.ornamenticon));
|
||||
v.push_back(columns[15] + " = " + std::to_string(e.ornamentidfile));
|
||||
v.push_back(columns[16] + " = " + std::to_string(e.ornament_hero_model));
|
||||
v.push_back(columns[17] + " = " + std::to_string(e.guid));
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
@@ -284,6 +290,7 @@ public:
|
||||
v.push_back(std::to_string(e.ornamenticon));
|
||||
v.push_back(std::to_string(e.ornamentidfile));
|
||||
v.push_back(std::to_string(e.ornament_hero_model));
|
||||
v.push_back(std::to_string(e.guid));
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
@@ -330,6 +337,7 @@ public:
|
||||
v.push_back(std::to_string(e.ornamenticon));
|
||||
v.push_back(std::to_string(e.ornamentidfile));
|
||||
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) + ")");
|
||||
}
|
||||
@@ -380,6 +388,7 @@ public:
|
||||
e.ornamenticon = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
|
||||
e.ornamentidfile = row[15] ? static_cast<uint32_t>(strtoul(row[15], nullptr, 10)) : 0;
|
||||
e.ornament_hero_model = row[16] ? static_cast<int32_t>(atoi(row[16])) : 0;
|
||||
e.guid = row[17] ? strtoull(row[17], nullptr, 10) : 0;
|
||||
|
||||
all_entries.push_back(e);
|
||||
}
|
||||
@@ -421,6 +430,7 @@ public:
|
||||
e.ornamenticon = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
|
||||
e.ornamentidfile = row[15] ? static_cast<uint32_t>(strtoul(row[15], nullptr, 10)) : 0;
|
||||
e.ornament_hero_model = row[16] ? static_cast<int32_t>(atoi(row[16])) : 0;
|
||||
e.guid = row[17] ? strtoull(row[17], nullptr, 10) : 0;
|
||||
|
||||
all_entries.push_back(e);
|
||||
}
|
||||
@@ -512,6 +522,7 @@ public:
|
||||
v.push_back(std::to_string(e.ornamenticon));
|
||||
v.push_back(std::to_string(e.ornamentidfile));
|
||||
v.push_back(std::to_string(e.ornament_hero_model));
|
||||
v.push_back(std::to_string(e.guid));
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
@@ -551,6 +562,7 @@ public:
|
||||
v.push_back(std::to_string(e.ornamenticon));
|
||||
v.push_back(std::to_string(e.ornamentidfile));
|
||||
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) + ")");
|
||||
}
|
||||
|
||||
@@ -19,31 +19,34 @@
|
||||
class BaseSpellBucketsRepository {
|
||||
public:
|
||||
struct SpellBuckets {
|
||||
uint64_t spellid;
|
||||
std::string key_;
|
||||
std::string value;
|
||||
uint32_t spell_id;
|
||||
std::string bucket_name;
|
||||
std::string bucket_value;
|
||||
uint8_t bucket_comparison;
|
||||
};
|
||||
|
||||
static std::string PrimaryKey()
|
||||
{
|
||||
return std::string("spellid");
|
||||
return std::string("spell_id");
|
||||
}
|
||||
|
||||
static std::vector<std::string> Columns()
|
||||
{
|
||||
return {
|
||||
"spellid",
|
||||
"`key`",
|
||||
"value",
|
||||
"spell_id",
|
||||
"bucket_name",
|
||||
"bucket_value",
|
||||
"bucket_comparison",
|
||||
};
|
||||
}
|
||||
|
||||
static std::vector<std::string> SelectColumns()
|
||||
{
|
||||
return {
|
||||
"spellid",
|
||||
"`key`",
|
||||
"value",
|
||||
"spell_id",
|
||||
"bucket_name",
|
||||
"bucket_value",
|
||||
"bucket_comparison",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -84,9 +87,10 @@ public:
|
||||
{
|
||||
SpellBuckets e{};
|
||||
|
||||
e.spellid = 0;
|
||||
e.key_ = "";
|
||||
e.value = "";
|
||||
e.spell_id = 0;
|
||||
e.bucket_name = "";
|
||||
e.bucket_value = "";
|
||||
e.bucket_comparison = 0;
|
||||
|
||||
return e;
|
||||
}
|
||||
@@ -97,7 +101,7 @@ public:
|
||||
)
|
||||
{
|
||||
for (auto &spell_buckets : spell_bucketss) {
|
||||
if (spell_buckets.spellid == spell_buckets_id) {
|
||||
if (spell_buckets.spell_id == spell_buckets_id) {
|
||||
return spell_buckets;
|
||||
}
|
||||
}
|
||||
@@ -123,9 +127,10 @@ public:
|
||||
if (results.RowCount() == 1) {
|
||||
SpellBuckets e{};
|
||||
|
||||
e.spellid = row[0] ? strtoull(row[0], nullptr, 10) : 0;
|
||||
e.key_ = row[1] ? row[1] : "";
|
||||
e.value = row[2] ? row[2] : "";
|
||||
e.spell_id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
|
||||
e.bucket_name = row[1] ? row[1] : "";
|
||||
e.bucket_value = row[2] ? row[2] : "";
|
||||
e.bucket_comparison = row[3] ? static_cast<uint8_t>(strtoul(row[3], nullptr, 10)) : 0;
|
||||
|
||||
return e;
|
||||
}
|
||||
@@ -159,9 +164,10 @@ public:
|
||||
|
||||
auto columns = Columns();
|
||||
|
||||
v.push_back(columns[0] + " = " + std::to_string(e.spellid));
|
||||
v.push_back(columns[1] + " = '" + Strings::Escape(e.key_) + "'");
|
||||
v.push_back(columns[2] + " = '" + Strings::Escape(e.value) + "'");
|
||||
v.push_back(columns[0] + " = " + std::to_string(e.spell_id));
|
||||
v.push_back(columns[1] + " = '" + Strings::Escape(e.bucket_name) + "'");
|
||||
v.push_back(columns[2] + " = '" + Strings::Escape(e.bucket_value) + "'");
|
||||
v.push_back(columns[3] + " = " + std::to_string(e.bucket_comparison));
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
@@ -169,7 +175,7 @@ public:
|
||||
TableName(),
|
||||
Strings::Implode(", ", v),
|
||||
PrimaryKey(),
|
||||
e.spellid
|
||||
e.spell_id
|
||||
)
|
||||
);
|
||||
|
||||
@@ -183,9 +189,10 @@ public:
|
||||
{
|
||||
std::vector<std::string> v;
|
||||
|
||||
v.push_back(std::to_string(e.spellid));
|
||||
v.push_back("'" + Strings::Escape(e.key_) + "'");
|
||||
v.push_back("'" + Strings::Escape(e.value) + "'");
|
||||
v.push_back(std::to_string(e.spell_id));
|
||||
v.push_back("'" + Strings::Escape(e.bucket_name) + "'");
|
||||
v.push_back("'" + Strings::Escape(e.bucket_value) + "'");
|
||||
v.push_back(std::to_string(e.bucket_comparison));
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
@@ -196,7 +203,7 @@ public:
|
||||
);
|
||||
|
||||
if (results.Success()) {
|
||||
e.spellid = results.LastInsertedID();
|
||||
e.spell_id = results.LastInsertedID();
|
||||
return e;
|
||||
}
|
||||
|
||||
@@ -215,9 +222,10 @@ public:
|
||||
for (auto &e: entries) {
|
||||
std::vector<std::string> v;
|
||||
|
||||
v.push_back(std::to_string(e.spellid));
|
||||
v.push_back("'" + Strings::Escape(e.key_) + "'");
|
||||
v.push_back("'" + Strings::Escape(e.value) + "'");
|
||||
v.push_back(std::to_string(e.spell_id));
|
||||
v.push_back("'" + Strings::Escape(e.bucket_name) + "'");
|
||||
v.push_back("'" + Strings::Escape(e.bucket_value) + "'");
|
||||
v.push_back(std::to_string(e.bucket_comparison));
|
||||
|
||||
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
|
||||
}
|
||||
@@ -251,9 +259,10 @@ public:
|
||||
for (auto row = results.begin(); row != results.end(); ++row) {
|
||||
SpellBuckets e{};
|
||||
|
||||
e.spellid = row[0] ? strtoull(row[0], nullptr, 10) : 0;
|
||||
e.key_ = row[1] ? row[1] : "";
|
||||
e.value = row[2] ? row[2] : "";
|
||||
e.spell_id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
|
||||
e.bucket_name = row[1] ? row[1] : "";
|
||||
e.bucket_value = row[2] ? row[2] : "";
|
||||
e.bucket_comparison = row[3] ? static_cast<uint8_t>(strtoul(row[3], nullptr, 10)) : 0;
|
||||
|
||||
all_entries.push_back(e);
|
||||
}
|
||||
@@ -278,9 +287,10 @@ public:
|
||||
for (auto row = results.begin(); row != results.end(); ++row) {
|
||||
SpellBuckets e{};
|
||||
|
||||
e.spellid = row[0] ? strtoull(row[0], nullptr, 10) : 0;
|
||||
e.key_ = row[1] ? row[1] : "";
|
||||
e.value = row[2] ? row[2] : "";
|
||||
e.spell_id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
|
||||
e.bucket_name = row[1] ? row[1] : "";
|
||||
e.bucket_value = row[2] ? row[2] : "";
|
||||
e.bucket_comparison = row[3] ? static_cast<uint8_t>(strtoul(row[3], nullptr, 10)) : 0;
|
||||
|
||||
all_entries.push_back(e);
|
||||
}
|
||||
@@ -355,9 +365,10 @@ public:
|
||||
{
|
||||
std::vector<std::string> v;
|
||||
|
||||
v.push_back(std::to_string(e.spellid));
|
||||
v.push_back("'" + Strings::Escape(e.key_) + "'");
|
||||
v.push_back("'" + Strings::Escape(e.value) + "'");
|
||||
v.push_back(std::to_string(e.spell_id));
|
||||
v.push_back("'" + Strings::Escape(e.bucket_name) + "'");
|
||||
v.push_back("'" + Strings::Escape(e.bucket_value) + "'");
|
||||
v.push_back(std::to_string(e.bucket_comparison));
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
@@ -380,9 +391,10 @@ public:
|
||||
for (auto &e: entries) {
|
||||
std::vector<std::string> v;
|
||||
|
||||
v.push_back(std::to_string(e.spellid));
|
||||
v.push_back("'" + Strings::Escape(e.key_) + "'");
|
||||
v.push_back("'" + Strings::Escape(e.value) + "'");
|
||||
v.push_back(std::to_string(e.spell_id));
|
||||
v.push_back("'" + Strings::Escape(e.bucket_name) + "'");
|
||||
v.push_back("'" + Strings::Escape(e.bucket_value) + "'");
|
||||
v.push_back(std::to_string(e.bucket_comparison));
|
||||
|
||||
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
|
||||
}
|
||||
|
||||
@@ -44,7 +44,24 @@ public:
|
||||
*/
|
||||
|
||||
// Custom extended repository methods here
|
||||
static std::vector<std::string> GetBaseDataFileLines(Database& db)
|
||||
{
|
||||
std::vector<std::string> lines;
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"SELECT CONCAT_WS('^', {}) FROM {} ORDER BY `level`, `class` ASC",
|
||||
ColumnsRaw(),
|
||||
TableName()
|
||||
)
|
||||
);
|
||||
|
||||
for (auto row : results) {
|
||||
lines.emplace_back(row[0]);
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
};
|
||||
|
||||
#endif //EQEMU_BASE_DATA_REPOSITORY_H
|
||||
|
||||
@@ -0,0 +1,360 @@
|
||||
#ifndef EQEMU_BUYER_BUY_LINES_REPOSITORY_H
|
||||
#define EQEMU_BUYER_BUY_LINES_REPOSITORY_H
|
||||
|
||||
#include "../database.h"
|
||||
#include "../strings.h"
|
||||
#include "base/base_buyer_buy_lines_repository.h"
|
||||
#include "buyer_trade_items_repository.h"
|
||||
#include "character_data_repository.h"
|
||||
#include "buyer_repository.h"
|
||||
|
||||
#include "../eq_packet_structs.h"
|
||||
|
||||
class BuyerBuyLinesRepository: public BaseBuyerBuyLinesRepository {
|
||||
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
|
||||
*
|
||||
* BuyerBuyLinesRepository::GetByZoneAndVersion(int zone_id, int zone_version)
|
||||
* BuyerBuyLinesRepository::GetWhereNeverExpires()
|
||||
* BuyerBuyLinesRepository::GetWhereXAndY()
|
||||
* BuyerBuyLinesRepository::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 WelcomeData_Struct {
|
||||
uint32 count_of_buyers;
|
||||
uint32 count_of_items;
|
||||
};
|
||||
|
||||
static int CreateBuyLine(Database& db, const BuyerLineItems_Struct b, uint32 char_id)
|
||||
{
|
||||
auto buyer = BuyerRepository::GetWhere(db, fmt::format("`char_id` = '{}' LIMIT 1", char_id));
|
||||
if (buyer.empty()){
|
||||
return 0;
|
||||
}
|
||||
|
||||
BuyerBuyLinesRepository::BuyerBuyLines buy_lines{};
|
||||
buy_lines.id = 0;
|
||||
buy_lines.buyer_id = buyer.front().id;
|
||||
buy_lines.char_id = char_id;
|
||||
buy_lines.buy_slot_id = b.slot;
|
||||
buy_lines.item_id = b.item_id;
|
||||
buy_lines.item_name = b.item_name;
|
||||
buy_lines.item_icon = b.item_icon;
|
||||
buy_lines.item_qty = b.item_quantity;
|
||||
buy_lines.item_price = b.item_cost;
|
||||
auto e = InsertOne(db, buy_lines);
|
||||
|
||||
std::vector<BuyerTradeItemsRepository::BuyerTradeItems> queue;
|
||||
|
||||
for (auto const &r: b.trade_items) {
|
||||
BuyerTradeItemsRepository::BuyerTradeItems bti{};
|
||||
bti.id = 0;
|
||||
bti.buyer_buy_lines_id = e.id;
|
||||
bti.item_id = r.item_id;
|
||||
bti.item_qty = r.item_quantity;
|
||||
bti.item_icon = r.item_icon;
|
||||
bti.item_name = r.item_name;
|
||||
|
||||
if (bti.item_id) {
|
||||
queue.push_back(bti);
|
||||
}
|
||||
}
|
||||
|
||||
if (!queue.empty()) {
|
||||
BuyerTradeItemsRepository::InsertMany(db, queue);
|
||||
}
|
||||
|
||||
return e.id;
|
||||
}
|
||||
|
||||
static int ModifyBuyLine(Database& db, const BuyerLineItems_Struct b, uint32 char_id)
|
||||
{
|
||||
auto b_lines = GetWhere(db, fmt::format("`char_id` = '{}' AND `buy_slot_id` = '{}';", char_id, b.slot));
|
||||
if (b_lines.empty() || b_lines.size() > 1){
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto b_line = b_lines.front();
|
||||
|
||||
b_line.item_qty = b.item_quantity;
|
||||
b_line.item_price = b.item_cost;
|
||||
b_line.item_icon = b.item_icon;
|
||||
auto e = UpdateOne(db, b_line);
|
||||
|
||||
std::vector<BuyerTradeItemsRepository::BuyerTradeItems> queue;
|
||||
|
||||
BuyerTradeItemsRepository::DeleteWhere(db, fmt::format("`buyer_buy_lines_id` = '{}';", b_line.id));
|
||||
for (auto const &r: b.trade_items) {
|
||||
BuyerTradeItemsRepository::BuyerTradeItems bti{};
|
||||
bti.id = 0;
|
||||
bti.buyer_buy_lines_id = b_line.id;
|
||||
bti.item_id = r.item_id;
|
||||
bti.item_qty = r.item_quantity;
|
||||
bti.item_icon = r.item_icon;
|
||||
bti.item_name = r.item_name;
|
||||
|
||||
if (bti.item_id) {
|
||||
queue.push_back(bti);
|
||||
}
|
||||
}
|
||||
|
||||
if (!queue.empty()) {
|
||||
BuyerTradeItemsRepository::InsertMany(db, queue);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static bool DeleteBuyLine(Database &db, uint32 char_id, int32 slot_id = 0xffffffff)
|
||||
{
|
||||
std::vector<BuyerBuyLines> buylines{};
|
||||
if (slot_id == 0xffffffff) {
|
||||
auto buylines = GetWhere(db, fmt::format("`char_id` = '{}'", char_id));
|
||||
DeleteWhere(db, fmt::format("`char_id` = '{}'", char_id));
|
||||
}
|
||||
else {
|
||||
auto buylines = GetWhere(db, fmt::format("`char_id` = '{}' AND `buy_slot_id` = '{}'", char_id, slot_id));
|
||||
DeleteWhere(db, fmt::format("`char_id` = '{}' AND `buy_slot_id` = '{}'", char_id, slot_id));
|
||||
}
|
||||
|
||||
if (buylines.empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::vector<std::string> buyline_ids{};
|
||||
for (auto const &bl: buylines) {
|
||||
buyline_ids.push_back((std::to_string(bl.id)));
|
||||
}
|
||||
|
||||
if (!buyline_ids.empty()) {
|
||||
BuyerTradeItemsRepository::DeleteWhere(
|
||||
db,
|
||||
fmt::format(
|
||||
"`buyer_buy_lines_id` IN({})",
|
||||
Strings::Implode(", ", buyline_ids)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static std::vector<BuyerLineItems_Struct> GetBuyLines(Database &db, uint32 char_id)
|
||||
{
|
||||
std::vector<BuyerLineItems_Struct> all_entries{};
|
||||
|
||||
auto buy_line = GetWhere(db, fmt::format("`char_id` = '{}';", char_id));
|
||||
if (buy_line.empty()){
|
||||
return all_entries;
|
||||
}
|
||||
|
||||
auto buy_line_trade_items = BuyerTradeItemsRepository::GetWhere(
|
||||
db,
|
||||
fmt::format(
|
||||
"`buyer_buy_lines_id` IN (SELECT b.id FROM buyer_buy_lines AS b WHERE b.char_id = '{}')",
|
||||
char_id
|
||||
)
|
||||
);
|
||||
|
||||
all_entries.reserve(buy_line.size());
|
||||
|
||||
for (auto const &l: buy_line) {
|
||||
BuyerLineItems_Struct bli{};
|
||||
bli.item_id = l.item_id;
|
||||
bli.item_cost = l.item_price;
|
||||
bli.item_quantity = l.item_qty;
|
||||
bli.slot = l.buy_slot_id;
|
||||
bli.item_name = l.item_name;
|
||||
|
||||
for (auto const &i: GetSubIDs(buy_line_trade_items, l.id)) {
|
||||
BuyerLineTradeItems_Struct blti{};
|
||||
blti.item_id = i.item_id;
|
||||
blti.item_icon = i.item_icon;
|
||||
blti.item_quantity = i.item_qty;
|
||||
blti.item_id = i.item_id;
|
||||
blti.item_name = i.item_name;
|
||||
bli.trade_items.push_back(blti);
|
||||
}
|
||||
all_entries.push_back(bli);
|
||||
}
|
||||
|
||||
return all_entries;
|
||||
}
|
||||
|
||||
static BuyerLineSearch_Struct SearchBuyLines(
|
||||
Database &db,
|
||||
std::string &search_string,
|
||||
uint32 char_id = 0,
|
||||
uint32 zone_id = 0,
|
||||
uint32 zone_instance_id = 0
|
||||
)
|
||||
{
|
||||
BuyerLineSearch_Struct all_entries{};
|
||||
std::string where_clause(fmt::format("`item_name` LIKE \"%{}%\" ", search_string));
|
||||
|
||||
if (char_id) {
|
||||
where_clause += fmt::format("AND `char_id` = '{}' ", char_id);
|
||||
}
|
||||
|
||||
if (zone_id) {
|
||||
auto buyers = BuyerRepository::GetWhere(
|
||||
db,
|
||||
fmt::format(
|
||||
"`char_zone_id` = '{}' AND char_zone_instance_id = '{}'",
|
||||
zone_id,
|
||||
zone_instance_id
|
||||
)
|
||||
);
|
||||
|
||||
if (buyers.empty()) {
|
||||
return all_entries;
|
||||
}
|
||||
|
||||
std::vector<std::string> char_ids{};
|
||||
for (auto const &bl : buyers) {
|
||||
char_ids.push_back((std::to_string(bl.char_id)));
|
||||
}
|
||||
|
||||
where_clause += fmt::format("AND `char_id` IN ({}) ", Strings::Implode(", ", char_ids));
|
||||
}
|
||||
|
||||
auto buy_line = GetWhere(db, where_clause);
|
||||
if (buy_line.empty()){
|
||||
return all_entries;
|
||||
}
|
||||
|
||||
std::vector<std::string> ids{};
|
||||
std::vector<std::string> char_ids{};
|
||||
for (auto const &bl : buy_line) {
|
||||
if (std::find(ids.begin(), ids.end(), std::to_string(bl.id)) == std::end(ids)) {
|
||||
ids.push_back(std::to_string(bl.id));
|
||||
}
|
||||
if (std::find(char_ids.begin(), char_ids.end(), std::to_string(bl.char_id)) == std::end(char_ids)) {
|
||||
char_ids.push_back((std::to_string(bl.char_id)));
|
||||
}
|
||||
}
|
||||
|
||||
auto buy_line_trade_items = BuyerTradeItemsRepository::GetWhere(
|
||||
db,
|
||||
fmt::format(
|
||||
"`buyer_buy_lines_id` IN ({});",
|
||||
Strings::Implode(", ", ids)
|
||||
)
|
||||
);
|
||||
|
||||
auto char_names = BuyerRepository::GetWhere(
|
||||
db,
|
||||
fmt::format(
|
||||
"`char_id` IN ({});",
|
||||
Strings::Implode(", ", char_ids)
|
||||
)
|
||||
);
|
||||
|
||||
all_entries.no_items = buy_line.size();
|
||||
for (auto const &l: buy_line) {
|
||||
BuyerLineItemsSearch_Struct blis{};
|
||||
blis.slot = l.buy_slot_id;
|
||||
blis.item_id = l.item_id;
|
||||
blis.item_cost = l.item_price;
|
||||
blis.item_icon = l.item_icon;
|
||||
blis.item_quantity = l.item_qty;
|
||||
blis.buyer_id = l.char_id;
|
||||
auto it = std::find_if(
|
||||
char_names.cbegin(),
|
||||
char_names.cend(),
|
||||
[&](BuyerRepository::Buyer e) { return e.char_id == l.char_id; }
|
||||
);
|
||||
blis.buyer_name = it != char_names.end() ? it->char_name : std::string("");
|
||||
blis.buyer_entity_id = it != char_names.end() ? it->char_entity_id : 0;
|
||||
blis.buyer_zone_id = it != char_names.end() ? it->char_zone_id : 0;
|
||||
blis.buyer_zone_instance_id = it != char_names.end() ? it->char_zone_instance_id : 0;
|
||||
strn0cpy(blis.item_name, l.item_name.c_str(), sizeof(blis.item_name));
|
||||
|
||||
for (auto const &i: GetSubIDs(buy_line_trade_items, l.id)) {
|
||||
BuyerLineTradeItems_Struct e{};
|
||||
e.item_id = i.item_id;
|
||||
e.item_icon = i.item_icon;
|
||||
e.item_quantity = i.item_qty;
|
||||
e.item_id = i.item_id;
|
||||
e.item_name = i.item_name;
|
||||
|
||||
blis.trade_items.push_back(e);
|
||||
}
|
||||
all_entries.buy_line.push_back(blis);
|
||||
}
|
||||
|
||||
return all_entries;
|
||||
}
|
||||
|
||||
static std::vector<BuyerTradeItemsRepository::BuyerTradeItems>
|
||||
GetSubIDs(std::vector<BuyerTradeItemsRepository::BuyerTradeItems> &in, uint64 id)
|
||||
{
|
||||
std::vector<BuyerTradeItemsRepository::BuyerTradeItems> results{};
|
||||
std::vector<uint64> indices{};
|
||||
|
||||
auto it = in.begin();
|
||||
while ((it = std::find_if(
|
||||
it,
|
||||
in.end(),
|
||||
[&](BuyerTradeItemsRepository::BuyerTradeItems const &e) {
|
||||
return e.buyer_buy_lines_id == id;
|
||||
}
|
||||
))
|
||||
!= in.end()
|
||||
) {
|
||||
indices.push_back(std::distance(in.begin(), it));
|
||||
results.push_back(*it);
|
||||
it++;
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
static WelcomeData_Struct GetWelcomeData(Database &db)
|
||||
{
|
||||
WelcomeData_Struct e{};
|
||||
|
||||
auto results = db.QueryDatabase("SELECT COUNT(DISTINCT char_id), COUNT(char_id) FROM buyer;");
|
||||
|
||||
if (!results.RowCount()) {
|
||||
return e;
|
||||
}
|
||||
|
||||
auto r = results.begin();
|
||||
e.count_of_buyers = Strings::ToInt(r[0]);
|
||||
e.count_of_items = Strings::ToInt(r[1]);
|
||||
return e;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif //EQEMU_BUYER_BUY_LINES_REPOSITORY_H
|
||||
@@ -4,6 +4,8 @@
|
||||
#include "../database.h"
|
||||
#include "../strings.h"
|
||||
#include "base/base_buyer_repository.h"
|
||||
#include "base/base_buyer_trade_items_repository.h"
|
||||
#include "base/base_buyer_buy_lines_repository.h"
|
||||
|
||||
class BuyerRepository: public BaseBuyerRepository {
|
||||
public:
|
||||
@@ -45,6 +47,97 @@ public:
|
||||
|
||||
// Custom extended repository methods here
|
||||
|
||||
static bool UpdateWelcomeMessage(Database& db, uint32 char_id, const char *message) {
|
||||
|
||||
auto const b = GetWhere(db, fmt::format("`char_id` = '{}';", char_id));
|
||||
|
||||
if (b.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto buyer = b.front();
|
||||
buyer.welcome_message = message;
|
||||
return UpdateOne(db, buyer);
|
||||
}
|
||||
|
||||
static std::string GetWelcomeMessage(Database& db, uint32 char_id) {
|
||||
|
||||
auto const b = GetWhere(db, fmt::format("`char_id` = '{}' LIMIT 1;", char_id));
|
||||
if (b.empty()) {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
return b.front().welcome_message;
|
||||
}
|
||||
|
||||
static int UpdateTransactionDate(Database& db, uint32 char_id, time_t transaction_date) {
|
||||
auto b = GetWhere(db, fmt::format("`char_id` = '{}' LIMIT 1;", char_id));
|
||||
if (b.empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto e = b.front();
|
||||
e.transaction_date = transaction_date;
|
||||
|
||||
return UpdateOne(db, e);
|
||||
}
|
||||
|
||||
static time_t GetTransactionDate(Database& db, uint32 char_id) {
|
||||
auto b = GetWhere(db, fmt::format("`char_id` = '{}' LIMIT 1;", char_id));
|
||||
if (b.empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto e = b.front();
|
||||
|
||||
return e.transaction_date;
|
||||
}
|
||||
|
||||
static bool DeleteBuyer(Database &db, uint32 char_id)
|
||||
{
|
||||
if (char_id == 0) {
|
||||
Truncate(db);
|
||||
BaseBuyerBuyLinesRepository::Truncate(db);
|
||||
BaseBuyerTradeItemsRepository::Truncate(db);
|
||||
}
|
||||
else {
|
||||
auto buyer = GetWhere(db, fmt::format("`char_id` = '{}' LIMIT 1;", char_id));
|
||||
if (buyer.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto buy_lines = BaseBuyerBuyLinesRepository::GetWhere(
|
||||
db,
|
||||
fmt::format("`buyer_id` = '{}'", buyer.front().id)
|
||||
);
|
||||
if (buy_lines.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> buy_line_ids{};
|
||||
for (auto const &bl: buy_lines) {
|
||||
buy_line_ids.push_back(std::to_string(bl.id));
|
||||
}
|
||||
|
||||
DeleteWhere(db, fmt::format("`char_id` = '{}';", char_id));
|
||||
if (buy_line_ids.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
#ifndef EQEMU_BUYER_TRADE_ITEMS_REPOSITORY_H
|
||||
#define EQEMU_BUYER_TRADE_ITEMS_REPOSITORY_H
|
||||
|
||||
#include "../database.h"
|
||||
#include "../strings.h"
|
||||
#include "base/base_buyer_trade_items_repository.h"
|
||||
|
||||
class BuyerTradeItemsRepository: public BaseBuyerTradeItemsRepository {
|
||||
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
|
||||
*
|
||||
* BuyerTradeItemsRepository::GetByZoneAndVersion(int zone_id, int zone_version)
|
||||
* BuyerTradeItemsRepository::GetWhereNeverExpires()
|
||||
* BuyerTradeItemsRepository::GetWhereXAndY()
|
||||
* BuyerTradeItemsRepository::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 std::vector<BuyerTradeItems> GetTradeItems(Database& db, const uint32 char_id)
|
||||
{
|
||||
std::vector<BuyerTradeItems> all_entries;
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"SELECT bti.* "
|
||||
"FROM buyer_trade_items AS bti "
|
||||
"INNER JOIN buyer_buy_lines AS bbl ON bti.buyer_buy_lines_id = bbl.id "
|
||||
"WHERE bbl.char_id = '{}';",
|
||||
char_id
|
||||
)
|
||||
);
|
||||
|
||||
all_entries.reserve(results.RowCount());
|
||||
|
||||
for (auto row = results.begin(); row != results.end(); ++row) {
|
||||
BuyerTradeItems e{};
|
||||
|
||||
e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0;
|
||||
e.buyer_buy_lines_id = row[1] ? strtoull(row[1], nullptr, 10) : 0;
|
||||
e.item_id = row[2] ? static_cast<int32_t>(atoi(row[2])) : 0;
|
||||
e.item_qty = row[3] ? static_cast<int32_t>(atoi(row[3])) : 0;
|
||||
e.item_icon = row[4] ? static_cast<int32_t>(atoi(row[4])) : 0;
|
||||
e.item_name = row[5] ? row[5] : "0";
|
||||
|
||||
all_entries.push_back(e);
|
||||
}
|
||||
|
||||
return all_entries;
|
||||
}
|
||||
};
|
||||
|
||||
#endif //EQEMU_BUYER_TRADE_ITEMS_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<CommandSubsettingsRepository::CommandSubsettings> static_records = {
|
||||
{.parent_command = "find", .sub_command = "aa", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "findaa"},
|
||||
{.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"},
|
||||
{.parent_command = "find", .sub_command = "class", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "findclass"},
|
||||
@@ -60,10 +61,12 @@ public:
|
||||
{.parent_command = "find", .sub_command = "item", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "fi|finditem|itemsearch"},
|
||||
{.parent_command = "find", .sub_command = "language", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "findlanguage"},
|
||||
{.parent_command = "find", .sub_command = "npc_type", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "fn|findnpc|findnpctype"},
|
||||
{.parent_command = "find", .sub_command = "object_type", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "findobjecttype"},
|
||||
{.parent_command = "find", .sub_command = "race", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "findrace"},
|
||||
{.parent_command = "find", .sub_command = "recipe", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "findrecipe"},
|
||||
{.parent_command = "find", .sub_command = "skill", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "findskill"},
|
||||
{.parent_command = "find", .sub_command = "special_ability", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "fsa|findspecialability"},
|
||||
{.parent_command = "find", .sub_command = "stance", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "findstance"},
|
||||
{.parent_command = "find", .sub_command = "spell", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "fs|findspell"},
|
||||
{.parent_command = "find", .sub_command = "task", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "findtask"},
|
||||
{.parent_command = "find", .sub_command = "zone", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "fz|findzone"},
|
||||
@@ -82,7 +85,6 @@ public:
|
||||
{.parent_command = "set", .sub_command = "endurance", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "setendurance"},
|
||||
{.parent_command = "set", .sub_command = "endurance_full", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "endurance"},
|
||||
{.parent_command = "set", .sub_command = "exp", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "setxp|setexp"},
|
||||
{.parent_command = "set", .sub_command = "faction", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "setfaction"},
|
||||
{.parent_command = "set", .sub_command = "flymode", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "flymode"},
|
||||
{.parent_command = "set", .sub_command = "freeze", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "freeze|unfreeze"},
|
||||
{.parent_command = "set", .sub_command = "gender", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "gender"},
|
||||
|
||||
@@ -44,7 +44,24 @@ public:
|
||||
*/
|
||||
|
||||
// Custom extended repository methods here
|
||||
static std::vector<std::string> GetDBStrFileLines(Database& db)
|
||||
{
|
||||
std::vector<std::string> lines;
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"SELECT CONCAT(CONCAT_WS('^', {}), '^0') FROM {} ORDER BY `id`, `type` ASC",
|
||||
ColumnsRaw(),
|
||||
TableName()
|
||||
)
|
||||
);
|
||||
|
||||
for (auto row : results) {
|
||||
lines.emplace_back(row[0]);
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
};
|
||||
|
||||
#endif //EQEMU_DB_STR_REPOSITORY_H
|
||||
|
||||
@@ -44,7 +44,23 @@ public:
|
||||
*/
|
||||
|
||||
// Custom extended repository methods here
|
||||
static std::vector<std::string> GetSkillCapFileLines(Database& db)
|
||||
{
|
||||
std::vector<std::string> lines;
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"SELECT CONCAT_WS('^', `class_id`, `skill_id`, `level`, `cap`, `class_`) FROM {} ORDER BY `class_id`, `skill_id`, `level` ASC",
|
||||
TableName()
|
||||
)
|
||||
);
|
||||
|
||||
for (auto row : results) {
|
||||
lines.emplace_back(row[0]);
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
};
|
||||
|
||||
#endif //EQEMU_SKILL_CAPS_REPOSITORY_H
|
||||
|
||||
@@ -44,7 +44,25 @@ public:
|
||||
*/
|
||||
|
||||
// Custom extended repository methods here
|
||||
static std::vector<std::string> GetSpellFileLines(Database& db)
|
||||
{
|
||||
std::vector<std::string> lines;
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"SELECT CONCAT_WS('^', {}) FROM {} ORDER BY {} ASC",
|
||||
ColumnsRaw(),
|
||||
TableName(),
|
||||
PrimaryKey()
|
||||
)
|
||||
);
|
||||
|
||||
for (auto row : results) {
|
||||
lines.emplace_back(row[0]);
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
};
|
||||
|
||||
#endif //EQEMU_SPELLS_NEW_REPOSITORY_H
|
||||
|
||||
@@ -217,6 +217,10 @@ public:
|
||||
delete_ids.push_back(std::to_string(e.id));
|
||||
}
|
||||
|
||||
if (delete_ids.empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return DeleteWhere(db, fmt::format("`id` IN({})", Strings::Implode(",", delete_ids)));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -189,6 +189,8 @@ void RuleManager::ResetRules(bool reload) {
|
||||
m_RuleRealValues[ Real__##rule_name ] = default_value;
|
||||
#define RULE_BOOL(category_name, rule_name, default_value, notes) \
|
||||
m_RuleBoolValues[ Bool__##rule_name ] = default_value;
|
||||
#define RULE_STRING(category_name, rule_name, default_value, notes) \
|
||||
m_RuleStringValues[ String__##rule_name ] = default_value;
|
||||
#include "ruletypes.h"
|
||||
|
||||
// restore these rules to their pre-reset values
|
||||
|
||||
+29
-2
@@ -178,6 +178,7 @@ RULE_BOOL(Character, NoSkillsOnHorse, false, "Enabling this will prevent Bind Wo
|
||||
RULE_BOOL(Character, UseNoJunkFishing, false, "Disregards junk items when fishing")
|
||||
RULE_BOOL(Character, SoftDeletes, true, "When characters are deleted in character select, they are only soft deleted")
|
||||
RULE_INT(Character, DefaultGuild, 0, "If not 0, new characters placed into the guild # indicated")
|
||||
RULE_INT(Character, DefaultGuildRank, 8, "Default guild rank when Character:DefaultGuild is a non-0 value, default is 8 (Recruit)")
|
||||
RULE_BOOL(Character, ProcessFearedProximity, false, "Processes proximity checks when feared")
|
||||
RULE_BOOL(Character, EnableCharacterEXPMods, false, "Enables character zone-based experience modifiers.")
|
||||
RULE_BOOL(Character, PVPEnableGuardFactionAssist, true, "Enables faction based assisting against the aggresor in pvp.")
|
||||
@@ -226,6 +227,9 @@ RULE_INT(Character, ClearXTargetDelay, 10, "Seconds between uses of the #clearxt
|
||||
RULE_BOOL(Character, PreventMountsFromZoning, false, "Enable to prevent mounts from zoning - Prior to December 15, 2004 this is enabled.")
|
||||
RULE_BOOL(Character, GroupInvitesRequireTarget, false, "Enable to require players to have invitee on target (Disables /invite name) - Classic Style")
|
||||
RULE_BOOL(Character, PlayerTradingLoreFeedback, true, "If enabled, during a player to player trade, if lore items exist, it will output which items.")
|
||||
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_CATEGORY_END()
|
||||
|
||||
RULE_CATEGORY(Mercs)
|
||||
@@ -240,6 +244,9 @@ RULE_INT(Mercs, AggroRadiusPuller, 25, "Determines the distance from which a mer
|
||||
RULE_INT(Mercs, ResurrectRadius, 50, "Determines the distance from which a healer merc will attempt to resurrect a group member's corpse")
|
||||
RULE_INT(Mercs, ScaleRate, 100, "Merc scale factor")
|
||||
RULE_BOOL(Mercs, AllowMercSuspendInCombat, true, "Allow merc suspend in combat")
|
||||
RULE_BOOL(Mercs, MercsIgnoreLevelBasedHasteCaps, false, "Ignores hard coded level based haste caps.")
|
||||
RULE_INT(Mercs, MercsHasteCap, 100, "Haste cap for non-v3(over haste) haste")
|
||||
RULE_INT(Mercs, MercsHastev3Cap, 25, "Haste cap for v3(over haste) haste")
|
||||
RULE_CATEGORY_END()
|
||||
|
||||
RULE_CATEGORY(Guild)
|
||||
@@ -331,6 +338,7 @@ RULE_STRING(World, IPExemptionZones, "", "Comma-delimited list of zones to exclu
|
||||
RULE_STRING(World, MOTD, "", "Server MOTD sent on login, change from empty to have this be used instead of variables table 'motd' value")
|
||||
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_CATEGORY_END()
|
||||
|
||||
RULE_CATEGORY(Zone)
|
||||
@@ -468,7 +476,6 @@ RULE_BOOL(Spells, OldRainTargets, false, "Use old incorrectly implemented maximu
|
||||
RULE_REAL(Spells, CallOfTheHeroAggroClearDist, 250.0, "Distance at which CoTH will wipe aggro. To disable and always enable aggro wipe on any distance of CoTH, set to 0.")
|
||||
RULE_BOOL(Spells, NPCSpellPush, false, "Enable spell push on NPCs")
|
||||
RULE_BOOL(Spells, July242002PetResists, true, "Enable Pets using PCs resist change from July 24 2002")
|
||||
RULE_INT(Spells, AOEMaxTargets, 0, "Max number of targets a Targeted AOE spell can cast on. Set to 0 for no limit.")
|
||||
RULE_BOOL(Spells, CazicTouchTargetsPetOwner, true, "If True, causes Cazic Touch to swap targets from pet to pet owner if a pet is tanking.")
|
||||
RULE_BOOL(Spells, PreventFactionWarOnCharmBreak, false, "Enable spell interupts and dot removal on charm break to prevent faction wars.")
|
||||
RULE_BOOL(Spells, AllowDoubleInvis, true, "Allows you to cast invisibility spells on a player that is already invisible, live like behavior.")
|
||||
@@ -503,9 +510,15 @@ RULE_BOOL(Spells, RequireMnemonicRetention, true, "Enabling will require spell s
|
||||
RULE_BOOL(Spells, EvacClearCharmPet, false, "Enable to have evac in zone clear charm from charm pets and detach buffs.")
|
||||
RULE_BOOL(Spells, ManaTapsRequireNPCMana, false, "Enabling will require target to have mana to tap. Default off as many npc's are caster class with 0 mana and need fixed.")
|
||||
RULE_INT(Spells, HarmTouchCritRatio, 200, "Harmtouch crit bonus, on top of BaseCritRatio")
|
||||
RULE_BOOL(Spells, UseClassicHarmTouchDamage, false, "Use pre 2007 Harm Touch calculations - Default: False")
|
||||
RULE_BOOL(Spells, UseClassicSpellFocus, false, "Enabling will tell the server to handle random focus damage as classic spell imports lack the limit values.")
|
||||
RULE_BOOL(Spells, ManaTapsOnAnyClass, false, "Enabling this will allow you to cast mana taps on any class, this will bypass ManaTapsRequireNPCMana rule.")
|
||||
RULE_INT(Spells, HealAmountMessageFilterThreshold, 100, "Lifetaps below this threshold will not have a message sent to the client (Heal will still process) 0 to Disable.")
|
||||
RULE_BOOL(Spells, SnareOverridesSpeedBonuses, false, "Enabling will allow snares to override any speed bonuses the entity may have. Default: False")
|
||||
RULE_INT(Spells, TargetedAOEMaxTargets, 4, "Max number of targets a Targeted AOE spell can cast on. Set to 0 for no limit.")
|
||||
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_CATEGORY_END()
|
||||
|
||||
RULE_CATEGORY(Combat)
|
||||
@@ -615,7 +628,8 @@ RULE_INT(Combat, PCAccuracyAvoidanceMod2Scale, 100, "Scale Factor for PC Accurac
|
||||
RULE_BOOL(Combat, AllowRaidTargetBlind, false, "Toggle to allow raid targets to be blinded, default is false (Live-like)")
|
||||
RULE_BOOL(Combat, RogueBackstabHasteCorrection, false, "Toggle to enable correction for Haste impacting Backstab DPS too much. DEFAULT: false")
|
||||
RULE_BOOL(Combat, LegacyComputeDefense, false, "Trim AGI Scaling of defense mostly for lower levels to help compensate for the newer agi based defense system. Default: False")
|
||||
RULE_REAL(Combat, SlayDamageAdjustment, 0.5, "Slay Damage Adjustment - Multiply final slay damage by this value. Default: 0.5")
|
||||
RULE_REAL(Combat, SlayDamageMultiplier, 1.0, "Slay Damage Adjustment - Multiply final slay damage by this value. Default: 1.0")
|
||||
RULE_REAL(Combat, SlayRateMultiplier, 1.0, "Slay Rate Adjustments - Multiply final slay rate check by this value. Default: 1.0")
|
||||
RULE_INT(Combat, MaximumLevelStunsCripplingBlow, 55, "Maximum level that Crippling Blows will stun a npc. Default: 55")
|
||||
RULE_INT(Combat, ArcheryBaseDamage, 0, "Archery base damage, default is 0")
|
||||
RULE_INT(Combat, BackstabBaseDamage, 0, "Backstab base damage, default is 0")
|
||||
@@ -663,6 +677,9 @@ RULE_REAL(NPC, NPCHealOnGateAmount, 25, "How much the NPC will heal on gate if e
|
||||
RULE_BOOL(NPC, AnimalsOpenDoors, true, "Determines or not whether animals open doors or not when they approach them")
|
||||
RULE_INT(NPC, MaxRaceID, 732, "Maximum Race ID, RoF2 by default supports up to 732")
|
||||
RULE_BOOL(NPC, DisableLastNames, false, "Enable to disable NPC Last Names")
|
||||
RULE_BOOL(NPC, NPCIgnoreLevelBasedHasteCaps, false, "Ignores hard coded level based haste caps.")
|
||||
RULE_INT(NPC, NPCHasteCap, 150, "Haste cap for non-v3(over haste) haste")
|
||||
RULE_INT(NPC, NPCHastev3Cap, 25, "Haste cap for v3(over haste) haste")
|
||||
RULE_CATEGORY_END()
|
||||
|
||||
RULE_CATEGORY(Aggro)
|
||||
@@ -688,6 +705,7 @@ RULE_BOOL(Aggro, UndeadAlwaysAggro, true, "should undead always aggro?")
|
||||
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_CATEGORY_END()
|
||||
|
||||
RULE_CATEGORY(TaskSystem)
|
||||
@@ -702,6 +720,7 @@ RULE_BOOL(TaskSystem, ExpRewardsIgnoreLevelBasedEXPMods, false, "Rewarding Level
|
||||
RULE_INT(TaskSystem, SharedTasksWorldProcessRate, 6000, "Timer interval (milliseconds) that shared tasks are processed in world")
|
||||
RULE_INT(TaskSystem, SharedTasksTerminateTimerMS, 120000, "Delay (milliseconds) until a shared task is terminated if requirements are no longer met after member removal (default: 2 minutes)")
|
||||
RULE_BOOL(TaskSystem, UpdateOneElementPerTask, true, "If true (live-like) task updates only increment the first matching activity. If false all matching elements will be incremented.")
|
||||
RULE_INT(TaskSystem, MaxUpdateMessages, 50, "Maximum update messages for non-GiveCash activity types in IncrementDoneCount")
|
||||
RULE_CATEGORY_END()
|
||||
|
||||
RULE_CATEGORY(Range)
|
||||
@@ -713,6 +732,7 @@ RULE_INT(Range, SpellParticles, 135, "The packet range in which spell particles
|
||||
RULE_INT(Range, DamageMessages, 50, "The packet range in which damage messages are sent (non-crit)")
|
||||
RULE_INT(Range, SpellMessages, 75, "The packet range in which spell damage messages are sent")
|
||||
RULE_INT(Range, SongMessages, 75, "The packet range in which song messages are sent")
|
||||
RULE_INT(Range, StunMessages, 75, "The packet range in which stun messages are sent")
|
||||
RULE_INT(Range, ClientPositionUpdates, 300, "Distance in which the own changed position is communicated to other clients")
|
||||
RULE_INT(Range, CriticalDamage, 80, "The packet range in which critical hit messages are sent")
|
||||
RULE_INT(Range, MobCloseScanDistance, 600, "Close scan distance")
|
||||
@@ -754,6 +774,9 @@ RULE_BOOL(Bots, CazicTouchBotsOwner, true, "Default True. Cazic Touch/DT will hi
|
||||
RULE_INT(Bots, BotsClickItemsMinLvl, 1, "Minimum level for bots to be able to use ^clickitem. Default 1.")
|
||||
RULE_BOOL(Bots, BotsCanClickItems, true, "Enables the ability for bots to click items they have equipped. Default TRUE")
|
||||
RULE_BOOL(Bots, CanClickMageEpicV1, true, "Whether or not bots are allowed to click Mage Epic 1.0. Default TRUE")
|
||||
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_CATEGORY_END()
|
||||
|
||||
RULE_CATEGORY(Chat)
|
||||
@@ -800,6 +823,7 @@ RULE_INT(Bazaar, MaxBarterSearchResults, 200, "The maximum results returned in t
|
||||
RULE_REAL(Bazaar, ParcelDeliveryCostMod, 0.20, "Cost of parcel delivery for a bazaar purchase as a percentage of item cost. Default is 20% of item cost. RoF+ Only.")
|
||||
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_CATEGORY_END()
|
||||
|
||||
RULE_CATEGORY(Mail)
|
||||
@@ -891,6 +915,7 @@ RULE_BOOL(Inventory, AllowAnyWeaponTransformation, false, "Weapons can use any w
|
||||
RULE_BOOL(Inventory, TransformSummonedBags, false, "Transforms summoned bags into disenchanted ones instead of deleting")
|
||||
RULE_BOOL(Inventory, AllowMultipleOfSameAugment, false, "Allows multiple of the same augment to be placed in an item via #augmentitem or MQ2, set to true to allow")
|
||||
RULE_INT(Inventory, AlternateAugmentationSealer, 53, "Allows RoF+ clients to augment items from a special container type")
|
||||
RULE_BOOL(Inventory, LazyLoadBank, true, "Don't load bank during zoning, only when in proximinity to a banker. May increase zone speed and stability")
|
||||
RULE_CATEGORY_END()
|
||||
|
||||
RULE_CATEGORY(Client)
|
||||
@@ -993,6 +1018,8 @@ RULE_BOOL(Items, DisableNoRent, false, "Enable this to disable No Rent Items")
|
||||
RULE_BOOL(Items, DisableNoTransfer, false, "Enable this to disable No Transfer Items")
|
||||
RULE_BOOL(Items, DisablePotionBelt, false, "Enable this to disable Potion Belt Items")
|
||||
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_CATEGORY_END()
|
||||
|
||||
RULE_CATEGORY(Parcel)
|
||||
|
||||
@@ -140,6 +140,7 @@
|
||||
|
||||
#define ServerOP_TraderMessaging 0x0120
|
||||
#define ServerOP_BazaarPurchase 0x0121
|
||||
#define ServerOP_BuyerMessaging 0x0122
|
||||
|
||||
#define ServerOP_InstanceUpdateTime 0x014F
|
||||
#define ServerOP_AdventureRequest 0x0150
|
||||
|
||||
+116
-85
@@ -46,6 +46,8 @@
|
||||
#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
|
||||
{
|
||||
@@ -300,15 +302,15 @@ bool SharedDatabase::UpdateInventorySlot(uint32 char_id, const EQ::ItemInstance*
|
||||
// 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) "
|
||||
"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, %lu, %lu, %lu, %lu, %lu, %lu, %lu, %lu, %lu)",
|
||||
static_cast<unsigned long>(char_id), static_cast<unsigned long>(slot_id), static_cast<unsigned long>(inst->GetItem()->ID),
|
||||
static_cast<unsigned long>(charges), static_cast<unsigned long>(inst->IsAttuned() ? 1 : 0),
|
||||
inst->GetCustomDataString().c_str(), static_cast<unsigned long>(inst->GetColor()),
|
||||
static_cast<unsigned long>(augslot[0]), static_cast<unsigned long>(augslot[1]), static_cast<unsigned long>(augslot[2]),
|
||||
static_cast<unsigned long>(augslot[3]), static_cast<unsigned long>(augslot[4]), static_cast<unsigned long>(augslot[5]), static_cast<unsigned long>(inst->GetOrnamentationIcon()),
|
||||
static_cast<unsigned long>(inst->GetOrnamentationIDFile()), static_cast<unsigned long>(inst->GetOrnamentHeroModel()));
|
||||
static_cast<unsigned long>(inst->GetOrnamentationIDFile()), static_cast<unsigned long>(inst->GetOrnamentHeroModel()), inst->GetSerialNumber());
|
||||
const auto results = QueryDatabase(query);
|
||||
|
||||
// Save bag contents, if slot supports bag contents
|
||||
@@ -651,48 +653,67 @@ bool SharedDatabase::GetInventory(uint32 char_id, EQ::InventoryProfile *inv)
|
||||
return false;
|
||||
|
||||
// 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 WHERE charid = %i ORDER BY slotid",
|
||||
char_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");
|
||||
auto results = InventoryRepository::GetWhere(*this, fmt::format("`charid` = '{}' ORDER BY `slotid`;", char_id));
|
||||
if (results.empty()) {
|
||||
LogError("Error loading inventory for char_id {} from the database.", char_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
auto cv_conflict = false;
|
||||
const auto pmask = inv->GetLookup()->PossessionsBitmask;
|
||||
const auto bank_size = inv->GetLookup()->InventoryTypeSize.Bank;
|
||||
std::vector<InventoryRepository::Inventory> queue{};
|
||||
for (auto &row: results) {
|
||||
const int16 slot_id = row.slotid;
|
||||
const uint32 item_id = row.itemid;
|
||||
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 uint32 ornament_hero_model = row.ornament_hero_model;
|
||||
|
||||
for (auto& row = results.begin(); row != results.end(); ++row) {
|
||||
int16 slot_id = Strings::ToInt(row[0]);
|
||||
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;
|
||||
|
||||
if (slot_id <= EQ::invslot::POSSESSIONS_END && slot_id >= EQ::invslot::POSSESSIONS_BEGIN) { // Titanium thru UF check
|
||||
if (slot_id <= EQ::invslot::POSSESSIONS_END && slot_id >= EQ::invslot::POSSESSIONS_BEGIN) {
|
||||
// Titanium thru UF check
|
||||
if (((static_cast<uint64>(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) { // Titanium thru UF check
|
||||
const auto parent_slot = EQ::invslot::GENERAL_BEGIN + ((slot_id - EQ::invbag::GENERAL_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT);
|
||||
else if (slot_id <= EQ::invbag::GENERAL_BAGS_END && slot_id >= EQ::invbag::GENERAL_BAGS_BEGIN) {
|
||||
// Titanium thru UF check
|
||||
const auto parent_slot = EQ::invslot::GENERAL_BEGIN + (
|
||||
(slot_id - EQ::invbag::GENERAL_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT);
|
||||
if (((static_cast<uint64>(1) << parent_slot) & pmask) == 0) {
|
||||
cv_conflict = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (slot_id <= EQ::invslot::BANK_END && slot_id >= EQ::invslot::BANK_BEGIN) { // Titanium check
|
||||
else if (slot_id <= EQ::invslot::BANK_END && slot_id >= EQ::invslot::BANK_BEGIN) {
|
||||
// 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) { // Titanium check
|
||||
else if (slot_id <= EQ::invbag::BANK_BAGS_END && slot_id >= EQ::invbag::BANK_BAGS_BEGIN) {
|
||||
// Titanium check
|
||||
const auto parent_index = ((slot_id - EQ::invbag::BANK_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT);
|
||||
if (parent_index >= bank_size) {
|
||||
cv_conflict = true;
|
||||
@@ -700,64 +721,55 @@ bool SharedDatabase::GetInventory(uint32 char_id, EQ::InventoryProfile *inv)
|
||||
}
|
||||
}
|
||||
|
||||
uint32 item_id = Strings::ToUnsignedInt(row[1]);
|
||||
const uint16 charges = Strings::ToUnsignedInt(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<uint16>(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);
|
||||
|
||||
auto *item = GetItem(item_id);
|
||||
if (!item) {
|
||||
LogError("Warning: charid [{}] has an invalid item_id [{}] in inventory slot [{}]", char_id, item_id,
|
||||
slot_id);
|
||||
LogError(
|
||||
"Warning: charid [{}] has an invalid item_id [{}] in inventory slot [{}]",
|
||||
char_id,
|
||||
item_id,
|
||||
slot_id
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
EQ::ItemInstance *inst = CreateBaseItem(item, charges);
|
||||
|
||||
if (inst == nullptr)
|
||||
auto *inst = CreateBaseItem(item, charges);
|
||||
if (!inst) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (row[11]) {
|
||||
std::string data_str(row[11]);
|
||||
inst->SetCustomDataString(data_str);
|
||||
if (!row.custom_data.empty()) {
|
||||
inst->SetCustomDataString(row.custom_data);
|
||||
}
|
||||
|
||||
inst->SetOrnamentIcon(ornament_icon);
|
||||
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 && slot_id >= EQ::invslot::EQUIPMENT_BEGIN && slot_id <=
|
||||
EQ::invslot::EQUIPMENT_END)) {
|
||||
inst->SetAttuned(true);
|
||||
}
|
||||
|
||||
if (color > 0)
|
||||
if (color > 0) {
|
||||
inst->SetColor(color);
|
||||
}
|
||||
|
||||
if (charges == 0x7FFF)
|
||||
if (charges == 0x7FFF) {
|
||||
inst->SetCharges(-1);
|
||||
else if (charges == 0 && inst->IsStackable()) // Stackable items need a minimum charge of 1 remain moveable.
|
||||
}
|
||||
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 {
|
||||
@@ -767,35 +779,50 @@ bool SharedDatabase::GetInventory(uint32 char_id, EQ::InventoryProfile *inv)
|
||||
|
||||
if (item->IsClassCommon()) {
|
||||
for (int i = EQ::invaug::SOCKET_BEGIN; i <= EQ::invaug::SOCKET_END; i++) {
|
||||
if (aug[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 if (slot_id >= 3111 && slot_id <= 3179) {
|
||||
}
|
||||
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);
|
||||
LogError(
|
||||
"Warning: Defunct location for item in inventory: charid={}, item_id={}, slot_id={} .. pushing to cursor...",
|
||||
char_id,
|
||||
item_id,
|
||||
slot_id
|
||||
);
|
||||
put_slot_id = inv->PushCursor(*inst);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
put_slot_id = inv->PutItem(slot_id, *inst);
|
||||
}
|
||||
|
||||
row.guid = inst->GetSerialNumber();
|
||||
queue.push_back(row);
|
||||
|
||||
safe_delete(inst);
|
||||
|
||||
// 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=[{}]",
|
||||
char_id, item_id, slot_id);
|
||||
LogError(
|
||||
"Warning: Invalid slot_id for item in inventory: charid=[{}], item_id=[{}], slot_id=[{}]",
|
||||
char_id,
|
||||
item_id,
|
||||
slot_id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (cv_conflict) {
|
||||
const std::string& char_name = GetCharName(char_id);
|
||||
LogError("ClientVersion/Expansion conflict during inventory load at zone entry for [{}] (charid: [{}], inver: [{}], gmi: [{}])",
|
||||
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()),
|
||||
@@ -803,6 +830,12 @@ bool SharedDatabase::GetInventory(uint32 char_id, EQ::InventoryProfile *inv)
|
||||
);
|
||||
}
|
||||
|
||||
if (!queue.empty()) {
|
||||
InventoryRepository::ReplaceMany(*this, queue);
|
||||
}
|
||||
|
||||
EQ::ItemInstance::ClearGUIDMap();
|
||||
|
||||
// Retrieve shared inventory
|
||||
return GetSharedBank(char_id, inv, true);
|
||||
}
|
||||
@@ -1359,30 +1392,28 @@ const EQ::ItemData* SharedDatabase::IterateItems(uint32* id) const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string SharedDatabase::GetBook(const char *txtfile, int16 *language)
|
||||
Book_Struct SharedDatabase::GetBook(const std::string& text_file)
|
||||
{
|
||||
char txtfile2[20];
|
||||
std::string txtout;
|
||||
strcpy(txtfile2, txtfile);
|
||||
const auto& l = BooksRepository::GetWhere(
|
||||
*this,
|
||||
fmt::format(
|
||||
"`name` = '{}'",
|
||||
Strings::Escape(text_file)
|
||||
)
|
||||
);
|
||||
|
||||
const std::string query = StringFormat("SELECT txtfile, language FROM books WHERE name = '%s'", txtfile2);
|
||||
auto results = QueryDatabase(query);
|
||||
if (!results.Success()) {
|
||||
txtout.assign(" ",1);
|
||||
return txtout;
|
||||
Book_Struct b;
|
||||
|
||||
if (l.empty()) {
|
||||
return b;
|
||||
}
|
||||
|
||||
if (results.RowCount() == 0) {
|
||||
LogError("No book to send, ({})", txtfile);
|
||||
txtout.assign(" ",1);
|
||||
return txtout;
|
||||
}
|
||||
const auto& e = l.front();
|
||||
|
||||
auto& row = results.begin();
|
||||
txtout.assign(row[0],strlen(row[0]));
|
||||
*language = static_cast<int16>(Strings::ToInt(row[1]));
|
||||
b.language = e.language;
|
||||
b.text = e.txtfile;
|
||||
|
||||
return txtout;
|
||||
return b;
|
||||
}
|
||||
|
||||
// Create appropriate EQ::ItemInstance class
|
||||
|
||||
+8
-3
@@ -41,8 +41,7 @@ struct NPCFactionList;
|
||||
struct FactionAssociations;
|
||||
|
||||
|
||||
namespace EQ
|
||||
{
|
||||
namespace EQ {
|
||||
|
||||
struct ItemData;
|
||||
class ItemInstance;
|
||||
@@ -50,6 +49,12 @@ namespace EQ
|
||||
class MemoryMappedFile;
|
||||
}
|
||||
|
||||
struct Book_Struct
|
||||
{
|
||||
uint8 language;
|
||||
std::string text;
|
||||
};
|
||||
|
||||
/*
|
||||
This object is inherited by world and zone's DB object,
|
||||
and is mainly here to facilitate shared memory, and other
|
||||
@@ -114,7 +119,7 @@ public:
|
||||
int admin
|
||||
);
|
||||
|
||||
std::string GetBook(const char *txtfile, int16 *language);
|
||||
Book_Struct GetBook(const std::string& text_file);
|
||||
|
||||
/**
|
||||
* items
|
||||
|
||||
@@ -249,7 +249,6 @@ const std::vector<EQ::skills::SkillType>& EQ::skills::GetExtraDamageSkills()
|
||||
EQ::skills::SkillFlyingKick,
|
||||
EQ::skills::SkillKick,
|
||||
EQ::skills::SkillRoundKick,
|
||||
EQ::skills::SkillRoundKick,
|
||||
EQ::skills::SkillTigerClaw,
|
||||
EQ::skills::SkillFrenzy
|
||||
};
|
||||
|
||||
@@ -431,6 +431,16 @@ bool IsCharmSpell(uint16 spell_id)
|
||||
return IsEffectInSpell(spell_id, SE_Charm);
|
||||
}
|
||||
|
||||
bool IsResurrectionSicknessSpell(uint16 spell_id) {
|
||||
return (
|
||||
spell_id == SPELL_RESURRECTION_SICKNESS ||
|
||||
spell_id == SPELL_RESURRECTION_SICKNESS2 ||
|
||||
spell_id == SPELL_RESURRECTION_SICKNESS3 ||
|
||||
spell_id == SPELL_RESURRECTION_SICKNESS4 ||
|
||||
spell_id == SPELL_REVIVAL_SICKNESS
|
||||
);
|
||||
}
|
||||
|
||||
bool IsBlindSpell(uint16 spell_id)
|
||||
{
|
||||
return IsEffectInSpell(spell_id, SE_Blind);
|
||||
@@ -2315,3 +2325,108 @@ bool IsCastNotStandingSpell(uint16 spell_id) {
|
||||
*/
|
||||
return spells[spell_id].cast_not_standing;
|
||||
}
|
||||
|
||||
bool IsAegolismSpell(uint16 spell_id) {
|
||||
|
||||
if (!IsValidSpell(spell_id)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool has_max_hp = false;
|
||||
bool has_current_hp = false;
|
||||
bool has_ac = false;
|
||||
|
||||
for (int i = 0; i < EFFECT_COUNT; ++i) {
|
||||
|
||||
if (i == 0 && spells[spell_id].effect_id[i] != SE_StackingCommand_Block) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (i == 1 && spells[spell_id].effect_id[i] == SE_TotalHP) {
|
||||
has_max_hp = true;
|
||||
}
|
||||
|
||||
if (i == 2 && spells[spell_id].effect_id[i] == SE_CurrentHPOnce) {
|
||||
has_current_hp = true;
|
||||
}
|
||||
|
||||
if (i == 3 && spells[spell_id].effect_id[i] == SE_ArmorClass) {
|
||||
has_ac = true;
|
||||
}
|
||||
|
||||
if (i == 4 && spells[spell_id].effect_id[i] != SE_StackingCommand_Overwrite) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (has_max_hp && has_current_hp && has_ac) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
if (!IsValidSpell(spell_id)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool has_max_hp = false;
|
||||
bool has_current_hp = false;
|
||||
|
||||
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] == SE_TotalHP) {
|
||||
has_max_hp = true;
|
||||
}
|
||||
|
||||
if (i == 3 && spells[spell_id].effect_id[i] == SE_CurrentHPOnce) {
|
||||
has_current_hp = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (has_max_hp && has_current_hp) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool AegolismStackingIsArmorClassSpell(uint16 spell_id) {
|
||||
/*
|
||||
This is hardcoded to be specific to the type of AC buffs that are removed if a mob has an Aegolism buff.
|
||||
*/
|
||||
if (!IsValidSpell(spell_id)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool has_ac = false;
|
||||
|
||||
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) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (i == 3 && spells[spell_id].effect_id[i] == SE_ArmorClass) {
|
||||
has_ac = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (has_ac) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1522,6 +1522,7 @@ bool IsSummonPetSpell(uint16 spell_id);
|
||||
bool IsSummonPCSpell(uint16 spell_id);
|
||||
bool IsPetSpell(uint16 spell_id);
|
||||
bool IsCharmSpell(uint16 spell_id);
|
||||
bool IsResurrectionSicknessSpell(uint16 spell_id);
|
||||
bool IsBlindSpell(uint16 spell_id);
|
||||
bool IsHealthSpell(uint16 spell_id);
|
||||
bool IsCastTimeReductionSpell(uint16 spell_id);
|
||||
@@ -1624,5 +1625,8 @@ bool IsSpellUsableInThisZoneType(uint16 spell_id, uint8 zone_type);
|
||||
const char *GetSpellName(uint16 spell_id);
|
||||
int GetSpellStatValue(uint16 spell_id, const char* stat_identifier, uint8 slot = 0);
|
||||
bool IsCastRestrictedSpell(uint16 spell_id);
|
||||
bool IsAegolismSpell(uint16 spell_id);
|
||||
bool AegolismStackingIsSymbolSpell(uint16 spell_id);
|
||||
bool AegolismStackingIsArmorClassSpell(uint16 spell_id);
|
||||
|
||||
#endif
|
||||
|
||||
+3
-3
@@ -25,7 +25,7 @@
|
||||
|
||||
// Build variables
|
||||
// these get injected during the build pipeline
|
||||
#define CURRENT_VERSION "22.52.0-dev" // always append -dev to the current version for custom-builds
|
||||
#define CURRENT_VERSION "22.58.0-dev" // always append -dev to the current version for custom-builds
|
||||
#define LOGIN_VERSION "0.8.0"
|
||||
#define COMPILE_DATE __DATE__
|
||||
#define COMPILE_TIME __TIME__
|
||||
@@ -42,8 +42,8 @@
|
||||
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
|
||||
*/
|
||||
|
||||
#define CURRENT_BINARY_DATABASE_VERSION 9280
|
||||
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9044
|
||||
#define CURRENT_BINARY_DATABASE_VERSION 9284
|
||||
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9045
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -251,4 +251,6 @@ private:
|
||||
scalar m_value;
|
||||
};
|
||||
|
||||
using ref = reference;
|
||||
|
||||
} // namespace perlbind
|
||||
|
||||
@@ -28,8 +28,8 @@ struct pusher
|
||||
++m_pushed;
|
||||
}
|
||||
void push(const std::string& value) { mPUSHp(value.c_str(), value.size()); ++m_pushed; }
|
||||
void push(scalar value) { mPUSHs(value.release()); ++m_pushed; };
|
||||
void push(reference value) { mPUSHs(value.release()); ++m_pushed; };
|
||||
void push(scalar value) { mPUSHs(value.release()); ++m_pushed; }
|
||||
void push(reference value) { mPUSHs(value.release()); ++m_pushed; }
|
||||
|
||||
void push(array value)
|
||||
{
|
||||
@@ -38,7 +38,8 @@ struct pusher
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
// mortalizes one reference to array element to avoid copying
|
||||
PUSHs(sv_2mortal(SvREFCNT_inc(value[i].sv())));
|
||||
SV** sv = av_fetch(static_cast<AV*>(value), i, true);
|
||||
mPUSHs(SvREFCNT_inc(*sv));
|
||||
}
|
||||
m_pushed += count;
|
||||
}
|
||||
|
||||
@@ -242,7 +242,7 @@ struct read_as<hash>
|
||||
static bool check(PerlInterpreter* my_perl, int i, int ax, int items)
|
||||
{
|
||||
int remaining = items - i;
|
||||
return remaining > 0 && remaining % 2 == 0 && SvTYPE(ST(i)) == SVt_PV;
|
||||
return remaining > 0 && remaining % 2 == 0 && SvTYPE(ST(i)) < SVt_PVAV;
|
||||
}
|
||||
|
||||
static hash get(PerlInterpreter* my_perl, int i, int ax, int items)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
constexpr int perlbind_version_major = 1;
|
||||
constexpr int perlbind_version_minor = 0;
|
||||
constexpr int perlbind_version_minor = 1;
|
||||
constexpr int perlbind_version_patch = 0;
|
||||
|
||||
constexpr int perlbind_version()
|
||||
|
||||
@@ -224,6 +224,23 @@ void Client::Handle_Login(const char *data, unsigned int size)
|
||||
if (server.db->GetLoginDataFromAccountInfo(user, db_loginserver, db_account_password_hash, db_account_id)) {
|
||||
result = VerifyLoginHash(user, db_loginserver, cred, db_account_password_hash);
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
LogDebug("Success [{0}]", (result ? "true" : "false"));
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -138,6 +138,13 @@ public:
|
||||
*/
|
||||
unsigned int GetPlaySequence() const { return m_play_sequence_id; }
|
||||
|
||||
/**
|
||||
* Gets the client version
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
LSClientVersion GetClientVersion() const { return m_client_version; }
|
||||
|
||||
/**
|
||||
* Gets the connection for this client
|
||||
*
|
||||
|
||||
+119
-12
@@ -7,6 +7,79 @@ 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) {
|
||||
if (File::Exists(path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto f = fopen(path.c_str(), "w");
|
||||
if (f) {
|
||||
fprintf(f, "#EQEmu Public Login Server OPCodes\n");
|
||||
fprintf(f, "OP_SessionReady=0x0001\n");
|
||||
fprintf(f, "OP_Login=0x0002\n");
|
||||
fprintf(f, "OP_ServerListRequest=0x0004\n");
|
||||
fprintf(f, "OP_PlayEverquestRequest=0x000d\n");
|
||||
fprintf(f, "OP_PlayEverquestResponse=0x0021\n");
|
||||
fprintf(f, "OP_ChatMessage=0x0016\n");
|
||||
fprintf(f, "OP_LoginAccepted=0x0017\n");
|
||||
fprintf(f, "OP_ServerListResponse=0x0018\n");
|
||||
fprintf(f, "OP_Poll=0x0029\n");
|
||||
fprintf(f, "OP_EnterChat=0x000f\n");
|
||||
fprintf(f, "OP_PollResponse=0x0011\n");
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
|
||||
void CheckSoDOpcodeFile(const std::string& path) {
|
||||
if (File::Exists(path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto f = fopen(path.c_str(), "w");
|
||||
if (f) {
|
||||
fprintf(f, "#EQEmu Public Login Server OPCodes\n");
|
||||
fprintf(f, "OP_SessionReady=0x0001\n");
|
||||
fprintf(f, "OP_Login=0x0002\n");
|
||||
fprintf(f, "OP_ServerListRequest=0x0004\n");
|
||||
fprintf(f, "OP_PlayEverquestRequest=0x000d\n");
|
||||
fprintf(f, "OP_PlayEverquestResponse=0x0022\n");
|
||||
fprintf(f, "OP_ChatMessage=0x0017\n");
|
||||
fprintf(f, "OP_LoginAccepted=0x0018\n");
|
||||
fprintf(f, "OP_ServerListResponse=0x0019\n");
|
||||
fprintf(f, "OP_Poll=0x0029\n");
|
||||
fprintf(f, "OP_LoginExpansionPacketData=0x0031\n");
|
||||
fprintf(f, "OP_EnterChat=0x000f\n");
|
||||
fprintf(f, "OP_PollResponse=0x0011\n");
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
|
||||
void CheckLarionOpcodeFile(const std::string& path) {
|
||||
if (File::Exists(path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto f = fopen(path.c_str(), "w");
|
||||
if (f) {
|
||||
fprintf(f, "#EQEmu Public Login Server OPCodes\n");
|
||||
fprintf(f, "OP_SessionReady=0x0001\n");
|
||||
fprintf(f, "OP_Login=0x0002\n");
|
||||
fprintf(f, "OP_ServerListRequest=0x0004\n");
|
||||
fprintf(f, "OP_PlayEverquestRequest=0x000d\n");
|
||||
fprintf(f, "OP_PlayEverquestResponse=0x0022\n");
|
||||
fprintf(f, "OP_ChatMessage=0x0017\n");
|
||||
fprintf(f, "OP_LoginAccepted=0x0018\n");
|
||||
fprintf(f, "OP_ServerListResponse=0x0019\n");
|
||||
fprintf(f, "OP_Poll=0x0029\n");
|
||||
fprintf(f, "OP_EnterChat=0x000f\n");
|
||||
fprintf(f, "OP_PollResponse=0x0011\n");
|
||||
fprintf(f, "OP_SystemFingerprint=0x0016\n");
|
||||
fprintf(f, "OP_ExpansionList=0x0030\n");
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
|
||||
ClientManager::ClientManager()
|
||||
{
|
||||
@@ -19,14 +92,12 @@ ClientManager::ClientManager()
|
||||
|
||||
std::string opcodes_path = fmt::format(
|
||||
"{}/{}",
|
||||
path.GetServerPath(),
|
||||
server.config.GetVariableString(
|
||||
"client_configuration",
|
||||
"titanium_opcodes",
|
||||
"login_opcodes.conf"
|
||||
)
|
||||
path.GetOpcodePath(),
|
||||
"login_opcodes.conf"
|
||||
);
|
||||
|
||||
CheckTitaniumOpcodeFile(opcodes_path);
|
||||
|
||||
if (!titanium_ops->LoadOpcodes(opcodes_path.c_str())) {
|
||||
LogError(
|
||||
"ClientManager fatal error: couldn't load opcodes for Titanium file [{0}]",
|
||||
@@ -58,14 +129,12 @@ ClientManager::ClientManager()
|
||||
|
||||
opcodes_path = fmt::format(
|
||||
"{}/{}",
|
||||
path.GetServerPath(),
|
||||
server.config.GetVariableString(
|
||||
"client_configuration",
|
||||
"sod_opcodes",
|
||||
"login_opcodes.conf"
|
||||
)
|
||||
path.GetOpcodePath(),
|
||||
"login_opcodes_sod.conf"
|
||||
);
|
||||
|
||||
CheckSoDOpcodeFile(opcodes_path);
|
||||
|
||||
if (!sod_ops->LoadOpcodes(opcodes_path.c_str())) {
|
||||
LogError(
|
||||
"ClientManager fatal error: couldn't load opcodes for SoD file {0}",
|
||||
@@ -88,6 +157,44 @@ ClientManager::ClientManager()
|
||||
clients.push_back(c);
|
||||
}
|
||||
);
|
||||
|
||||
int larion_port = server.config.GetVariableInt("client_configuration", "larion_port", 15900);
|
||||
|
||||
EQStreamManagerInterfaceOptions larion_opts(larion_port, false, false);
|
||||
|
||||
larion_stream = new EQ::Net::EQStreamManager(larion_opts);
|
||||
larion_ops = new RegularOpcodeManager;
|
||||
|
||||
opcodes_path = fmt::format(
|
||||
"{}/{}",
|
||||
path.GetOpcodePath(),
|
||||
"login_opcodes_larion.conf"
|
||||
);
|
||||
|
||||
CheckLarionOpcodeFile(opcodes_path);
|
||||
|
||||
if (!larion_ops->LoadOpcodes(opcodes_path.c_str())) {
|
||||
LogError(
|
||||
"ClientManager fatal error: couldn't load opcodes for Larion file [{0}]",
|
||||
server.config.GetVariableString("client_configuration", "larion_opcodes", "login_opcodes.conf")
|
||||
);
|
||||
|
||||
run_server = false;
|
||||
}
|
||||
|
||||
larion_stream->OnNewConnection(
|
||||
[this](std::shared_ptr<EQ::Net::EQStream> stream) {
|
||||
LogInfo(
|
||||
"New Larion client connection from [{0}:{1}]",
|
||||
long2ip(stream->GetRemoteIP()),
|
||||
stream->GetRemotePort()
|
||||
);
|
||||
|
||||
stream->SetOpcodeManager(&larion_ops);
|
||||
Client* c = new Client(stream, cv_larion);
|
||||
clients.push_back(c);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
ClientManager::~ClientManager()
|
||||
|
||||
@@ -55,6 +55,8 @@ private:
|
||||
EQ::Net::EQStreamManager *titanium_stream;
|
||||
OpcodeManager *sod_ops;
|
||||
EQ::Net::EQStreamManager *sod_stream;
|
||||
OpcodeManager *larion_ops;
|
||||
EQ::Net::EQStreamManager* larion_stream;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -83,7 +83,8 @@ struct PlayEverquestResponse_Struct {
|
||||
|
||||
enum LSClientVersion {
|
||||
cv_titanium,
|
||||
cv_sod
|
||||
cv_sod,
|
||||
cv_larion
|
||||
};
|
||||
|
||||
enum LSClientStatus {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
#EQEmu Public Login Server OPCodes
|
||||
OP_SessionReady=0x0001
|
||||
OP_Login=0x0002
|
||||
OP_ServerListRequest=0x0004
|
||||
OP_PlayEverquestRequest=0x000d
|
||||
OP_PlayEverquestResponse=0x0022
|
||||
OP_ChatMessage=0x0017
|
||||
OP_LoginAccepted=0x0018
|
||||
OP_ServerListResponse=0x0019
|
||||
OP_Poll=0x0029
|
||||
OP_EnterChat=0x000f
|
||||
OP_PollResponse=0x0011
|
||||
OP_SystemFingerprint=0x0016
|
||||
OP_ExpansionList=0x0030
|
||||
@@ -137,7 +137,7 @@ std::unique_ptr<EQApplicationPacket> ServerManager::CreateServerListPacket(Clien
|
||||
use_local_ip ? "Local" : "Remote"
|
||||
);
|
||||
|
||||
world_server->SerializeForClientServerList(buf, use_local_ip);
|
||||
world_server->SerializeForClientServerList(buf, use_local_ip, client->GetClientVersion());
|
||||
}
|
||||
|
||||
return std::make_unique<EQApplicationPacket>(OP_ServerListResponse, buf);
|
||||
|
||||
@@ -977,7 +977,7 @@ bool WorldServer::ValidateWorldServerAdminLogin(
|
||||
return false;
|
||||
}
|
||||
|
||||
void WorldServer::SerializeForClientServerList(SerializeBuffer &out, bool use_local_ip) const
|
||||
void WorldServer::SerializeForClientServerList(SerializeBuffer& out, bool use_local_ip, LSClientVersion version) const
|
||||
{
|
||||
// see LoginClientServerData_Struct
|
||||
if (use_local_ip) {
|
||||
@@ -987,19 +987,31 @@ void WorldServer::SerializeForClientServerList(SerializeBuffer &out, bool use_lo
|
||||
out.WriteString(GetRemoteIP());
|
||||
}
|
||||
|
||||
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;
|
||||
if (version == cv_larion) {
|
||||
out.WriteUInt32(9000);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
if (version == cv_larion) {
|
||||
auto server_id = GetServerId();
|
||||
//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(GetServerId());
|
||||
out.WriteString(GetServerLongName());
|
||||
out.WriteString("us"); // country code
|
||||
out.WriteString("en"); // language code
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "../common/packet_dump.h"
|
||||
#include "database.h"
|
||||
#include "../common/event/timer.h"
|
||||
#include "login_types.h"
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
@@ -149,7 +150,7 @@ public:
|
||||
bool HandleNewLoginserverRegisteredOnly(Database::DbWorldRegistration &world_registration);
|
||||
bool HandleNewLoginserverInfoUnregisteredAllowed(Database::DbWorldRegistration &world_registration);
|
||||
|
||||
void SerializeForClientServerList(class SerializeBuffer& out, bool use_local_ip) const;
|
||||
void SerializeForClientServerList(class SerializeBuffer& out, bool use_local_ip, LSClientVersion version) const;
|
||||
|
||||
private:
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "eqemu-server",
|
||||
"version": "22.52.0",
|
||||
"version": "22.58.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/EQEmu/Server.git"
|
||||
|
||||
@@ -433,6 +433,7 @@ OP_TraderShop=0x31df
|
||||
OP_TraderBulkSend=0x6a96
|
||||
OP_Trader=0x4ef5
|
||||
OP_Barter=0x243a
|
||||
OP_BuyerItems=0x1a6a
|
||||
OP_TraderBuy=0x0000
|
||||
OP_ShopItem=0x0000
|
||||
OP_BazaarInspect=0x0000
|
||||
|
||||
@@ -287,6 +287,7 @@ void Adventure::Finished(AdventureWinStatus ws)
|
||||
auto character_id = database.GetCharacterID(*iter);
|
||||
|
||||
if (character_id == 0) {
|
||||
++iter;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
+279
-234
@@ -528,276 +528,321 @@ void ClientList::SendOnlineGuildMembers(uint32 FromID, uint32 GuildID)
|
||||
safe_delete(pack);
|
||||
}
|
||||
|
||||
|
||||
void ClientList::SendWhoAll(uint32 fromid,const char* to, int16 admin, Who_All_Struct* whom, WorldTCPConnection* connection) {
|
||||
try{
|
||||
LinkedListIterator<ClientListEntry*> iterator(clientlist);
|
||||
LinkedListIterator<ClientListEntry*> countclients(clientlist);
|
||||
ClientListEntry* cle = 0;
|
||||
ClientListEntry* countcle = 0;
|
||||
//char tmpgm[25] = "";
|
||||
//char accinfo[150] = "";
|
||||
char line[300] = "";
|
||||
//char tmpguild[50] = "";
|
||||
//char LFG[10] = "";
|
||||
//uint32 x = 0;
|
||||
int whomlen = 0;
|
||||
if (whom) {
|
||||
// fixes for client converting some queries into a race query instead of zone
|
||||
if (whom->wrace == 221) {
|
||||
whom->wrace = 0xFFFF;
|
||||
strcpy(whom->whom, "scarlet");
|
||||
}
|
||||
if (whom->wrace == 327) {
|
||||
whom->wrace = 0xFFFF;
|
||||
strcpy(whom->whom, "crystal");
|
||||
}
|
||||
if (whom->wrace == 103) {
|
||||
whom->wrace = 0xFFFF;
|
||||
strcpy(whom->whom, "kedge");
|
||||
}
|
||||
if (whom->wrace == 230) {
|
||||
whom->wrace = 0xFFFF;
|
||||
strcpy(whom->whom, "akheva");
|
||||
}
|
||||
if (whom->wrace == 229) {
|
||||
whom->wrace = 0xFFFF;
|
||||
strcpy(whom->whom, "netherbian");
|
||||
}
|
||||
try {
|
||||
LinkedListIterator<ClientListEntry*> iterator(clientlist);
|
||||
LinkedListIterator<ClientListEntry*> countclients(clientlist);
|
||||
ClientListEntry* cle = 0;
|
||||
ClientListEntry* countcle = 0;
|
||||
//char tmpgm[25] = "";
|
||||
//char accinfo[150] = "";
|
||||
char line[300] = "";
|
||||
//char tmpguild[50] = "";
|
||||
//char LFG[10] = "";
|
||||
//uint32 x = 0;
|
||||
int whomlen = 0;
|
||||
|
||||
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.
|
||||
}
|
||||
|
||||
uint32 totalusers=0;
|
||||
uint32 totallength=0;
|
||||
countclients.Reset();
|
||||
while(countclients.MoreElements()){
|
||||
countcle = countclients.GetData();
|
||||
const char* tmpZone = ZoneName(countcle->zone());
|
||||
if (
|
||||
(countcle->Online() >= CLE_Status::Zoning) &&
|
||||
(!countcle->GetGM() || countcle->Anon() != 1 || admin >= countcle->Admin()) &&
|
||||
(whom == 0 || (
|
||||
((countcle->Admin() >= AccountStatus::QuestTroupe && countcle->GetGM()) || whom->gmlookup == 0xFFFF) &&
|
||||
(whom->lvllow == 0xFFFF || (countcle->level() >= whom->lvllow && countcle->level() <= whom->lvlhigh && (countcle->Anon()==0 || admin > countcle->Admin()))) &&
|
||||
(whom->wclass == 0xFFFF || (countcle->class_() == whom->wclass && (countcle->Anon()==0 || admin > countcle->Admin()))) &&
|
||||
(whom->wrace == 0xFFFF || (countcle->race() == whom->wrace && (countcle->Anon()==0 || admin > countcle->Admin()))) &&
|
||||
(whomlen == 0 || (
|
||||
(tmpZone != 0 && strncasecmp(tmpZone, whom->whom, whomlen) == 0) ||
|
||||
strncasecmp(countcle->name(),whom->whom, whomlen) == 0 ||
|
||||
(strncasecmp(guild_mgr.GetGuildName(countcle->GuildID()), whom->whom, whomlen) == 0) ||
|
||||
(admin >= AccountStatus::GMAdmin && strncasecmp(countcle->AccountName(), whom->whom, whomlen) == 0)
|
||||
))
|
||||
))
|
||||
) {
|
||||
if((countcle->Anon()>0 && admin >= countcle->Admin() && admin > AccountStatus::Player) || countcle->Anon()==0 ){
|
||||
totalusers++;
|
||||
if(totalusers<=20 || admin >= AccountStatus::GMAdmin)
|
||||
totallength=totallength+strlen(countcle->name())+strlen(countcle->AccountName())+strlen(guild_mgr.GetGuildName(countcle->GuildID()))+5;
|
||||
if (whom) {
|
||||
// fixes for client converting some queries into a race query instead of zone
|
||||
if (whom->wrace == 221) {
|
||||
whom->wrace = 0xFFFF;
|
||||
strcpy(whom->whom, "scarlet");
|
||||
}
|
||||
else if((countcle->Anon()>0 && admin<=countcle->Admin()) || (countcle->Anon()==0 && !countcle->GetGM())) {
|
||||
totalusers++;
|
||||
if(totalusers<=20 || admin >= AccountStatus::GMAdmin)
|
||||
totallength=totallength+strlen(countcle->name())+strlen(guild_mgr.GetGuildName(countcle->GuildID()))+5;
|
||||
|
||||
if (whom->wrace == 327) {
|
||||
whom->wrace = 0xFFFF;
|
||||
strcpy(whom->whom, "crystal");
|
||||
}
|
||||
|
||||
if (whom->wrace == 103) {
|
||||
whom->wrace = 0xFFFF;
|
||||
strcpy(whom->whom, "kedge");
|
||||
}
|
||||
|
||||
if (whom->wrace == 230) {
|
||||
whom->wrace = 0xFFFF;
|
||||
strcpy(whom->whom, "akheva");
|
||||
}
|
||||
|
||||
if (whom->wrace == 229) {
|
||||
whom->wrace = 0xFFFF;
|
||||
strcpy(whom->whom, "netherbian");
|
||||
}
|
||||
|
||||
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.
|
||||
}
|
||||
}
|
||||
countclients.Advance();
|
||||
}
|
||||
uint32 plid=fromid;
|
||||
uint32 playerineqstring=5001;
|
||||
const char line2[]="---------------------------";
|
||||
uint8 unknown35=0x0A;
|
||||
uint32 unknown36=0;
|
||||
uint32 playersinzonestring=5028;
|
||||
if(totalusers>20 && admin<AccountStatus::GMAdmin){
|
||||
totalusers=20;
|
||||
playersinzonestring=5033;
|
||||
}
|
||||
else if(totalusers>1)
|
||||
playersinzonestring=5036;
|
||||
uint32 unknown44[2];
|
||||
unknown44[0]=0;
|
||||
unknown44[1]=0;
|
||||
uint32 unknown52=totalusers;
|
||||
uint32 unknown56=1;
|
||||
auto pack2 = new ServerPacket(ServerOP_WhoAllReply, 64 + totallength + (49 * totalusers));
|
||||
memset(pack2->pBuffer,0,pack2->size);
|
||||
uchar *buffer=pack2->pBuffer;
|
||||
uchar *bufptr=buffer;
|
||||
//memset(buffer,0,pack2->size);
|
||||
memcpy(bufptr,&plid, sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
memcpy(bufptr,&playerineqstring, sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
memcpy(bufptr,&line2, strlen(line2));
|
||||
bufptr+=strlen(line2);
|
||||
memcpy(bufptr,&unknown35, sizeof(uint8));
|
||||
bufptr+=sizeof(uint8);
|
||||
memcpy(bufptr,&unknown36, sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
memcpy(bufptr,&playersinzonestring, sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
memcpy(bufptr,&unknown44[0], sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
memcpy(bufptr,&unknown44[1], sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
memcpy(bufptr,&unknown52, sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
memcpy(bufptr,&unknown56, sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
memcpy(bufptr,&totalusers, sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
|
||||
iterator.Reset();
|
||||
int idx=-1;
|
||||
while(iterator.MoreElements()) {
|
||||
cle = iterator.GetData();
|
||||
const char* tmpZone = ZoneName(cle->zone());
|
||||
uint32 totalusers=0;
|
||||
uint32 totallength=0;
|
||||
countclients.Reset();
|
||||
while (countclients.MoreElements()) {
|
||||
countcle = countclients.GetData();
|
||||
const char* tmpZone = ZoneName(countcle->zone());
|
||||
if (
|
||||
(countcle->Online() >= CLE_Status::Zoning) &&
|
||||
(!countcle->GetGM() || countcle->Anon() != 1 || admin >= countcle->Admin()) &&
|
||||
(whom == 0 || (
|
||||
((countcle->Admin() >= AccountStatus::QuestTroupe && countcle->GetGM()) || whom->gmlookup == 0xFFFF) &&
|
||||
(whom->lvllow == 0xFFFF ||
|
||||
(countcle->level() >= whom->lvllow && countcle->level() <= whom->lvlhigh &&
|
||||
(countcle->Anon() == 0 || admin > countcle->Admin()))) &&
|
||||
(whom->wclass == 0xFFFF || (countcle->class_() == whom->wclass &&
|
||||
(countcle->Anon() == 0 || admin > countcle->Admin()))) &&
|
||||
(whom->wrace == 0xFFFF ||
|
||||
(countcle->race() == whom->wrace && (countcle->Anon() == 0 || admin > countcle->Admin()))) &&
|
||||
(whomlen == 0 || (
|
||||
(tmpZone != 0 && strncasecmp(tmpZone, whom->whom, whomlen) == 0) ||
|
||||
strncasecmp(countcle->name(),whom->whom, whomlen) == 0 ||
|
||||
(strncasecmp(guild_mgr.GetGuildName(countcle->GuildID()), whom->whom, whomlen) == 0) ||
|
||||
(admin >= AccountStatus::GMAdmin && strncasecmp(countcle->AccountName(), whom->whom, whomlen) == 0)
|
||||
))
|
||||
))
|
||||
) {
|
||||
// these blocks can all be condensed but it's simpler to conceptualize this way
|
||||
if ((countcle->Anon()>0 && admin >= countcle->Admin() && admin > AccountStatus::Player) || countcle->Anon()==0 ) {
|
||||
totalusers++;
|
||||
if (totalusers<=20 || admin >= AccountStatus::GMAdmin) {
|
||||
totallength = totallength + strlen(countcle->name()) + strlen(countcle->AccountName()) +
|
||||
strlen(guild_mgr.GetGuildName(countcle->GuildID())) + 5;
|
||||
}
|
||||
} else if (((countcle->Anon() == 1 && admin <= countcle->Admin()) && whomlen != 0 &&
|
||||
strncasecmp(countcle->name(), whom->whom, whomlen) == 0)) {
|
||||
totalusers++;
|
||||
if (totalusers <= 20 || admin >= AccountStatus::GMAdmin) {
|
||||
totallength = totallength + strlen(countcle->name()) + strlen(countcle->AccountName()) +
|
||||
strlen(guild_mgr.GetGuildName(countcle->GuildID())) + 5;
|
||||
}
|
||||
} else if (((countcle->Anon() == 2 && admin <= countcle->Admin()) && whomlen != 0 &&
|
||||
(strncasecmp(countcle->name(), whom->whom, whomlen) == 0 ||
|
||||
strncasecmp(guild_mgr.GetGuildName(countcle->GuildID()), whom->whom, whomlen) == 0))) {
|
||||
totalusers++;
|
||||
if (totalusers <= 20 || admin >= AccountStatus::GMAdmin) {
|
||||
totallength = totallength + strlen(countcle->name()) + strlen(countcle->AccountName()) +
|
||||
strlen(guild_mgr.GetGuildName(countcle->GuildID())) + 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
countclients.Advance();
|
||||
}
|
||||
|
||||
if (
|
||||
(cle->Online() >= CLE_Status::Zoning) &&
|
||||
(!cle->GetGM() || cle->Anon() != 1 || admin >= cle->Admin()) &&
|
||||
(whom == 0 || (
|
||||
((cle->Admin() >= AccountStatus::QuestTroupe && cle->GetGM()) || whom->gmlookup == 0xFFFF) &&
|
||||
(whom->lvllow == 0xFFFF || (cle->level() >= whom->lvllow && cle->level() <= whom->lvlhigh && (cle->Anon()==0 || admin>cle->Admin()))) &&
|
||||
(whom->wclass == 0xFFFF || (cle->class_() == whom->wclass && (cle->Anon()==0 || admin>cle->Admin()))) &&
|
||||
(whom->wrace == 0xFFFF || (cle->race() == whom->wrace && (cle->Anon()==0 || admin>cle->Admin()))) &&
|
||||
(whomlen == 0 || (
|
||||
(tmpZone != 0 && strncasecmp(tmpZone, whom->whom, whomlen) == 0) ||
|
||||
strncasecmp(cle->name(),whom->whom, whomlen) == 0 ||
|
||||
(strncasecmp(guild_mgr.GetGuildName(cle->GuildID()), whom->whom, whomlen) == 0) ||
|
||||
(admin >= AccountStatus::GMAdmin && strncasecmp(cle->AccountName(), whom->whom, whomlen) == 0)
|
||||
))
|
||||
))
|
||||
) {
|
||||
line[0] = 0;
|
||||
uint32 rankstring = 0xFFFFFFFF;
|
||||
if((cle->Anon()==1 && cle->GetGM() && cle->Admin()>admin) || (idx>=20 && admin < AccountStatus::GMAdmin)){ //hide gms that are anon from lesser gms and normal players, cut off at 20
|
||||
uint32 plid=fromid;
|
||||
uint32 playerineqstring=5001;
|
||||
const char line2[]="---------------------------";
|
||||
uint8 unknown35=0x0A;
|
||||
uint32 unknown36=0;
|
||||
uint32 playersinzonestring=5028;
|
||||
|
||||
if (totalusers>20 && admin<AccountStatus::GMAdmin) {
|
||||
totalusers=20;
|
||||
playersinzonestring=5033;
|
||||
} else if(totalusers>1) {
|
||||
playersinzonestring=5036;
|
||||
}
|
||||
|
||||
uint32 unknown44[2];
|
||||
unknown44[0]=0;
|
||||
unknown44[1]=0;
|
||||
uint32 unknown52=totalusers;
|
||||
uint32 unknown56=1;
|
||||
auto pack2 = new ServerPacket(ServerOP_WhoAllReply, 64 + totallength + (49 * totalusers));
|
||||
memset(pack2->pBuffer,0,pack2->size);
|
||||
uchar *buffer=pack2->pBuffer;
|
||||
uchar *bufptr=buffer;
|
||||
//memset(buffer,0,pack2->size);
|
||||
memcpy(bufptr,&plid, sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
memcpy(bufptr,&playerineqstring, sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
memcpy(bufptr,&line2, strlen(line2));
|
||||
bufptr+=strlen(line2);
|
||||
memcpy(bufptr,&unknown35, sizeof(uint8));
|
||||
bufptr+=sizeof(uint8);
|
||||
memcpy(bufptr,&unknown36, sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
memcpy(bufptr,&playersinzonestring, sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
memcpy(bufptr,&unknown44[0], sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
memcpy(bufptr,&unknown44[1], sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
memcpy(bufptr,&unknown52, sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
memcpy(bufptr,&unknown56, sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
memcpy(bufptr,&totalusers, sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
|
||||
iterator.Reset();
|
||||
int idx=-1;
|
||||
while(iterator.MoreElements()) {
|
||||
cle = iterator.GetData();
|
||||
const char* tmpZone = ZoneName(cle->zone());
|
||||
|
||||
if (
|
||||
(cle->Online() >= CLE_Status::Zoning) &&
|
||||
(!cle->GetGM() || cle->Anon() != 1 || admin >= cle->Admin()) &&
|
||||
(whom == 0 || (
|
||||
((cle->Admin() >= AccountStatus::QuestTroupe && cle->GetGM()) || whom->gmlookup == 0xFFFF) &&
|
||||
(whom->lvllow == 0xFFFF || (cle->level() >= whom->lvllow && cle->level() <= whom->lvlhigh && (cle->Anon()==0 || admin>cle->Admin()))) &&
|
||||
(whom->wclass == 0xFFFF || (cle->class_() == whom->wclass && (cle->Anon()==0 || admin>cle->Admin()))) &&
|
||||
(whom->wrace == 0xFFFF || (cle->race() == whom->wrace && (cle->Anon()==0 || admin>cle->Admin()))) &&
|
||||
(whomlen == 0 || (
|
||||
(tmpZone != 0 && strncasecmp(tmpZone, whom->whom, whomlen) == 0) ||
|
||||
strncasecmp(cle->name(),whom->whom, whomlen) == 0 ||
|
||||
(strncasecmp(guild_mgr.GetGuildName(cle->GuildID()), whom->whom, whomlen) == 0) ||
|
||||
(admin >= AccountStatus::GMAdmin && strncasecmp(cle->AccountName(), whom->whom, whomlen) == 0)
|
||||
))
|
||||
))
|
||||
) {
|
||||
line[0] = 0;
|
||||
uint32 rankstring = 0xFFFFFFFF;
|
||||
// These lines can be simplified but easier to conceptualize this way
|
||||
if ((cle->Anon()==1 && cle->GetGM() && cle->Admin()>admin) || (idx>=20 && admin < AccountStatus::GMAdmin)) { //hide gms that are anon from lesser gms and normal players, cut off at 20
|
||||
rankstring = 0;
|
||||
iterator.Advance();
|
||||
continue;
|
||||
} else if (cle->Anon() == 1 && cle->Admin()>=admin && (whomlen == 0 || (whomlen !=0 && strncasecmp(cle->name(), whom->whom, whomlen) != 0))) {
|
||||
rankstring = 0;
|
||||
iterator.Advance();
|
||||
continue;
|
||||
} else if (cle->Anon() == 2 && cle->Admin()>=admin && (whomlen == 0 || (whomlen !=0 && strncasecmp(cle->name(), whom->whom, whomlen) != 0 && strncasecmp(guild_mgr.GetGuildName(cle->GuildID()), whom->whom, whomlen) != 0))) {
|
||||
rankstring = 0;
|
||||
iterator.Advance();
|
||||
continue;
|
||||
} else if (cle->GetGM()) {
|
||||
if (cle->Admin() >= AccountStatus::GMImpossible)
|
||||
if (cle->Admin() >= AccountStatus::GMImpossible) {
|
||||
rankstring = 5021;
|
||||
else if (cle->Admin() >= AccountStatus::GMMgmt)
|
||||
} else if (cle->Admin() >= AccountStatus::GMMgmt) {
|
||||
rankstring = 5020;
|
||||
else if (cle->Admin() >= AccountStatus::GMCoder)
|
||||
} else if (cle->Admin() >= AccountStatus::GMCoder) {
|
||||
rankstring = 5019;
|
||||
else if (cle->Admin() >= AccountStatus::GMAreas)
|
||||
} else if (cle->Admin() >= AccountStatus::GMAreas) {
|
||||
rankstring = 5018;
|
||||
else if (cle->Admin() >= AccountStatus::QuestMaster)
|
||||
} else if (cle->Admin() >= AccountStatus::QuestMaster) {
|
||||
rankstring = 5017;
|
||||
else if (cle->Admin() >= AccountStatus::GMLeadAdmin)
|
||||
} else if (cle->Admin() >= AccountStatus::GMLeadAdmin) {
|
||||
rankstring = 5016;
|
||||
else if (cle->Admin() >= AccountStatus::GMAdmin)
|
||||
} else if (cle->Admin() >= AccountStatus::GMAdmin) {
|
||||
rankstring = 5015;
|
||||
else if (cle->Admin() >= AccountStatus::GMStaff)
|
||||
} else if (cle->Admin() >= AccountStatus::GMStaff) {
|
||||
rankstring = 5014;
|
||||
else if (cle->Admin() >= AccountStatus::EQSupport)
|
||||
} else if (cle->Admin() >= AccountStatus::EQSupport) {
|
||||
rankstring = 5013;
|
||||
else if (cle->Admin() >= AccountStatus::GMTester)
|
||||
} else if (cle->Admin() >= AccountStatus::GMTester) {
|
||||
rankstring = 5012;
|
||||
else if (cle->Admin() >= AccountStatus::SeniorGuide)
|
||||
} else if (cle->Admin() >= AccountStatus::SeniorGuide) {
|
||||
rankstring = 5011;
|
||||
else if (cle->Admin() >= AccountStatus::QuestTroupe)
|
||||
} else if (cle->Admin() >= AccountStatus::QuestTroupe) {
|
||||
rankstring = 5010;
|
||||
else if (cle->Admin() >= AccountStatus::Guide)
|
||||
} else if (cle->Admin() >= AccountStatus::Guide) {
|
||||
rankstring = 5009;
|
||||
else if (cle->Admin() >= AccountStatus::ApprenticeGuide)
|
||||
} else if (cle->Admin() >= AccountStatus::ApprenticeGuide) {
|
||||
rankstring = 5008;
|
||||
else if (cle->Admin() >= AccountStatus::Steward)
|
||||
} else if (cle->Admin() >= AccountStatus::Steward) {
|
||||
rankstring = 5007;
|
||||
}
|
||||
}
|
||||
idx++;
|
||||
char guildbuffer[67]={0};
|
||||
if (cle->GuildID() != GUILD_NONE && cle->GuildID()>0)
|
||||
sprintf(guildbuffer,"<%s>", guild_mgr.GetGuildName(cle->GuildID()));
|
||||
uint32 formatstring=5025;
|
||||
if(cle->Anon()==1 && (admin<cle->Admin() || admin == AccountStatus::Player))
|
||||
formatstring=5024;
|
||||
else if(cle->Anon()==1 && admin>=cle->Admin() && admin > AccountStatus::Player)
|
||||
formatstring=5022;
|
||||
else if(cle->Anon()==2 && (admin<cle->Admin() || admin == AccountStatus::Player))
|
||||
formatstring=5023;//display guild
|
||||
else if(cle->Anon()==2 && admin>=cle->Admin() && admin > AccountStatus::Player)
|
||||
formatstring=5022;//display everything
|
||||
|
||||
//war* wars2 = (war*)pack2->pBuffer;
|
||||
idx++;
|
||||
char guildbuffer[67]={0};
|
||||
|
||||
uint32 plclass_=0;
|
||||
uint32 pllevel=0;
|
||||
uint32 pidstring=0xFFFFFFFF;//5003;
|
||||
uint32 plrace=0;
|
||||
uint32 zonestring=0xFFFFFFFF;
|
||||
uint32 plzone=0;
|
||||
uint32 unknown80[2];
|
||||
if(cle->Anon()==0 || (admin>=cle->Admin() && admin> AccountStatus::Player)){
|
||||
plclass_=cle->class_();
|
||||
pllevel=cle->level();
|
||||
if(admin>=AccountStatus::GMAdmin)
|
||||
pidstring=5003;
|
||||
plrace=cle->race();
|
||||
zonestring=5006;
|
||||
plzone=cle->zone();
|
||||
}
|
||||
if (cle->GuildID() != GUILD_NONE && cle->GuildID()>0) {
|
||||
sprintf(guildbuffer,"<%s>", guild_mgr.GetGuildName(cle->GuildID()));
|
||||
}
|
||||
|
||||
uint32 formatstring=5025;
|
||||
|
||||
if(admin>=cle->Admin() && admin > AccountStatus::Player)
|
||||
unknown80[0]=cle->Admin();
|
||||
else
|
||||
unknown80[0]=0xFFFFFFFF;
|
||||
unknown80[1]=0xFFFFFFFF;//1035
|
||||
if (cle->Anon()==1 && (admin<cle->Admin() || admin == AccountStatus::Player)) {
|
||||
formatstring=5024;
|
||||
} else if(cle->Anon()==1 && admin>=cle->Admin() && admin > AccountStatus::Player) {
|
||||
formatstring=5022;
|
||||
} else if(cle->Anon()==2 && (admin<cle->Admin() || admin == AccountStatus::Player)) {
|
||||
formatstring=5023;//display guild
|
||||
} else if(cle->Anon()==2 && admin>=cle->Admin() && admin > AccountStatus::Player) {
|
||||
formatstring=5022;//display everything
|
||||
}
|
||||
|
||||
//war* wars2 = (war*)pack2->pBuffer;
|
||||
|
||||
//char plstatus[20]={0};
|
||||
//sprintf(plstatus, "Status %i",cle->Admin());
|
||||
char plname[64]={0};
|
||||
strcpy(plname,cle->name());
|
||||
uint32 plclass_=0;
|
||||
uint32 pllevel=0;
|
||||
uint32 pidstring=0xFFFFFFFF;//5003;
|
||||
uint32 plrace=0;
|
||||
uint32 zonestring=0xFFFFFFFF;
|
||||
uint32 plzone=0;
|
||||
uint32 unknown80[2];
|
||||
|
||||
char placcount[30]={0};
|
||||
if(admin>=cle->Admin() && admin > AccountStatus::Player)
|
||||
strcpy(placcount,cle->AccountName());
|
||||
if (cle->Anon()==0 || (admin>=cle->Admin() && admin> AccountStatus::Player)) {
|
||||
plclass_=cle->class_();
|
||||
pllevel=cle->level();
|
||||
|
||||
memcpy(bufptr,&formatstring, sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
memcpy(bufptr,&pidstring, sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
memcpy(bufptr,&plname, strlen(plname)+1);
|
||||
bufptr+=strlen(plname)+1;
|
||||
memcpy(bufptr,&rankstring, sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
memcpy(bufptr,&guildbuffer, strlen(guildbuffer)+1);
|
||||
bufptr+=strlen(guildbuffer)+1;
|
||||
memcpy(bufptr,&unknown80[0], sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
memcpy(bufptr,&unknown80[1], sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
memcpy(bufptr,&zonestring, sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
memcpy(bufptr,&plzone, sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
memcpy(bufptr,&plclass_, sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
memcpy(bufptr,&pllevel, sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
memcpy(bufptr,&plrace, sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
uint32 ending=0;
|
||||
memcpy(bufptr,&placcount, strlen(placcount)+1);
|
||||
bufptr+=strlen(placcount)+1;
|
||||
ending=207;
|
||||
memcpy(bufptr,&ending, sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
if(admin>=AccountStatus::GMAdmin) {
|
||||
pidstring=5003;
|
||||
}
|
||||
plrace=cle->race();
|
||||
zonestring=5006;
|
||||
plzone=cle->zone();
|
||||
}
|
||||
|
||||
if (admin>=cle->Admin() && admin > AccountStatus::Player) {
|
||||
unknown80[0]=cle->Admin();
|
||||
} else {
|
||||
unknown80[0]=0xFFFFFFFF;
|
||||
}
|
||||
|
||||
unknown80[1]=0xFFFFFFFF;//1035
|
||||
|
||||
//char plstatus[20]={0};
|
||||
//sprintf(plstatus, "Status %i",cle->Admin());
|
||||
char plname[64]={0};
|
||||
strcpy(plname,cle->name());
|
||||
|
||||
char placcount[30]={0};
|
||||
if (admin>=cle->Admin() && admin > AccountStatus::Player) {
|
||||
strcpy(placcount,cle->AccountName());
|
||||
}
|
||||
|
||||
memcpy(bufptr,&formatstring, sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
memcpy(bufptr,&pidstring, sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
memcpy(bufptr,&plname, strlen(plname)+1);
|
||||
bufptr+=strlen(plname)+1;
|
||||
memcpy(bufptr,&rankstring, sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
memcpy(bufptr,&guildbuffer, strlen(guildbuffer)+1);
|
||||
bufptr+=strlen(guildbuffer)+1;
|
||||
memcpy(bufptr,&unknown80[0], sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
memcpy(bufptr,&unknown80[1], sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
memcpy(bufptr,&zonestring, sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
memcpy(bufptr,&plzone, sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
memcpy(bufptr,&plclass_, sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
memcpy(bufptr,&pllevel, sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
memcpy(bufptr,&plrace, sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
uint32 ending=0;
|
||||
memcpy(bufptr,&placcount, strlen(placcount)+1);
|
||||
bufptr+=strlen(placcount)+1;
|
||||
ending=207;
|
||||
memcpy(bufptr,&ending, sizeof(uint32));
|
||||
bufptr+=sizeof(uint32);
|
||||
}
|
||||
iterator.Advance();
|
||||
}
|
||||
iterator.Advance();
|
||||
}
|
||||
//zoneserver_list.SendPacket(pack2); // NO NO NO WHY WOULD YOU SEND IT TO EVERY ZONE SERVER?!?
|
||||
SendPacket(to,pack2);
|
||||
safe_delete(pack2);
|
||||
}
|
||||
catch(...){
|
||||
|
||||
SendPacket(to,pack2);
|
||||
safe_delete(pack2);
|
||||
} catch(...) {
|
||||
LogInfo("Unknown error in world's SendWhoAll (probably mem error), ignoring");
|
||||
return;
|
||||
}
|
||||
|
||||
+26
-8
@@ -315,6 +315,13 @@ void LoginServer::ProcessLSFatalError(uint16_t opcode, EQ::Net::Packet &p)
|
||||
error,
|
||||
reason
|
||||
);
|
||||
|
||||
if (m_legacy_client) {
|
||||
m_legacy_client.release();
|
||||
}
|
||||
else if (m_client) {
|
||||
m_client.release();
|
||||
}
|
||||
}
|
||||
|
||||
void LoginServer::ProcessSystemwideMessage(uint16_t opcode, EQ::Net::Packet &p)
|
||||
@@ -598,6 +605,11 @@ bool LoginServer::Connect()
|
||||
|
||||
void LoginServer::SendInfo()
|
||||
{
|
||||
if (m_client == nullptr && m_legacy_client == nullptr) {
|
||||
LogDebug("No client to send info to loginserver");
|
||||
return;
|
||||
}
|
||||
|
||||
const WorldConfig *Config = WorldConfig::get();
|
||||
|
||||
auto pack = new ServerPacket;
|
||||
@@ -643,6 +655,11 @@ void LoginServer::SendInfo()
|
||||
|
||||
void LoginServer::SendStatus()
|
||||
{
|
||||
if (m_client == nullptr && m_legacy_client == nullptr) {
|
||||
LogDebug("No client to send status to loginserver");
|
||||
return;
|
||||
}
|
||||
|
||||
auto pack = new ServerPacket;
|
||||
pack->opcode = ServerOP_LSStatus;
|
||||
pack->size = sizeof(ServerLSStatus_Struct);
|
||||
@@ -671,20 +688,21 @@ void LoginServer::SendStatus()
|
||||
*/
|
||||
void LoginServer::SendPacket(ServerPacket *pack)
|
||||
{
|
||||
if (m_is_legacy) {
|
||||
if (m_legacy_client) {
|
||||
m_legacy_client->SendPacket(pack);
|
||||
}
|
||||
if (m_legacy_client) {
|
||||
m_legacy_client->SendPacket(pack);
|
||||
}
|
||||
else {
|
||||
if (m_client) {
|
||||
m_client->SendPacket(pack);
|
||||
}
|
||||
else if (m_client) {
|
||||
m_client->SendPacket(pack);
|
||||
}
|
||||
}
|
||||
|
||||
void LoginServer::SendAccountUpdate(ServerPacket *pack)
|
||||
{
|
||||
if (m_client == nullptr && m_legacy_client == nullptr) {
|
||||
LogDebug("No client to send account update to loginserver");
|
||||
return;
|
||||
}
|
||||
|
||||
auto *ls_account_update = (ServerLSAccountUpdate_Struct *) pack->pBuffer;
|
||||
if (CanUpdate()) {
|
||||
LogInfo(
|
||||
|
||||
@@ -289,6 +289,17 @@ bool WorldBoot::DatabaseLoadRoutines(int argc, char **argv)
|
||||
LogInfo("Clearing inventory snapshots");
|
||||
database.ClearInvSnapshots();
|
||||
LogInfo("Loading items");
|
||||
LogInfo("Clearing trader table details");
|
||||
database.ClearTraderDetails();
|
||||
database.ClearBuyerDetails();
|
||||
LogInfo("Clearing buyer table details");
|
||||
|
||||
if (RuleB(Bots, Enabled)) {
|
||||
LogInfo("Clearing [bot_pet_buffs] table of stale entries");
|
||||
database.QueryDatabase(
|
||||
"DELETE FROM bot_pet_buffs WHERE NOT EXISTS (SELECT * FROM bot_pets WHERE bot_pets.pets_index = bot_pet_buffs.pets_index)"
|
||||
);
|
||||
}
|
||||
|
||||
if (!content_db.LoadItems(hotfix_name)) {
|
||||
LogError("Error: Could not load item data. But ignoring");
|
||||
|
||||
+31
-1
@@ -1742,7 +1742,6 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
|
||||
}
|
||||
|
||||
zoneserver_list.SendPacketToBootedZones(pack);
|
||||
|
||||
break;
|
||||
}
|
||||
case ServerOP_BazaarPurchase: {
|
||||
@@ -1753,9 +1752,40 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
|
||||
"ServerOP_BazaarPurchase",
|
||||
in->trader_buy_struct.trader_id
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
zoneserver_list.SendPacket(Zones::BAZAAR, pack);
|
||||
break;
|
||||
}
|
||||
case ServerOP_BuyerMessaging: {
|
||||
auto in = (BuyerMessaging_Struct *)pack->pBuffer;
|
||||
switch (in->action) {
|
||||
case Barter_AddToBarterWindow:
|
||||
case Barter_RemoveFromBarterWindow: {
|
||||
if (in->buyer_id <= 0) {
|
||||
LogTrading("World Message <red>[{}] received with invalid buyer_id <red>[{}]",
|
||||
"ServerOP_BecomeBuyer",
|
||||
in->buyer_id
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
zoneserver_list.SendPacketToBootedZones(pack);
|
||||
break;
|
||||
}
|
||||
case Barter_SellItem: {
|
||||
zoneserver_list.SendPacket(Zones::BAZAAR, pack);
|
||||
break;
|
||||
}
|
||||
case Barter_FailedTransaction:
|
||||
case Barter_BuyerTransactionComplete: {
|
||||
zoneserver_list.SendPacket(in->zone_id, pack);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
default: {
|
||||
LogInfo("Unknown ServerOPcode from zone {:#04x}, size [{}]", pack->opcode, pack->size);
|
||||
|
||||
@@ -65,6 +65,7 @@ SET(zone_sources
|
||||
lua_inventory.cpp
|
||||
lua_item.cpp
|
||||
lua_iteminst.cpp
|
||||
lua_merc.cpp
|
||||
lua_mob.cpp
|
||||
lua_mod.cpp
|
||||
lua_npc.cpp
|
||||
@@ -115,6 +116,7 @@ SET(zone_sources
|
||||
perl_groups.cpp
|
||||
perl_hateentry.cpp
|
||||
perl_inventory.cpp
|
||||
perl_merc.cpp
|
||||
perl_mob.cpp
|
||||
perl_npc.cpp
|
||||
perl_object.cpp
|
||||
@@ -224,6 +226,7 @@ SET(zone_headers
|
||||
lua_inventory.h
|
||||
lua_item.h
|
||||
lua_iteminst.h
|
||||
lua_merc.h
|
||||
lua_mob.h
|
||||
lua_mod.h
|
||||
lua_npc.h
|
||||
|
||||
+5
-1
@@ -2185,7 +2185,7 @@ void Client::AutoGrantAAPoints() {
|
||||
SendAlternateAdvancementStats();
|
||||
}
|
||||
|
||||
void Client::GrantAllAAPoints(uint8 unlock_level)
|
||||
void Client::GrantAllAAPoints(uint8 unlock_level, bool skip_grant_only)
|
||||
{
|
||||
//iterate through every AA
|
||||
for (auto& aa : zone->aa_abilities) {
|
||||
@@ -2195,6 +2195,10 @@ void Client::GrantAllAAPoints(uint8 unlock_level)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ability->grant_only && skip_grant_only) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint8 level = unlock_level ? unlock_level : GetLevel();
|
||||
|
||||
AA::Rank* rank = ability->first;
|
||||
|
||||
+95
-7
@@ -222,7 +222,7 @@ void NPC::DescribeAggro(Client *to_who, Mob *mob, bool verbose) {
|
||||
if (
|
||||
GetLevel() < RuleI(Aggro, MinAggroLevel) &&
|
||||
mob->GetLevelCon(GetLevel()) == ConsiderColor::Gray &&
|
||||
GetBodyType() != BT_Undead &&
|
||||
GetBodyType() != BodyType::Undead &&
|
||||
!AlwaysAggro()
|
||||
) {
|
||||
to_who->Message(
|
||||
@@ -452,8 +452,26 @@ bool Mob::CheckWillAggro(Mob *mob) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't aggro new clients if we are already engaged unless SpecialAbility::ProximityAggro is set
|
||||
if (IsEngaged() && (!GetSpecialAbility(SpecialAbility::ProximityAggro) || (GetSpecialAbility(SpecialAbility::ProximityAggro) && !CombatRange(mob)))) {
|
||||
// Don't aggro new clients if we are already engaged unless PROX_AGGRO is set
|
||||
// Frustrated mobs (all rooted up with no one to kill)
|
||||
// will engage without PROX_AGGRO ability if someone new is close now.
|
||||
|
||||
const bool is_frustrated = IsRooted() && !CombatRange(target);
|
||||
|
||||
if (
|
||||
!is_frustrated &&
|
||||
IsEngaged() &&
|
||||
(
|
||||
(
|
||||
!GetSpecialAbility(SpecialAbility::ProximityAggro) &&
|
||||
GetBodyType() != BodyType::Undead
|
||||
) ||
|
||||
(
|
||||
GetSpecialAbility(SpecialAbility::ProximityAggro) &&
|
||||
!CombatRange(mob)
|
||||
)
|
||||
)
|
||||
) {
|
||||
LogAggro(
|
||||
"[{}] is in combat, and does not have prox_aggro, or does and is out of combat range with [{}]",
|
||||
GetName(),
|
||||
@@ -496,7 +514,7 @@ bool Mob::CheckWillAggro(Mob *mob) {
|
||||
RuleB(Aggro, UseLevelAggro) &&
|
||||
(
|
||||
GetLevel() >= RuleI(Aggro, MinAggroLevel) ||
|
||||
GetBodyType() == BT_Undead ||
|
||||
GetBodyType() == BodyType::Undead ||
|
||||
AlwaysAggro() ||
|
||||
(
|
||||
mob->IsClient() &&
|
||||
@@ -524,7 +542,7 @@ bool Mob::CheckWillAggro(Mob *mob) {
|
||||
} else {
|
||||
if (
|
||||
(
|
||||
(RuleB(Aggro, UndeadAlwaysAggro) && GetBodyType() == BT_Undead) ||
|
||||
(RuleB(Aggro, UndeadAlwaysAggro) && GetBodyType() == BodyType::Undead) ||
|
||||
(GetINT() <= RuleI(Aggro, IntAggroThreshold)) ||
|
||||
AlwaysAggro() ||
|
||||
(
|
||||
@@ -564,6 +582,76 @@ bool Mob::CheckWillAggro(Mob *mob) {
|
||||
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.
|
||||
if (!attacker) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
|
||||
for (const auto& e : npc_list) {
|
||||
NPC* n = e.second;
|
||||
if (!n || n == skipped) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float aggro_range = n->GetAggroRange();
|
||||
const float assist_range = n->GetAssistRange();
|
||||
|
||||
if (assist_range > aggro_range) {
|
||||
aggro_range = assist_range;
|
||||
}
|
||||
|
||||
// Square it because we will be using DistNoRoot
|
||||
aggro_range *= aggro_range;
|
||||
|
||||
if (DistanceSquared(n->GetPosition(), skipped->GetPosition()) > aggro_range) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& excluded = Strings::Split(RuleS(Aggro, ExcludedFleeAllyFactionIDs));
|
||||
|
||||
const auto& f = std::find_if(
|
||||
excluded.begin(),
|
||||
excluded.end(),
|
||||
[&](std::string x) {
|
||||
return Strings::ToUnsignedInt(x) == skipped->GetPrimaryFaction();
|
||||
}
|
||||
);
|
||||
|
||||
const bool is_excluded = f != excluded.end();
|
||||
|
||||
// If exclude doesn't have a faction, check for buddies based on race.
|
||||
// Also exclude common factions such as noob monsters, indifferent, kos, kos animal
|
||||
if (!is_excluded) {
|
||||
if (n->GetPrimaryFaction() != skipped->GetPrimaryFaction()) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (n->GetBaseRace() != skipped->GetBaseRace() || n->IsCharmedPet()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
LogFleeDetail(
|
||||
"[{}] on faction [{}] with aggro_range [{}] is at [{}], [{}], [{}] and will count as an ally for [{}]",
|
||||
n->GetName(),
|
||||
n->GetPrimaryFaction(),
|
||||
aggro_range,
|
||||
n->GetX(),
|
||||
n->GetY(),
|
||||
n->GetZ(),
|
||||
skipped->GetName()
|
||||
);
|
||||
|
||||
++count;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
int EntityList::GetHatedCount(Mob *attacker, Mob *exclude, bool inc_gray_con)
|
||||
{
|
||||
// Return a list of how many non-feared, non-mezzed, non-green mobs, within aggro range, hate *attacker
|
||||
@@ -671,9 +759,9 @@ bool Mob::IsAttackAllowed(Mob *target, bool isSpellAttack)
|
||||
target_owner = nullptr;
|
||||
|
||||
//cannot hurt untargetable mobs
|
||||
bodyType bt = target->GetBodyType();
|
||||
uint8 bt = target->GetBodyType();
|
||||
|
||||
if(bt == BT_NoTarget || bt == BT_NoTarget2) {
|
||||
if(bt == BodyType::NoTarget || bt == BodyType::NoTarget2) {
|
||||
if (RuleB(Pets, UnTargetableSwarmPet)) {
|
||||
if (target->IsNPC()) {
|
||||
if (!target->CastToNPC()->GetSwarmOwner()) {
|
||||
|
||||
@@ -651,7 +651,6 @@ Json::Value ApiGetClientListDetail(EQ::Net::WebsocketServerConnection *connectio
|
||||
row["base_wis"] = client->GetBaseWIS();
|
||||
row["become_npc_level"] = client->GetBecomeNPCLevel();
|
||||
row["boat_id"] = client->GetBoatID();
|
||||
row["buyer_welcome_message"] = client->GetBuyerWelcomeMessage();
|
||||
row["calc_atk"] = client->CalcATK();
|
||||
row["calc_base_mana"] = client->CalcBaseMana();
|
||||
row["calc_current_weight"] = client->CalcCurrentWeight();
|
||||
|
||||
+385
-371
@@ -1478,8 +1478,10 @@ int64 Mob::DoDamageCaps(int64 base_damage)
|
||||
//SYNC WITH: tune.cpp, mob.h TuneDoAttack
|
||||
void Mob::DoAttack(Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts, bool FromRiposte)
|
||||
{
|
||||
if (!other)
|
||||
if (!other) {
|
||||
return;
|
||||
}
|
||||
|
||||
LogCombat("[{}]::DoAttack vs [{}] base [{}] min [{}] offense [{}] tohit [{}] skill [{}]", GetName(),
|
||||
other->GetName(), hit.base_damage, hit.min_damage, hit.offense, hit.tohit, hit.skill);
|
||||
|
||||
@@ -1491,14 +1493,22 @@ void Mob::DoAttack(Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts, boo
|
||||
if (!FromRiposte && other->AvoidDamage(this, hit)) {
|
||||
if (int strike_through = itembonuses.StrikeThrough + spellbonuses.StrikeThrough + aabonuses.StrikeThrough;
|
||||
strike_through && zone->random.Roll(strike_through)) {
|
||||
MessageString(Chat::StrikeThrough,
|
||||
STRIKETHROUGH_STRING); // You strike through your opponents defenses!
|
||||
|
||||
FilteredMessageString(
|
||||
this, /* Sender */
|
||||
Chat::StrikeThrough, /* Type: 339 */
|
||||
FilterStrikethrough, /* FilterType: 12 */
|
||||
STRIKETHROUGH_STRING /* You strike through your opponent's defenses! */
|
||||
);
|
||||
|
||||
hit.damage_done = 1; // set to one, we will check this to continue
|
||||
}
|
||||
|
||||
if (hit.damage_done == DMG_RIPOSTED) {
|
||||
DoRiposte(other);
|
||||
return;
|
||||
}
|
||||
|
||||
LogCombat("Avoided/strikethrough damage with code [{}]", hit.damage_done);
|
||||
}
|
||||
|
||||
@@ -1510,9 +1520,19 @@ void Mob::DoAttack(Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts, boo
|
||||
int stun_resist2 = other->spellbonuses.FrontalStunResist + other->itembonuses.FrontalStunResist + other->aabonuses.FrontalStunResist;
|
||||
int stun_resist = other->spellbonuses.StunResist + other->itembonuses.StunResist + other->aabonuses.StunResist;
|
||||
if (zone->random.Roll(stun_resist2)) {
|
||||
other->MessageString(Chat::Stun, AVOID_STUNNING_BLOW);
|
||||
other->FilteredMessageString(
|
||||
this,
|
||||
Chat::Stun,
|
||||
FilterStuns,
|
||||
AVOID_STUNNING_BLOW
|
||||
);
|
||||
} else if (zone->random.Roll(stun_resist)) {
|
||||
other->MessageString(Chat::Stun, SHAKE_OFF_STUN);
|
||||
other->FilteredMessageString(
|
||||
this,
|
||||
Chat::Stun,
|
||||
FilterStuns,
|
||||
SHAKE_OFF_STUN
|
||||
);
|
||||
} else {
|
||||
other->Stun(3000); // yuck -- 3 seconds
|
||||
}
|
||||
@@ -1530,17 +1550,18 @@ void Mob::DoAttack(Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts, boo
|
||||
hit.damage_done = 0;
|
||||
}
|
||||
|
||||
if (IsBot()) {
|
||||
if (parse->BotHasQuestSub(EVENT_USE_SKILL)) {
|
||||
const auto& export_string = fmt::format(
|
||||
parse->EventBotMerc(
|
||||
EVENT_USE_SKILL,
|
||||
this,
|
||||
nullptr,
|
||||
[&]() {
|
||||
return fmt::format(
|
||||
"{} {}",
|
||||
hit.skill,
|
||||
GetSkill(hit.skill)
|
||||
);
|
||||
|
||||
parse->EventBot(EVENT_USE_SKILL, CastToBot(), nullptr, export_string, 0);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1719,7 +1740,7 @@ bool Mob::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool
|
||||
(HasOwner() && GetOwner()->IsClient() && other->IsClient())
|
||||
)
|
||||
) {
|
||||
for (auto const& [id, mob] : entity_list.GetCloseMobList(other)) {
|
||||
for (auto const& [id, mob] : other->GetCloseMobList()) {
|
||||
if (!mob) {
|
||||
continue;
|
||||
}
|
||||
@@ -1902,11 +1923,9 @@ bool Client::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::Skil
|
||||
}
|
||||
|
||||
if (killer_mob) {
|
||||
if (killer_mob->IsNPC()) {
|
||||
if (parse->HasQuestSub(killer_mob->GetNPCTypeID(), EVENT_SLAY)) {
|
||||
parse->EventNPC(EVENT_SLAY, killer_mob->CastToNPC(), this, "", 0);
|
||||
}
|
||||
parse->EventBotMercNPC(EVENT_SLAY, killer_mob, this);
|
||||
|
||||
if (killer_mob->IsNPC()) {
|
||||
killed_by = KilledByTypes::Killed_NPC;
|
||||
|
||||
auto emote_id = killer_mob->GetEmoteID();
|
||||
@@ -1914,12 +1933,6 @@ bool Client::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::Skil
|
||||
killer_mob->CastToNPC()->DoNPCEmote(EQ::constants::EmoteEventTypes::KilledPC, emoteid, this);
|
||||
}
|
||||
|
||||
killer_mob->TrySpellOnKill(killed_level, spell);
|
||||
} else if (killer_mob->IsBot()) {
|
||||
if (parse->BotHasQuestSub(EVENT_SLAY)) {
|
||||
parse->EventBot(EVENT_SLAY, killer_mob->CastToBot(), this, "", 0);
|
||||
}
|
||||
|
||||
killer_mob->TrySpellOnKill(killed_level, spell);
|
||||
}
|
||||
|
||||
@@ -2186,6 +2199,19 @@ bool Client::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::Skil
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Client::CheckIfAlreadyDead()
|
||||
{
|
||||
if (!ClientFinishedLoading()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dead) {
|
||||
return false; //cant die more than once...
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//SYNC WITH: tune.cpp, mob.h TuneNPCAttack
|
||||
bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts)
|
||||
{
|
||||
@@ -2286,8 +2312,7 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool
|
||||
//Guard Assist Code
|
||||
if (RuleB(Character, PVPEnableGuardFactionAssist)) {
|
||||
if (IsClient() && other->IsClient() || (HasOwner() && GetOwner()->IsClient() && other->IsClient())) {
|
||||
auto& mob_list = entity_list.GetCloseMobList(other);
|
||||
for (auto& e : mob_list) {
|
||||
for (auto& e : other->GetCloseMobList()) {
|
||||
auto mob = e.second;
|
||||
if (!mob) {
|
||||
continue;
|
||||
@@ -2426,14 +2451,10 @@ void NPC::Damage(Mob* other, int64 damage, uint16 spell_id, EQ::skills::SkillTyp
|
||||
spell_id = SPELL_UNKNOWN;
|
||||
|
||||
//handle EVENT_ATTACK. Resets after we have not been attacked for 12 seconds
|
||||
if (attacked_timer.Check())
|
||||
{
|
||||
if (parse->HasQuestSub(GetNPCTypeID(), EVENT_ATTACK)) {
|
||||
LogCombat("Triggering EVENT_ATTACK due to attack by [{}]", other ? other->GetName() : "nullptr");
|
||||
|
||||
parse->EventNPC(EVENT_ATTACK, this, other, "", 0);
|
||||
}
|
||||
if (attacked_timer.Check()) {
|
||||
parse->EventMercNPC(EVENT_ATTACK, this, other);
|
||||
}
|
||||
|
||||
attacked_timer.Start(CombatEventTimer_expire);
|
||||
|
||||
if (!IsEngaged())
|
||||
@@ -2460,11 +2481,6 @@ void NPC::Damage(Mob* other, int64 damage, uint16 spell_id, EQ::skills::SkillTyp
|
||||
|
||||
//do a majority of the work...
|
||||
CommonDamage(other, damage, spell_id, attack_skill, avoidable, buffslot, iBuffTic, special);
|
||||
|
||||
if (damage > 0) {
|
||||
//see if we are gunna start fleeing
|
||||
if (!IsPet()) CheckFlee();
|
||||
}
|
||||
}
|
||||
|
||||
bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillType attack_skill, KilledByTypes killed_by, bool is_buff_tic)
|
||||
@@ -2479,41 +2495,22 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy
|
||||
|
||||
Mob* owner_or_self = killer_mob ? killer_mob->GetOwnerOrSelf() : nullptr;
|
||||
|
||||
if (IsNPC()) {
|
||||
if (parse->HasQuestSub(GetNPCTypeID(), EVENT_DEATH)) {
|
||||
const auto& export_string = fmt::format(
|
||||
"{} {} {} {}",
|
||||
killer_mob ? killer_mob->GetID() : 0,
|
||||
damage,
|
||||
spell,
|
||||
static_cast<int>(attack_skill)
|
||||
);
|
||||
auto exports = [&]() {
|
||||
return fmt::format(
|
||||
"{} {} {} {}",
|
||||
killer_mob ? killer_mob->GetID() : 0,
|
||||
damage,
|
||||
spell,
|
||||
static_cast<int>(attack_skill)
|
||||
);
|
||||
};
|
||||
|
||||
if (parse->EventNPC(EVENT_DEATH, this, owner_or_self, export_string, 0) != 0) {
|
||||
if (GetHP() < 0) {
|
||||
SetHP(0);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
if (parse->EventBotMercNPC(EVENT_DEATH, this, owner_or_self, exports) != 0) {
|
||||
if (GetHP() < 0) {
|
||||
SetHP(0);
|
||||
}
|
||||
} else if (IsBot()) {
|
||||
if (parse->BotHasQuestSub(EVENT_DEATH)) {
|
||||
const auto& export_string = fmt::format(
|
||||
"{} {} {} {}",
|
||||
killer_mob ? killer_mob->GetID() : 0,
|
||||
damage,
|
||||
spell,
|
||||
static_cast<int>(attack_skill)
|
||||
);
|
||||
if (parse->EventBot(EVENT_DEATH, CastToBot(), owner_or_self, export_string, 0) != 0) {
|
||||
if (GetHP() < 0) {
|
||||
SetHP(0);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (killer_mob && killer_mob->IsOfClientBot() && IsValidSpell(spell) && damage > 0) {
|
||||
@@ -2945,8 +2942,7 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy
|
||||
entity_list.UnMarkNPC(GetID());
|
||||
entity_list.RemoveNPC(GetID());
|
||||
|
||||
// entity_list.RemoveMobFromCloseLists(this);
|
||||
close_mobs.clear();
|
||||
m_close_mobs.clear();
|
||||
SetID(0);
|
||||
ApplyIllusionToCorpse(illusion_spell_id, corpse);
|
||||
|
||||
@@ -3051,10 +3047,8 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy
|
||||
}
|
||||
}
|
||||
|
||||
if (killer_mob && killer_mob->IsBot()) {
|
||||
if (parse->BotHasQuestSub(EVENT_NPC_SLAY)) {
|
||||
parse->EventBot(EVENT_NPC_SLAY, killer_mob->CastToBot(), this, "", 0);
|
||||
}
|
||||
if (killer_mob) {
|
||||
parse->EventBotMerc(EVENT_NPC_SLAY, killer_mob, this);
|
||||
|
||||
killer_mob->TrySpellOnKill(killed_level, spell);
|
||||
}
|
||||
@@ -3082,24 +3076,29 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy
|
||||
}
|
||||
}
|
||||
|
||||
if (parse->HasQuestSub(GetNPCTypeID(), EVENT_DEATH_COMPLETE)) {
|
||||
const auto& export_string = fmt::format(
|
||||
"{} {} {} {} {} {} {} {} {}",
|
||||
killer_mob ? killer_mob->GetID() : 0,
|
||||
damage,
|
||||
spell,
|
||||
static_cast<int>(attack_skill),
|
||||
entity_id,
|
||||
m_combat_record.GetStartTime(),
|
||||
m_combat_record.GetEndTime(),
|
||||
m_combat_record.GetDamageReceived(),
|
||||
m_combat_record.GetHealingReceived()
|
||||
);
|
||||
std::vector<std::any> args = { corpse };
|
||||
|
||||
std::vector<std::any> args = { corpse };
|
||||
|
||||
parse->EventNPC(EVENT_DEATH_COMPLETE, this, owner_or_self, export_string, 0, &args);
|
||||
}
|
||||
parse->EventMercNPC(
|
||||
EVENT_DEATH_COMPLETE,
|
||||
this,
|
||||
owner_or_self,
|
||||
[&]() {
|
||||
return fmt::format(
|
||||
"{} {} {} {} {} {} {} {} {}",
|
||||
killer_mob ? killer_mob->GetID() : 0,
|
||||
damage,
|
||||
spell,
|
||||
static_cast<int>(attack_skill),
|
||||
entity_id,
|
||||
m_combat_record.GetStartTime(),
|
||||
m_combat_record.GetEndTime(),
|
||||
m_combat_record.GetDamageReceived(),
|
||||
m_combat_record.GetHealingReceived()
|
||||
);
|
||||
},
|
||||
0,
|
||||
&args
|
||||
);
|
||||
|
||||
// Zone controller process EVENT_DEATH_ZONE (Death events)
|
||||
if (parse->HasQuestSub(ZONE_CONTROLLER_NPC_ID, EVENT_DEATH_ZONE)) {
|
||||
@@ -4132,6 +4131,7 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
|
||||
AddToHateList(attacker, 0, damage, true, false, iBuffTic, spell_id);
|
||||
}
|
||||
|
||||
bool died = false;
|
||||
if (damage > 0) {
|
||||
//if there is some damage being done and theres an attacker involved
|
||||
int previous_hp_ratio = GetHPRatio();
|
||||
@@ -4256,110 +4256,89 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
|
||||
}
|
||||
|
||||
//final damage has been determined.
|
||||
SetHP(int64(GetHP() - damage));
|
||||
int old_hp_ratio = (int)GetHPRatio();
|
||||
|
||||
const auto has_bot_given_event = parse->BotHasQuestSub(EVENT_DAMAGE_GIVEN);
|
||||
|
||||
const auto has_bot_taken_event = parse->BotHasQuestSub(EVENT_DAMAGE_TAKEN);
|
||||
|
||||
const auto has_npc_given_event = (
|
||||
(
|
||||
IsNPC() &&
|
||||
parse->HasQuestSub(CastToNPC()->GetNPCTypeID(), EVENT_DAMAGE_GIVEN)
|
||||
) ||
|
||||
(
|
||||
attacker &&
|
||||
attacker->IsNPC() &&
|
||||
parse->HasQuestSub(attacker->CastToNPC()->GetNPCTypeID(), EVENT_DAMAGE_GIVEN)
|
||||
)
|
||||
);
|
||||
|
||||
const auto has_npc_taken_event = (
|
||||
(
|
||||
IsNPC() &&
|
||||
parse->HasQuestSub(CastToNPC()->GetNPCTypeID(), EVENT_DAMAGE_TAKEN)
|
||||
) ||
|
||||
(
|
||||
attacker &&
|
||||
attacker->IsNPC() &&
|
||||
parse->HasQuestSub(attacker->CastToNPC()->GetNPCTypeID(), EVENT_DAMAGE_TAKEN)
|
||||
)
|
||||
);
|
||||
|
||||
const auto has_player_given_event = parse->PlayerHasQuestSub(EVENT_DAMAGE_GIVEN);
|
||||
|
||||
const auto has_player_taken_event = parse->PlayerHasQuestSub(EVENT_DAMAGE_TAKEN);
|
||||
|
||||
const auto has_given_event = (
|
||||
has_bot_given_event ||
|
||||
has_npc_given_event ||
|
||||
has_player_given_event
|
||||
);
|
||||
|
||||
const auto has_taken_event = (
|
||||
has_bot_taken_event ||
|
||||
has_npc_taken_event ||
|
||||
has_player_taken_event
|
||||
);
|
||||
|
||||
std::vector<std::any> args;
|
||||
|
||||
if (has_taken_event) {
|
||||
const auto export_string = fmt::format(
|
||||
"{} {} {} {} {} {} {} {} {}",
|
||||
attacker ? attacker->GetID() : 0,
|
||||
damage,
|
||||
spell_id,
|
||||
static_cast<int>(skill_used),
|
||||
FromDamageShield ? 1 : 0,
|
||||
avoidable ? 1 : 0,
|
||||
buffslot,
|
||||
iBuffTic ? 1 : 0,
|
||||
static_cast<int>(special)
|
||||
);
|
||||
int64 damage_override = 0;
|
||||
|
||||
if (IsBot() && has_bot_taken_event) {
|
||||
parse->EventBot(EVENT_DAMAGE_TAKEN, CastToBot(), attacker ? attacker : nullptr, export_string, 0);
|
||||
} else if (IsClient() && has_player_taken_event) {
|
||||
args.push_back(attacker ? attacker : nullptr);
|
||||
parse->EventPlayer(EVENT_DAMAGE_TAKEN, CastToClient(), export_string, 0, &args);
|
||||
} else if (IsNPC() && has_npc_taken_event) {
|
||||
parse->EventNPC(EVENT_DAMAGE_TAKEN, CastToNPC(), attacker ? attacker : nullptr, export_string, 0);
|
||||
}
|
||||
if (attacker) {
|
||||
args = { this };
|
||||
|
||||
parse->EventMob(
|
||||
EVENT_DAMAGE_GIVEN,
|
||||
attacker,
|
||||
this,
|
||||
[&]() {
|
||||
return fmt::format(
|
||||
"{} {} {} {} {} {} {} {} {}",
|
||||
GetID(),
|
||||
damage,
|
||||
spell_id,
|
||||
static_cast<int>(skill_used),
|
||||
FromDamageShield ? 1 : 0,
|
||||
avoidable ? 1 : 0,
|
||||
buffslot,
|
||||
iBuffTic ? 1 : 0,
|
||||
static_cast<int>(special)
|
||||
);
|
||||
},
|
||||
0,
|
||||
&args
|
||||
);
|
||||
}
|
||||
|
||||
if (has_given_event && attacker) {
|
||||
const auto export_string = fmt::format(
|
||||
"{} {} {} {} {} {} {} {} {}",
|
||||
GetID(),
|
||||
damage,
|
||||
spell_id,
|
||||
static_cast<int>(skill_used),
|
||||
FromDamageShield ? 1 : 0,
|
||||
avoidable ? 1 : 0,
|
||||
buffslot,
|
||||
iBuffTic ? 1 : 0,
|
||||
static_cast<int>(special)
|
||||
);
|
||||
args = { attacker };
|
||||
|
||||
if (attacker->IsBot() && has_bot_given_event) {
|
||||
parse->EventBot(EVENT_DAMAGE_GIVEN, attacker->CastToBot(), this, export_string, 0);
|
||||
} else if (attacker->IsClient() && has_player_given_event) {
|
||||
args.push_back(this);
|
||||
parse->EventPlayer(EVENT_DAMAGE_GIVEN, attacker->CastToClient(), export_string, 0, &args);
|
||||
} else if (attacker->IsNPC() && has_npc_given_event) {
|
||||
parse->EventNPC(EVENT_DAMAGE_GIVEN, attacker->CastToNPC(), this, export_string, 0);
|
||||
}
|
||||
damage_override = parse->EventMob(
|
||||
EVENT_DAMAGE_TAKEN,
|
||||
this,
|
||||
attacker,
|
||||
[&]() {
|
||||
return fmt::format(
|
||||
"{} {} {} {} {} {} {} {} {}",
|
||||
attacker ? attacker->GetID() : 0,
|
||||
damage,
|
||||
spell_id,
|
||||
static_cast<int>(skill_used),
|
||||
FromDamageShield ? 1 : 0,
|
||||
avoidable ? 1 : 0,
|
||||
buffslot,
|
||||
iBuffTic ? 1 : 0,
|
||||
static_cast<int>(special)
|
||||
);
|
||||
},
|
||||
0,
|
||||
&args
|
||||
);
|
||||
|
||||
if (damage_override > 0) {
|
||||
damage = damage_override;
|
||||
} else if (damage_override < 0) {
|
||||
damage = 0;
|
||||
}
|
||||
|
||||
SetHP(int64(GetHP() - damage));
|
||||
|
||||
if (HasDied()) {
|
||||
bool IsSaved = false;
|
||||
|
||||
if (TryDivineSave())
|
||||
if (TryDivineSave()) {
|
||||
IsSaved = true;
|
||||
}
|
||||
|
||||
if (!IsSaved && !TrySpellOnDeath()) {
|
||||
SetHP(-500);
|
||||
if (IsNPC()) {
|
||||
died = !CastToNPC()->GetDepop();
|
||||
} else if (IsClient()) {
|
||||
died = CastToClient()->CheckIfAlreadyDead();
|
||||
}
|
||||
|
||||
if (died) {
|
||||
SetHP(-500);
|
||||
}
|
||||
|
||||
// killedByType is clarified in Client::Death if we are client.
|
||||
if (Death(attacker, damage, spell_id, skill_used, KilledByTypes::Killed_NPC, iBuffTic)) {
|
||||
return;
|
||||
@@ -4485,32 +4464,59 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
|
||||
Stun(RuleI(Combat, StunDuration));
|
||||
if (RuleB(Combat, ClientStunMessage) && attacker->IsClient()) {
|
||||
if (attacker) {
|
||||
entity_list.MessageClose(this, true, 500, Chat::Emote, "%s is stunned after being bashed by %s.", GetCleanName(), attacker->GetCleanName());
|
||||
}
|
||||
else {
|
||||
entity_list.MessageClose(this, true, 500, Chat::Emote, "%s is stunned by a bash to the head.", GetCleanName());
|
||||
entity_list.FilteredMessageClose(
|
||||
this,
|
||||
true,
|
||||
RuleI(Range, StunMessages),
|
||||
Chat::Stun,
|
||||
FilterStuns,
|
||||
"%s is stunned after being bashed by %s.",
|
||||
GetCleanName(),
|
||||
attacker->GetCleanName()
|
||||
);
|
||||
} else {
|
||||
entity_list.FilteredMessageClose(
|
||||
this,
|
||||
true,
|
||||
RuleI(Range, StunMessages),
|
||||
Chat::Stun,
|
||||
FilterStuns,
|
||||
"%s is stunned by a bash to the head.",
|
||||
GetCleanName()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// stun resist passed!
|
||||
if (IsClient())
|
||||
MessageString(Chat::Stun, SHAKE_OFF_STUN);
|
||||
if (IsClient()) {
|
||||
FilteredMessageString(
|
||||
this,
|
||||
Chat::Stun,
|
||||
FilterStuns,
|
||||
SHAKE_OFF_STUN
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// stun resist 2 passed!
|
||||
if (IsClient()) {
|
||||
FilteredMessageString(
|
||||
this,
|
||||
Chat::Stun,
|
||||
FilterStuns,
|
||||
AVOID_STUNNING_BLOW
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// stun resist 2 passed!
|
||||
if (IsClient())
|
||||
MessageString(Chat::Stun, AVOID_STUNNING_BLOW);
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// main stun failed -- extra interrupt roll
|
||||
if (IsCasting() &&
|
||||
!EQ::ValueWithin(casting_spell_id, 859, 1023)) // these spells are excluded
|
||||
// 90% chance >< -- stun immune won't reach this branch though :(
|
||||
if (zone->random.Int(0, 9) > 1)
|
||||
// these spells are excluded
|
||||
// 90% chance >< -- stun immune won't reach this branch though :(
|
||||
if (IsCasting() && !EQ::ValueWithin(casting_spell_id, 859, 1023)) {
|
||||
if (zone->random.Int(0, 9) > 1) {
|
||||
InterruptSpell();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4529,8 +4535,21 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
|
||||
}
|
||||
|
||||
//send an HP update if we are hurt
|
||||
if (GetHP() < GetMaxHP()) {
|
||||
SendHPUpdate(); // the OP_Damage actually updates the client in these cases, so we skip the HP update for them
|
||||
if(GetHP() < GetMaxHP())
|
||||
{
|
||||
// Don't send a HP update for melee damage unless we've damaged ourself.
|
||||
if (IsNPC()) {
|
||||
int cur_hp_ratio = (int)GetHPRatio();
|
||||
if (cur_hp_ratio != old_hp_ratio) {
|
||||
SendHPUpdate(true);
|
||||
}
|
||||
} else if (!iBuffTic || died) { // Let regen handle buff tics unless this tic killed us.
|
||||
SendHPUpdate(true);
|
||||
}
|
||||
|
||||
if (!died && IsNPC()) {
|
||||
CheckFlee();
|
||||
}
|
||||
}
|
||||
} //end `if damage was done`
|
||||
|
||||
@@ -4593,10 +4612,10 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
|
||||
//this was done to simplify the code here (since we can only effectively skip one mob on queue)
|
||||
eqFilterType filter;
|
||||
Mob* skip = attacker;
|
||||
if (attacker && attacker->IsPet() && !attacker->IsBot()) {
|
||||
Mob* owner = attacker ? attacker->GetOwner() : nullptr;
|
||||
if (attacker && owner && !attacker->IsBot()) {
|
||||
//attacker is a pet, let pet owners see their pet's damage
|
||||
Mob* owner = attacker->GetOwner();
|
||||
if (owner && owner->IsClient()) {
|
||||
if (owner->IsClient()) {
|
||||
if (FromDamageShield && damage > 0) {
|
||||
//special crap for spell damage, looks hackish to me
|
||||
char val1[20] = { 0 };
|
||||
@@ -5388,101 +5407,6 @@ void Mob::TryPetCriticalHit(Mob *defender, DamageHitInfo &hit)
|
||||
}
|
||||
}
|
||||
|
||||
bool Mob::RollMeleeCritCheck(Mob *defender, EQ::skills::SkillType skill)
|
||||
{
|
||||
// We either require an innate crit chance or some SPA 169 to crit
|
||||
bool innate_crit = false;
|
||||
int crit_chance = GetCriticalChanceBonus(skill);
|
||||
// Paladin check
|
||||
if (defender->IsUndeadForSlay()) {
|
||||
crit_chance = crit_chance + GetUndeadSlayRate();
|
||||
}
|
||||
|
||||
if (GetLevel() >= 12) {
|
||||
if (
|
||||
GetClass() == Class::Warrior ||
|
||||
(GetClass() == Class::Ranger && skill == EQ::skills::SkillArchery) ||
|
||||
(GetClass() == Class::Rogue && skill == EQ::skills::SkillThrowing) ||
|
||||
GetClass() == Class::Berserker
|
||||
) {
|
||||
innate_crit = true;
|
||||
}
|
||||
}
|
||||
|
||||
// we have a chance to crit!
|
||||
if (innate_crit || crit_chance) {
|
||||
int difficulty = 0;
|
||||
|
||||
if (skill == EQ::skills::SkillArchery) {
|
||||
difficulty = RuleI(Combat, ArcheryCritDifficulty);
|
||||
} else if (skill == EQ::skills::SkillThrowing) {
|
||||
difficulty = RuleI(Combat, ThrowingCritDifficulty);
|
||||
} else {
|
||||
difficulty = RuleI(Combat, MeleeCritDifficulty);
|
||||
}
|
||||
|
||||
int roll = zone->random.Int(1, difficulty);
|
||||
|
||||
int dex_bonus = GetDEX();
|
||||
|
||||
if (dex_bonus > 255) {
|
||||
dex_bonus = 255 + ((dex_bonus - 255) / 5);
|
||||
}
|
||||
|
||||
dex_bonus += 45; // chances did not match live without a small boost
|
||||
|
||||
// so if we have an innate crit we have a better chance, except for ber throwing
|
||||
if (!innate_crit || (GetClass() == Class::Berserker && skill == EQ::skills::SkillThrowing)) {
|
||||
dex_bonus = dex_bonus * 3 / 5;
|
||||
}
|
||||
|
||||
LogCombat("Crit Chance: dex_bonus ({}) * crit_chance ({}) / 100", dex_bonus, crit_chance);
|
||||
|
||||
if (crit_chance) {
|
||||
dex_bonus += dex_bonus * crit_chance / 100;
|
||||
}
|
||||
|
||||
// check if we crited
|
||||
LogCombat("Final Roll! Difficulty = [{}] -- Dex_Bonus = [{}] ", difficulty, dex_bonus);
|
||||
return (roll < dex_bonus);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int Mob::GetUndeadSlayRate()
|
||||
{
|
||||
return aabonuses.SlayUndead[0] + itembonuses.SlayUndead[0] + spellbonuses.SlayUndead[0];
|
||||
}
|
||||
|
||||
void Mob::DoUndeadSlay(DamageHitInfo &hit, int crit_mod)
|
||||
{
|
||||
|
||||
int slay_damage_bonus = std::max(
|
||||
{ aabonuses.SlayUndead[1], itembonuses.SlayUndead[1], spellbonuses.SlayUndead[1] });
|
||||
|
||||
LogCombatDetail("Slayundead bonus [{}]", slay_damage_bonus);
|
||||
|
||||
hit.damage_done = std::max(hit.damage_done, hit.base_damage) + 5;
|
||||
hit.damage_done = (hit.damage_done * slay_damage_bonus * crit_mod) / 100;
|
||||
hit.damage_done = static_cast<int>(hit.damage_done * RuleR(Combat, SlayDamageAdjustment));
|
||||
|
||||
LogCombatDetail("Slayundead damage [{}]", hit.damage_done);
|
||||
|
||||
int slay_sex = GetGender() == Gender::Female ? FEMALE_SLAYUNDEAD : MALE_SLAYUNDEAD;
|
||||
|
||||
entity_list.FilteredMessageString(
|
||||
this, /* Sender */
|
||||
false, /* Skip Sender */
|
||||
Chat::MeleeCrit, /* Type: 301 */
|
||||
FilterMeleeCrits, /* FilterType: 12 */
|
||||
slay_sex, /* MessageFormat: %1's holy blade cleanses her target!(%2) */
|
||||
GetCleanName(), /* Message1 */
|
||||
itoa(hit.damage_done) /* Message2 */
|
||||
);
|
||||
}
|
||||
|
||||
// a lot of good info: http://giline.versus.jp/shiden/damage_e.htm, http://giline.versus.jp/shiden/su.htm
|
||||
void Mob::TryCriticalHit(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions *opts)
|
||||
{
|
||||
#ifdef LUA_EQEMU
|
||||
@@ -5494,8 +5418,9 @@ void Mob::TryCriticalHit(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions *
|
||||
}
|
||||
#endif
|
||||
|
||||
if (hit.damage_done < 1 || !defender)
|
||||
if (hit.damage_done < 1 || !defender) {
|
||||
return;
|
||||
}
|
||||
|
||||
// decided to branch this into it's own function since it's going to be duplicating a lot of the
|
||||
// code in here, but could lead to some confusion otherwise
|
||||
@@ -5513,112 +5438,201 @@ void Mob::TryCriticalHit(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions *
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 1: Check if we are critting
|
||||
if (!RollMeleeCritCheck(defender, hit.skill)) {
|
||||
return;
|
||||
}
|
||||
// 1: Try Slay Undead
|
||||
if (defender->GetBodyType() == BodyType::Undead || defender->GetBodyType() == BodyType::SummonedUndead ||
|
||||
defender->GetBodyType() == BodyType::Vampire) {
|
||||
int slay_rate_bonus = aabonuses.SlayUndead[SBIndex::SLAYUNDEAD_RATE_MOD] + itembonuses.SlayUndead[SBIndex::SLAYUNDEAD_RATE_MOD] + spellbonuses.SlayUndead[SBIndex::SLAYUNDEAD_RATE_MOD];
|
||||
|
||||
int crit_mod = EQ::ClampLower((170 + GetCritDmgMod(hit.skill)), 100);
|
||||
LogCombatDetail("Slayundead hit rate [{}]", slay_rate_bonus);
|
||||
|
||||
// Step 2: Calculate damage
|
||||
hit.damage_done = std::max(hit.damage_done, hit.base_damage) + 5;
|
||||
int og_damage = hit.damage_done;
|
||||
hit.damage_done = hit.damage_done * crit_mod / 100;
|
||||
if (slay_rate_bonus) {
|
||||
float slay_chance = ((static_cast<float>(slay_rate_bonus) / 10000.0f) * RuleR(Combat, SlayRateMultiplier));
|
||||
|
||||
LogCombatDetail("Crit info: [{}] scaled from: [{}] - IsUndeadForSlay: [{}]", hit.damage_done, og_damage, IsUndeadForSlay() ? "true" : "false");
|
||||
if (zone->random.Roll(slay_chance)) {
|
||||
int slay_damage_bonus = aabonuses.SlayUndead[SBIndex::SLAYUNDEAD_DMG_MOD] + itembonuses.SlayUndead[SBIndex::SLAYUNDEAD_DMG_MOD] + spellbonuses.SlayUndead[SBIndex::SLAYUNDEAD_DMG_MOD];
|
||||
|
||||
// Try Slay Undead
|
||||
if (defender->IsUndeadForSlay()) {
|
||||
float chance = GetUndeadSlayRate() / 100.0f;
|
||||
LogCombatDetail("Trying Undead slay: Chance: [{}]", chance);
|
||||
LogCombatDetail("Slayundead damage bonus [{}]", slay_damage_bonus);
|
||||
|
||||
if(zone->random.Roll(chance)) {
|
||||
DoUndeadSlay(hit, crit_mod);
|
||||
return;
|
||||
}
|
||||
}
|
||||
hit.damage_done = std::max(hit.damage_done, hit.base_damage) + 5;
|
||||
hit.damage_done = (hit.damage_done * slay_damage_bonus) / 100;
|
||||
hit.damage_done = static_cast<int>(hit.damage_done * RuleR(Combat, SlayDamageMultiplier));
|
||||
|
||||
// Step 3: Check deadly strike
|
||||
if (GetClass() == Class::Rogue && hit.skill == EQ::skills::SkillThrowing && BehindMob(defender, GetX(), GetY())) {
|
||||
int chance = GetLevel() * 12;
|
||||
if (zone->random.Int(1, 1000) < chance) {
|
||||
// Check assassinate
|
||||
int assassinate_damage = TryAssassinate(defender, hit.skill);
|
||||
int min_slay = (hit.min_damage + 5) * slay_damage_bonus / 100;
|
||||
|
||||
if (assassinate_damage) {
|
||||
hit.damage_done = assassinate_damage;
|
||||
return;
|
||||
}
|
||||
LogCombatDetail(" Calculated Slayundead damage [{}] - Min Slay Undead Damage [{}]", hit.damage_done, min_slay);
|
||||
|
||||
hit.damage_done = hit.damage_done * 200 / 100;
|
||||
if (hit.damage_done < min_slay) {
|
||||
hit.damage_done = min_slay;
|
||||
}
|
||||
|
||||
entity_list.FilteredMessageCloseString(
|
||||
LogCombatDetail("Final Slayundead damage [{}]", hit.damage_done);
|
||||
|
||||
int slay_sex = GetGender() == Gender::Female ? FEMALE_SLAYUNDEAD : MALE_SLAYUNDEAD;
|
||||
|
||||
entity_list.FilteredMessageCloseString(
|
||||
this, /* Sender */
|
||||
false, /* Skip Sender */
|
||||
RuleI(Range, CriticalDamage),
|
||||
Chat::MeleeCrit, /* Type: 301 */
|
||||
FilterMeleeCrits, /* FilterType: 12 */
|
||||
DEADLY_STRIKE, /* MessageFormat: %1 scores a Deadly Strike!(%2) */
|
||||
slay_sex,
|
||||
0,
|
||||
GetCleanName(), /* Message1 */
|
||||
itoa(hit.damage_done) /* Message2 */
|
||||
);
|
||||
return;
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4: check cripple
|
||||
bool berserk = spellbonuses.BerserkSPA || itembonuses.BerserkSPA || aabonuses.BerserkSPA;
|
||||
// 2: Try Melee Critical
|
||||
// a lot of good info: http://giline.versus.jp/shiden/damage_e.htm, http://giline.versus.jp/shiden/su.htm
|
||||
|
||||
if (!berserk && zone->random.Roll(GetCrippBlowChance())) {
|
||||
berserk = true;
|
||||
// We either require an innate crit chance or some SPA 169 to crit
|
||||
bool innate_crit = false;
|
||||
int crit_chance = GetCriticalChanceBonus(hit.skill);
|
||||
if ((GetClass() == Class::Warrior || GetClass() == Class::Berserker) && GetLevel() >= 12) {
|
||||
innate_crit = true;
|
||||
} else if (GetClass() == Class::Ranger && GetLevel() >= 12 && hit.skill == EQ::skills::SkillArchery) {
|
||||
innate_crit = true;
|
||||
} else if (GetClass() == Class::Rogue && GetLevel() >= 12 && hit.skill == EQ::skills::SkillThrowing) {
|
||||
innate_crit = true;
|
||||
}
|
||||
|
||||
if (IsBerserk() || berserk) {
|
||||
hit.damage_done += og_damage * 119 / 100;
|
||||
LogCombatDetail("Crippling damage [{}]", hit.damage_done);
|
||||
// we have a chance to crit!
|
||||
if (innate_crit || crit_chance) {
|
||||
int difficulty = 0;
|
||||
|
||||
entity_list.FilteredMessageCloseString(
|
||||
if (hit.skill == EQ::skills::SkillArchery) {
|
||||
difficulty = RuleI(Combat, ArcheryCritDifficulty);
|
||||
} else if (hit.skill == EQ::skills::SkillThrowing) {
|
||||
difficulty = RuleI(Combat, ThrowingCritDifficulty);
|
||||
} else {
|
||||
difficulty = RuleI(Combat, MeleeCritDifficulty);
|
||||
}
|
||||
|
||||
int roll = zone->random.Int(1, difficulty);
|
||||
int dex_bonus = GetDEX();
|
||||
|
||||
if (dex_bonus > 255) {
|
||||
dex_bonus = 255 + ((dex_bonus - 255) / 5);
|
||||
}
|
||||
|
||||
dex_bonus += 45; // chances did not match live without a small boost
|
||||
|
||||
// so if we have an innate crit we have a better chance, except for ber throwing
|
||||
if (!innate_crit || (GetClass() == Class::Berserker && hit.skill == EQ::skills::SkillThrowing)) {
|
||||
dex_bonus = dex_bonus * 3 / 5;
|
||||
}
|
||||
|
||||
if (crit_chance) {
|
||||
dex_bonus += dex_bonus * crit_chance / 100;
|
||||
}
|
||||
|
||||
// check if we crited
|
||||
if (roll < dex_bonus) {
|
||||
// step 1: check for finishing blow
|
||||
if (TryFinishingBlow(defender, hit.damage_done)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// step 2: calculate damage
|
||||
hit.damage_done = std::max(hit.damage_done, hit.base_damage) + 5;
|
||||
int og_damage = hit.damage_done;
|
||||
int crit_mod = 170 + GetCritDmgMod(hit.skill);
|
||||
|
||||
if (crit_mod < 100) {
|
||||
crit_mod = 100;
|
||||
}
|
||||
|
||||
hit.damage_done = hit.damage_done * crit_mod / 100;
|
||||
LogCombatDetail("Crit success roll [{}] dex chance [{}] og dmg [{}] crit_mod [{}] new dmg [{}]", roll, dex_bonus, og_damage, crit_mod, hit.damage_done);
|
||||
|
||||
// step 3: check deadly strike
|
||||
if (GetClass() == Class::Rogue && hit.skill == EQ::skills::SkillThrowing) {
|
||||
if (BehindMob(defender, GetX(), GetY())) {
|
||||
int chance = GetLevel() * 12;
|
||||
if (zone->random.Int(1, 1000) < chance) {
|
||||
// step 3a: check assassinate
|
||||
int assassinate_damage = TryAssassinate(defender, hit.skill); // I don't think this is right
|
||||
if (assassinate_damage) {
|
||||
hit.damage_done = assassinate_damage;
|
||||
return;
|
||||
}
|
||||
hit.damage_done = hit.damage_done * 200 / 100;
|
||||
|
||||
entity_list.FilteredMessageCloseString(
|
||||
this, /* Sender */
|
||||
false, /* Skip Sender */
|
||||
RuleI(Range, CriticalDamage),
|
||||
Chat::MeleeCrit, /* Type: 301 */
|
||||
FilterMeleeCrits, /* FilterType: 12 */
|
||||
DEADLY_STRIKE, /* MessageFormat: %1 scores a Deadly Strike!(%2) */
|
||||
0,
|
||||
GetCleanName(), /* Message1 */
|
||||
itoa(hit.damage_done + hit.min_damage) /* Message2 */
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// step 4: check crips
|
||||
// this SPA was reused on live ...
|
||||
bool berserk = spellbonuses.BerserkSPA || itembonuses.BerserkSPA || aabonuses.BerserkSPA;
|
||||
if (!berserk) {
|
||||
if (zone->random.Roll(GetCrippBlowChance())) {
|
||||
berserk = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (IsBerserk() || berserk) {
|
||||
hit.damage_done += og_damage * 119 / 100;
|
||||
LogCombat("Crip damage [{}]", hit.damage_done);
|
||||
|
||||
entity_list.FilteredMessageCloseString(
|
||||
this, /* Sender */
|
||||
false, /* Skip Sender */
|
||||
RuleI(Range, CriticalDamage),
|
||||
Chat::MeleeCrit, /* Type: 301 */
|
||||
FilterMeleeCrits, /* FilterType: 12 */
|
||||
CRIPPLING_BLOW, /* MessageFormat: %1 lands a Crippling Blow!(%2) */
|
||||
0,
|
||||
GetCleanName(), /* Message1 */
|
||||
itoa(hit.damage_done + hit.min_damage) /* Message2 */
|
||||
);
|
||||
|
||||
// Crippling blows also have a chance to stun
|
||||
// Kayen: Crippling Blow would cause a chance to interrupt for npcs < 55, with a
|
||||
// staggers message.
|
||||
if (defender->GetLevel() <= 55 && !defender->GetSpecialAbility(SpecialAbility::StunImmunity)) {
|
||||
entity_list.MessageCloseString(
|
||||
defender,
|
||||
true,
|
||||
RuleI(Range, Emote),
|
||||
Chat::Emote,
|
||||
STAGGERS,
|
||||
GetName()
|
||||
);
|
||||
defender->Stun(RuleI(Combat, StunDuration));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Normal Critical hit message */
|
||||
entity_list.FilteredMessageCloseString(
|
||||
this, /* Sender */
|
||||
false, /* Skip Sender */
|
||||
RuleI(Range, CriticalDamage),
|
||||
Chat::MeleeCrit, /* Type: 301 */
|
||||
FilterMeleeCrits, /* FilterType: 12 */
|
||||
CRIPPLING_BLOW, /* MessageFormat: %1 lands a Crippling Blow!(%2) */
|
||||
CRITICAL_HIT, /* MessageFormat: %1 scores a critical hit! (%2) */
|
||||
0,
|
||||
GetCleanName(), /* Message1 */
|
||||
itoa(hit.damage_done) /* Message2 */
|
||||
);
|
||||
|
||||
// Crippling blows also have a chance to stun
|
||||
// Kayen: Crippling Blow would cause a chance to interrupt for npcs < 55, with a
|
||||
// staggers message.
|
||||
if (defender->GetLevel() <= RuleI(Combat, MaximumLevelStunsCripplingBlow) && !defender->GetSpecialAbility(SpecialAbility::StunImmunity)) {
|
||||
entity_list.MessageCloseString(
|
||||
defender,
|
||||
true,
|
||||
RuleI(Range, Emote),
|
||||
Chat::Emote,
|
||||
STAGGERS,
|
||||
GetName()
|
||||
itoa(hit.damage_done + hit.min_damage) /* Message2 */
|
||||
);
|
||||
defender->Stun(RuleI(Combat, StunDuration));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Normal Critical hit message */
|
||||
entity_list.FilteredMessageCloseString(
|
||||
this, /* Sender */
|
||||
false, /* Skip Sender */
|
||||
RuleI(Range, CriticalDamage),
|
||||
Chat::MeleeCrit, /* Type: 301 */
|
||||
FilterMeleeCrits, /* FilterType: 12 */
|
||||
CRITICAL_HIT, /* MessageFormat: %1 scores a critical hit! (%2) */
|
||||
0,
|
||||
GetCleanName(), /* Message1 */
|
||||
itoa(hit.damage_done) /* Message2 */
|
||||
);
|
||||
}
|
||||
|
||||
bool Mob::TryFinishingBlow(Mob *defender, int64 &damage)
|
||||
|
||||
+7
-12
@@ -72,7 +72,7 @@ Mob *Aura::GetOwner()
|
||||
// not 100% sure how this one should work and PVP affects ...
|
||||
void Aura::ProcessOnAllFriendlies(Mob *owner)
|
||||
{
|
||||
auto &mob_list = entity_list.GetCloseMobList(this, distance);
|
||||
auto &mob_list = GetCloseMobList(distance);
|
||||
std::set<int> delayed_remove;
|
||||
bool is_buff = IsBuffSpell(spell_id); // non-buff spells don't cast on enter
|
||||
|
||||
@@ -127,7 +127,7 @@ void Aura::ProcessOnAllFriendlies(Mob *owner)
|
||||
|
||||
void Aura::ProcessOnAllGroupMembers(Mob *owner)
|
||||
{
|
||||
auto &mob_list = entity_list.GetCloseMobList(this, distance);
|
||||
auto &mob_list = GetCloseMobList(distance);
|
||||
std::set<int> delayed_remove;
|
||||
bool is_buff = IsBuffSpell(spell_id); // non-buff spells don't cast on enter
|
||||
|
||||
@@ -369,7 +369,7 @@ void Aura::ProcessOnAllGroupMembers(Mob *owner)
|
||||
|
||||
void Aura::ProcessOnGroupMembersPets(Mob *owner)
|
||||
{
|
||||
auto &mob_list = entity_list.GetCloseMobList(this,distance);
|
||||
auto &mob_list = GetCloseMobList(distance);
|
||||
std::set<int> delayed_remove;
|
||||
bool is_buff = IsBuffSpell(spell_id); // non-buff spells don't cast on enter
|
||||
// This type can either live on the pet (level 55/70 MAG aura) or on the pet owner (level 85 MAG aura)
|
||||
@@ -576,7 +576,7 @@ void Aura::ProcessOnGroupMembersPets(Mob *owner)
|
||||
|
||||
void Aura::ProcessTotem(Mob *owner)
|
||||
{
|
||||
auto &mob_list = entity_list.GetCloseMobList(this, distance);
|
||||
auto &mob_list = GetCloseMobList(distance);
|
||||
std::set<int> delayed_remove;
|
||||
bool is_buff = IsBuffSpell(spell_id); // non-buff spells don't cast on enter
|
||||
|
||||
@@ -634,9 +634,7 @@ void Aura::ProcessTotem(Mob *owner)
|
||||
|
||||
void Aura::ProcessEnterTrap(Mob *owner)
|
||||
{
|
||||
auto &mob_list = entity_list.GetCloseMobList(this, distance);
|
||||
|
||||
for (auto &e : mob_list) {
|
||||
for (auto &e : GetCloseMobList(distance)) {
|
||||
auto mob = e.second;
|
||||
if (!mob) {
|
||||
continue;
|
||||
@@ -656,9 +654,7 @@ void Aura::ProcessEnterTrap(Mob *owner)
|
||||
|
||||
void Aura::ProcessExitTrap(Mob *owner)
|
||||
{
|
||||
auto &mob_list = entity_list.GetCloseMobList(this, distance);
|
||||
|
||||
for (auto &e : mob_list) {
|
||||
for (auto &e : GetCloseMobList(distance)) {
|
||||
auto mob = e.second;
|
||||
if (!mob) {
|
||||
continue;
|
||||
@@ -689,8 +685,7 @@ void Aura::ProcessExitTrap(Mob *owner)
|
||||
// and hard to reason about
|
||||
void Aura::ProcessSpawns()
|
||||
{
|
||||
const auto &clients = entity_list.GetCloseMobList(this, distance);
|
||||
for (auto &e : clients) {
|
||||
for (auto &e: GetCloseMobList(distance)) {
|
||||
if (!e.second) {
|
||||
continue;
|
||||
}
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ Beacon::Beacon(const glm::vec4 &in_pos, int lifetime) : Mob(
|
||||
Gender::Male, // in_gender
|
||||
Race::InvisibleMan, // in_race
|
||||
Class::None, // in_class
|
||||
BT_NoTarget, // in_bodytype
|
||||
BodyType::NoTarget, // in_bodytype
|
||||
Deity::Unknown, // in_deity
|
||||
0, // in_level
|
||||
0, // in_npctype_id
|
||||
|
||||
+22
-12
@@ -182,14 +182,20 @@ void Mob::CalcItemBonuses(StatBonuses* b) {
|
||||
SetDualWeaponsEquipped(true);
|
||||
}
|
||||
|
||||
if (IsOfClientBot()) {
|
||||
for (i = EQ::invslot::TRIBUTE_BEGIN; i <= EQ::invslot::TRIBUTE_END; i++) {
|
||||
const EQ::ItemInstance* inst = m_inv[i];
|
||||
if (!inst) {
|
||||
continue;
|
||||
}
|
||||
if (IsClient()) {
|
||||
if (CastToClient()->GetPP().tribute_active) {
|
||||
for (auto const &t: CastToClient()->GetPP().tributes) {
|
||||
auto item_id = CastToClient()->LookupTributeItemID(t.tribute, t.tier);
|
||||
if (item_id) {
|
||||
const EQ::ItemInstance *inst = database.CreateItem(item_id);
|
||||
if (!inst) {
|
||||
continue;
|
||||
}
|
||||
|
||||
AddItemBonuses(inst, b, false, true);
|
||||
AddItemBonuses(inst, b, false, true);
|
||||
safe_delete(inst);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1439,8 +1445,8 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon)
|
||||
|
||||
case SE_SlayUndead: {
|
||||
if (newbon->SlayUndead[SBIndex::SLAYUNDEAD_DMG_MOD] < base_value) {
|
||||
newbon->SlayUndead[SBIndex::SLAYUNDEAD_RATE_MOD] = base_value; // Rate
|
||||
newbon->SlayUndead[SBIndex::SLAYUNDEAD_DMG_MOD] = limit_value; // Damage Modifier
|
||||
newbon->SlayUndead[SBIndex::SLAYUNDEAD_DMG_MOD] = base_value; // Rate
|
||||
newbon->SlayUndead[SBIndex::SLAYUNDEAD_RATE_MOD] = limit_value; // Damage Modifier
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -2438,6 +2444,10 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne
|
||||
case SE_CastingLevel2:
|
||||
{
|
||||
new_bonus->effective_casting_level += effect_value;
|
||||
|
||||
if (RuleB(Spells, SnareOverridesSpeedBonuses) && effect_value < 0) {
|
||||
new_bonus->movementspeed = effect_value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -3356,7 +3366,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne
|
||||
break;
|
||||
|
||||
case SE_Blind:
|
||||
if (RuleB(Combat, AllowRaidTargetBlind) && IsRaidTarget()) { // do not blind raid targets
|
||||
if (!RuleB(Combat, AllowRaidTargetBlind) && IsRaidTarget()) { // do not blind raid targets
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -3585,8 +3595,8 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne
|
||||
|
||||
case SE_SlayUndead: {
|
||||
if (new_bonus->SlayUndead[SBIndex::SLAYUNDEAD_DMG_MOD] < effect_value) {
|
||||
new_bonus->SlayUndead[SBIndex::SLAYUNDEAD_RATE_MOD] = effect_value; // Rate
|
||||
new_bonus->SlayUndead[SBIndex::SLAYUNDEAD_DMG_MOD] = limit_value; // Damage Modifier
|
||||
new_bonus->SlayUndead[SBIndex::SLAYUNDEAD_RATE_MOD] = limit_value; // Rate
|
||||
new_bonus->SlayUndead[SBIndex::SLAYUNDEAD_DMG_MOD] = effect_value; // Damage Modifier
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
+15
-29
@@ -204,7 +204,7 @@ Bot::Bot(
|
||||
);
|
||||
}
|
||||
|
||||
SetTaunting((GetClass() == Class::Warrior || GetClass() == Class::Paladin || GetClass() == Class::ShadowKnight) && (GetBotStance() == EQ::constants::stanceAggressive));
|
||||
SetTaunting((GetClass() == Class::Warrior || GetClass() == Class::Paladin || GetClass() == Class::ShadowKnight) && (GetBotStance() == Stance::Aggressive));
|
||||
SetPauseAI(false);
|
||||
|
||||
m_auto_defend_timer.Disable();
|
||||
@@ -1578,17 +1578,7 @@ bool Bot::Process()
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mob_close_scan_timer.Check()) {
|
||||
LogAIScanCloseDetail(
|
||||
"is_moving [{}] bot [{}] timer [{}]",
|
||||
moving ? "true" : "false",
|
||||
GetCleanName(),
|
||||
mob_close_scan_timer.GetDuration()
|
||||
);
|
||||
|
||||
entity_list.ScanCloseMobs(close_mobs, this, IsMoving());
|
||||
}
|
||||
|
||||
ScanCloseMobProcess();
|
||||
SpellProcess();
|
||||
|
||||
if (tic_timer.Check()) {
|
||||
@@ -1899,8 +1889,8 @@ void Bot::AI_Process()
|
||||
#define NOT_GUARDING (!GetGuardFlag())
|
||||
#define HOLDING (GetHoldFlag())
|
||||
#define NOT_HOLDING (!GetHoldFlag())
|
||||
#define PASSIVE (GetBotStance() == EQ::constants::stancePassive)
|
||||
#define NOT_PASSIVE (GetBotStance() != EQ::constants::stancePassive)
|
||||
#define PASSIVE (GetBotStance() == Stance::Passive)
|
||||
#define NOT_PASSIVE (GetBotStance() != Stance::Passive)
|
||||
|
||||
Client* bot_owner = (GetBotOwner() && GetBotOwner()->IsClient() ? GetBotOwner()->CastToClient() : nullptr);
|
||||
|
||||
@@ -2978,7 +2968,7 @@ void Bot::SetOwnerTarget(Client* bot_owner) {
|
||||
if (NOT_HOLDING && NOT_PASSIVE) {
|
||||
|
||||
auto attack_target = bot_owner->GetTarget();
|
||||
if (attack_target) {
|
||||
if (attack_target && HasBotAttackFlag(attack_target)) {
|
||||
|
||||
InterruptSpell();
|
||||
WipeHateList();
|
||||
@@ -5481,13 +5471,13 @@ bool Bot::IsImmuneToSpell(uint16 spell_id, Mob *caster) {
|
||||
if (!Result) {
|
||||
if (caster->IsBot()) {
|
||||
if (spells[spell_id].target_type == ST_Undead) {
|
||||
if ((GetBodyType() != BT_SummonedUndead) && (GetBodyType() != BT_Undead) && (GetBodyType() != BT_Vampire)) {
|
||||
if ((GetBodyType() != BodyType::SummonedUndead) && (GetBodyType() != BodyType::Undead) && (GetBodyType() != BodyType::Vampire)) {
|
||||
LogSpellsDetail("Bot's target is not an undead");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (spells[spell_id].target_type == ST_Summoned) {
|
||||
if ((GetBodyType() != BT_SummonedUndead) && (GetBodyType() != BT_Summoned) && (GetBodyType() != BT_Summoned2) && (GetBodyType() != BT_Summoned3)) {
|
||||
if ((GetBodyType() != BodyType::SummonedUndead) && (GetBodyType() != BodyType::Summoned) && (GetBodyType() != BodyType::Summoned2) && (GetBodyType() != BodyType::Summoned3)) {
|
||||
LogSpellsDetail("Bot's target is not a summoned creature");
|
||||
return true;
|
||||
}
|
||||
@@ -6912,16 +6902,16 @@ bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, fl
|
||||
if ((botCasterClass == Class::Paladin || botCasterClass == Class::Beastlord || botCasterClass == Class::Ranger) && (caster->HasGroup() || caster->IsRaidGrouped())) {
|
||||
float hpRatioToHeal = 25.0f;
|
||||
switch(caster->GetBotStance()) {
|
||||
case EQ::constants::stanceReactive:
|
||||
case EQ::constants::stanceBalanced:
|
||||
case Stance::Reactive:
|
||||
case Stance::Balanced:
|
||||
hpRatioToHeal = 50.0f;
|
||||
break;
|
||||
case EQ::constants::stanceBurn:
|
||||
case EQ::constants::stanceBurnAE:
|
||||
case Stance::Burn:
|
||||
case Stance::AEBurn:
|
||||
hpRatioToHeal = 20.0f;
|
||||
break;
|
||||
case EQ::constants::stanceAggressive:
|
||||
case EQ::constants::stanceEfficient:
|
||||
case Stance::Aggressive:
|
||||
case Stance::Efficient:
|
||||
default:
|
||||
hpRatioToHeal = 25.0f;
|
||||
break;
|
||||
@@ -7655,11 +7645,7 @@ bool Bot::HasOrMayGetAggro() {
|
||||
}
|
||||
|
||||
void Bot::SetDefaultBotStance() {
|
||||
EQ::constants::StanceType defaultStance = EQ::constants::stanceBalanced;
|
||||
if (GetClass() == Class::Warrior)
|
||||
defaultStance = EQ::constants::stanceAggressive;
|
||||
|
||||
_botStance = defaultStance;
|
||||
_botStance = GetClass() == Class::Warrior ? Stance::Aggressive : Stance::Balanced;
|
||||
}
|
||||
|
||||
void Bot::BotGroupSay(Mob* speaker, const char* msg, ...) {
|
||||
@@ -9233,4 +9219,4 @@ void Bot::DoItemClick(const EQ::ItemData *item, uint16 slot_id)
|
||||
|
||||
}
|
||||
|
||||
uint8 Bot::spell_casting_chances[SPELL_TYPE_COUNT][Class::PLAYER_CLASS_COUNT][EQ::constants::STANCE_TYPE_COUNT][cntHSND] = { 0 };
|
||||
uint8 Bot::spell_casting_chances[SPELL_TYPE_COUNT][Class::PLAYER_CLASS_COUNT][Stance::AEBurn][cntHSND] = { 0 };
|
||||
|
||||
+4
-9
@@ -484,7 +484,7 @@ public:
|
||||
bool IsOfClientBotMerc() const override { return true; }
|
||||
|
||||
bool GetRangerAutoWeaponSelect() { return _rangerAutoWeaponSelect; }
|
||||
EQ::constants::StanceType GetBotStance() { return _botStance; }
|
||||
uint8 GetBotStance() { return _botStance; }
|
||||
uint8 GetChanceToCastBySpellType(uint32 spellType);
|
||||
bool GetBotEnforceSpellSetting() { return m_enforce_spell_settings; }
|
||||
float GetBotCasterMaxRange(float melee_distance_max);
|
||||
@@ -605,12 +605,7 @@ public:
|
||||
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(EQ::constants::StanceType botStance) {
|
||||
if (botStance >= EQ::constants::stancePassive && botStance <= EQ::constants::stanceBurnAE)
|
||||
_botStance = botStance;
|
||||
else
|
||||
_botStance = EQ::constants::stancePassive;
|
||||
}
|
||||
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);
|
||||
@@ -753,7 +748,7 @@ public:
|
||||
//Raid additions
|
||||
Raid* p_raid_instance;
|
||||
|
||||
static uint8 spell_casting_chances[SPELL_TYPE_COUNT][Class::PLAYER_CLASS_COUNT][EQ::constants::STANCE_TYPE_COUNT][cntHSND];
|
||||
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);
|
||||
@@ -870,7 +865,7 @@ private:
|
||||
std::string _suffix;
|
||||
uint32 _lastZoneId;
|
||||
bool _rangerAutoWeaponSelect;
|
||||
EQ::constants::StanceType _botStance;
|
||||
uint8 _botStance;
|
||||
unsigned int RestRegenHP;
|
||||
unsigned int RestRegenMana;
|
||||
unsigned int RestRegenEndurance;
|
||||
|
||||
+8
-8
@@ -753,19 +753,19 @@ namespace ActionableTarget
|
||||
verified_friendly = target_mob;
|
||||
break;
|
||||
case BCEnum::TT_Animal:
|
||||
if (target_mob && target_mob->GetBodyType() == BT_Animal)
|
||||
if (target_mob && target_mob->GetBodyType() == BodyType::Animal)
|
||||
verified_friendly = target_mob;
|
||||
break;
|
||||
case BCEnum::TT_Undead:
|
||||
if (target_mob && target_mob->GetBodyType() == BT_Undead)
|
||||
if (target_mob && target_mob->GetBodyType() == BodyType::Undead)
|
||||
verified_friendly = target_mob;
|
||||
break;
|
||||
case BCEnum::TT_Summoned:
|
||||
if (target_mob && target_mob->GetBodyType() == BT_Summoned)
|
||||
if (target_mob && target_mob->GetBodyType() == BodyType::Summoned)
|
||||
verified_friendly = target_mob;
|
||||
break;
|
||||
case BCEnum::TT_Plant:
|
||||
if (target_mob && target_mob->GetBodyType() == BT_Plant)
|
||||
if (target_mob && target_mob->GetBodyType() == BodyType::Plant)
|
||||
verified_friendly = target_mob;
|
||||
break;
|
||||
case BCEnum::TT_Corpse:
|
||||
@@ -800,19 +800,19 @@ namespace ActionableTarget
|
||||
Mob* verified_enemy = nullptr;
|
||||
switch (target_type) {
|
||||
case BCEnum::TT_Animal:
|
||||
if (target_mob->GetBodyType() == BT_Animal)
|
||||
if (target_mob->GetBodyType() == BodyType::Animal)
|
||||
verified_enemy = target_mob;
|
||||
break;
|
||||
case BCEnum::TT_Undead:
|
||||
if (target_mob->GetBodyType() == BT_Undead)
|
||||
if (target_mob->GetBodyType() == BodyType::Undead)
|
||||
verified_enemy = target_mob;
|
||||
break;
|
||||
case BCEnum::TT_Summoned:
|
||||
if (target_mob->GetBodyType() == BT_Summoned)
|
||||
if (target_mob->GetBodyType() == BodyType::Summoned)
|
||||
verified_enemy = target_mob;
|
||||
break;
|
||||
case BCEnum::TT_Plant:
|
||||
if (target_mob->GetBodyType() == BT_Plant)
|
||||
if (target_mob->GetBodyType() == BodyType::Plant)
|
||||
verified_enemy = target_mob;
|
||||
break;
|
||||
case BCEnum::TT_Single:
|
||||
|
||||
@@ -35,12 +35,17 @@ void bot_command_attack(Client *c, const Seperator *sep)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!c->HasBotAttackFlag(target_mob)) {
|
||||
target_mob->SetBotAttackFlag(c->CharacterID());
|
||||
target_mob->bot_attack_flag_timer.Start(10000);
|
||||
}
|
||||
|
||||
size_t attacker_count = 0;
|
||||
Bot *first_attacker = nullptr;
|
||||
sbl.remove(nullptr);
|
||||
for (auto bot_iter : sbl) {
|
||||
|
||||
if (bot_iter->GetAppearance() != eaDead && bot_iter->GetBotStance() != EQ::constants::stancePassive) {
|
||||
if (bot_iter->GetAppearance() != eaDead && bot_iter->GetBotStance() != Stance::Passive) {
|
||||
|
||||
if (!first_attacker) {
|
||||
first_attacker = bot_iter;
|
||||
|
||||
+43
-21
@@ -198,7 +198,7 @@ void bot_command_clone(Client *c, const Seperator *sep)
|
||||
return;
|
||||
}
|
||||
|
||||
int clone_stance = EQ::constants::stancePassive;
|
||||
int clone_stance = Stance::Passive;
|
||||
if (!database.botdb.LoadStance(my_bot->GetBotID(), clone_stance)) {
|
||||
c->Message(
|
||||
Chat::White,
|
||||
@@ -857,7 +857,7 @@ void bot_command_spawn(Client *c, const Seperator *sep)
|
||||
c->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"Usage: {} [bot_name]",
|
||||
"Usage: {} [bot_name] [optional: silent]",
|
||||
sep->arg[0]
|
||||
).c_str()
|
||||
);
|
||||
@@ -1045,9 +1045,17 @@ void bot_command_spawn(Client *c, const Seperator *sep)
|
||||
message_index = VALIDATECLASSID(my_bot->GetClass());
|
||||
}
|
||||
|
||||
if (c->GetBotOption(Client::booSpawnMessageSay)) {
|
||||
std::string silent_confirm = sep->arg[2];
|
||||
bool silentTell = false;
|
||||
|
||||
if (!silent_confirm.compare("silent")) {
|
||||
silentTell = true;
|
||||
}
|
||||
|
||||
if (!silentTell && c->GetBotOption(Client::booSpawnMessageSay)) {
|
||||
Bot::BotGroupSay(my_bot, bot_spawn_message[message_index].c_str());
|
||||
} else if (c->GetBotOption(Client::booSpawnMessageTell)) {
|
||||
}
|
||||
else if (!silentTell && c->GetBotOption(Client::booSpawnMessageTell)) {
|
||||
my_bot->OwnerMessage(bot_spawn_message[message_index]);
|
||||
}
|
||||
}
|
||||
@@ -1058,33 +1066,47 @@ void bot_command_stance(Client *c, const Seperator *sep)
|
||||
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, "value: %u(%s), %u(%s), %u(%s), %u(%s), %u(%s), %u(%s), %u(%s)",
|
||||
EQ::constants::stancePassive, EQ::constants::GetStanceName(EQ::constants::stancePassive),
|
||||
EQ::constants::stanceBalanced, EQ::constants::GetStanceName(EQ::constants::stanceBalanced),
|
||||
EQ::constants::stanceEfficient, EQ::constants::GetStanceName(EQ::constants::stanceEfficient),
|
||||
EQ::constants::stanceReactive, EQ::constants::GetStanceName(EQ::constants::stanceReactive),
|
||||
EQ::constants::stanceAggressive, EQ::constants::GetStanceName(EQ::constants::stanceAggressive),
|
||||
EQ::constants::stanceAssist, EQ::constants::GetStanceName(EQ::constants::stanceAssist),
|
||||
EQ::constants::stanceBurn, EQ::constants::GetStanceName(EQ::constants::stanceBurn),
|
||||
EQ::constants::stanceEfficient2, EQ::constants::GetStanceName(EQ::constants::stanceEfficient2),
|
||||
EQ::constants::stanceBurnAE, EQ::constants::GetStanceName(EQ::constants::stanceBurnAE)
|
||||
c->Message(
|
||||
Chat::White,
|
||||
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,
|
||||
Stance::GetName(Stance::Aggressive),
|
||||
Stance::Assist,
|
||||
Stance::GetName(Stance::Assist),
|
||||
Stance::Burn,
|
||||
Stance::GetName(Stance::Burn),
|
||||
Stance::Efficient2,
|
||||
Stance::GetName(Stance::Efficient2),
|
||||
Stance::AEBurn,
|
||||
Stance::GetName(Stance::AEBurn)
|
||||
).c_str()
|
||||
);
|
||||
return;
|
||||
}
|
||||
int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_ByName);
|
||||
|
||||
bool current_flag = false;
|
||||
auto bst = EQ::constants::stanceUnknown;
|
||||
uint8 bst = Stance::Unknown;
|
||||
|
||||
if (!strcasecmp(sep->arg[1], "current"))
|
||||
current_flag = true;
|
||||
else if (sep->IsNumber(1)) {
|
||||
bst = (EQ::constants::StanceType)Strings::ToInt(sep->arg[1]);
|
||||
if (bst < EQ::constants::stanceUnknown || bst > EQ::constants::stanceBurnAE)
|
||||
bst = EQ::constants::stanceUnknown;
|
||||
bst = static_cast<uint8>(Strings::ToUnsignedInt(sep->arg[1]));
|
||||
if (!Stance::IsValid(bst)) {
|
||||
bst = Stance::Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
if (!current_flag && bst == EQ::constants::stanceUnknown) {
|
||||
if (!current_flag && bst == Stance::Unknown) {
|
||||
c->Message(Chat::White, "A [current] argument or valid numeric [value] is required to use this command");
|
||||
return;
|
||||
}
|
||||
@@ -1106,8 +1128,8 @@ void bot_command_stance(Client *c, const Seperator *sep)
|
||||
bot_iter,
|
||||
fmt::format(
|
||||
"My current stance is {} ({}).",
|
||||
EQ::constants::GetStanceName(bot_iter->GetBotStance()),
|
||||
static_cast<int>(bot_iter->GetBotStance())
|
||||
Stance::GetName(bot_iter->GetBotStance()),
|
||||
bot_iter->GetBotStance()
|
||||
).c_str()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ void bot_command_pull(Client *c, const Seperator *sep)
|
||||
Bot* bot_puller = nullptr;
|
||||
for (auto bot_iter : sbl) {
|
||||
|
||||
if (bot_iter->GetAppearance() == eaDead || bot_iter->GetBotStance() == EQ::constants::stancePassive) {
|
||||
if (bot_iter->GetAppearance() == eaDead || bot_iter->GetBotStance() == Stance::Passive) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -135,7 +135,7 @@ bool BotDatabase::LoadBotSpellCastingChances()
|
||||
if (
|
||||
e.spell_type_index >= Bot::SPELL_TYPE_COUNT ||
|
||||
!IsPlayerClass(e.class_id) ||
|
||||
e.stance_index >= EQ::constants::STANCE_TYPE_COUNT
|
||||
e.stance_index >= Stance::AEBurn
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
@@ -761,7 +761,7 @@ bool BotDatabase::LoadStance(Bot* b, bool& stance_flag)
|
||||
|
||||
auto e = l.front();
|
||||
|
||||
b->SetBotStance(static_cast<EQ::constants::StanceType>(e.stance_id));
|
||||
b->SetBotStance(e.stance_id);
|
||||
|
||||
stance_flag = true;
|
||||
|
||||
@@ -793,7 +793,7 @@ bool BotDatabase::SaveStance(Bot* b)
|
||||
database,
|
||||
BotStancesRepository::BotStances{
|
||||
.bot_id = b->GetBotID(),
|
||||
.stance_id = static_cast<uint8_t>(b->GetBotStance())
|
||||
.stance_id = b->GetBotStance()
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -828,7 +828,7 @@ bool BotDatabase::LoadTimers(Bot* b)
|
||||
BotTimer_Struct t{ };
|
||||
|
||||
for (const auto& e : l) {
|
||||
if (t.timer_value < (Timer::GetCurrentTime() + t.recast_time)) {
|
||||
if (e.timer_value < (Timer::GetCurrentTime() + e.recast_time)) {
|
||||
t.timer_id = e.timer_id;
|
||||
t.timer_value = e.timer_value;
|
||||
t.recast_time = e.recast_time;
|
||||
@@ -1451,7 +1451,7 @@ bool BotDatabase::DeletePetBuffs(const uint32 bot_id)
|
||||
return true;
|
||||
}
|
||||
|
||||
BotPetBuffsRepository::DeleteOne(database, saved_pet_index);
|
||||
BotPetBuffsRepository::DeleteWhere(database, fmt::format("pets_index = {}", saved_pet_index));
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -2208,7 +2208,7 @@ uint8 BotDatabase::GetSpellCastingChance(uint8 spell_type_index, uint8 class_ind
|
||||
if (
|
||||
spell_type_index >= Bot::SPELL_TYPE_COUNT ||
|
||||
class_index >= Class::PLAYER_CLASS_COUNT ||
|
||||
stance_index >= EQ::constants::STANCE_TYPE_COUNT ||
|
||||
stance_index >= Stance::AEBurn ||
|
||||
conditional_index >= cntHSND
|
||||
) {
|
||||
return 0;
|
||||
|
||||
+3
-1
@@ -312,10 +312,12 @@ void Client::SpawnRaidBotsOnConnect(Raid* raid) {
|
||||
for (const auto& m: r_members) {
|
||||
if (strlen(m.member_name) != 0) {
|
||||
|
||||
for (const auto& b: bots_list) {
|
||||
for (const auto& b : bots_list) {
|
||||
if (strcmp(m.member_name, b.bot_name) == 0) {
|
||||
std::string buffer = "^spawn ";
|
||||
buffer.append(m.member_name);
|
||||
std::string silent = " silent";
|
||||
buffer.append(silent);
|
||||
bot_command_real_dispatch(this, buffer.c_str());
|
||||
auto bot = entity_list.GetBotByBotName(m.member_name);
|
||||
|
||||
|
||||
+45
-43
@@ -629,16 +629,16 @@ bool Bot::BotCastCombatBuff(Mob* tar, uint8 botLevel, uint8 botClass) {
|
||||
float manaRatioToCast = 75.0f;
|
||||
|
||||
switch(GetBotStance()) {
|
||||
case EQ::constants::stanceEfficient:
|
||||
case Stance::Efficient:
|
||||
manaRatioToCast = 90.0f;
|
||||
break;
|
||||
case EQ::constants::stanceBalanced:
|
||||
case EQ::constants::stanceAggressive:
|
||||
case Stance::Balanced:
|
||||
case Stance::Aggressive:
|
||||
manaRatioToCast = 75.0f;
|
||||
break;
|
||||
case EQ::constants::stanceReactive:
|
||||
case EQ::constants::stanceBurn:
|
||||
case EQ::constants::stanceBurnAE:
|
||||
case Stance::Reactive:
|
||||
case Stance::Burn:
|
||||
case Stance::AEBurn:
|
||||
manaRatioToCast = 50.0f;
|
||||
break;
|
||||
default:
|
||||
@@ -746,18 +746,18 @@ bool Bot::BotCastNuke(Mob* tar, uint8 botLevel, uint8 botClass, BotSpell& botSpe
|
||||
float manaRatioToCast = 75.0f;
|
||||
|
||||
switch(GetBotStance()) {
|
||||
case EQ::constants::stanceEfficient:
|
||||
case Stance::Efficient:
|
||||
manaRatioToCast = 90.0f;
|
||||
break;
|
||||
case EQ::constants::stanceBalanced:
|
||||
case Stance::Balanced:
|
||||
manaRatioToCast = 75.0f;
|
||||
break;
|
||||
case EQ::constants::stanceReactive:
|
||||
case EQ::constants::stanceAggressive:
|
||||
case Stance::Reactive:
|
||||
case Stance::Aggressive:
|
||||
manaRatioToCast = 50.0f;
|
||||
break;
|
||||
case EQ::constants::stanceBurn:
|
||||
case EQ::constants::stanceBurnAE:
|
||||
case Stance::Burn:
|
||||
case Stance::AEBurn:
|
||||
manaRatioToCast = 25.0f;
|
||||
break;
|
||||
default:
|
||||
@@ -771,9 +771,9 @@ bool Bot::BotCastNuke(Mob* tar, uint8 botLevel, uint8 botClass, BotSpell& botSpe
|
||||
}
|
||||
|
||||
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() == BT_Undead || tar->GetBodyType() == BT_SummonedUndead || tar->GetBodyType() == BT_Vampire)
|
||||
if (tar->GetBodyType() == BodyType::Undead || tar->GetBodyType() == BodyType::SummonedUndead || tar->GetBodyType() == BodyType::Vampire)
|
||||
botSpell = GetBestBotSpellForNukeByTargetType(this, ST_Undead);
|
||||
else if (tar->GetBodyType() == BT_Summoned || tar->GetBodyType() == BT_Summoned2 || tar->GetBodyType() == BT_Summoned3)
|
||||
else if (tar->GetBodyType() == BodyType::Summoned || tar->GetBodyType() == BodyType::Summoned2 || tar->GetBodyType() == BodyType::Summoned3)
|
||||
botSpell = GetBestBotSpellForNukeByTargetType(this, ST_Summoned);
|
||||
}
|
||||
|
||||
@@ -924,16 +924,16 @@ bool Bot::BotCastBuff(Mob* tar, uint8 botLevel, uint8 botClass) {
|
||||
float manaRatioToCast = 75.0f;
|
||||
|
||||
switch (GetBotStance()) {
|
||||
case EQ::constants::stanceEfficient:
|
||||
case Stance::Efficient:
|
||||
manaRatioToCast = 90.0f;
|
||||
break;
|
||||
case EQ::constants::stanceBalanced:
|
||||
case EQ::constants::stanceAggressive:
|
||||
case Stance::Balanced:
|
||||
case Stance::Aggressive:
|
||||
manaRatioToCast = 75.0f;
|
||||
break;
|
||||
case EQ::constants::stanceReactive:
|
||||
case EQ::constants::stanceBurn:
|
||||
case EQ::constants::stanceBurnAE:
|
||||
case Stance::Reactive:
|
||||
case Stance::Burn:
|
||||
case Stance::AEBurn:
|
||||
manaRatioToCast = 50.0f;
|
||||
break;
|
||||
default:
|
||||
@@ -1088,18 +1088,18 @@ bool Bot::BotCastHeal(Mob* tar, uint8 botLevel, uint8 botClass, BotSpell& botSpe
|
||||
float hpRatioToCast = 0.0f;
|
||||
|
||||
switch (GetBotStance()) {
|
||||
case EQ::constants::stanceEfficient:
|
||||
case EQ::constants::stanceAggressive:
|
||||
case Stance::Efficient:
|
||||
case Stance::Aggressive:
|
||||
hpRatioToCast = isPrimaryHealer ? 90.0f : 50.0f;
|
||||
break;
|
||||
case EQ::constants::stanceBalanced:
|
||||
case Stance::Balanced:
|
||||
hpRatioToCast = isPrimaryHealer ? 95.0f : 75.0f;
|
||||
break;
|
||||
case EQ::constants::stanceReactive:
|
||||
case Stance::Reactive:
|
||||
hpRatioToCast = isPrimaryHealer ? 100.0f : 90.0f;
|
||||
break;
|
||||
case EQ::constants::stanceBurn:
|
||||
case EQ::constants::stanceBurnAE:
|
||||
case Stance::Burn:
|
||||
case Stance::AEBurn:
|
||||
hpRatioToCast = isPrimaryHealer ? 75.0f : 25.0f;
|
||||
break;
|
||||
default:
|
||||
@@ -2939,11 +2939,12 @@ uint8 Bot::GetChanceToCastBySpellType(uint32 spellType)
|
||||
return 0;
|
||||
--class_index;
|
||||
|
||||
EQ::constants::StanceType stance_type = GetBotStance();
|
||||
if (stance_type < EQ::constants::stancePassive || stance_type > EQ::constants::stanceBurnAE)
|
||||
uint32 stance_id = GetBotStance();
|
||||
if (!Stance::IsValid(stance_id)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8 stance_index = EQ::constants::ConvertStanceTypeToIndex(stance_type);
|
||||
uint8 stance_index = Stance::GetIndex(stance_id);
|
||||
uint8 type_index = nHSND;
|
||||
|
||||
if (HasGroup()) {
|
||||
@@ -3323,18 +3324,19 @@ DBbotspells_Struct* ZoneDatabase::GetBotSpells(uint32 bot_spell_id)
|
||||
if (!bse.empty()) {
|
||||
for (const auto& e : bse) {
|
||||
DBbotspells_entries_Struct entry;
|
||||
entry.spellid = e.spellid;
|
||||
entry.type = e.type;
|
||||
entry.minlevel = e.minlevel;
|
||||
entry.maxlevel = e.maxlevel;
|
||||
entry.manacost = e.manacost;
|
||||
entry.recast_delay = e.recast_delay;
|
||||
entry.priority = e.priority;
|
||||
entry.min_hp = e.min_hp;
|
||||
entry.max_hp = e.max_hp;
|
||||
entry.resist_adjust = e.resist_adjust;
|
||||
entry.bucket_name = e.bucket_name;
|
||||
entry.bucket_value = e.bucket_value;
|
||||
|
||||
entry.spellid = e.spell_id;
|
||||
entry.type = e.type;
|
||||
entry.minlevel = e.minlevel;
|
||||
entry.maxlevel = e.maxlevel;
|
||||
entry.manacost = e.manacost;
|
||||
entry.recast_delay = e.recast_delay;
|
||||
entry.priority = e.priority;
|
||||
entry.min_hp = e.min_hp;
|
||||
entry.max_hp = e.max_hp;
|
||||
entry.resist_adjust = e.resist_adjust;
|
||||
entry.bucket_name = e.bucket_name;
|
||||
entry.bucket_value = e.bucket_value;
|
||||
entry.bucket_comparison = e.bucket_comparison;
|
||||
|
||||
// some spell types don't make much since to be priority 0, so fix that
|
||||
@@ -3344,8 +3346,8 @@ DBbotspells_Struct* ZoneDatabase::GetBotSpells(uint32 bot_spell_id)
|
||||
|
||||
if (e.resist_adjust) {
|
||||
entry.resist_adjust = e.resist_adjust;
|
||||
} else if (IsValidSpell(e.spellid)) {
|
||||
entry.resist_adjust = spells[e.spellid].resist_difficulty;
|
||||
} else if (IsValidSpell(e.spell_id)) {
|
||||
entry.resist_adjust = spells[e.spell_id].resist_difficulty;
|
||||
}
|
||||
|
||||
spell_set.entries.push_back(entry);
|
||||
|
||||
+663
-167
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user