Compare commits

...

130 Commits

Author SHA1 Message Date
KimLS fa91559a85 Starting work on character guids 2024-11-14 22:13:36 -08:00
KimLS ce728c4d0d This is close but no cigar yet. 2024-11-10 10:07:32 -08:00
KimLS d29937cba6 WIP player profile 2024-11-07 23:03:38 -08:00
KimLS 799593c394 Working on zone packets. 2024-11-05 21:38:10 -08:00
KimLS b7c93e12de Add some sanity checking for stream_parser so it's less likely to pickup other protocols as an everquest protocol. 2024-10-26 10:03:57 -07:00
KimLS c1651b7dca Fix char select 2024-10-25 22:47:27 -07:00
KimLS 4093b505d3 wip char select 2024-10-24 21:11:01 -07:00
KimLS dbb0288a13 Filled out a lot of the version stuff for a new client version (oh my lawd this could be easier!) 2024-10-24 20:35:53 -07:00
KimLS 0a9a941b6b WIP on membership 2024-10-23 21:53:24 -07:00
KimLS 702024fe04 Encode OP_LogServer 2024-10-23 18:15:40 -07:00
KimLS acdd65b55c Distracted by world server issues 2024-10-22 20:08:31 -07:00
KimLS 6db7c624e9 Larion expansion data 2024-10-22 17:52:14 -07:00
KimLS f485dfd8d3 WIP logserver struct 2024-10-21 19:31:45 -07:00
KimLS 823bacf08a Starting to poke at op_logserver 2024-10-20 19:18:32 -07:00
KimLS b8ecb297ec Beginning of world opcodes 2024-10-19 22:34:43 -07:00
KimLS 7f7c80eab7 Merge branch 'stream_parser' into lsong_world 2024-10-19 18:54:20 -07:00
KimLS 0c87af7d6b Decompress wont default to false 2024-10-18 17:32:55 -07:00
KimLS 2e087cde5b Force add stream_parser.sln 2024-10-18 17:23:12 -07:00
KimLS a7c1c85f71 Initial 2024-10-18 17:18:37 -07:00
KimLS d1c7c00f19 Binary dumping; not yet implemented 2024-10-18 15:49:49 -07:00
KimLS 999ccdcb19 Add experimental decompression support. 2024-10-17 22:53:33 -07:00
KimLS d3cd037fa7 Fix for broken AppCombined; both logic error and bad enum doh 2024-10-17 20:12:33 -07:00
Alex King be42b73f5c [Rules] Add Rule to disable PVP Regions (#4513) 2024-10-17 01:48:19 -05:00
Mitch Freeman f76c798910 [BugFix] Fix a display error regarding a few trader/buyer query errors (#4514) 2024-10-17 01:43:24 -05:00
KimLS dc1509e768 update from 6LTS to 8LTS because 6 goes out of support in november. 2024-10-16 23:08:33 -07:00
KimLS 124b9c7abe Update packages 2024-10-16 23:07:30 -07:00
KimLS 534de0c414 Add my stream parser to utils, simple probably could use some work. 2024-10-16 23:04:10 -07:00
Alex ae198ae043 [Crash] Fixes a crash when the faction_list db table is empty. (#4511)
Co-authored-by: KimLS <KimLS@peqtgc.com>
2024-10-13 20:50:28 -05:00
Alex King 520943ebf1 [Logs] Add NPC Trades to Player Events (#4505)
* [Logs] Add NPC Trades to Player Events

* Update player_event_discord_formatter.cpp

* Push

* Fix money and add NPC info

* [Logs] Add NPC Trades to Player Events

* Update player_event_discord_formatter.cpp

* Push

* Minor logic fix

* Push

* Update perl_client.cpp

---------

Co-authored-by: Akkadius <akkadius1@gmail.com>
2024-10-13 17:26:10 -05:00
Fryguy 9ac306fe67 [Bug] FindBestZ selecting false zone floor as bestz - Results in roambox failures (#4504)
Added underworld checks per the EQMac project
2024-10-13 15:53:09 -05:00
Alex King 7a1d69d0d4 [Bug Fix] Fix Spells:DefaultAOEMaxTargets Default Value (#4508) 2024-10-12 14:32:40 -04:00
Alex King c873fe5a22 [Bug Fix] Fix Mercenary Encounter Crash (#4509) 2024-10-11 23:00:09 -04:00
Fryguy e06b0c4b0c [Bug Fix] Master of Disguise should apply to illusions casted by others. (#4506)
Many era comments outline how Master of Disguise would apply to Project Illusion spells on you:

https://thesafehouse.org/forums/forum/everquest-wing/main-lounge/14249-new-aa-master-of-disguise/page4

https://thesafehouse.org/forums/forum/everquest-wing/training-studios/18143-master-of-disguise-broken

```
Im not a big fan of wolf form, but having a 1200 min NDT is pretty nice  I also agree its great to shrink on a raid once and not have to worry about it. 7 aa is a little steep imho, but with a name change and some frog potions, I may reapply to my guild as the servers only froggy rogue /cackle.
```

```
share form of the great wolf gave a 1500min timer.
```
2024-10-11 13:39:36 -04:00
catapultam-habeo ed2130f649 [Bug Fix] Correctly limit max targets of PBAOE (#4507)
* fix pbaoe max targets incorrectly set

* fix scratch copy
2024-10-11 13:15:19 -04:00
Alex King 448a33a60c [Quest API] Add Scripting Support to Mercenaries (#4500)
* [Quest API] Add Scripting Support to Mercenaries

* Cleanup

* Cleanup

* Update lua_merc.h

* Update mob.cpp

* XYZH

* Final

* Update attack.cpp

* Update attack.cpp

* Simplify event invocation

* Inline example

* Nullptr init example

* EVENT_TIMER simplify add EventPlayerNpcBotMerc

* EVENT_TIMER_START

* Remove has_start_event

* EVENT_TIMER_START with settimerMS

* EVENT_POPUP_RESPONSE

* Consolidation

* Update attack.cpp

* Push

* Update quest_parser_collection.h

* Comments

* Cleanup per comments

---------

Co-authored-by: Akkadius <akkadius1@gmail.com>
2024-10-10 21:29:29 -04:00
Fryguy 8f86cb353e [Bug Fix] Spells - Self Only (Yellow) cast when non group member is targeted (#4503)
* [Bug Fix] Spells - Self Only (Yellow) cast when non group member is targeted

When using a Yellow gem invis spell, it should cast on yourself regardless of the targetted entity.

* Update spells.cpp

---------

Co-authored-by: Alex King <89047260+Kinglykrab@users.noreply.github.com>
2024-10-10 21:27:49 -04:00
Alex 178129443f [Loginserver] Login Fatal Error Spamming (#4476)
Co-authored-by: KimLS <KimLS@peqtgc.com>
2024-10-09 02:15:49 -05:00
Alex King a7c3b41afc [Quest API] Add Buff Fade Methods to Perl/Lua (#4501)
* [Quest API] Add Buff Fade Methods to Perl/Lua

* BuffFadeSongs()
2024-10-09 02:12:33 -05:00
Alex King a5a568d548 [Bug Fix] Fix character_exp_modifiers Default Values (#4502) 2024-10-09 02:11:57 -05:00
Alex King e3198edb86 [Quest API] Add EVENT_READ_ITEM to Perl/Lua (#4497)
* [Quest API] Add EVENT_READ_ITEM to Perl/Lua

* Add item_id export

* Add item export.

* Update client.cpp
2024-10-08 18:25:14 -04:00
Alex King 8568cf7d49 [Bug Fix] Fix NPC::CanTalk() Crash (#4499)
* [Bug FIx] Fix NPC::CanTalk() Crash

* Update npc.cpp

* Update mob.cpp

* Update npc.cpp
2024-10-07 00:17:49 -05:00
Alex King 1fb7a860a1 [Bug Fix] Fix #set motd Crash (#4495) 2024-10-05 07:58:22 -05:00
nytmyr 7eaee2649e [Bots] Add "silent" option to ^spawn and mute raid spawn (#4494)
When zoning or forming a raid, bots would spam their spawn message. They will now be muted.

Adds an optional argument "silent" to the ^spawn command. This will bypass ^oo spawnmessage settings and not send a spawn message. Example: ^spawn Warbot silent
2024-10-04 20:20:52 -04:00
Alex King a17f467b98 [Quest API] Add NPC List Filter Methods to Perl/Lua (#4493)
* [Quest API] Add GetNPCsByNPCIDs to Perl/Lua

* Push

* Update entity.cpp

* Separate methods.
2024-10-03 20:28:57 -04:00
Alex King 3359839a9b [Bug Fix] Fix Targeted AOE Max Targets Rule (#4488) 2024-10-02 20:25:35 -05:00
Alex 7e51e629f9 [Loginserver] Larion loginserver support (#4492)
* Add larion version and opcode path

* WIP: getting server to work

* Identify server_id

* Add missing opcode, add opcodes file

---------

Co-authored-by: KimLS <KimLS@peqtgc.com>
2024-10-02 20:20:13 -05:00
Alex King dc6c28a52d [Cleanup] Remove Extra Skill in EQ::skills::GetExtraDamageSkills() (#4486) 2024-10-02 20:07:19 -05:00
Alex King 78aee0780a [Bug Fix] Fix Group ID 0 in Group::SaveGroupLeaderAA() (#4487) 2024-10-02 20:06:56 -05:00
Chris Miles bcd943a964 [Code Cleanup] Optimization Code Cleanup (#4489)
* Initial push

* More

* More

* Further simplify

* More cleanup

* More consolidation

* Fix

* Update

* Update npc.cpp

---------

Co-authored-by: Kinglykrab <kinglykrab@gmail.com>
2024-09-30 18:34:42 -04:00
nytmyr 56608e84bd [Bots] Add attack flag when told to attack (#4490)
This adds a flag to mobs that are told to attack by their owner to prevent unintended attacks.

Previously, if you were to send your bots to attack a target and then switch targets: before casters land their spell or if melee (especially anyone with pets)  hasn't engaged before the target switch, they could switch to your new target and attack.

This adds a flag upon attack and bots will only attack flagged targets.
2024-09-29 17:59:26 -04:00
regneq 8d23e710ce [Bug Fix] fixed a bug where it would use npc value instead of faction value in the database. (#4491) 2024-09-29 17:42:43 -04:00
Morzain 4d11077b21 [Bug Fix] Add character_instance_safereturns to tables_to_zero_id (#4485)
Co-authored-by: morzain <morzain@users.noreply.github.com>
2024-09-26 18:14:32 -04:00
Alex King 5c0bdfdc4c [Bug Fix] Fix issue with Client::SaveDisciplines() not specifying character ID (#4481) 2024-09-23 23:00:52 -05:00
Alex King 6130e10831 [Release] 22.56.2 (#4480) 2024-09-19 21:59:53 -05:00
Alex King c3e1c531d2 [Bug Fix] Fix Issue with Database::ReserveName (#4477) 2024-09-19 21:15:14 -05:00
Alex King b52719a535 [Quest API] Add GrantAllAAPoints() Overload To Perl/Lua (#4474) 2024-09-19 21:09:24 -05:00
Alex King 1af252466f [Bug Fix] Fix Untrained Disciplines in Client::SaveDisciplines() (#4472)
* [Bug Fix] Fix Untrained Disciplines in Client::SaveDisciplines()

* [Bug Fix] Fix Infinite Loop in Adventure::Finished() (#4473)

Fix infinite loop condition when bot encountered

* [Bug Fix] Fix Untrained Disciplines in Client::SaveDisciplines()

* Change to release

---------

Co-authored-by: oddx2k <103136558+oddx2k@users.noreply.github.com>
2024-09-19 21:09:09 -05:00
catapultam-habeo 699d22fc28 [Bug Fix] Fix 'Teleport Doors' from being blocked by GM flag (#4475)
* gm flag blocks teleport doors with keys from working instead of allowing them to work

* correct coniditional logic
2024-09-18 17:18:07 -04:00
Mitch Freeman 5d1fe68906 [Bug Fix] Parcel purchase of bazaar items with unlimited charges (#4479)
Fix for unlimited charges in bazaar
2024-09-18 09:36:00 -04:00
oddx2k 52dcf35425 [Bug Fix] Fix Infinite Loop in Adventure::Finished() (#4473)
Fix infinite loop condition when bot encountered
2024-09-13 13:20:55 -04:00
Alex King a7550fbd9e [Release] 22.56.0 (#4471)
* [Release] 22.55.2

### 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.56.0
2024-09-12 20:39:48 -05:00
fuzzlecutter cc0171dfe1 [Feature] Allow NPCs to cast Sacrifice (#4470)
* [Feature] Teach npcs how to cast sacrifice

* [Feature] Teach npcs how to cast sacrifice

- Remove the hardcoded limit preventing npcs from casting sacrifice. The
  npc will receive as loot an emerald essence as expected.

* Update client.cpp
* Update client_packet.cpp
* Update spell_effects.cpp

* rename Client::SacrificeCaster to Client::sacrifice_caster_id
2024-09-12 15:42:44 -04:00
carolus21rex 913c5da70f [Bug Fix] ModernAAScalingEnabled() Calculation Error (#4469)
Current version only looks at your unspent AAs, meaning if you have 2000 spent AAs and 1 unspent AA, your scaling will be based on the 1 unspent AA instead of the 2001 total AA.

Here's the original log which is custom code found in the ModernAAScalingEnabled function:

[Wed Sep 11 14:10:19 2024] [AA] [ScaleAAXPBasedOnCurrentAATotal] AA Experience Calculation: add_aaxp = 660796, Base Bonus = 256.000000, Half-Life = 64.000000, Minimum Bonus = 1.000000, Earned AA = 1, Calculated Bonus = 253.242371

Custom code looks like this:

uint64 totalWithExpMod = add_aaxp;
	if (RuleB(AA, EnableLogrithmicClasslessAABonus)) {
		float base_bonus = RuleR(AA, InitialLogrithmicClasslessAABonus);
		float half_life = RuleR(AA, HalfLifeLogrithmicClasslessAABonus);
		float min_bon = RuleR(AA, MinimumLogrithmicClasslessAABonus);
		float bonus_expon = earnedAA / half_life;

		float bonus = base_bonus * std::pow(0.5, bonus_expon);
		Log(Logs::General,
			Logs::AA,
			"AA Experience Calculation: add_aaxp = %d, Base Bonus = %f, Half-Life = %f, Minimum Bonus = %f, Earned AA = %d, Calculated Bonus = %f",
			add_aaxp, base_bonus, half_life, min_bon, earnedAA, bonus);

		if (bonus < min_bon) bonus = min_bon;

		totalWithExpMod = (uint64)(totalWithExpMod * bonus);
	}

After the fix, the log becomes:

[Wed Sep 11 14:10:19 2024] [AA] [ScaleAAXPBasedOnCurrentAATotal] AA Experience Calculation: add_aaxp = 660796, Base Bonus = 256.000000, Half-Life = 64.000000, Minimum Bonus = 1.000000, Earned AA = 1, Calculated Bonus = 253.242371

Which is much closer to the expected behavior
2024-09-11 17:06:48 -04:00
Alex King 40fecbfaf5 [Performance] Move Discipline Loading to Client::CompleteConnect() (#4466)
* [Performance] Move Character Discipline Loading

* Push

* Final
2024-09-09 18:20:12 -05:00
Alex King b1646381b0 [Bug Fix] Fix Bard Effect in Migration 9237 (#4468) 2024-09-09 18:02:58 -05:00
Alex King bb1578796b [Rule] Add a Bandolier Swap Delay Rule (#4465)
* [Rule] Add a Bandolier Swap Delay Rule

* Push

* Update exp.cpp
2024-09-07 22:05:44 -05:00
Alex King 0e5a38f072 [Bug Fix] Add RULE_STRING to RuleManager::ResetRules (#4467)
* [Bug Fix] Add RULE_STRING to RuleManager::ResetRules

* Update rulesys.cpp
2024-09-07 18:29:46 -05:00
Alex King 39876ab858 [Feature] Add Rule to Limit Task Update Messages (#4459)
* [Feature] Add Rule to Limit Task Update Messages

* Update task_client_state.cpp

* Update task_client_state.cpp

* Change rule
2024-08-27 21:49:07 -04:00
catapultam-habeo ff16a76481 [Feature] Lazy Load Bank Contents (#4453)
* initial work porting this to upstream

* more

* track complete connect

* it sucks to suck

* Few optimizations

* Move sent_inventory init

* Move var

* Adjustments

---------

Co-authored-by: Akkadius <akkadius1@gmail.com>
2024-08-27 13:21:55 -05:00
Akkadius ffd68eb63d [Release] 22.55.1 2024-08-27 08:17:20 -05:00
Akkadius 76c1da1aad [Release] 22.55.1 2024-08-27 08:09:13 -05:00
Chris Miles a91e03fa43 [Code] Add IsCloseToBanker method (#4462)
* [Code] Add IsCloseToBanker method

* Update mob.cpp
2024-08-26 22:59:06 -05:00
Chris Miles 453106439f [Release] 22.55.0 (#4464) 2024-08-26 22:03:37 -05:00
Mitch Freeman 3da24fffa4 [Bug Fix] Fix client hotbar exchanging items when zoning (#4460)
* Add an exception process to assigning item serial numbers to correct a bug in the client hot bar clicky system.

* fixed missing guid in replace statement

* added snapshot support

* upate #show inventory command to protect against crash conditions
2024-08-26 21:58:07 -05:00
Kurt Gilpin 8d8ef6d480 [Bug Fix] Correct missed maxlevel reference in exp.cpp (#4463)
This was causing characters to de-level when gaining experience and was missed as part of https://github.com/EQEmu/Server/pull/4455
2024-08-26 22:34:22 -04:00
Chris Miles 1f9c4b3a22 [World] Fix slow world bootup bug (#4461)
* [World] Fix slow world bootup bug

* Update ip_util.cpp

* Add timeout

* Update ip_util.cpp

* Cross platform timeout
2024-08-26 20:59:43 -05:00
Alex King 7dfda95d86 [Bug Fix] Fix Bot Spell Entries IDs Capping at 32,767 (#4444)
* [Bug Fix] Fix Bot Spell Entries IDs Capping at 32,767

* Fix manifest
2024-08-26 20:29:50 -05:00
Alex King 40738b29e3 [Quest API] Add Area-Based Quest Methods to Perl/Lua (#4447)
* [Quest API] Add Area-Based Quest Methods to Perl/Lua

* Convert some to mob

* Fix
2024-08-26 20:29:07 -05:00
Alex King 080865faa2 [Feature] Add Optional Return to EVENT_DAMAGE_TAKEN (#4454)
* [Feature] Add Optional Return to EVENT_DAMAGE_TAKEN

# Description
- Allows operators to return a value from `EVENT_DAMAGE_TAKEN` to override the amount of damage taken based on any arbitrary criteria they'd like to apply.

* Update attack.cpp
2024-08-26 20:27:29 -05:00
Alex King e2b545991a [Quest API] Add AreTasksCompleted() to Perl/Lua. (#4456)
* [Quest API] Add AreTasksCompleted() to Perl/Lua.

* Bool
2024-08-22 20:21:14 -04:00
Alex King b7f8d0f179 [Feature] Extend Spell Buckets Functionality (#4441) 2024-08-22 18:49:52 -04:00
Alex King e3588781aa [Cleanup] Remove unused methods (#4449) 2024-08-22 11:48:02 -04:00
Alex King e9b84f4d11 [Bug Fix] Fix issue with killed mob coordinates (#4457) 2024-08-22 11:45:31 -04:00
Alex King 4f03970fd1 [Bug Fix] Fix Character ID of 0 being inserted into character_stats_record (#4458) 2024-08-22 11:45:19 -04:00
catapultam-habeo 4979da6932 [Bug Fix] Apply Race & Class restrictions to Auto-Combines (#4452) 2024-08-19 21:57:34 -04:00
Fryguy 9987029791 [Bug Fix] client_max_level allow leveling to end of level (#4455)
When using a method that leverages client_max_level (e.g. Max Level by bucket / qglobal / quest API) it would stop xp at 0% into the level rather than maximum xp for the level.

This could pose an issue where: If you had a max level of 65 via a databucket and a raid zone required level 65, one death would de-level them and potentially prevent them from entering the zone.

I reorganized the code to leverage the existing max_level logic which allows max xp in the max level.

I also cleaned up the overall functions formating (Mostly brackets and implied if statements).
2024-08-19 21:56:14 -04:00
Alex King eece0a92e3 [Quest API] Add Several Door Methods to Perl/Lua (#4451) 2024-08-16 15:52:49 -04:00
Alex King 057f96796a [Bug Fix] Fix Issue with Removed #setfaction Command (#4448) 2024-08-10 21:21:46 -04:00
Alex King f475cecdb1 [Bug Fix] Fix AddCrystals() in Perl/Lua (#4445) 2024-08-09 22:48:32 -04:00
Fryguy 6296ed6d41 [Bug Fix] Attune Augments when Equipped (#4446) 2024-08-09 22:27:04 -04:00
Alex King ac0f729aa2 [Feature] Add Character:DefaultGuildRank Rule (#4438)
* [Feature] Add Character:DefaultGuildRank Rule

* Update ruletypes.h

* Update ruletypes.h

* Update database.cpp
2024-08-03 22:48:30 -04:00
JJ 2937852cf9 [Bug Fix] Ensure close of Tribute Item search (#4439) 2024-08-03 20:25:07 -04:00
Alex King 2cf5bae571 [Bug Fix] Fix Lua Client FilteredMessage (#4437) 2024-07-31 19:39:57 -04:00
Fryguy 2feb05be18 [Improvement] Filtered Messages Extension (#4435)
* [Improvment] Filtered Messages Extension

Added:
ItemSpeech 25
Strikethrough 26
Stuns 27
BardSongsOnPets 28

I wired up Strikethrough and Stuns as they already had message entries.

ItemSpeech and BardSongsOnPets do not appear to be currently used in the source.

Note: There are still 5 unknown Filters in RoF2 that need to be investigated:

Achievments
Fellowships
Mercenary Messages
PVP Messages
Spam

* Spelling Error

* Missed some stun calls
2024-07-31 18:28:45 -04:00
Fryguy 421767e1e5 [Bug Fix] Imitate Death should also clear zone feign aggro (#4436) 2024-07-31 18:28:36 -04:00
JJ 6e9ff52dce [Release] 22.54.0 (#4434)
* Update CHANGELOG.md

* Update version.h

* Update package.json

* Update CHANGELOG.md
2024-07-30 20:30:38 -04:00
Alex King aa700f8960 [Cleanup] Cleanup Client File Exporting (#4348)
* [Cleanup] Cleanup Client File Exporting

* Update base_data_repository.h

* Update db_str_repository.h

* Update base_data_repository.h

* Update skill_caps_repository.h

* Update skill_caps_repository.h

* Update skill_caps_repository.h

* Update main.cpp

* Push
2024-07-30 20:10:00 -04:00
Fryguy 2ef959c5ed [Improvement] Flee Overhaul (#4407)
* Lots of flee updates primarily based on TAKPs source

* Update Values to EQEmu values.

* Add rule

* Adjustments to fear pathing

* Flee/Pathing adjustments (More TAKP code adjusted)

* updates

* Updates (Massaged functions from TAKP source)

---------

Co-authored-by: Kinglykrab <kinglykrab@gmail.com>
2024-07-30 18:27:47 -04:00
Mitch Freeman e49ab924cc [Feature] Add Barter/Buyer Features (#4405)
* Add Barter/Buyer Features

Adds barter and buyer features, for ROF2 only at this time including item compensation

* Remove FKs from buyer tables

Remove FKs from buyer tables

* Bug fix for Find Buyer and mutli item selling

Update for quantity purchases not correctly providing multi items.
Update for Find Buyer functionality based on zone instancing.
Update buyer messaging
Update buyer LORE duplicate check

* Revert zone instance comment

* Revert zone_id packet size field

* Add zone instancing to barter/buyer

---------

Co-authored-by: Akkadius <akkadius1@gmail.com>
2024-07-30 16:23:37 -04:00
catapultam-habeo fc3c691588 [Feature] Implement Move Multiple Items (#4259)
* Implement Move Multiple Items

* Send LinkDead on invalid packet

* structure this more like MoveItem

* implement all modes

* remove un-needed debug message

* handle mode 3 swaps in bank\shared bank correctly.

* Revert "handle mode 3 swaps in bank\shared bank correctly."

This reverts commit ce01fbfde70d52e88381772a6c7a77b4b650c7c5.

* Revert "remove un-needed debug message"

This reverts commit f4b662459e11a60c3a46a97e5320757c4b2b9a84.

* handle mode 3 swaps without extra unintended code

* correct variable type

* remove magic numbers

* forgot a semicolon in emu_constants.h

* fix bad rebase artifact

* Remove unused struct

* apply changes discussed in PR

* last rebase conflict

* last rebase conflict

fix more inventory type enum refs

* fix windows build error

* fix other windows build error.

* fix duplication bug
2024-07-30 13:40:48 -04:00
catapultam-habeo d465a3deba [Bug Fix] Stop DOSing ourselves with OP_WearChange (#4432)
* initial commit to start convo

* additional potential problem

* Revert "additional potential problem"

This reverts commit 689e94ea95.
2024-07-30 13:00:26 -04:00
Alex King 40c9c8044b [Bug Fix] Fix issue with quest::echo and quest::me (#4433) 2024-07-30 09:25:05 -04:00
Chris Miles 70a96ea098 [Zoning] Improve zone routing (#4428)
* [Zoning] Improvements to zone routing

* Update world_content_service.h

* Update world_content_service.h
2024-07-30 09:12:31 -04:00
Chris Miles d5cbec714e Revert "[Zone Instances] Handle routing to instances when using evac/succor (#4297)" (#4429)
This reverts commit dfd1bfbd49.
2024-07-30 09:12:19 -04:00
Mitch Freeman 6903205484 [Bug Fix] Fix #parcels add subcommand (#4431)
The parcel object was not be initialized correctly resulting in the possibility of incorrect data being written for the uninitialized members.
2024-07-28 21:56:21 -04:00
Chris Miles 4c81321847 [Databuckets] Remove memory reserve from bulk load (#4427) 2024-07-23 00:37:34 -05:00
Alex King e5cea73e0c [Bug Fix] Fix Client::RemoveTitle (#4421)
* [Bug Fix] Fix Client::RemoveTitle

* Remove title/suffix if in use.

* Update titles.cpp

* Non static
2024-07-23 00:33:09 -05:00
Alex King 23308192b5 [Bug Fix] Fix #setlevel Allowing Skills Above Max (#4423) 2024-07-22 20:46:38 -05:00
Alex King 29fdf7e2ae [Bug Fix] Fix EVENT_USE_SKILL with Sense Heading (#4424) 2024-07-22 20:45:32 -05:00
Alex King 098498dedd [Commands] Extend #devtools Functionality (#4425) 2024-07-22 20:44:34 -05:00
Alex King b6fb8daae8 [Bug Fix] Fix Bot::SetBotStance (#4426) 2024-07-22 20:43:18 -05:00
nytmyr 563f7d5564 [Cleanup] Mask GM Show Buff message behind EntityVariable (#4419)
* [Cleanup] Mask GM Show Buff message behind EntityVariable

Removes the spam of "Your GM flag allows you to always see your targets' buffs." for GMs every time a buff lands on a target.

It will now lock to an Entity Variable and only show once per zone.

* Convert string to constexpr

* Switch from string to char
2024-07-22 12:51:41 -04:00
Fryguy 1e5abc456b [Bug Fix] Proximity Aggro for Frustrated and Undead (#4411)
* [Bug Fix] Prox aggro for frustrated and undead.

If mob is frustrated (Rooted and has no one to kill or is undead will add prox aggro to hate list.

* Update aggro.cpp

---------

Co-authored-by: Kinglykrab <kinglykrab@gmail.com>
2024-07-22 07:01:12 -04:00
Fryguy 3b0fa015a7 [Bug Fix] Corpse Call removing Resurrection Effects (#4410)
* [Bug Fix] Corpse Call removing Rez Effects

When calling a corpse, it should not remove rez effects.

* Update client_process.cpp

---------

Co-authored-by: Kinglykrab <kinglykrab@gmail.com>
2024-07-22 06:26:40 -04:00
nytmyr c73a1e8bea [Rules] Add HasteCap and Hastev3Cap rules for NPCs, Bots and Mercs (#4406)
* [Rules] Add HasteCap and Hastev3Cap rules for NPCs, Bots and Mercs

Previously NPCs, bots and mercs all had a flat haste cap of 150 whereas clients were capped at 100.

NPCs, bots and mercs used the character rule for v3 cap, they now each have their own.

Rules for v3 cap are the default of 25 as they were using.
Rules for haste caps are the default of 150 for NPCs they were using but lowered to 100 for bots and mercs, the same as clients.

This also adds haste output to the GM target stat window

* Fix for stat windows to account for client haste
2024-07-22 06:06:49 -04:00
Fryguy 3bfdc0cf71 [Bug Fix] Potential fix for some undesired ranged explotative behavior. (#4413)
Original Commit: 33fecd68d4eab36885eb7f8067102ba6bce95bac

Conditional ranged double attack
2024-07-22 06:05:46 -04:00
Mitch Freeman a23ac4628f [Feature] Add Parcel notification for online players when using the Quest API (#4418)
* Add parcel notification for online players when using the quest api for send_parcel

* Compile fix

fix for compile issues
2024-07-22 05:57:42 -04:00
nytmyr 5ef4612249 [Bug Fix] [Quest API] Fix getraididbycharid and getgroupidbycharid (#4417) 2024-07-16 15:53:22 -04:00
Mitch Freeman 17f66c5d60 [Bug Fix] Personal tributes for bard items were not applying correctly (#4416)
* Fixes Personal Tributes for bard items not being applied.

* Fix for bots
2024-07-16 11:18:42 -04:00
Mitch Freeman 51eb95ed31 Revert "Fixes Personal Tributes for bard items not being applied. (#4414)" (#4415)
This reverts commit 080abaede1.
2024-07-16 10:49:08 -04:00
Mitch Freeman 080abaede1 Fixes Personal Tributes for bard items not being applied. (#4414) 2024-07-15 23:02:35 -04:00
Mitch Freeman 97e332819d When searching in the bazaar, the minimum cost was not be honoured. (#4412) 2024-07-15 08:41:04 -04:00
Mitch Freeman 1e41c5517e [Bug Fix] Fix for random disconnects when a large number of guild members zone or disconnect (#4402) 2024-07-10 00:10:33 -05:00
Fryguy c7a88af11a [Bug Fix] AutoSplit unknown bug and cleanup. (#4401)
Code Credit TAKP:

Bug Post: https://discord.com/channels/212663220849213441/1258430167764832319

Resolved issue with split message being sent to group members when no split is present which creates an "Unknown Split".

Also added the random remainder split portion unless using a manual /split

Converted manual messages to Strings
2024-07-07 00:53:57 -04:00
KayenEQ d8ddd0aab9 [Bug Fix] Aegolism Spell line stacking (#4399)
* fix stacking issues with Aegolism spell line

Issue: When casting buffing a player with aegolism spell line, who already has cleric AC, symbol and heroism spell, it would overwrite heorism buff and leave other two.

Aegolism spell line when applied when a client has Heroism spell line, AC spell line, and symbol spell line. Should overwrite the Heroism spell and fade the AC and Symbol buffs.

* Update spdat.cpp
2024-07-07 00:53:46 -04:00
Fryguy 95cbadade5 [Bug Fix] Slay Adjustments (#4389)
Previous change did not account for the modern slay undead and holyforge spells.

Reverted some of the changes and cleaned up others.

Rule Renamed (Default value was incorrect, this was a clean way to fix that) - SlayDamageAdjustment -> SlayDamageMultiplier

Also added a rate multiplier

RULE_REAL(Combat, SlayRateMultiplier, 1.0, "Slay Rate Adjustments - Multiply final slay rate check by this value. Default: 1.0")

Fixed the ordering of the constants for the slay undead SPA that were backwards and causing major headaches with tuning and setting up slay undead correctly.

Base = Damage Mod (100 is base, so 240 = 140% more)
Limit = Proc Rate - Value is divided by 10000 for a Float %. e.g. 1700 becomes 0.17 (Or 17% proc rate).

Damage bonus should be additive not std::max as AA, Spells and Item bonuses should stack.

e.g. Slay Undead RK3 240 + Holy Forge 140 should = 380 (280% damage)
2024-07-07 00:53:29 -04:00
Alex King a85f4fb703 [Cleanup] Cleanup Stance Code (#4368)
* [Cleanup] Cleanup Stance-based Code

* Command

* Update emu_constants.h

* Update stance.cpp

* Cleanup
2024-07-02 21:50:34 -04:00
Fryguy e63f34638b [Bug Fix] AllowRaidTargetBlind logic backwards (#4400) 2024-07-01 08:15:36 -04:00
JJ 7918fed81c [Release] 22.53.1 (#4398) 2024-06-16 21:17:16 -04:00
JJ ac24c9bf5a [Bug Fix] Fix trader mode (#4397)
* Fix bazaar trading

* Update `constexpr`

* Added world trader table truncate on boot to ensure that the trader table is always empty when world starts.

---------

Co-authored-by: Mitch Freeman <65987027+neckkola@users.noreply.github.com>
2024-06-16 20:55:14 -04:00
207 changed files with 17211 additions and 4050 deletions
+155
View File
@@ -1,3 +1,158 @@
## [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
+38 -139
View File
@@ -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" : "");
}
+16 -2
View File
@@ -111,6 +111,8 @@ SET(common_sources
net/websocket_server.cpp
net/websocket_server_connection.cpp
patches/patches.cpp
patches/larion.cpp
patches/larion_limits.cpp
patches/sod.cpp
patches/sod_limits.cpp
patches/sof.cpp
@@ -158,6 +160,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
@@ -339,7 +342,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
@@ -651,7 +655,11 @@ SET(common_headers
net/websocket_server.h
net/websocket_server_connection.h
patches/patches.h
patches/sod.h
patches/larion.h
patches/larion_limits.h
patches/larion_ops.h
patches/larion_structs.h
patches/sod.h
patches/sod_limits.h
patches/sod_ops.h
patches/sod_structs.h
@@ -737,6 +745,10 @@ SOURCE_GROUP(Net FILES
SOURCE_GROUP(Patches FILES
patches/patches.h
patches/larion.h
patches/larion_limits.h
patches/larion_ops.h
patches/larion_structs.h
patches/sod.h
patches/sod_limits.h
patches/sod_ops.h
@@ -765,6 +777,8 @@ SOURCE_GROUP(Patches FILES
patches/uf_ops.h
patches/uf_structs.h
patches/patches.cpp
patches/larion.cpp
patches/larion_limits.cpp
patches/sod.cpp
patches/sod_limits.cpp
patches/sof.cpp
+1
View File
@@ -59,6 +59,7 @@ public:
void WriteUInt8(uint8 value) { *(uint8 *)(pBuffer + _wpos) = value; _wpos += sizeof(uint8); }
void WriteUInt32(uint32 value) { *(uint32 *)(pBuffer + _wpos) = value; _wpos += sizeof(uint32); }
void WriteUInt64(uint64 value) { *(uint64 *)(pBuffer + _wpos) = value; _wpos += sizeof(uint64); }
void WriteSInt16(int32 value) { *(int16*)(pBuffer + _wpos) = value; _wpos += sizeof(int16); }
void WriteUInt16(uint32 value) { *(uint16 *)(pBuffer + _wpos) = value; _wpos += sizeof(uint16); }
void WriteSInt32(int32 value) { *(int32 *)(pBuffer + _wpos) = value; _wpos += sizeof(int32); }
void WriteFloat(float value) { *(float *)(pBuffer + _wpos) = value; _wpos += sizeof(float); }
+1 -1
View File
@@ -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));
+61 -81
View File
@@ -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);
}
+2 -3
View File
@@ -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;
+18
View File
@@ -101,6 +101,24 @@ void CRC32::SetEQChecksum(uchar* in_data, uint32 in_length, uint32 start_at)
memcpy(in_data, (char*)&check, 4);
}
unsigned long CRC32::GetEQChecksum(uchar* in_data, uint32 in_length, uint32 start_at)
{
unsigned long data;
unsigned long check = 0xffffffff;
for (uint32 i = start_at; i < in_length; i++)
{
data = in_data[i];
data = data ^ (check);
data = data & 0x000000ff;
check = check >> 8;
data = CRC32Table[data];
check = check ^ data;
}
return check;
}
uint32 CRC32::Update(const uint8* buf, uint32 bufsize, uint32 crc32var) {
for(uint32 i=0; i < bufsize; i++)
Calc(buf[i], crc32var);
+1
View File
@@ -8,6 +8,7 @@ public:
static uint32 Generate(const uint8* buf, uint32 bufsize);
static uint32 GenerateNoFlip(const uint8* buf, uint32 bufsize); // Same as Generate(), but without the ~
static void SetEQChecksum(uchar* in_data, uint32 in_length, uint32 start_at=4);
static unsigned long GetEQChecksum(uchar* in_data, uint32 in_length, uint32 start_at = 4);
// Multiple buffer CRC32
static uint32 Update(const uint8* buf, uint32 bufsize, uint32 crc32 = 0xFFFFFFFF);
+57 -14
View File
@@ -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);
}
+2
View File
@@ -244,6 +244,8 @@ public:
void PurgeAllDeletedDataBuckets();
void ClearGuildOnlineStatus();
void ClearTraderDetails();
void ClearBuyerDetails();
/* Database Variables */
+99 -1
View File
@@ -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
+5 -1
View File
@@ -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",
+10 -30
View File
@@ -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()
+42 -21
View File
@@ -274,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,
@@ -362,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);
@@ -401,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 {
@@ -471,7 +451,7 @@ namespace EQ
Raid,
Guild
};
}; // namespace consent
};
} /*EQEmu*/
enum ServerLockType : int {
@@ -741,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*/
+2
View File
@@ -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),
+103
View File
@@ -56,6 +56,8 @@ const char* EQ::versions::ClientVersionName(ClientVersion client_version)
return "RoF";
case ClientVersion::RoF2:
return "RoF2";
case ClientVersion::Larion:
return "Larion";
default:
return "Invalid Version";
};
@@ -76,6 +78,8 @@ uint32 EQ::versions::ConvertClientVersionToClientVersionBit(ClientVersion client
return bitRoF;
case ClientVersion::RoF2:
return bitRoF2;
case ClientVersion::Larion:
return bitLarion;
default:
return bitUnknown;
}
@@ -96,6 +100,8 @@ EQ::versions::ClientVersion EQ::versions::ConvertClientVersionBitToClientVersion
return ClientVersion::RoF;
case ((uint32)1 << (static_cast<unsigned int>(ClientVersion::RoF2) - 1)) :
return ClientVersion::RoF2;
case ((uint32)1 << (static_cast<unsigned int>(ClientVersion::Larion) - 1)):
return ClientVersion::Larion;
default:
return ClientVersion::Unknown;
}
@@ -184,6 +190,8 @@ const char* EQ::versions::MobVersionName(MobVersion mob_version)
return "RoF";
case MobVersion::RoF2:
return "RoF2";
case MobVersion::Larion:
return "Larion";
case MobVersion::NPC:
return "NPC";
case MobVersion::NPCMerchant:
@@ -212,6 +220,8 @@ const char* EQ::versions::MobVersionName(MobVersion mob_version)
return "Offline RoF";
case MobVersion::OfflineRoF2:
return "Offline RoF2";
case MobVersion::OfflineLarion:
return "Offline Larion";
default:
return "Invalid Version";
};
@@ -235,6 +245,8 @@ EQ::versions::ClientVersion EQ::versions::ConvertMobVersionToClientVersion(MobVe
return ClientVersion::RoF;
case MobVersion::RoF2:
return ClientVersion::RoF2;
case MobVersion::Larion:
return ClientVersion::Larion;
default:
return ClientVersion::Unknown;
}
@@ -258,6 +270,8 @@ EQ::versions::MobVersion EQ::versions::ConvertClientVersionToMobVersion(ClientVe
return MobVersion::RoF;
case ClientVersion::RoF2:
return MobVersion::RoF2;
case ClientVersion::Larion:
return MobVersion::Larion;
default:
return MobVersion::Unknown;
}
@@ -278,6 +292,8 @@ EQ::versions::MobVersion EQ::versions::ConvertPCMobVersionToOfflinePCMobVersion(
return MobVersion::OfflineRoF;
case MobVersion::RoF2:
return MobVersion::OfflineRoF2;
case MobVersion::Larion:
return MobVersion::OfflineLarion;
default:
return MobVersion::Unknown;
}
@@ -298,6 +314,8 @@ EQ::versions::MobVersion EQ::versions::ConvertOfflinePCMobVersionToPCMobVersion(
return MobVersion::RoF;
case MobVersion::OfflineRoF2:
return MobVersion::RoF2;
case MobVersion::OfflineLarion:
return MobVersion::Larion;
default:
return MobVersion::Unknown;
}
@@ -318,6 +336,8 @@ EQ::versions::ClientVersion EQ::versions::ConvertOfflinePCMobVersionToClientVers
return ClientVersion::RoF;
case MobVersion::OfflineRoF2:
return ClientVersion::RoF2;
case MobVersion::OfflineLarion:
return ClientVersion::Larion;
default:
return ClientVersion::Unknown;
}
@@ -338,6 +358,8 @@ EQ::versions::MobVersion EQ::versions::ConvertClientVersionToOfflinePCMobVersion
return MobVersion::OfflineRoF;
case ClientVersion::RoF2:
return MobVersion::OfflineRoF2;
case ClientVersion::Larion:
return MobVersion::OfflineLarion;
default:
return MobVersion::Unknown;
}
@@ -388,6 +410,27 @@ const char* EQ::expansions::ExpansionName(Expansion expansion)
return "Rain of Fear";
case Expansion::CotF:
return "Call of the Forsaken";
case Expansion::TDS:
return "The Darkened Sea";
case Expansion::TBM:
return "The Broken Mirror";
case Expansion::EoK:
return "Empires of Kunark";
case Expansion::RoS:
return "Ring of Scale";
case Expansion::TBL:
return "The Burning Lands";
case Expansion::ToV:
return "Torment of Velious";
case Expansion::CoV:
return "Claws of Veeshan";
case Expansion::ToL:
return "Terror of Luclin";
case Expansion::NoS:
return "Night of Shadows";
case Expansion::LS:
return "Laurion's Song";
default:
return "Invalid Expansion";
}
@@ -441,6 +484,26 @@ uint32 EQ::expansions::ConvertExpansionToExpansionBit(Expansion expansion)
return bitRoF;
case Expansion::CotF:
return bitCotF;
case Expansion::TDS:
return bitTDS;
case Expansion::TBM:
return bitTBM;
case Expansion::EoK:
return bitEoK;
case Expansion::RoS:
return bitRoS;
case Expansion::TBL:
return bitTBL;
case Expansion::ToV:
return bitToV;
case Expansion::CoV:
return bitCoV;
case Expansion::ToL:
return bitToL;
case Expansion::NoS:
return bitNoS;
case Expansion::LS:
return bitLS;
default:
return bitEverQuest;
}
@@ -489,6 +552,26 @@ EQ::expansions::Expansion EQ::expansions::ConvertExpansionBitToExpansion(uint32
return Expansion::RoF;
case bitCotF:
return Expansion::CotF;
case bitTDS:
return Expansion::TDS;
case bitTBM:
return Expansion::TBM;
case bitEoK:
return Expansion::EoK;
case bitRoS:
return Expansion::RoS;
case bitTBL:
return Expansion::TBL;
case bitToV:
return Expansion::ToV;
case bitCoV:
return Expansion::CoV;
case bitToL:
return Expansion::ToL;
case bitNoS:
return Expansion::NoS;
case bitLS:
return Expansion::LS;
default:
return Expansion::EverQuest;
}
@@ -537,6 +620,26 @@ uint32 EQ::expansions::ConvertExpansionToExpansionsMask(Expansion expansion)
return maskRoF;
case Expansion::CotF:
return maskCotF;
case Expansion::TDS:
return maskTDS;
case Expansion::TBM:
return maskTBM;
case Expansion::EoK:
return maskEoK;
case Expansion::RoS:
return maskRoS;
case Expansion::TBL:
return maskTBL;
case Expansion::ToV:
return maskToV;
case Expansion::CoV:
return maskCoV;
case Expansion::ToL:
return maskToL;
case Expansion::NoS:
return maskNoS;
case Expansion::LS:
return maskLS;
default:
return maskEverQuest;
}
+45 -9
View File
@@ -36,7 +36,8 @@ namespace EQ
SoD, // Build: 'Dec 19 2008 15:22:49'
UF, // Build: 'Jun 8 2010 16:44:32'
RoF, // Build: 'Dec 10 2012 17:35:44'
RoF2 // Build: 'May 10 2013 23:30:08'
RoF2, // Build: 'May 10 2013 23:30:08'
Larion
};
enum ClientVersionBitmask : uint32 {
@@ -48,6 +49,7 @@ namespace EQ
bitUF = 0x00000010,
bitRoF = 0x00000020,
bitRoF2 = 0x00000040,
bitLarion = 0x00000080,
maskUnknown = 0x00000000,
maskTitaniumAndEarlier = 0x00000003,
maskSoFAndEarlier = 0x00000007,
@@ -59,10 +61,11 @@ namespace EQ
maskUFAndLater = 0xFFFFFFF0,
maskRoFAndLater = 0xFFFFFFE0,
maskRoF2AndLater = 0xFFFFFFC0,
maskLarionAndLater = 0xFFFFFF80,
maskAllClients = 0xFFFFFFFF
};
const ClientVersion LastClientVersion = ClientVersion::RoF2;
const ClientVersion LastClientVersion = ClientVersion::Larion;
const size_t ClientVersionCount = (static_cast<size_t>(LastClientVersion) + 1);
bool IsValidClientVersion(ClientVersion client_version);
@@ -80,6 +83,7 @@ namespace EQ
UF,
RoF,
RoF2,
Larion,
NPC,
NPCMerchant,
Merc,
@@ -93,13 +97,14 @@ namespace EQ
OfflineSoD,
OfflineUF,
OfflineRoF,
OfflineRoF2
OfflineRoF2,
OfflineLarion
};
const MobVersion LastMobVersion = MobVersion::OfflineRoF2;
const MobVersion LastPCMobVersion = MobVersion::RoF2;
const MobVersion LastMobVersion = MobVersion::OfflineLarion;
const MobVersion LastPCMobVersion = MobVersion::Larion;
const MobVersion LastNonPCMobVersion = MobVersion::BotPet;
const MobVersion LastOfflinePCMobVersion = MobVersion::OfflineRoF2;
const MobVersion LastOfflinePCMobVersion = MobVersion::OfflineLarion;
const size_t MobVersionCount = (static_cast<size_t>(LastMobVersion) + 1);
bool IsValidMobVersion(MobVersion mob_version);
@@ -131,7 +136,8 @@ namespace EQ
ucsSoDCombined = 'D',
ucsUFCombined = 'E',
ucsRoFCombined = 'F',
ucsRoF2Combined = 'G'
ucsRoF2Combined = 'G',
ucsLaurionCombined = 'G'
};
} /*versions*/
@@ -158,7 +164,17 @@ namespace EQ
HoT,
VoA,
RoF,
CotF
CotF,
TDS,
TBM,
EoK,
RoS,
TBL,
ToV,
CoV,
ToL,
NoS,
LS
};
enum ExpansionBitmask : uint32 {
@@ -183,6 +199,16 @@ namespace EQ
bitVoA = 0x00020000,
bitRoF = 0x00040000,
bitCotF = 0x00080000,
bitTDS = 0x00100000,
bitTBM = 0x00200000,
bitEoK = 0x00400000,
bitRoS = 0x00800000,
bitTBL = 0x01000000,
bitToV = 0x02000000,
bitCoV = 0x04000000,
bitToL = 0x08000000,
bitNoS = 0x10000000,
bitLS = 0x20000000,
maskEverQuest = 0x00000000,
maskRoK = 0x00000001,
maskSoV = 0x00000003,
@@ -203,7 +229,17 @@ namespace EQ
maskHoT = 0x0001FFFF,
maskVoA = 0x0003FFFF,
maskRoF = 0x0007FFFF,
maskCotF = 0x000FFFFF
maskCotF = 0x000FFFFF,
maskTDS = 0x001FFFFF,
maskTBM = 0x003FFFFF,
maskEoK = 0x007FFFFF,
maskRoS = 0x00FFFFFF,
maskTBL = 0x01FFFFFF,
maskToV = 0x03FFFFFF,
maskCoV = 0x07FFFFFF,
maskToL = 0x0FFFFFFF,
maskNoS = 0x1FFFFFFF,
maskLS = 0x3FFFFFFF,
};
const char* ExpansionName(Expansion expansion);
+7 -4
View File
@@ -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*/
+83
View File
@@ -104,6 +104,14 @@ static const EQ::constants::LookupEntry constants_static_lookup_entries[EQ::vers
RoF2::constants::EXPANSIONS_MASK,
RoF2::constants::CHARACTER_CREATION_LIMIT,
RoF2::constants::SAY_LINK_BODY_SIZE
),
/*[ClientVersion::Larion] =*/
EQ::constants::LookupEntry(
Larion::constants::EXPANSION,
Larion::constants::EXPANSION_BIT,
Larion::constants::EXPANSIONS_MASK,
Larion::constants::CHARACTER_CREATION_LIMIT,
Larion::constants::SAY_LINK_BODY_SIZE
)
};
@@ -370,6 +378,33 @@ static const EQ::inventory::LookupEntry inventory_static_lookup_entries[EQ::vers
RoF2::inventory::ConcatenateInvTypeLimbo,
RoF2::inventory::AllowOverLevelEquipment
),
/*[MobVersion::LS] =*/
EQ::inventory::LookupEntry(
EQ::inventory::LookupEntry::InventoryTypeSize_Struct(
EQ::invtype::POSSESSIONS_SIZE, RoF2::invtype::BANK_SIZE, RoF2::invtype::SHARED_BANK_SIZE,
RoF2::invtype::TRADE_SIZE, RoF2::invtype::WORLD_SIZE, RoF2::invtype::LIMBO_SIZE,
RoF2::invtype::TRIBUTE_SIZE, RoF2::invtype::TROPHY_TRIBUTE_SIZE, RoF2::invtype::GUILD_TRIBUTE_SIZE,
RoF2::invtype::MERCHANT_SIZE, RoF2::invtype::DELETED_SIZE, RoF2::invtype::CORPSE_SIZE,
RoF2::invtype::BAZAAR_SIZE, RoF2::invtype::INSPECT_SIZE, RoF2::invtype::REAL_ESTATE_SIZE,
RoF2::invtype::VIEW_MOD_PC_SIZE, RoF2::invtype::VIEW_MOD_BANK_SIZE, RoF2::invtype::VIEW_MOD_SHARED_BANK_SIZE,
RoF2::invtype::VIEW_MOD_LIMBO_SIZE, RoF2::invtype::ALT_STORAGE_SIZE, RoF2::invtype::ARCHIVED_SIZE,
RoF2::invtype::MAIL_SIZE, RoF2::invtype::GUILD_TROPHY_TRIBUTE_SIZE, RoF2::invtype::KRONO_SIZE,
RoF2::invtype::OTHER_SIZE
),
RoF2::invslot::EQUIPMENT_BITMASK,
RoF2::invslot::GENERAL_BITMASK,
RoF2::invslot::CURSOR_BITMASK,
RoF2::invslot::POSSESSIONS_BITMASK,
RoF2::invslot::CORPSE_BITMASK,
RoF2::invbag::SLOT_COUNT,
RoF2::invaug::SOCKET_COUNT,
RoF2::inventory::AllowEmptyBagInBag,
RoF2::inventory::AllowClickCastFromBag,
RoF2::inventory::ConcatenateInvTypeLimbo,
RoF2::inventory::AllowOverLevelEquipment
),
/*[MobVersion::NPC] =*/
EQ::inventory::LookupEntry(
EQ::inventory::LookupEntry::InventoryTypeSize_Struct(
@@ -743,6 +778,33 @@ static const EQ::inventory::LookupEntry inventory_static_lookup_entries[EQ::vers
RoF2::invbag::SLOT_COUNT,
RoF2::invaug::SOCKET_COUNT,
false,
false,
false,
false
),
/*[MobVersion::OfflineLS] =*/
EQ::inventory::LookupEntry(
EQ::inventory::LookupEntry::InventoryTypeSize_Struct(
RoF2::INULL, RoF2::INULL, RoF2::INULL,
RoF2::invtype::TRADE_SIZE, RoF2::INULL, RoF2::INULL,
RoF2::INULL, RoF2::INULL, RoF2::INULL,
RoF2::invtype::MERCHANT_SIZE, RoF2::INULL, RoF2::INULL,
RoF2::invtype::BAZAAR_SIZE, RoF2::invtype::INSPECT_SIZE, RoF2::INULL,
RoF2::invtype::VIEW_MOD_PC_SIZE, RoF2::invtype::VIEW_MOD_BANK_SIZE, RoF2::invtype::VIEW_MOD_SHARED_BANK_SIZE,
RoF2::invtype::VIEW_MOD_LIMBO_SIZE, RoF2::INULL, RoF2::INULL,
RoF2::INULL, RoF2::INULL, RoF2::INULL,
RoF2::INULL
),
RoF2::INULL,
RoF2::INULL,
RoF2::INULL,
RoF2::INULL,
RoF2::INULL,
RoF2::invbag::SLOT_COUNT,
RoF2::invaug::SOCKET_COUNT,
false,
false,
false,
@@ -994,6 +1056,10 @@ static const EQ::behavior::LookupEntry behavior_static_lookup_entries[EQ::versio
EQ::behavior::LookupEntry(
RoF2::behavior::CoinHasWeight
),
/*[MobVersion::LS] =*/
EQ::behavior::LookupEntry(
RoF2::behavior::CoinHasWeight
),
/*[MobVersion::NPC] =*/
EQ::behavior::LookupEntry(
EQ::behavior::CoinHasWeight
@@ -1047,6 +1113,10 @@ static const EQ::behavior::LookupEntry behavior_static_lookup_entries[EQ::versio
RoF::behavior::CoinHasWeight
),
/*[MobVersion::OfflineRoF2] =*/
EQ::behavior::LookupEntry(
RoF2::behavior::CoinHasWeight
),
/*[MobVersion::OfflineLS] =*/
EQ::behavior::LookupEntry(
RoF2::behavior::CoinHasWeight
)
@@ -1202,6 +1272,19 @@ static const EQ::spells::LookupEntry spells_static_lookup_entries[EQ::versions::
RoF2::spells::NPC_BUFFS,
RoF2::spells::PET_BUFFS,
RoF2::spells::MERC_BUFFS
),
/*[ClientVersion::Larion] =*/
EQ::spells::LookupEntry(
Larion::spells::SPELL_ID_MAX,
Larion::spells::SPELLBOOK_SIZE,
UF::spells::SPELL_GEM_COUNT, // client translators are setup to allow the max value a client supports..however, the top 4 indices are not valid in this case
Larion::spells::LONG_BUFFS,
Larion::spells::SHORT_BUFFS,
Larion::spells::DISC_BUFFS,
Larion::spells::TOTAL_BUFFS,
Larion::spells::NPC_BUFFS,
Larion::spells::PET_BUFFS,
Larion::spells::MERC_BUFFS
)
};
+1 -1
View File
@@ -29,7 +29,7 @@
#include "../common/patches/uf_limits.h"
#include "../common/patches/rof_limits.h"
#include "../common/patches/rof2_limits.h"
#include "../common/patches/larion_limits.h"
namespace EQ
{
+392 -66
View File
@@ -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,18 @@ struct BazaarSearchMessaging_Struct {
}
};
struct BuylineItemDetails_Struct {
uint64 item_cost;
uint32 item_quantity;
};
struct EqGuid
{
uint32_t Id;
uint16_t WorldId;
uint16_t Reserved;
};
// Restore structure packing to default
#pragma pack()
@@ -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 = {};
+48 -47
View File
@@ -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;
+41 -7
View File
@@ -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
+65
View File
@@ -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;
}
+2
View File
@@ -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
View File
@@ -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
+24 -8
View File
@@ -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;
}
//
@@ -1935,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
//
+2
View File
@@ -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:
//////////////////////////
File diff suppressed because it is too large Load Diff
+55
View File
@@ -0,0 +1,55 @@
/* EQEMu: Everquest Server Emulator
Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.net)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY except by those people which sell it, which
are required to give you total support for your newly bought product;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef COMMON_LARION_H
#define COMMON_LARION_H
#include "../struct_strategy.h"
class EQStreamIdentifier;
namespace Larion
{
//these are the only public member of this namespace.
extern void Register(EQStreamIdentifier& into);
extern void Reload();
//you should not directly access anything below..
//I just dont feel like making a seperate header for it.
class Strategy : public StructStrategy {
public:
Strategy();
protected:
virtual std::string Describe() const;
virtual const EQ::versions::ClientVersion ClientVersion() const;
//magic macro to declare our opcode processors
#include "ss_declare.h"
#include "larion_ops.h"
};
}; /*Larion*/
#endif /*COMMON_LARION_H*/
+284
View File
@@ -0,0 +1,284 @@
/* EQEMu: Everquest Server Emulator
Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.net)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY except by those people which sell it, which
are required to give you total support for your newly bought product;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "larion_limits.h"
#include "../strings.h"
int16 Larion::invtype::GetInvTypeSize(int16 inv_type)
{
switch (inv_type) {
case invtype::typePossessions:
return invtype::POSSESSIONS_SIZE;
case invtype::typeBank:
return invtype::BANK_SIZE;
case invtype::typeSharedBank:
return invtype::SHARED_BANK_SIZE;
case invtype::typeTrade:
return invtype::TRADE_SIZE;
case invtype::typeWorld:
return invtype::WORLD_SIZE;
case invtype::typeLimbo:
return invtype::LIMBO_SIZE;
case invtype::typeTribute:
return invtype::TRIBUTE_SIZE;
case invtype::typeTrophyTribute:
return invtype::TROPHY_TRIBUTE_SIZE;
case invtype::typeGuildTribute:
return invtype::GUILD_TRIBUTE_SIZE;
case invtype::typeMerchant:
return invtype::MERCHANT_SIZE;
case invtype::typeDeleted:
return invtype::DELETED_SIZE;
case invtype::typeCorpse:
return invtype::CORPSE_SIZE;
case invtype::typeBazaar:
return invtype::BAZAAR_SIZE;
case invtype::typeInspect:
return invtype::INSPECT_SIZE;
case invtype::typeRealEstate:
return invtype::REAL_ESTATE_SIZE;
case invtype::typeViewMODPC:
return invtype::VIEW_MOD_PC_SIZE;
case invtype::typeViewMODBank:
return invtype::VIEW_MOD_BANK_SIZE;
case invtype::typeViewMODSharedBank:
return invtype::VIEW_MOD_SHARED_BANK_SIZE;
case invtype::typeViewMODLimbo:
return invtype::VIEW_MOD_LIMBO_SIZE;
case invtype::typeAltStorage:
return invtype::ALT_STORAGE_SIZE;
case invtype::typeArchived:
return invtype::ARCHIVED_SIZE;
case invtype::typeMail:
return invtype::MAIL_SIZE;
case invtype::typeGuildTrophyTribute:
return invtype::GUILD_TROPHY_TRIBUTE_SIZE;
case invtype::typeKrono:
return invtype::KRONO_SIZE;
case invtype::typeOther:
return invtype::OTHER_SIZE;
default:
return INULL;
}
}
const char* Larion::invtype::GetInvTypeName(int16 inv_type)
{
switch (inv_type) {
case invtype::TYPE_INVALID:
return "Invalid Type";
case invtype::typePossessions:
return "Possessions";
case invtype::typeBank:
return "Bank";
case invtype::typeSharedBank:
return "Shared Bank";
case invtype::typeTrade:
return "Trade";
case invtype::typeWorld:
return "World";
case invtype::typeLimbo:
return "Limbo";
case invtype::typeTribute:
return "Tribute";
case invtype::typeTrophyTribute:
return "Trophy Tribute";
case invtype::typeGuildTribute:
return "Guild Tribute";
case invtype::typeMerchant:
return "Merchant";
case invtype::typeDeleted:
return "Deleted";
case invtype::typeCorpse:
return "Corpse";
case invtype::typeBazaar:
return "Bazaar";
case invtype::typeInspect:
return "Inspect";
case invtype::typeRealEstate:
return "Real Estate";
case invtype::typeViewMODPC:
return "View MOD PC";
case invtype::typeViewMODBank:
return "View MOD Bank";
case invtype::typeViewMODSharedBank:
return "View MOD Shared Bank";
case invtype::typeViewMODLimbo:
return "View MOD Limbo";
case invtype::typeAltStorage:
return "Alt Storage";
case invtype::typeArchived:
return "Archived";
case invtype::typeMail:
return "Mail";
case invtype::typeGuildTrophyTribute:
return "Guild Trophy Tribute";
case invtype::typeKrono:
return "Krono";
case invtype::typeOther:
return "Other";
default:
return "Unknown Type";
}
}
bool Larion::invtype::IsInvTypePersistent(int16 inv_type)
{
switch (inv_type) {
case invtype::typePossessions:
case invtype::typeBank:
case invtype::typeSharedBank:
case invtype::typeTrade:
case invtype::typeWorld:
case invtype::typeLimbo:
case invtype::typeTribute:
case invtype::typeTrophyTribute:
case invtype::typeGuildTribute:
return true;
default:
return false;
}
}
const char* Larion::invslot::GetInvPossessionsSlotName(int16 inv_slot)
{
switch (inv_slot) {
case invslot::SLOT_INVALID:
return "Invalid Slot";
case invslot::slotCharm:
return "Charm";
case invslot::slotEar1:
return "Ear 1";
case invslot::slotHead:
return "Head";
case invslot::slotFace:
return "Face";
case invslot::slotEar2:
return "Ear 2";
case invslot::slotNeck:
return "Neck";
case invslot::slotShoulders:
return "Shoulders";
case invslot::slotArms:
return "Arms";
case invslot::slotBack:
return "Back";
case invslot::slotWrist1:
return "Wrist 1";
case invslot::slotWrist2:
return "Wrist 2";
case invslot::slotRange:
return "Range";
case invslot::slotHands:
return "Hands";
case invslot::slotPrimary:
return "Primary";
case invslot::slotSecondary:
return "Secondary";
case invslot::slotFinger1:
return "Finger 1";
case invslot::slotFinger2:
return "Finger 2";
case invslot::slotChest:
return "Chest";
case invslot::slotLegs:
return "Legs";
case invslot::slotFeet:
return "Feet";
case invslot::slotWaist:
return "Waist";
case invslot::slotPowerSource:
return "Power Source";
case invslot::slotAmmo:
return "Ammo";
case invslot::slotGeneral1:
return "General 1";
case invslot::slotGeneral2:
return "General 2";
case invslot::slotGeneral3:
return "General 3";
case invslot::slotGeneral4:
return "General 4";
case invslot::slotGeneral5:
return "General 5";
case invslot::slotGeneral6:
return "General 6";
case invslot::slotGeneral7:
return "General 7";
case invslot::slotGeneral8:
return "General 8";
case invslot::slotGeneral9:
return "General 9";
case invslot::slotGeneral10:
return "General 10";
case invslot::slotCursor:
return "Cursor";
default:
return "Unknown Slot";
}
}
const char* Larion::invslot::GetInvSlotName(int16 inv_type, int16 inv_slot)
{
if (inv_type == invtype::typePossessions)
return invslot::GetInvPossessionsSlotName(inv_slot);
int16 type_size = invtype::GetInvTypeSize(inv_type);
if (!type_size || inv_slot == invslot::SLOT_INVALID)
return "Invalid Slot";
if ((inv_slot + 1) >= type_size)
return "Unknown Slot";
static std::string ret_str;
ret_str = StringFormat("Slot %i", (inv_slot + 1));
return ret_str.c_str();
}
const char* Larion::invbag::GetInvBagIndexName(int16 bag_index)
{
if (bag_index == invbag::SLOT_INVALID)
return "Invalid Bag";
if (bag_index >= invbag::SLOT_COUNT)
return "Unknown Bag";
static std::string ret_str;
ret_str = StringFormat("Bag %i", (bag_index + 1));
return ret_str.c_str();
}
const char* Larion::invaug::GetInvAugIndexName(int16 aug_index)
{
if (aug_index == invaug::SOCKET_INVALID)
return "Invalid Augment";
if (aug_index >= invaug::SOCKET_COUNT)
return "Unknown Augment";
static std::string ret_str;
ret_str = StringFormat("Augment %i", (aug_index + 1));
return ret_str.c_str();
}
+330
View File
@@ -0,0 +1,330 @@
/* EQEMu: Everquest Server Emulator
Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.net)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY except by those people which sell it, which
are required to give you total support for your newly bought product;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef COMMON_LARION_LIMITS_H
#define COMMON_LARION_LIMITS_H
#include "../types.h"
#include "../emu_versions.h"
#include "../skills.h"
namespace Larion
{
const int16 IINVALID = -1;
const int16 INULL = 0;
namespace inventory {
inline EQ::versions::ClientVersion GetInventoryRef() { return EQ::versions::ClientVersion::Larion; }
const bool ConcatenateInvTypeLimbo = false;
const bool AllowOverLevelEquipment = true;
const bool AllowEmptyBagInBag = true;
const bool AllowClickCastFromBag = true;
} /*inventory*/
namespace invtype {
inline EQ::versions::ClientVersion GetInvTypeRef() { return EQ::versions::ClientVersion::Larion; }
namespace enum_ {
enum InventoryTypes : int16 {
typePossessions = INULL,
typeBank,
typeSharedBank,
typeTrade,
typeWorld,
typeLimbo,
typeTribute,
typeTrophyTribute,
typeGuildTribute,
typeMerchant,
typeDeleted,
typeCorpse,
typeBazaar,
typeInspect,
typeRealEstate,
typeViewMODPC,
typeViewMODBank,
typeViewMODSharedBank,
typeViewMODLimbo,
typeAltStorage,
typeArchived,
typeMail,
typeGuildTrophyTribute,
typeKrono,
typeOther
};
} // namespace enum_
using namespace enum_;
const int16 POSSESSIONS_SIZE = 34;
const int16 BANK_SIZE = 24;
const int16 SHARED_BANK_SIZE = 2;
const int16 TRADE_SIZE = 8;
const int16 WORLD_SIZE = 10;
const int16 LIMBO_SIZE = 36;
const int16 TRIBUTE_SIZE = 5;
const int16 TROPHY_TRIBUTE_SIZE = 0;//unknown
const int16 GUILD_TRIBUTE_SIZE = 2;//unverified
const int16 MERCHANT_SIZE = 200;
const int16 DELETED_SIZE = 0;//unknown - "Recovery Tab"
const int16 CORPSE_SIZE = POSSESSIONS_SIZE;
const int16 BAZAAR_SIZE = 200;
const int16 INSPECT_SIZE = 23;
const int16 REAL_ESTATE_SIZE = 0;//unknown
const int16 VIEW_MOD_PC_SIZE = POSSESSIONS_SIZE;
const int16 VIEW_MOD_BANK_SIZE = BANK_SIZE;
const int16 VIEW_MOD_SHARED_BANK_SIZE = SHARED_BANK_SIZE;
const int16 VIEW_MOD_LIMBO_SIZE = LIMBO_SIZE;
const int16 ALT_STORAGE_SIZE = 0;//unknown - "Shroud Bank"
const int16 ARCHIVED_SIZE = 0;//unknown
const int16 MAIL_SIZE = 0;//unknown
const int16 GUILD_TROPHY_TRIBUTE_SIZE = 0;//unknown
const int16 KRONO_SIZE = 0;//unknown
const int16 OTHER_SIZE = 0;//unknown
const int16 TRADE_NPC_SIZE = 4; // defined by implication
const int16 TYPE_INVALID = IINVALID;
const int16 TYPE_BEGIN = typePossessions;
const int16 TYPE_END = typeOther;
const int16 TYPE_COUNT = (TYPE_END - TYPE_BEGIN) + 1;
int16 GetInvTypeSize(int16 inv_type);
const char* GetInvTypeName(int16 inv_type);
bool IsInvTypePersistent(int16 inv_type);
} /*invtype*/
namespace invslot {
inline EQ::versions::ClientVersion GetInvSlotRef() { return EQ::versions::ClientVersion::Larion; }
namespace enum_ {
enum InventorySlots : int16 {
slotCharm = INULL,
slotEar1,
slotHead,
slotFace,
slotEar2,
slotNeck,
slotShoulders,
slotArms,
slotBack,
slotWrist1,
slotWrist2,
slotRange,
slotHands,
slotPrimary,
slotSecondary,
slotFinger1,
slotFinger2,
slotChest,
slotLegs,
slotFeet,
slotWaist,
slotPowerSource,
slotAmmo,
slotGeneral1,
slotGeneral2,
slotGeneral3,
slotGeneral4,
slotGeneral5,
slotGeneral6,
slotGeneral7,
slotGeneral8,
slotGeneral9,
slotGeneral10,
slotCursor
};
constexpr int16 format_as(InventorySlots slot) { return static_cast<int16>(slot); }
} // namespace enum_
using namespace enum_;
const int16 SLOT_INVALID = IINVALID;
const int16 SLOT_BEGIN = INULL;
const int16 POSSESSIONS_BEGIN = slotCharm;
const int16 POSSESSIONS_END = slotCursor;
const int16 POSSESSIONS_COUNT = (POSSESSIONS_END - POSSESSIONS_BEGIN) + 1;
const int16 EQUIPMENT_BEGIN = slotCharm;
const int16 EQUIPMENT_END = slotAmmo;
const int16 EQUIPMENT_COUNT = (EQUIPMENT_END - EQUIPMENT_BEGIN) + 1;
const int16 GENERAL_BEGIN = slotGeneral1;
const int16 GENERAL_END = slotGeneral10;
const int16 GENERAL_COUNT = (GENERAL_END - GENERAL_BEGIN) + 1;
const int16 BONUS_BEGIN = invslot::slotCharm;
const int16 BONUS_STAT_END = invslot::slotPowerSource;
const int16 BONUS_SKILL_END = invslot::slotAmmo;
const int16 CORPSE_BEGIN = invslot::slotGeneral1;
const int16 CORPSE_END = invslot::slotGeneral1 + invslot::slotCursor;
const uint64 EQUIPMENT_BITMASK = 0x00000000007FFFFF;
const uint64 GENERAL_BITMASK = 0x00000001FF800000;
const uint64 CURSOR_BITMASK = 0x0000000200000000;
const uint64 POSSESSIONS_BITMASK = (EQUIPMENT_BITMASK | GENERAL_BITMASK | CURSOR_BITMASK); // based on 34-slot count (RoF+)
const uint64 CORPSE_BITMASK = (GENERAL_BITMASK | CURSOR_BITMASK | (EQUIPMENT_BITMASK << 34)); // based on 34-slot count (RoF+)
const char* GetInvPossessionsSlotName(int16 inv_slot);
const char* GetInvSlotName(int16 inv_type, int16 inv_slot);
} /*invslot*/
namespace invbag {
inline EQ::versions::ClientVersion GetInvBagRef() { return EQ::versions::ClientVersion::Larion; }
const int16 SLOT_INVALID = IINVALID;
const int16 SLOT_BEGIN = INULL;
const int16 SLOT_END = 9; //254;
const int16 SLOT_COUNT = 10; //255; // server Size will be 255..unsure what actual client is (test)
const char* GetInvBagIndexName(int16 bag_index);
} /*invbag*/
namespace invaug {
inline EQ::versions::ClientVersion GetInvAugRef() { return EQ::versions::ClientVersion::Larion; }
const int16 SOCKET_INVALID = IINVALID;
const int16 SOCKET_BEGIN = INULL;
const int16 SOCKET_END = 5;
const int16 SOCKET_COUNT = 6;
const char* GetInvAugIndexName(int16 aug_index);
} /*invaug*/
namespace item {
inline EQ::versions::ClientVersion GetItemRef() { return EQ::versions::ClientVersion::Larion; }
//enum Unknown : int { // looks like item class..but, RoF has it too - nothing in UF-
// Unknown1 = 0,
// Unknown2 = 1,
// Unknown3 = 2,
// Unknown4 = 5 // krono?
//};
enum ItemPacketType : int {
ItemPacketMerchant = 100,
ItemPacketTradeView = 101,
ItemPacketLoot = 102,
ItemPacketTrade = 103,
ItemPacketCharInventory = 105,
ItemPacketLimbo = 106,
ItemPacketWorldContainer = 107,
ItemPacketTributeItem = 108,
ItemPacketGuildTribute = 109,
ItemPacket10 = 110,
ItemPacket11 = 111,
ItemPacket12 = 112,
ItemPacketRecovery = 113,
ItemPacket14 = 115 // Parcel? adds to merchant window too
};
} /*item*/
namespace profile {
inline EQ::versions::ClientVersion GetProfileRef() { return EQ::versions::ClientVersion::Larion; }
const int16 BANDOLIERS_SIZE = 20; // number of bandolier instances
const int16 BANDOLIER_ITEM_COUNT = 4; // number of equipment slots in bandolier instance
const int16 POTION_BELT_SIZE = 5;
const int16 SKILL_ARRAY_SIZE = 100;
} /*profile*/
namespace constants {
inline EQ::versions::ClientVersion GetConstantsRef() { return EQ::versions::ClientVersion::Larion; }
const EQ::expansions::Expansion EXPANSION = EQ::expansions::Expansion::LS;
const uint32 EXPANSION_BIT = EQ::expansions::bitLS;
const uint32 EXPANSIONS_MASK = EQ::expansions::maskLS;
const size_t CHARACTER_CREATION_LIMIT = 12;
const size_t SAY_LINK_BODY_SIZE = 56;
const uint32 MAX_GUILD_ID = 50000;
} /*constants*/
namespace behavior {
inline EQ::versions::ClientVersion GetBehaviorRef() { return EQ::versions::ClientVersion::Larion; }
const bool CoinHasWeight = false;
} /*behavior*/
namespace skills {
inline EQ::versions::ClientVersion GetSkillsRef() { return EQ::versions::ClientVersion::Larion; }
const size_t LastUsableSkill = EQ::skills::Skill2HPiercing;
} /*skills*/
namespace spells {
inline EQ::versions::ClientVersion GetSkillsRef() { return EQ::versions::ClientVersion::Larion; }
enum class CastingSlot : uint32 {
Gem1 = 0,
Gem2 = 1,
Gem3 = 2,
Gem4 = 3,
Gem5 = 4,
Gem6 = 5,
Gem7 = 6,
Gem8 = 7,
Gem9 = 8,
Gem10 = 9,
Gem11 = 10,
Gem12 = 11,
MaxGems = 18, // fallacy..only 12 slot are useable...
Item = 12,
Discipline = 13,
AltAbility = 0xFF
};
const int SPELL_ID_MAX = 71999;
const int SPELLBOOK_SIZE = 1120;
const int SPELL_GEM_COUNT = static_cast<uint32>(CastingSlot::MaxGems);
const int SPELL_GEM_RECAST_TIMER = 15;
const int LONG_BUFFS = 42;
const int SHORT_BUFFS = 20;
const int DISC_BUFFS = 1;
const int TOTAL_BUFFS = LONG_BUFFS + SHORT_BUFFS + DISC_BUFFS;
const int NPC_BUFFS = 97;
const int PET_BUFFS = NPC_BUFFS;
const int MERC_BUFFS = LONG_BUFFS;
} /*spells*/
}; /* Larion */
#endif /*COMMON_LARION_LIMITS_H*/
+19
View File
@@ -0,0 +1,19 @@
//list of packets we need to encode on the way out:
E(OP_LogServer)
E(OP_SendMembership)
E(OP_SendMembershipDetails)
E(OP_SendMaxCharacters)
E(OP_SendCharInfo)
E(OP_ExpansionInfo)
E(OP_SpawnAppearance)
//E(OP_SendAATable)
E(OP_PlayerProfile)
E(OP_ZoneEntry)
E(OP_ZoneSpawns)
//list of packets we need to decode on the way in:
D(OP_ZoneEntry)
#undef E
#undef D
+233
View File
@@ -0,0 +1,233 @@
#ifndef LARION_STRUCTS_H_
#define LARION_STRUCTS_H_
namespace Larion {
namespace structs {
// constants
static const uint32 MAX_PP_AA_ARRAY = 300;
static const uint32 MAX_PP_SKILL = PACKET_SKILL_ARRAY_SIZE;
static const uint32 MAX_PP_INNATE_SKILL = 25;
static const uint32 MAX_PP_DISCIPLINES = 300;
static const uint32 MAX_PP_COMBAT_ABILITY_TIMERS = 25;
static const uint32 MAX_PP_UNKNOWN_ABILITIES = 25;
static const uint32 MAX_RECAST_TYPES = 25;
static const uint32 MAX_ITEM_RECAST_TYPES = 100;
static const uint32 BUFF_COUNT = 62;
static const uint32 MAX_PP_LANGUAGE = 32;
#pragma pack(1)
struct LoginInfo_Struct {
/*000*/ char login_info[64];
/*064*/ uint8 unknown064[124];
/*188*/ uint8 zoning; // 01 if zoning, 00 if not
/*189*/ uint8 unknown189[275];
/*488*/
};
struct ClientZoneEntry_Struct {
/*00*/ uint32 unknown00; // ***Placeholder
/*04*/ char char_name[64]; // Player firstname [32]
/*68*/ uint32 unknown68;
/*72*/ uint32 unknown72;
/*76*/ uint32 unknown76;
/*80*/ uint32 unknown80;
/*84*/ uint32 unknown84;
/*88*/ uint32 unknown88;
/*92*/
};
struct Membership_Struct
{
/*000*/ uint8 membership; //0 not gold, 2 gold
/*001*/ uint32 races; // Seen ff ff 01 00
/*005*/ uint32 classes; // Seen ff ff 01 00
/*009*/ uint32 entrysize; // Seen 33
/*013*/ int32 entries[33]; // Most -1, 1, and 0 for Gold Status
/*145*/
};
struct Membership_Entry_Struct
{
/*000*/ uint32 purchase_id; // Seen 1, then increments 90287 to 90300
/*004*/ uint32 bitwise_entry; // Seen 16 to 65536 - Skips 4096
/*008*/
};
struct Membership_Setting_Struct
{
/*000*/ int8 setting_index; // 0, 1, 2 or 3: f2p, silver, gold, platinum?
/*001*/ int32 setting_id; // 0 to 23 actually seen but the OP_Membership packet has up to 32
/*005*/ int32 setting_value;
/*009*/
};
struct Membership_Details_Struct
{
/*000*/ uint32 membership_setting_count; // Seen 96
/*004*/ Membership_Setting_Struct settings[96]; // 864 Bytes
/*364*/ uint32 race_entry_count; // Seen 17
/*368*/ Membership_Entry_Struct membership_races[17]; // 136 Bytes
/*3f0*/ uint32 class_entry_count; // Seen 15
/*3f4*/ Membership_Entry_Struct membership_classes[17]; // 136 Bytes
/*47c*/ uint32 exit_url_length; // Length of the exit_url string (0 for none)
/*480*/ //char exit_url[42]; // Upgrade to Silver or Gold Membership URL
};
struct MaxCharacters_Struct {
/*000*/ uint32 max_chars;
/*004*/ uint32 marketplace_chars;
/*008*/ int32 unknown008; //some of these probably deal with heroic characters or something
/*00c*/ int32 unknown00c;
/*010*/ int32 unknown010;
/*014*/ int32 unknown014;
/*018*/ int32 unknown018;
/*01c*/ int32 unknown01c;
/*020*/ int32 unknown020;
/*024*/ int32 unknown024;
/*028*/ int32 unknown028;
/*02c*/ int32 unknown02c;
/*030*/ int32 unknown030;
/*034*/ int32 unknown034;
/*038*/
};
struct ExpansionInfo_Struct {
/*000*/ char Unknown000[64];
/*064*/ uint32 Expansions;
};
/*
* Visible equiptment.
* Size: 20 Octets
*/
struct Texture_Struct
{
uint32 Material;
uint32 Unknown1;
uint32 EliteMaterial;
uint32 HeroForgeModel;
uint32 Material2; // Same as material?
};
/*
** Color_Struct
** Size: 4 bytes
** Used for convenience
** Merth: Gave struct a name so gcc 2.96 would compile
**
*/
struct Tint_Struct
{
union {
struct {
uint8 Blue;
uint8 Green;
uint8 Red;
uint8 UseTint; // if there's a tint this is FF
};
uint32 Color;
};
};
struct CharSelectEquip : Texture_Struct, Tint_Struct {};
struct CharacterSelectEntry_Struct
{
char Name[1];
uint32 Class;
uint32 Race;
uint8 Level;
uint32 ShroudClass;
uint32 ShroudRace;
uint16 Zone;
uint16 Instance;
uint8 Gender;
uint8 Face;
CharSelectEquip Equip[9];
uint8 Unknown1; //Seen 256
uint8 Unknown2; //Seen 0
uint32 DrakkinTattoo;
uint32 DrakkinDetails;
uint32 Deity;
uint32 PrimaryIDFile;
uint32 SecondaryIDFile;
uint8 HairColor;
uint8 BeardColor;
uint8 EyeColor1;
uint8 EyeColor2;
uint8 HairStyle;
uint8 Beard;
uint8 Enabled;
uint8 Tutorial;
uint32 DrakkinHeritage;
uint8 Unknown3;
uint8 GoHome;
uint32 LastLogin;
uint8 Unknown4; // Seen 0
uint8 Unknown5; // Seen 0
uint8 Unknown6; // Seen 0
uint8 Unknown7; // Seen 0
uint32 CharacterId; //A Guess, Character I made a little bit after has a number a few hundred after the first
uint32 Unknown8; // Seen 1
};
/*
** Character Selection Struct
**
*/
struct CharacterSelect_Struct
{
/*000*/ uint32 CharCount; //number of chars in this packet
};
struct SpawnAppearance_Struct
{
/*0000*/ uint32 spawn_id; // ID of the spawn
/*0004*/ uint32 type; // Values associated with the type
/*0008*/ uint32 parameter; // Type of data sent
/*0012*/ uint32 unknown012;
/*0016*/ uint32 unknown016;
/*0020*/ uint32 unknown020;
/*0024*/
};
struct Spawn_Struct_Bitfields
{
// byte 1
/*00*/ unsigned gender : 2; // Gender (0=male, 1=female, 2=monster)
/*02*/ unsigned ispet : 1; // Guessed based on observing live spawns
/*03*/ unsigned afk : 1; // 0=no, 1=afk
/*04*/ unsigned anon : 2; // 0=normal, 1=anon, 2=roleplay
/*06*/ unsigned gm : 1;
/*07*/ unsigned sneak : 1;
// byte 2
/*08*/ unsigned lfg : 1;
/*09*/ unsigned unk9 : 1;
/*10*/ unsigned invis : 12; // there are 3000 different (non-GM) invis levels
/*22*/ unsigned linkdead : 1; // 1 Toggles LD on or off after name. Correct for RoF2
/*23*/ unsigned showhelm : 1;
// byte 4
/*24*/ unsigned betabuffed : 1; // Prefixes name with !
/*25*/ unsigned trader : 1;
/*26*/ unsigned animationonpop : 1;
/*27*/ unsigned targetable : 1;
/*28*/ unsigned targetable_with_hotkey : 1;
/*29*/ unsigned showname : 1;
/*30*/ unsigned idleanimationsoff : 1; // what we called statue?
/*31*/ unsigned untargetable : 1; // bClickThrough
// byte 5
/*32*/ unsigned buyer : 1;
/*33*/ unsigned offline : 1;
/*34*/ unsigned interactiveobject : 1;
/*35*/ unsigned missile : 1;
/*36*/ unsigned title : 1;
/*37*/ unsigned suffix : 1;
/*38*/ unsigned unk38 : 1;
/*39*/ unsigned unk39 : 1;
};
#pragma pack()
}; //end namespace structs
}; //end namespace larion
#endif /*LARION_STRUCTS_H_*/
+3 -1
View File
@@ -26,7 +26,7 @@
#include "sod.h"
#include "rof.h"
#include "rof2.h"
#include "larion.h"
void RegisterAllPatches(EQStreamIdentifier &into)
{
@@ -36,6 +36,7 @@ void RegisterAllPatches(EQStreamIdentifier &into)
UF::Register(into);
RoF::Register(into);
RoF2::Register(into);
Larion::Register(into);
}
void ReloadAllPatches()
@@ -46,4 +47,5 @@ void ReloadAllPatches()
UF::Reload();
RoF::Reload();
RoF2::Reload();
Larion::Reload();
}
+523 -32
View File
@@ -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)
@@ -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{};
@@ -2393,7 +2680,7 @@ namespace RoF2
{
float instrument_mod = 0.0f;
uint8 effect_type = emu->buffs[r].effect_type;
uint32 player_id = emu->buffs[r].player_id;;
uint32 player_id = emu->buffs[r].player_id;
if (emu->buffs[r].spellid != 0xFFFF && emu->buffs[r].spellid != 0)
{
@@ -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);
+3
View File
@@ -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
View File
@@ -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 {
@@ -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
+93
View File
@@ -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
@@ -66,6 +66,7 @@ public:
{.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"},
@@ -84,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"},
+17
View File
@@ -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
+4
View File
@@ -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)));
}
};
+2
View File
@@ -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
+23 -2
View File
@@ -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.")
@@ -228,6 +229,7 @@ RULE_BOOL(Character, GroupInvitesRequireTarget, false, "Enable to require player
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)
@@ -242,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)
@@ -333,6 +338,8 @@ 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_INT(World, Id, 100, "Used by later clients to create GUIDs, expected to be Unique to the world but ultimately not that important")
RULE_CATEGORY_END()
RULE_CATEGORY(Zone)
@@ -470,7 +477,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.")
@@ -510,6 +516,9 @@ RULE_BOOL(Spells, UseClassicSpellFocus, false, "Enabling will tell the server to
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_CATEGORY_END()
RULE_CATEGORY(Combat)
@@ -619,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")
@@ -667,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)
@@ -692,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)
@@ -706,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)
@@ -717,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")
@@ -758,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)
@@ -804,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)
@@ -895,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)
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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
-1
View File
@@ -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
};
+105
View File
@@ -2325,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;
}
+3
View File
@@ -1625,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
View File
@@ -25,7 +25,7 @@
// Build variables
// these get injected during the build pipeline
#define CURRENT_VERSION "22.53.0-dev" // always append -dev to the current version for custom-builds
#define CURRENT_VERSION "22.56.3-dev" // always append -dev to the current version for custom-builds
#define LOGIN_VERSION "0.8.0"
#define COMPILE_DATE __DATE__
#define COMPILE_TIME __TIME__
@@ -42,8 +42,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
+42 -3
View File
@@ -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 {
@@ -556,10 +573,13 @@ void Client::SendExpansionPacketData(PlayerLoginReply_Struct& plrs)
{
SerializeBuffer buf;
//from eqlsstr_us.txt id of each expansion, excluding 'Everquest'
int ExpansionLookup[20] = { 3007, 3008, 3009, 3010, 3012,
int ExpansionLookup[30] = { 3007, 3008, 3009, 3010, 3012,
3014, 3031, 3033, 3036, 3040,
3045, 3046, 3047, 3514, 3516,
3518, 3520, 3522, 3524 };
3518, 3520, 3522, 3524, 3526,
3528, 3530, 3532, 3534, 3535,
3536, 3537, 3538, 3539, 3540
};
if (server.options.IsDisplayExpansions()) {
@@ -567,7 +587,26 @@ void Client::SendExpansionPacketData(PlayerLoginReply_Struct& plrs)
int32_t expansion = server.options.GetMaxExpansions();
int32_t owned_expansion = (expansion << 1) | 1;
if (m_client_version == cv_sod) {
if (m_client_version == cv_larion) {
buf.WriteInt32(0x00);
buf.WriteInt32(0x00);
buf.WriteInt16(0x00);
buf.WriteInt32(30);
for (int i = 0; i < 30; i++)
{
buf.WriteUInt32(i + 1);
buf.WriteUInt8(1);
buf.WriteInt32(ExpansionLookup[i]);
buf.WriteInt32(6046);
buf.WriteUInt32(0xFFFFFFFF);
buf.WriteUInt32(0);
}
auto out = std::make_unique<EQApplicationPacket>(OP_LoginExpansionPacketData, buf);
m_connection->QueuePacket(out.get());
}
else if (m_client_version == cv_sod) {
// header info of packet. Requires OP_LoginExpansionPacketData=0x0031 to be in login_opcodes_sod.conf
buf.WriteInt32(0x00);
+7
View File
@@ -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
*
+40
View File
@@ -88,6 +88,46 @@ 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.GetServerPath(),
server.config.GetVariableString(
"client_configuration",
"larion_opcodes",
"login_opcodes.conf"
)
);
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()
+2
View File
@@ -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
+2 -1
View File
@@ -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
+1 -1
View File
@@ -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);
+66 -11
View File
@@ -977,8 +977,7 @@ bool WorldServer::ValidateWorldServerAdminLogin(
return false;
}
void WorldServer::SerializeForClientServerList(SerializeBuffer &out, bool use_local_ip) const
{
void WorldServer::SerializeForClientServerListLegacy(class SerializeBuffer& out, bool use_local_ip) const {
// see LoginClientServerData_Struct
if (use_local_ip) {
out.WriteString(GetLocalIP());
@@ -988,18 +987,19 @@ void WorldServer::SerializeForClientServerList(SerializeBuffer &out, bool use_lo
}
switch (GetServerListID()) {
case LS::ServerType::Legends:
out.WriteInt32(LS::ServerTypeFlags::Legends);
break;
case LS::ServerType::Preferred:
out.WriteInt32(LS::ServerTypeFlags::Preferred);
break;
default:
out.WriteInt32(LS::ServerTypeFlags::Standard);
break;
case LS::ServerType::Legends:
out.WriteInt32(LS::ServerTypeFlags::Legends);
break;
case LS::ServerType::Preferred:
out.WriteInt32(LS::ServerTypeFlags::Preferred);
break;
default:
out.WriteInt32(LS::ServerTypeFlags::Standard);
break;
}
out.WriteUInt32(GetServerId());
out.WriteString(GetServerLongName());
out.WriteString("us"); // country code
out.WriteString("en"); // language code
@@ -1020,6 +1020,61 @@ void WorldServer::SerializeForClientServerList(SerializeBuffer &out, bool use_lo
out.WriteUInt32(GetPlayersOnline());
}
void WorldServer::SerializeForClientServerListLarion(class SerializeBuffer& out, bool use_local_ip) const {
if (use_local_ip) {
out.WriteString(GetLocalIP());
}
else {
out.WriteString(GetRemoteIP());
}
out.WriteUInt32(9000);
out.WriteUInt32(0);
uint32_t flags = 32; //all servers i saw had this set
switch (GetServerListID()) {
case LS::ServerType::Legends:
flags += LS::ServerTypeFlags::Legends;
break;
case LS::ServerType::Preferred:
flags += LS::ServerTypeFlags::Preferred;
break;
default:
flags += LS::ServerTypeFlags::Standard;
break;
}
out.WriteUInt32(flags);
out.WriteUInt32(GetServerId());
out.WriteString(GetServerLongName());
out.WriteString("EN");
out.WriteString("US");
if (GetStatus() < 0) {
if (GetZonesBooted() == 0) {
out.WriteInt32(LS::ServerStatusFlags::Down);
}
else {
out.WriteInt32(LS::ServerStatusFlags::Locked);
}
}
else {
out.WriteInt32(LS::ServerStatusFlags::Up);
}
out.WriteUInt32(GetPlayersOnline());
}
void WorldServer::SerializeForClientServerList(SerializeBuffer& out, bool use_local_ip, LSClientVersion version) const
{
if (version == cv_larion) {
SerializeForClientServerListLarion(out, use_local_ip);
}
else {
SerializeForClientServerListLegacy(out, use_local_ip);
}
}
/**
* @param in_server_list_id
* @return
+6 -2
View File
@@ -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>
@@ -148,8 +149,11 @@ public:
bool HandleNewLoginserverRegisteredOnly(Database::DbWorldRegistration &world_registration);
bool HandleNewLoginserverInfoUnregisteredAllowed(Database::DbWorldRegistration &world_registration);
void SerializeForClientServerList(class SerializeBuffer& out, bool use_local_ip) const;
private:
void SerializeForClientServerListLegacy(class SerializeBuffer& out, bool use_local_ip) const;
void SerializeForClientServerListLarion(class SerializeBuffer& out, bool use_local_ip) const;
public:
void SerializeForClientServerList(class SerializeBuffer& out, bool use_local_ip, LSClientVersion version) const;
private:
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "eqemu-server",
"version": "22.53.0",
"version": "22.56.3",
"repository": {
"type": "git",
"url": "https://github.com/EQEmu/Server.git"
+730
View File
@@ -0,0 +1,730 @@
# ShowEQ Import Notes:
# ZERO THE FILE first
# perl -pi -e 's/0x[0-9a-fA-F]{4}/0x0000/g' opcodes.conf
# Unknown Mapping:
# OP_Action2 -> OP_Damage
# OP_EnvDamage -> OP_Damage ---> might have been a one time mistake
# Name Differences:
# OP_CancelInvite -> OP_GroupCancelInvite
# OP_GMFind -> OP_FindPersonRequest
# OP_CommonMessage -> OP_ChannelMessage
OP_Unknown=0x0000
OP_ExploreUnknown=0x0000 # used for unknown explorer
# world packets
# Required to reach Char Select:
OP_SendLoginInfo=0x2fca
OP_ApproveWorld=0x0000
OP_LogServer=0x6d4d
OP_SendCharInfo=0x832
OP_ExpansionInfo=0x066d
OP_GuildsList=0x0000
OP_EnterWorld=0x6691
OP_PostEnterWorld=0x2062
OP_World_Client_CRC1=0x74c8
OP_World_Client_CRC2=0x3984
OP_World_Client_CRC3=0x6516
OP_SendSpellChecksum=0x0000
OP_SendSkillCapsChecksum=0x0000
# Character Select Related:
OP_SendMaxCharacters=0x13af
OP_SendMembership=0x2aca
OP_SendMembershipDetails=0x2608
OP_CharacterCreateRequest=0x0000
OP_CharacterCreate=0x0000
OP_DeleteCharacter=0x0000
OP_RandomNameGenerator=0x0000
OP_ApproveName=0x0000
OP_MOTD=0x0000
OP_SetChatServer=0x2726
OP_SetChatServer2=0x0000
OP_ZoneServerInfo=0x2273
OP_WorldComplete=0x195c
OP_WorldUnknown001=0x2049
OP_FloatListThing=0x0000
# Reasons for Disconnect:
OP_ZoneUnavail=0x0000
OP_WorldClientReady=0x0000
OP_CharacterStillInZone=0x0000
OP_WorldChecksumFailure=0x0000
OP_WorldLoginFailed=0x0000
OP_WorldLogout=0x0000
OP_WorldLevelTooHigh=0x0000
OP_CharInacessable=0x0000
OP_UserCompInfo=0x0000
OP_SendExeChecksum=0x0000
OP_SendBaseDataChecksum=0x0000
# Zone in opcodes
OP_AckPacket=0x77c9
OP_ZoneEntry=0x0000
OP_ReqNewZone=0x0000
OP_NewZone=0x0000
OP_ZoneSpawns=0x0000
OP_PlayerProfile=0x0000
OP_TimeOfDay=0x0000
OP_LevelUpdate=0x0000
OP_Stamina=0x0000
OP_RequestClientZoneChange=0x0000
OP_ZoneChange=0x0000
OP_LockoutTimerInfo=0x0000
OP_ZoneServerReady=0x0000
OP_ZoneInUnknown=0x0000
OP_LogoutReply=0x0000
OP_PreLogoutReply=0x0000
# Required to fully log in
OP_SpawnAppearance=0x0000
OP_ChangeSize=0x0000
OP_TributeUpdate=0x0000
OP_TributeTimer=0x0000
OP_SendTributes=0x0000
OP_RequestGuildTributes=0x0000
OP_TributeInfo=0x0000
OP_Weather=0x0000
OP_ReqClientSpawn=0x0000
OP_SpawnDoor=0x0000
OP_GroundSpawn=0x0000
OP_SendZonepoints=0x0000
OP_BlockedBuffs=0x0000
OP_RemoveBlockedBuffs=0x0000
OP_ClearBlockedBuffs=0x0000
OP_WorldObjectsSent=0x0000
OP_SendExpZonein=0x0000
OP_SendAATable=0x0000
OP_ClearAA=0x0000
OP_ClearLeadershipAbilities=0x0000
OP_RespondAA=0x0000
OP_UpdateAA=0x0000
OP_SendAAStats=0x0000
OP_AAExpUpdate=0x0000
OP_ExpUpdate=0x0000
OP_HPUpdate=0x0000
OP_ManaChange=0x0000
OP_TGB=0x0000
OP_SpecialMesg=0x0000
OP_GuildMemberList=0x0000
OP_GuildMOTD=0x0000
OP_CharInventory=0x0000
OP_WearChange=0x0000
OP_ClientUpdate=0x0000
OP_ClientReady=0x0000
OP_SetServerFilter=0x0000
# Guild Opcodes
OP_GetGuildMOTD=0x0000
OP_GetGuildMOTDReply=0x0000
OP_GuildMemberUpdate=0x0000
OP_GuildInvite=0x0000
OP_GuildRemove=0x0000
OP_GuildPeace=0x0000
OP_SetGuildMOTD=0x0000
OP_GuildWar=0x0000
OP_GuildLeader=0x0000
OP_GuildDelete=0x0000
OP_GuildInviteAccept=0x0000
OP_GuildDemote=0x0000
OP_GuildPromote=0x0000
OP_GuildPublicNote=0x0000
OP_GuildManageBanker=0x0000
OP_GuildBank=0x0000
OP_GuildBankItemList=0x0000
OP_SetGuildRank=0x0000
OP_GuildUpdate=0x0000
OP_GuildStatus=0x0000
OP_GuildCreate=0x0000
OP_GuildOpenGuildWindow=0x0000
OP_GuildMemberLevel=0x0000
OP_GuildMemberRankAltBanker=0x0000
OP_GuildMemberPublicNote=0x0000
OP_GuildMemberAdd=0x0000
OP_GuildMemberRename=0x0000
OP_GuildMemberDelete=0x0000
OP_GuildMemberDetails=0x0000
OP_GuildRenameGuild=0x0000
OP_LFGuild=0x0000
OP_GuildDeleteGuild=0x0000
# GM/Guide Opcodes
OP_GMServers=0x0000
OP_GMBecomeNPC=0x0000
OP_GMZoneRequest=0x0000
OP_GMZoneRequest2=0x0000
OP_GMGoto=0x0000
OP_GMSearchCorpse=0x0000
OP_GMHideMe=0x0000
OP_GMDelCorpse=0x0000
OP_GMApproval=0x0000
OP_GMToggle=0x0000
OP_GMSummon=0x0000
OP_GMEmoteZone=0x0000
OP_GMEmoteWorld=0x0000
OP_GMFind=0x0000
OP_GMKick=0x0000
OP_GMKill=0x0000
OP_GMNameChange=0x0000
OP_GMLastName=0x0000
# Misc Opcodes
OP_QueryUCSServerStatus=0x0000
OP_InspectRequest=0x0000
OP_InspectAnswer=0x0000
OP_InspectMessageUpdate=0x0000
OP_BeginCast=0x0000
OP_ColoredText=0x0000
OP_ConsentResponse=0x0000
OP_MemorizeSpell=0x0000
OP_LinkedReuse=0x0000
OP_SwapSpell=0x0000
OP_CastSpell=0x0000
OP_Consider=0x0000
OP_FormattedMessage=0x0000
OP_SimpleMessage=0x0000
OP_Buff=0x0000
OP_Illusion=0x0000
OP_MoneyOnCorpse=0x0000
OP_RandomReply=0x0000
OP_DenyResponse=0x0000
OP_SkillUpdate=0x04c
OP_GMTrainSkillConfirm=0x0000
OP_RandomReq=0x0000
OP_Death=0x0000
OP_GMTraining=0x0000
OP_GMEndTraining=0x0000
OP_GMTrainSkill=0x0000
OP_Animation=0x0000
OP_Begging=0x0000
OP_Consent=0x0000
OP_ConsentDeny=0x0000
OP_AutoFire=0x0000
OP_PetCommands=0x0000
OP_PetCommandState=0x0000
OP_PetHoTT=0x0000
OP_DeleteSpell=0x0000
OP_Surname=0x0000
OP_ClearSurname=0x0000
OP_FaceChange=0x0000
OP_SetFace=0x0000
OP_SenseHeading=0x0000
OP_Action=0x0000
OP_ConsiderCorpse=0x0000
OP_HideCorpse=0x0000
OP_CorpseDrag=0x0000
OP_CorpseDrop=0x0000
OP_Bug=0x0000
OP_Feedback=0x0000
OP_Report=0x0000
OP_Damage=0x0000
OP_ChannelMessage=0x0000
OP_Assist=0x0000
OP_AssistGroup=0x0000
OP_MoveCoin=0x0000
OP_ZonePlayerToBind=0x0000
OP_KeyRing=0x0000
OP_WhoAllRequest=0x0000
OP_WhoAllResponse=0x0000
OP_FriendsWho=0x0000
OP_ConfirmDelete=0x0000
OP_Logout=0x0000
OP_Rewind=0x0000
OP_TargetCommand=0x0000
OP_Hide=0x0000
OP_Jump=0x0000
OP_Camp=0x0000
OP_Emote=0x0000
OP_SetRunMode=0x0000
OP_BankerChange=0x0000
OP_TargetMouse=0x0000
OP_MobHealth=0x0000
OP_InitialMobHealth=0x0000 # Unused?
OP_TargetHoTT=0x0000
OP_TargetBuffs=0x0000
OP_XTargetResponse=0x0000
OP_XTargetRequest=0x0000
OP_XTargetAutoAddHaters=0x0000
OP_XTargetOpen=0x0000
OP_XTargetOpenResponse=0x0000
OP_BuffCreate=0x0000
OP_BuffRemoveRequest=0x0000
OP_DeleteSpawn=0x0000
OP_AutoAttack=0x0000
OP_AutoAttack2=0x0000
OP_Consume=0x0000
OP_MoveItem=0x0000
OP_MoveMultipleItems=0x0000
OP_DeleteItem=0x0000
OP_DeleteCharge=0x0000
OP_ItemPacket=0x0000
OP_ItemLinkResponse=0x0000
OP_ItemLinkClick=0x0000
OP_ItemPreview=0x0000
OP_NewSpawn=0x0000
OP_Track=0x0000
OP_TrackTarget=0x0000
OP_TrackUnknown=0x0000
OP_ClickDoor=0x0000
OP_MoveDoor=0x0000
OP_RemoveAllDoors=0x0000
OP_EnvDamage=0x0000
OP_BoardBoat=0x0000
OP_LeaveBoat=0x0000
OP_ControlBoat=0x0000
OP_Forage=0x0000
OP_SafeFallSuccess=0x0000
OP_RezzComplete=0x0000
OP_RezzRequest=0x0000
OP_RezzAnswer=0x0000
OP_Shielding=0x0000
OP_RequestDuel=0x0000
OP_MobRename=0x0000
OP_AugmentItem=0x0000
OP_WeaponEquip1=0x0000
OP_PlayerStateAdd=0x0000
OP_PlayerStateRemove=0x0000
OP_ApplyPoison=0x0000
OP_Save=0x0000
OP_TestBuff=0x0000
OP_CustomTitles=0x0000
OP_Split=0x0000
OP_YellForHelp=0x0000
OP_LoadSpellSet=0x0000
OP_Bandolier=0x0000
OP_PotionBelt=0x0000
OP_DuelDecline=0x0000
OP_DuelAccept=0x0000
OP_SaveOnZoneReq=0x0000
OP_ReadBook=0x0000
OP_Dye=0x0000
OP_InterruptCast=0x0000
OP_AAAction=0x0000
OP_LeadershipExpToggle=0x0000
OP_LeadershipExpUpdate=0x0000
OP_PurchaseLeadershipAA=0x0000
OP_UpdateLeadershipAA=0x0000
OP_MarkNPC=0x0000
OP_ClearNPCMarks=0x0000
OP_DelegateAbility=0x0000
OP_SetGroupTarget=0x0000
OP_Charm=0x0000
OP_Stun=0x0000
OP_SendFindableNPCs=0x0000
OP_FindPersonRequest=0x0000
OP_FindPersonReply=0x0000
OP_Sound=0x0000
OP_CashReward=0x0000
OP_PetBuffWindow=0x0000
OP_LevelAppearance=0x0000
OP_Translocate=0x0000
OP_Sacrifice=0x0000
OP_PopupResponse=0x0000
OP_OnLevelMessage=0x0000
OP_AugmentInfo=0x0000
OP_Petition=0x0000
OP_SomeItemPacketMaybe=0x0000
OP_PVPStats=0x0000
OP_PVPLeaderBoardRequest=0x0000
OP_PVPLeaderBoardReply=0x0000
OP_PVPLeaderBoardDetailsRequest=0x0000
OP_PVPLeaderBoardDetailsReply=0x0000
OP_RestState=0x0000
OP_RespawnWindow=0x0000
OP_LDoNButton=0x0000
OP_SetStartCity=0x0000
OP_VoiceMacroIn=0x0000
OP_VoiceMacroOut=0x0000
OP_ItemViewUnknown=0x0000
OP_VetRewardsAvaliable=0x0000
OP_VetClaimRequest=0x0000
OP_VetClaimReply=0x0000
OP_DisciplineUpdate=0x0000
OP_DisciplineTimer=0x0000
OP_BecomeCorpse=0x0000 # Unused?
OP_Action2=0x0000 # Unused?
OP_MobUpdate=0x0000
OP_NPCMoveUpdate=0x0000
OP_CameraEffect=0x0000
OP_SpellEffect=0x0000
OP_AddNimbusEffect=0x0000
OP_RemoveNimbusEffect=0x0000
OP_AltCurrency=0x0000
OP_AltCurrencyMerchantRequest=0x0000
OP_AltCurrencyMerchantReply=0x0000
OP_AltCurrencyPurchase=0x0000
OP_AltCurrencySell=0x0000
OP_AltCurrencySellSelection=0x0000
OP_AltCurrencyReclaim=0x0000
OP_CrystalCountUpdate=0x0000
OP_CrystalCreate=0x0000
OP_CrystalReclaim=0x0000
OP_Untargetable=0x0000
OP_IncreaseStats=0x0000
OP_Weblink=0x0000
OP_OpenContainer=0x0000
OP_Marquee=0x0000
OP_ItemRecastDelay=0x0000
#OP_OpenInventory=0x0000 # Likely does not exist in RoF -U
OP_ResetAA=0x0000
OP_Fling=0x0000
OP_CancelSneakHide=0x0000
OP_AggroMeterLockTarget=0x0000
OP_AggroMeterTargetInfo=0x0000
OP_AggroMeterUpdate=0x0000
OP_UnderWorld=0x0000 # clients sends up when they detect an underworld issue, might be useful for cheat detection
OP_KickPlayers=0x0000
OP_BookButton=0x0000
# Expeditions
OP_DzQuit=0x0000
OP_DzListTimers=0x0000
OP_DzAddPlayer=0x0000
OP_DzRemovePlayer=0x0000
OP_DzSwapPlayer=0x0000
OP_DzMakeLeader=0x0000
OP_DzPlayerList=0x0000
OP_DzExpeditionInvite=0x0000
OP_DzExpeditionInviteResponse=0x0000
OP_DzExpeditionInfo=0x0000
OP_DzExpeditionLockoutTimers=0x0000
OP_DzMemberList=0x0000
OP_DzMemberListName=0x0000
OP_DzMemberListStatus=0x0000
OP_DzSetLeaderName=0x0000
OP_DzExpeditionEndsWarning=0x0000
OP_DzCompass=0x0000
OP_DzChooseZone=0x0000
OP_DzChooseZoneReply=0x0000
# New Opcodes
OP_SpawnPositionUpdate=0x0000 # Actually OP_MobUpdate ?
OP_ManaUpdate=0x0000
OP_EnduranceUpdate=0x0000
OP_MobManaUpdate=0x0000
OP_MobEnduranceUpdate=0x0000
# Mercenary Opcodes
OP_MercenaryDataUpdateRequest=0x0000
OP_MercenaryDataUpdate=0x0000
OP_MercenaryDataRequest=0x0000
OP_MercenaryDataResponse=0x0000
OP_MercenaryHire=0x0000
OP_MercenaryDismiss=0x0000
OP_MercenaryTimerRequest=0x0000
OP_MercenaryTimer=0x0000
OP_MercenaryUnknown1=0x0000
OP_MercenaryCommand=0x0000
OP_MercenarySuspendRequest=0x0000
OP_MercenarySuspendResponse=0x0000
OP_MercenaryUnsuspendResponse=0x0000
# Looting
OP_LootRequest=0x0000
OP_EndLootRequest=0x0000
OP_LootItem=0x0000
OP_LootComplete=0x0000
# bazaar trader stuff:
OP_BazaarSearch=0x0000
OP_TraderDelItem=0x0000
OP_BecomeTrader=0x0000
OP_TraderShop=0x0000
OP_TraderBulkSend=0x0000
OP_Trader=0x0000
OP_Barter=0x0000
OP_BuyerItems=0x0000
OP_TraderBuy=0x0000
OP_ShopItem=0x0000
OP_BazaarInspect=0x0000
OP_Bazaar=0x0000
OP_TraderItemUpdate=0x0000
# pc/npc trading
OP_TradeRequest=0x0000
OP_TradeAcceptClick=0x0000
OP_TradeRequestAck=0x0000
OP_TradeCoins=0x0000
OP_FinishTrade=0x0000
OP_CancelTrade=0x0000
OP_TradeMoneyUpdate=0x0000
OP_MoneyUpdate=0x0000
OP_TradeBusy=0x0000
# Sent after canceling trade or after closing tradeskill object
OP_FinishWindow=0x0000
OP_FinishWindow2=0x0000
# Sent on Live for what seems to be item existance verification
# Ex. Before Right Click Effect happens from items
OP_ItemVerifyRequest=0x0000
OP_ItemVerifyReply=0x0000
OP_ItemAdvancedLoreText=0x0000
# merchant stuff
OP_ShopPlayerSell=0x0000
OP_ShopRequest=0x0000
OP_ShopEnd=0x0000
OP_ShopEndConfirm=0x0000
OP_ShopPlayerBuy=0x0000
OP_ShopDelItem=0x0000
OP_ShopSendParcel=0x0000
OP_ShopDeleteParcel=0x0000
OP_ShopRetrieveParcel=0x0000
OP_ShopParcelIcon=0x0000
# tradeskill stuff:
OP_ClickObject=0x0000
OP_ClickObjectAction=0x0000
OP_ClearObject=0x0000
OP_RecipeDetails=0x0000
OP_RecipesFavorite=0x0000
OP_RecipesSearch=0x0000
OP_RecipeReply=0x0000
OP_RecipeAutoCombine=0x0000
OP_TradeSkillCombine=0x0000
# Tribute Packets:
OP_OpenTributeMaster=0x0000
OP_SelectTribute=0x0000
OP_TributeItem=0x0000
OP_TributeMoney=0x0000
OP_TributeToggle=0x0000
OP_TributePointUpdate=0x0000
OP_TributeNPC=0x0000
OP_GuildTributeInfo=0x0000
OP_OpenTributeReply=0x0000
OP_GuildTributeStatus=0x0000
OP_GuildSaveActiveTributes=0x0000
OP_GuildSendActiveTributes=0x0000
OP_GuildTributeToggleReq=0x0000
OP_GuildTributeToggleReply=0x0000
OP_GuildTributeFavorAndTimer=0x0000
OP_GuildTributeDonateItem=0x0000
OP_GuildTributeDonatePlat=0x0000
OP_GuildSelectTribute=0x0000
OP_GuildModifyBenefits=0x0000
OP_GuildOptInOut=0x0000
OP_SendGuildTributes=0x0000
OP_OpenGuildTributeMaster=0x0000
# Adventure packets:
OP_LeaveAdventure=0x0000
OP_AdventureFinish=0x0000
OP_AdventureInfoRequest=0x0000
OP_AdventureInfo=0x0000
OP_AdventureRequest=0x0000
OP_AdventureDetails=0x0000
OP_AdventureData=0x0000
OP_AdventureUpdate=0x0000
OP_AdventureMerchantRequest=0x0000
OP_AdventureMerchantResponse=0x0000
OP_AdventureMerchantPurchase=0x0000
OP_AdventureMerchantSell=0x0000
OP_AdventurePointsUpdate=0x0000
OP_AdventureStatsRequest=0x0000
OP_AdventureStatsReply=0x0000
OP_AdventureLeaderboardRequest=0x0000
OP_AdventureLeaderboardReply=0x0000
# Group Opcodes
OP_GroupDisband=0x0000
OP_GroupInvite=0x0000
OP_GroupFollow=0x0000
OP_GroupUpdate=0x0000
OP_GroupUpdateB=0x0000
OP_GroupCancelInvite=0x0000
OP_GroupAcknowledge=0x0000
OP_GroupDelete=0x0000
OP_CancelInvite=0x0000
OP_GroupFollow2=0x0000
OP_GroupInvite2=0x0000
OP_GroupDisbandYou=0x0000
OP_GroupDisbandOther=0x0000
OP_GroupLeaderChange=0x0000
OP_GroupRoles=0x0000
OP_GroupMakeLeader=0x0000
OP_DoGroupLeadershipAbility=0x0000
OP_GroupLeadershipAAUpdate=0x0000
OP_GroupMentor=0x0000
OP_InspectBuffs=0x0000
# LFG/LFP Opcodes
OP_LFGCommand=0x0000
OP_LFGGetMatchesRequest=0x0000
OP_LFGGetMatchesResponse=0x0000
OP_LFPGetMatchesRequest=0x0000
OP_LFPGetMatchesResponse=0x0000
OP_LFPCommand=0x0000
OP_LFGAppearance=0x0000
OP_LFGResponse=0x0000
# Raid Opcodes
OP_RaidInvite=0x0000
OP_RaidUpdate=0x0000
OP_RaidJoin=0x0000
OP_RaidDelegateAbility=0x0000
OP_MarkRaidNPC=0x0000
OP_RaidClearNPCMarks=0x0000
# Button-push commands
OP_Taunt=0x0000
OP_CombatAbility=0x0000
OP_SenseTraps=0x0000
OP_PickPocket=0x0000
OP_DisarmTraps=0x0000
OP_Disarm=0x0000
OP_Sneak=0x0000
OP_Fishing=0x0000
OP_InstillDoubt=0x0000
OP_FeignDeath=0x0000
OP_Mend=0x0000
OP_Bind_Wound=0x0000
OP_LDoNOpen=0x0000
#OP_LDoNDisarmTraps= #Same as OP_DisarmTraps in RoF
OP_LDoNPickLock=0x0000
OP_LDoNInspect=0x0000
# Task packets
OP_TaskDescription=0x0000
OP_TaskActivity=0x0000
OP_CompletedTasks=0x0000
OP_TaskActivityComplete=0x0000
OP_AcceptNewTask=0x0000
OP_CancelTask=0x0000
OP_AvaliableTask=0x0000
OP_TaskHistoryRequest=0x0000
OP_TaskHistoryReply=0x0000
OP_DeclineAllTasks=0x0000
OP_TaskRequestTimer=0x0000
OP_TaskSelectWindow=0x0000
# Shared Tasks
OP_SharedTaskMemberList=0x0000 #
OP_SharedTaskRemovePlayer=0x0000 # /taskremoveplayer
OP_SharedTaskAddPlayer=0x0000 # /taskaddplayer
OP_SharedTaskMakeLeader=0x0000 # /taskmakeleader
OP_SharedTaskInvite=0x0000 # Dialog window
OP_SharedTaskInviteResponse=0x0000 # Dialog window response
OP_SharedTaskAcceptNew=0x0000 #
OP_SharedTaskMemberChange=0x0000 #
OP_TaskTimers=0x0000 # /tasktimers
OP_SharedTaskQuit=0x0000 # /taskquit
OP_SharedTaskSelectWindow=0x0000
OP_SharedTaskPlayerList=0x0000 # /taskplayerlist
# Title opcodes
OP_NewTitlesAvailable=0x0000
OP_RequestTitles=0x0000
OP_SendTitleList=0x0000
OP_SetTitle=0x0000
OP_SetTitleReply=0x0000
# mail opcodes
OP_Command=0x0000
OP_MailboxHeader=0x0000
OP_MailHeader=0x0000
OP_MailBody=0x0000
OP_NewMail=0x0000
OP_SentConfirm=0x0000
########### Below this point should not be needed ###########
# This section are all unknown in Titanium
OP_ForceFindPerson=0x0000
OP_LocInfo=0x0000
OP_ReloadUI=0x0000
OP_ItemName=0x0000
OP_ItemLinkText=0x0000
OP_MultiLineMsg=0x0000
OP_MendHPUpdate=0x0000
OP_TargetReject=0x0000
OP_SafePoint=0x0000
OP_ApproveZone=0x0000
OP_ZoneComplete=0x0000
OP_ClientError=0x0000
OP_DumpName=0x0000
OP_Heartbeat=0x0000
OP_CrashDump=0x0000
OP_LoginComplete=0x0000
# discovered opcodes not yet used:
OP_PickLockSuccess=0x0000
OP_PlayMP3=0x0000
OP_ReclaimCrystals=0x0000
OP_DynamicWall=0x0000
OP_OpenDiscordMerchant=0x0000
OP_DiscordMerchantInventory=0x0000
OP_GiveMoney=0x0000
OP_RequestKnowledgeBase=0x0000
OP_KnowledgeBase=0x0000
OP_SlashAdventure=0x0000 # /adventure
OP_BecomePVPPrompt=0x0000
OP_MoveLogRequest=0x0000 # gone I think
OP_MoveLogDisregard=0x0000 # gone I think
# named unknowns, to make looking for real unknown easier
OP_AnnoyingZoneUnknown=0x0000
OP_Some6ByteHPUpdate=0x0000 #seems to happen when you target group members
OP_QueryResponseThing=0x0000
# realityincarnate: these are just here to stop annoying several thousand byte packet dumps
#OP_LoginUnknown1=0x0000 # OP_SendSpellChecksum
#OP_LoginUnknown2=0x0000 # OP_SendSkillCapsChecksum
# Petition Opcodes
OP_PetitionSearch=0x0000 #search term for petition
OP_PetitionSearchResults=0x0000 #(list of?) matches from search
OP_PetitionSearchText=0x0000 #text results of search
OP_PetitionUpdate=0x0000
OP_PetitionCheckout=0x0000
OP_PetitionCheckIn=0x0000
OP_PetitionQue=0x0000
OP_PetitionUnCheckout=0x0000
OP_PetitionDelete=0x0000
OP_DeletePetition=0x0000
OP_PetitionResolve=0x0000
OP_PDeletePetition=0x0000
OP_PetitionBug=0x0000
OP_PetitionRefresh=0x0000
OP_PetitionCheckout2=0x0000
OP_PetitionViewPetition=0x0000
# Login opcodes
OP_SessionReady=0x0000
OP_Login=0x0000
OP_ServerListRequest=0x0000
OP_PlayEverquestRequest=0x0000
OP_PlayEverquestResponse=0x0000
OP_ChatMessage=0x0000
OP_LoginAccepted=0x0000
OP_ServerListResponse=0x0000
OP_Poll=0x0000
OP_EnterChat=0x0000
OP_PollResponse=0x0000
# raw opcodes
OP_RAWSessionRequest=0x0000
OP_RAWSessionResponse=0x0000
OP_RAWCombined=0x0000
OP_RAWSessionDisconnect=0x0000
OP_RAWKeepAlive=0x0000
OP_RAWSessionStatRequest=0x0000
OP_RAWSessionStatResponse=0x0000
OP_RAWPacket=0x0000
OP_RAWFragment=0x0000
OP_RAWOutOfOrderAck=0x0000
OP_RAWAck=0x0000
OP_RAWAppCombined=0x0000
OP_RAWOutOfSession=0x0000
# we need to document the differences between these packets to make identifying them easier
OP_Some3ByteHPUpdate=0x0000 # initial HP update for mobs
OP_InitialHPUpdate=0x0000
#aura related
OP_UpdateAura=0x0000
OP_RemoveTrap=0x0000
+1
View File
@@ -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
+388
View File
@@ -0,0 +1,388 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Nuget personal access tokens and Credentials
nuget.config
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
.idea/
*.sln.iml
@@ -0,0 +1,621 @@
using Ionic.Zlib;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace StreamParser.Common.Daybreak
{
public class Connection : IConnection
{
private class EncodeType
{
public const int None = 0;
public const int Compression = 1;
public const int XOR = 4;
}
private enum SequenceOrder
{
Past,
Current,
Future
}
private readonly IParser _owner;
private readonly IPAddress _srcAddr;
private readonly int _srcPort;
private readonly IPAddress _dstAddr;
private readonly int _dstPort;
private readonly Util.Crc32 _crc_generator = new Util.Crc32();
private readonly Guid _id = Guid.NewGuid();
private uint _connect_code = 0;
private int _encode_key = 0;
private int _crc_bytes = 0;
private int[] _encode_pass = new int[2] { 0, 0 };
private ConnectionStream[] _client_streams = new ConnectionStream[4] {
new ConnectionStream(),
new ConnectionStream(),
new ConnectionStream(),
new ConnectionStream()
};
private ConnectionStream[] _server_streams = new ConnectionStream[4] {
new ConnectionStream(),
new ConnectionStream(),
new ConnectionStream(),
new ConnectionStream()
};
public IConnection.OnPacketRecvHandler OnPacketRecv { get; set; }
public IPAddress ClientAddress => _srcAddr;
public int ClientPort => _srcPort;
public IPAddress ServerAddress => _dstAddr;
public int ServerPort => _dstPort;
public Guid Id => _id;
public ConnectionType ConnectionType {
get
{
//World servers used to be coded to always be 9000 but live started using dynamic ports
//I've seen from 9000 to 9008 on live
if (_dstPort >= 9000 && _dstPort <= 9010)
{
return ConnectionType.World;
}
else if (_encode_pass[0] == EncodeType.None && _encode_pass[1] == EncodeType.None)
{
return ConnectionType.Login;
}
else if (_encode_pass[0] == EncodeType.XOR && _encode_pass[1] == EncodeType.None)
{
return ConnectionType.Chat;
}
else if (_encode_pass[0] == EncodeType.Compression && _encode_pass[1] == EncodeType.None)
{
return ConnectionType.Zone;
}
return ConnectionType.Unknown;
}
}
public Connection(IParser owner, IPAddress srcAddr, int srcPort, IPAddress dstAddr, int dstPort)
{
_owner = owner;
_srcAddr = srcAddr;
_srcPort = srcPort;
_dstAddr = dstAddr;
_dstPort = dstPort;
}
public void ProcessPacket(IPAddress srcAddr, int srcPort, DateTime packetTime, ReadOnlySpan<byte> data)
{
if (data.Length < 1)
{
return;
}
var opcode = data[1];
if (data[0] == 0 && (opcode == Opcode.KeepAlive || opcode == Opcode.OutboundPing))
{
return;
}
if (PacketCanBeDecoded(data))
{
if (!ValidateCRC(data))
{
return;
}
if (_encode_pass[0] == EncodeType.None && _encode_pass[1] == EncodeType.None)
{
ProcessDecodedPacket(srcAddr, srcPort, packetTime, data.Slice(0, data.Length - _crc_bytes));
}
else
{
//unfortunately we can't avoid a copy here
var temp = data.Slice(0, data.Length - _crc_bytes).ToArray();
for (int i = 1; i >= 0; --i)
{
switch(_encode_pass[i])
{
case EncodeType.Compression:
if(temp[0] == 0)
{
temp = Decompress(temp, 2, temp.Length - 2);
}
else
{
temp = Decompress(temp, 1, temp.Length - 1);
}
break;
case EncodeType.XOR:
if (temp[0] == 0)
{
temp = Decode(temp, 2, temp.Length - 2);
}
else
{
temp = Decode(temp, 1, temp.Length - 1);
}
break;
}
}
ProcessDecodedPacket(srcAddr, srcPort, packetTime, temp);
}
}
else
{
ProcessDecodedPacket(srcAddr, srcPort, packetTime, data);
}
}
private void ProcessDecodedPacket(IPAddress srcAddr, int srcPort, DateTime packetTime, ReadOnlySpan<byte> data)
{
if (data.Length < 1)
{
return;
}
if (data[0] == 0)
{
if (data.Length < 2)
{
return;
}
var opcode = data[1];
switch (opcode)
{
case Opcode.SessionResponse:
if (_connect_code == 0)
{
if(data.Length != 21)
{
return;
}
_connect_code = BitConverter.ToUInt32(data.Slice(2, 4));
_encode_key = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data.Slice(6, 4)));
_crc_bytes = data[10];
_encode_pass[0] = data[11];
_encode_pass[1] = data[12];
_owner.OnNewConnection?.Invoke(this, packetTime);
}
break;
case Opcode.SessionDisconnect:
if(_connect_code != 0)
{
_connect_code = 0;
_encode_key = 0;
_crc_bytes = 0;
_encode_pass[0] = 0;
_encode_pass[1] = 0;
_owner.OnLostConnection?.Invoke(this, packetTime);
}
break;
case Opcode.Combined:
{
int current = 2;
int end = data.Length;
while (current < end)
{
byte subpacket_length = data[current];
current += 1;
if (end < current + subpacket_length)
{
return;
}
var subpacket = data.Slice(current, subpacket_length);
ProcessDecodedPacket(srcAddr, srcPort, packetTime, subpacket);
current += subpacket_length;
}
}
break;
case Opcode.AppCombined:
{
int current = 2;
int end = data.Length;
while (current < end)
{
int subpacket_length = 0;
if (data[current] == 0xff)
{
if (end < current + 3)
{
return;
}
if (data[current + 1] == 0xff && data[current + 2] == 0xff)
{
if (end < current + 7)
{
return;
}
subpacket_length =
((data[current + 3]) << 24) |
((data[current + 4]) << 16) |
((data[current + 5]) << 8) |
(data[current + 6]);
current += 7;
}
else
{
subpacket_length =
((data[current + 1]) << 8) |
(data[current + 2]);
current += 3;
}
}
else
{
subpacket_length = data[current];
current += 1;
}
var subpacket = data.Slice(current, subpacket_length);
ProcessDecodedPacket(srcAddr, srcPort, packetTime, subpacket);
current += subpacket_length;
}
}
break;
case Opcode.Packet:
case Opcode.Packet2:
case Opcode.Packet3:
case Opcode.Packet4:
{
var stream_id = opcode - Opcode.Packet;
var stream = FindStream(srcAddr, srcPort, stream_id);
var sequence = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data.Slice(2, 2)));
var order = CompareSequence(stream.Sequence, sequence);
if (order == SequenceOrder.Future)
{
if (!stream.PacketQueue.ContainsKey(sequence))
{
stream.PacketQueue.Add(sequence, new ConnectionStream.QueuedPacket
{
Data = data.ToArray(),
PacketTime = packetTime
});
}
}
else if (order == SequenceOrder.Current)
{
if (stream.PacketQueue.ContainsKey(sequence))
{
stream.PacketQueue.Remove(sequence);
}
stream.Sequence++;
ProcessDecodedPacket(srcAddr, srcPort, packetTime, data.Slice(4));
ProcessQueue(srcAddr, srcPort, stream_id);
}
}
break;
case Opcode.Fragment:
case Opcode.Fragment2:
case Opcode.Fragment3:
case Opcode.Fragment4:
{
var stream_id = opcode - Opcode.Fragment;
var stream = FindStream(srcAddr, srcPort, stream_id);
var sequence = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data.Slice(2, 2)));
var order = CompareSequence(stream.Sequence, sequence);
if (order == SequenceOrder.Future)
{
if (!stream.PacketQueue.ContainsKey(sequence))
{
stream.PacketQueue.Add(sequence, new ConnectionStream.QueuedPacket
{
Data = data.ToArray(),
PacketTime = packetTime
});
}
}
else if (order == SequenceOrder.Current)
{
if (stream.PacketQueue.ContainsKey(sequence))
{
stream.PacketQueue.Remove(sequence);
}
stream.Sequence++;
if (stream.TotalFragmentedBytes == 0)
{
stream.TotalFragmentedBytes = (uint)IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data.Slice(4, 4)));
stream.CurrentFragmentedBytes = (uint)(data.Length - 8);
if(stream.FragmentBuffer == null || stream.FragmentBuffer.Length < (stream.TotalFragmentedBytes + 512))
{
stream.FragmentBuffer = new byte[stream.TotalFragmentedBytes + 512];
}
var target = stream.FragmentBuffer.AsSpan();
data.Slice(8).CopyTo(target);
} else
{
var target = stream.FragmentBuffer.AsSpan((int)stream.CurrentFragmentedBytes);
data.Slice(4).CopyTo(target);
stream.CurrentFragmentedBytes += (uint)(data.Length - 4);
if (stream.CurrentFragmentedBytes >= stream.TotalFragmentedBytes)
{
ProcessDecodedPacket(srcAddr, srcPort, packetTime,
stream.FragmentBuffer.AsSpan(0, (int)stream.TotalFragmentedBytes));
stream.CurrentFragmentedBytes = 0;
stream.TotalFragmentedBytes = 0;
}
}
ProcessQueue(srcAddr, srcPort, stream_id);
}
}
break;
case Opcode.Padding:
OnPacketRecv?.Invoke(this, GetDirection(srcAddr, srcPort), packetTime, data.Slice(1));
break;
default:
break;
}
} else
{
OnPacketRecv?.Invoke(this, GetDirection(srcAddr, srcPort), packetTime, data);
}
}
private void ProcessQueue(IPAddress srcAddr, int srcPort, int stream_id)
{
var stream = FindStream(srcAddr, srcPort, stream_id);
var sequence = stream.Sequence;
//try to get the current sequence in the queue, if it exists then process it
ConnectionStream.QueuedPacket value;
if(stream.PacketQueue.TryGetValue(sequence, out value))
{
ProcessDecodedPacket(srcAddr, srcPort, value.PacketTime, value.Data);
}
}
public bool Match(IPAddress srcAddr, int srcPort, IPAddress dstAddr, int dstPort)
{
var p1 = _srcAddr.Equals(srcAddr) && _srcPort == srcPort && _dstAddr.Equals(dstAddr) && _dstPort == dstPort;
var p2 = _srcAddr.Equals(dstAddr) && _srcPort == dstPort && _dstAddr.Equals(srcAddr) && _dstPort == srcPort;
return p1 || p2;
}
private SequenceOrder CompareSequence(ushort expected, ushort actual)
{
int diff = (int)actual - (int)expected;
if (diff == 0)
{
return SequenceOrder.Current;
}
if (diff > 0)
{
if (diff > 10000)
{
return SequenceOrder.Past;
}
return SequenceOrder.Future;
}
if (diff < -10000)
{
return SequenceOrder.Future;
}
return SequenceOrder.Past;
}
private bool PacketCanBeDecoded(ReadOnlySpan<byte> p)
{
if (p.Length < 2)
{
return false;
}
if (p[0] != 0)
{
return true;
}
var opcode = p[1];
if (opcode == Opcode.SessionRequest || opcode == Opcode.SessionResponse || opcode == Opcode.OutOfSession)
{
return false;
}
return true;
}
private bool ValidateCRC(ReadOnlySpan<byte> p)
{
try
{
if (_crc_bytes == 0)
{
return true;
}
int actual = 0;
int calculated = _crc_generator.Calculate(p.Slice(0, p.Length - _crc_bytes), _encode_key);
switch (_crc_bytes)
{
case 2:
actual = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(p.Slice(p.Length - 2, 2))) & 0xFFFF;
calculated = calculated & 0xFFFF;
break;
case 4:
actual = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(p.Slice(p.Length - 4, 4)));
break;
default:
return false;
}
return actual == calculated;
} catch(Exception ex)
{
return false;
}
}
private byte[] Decompress(byte[] p, int offset, int length)
{
if (length < 2)
{
return p;
}
Span<byte> header = p.AsSpan(0, offset);
byte flag = p[offset];
Span<byte> payload = p.AsSpan(offset + 1, length - 1);
if (flag == 0x5a)
{
var pl = payload.ToArray();
var inflated = Inflate(payload.ToArray());
byte[] ret = new byte[offset + inflated.Length];
Array.Copy(p, 0, ret, 0, offset);
Array.Copy(inflated, 0, ret, offset, inflated.Length);
return ret;
}
else if (flag == 0xa5)
{
byte[] ret = new byte[offset + length - 1];
Array.Copy(p, 0, ret, 0, offset);
Array.Copy(p, offset + 1, ret, offset, length - 1);
return ret;
}
else
{
return p;
}
}
private byte[] Inflate(byte[] p)
{
try
{
using (var out_stream = new MemoryStream())
{
using (var in_stream = new MemoryStream(p))
{
var buffer = new byte[512];
using (var zs = new ZlibStream(in_stream, CompressionMode.Decompress))
{
int r = 0;
do
{
r = zs.Read(buffer, 0, 512);
out_stream.Write(buffer, 0, r);
} while (r == 512);
}
}
var ret = out_stream.ToArray();
return ret;
}
}
catch (Exception)
{
return null;
}
}
private byte[] Decode(byte[] p, int offset, int length)
{
int key = _encode_key;
Span<byte> buffer = p.AsSpan(offset, length);
int i = 0;
for (i = 0; i + 4 <= length; i += 4)
{
int pt = BitConverter.ToInt32(buffer.Slice(i)) ^ key;
key = BitConverter.ToInt32(buffer.Slice(i));
if(BitConverter.TryWriteBytes(buffer.Slice(i), pt) == false)
{
throw new Exception("Error writing bytes back in decode.");
}
}
byte kc = (byte)(key & 0xFF);
for (; i < length; i++)
{
buffer[i] = (byte)(buffer[i] ^ kc);
}
return p;
}
private Direction GetDirection(IPAddress srcAddr, int srcPort)
{
if(srcAddr.Equals(_srcAddr) && srcPort == _srcPort)
{
return Direction.ClientToServer;
} else
{
return Direction.ServerToClient;
}
}
private ConnectionStream FindStream(IPAddress srcAddr, int srcPort, int index)
{
if (index < 0 || index > 3)
{
return null;
}
var dir = GetDirection(srcAddr, srcPort);
if(dir == Direction.ClientToServer)
{
return _client_streams[index];
} else
{
return _server_streams[index];
}
}
private class ConnectionStream
{
public class QueuedPacket
{
public byte[] Data { get; set; }
public DateTime PacketTime { get; set; }
}
public ConnectionStream()
{
Sequence = 0;
CurrentFragmentedBytes = 0;
TotalFragmentedBytes = 0;
FragmentBuffer = null;
PacketQueue = new Dictionary<ushort, QueuedPacket>();
}
public ushort Sequence { get; set; }
public uint CurrentFragmentedBytes { get; set; }
public uint TotalFragmentedBytes { get; set; }
public byte[] FragmentBuffer { get; set; }
public Dictionary<ushort, QueuedPacket> PacketQueue { get; set; }
}
}
}
@@ -0,0 +1,114 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StreamParser.Common.Daybreak
{
public ref struct GamePacket
{
private readonly ReadOnlySpan<byte> _data;
public GamePacket(ReadOnlySpan<byte> data)
{
_data = data;
}
public readonly override string ToString()
{
return ToString(16);
}
public readonly string ToString(int columns)
{
int rows = _data.Length / columns;
if (_data.Length % columns != 0)
{
rows += 1;
}
int expected = (10 + columns * 4) * rows;
var sb = new StringBuilder(expected);
for(var i = 0; i < rows; ++i)
{
sb.AppendFormat("{0} |", (i * columns).ToString("X5"));
for(var j = 0; j < columns; ++j)
{
var index = (i * 16) + j;
if (index >= _data.Length)
{
sb.Append(" ");
} else
{
var c = _data[index];
sb.AppendFormat("{0,3}", c.ToString("X2"));
}
}
sb.Append(" | ");
for (var j = 0; j < columns; ++j)
{
var index = (i * 16) + j;
if (index >= _data.Length)
{
sb.Append(" ");
}
else
{
var c = _data[index];
var ch = (char)c;
if (char.IsLetterOrDigit(ch) || char.IsPunctuation(ch) || char.IsSymbol(ch) || (ch == ' '))
{
sb.Append(ch);
}
else
{
sb.Append(".");
}
}
}
sb.AppendLine();
}
return sb.ToString();
}
public readonly string ToModelString(int max_taken, bool hex)
{
int expected = Math.Min(_data.Length, max_taken) * (hex ? 2 : 1);
if(expected <= 0)
{
return string.Empty;
}
var sb = new StringBuilder(expected);
for(var i = 0; i < Math.Min(_data.Length, max_taken); ++i)
{
var c = _data[i];
if(hex)
{
sb.Append(c.ToString("X2"));
} else
{
var ch = (char)c;
if (char.IsLetterOrDigit(ch) || char.IsPunctuation(ch) || char.IsSymbol(ch) || (ch == ' '))
{
sb.Append(ch);
}
else
{
sb.Append(".");
}
}
}
return sb.ToString();
}
}
}
@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace StreamParser.Common.Daybreak
{
public enum Direction
{
ClientToServer,
ServerToClient
}
public enum ConnectionType
{
Unknown,
Login,
World,
Chat,
Zone
}
public interface IConnection
{
delegate void OnPacketRecvHandler(Connection connection, Direction direction, DateTime packetTime, ReadOnlySpan<byte> data);
OnPacketRecvHandler OnPacketRecv { get; set; }
void ProcessPacket(IPAddress srcAddr, int srcPort, DateTime packetTime, ReadOnlySpan<byte> data);
bool Match(IPAddress srcAddr, int srcPort, IPAddress dstAddr, int dstPort);
ConnectionType ConnectionType { get; }
IPAddress ClientAddress { get; }
int ClientPort { get; }
IPAddress ServerAddress { get; }
int ServerPort { get; }
Guid Id { get; }
}
}
@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StreamParser.Common.Daybreak
{
public interface IParser
{
delegate void ConnectionHandler(IConnection connection, DateTime connectionTime);
ConnectionHandler OnNewConnection { get; set; }
ConnectionHandler OnLostConnection { get; set; }
void Parse(string filename);
}
}
@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StreamParser.Common.Daybreak
{
public class Opcode
{
public const byte Padding = 0;
public const byte SessionRequest = 1;
public const byte SessionResponse = 2;
public const byte Combined = 3;
public const byte SessionDisconnect = 5;
public const byte KeepAlive = 6;
public const byte SessionStatRequest = 7;
public const byte SessionStatResponse = 8;
public const byte Packet = 9;
public const byte Packet2 = 10;
public const byte Packet3 = 11;
public const byte Packet4 = 12;
public const byte Fragment = 13;
public const byte Fragment2 = 14;
public const byte Fragment3 = 15;
public const byte Fragment4 = 16;
public const byte OutOfOrderAck = 17;
public const byte OutOfOrderAck2 = 18;
public const byte OutOfOrderAck3 = 19;
public const byte OutOfOrderAck4 = 20;
public const byte Ack = 21;
public const byte Ack2 = 22;
public const byte Ack3 = 23;
public const byte Ack4 = 22;
public const byte AppCombined = 25;
public const byte OutboundPing = 28;
public const byte OutOfSession = 29;
}
}
@@ -0,0 +1,115 @@
using Microsoft.Extensions.Logging;
using SharpPcap;
using SharpPcap.LibPcap;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace StreamParser.Common.Daybreak
{
public class Parser : IParser
{
/**
* Dependencies
*/
private readonly ILogger<Parser> _logger;
private readonly List<IConnection> _connections = new List<IConnection>();
public IParser.ConnectionHandler OnNewConnection { get; set; }
public IParser.ConnectionHandler OnLostConnection { get; set; }
public Parser(ILogger<Parser> logger)
{
_logger = logger;
}
public void Parse(string filename)
{
ICaptureDevice device = null;
try
{
device = new CaptureFileReaderDevice(filename);
device.Open();
device.OnPacketArrival += new PacketArrivalEventHandler(OnPacketCapture);
device.Capture();
device.Close();
} catch(Exception ex)
{
_logger.LogError(ex, "Error reading device capture.");
}
}
private void OnPacketCapture(object sender, PacketCapture capture)
{
var raw = capture.GetPacket();
if (raw.LinkLayerType == PacketDotNet.LinkLayers.Ethernet)
{
var packet = PacketDotNet.Packet.ParsePacket(raw.LinkLayerType, raw.Data);
var ipPacket = packet.Extract<PacketDotNet.IPv4Packet>();
var udpPacket = packet.Extract<PacketDotNet.UdpPacket>();
if (ipPacket != null && udpPacket != null)
{
try
{
ProcessPacket(ipPacket.SourceAddress,
udpPacket.SourcePort,
ipPacket.DestinationAddress,
udpPacket.DestinationPort,
raw.Timeval.Date,
udpPacket.PayloadData);
} catch(Exception ex)
{
_logger.LogError(ex, "Error processing packet");
}
}
}
}
private void ProcessPacket(IPAddress srcAddr, int srcPort, IPAddress dstAddr, int dstPort, DateTime packetTime, ReadOnlySpan<byte> data)
{
if(data.Length < 2)
{
_logger.LogTrace("Tossing packet, {0} was less than minimum packet size", data.Length);
return;
}
var c = FindConnection(srcAddr, srcPort, dstAddr, dstPort);
if(c != null)
{
c.ProcessPacket(srcAddr, srcPort, packetTime, data);
}
else if (data[0] == 0 && data[1] == Opcode.SessionRequest)
{
if(data.Length != 24)
{
_logger.LogTrace("Tossing packet, {0} was not the right size for a SessionRequest", data.Length);
return;
}
c = new Connection(this, srcAddr, srcPort, dstAddr, dstPort);
_connections.Add(c);
c.ProcessPacket(srcAddr, srcPort, packetTime, data);
}
}
private IConnection FindConnection(IPAddress srcAddr, int srcPort, IPAddress dstAddr, int dstPort)
{
foreach (var c in _connections)
{
if (c.Match(srcAddr, srcPort, dstAddr, dstPort))
{
return c;
}
}
return null;
}
}
}
+103
View File
@@ -0,0 +1,103 @@
using System;
namespace StreamParser.Common.Util
{
public class Crc32
{
private uint[] _encodeTable =
{
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA,
0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE,
0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC,
0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940,
0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116,
0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A,
0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818,
0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C,
0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2,
0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086,
0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4,
0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE,
0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252,
0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60,
0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04,
0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A,
0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E,
0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0,
0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6,
0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
};
public int Calculate(ReadOnlySpan<byte> data)
{
uint crc = 0xffffffff;
for (int i = 0; i < data.Length; ++i)
{
crc = ((crc >> 8) & 0x00FFFFFF) ^ _encodeTable[(crc ^ data[i]) & 0x000000FF];
}
return (int)~crc;
}
public int Calculate(ReadOnlySpan<byte> data, int key)
{
uint crc = 0xffffffff;
for (int i = 0; i < 4; ++i)
{
crc = ((crc >> 8) & 0x00FFFFFF) ^ _encodeTable[(crc ^ ((key >> (i * 8)) & 0xff)) & 0x000000FF];
}
for (int i = 0; i < data.Length; ++i)
{
crc = ((crc >> 8) & 0x00FFFFFF) ^ _encodeTable[(crc ^ data[i]) & 0x000000FF];
}
return (int)~crc;
}
}
}
+14
View File
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace>StreamParser.Common</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Iconic.Zlib.Netstandard" Version="1.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
<PackageReference Include="SharpPcap" Version="6.3.0" />
</ItemGroup>
</Project>
+51
View File
@@ -0,0 +1,51 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.6.30114.105
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "stream_parser", "stream_parser\stream_parser.csproj", "{A5662497-4771-4A00-92E7-E7790CEB20E9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "common", "common\common.csproj", "{FC625344-C003-4602-A571-D8811CF5B37A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A5662497-4771-4A00-92E7-E7790CEB20E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A5662497-4771-4A00-92E7-E7790CEB20E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A5662497-4771-4A00-92E7-E7790CEB20E9}.Debug|x64.ActiveCfg = Debug|Any CPU
{A5662497-4771-4A00-92E7-E7790CEB20E9}.Debug|x64.Build.0 = Debug|Any CPU
{A5662497-4771-4A00-92E7-E7790CEB20E9}.Debug|x86.ActiveCfg = Debug|Any CPU
{A5662497-4771-4A00-92E7-E7790CEB20E9}.Debug|x86.Build.0 = Debug|Any CPU
{A5662497-4771-4A00-92E7-E7790CEB20E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A5662497-4771-4A00-92E7-E7790CEB20E9}.Release|Any CPU.Build.0 = Release|Any CPU
{A5662497-4771-4A00-92E7-E7790CEB20E9}.Release|x64.ActiveCfg = Release|Any CPU
{A5662497-4771-4A00-92E7-E7790CEB20E9}.Release|x64.Build.0 = Release|Any CPU
{A5662497-4771-4A00-92E7-E7790CEB20E9}.Release|x86.ActiveCfg = Release|Any CPU
{A5662497-4771-4A00-92E7-E7790CEB20E9}.Release|x86.Build.0 = Release|Any CPU
{FC625344-C003-4602-A571-D8811CF5B37A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FC625344-C003-4602-A571-D8811CF5B37A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FC625344-C003-4602-A571-D8811CF5B37A}.Debug|x64.ActiveCfg = Debug|Any CPU
{FC625344-C003-4602-A571-D8811CF5B37A}.Debug|x64.Build.0 = Debug|Any CPU
{FC625344-C003-4602-A571-D8811CF5B37A}.Debug|x86.ActiveCfg = Debug|Any CPU
{FC625344-C003-4602-A571-D8811CF5B37A}.Debug|x86.Build.0 = Debug|Any CPU
{FC625344-C003-4602-A571-D8811CF5B37A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC625344-C003-4602-A571-D8811CF5B37A}.Release|Any CPU.Build.0 = Release|Any CPU
{FC625344-C003-4602-A571-D8811CF5B37A}.Release|x64.ActiveCfg = Release|Any CPU
{FC625344-C003-4602-A571-D8811CF5B37A}.Release|x64.Build.0 = Release|Any CPU
{FC625344-C003-4602-A571-D8811CF5B37A}.Release|x86.ActiveCfg = Release|Any CPU
{FC625344-C003-4602-A571-D8811CF5B37A}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {89CDF826-F878-4BF4-8583-B9E66FE8B61D}
EndGlobalSection
EndGlobal
@@ -0,0 +1,337 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using StreamParser.Common.Daybreak;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using CommandLine;
using System.Globalization;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Ionic.Zlib;
namespace StreamParser
{
public class ConsoleHostedService : IHostedService
{
private class ParsedPacket {
public byte[] Data { get; set; }
public Direction Direction { get; set; }
public DateTime Time { get; set; }
}
private class ParsedConnection
{
public IPAddress ClientAddress { get; set; }
public int ClientPort { get; set; }
public IPAddress ServerAddress { get; set; }
public int ServerPort { get; set; }
public ConnectionType ConnectionType { get; set; }
public List<ParsedPacket> Packets { get; set; }
public DateTime ConnectedTime { get; set; }
public DateTime? DisconnectedTime { get; set; }
}
private readonly ILogger<ConsoleHostedService> _logger;
private readonly IHostApplicationLifetime _applicationLifetime;
private readonly IParser _parser;
private readonly Dictionary<Guid, ParsedConnection> _connections = new Dictionary<Guid, ParsedConnection>();
public ConsoleHostedService(ILogger<ConsoleHostedService> logger,
IHostApplicationLifetime applicationLifetime,
IParser parser)
{
_logger = logger;
_applicationLifetime = applicationLifetime;
_parser = parser;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_applicationLifetime.ApplicationStarted.Register(() =>
{
var args = Environment.GetCommandLineArgs();
try
{
CommandLine.Parser.Default.ParseArguments<ConsoleHostedServiceOptions>(args)
.WithParsed<ConsoleHostedServiceOptions>(o =>
{
_parser.OnNewConnection += OnNewConnection;
_parser.OnLostConnection += OnLostConnection;
foreach (var f in o.Input)
{
_logger.LogInformation("Parsing {0}...", f);
_parser.Parse(f);
}
foreach (var c in _connections)
{
if (c.Value.ConnectionType == ConnectionType.Unknown && !o.DumpUnknownStreams)
{
continue;
}
if (o.Text)
{
DumpConnectionToTextFile(c.Value, o.Output, o.Decrypt, o.DecompressOpcodes);
}
}
_applicationLifetime.StopApplication();
})
.WithNotParsed<ConsoleHostedServiceOptions>(e =>
{
bool stops_processing = false;
foreach (var err in e)
{
stops_processing = stops_processing || err.StopsProcessing || err is MissingRequiredOptionError;
}
if (stops_processing)
{
_applicationLifetime.StopApplication();
}
});
}
catch(Exception ex)
{
_logger.LogError(ex, "Error parsing command line arguments");
_applicationLifetime.StopApplication();
}
});
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
private void OnNewConnection(IConnection connection, DateTime connectionTime)
{
_logger.LogTrace("New connection {0}:{1} <-> {2}:{3} of type {4}",
connection.ClientAddress,
connection.ClientPort,
connection.ServerAddress,
connection.ServerPort,
connection.ConnectionType);
connection.OnPacketRecv += OnPacketRecv;
_connections.Add(connection.Id, new ParsedConnection
{
ClientAddress = connection.ClientAddress,
ClientPort = connection.ClientPort,
ServerAddress = connection.ServerAddress,
ServerPort = connection.ServerPort,
ConnectionType = connection.ConnectionType,
Packets = new List<ParsedPacket>(),
ConnectedTime = connectionTime,
});
}
private void OnLostConnection(IConnection connection, DateTime connectionTime)
{
_logger.LogTrace("Lost connection {0}:{1} <-> {2}:{3}",
connection.ClientAddress,
connection.ClientPort,
connection.ServerAddress,
connection.ServerPort);
connection.OnPacketRecv -= OnPacketRecv;
var parsedConnection = _connections.GetValueOrDefault(connection.Id);
parsedConnection.DisconnectedTime = connectionTime;
}
private void OnPacketRecv(IConnection connection, Direction direction, DateTime packetTime, ReadOnlySpan<byte> data)
{
var parsedConnection = _connections.GetValueOrDefault(connection.Id);
parsedConnection.Packets.Add(new ParsedPacket
{
Data = data.ToArray(),
Direction = direction,
Time = packetTime
});
}
private void DumpConnectionToTextFile(ParsedConnection c, string output, bool decrypt, IEnumerable<int> decompressOpcodes)
{
try
{
var path = output + string.Format("{0}-{1}.txt", c.ConnectionType.ToString().ToLower(), c.ConnectedTime.ToString("yyyyMMddHHmmssfff"));
if (File.Exists(path))
{
File.Delete(path);
}
File.AppendAllText(path, string.Format("### type: {0}\n", c.ConnectionType));
File.AppendAllText(path, string.Format("### started: {0}\n", c.ConnectedTime.ToString("s")));
File.AppendAllText(path, string.Format("### ended: {0}\n", c.DisconnectedTime.HasValue ? c.DisconnectedTime.Value.ToString("s") : "unknown"));
File.AppendAllText(path, string.Format("### client: {0}:{1}\n", c.ClientAddress.ToString(), c.ClientPort));
File.AppendAllText(path, string.Format("### server: {0}:{1}\n\n", c.ServerAddress.ToString(), c.ServerPort));
foreach(var p in c.Packets)
{
ReadOnlySpan<byte> data = p.Data;
string dir = p.Direction == Direction.ClientToServer ? "Client -> Server" : "Server -> Client";
switch (c.ConnectionType)
{
case ConnectionType.Login:
{
int opcode = BitConverter.ToUInt16(data.Slice(0, 2));
{
File.AppendAllText(path,
string.Format("{0} [Opcode: 0x{1}, Size: {2}] ({3})\n", dir, opcode.ToString("X4"), data.Length - 2, p.Time.ToString("s")));
var gp = new GamePacket(data.Slice(2));
File.AppendAllText(path, string.Format("{0}\n", gp.ToString()));
if(decrypt && opcode == 2 || opcode == 24)
{
var encrypted_block = data.Slice(12, data.Length - 12);
var dec = EQDecrypt(encrypted_block);
if(dec != null)
{
File.AppendAllText(path, string.Format("[Decrypted Data, Offset: {0}, Size: {1}]\n", 10, dec.Length));
gp = new GamePacket(dec);
File.AppendAllText(path, string.Format("{0}\n", gp.ToString()));
}
}
}
}
break;
case ConnectionType.Chat:
{
int opcode = data[0];
File.AppendAllText(path,
string.Format("{0} [Opcode: 0x{1}, Size: {2}] ({3})\n", dir, opcode.ToString("X2"), data.Length - 1, p.Time.ToString("s")));
var gp = new GamePacket(data.Slice(1));
File.AppendAllText(path, string.Format("{0}\n", gp.ToString()));
}
break;
default:
{
bool reported_decompressed = false;
int opcode = BitConverter.ToUInt16(data.Slice(0, 2));
foreach (var decompressOpcode in decompressOpcodes)
{
if (opcode == decompressOpcode && data.Length > 12)
{
if (data[10] == 0x78 && data[11] == 0xDA)
{
var totalLen = BitConverter.ToInt32(data.Slice(6, 4));
if (totalLen > 0)
{
var decompressed = Inflate(data.Slice(10));
if(decompressed != null)
{
var decompressed_gp = new GamePacket(decompressed);
File.AppendAllText(path,
string.Format("{0} [Opcode: 0x{1}, Size (decompressed): {2}] ({3})\n", dir, opcode.ToString("X4"), totalLen, p.Time.ToString("s")));
File.AppendAllText(path, string.Format("{0}\n", decompressed_gp.ToString()));
reported_decompressed = true;
break;
}
}
}
else if (data[6] == 0x78 && data[7] == 0xDA)
{
var totalLen = BitConverter.ToInt32(data.Slice(2, 4));
if (totalLen > 0)
{
var decompressed = Inflate(data.Slice(6));
if (decompressed != null)
{
File.AppendAllText(path,
string.Format("{0} [Opcode: 0x{1}, Size (decompressed): {2}] ({3})\n", dir, opcode.ToString("X4"), totalLen, p.Time.ToString("s")));
var decompressed_gp = new GamePacket(decompressed);
File.AppendAllText(path, string.Format("{0}\n", decompressed_gp.ToString()));
reported_decompressed = true;
break;
}
}
}
}
}
if (!reported_decompressed)
{
File.AppendAllText(path,
string.Format("{0} [Opcode: 0x{1}, Size: {2}] ({3})\n", dir, opcode.ToString("X4"), data.Length - 2, p.Time.ToString("s")));
var gp = new GamePacket(data.Slice(2));
File.AppendAllText(path, string.Format("{0}\n", gp.ToString()));
}
}
break;
}
}
}
catch(Exception ex)
{
_logger.LogError(ex, "Error dumping connection {0} to txt file", c.ConnectedTime.ToString("s"));
}
}
private byte[] Inflate(ReadOnlySpan<byte> data)
{
try
{
using (var out_stream = new MemoryStream())
using (var in_stream = new MemoryStream(data.ToArray()))
{
const int bufferLen = 4096;
var buffer = new byte[bufferLen];
using (var zs = new ZlibStream(in_stream, CompressionMode.Decompress))
{
int r = 0;
do
{
r = zs.Read(buffer, 0, bufferLen);
out_stream.Write(buffer, 0, r);
} while (r == bufferLen);
}
var ret = out_stream.ToArray();
return ret;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error inflating data");
return null;
}
}
private byte[] EQDecrypt(ReadOnlySpan<byte> data)
{
try
{
var desEngine = new DesEngine();
var cbcBlockCipher = new CbcBlockCipher(desEngine);
var bufferedBlockCipher = new BufferedBlockCipher(cbcBlockCipher);
bufferedBlockCipher.Init(false, new ParametersWithIV(new KeyParameter(new byte[16]), new byte[8]));
var cipherData = new byte[bufferedBlockCipher.GetOutputSize(data.Length)];
var outputLength = bufferedBlockCipher.ProcessBytes(data.ToArray(), 0, data.Length, cipherData, 0);
bufferedBlockCipher.DoFinal(cipherData, outputLength);
return cipherData;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error decrypting EQ Datablock");
return null;
}
}
}
}
@@ -0,0 +1,29 @@
using System.Collections.Generic;
using CommandLine;
namespace StreamParser
{
public class ConsoleHostedServiceOptions
{
[Option("input", Required = true, HelpText = "Input pcap files to be processed.")]
public IEnumerable<string> Input { get; set; }
[Option("output", Default = "output/", HelpText = "Directory to put output files")]
public string Output { get; set; }
[Option("text", Default = true, HelpText = "Dump connections to text files.")]
public bool Text { get; set; }
[Option("binary", Default = false, HelpText = "Dump connections to binary files.")]
public bool Binary { get; set; }
[Option("decrypt", Default = false, HelpText = "Decrypt the \"Encrypted\" packets.")]
public bool Decrypt { get; set; }
[Option("decompress", Default = null, HelpText = "Which opcodes to attempt to decompress")]
public IEnumerable<int> DecompressOpcodes { get; set; }
[Option("enable-unknown-streams", Default = false, HelpText = "Enable dumping of unknown streams; they will be skipped otherwise.")]
public bool DumpUnknownStreams { get; set; }
}
}
@@ -0,0 +1,25 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using StreamParser.Common.Daybreak;
using System;
namespace StreamParser
{
class Program
{
static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddScoped<IParser, Parser>();
services.AddHostedService<ConsoleHostedService>();
});
}
}
}
@@ -0,0 +1,9 @@
{
"profiles": {
"stream_parser": {
"commandName": "Project",
"commandLineArgs": "--input input/cap_login_to_zone_10_16_2024.pcap --output output_test/ --binary --decrypt --decompress 16742 168 30346",
"workingDirectory": "E:\\Projects\\stream_parser\\stream_parser\\bin\\Debug\\net6.0\\"
}
}
}
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>StreamParser</RootNamespace>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="CsvHelper" Version="33.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\common\common.csproj" />
</ItemGroup>
</Project>
+1
View File
@@ -287,6 +287,7 @@ void Adventure::Finished(AdventureWinStatus ws)
auto character_id = database.GetCharacterID(*iter);
if (character_id == 0) {
++iter;
continue;
}
+26 -8
View File
@@ -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(
+4
View File
@@ -289,6 +289,10 @@ 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 (!content_db.LoadItems(hotfix_name)) {
LogError("Error: Could not load item data. But ignoring");
+31 -1
View File
@@ -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);
+3
View File
@@ -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
View File
@@ -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;
+90 -2
View File
@@ -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(),
@@ -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

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