Compare commits

..

76 Commits

Author SHA1 Message Date
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
143 changed files with 8385 additions and 2378 deletions
+149
View File
@@ -1,3 +1,152 @@
## [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 ## [22.53.1] 6/16/2024
### Fixes ### Fixes
+40 -141
View File
@@ -29,7 +29,10 @@
#include "../../common/content/world_content_service.h" #include "../../common/content/world_content_service.h"
#include "../../common/zone_store.h" #include "../../common/zone_store.h"
#include "../../common/path_manager.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/skill_caps_repository.h"
#include "../../common/repositories/spells_new_repository.h"
#include "../../common/file.h" #include "../../common/file.h"
#include "../../common/events/player_event_logs.h" #include "../../common/events/player_event_logs.h"
#include "../../common/skill_caps.h" #include "../../common/skill_caps.h"
@@ -99,25 +102,22 @@ int main(int argc, char **argv)
->LoadLogDatabaseSettings() ->LoadLogDatabaseSettings()
->StartFileLogs(); ->StartFileLogs();
std::string arg_1; std::string export_type;
if (argv[1]) { if (argv[1]) {
arg_1 = argv[1]; export_type = argv[1];
} }
if (arg_1 == "spells") { if (Strings::EqualFold(export_type, "spells")) {
ExportSpells(&content_db); ExportSpells(&content_db);
return 0; return 0;
} } else if (Strings::EqualFold(export_type, "skills")) {
if (arg_1 == "skills") {
ExportSkillCaps(&content_db); ExportSkillCaps(&content_db);
return 0; return 0;
} } else if (Strings::EqualFold(export_type, "basedata") || Strings::EqualFold(export_type, "base_data")) {
if (arg_1 == "basedata") {
ExportBaseData(&content_db); ExportBaseData(&content_db);
return 0; return 0;
} } else if (Strings::EqualFold(export_type, "dbstr") || Strings::EqualFold(export_type, "dbstring")) {
if (arg_1 == "dbstring") {
ExportDBStrings(&database); ExportDBStrings(&database);
return 0; return 0;
} }
@@ -134,178 +134,77 @@ int main(int argc, char **argv)
void ExportSpells(SharedDatabase* db) void ExportSpells(SharedDatabase* db)
{ {
LogInfo("Exporting Spells"); std::ofstream file(fmt::format("{}/export/spells_us.txt", path.GetServerPath()));
if (!file || !file.is_open()) {
std::string file = fmt::format("{}/export/spells_us.txt", path.GetServerPath());
FILE *f = fopen(file.c_str(), "w");
if (!f) {
LogError("Unable to open export/spells_us.txt to write, skipping."); LogError("Unable to open export/spells_us.txt to write, skipping.");
return; return;
} }
const std::string query = "SELECT * FROM spells_new ORDER BY id"; const auto& lines = SpellsNewRepository::GetSpellFileLines(*db);
auto results = db->QueryDatabase(query);
if (results.Success()) { const std::string& file_string = Strings::Implode("\n", lines);
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('^');
}
if (row[i] != nullptr) { file << file_string;
line += row[i];
}
}
fprintf(f, "%s\n", line.c_str()); file.close();
}
}
else {
}
fclose(f); LogInfo("Exported [{}] Spell{}", lines.size(), lines.size() != 1 ? "s" : "");
}
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;
} }
void ExportSkillCaps(SharedDatabase* db) void ExportSkillCaps(SharedDatabase* db)
{ {
LogInfo("Exporting Skill Caps");
std::ofstream file(fmt::format("{}/export/SkillCaps.txt", path.GetServerPath())); std::ofstream file(fmt::format("{}/export/SkillCaps.txt", path.GetServerPath()));
if (!file || !file.is_open()) { if (!file || !file.is_open()) {
LogError("Unable to open export/SkillCaps.txt to write, skipping."); LogError("Unable to open export/SkillCaps.txt to write, skipping.");
return; return;
} }
for (uint8 class_id = Class::Warrior; class_id <= Class::Berserker; class_id++) { const auto& lines = SkillCapsRepository::GetSkillCapFileLines(*db);
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;
for ( const std::string& file_string = Strings::Implode("\n", lines);
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;
}
file << fmt::format("{}^{}^{}^{}^0", class_id, skill_id, level, cap) << std::endl; file << file_string;
previous_cap = cap;
}
}
}
}
file.close(); file.close();
LogInfo("Exported [{}] Skill Cap{}", lines.size(), lines.size() != 1 ? "s" : "");
} }
void ExportBaseData(SharedDatabase *db) void ExportBaseData(SharedDatabase *db)
{ {
LogInfo("Exporting Base Data"); std::ofstream file(fmt::format("{}/export/BaseData.txt", path.GetServerPath()));
if (!file || !file.is_open()) {
std::string file = fmt::format("{}/export/BaseData.txt", path.GetServerPath());
FILE *f = fopen(file.c_str(), "w");
if (!f) {
LogError("Unable to open export/BaseData.txt to write, skipping."); LogError("Unable to open export/BaseData.txt to write, skipping.");
return; return;
} }
const std::string query = "SELECT * FROM base_data ORDER BY level, class"; const auto& lines = BaseDataRepository::GetBaseDataFileLines(*db);
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('^');
}
if (row[rowIndex] != nullptr) { const std::string& file_string = Strings::Implode("\n", lines);
line += row[rowIndex];
}
}
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) void ExportDBStrings(SharedDatabase *db)
{ {
LogInfo("Exporting DB Strings"); std::ofstream file(fmt::format("{}/export/dbstr_us.txt", path.GetServerPath()));
if (!file || !file.is_open()) {
std::string file = fmt::format("{}/export/dbstr_us.txt", path.GetServerPath());
FILE *f = fopen(file.c_str(), "w");
if (!f) {
LogError("Unable to open export/dbstr_us.txt to write, skipping."); LogError("Unable to open export/dbstr_us.txt to write, skipping.");
return; return;
} }
fprintf(f, "Major^Minor^String(New)\n"); const auto& lines = DbStrRepository::GetDBStrFileLines(*db);
const std::string query = "SELECT * FROM db_str ORDER BY id, type";
auto results = db->QueryDatabase(query); const std::string& file_string = Strings::Implode("\n", lines);
if (results.Success()) {
for (auto row = results.begin(); row != results.end(); ++row) { file << file_string;
std::string line;
unsigned int fields = results.ColumnCount(); file.close();
for (unsigned int rowIndex = 0; rowIndex < fields; ++rowIndex) {
if (rowIndex != 0) { LogInfo("Exported [{}] Database String{}", lines.size(), lines.size() != 1 ? "s" : "");
line.push_back('^');
}
if (row[rowIndex] != nullptr) {
line += row[rowIndex];
}
}
fprintf(f, "%s\n", line.c_str());
}
}
fclose(f);
} }
+3 -1
View File
@@ -158,6 +158,7 @@ SET(repositories
repositories/base/base_bugs_repository.h repositories/base/base_bugs_repository.h
repositories/base/base_bug_reports_repository.h repositories/base/base_bug_reports_repository.h
repositories/base/base_buyer_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_activities_repository.h
repositories/base/base_character_alternate_abilities_repository.h repositories/base/base_character_alternate_abilities_repository.h
repositories/base/base_character_alt_currency_repository.h repositories/base/base_character_alt_currency_repository.h
@@ -339,7 +340,8 @@ SET(repositories
repositories/books_repository.h repositories/books_repository.h
repositories/bugs_repository.h repositories/bugs_repository.h
repositories/bug_reports_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_activities_repository.h
repositories/character_alternate_abilities_repository.h repositories/character_alternate_abilities_repository.h
repositories/character_alt_currency_repository.h repositories/character_alt_currency_repository.h
+1 -1
View File
@@ -47,7 +47,7 @@ Bazaar::GetSearchResults(
search_criteria_trader.append(fmt::format(" AND trader.char_id = {}", search.trader_id)); search_criteria_trader.append(fmt::format(" AND trader.char_id = {}", search.trader_id));
} }
if (search.min_cost != 0) { 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) { if (search.max_cost != 0) {
search_criteria_trader.append(fmt::format(" AND trader.item_cost <= {}", (uint64) search.max_cost * 1000)); search_criteria_trader.append(fmt::format(" AND trader.item_cost <= {}", (uint64) search.max_cost * 1000));
+65 -85
View File
@@ -6,6 +6,7 @@
#include "../rulesys.h" #include "../rulesys.h"
#include "../eqemu_logsys.h" #include "../eqemu_logsys.h"
#include "../repositories/instance_list_repository.h" #include "../repositories/instance_list_repository.h"
#include "../zone_store.h"
WorldContentService::WorldContentService() WorldContentService::WorldContentService()
@@ -183,8 +184,8 @@ void WorldContentService::ReloadContentFlags()
} }
SetContentFlags(set_content_flags); SetContentFlags(set_content_flags);
LoadZones();
LoadStaticGlobalZoneInstances(); LoadStaticGlobalZoneInstances();
zone_store.LoadZones(*m_content_database);
} }
Database *WorldContentService::GetDatabase() const Database *WorldContentService::GetDatabase() const
@@ -236,18 +237,6 @@ void WorldContentService::SetContentFlag(const std::string &content_flag_name, b
ReloadContentFlags(); 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) void WorldContentService::HandleZoneRoutingMiddleware(ZoneChange_Struct *zc)
{ {
auto r = FindZone(zc->zoneID, zc->instanceID); auto r = FindZone(zc->zoneID, zc->instanceID);
@@ -263,63 +252,59 @@ void WorldContentService::HandleZoneRoutingMiddleware(ZoneChange_Struct *zc)
// these are used commonly in v1/v2/v3 versions of the same zone for expansion routing // 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(),
LogInfo("Loaded [{}] zone_instances", m_zone_instances.size()); fmt::format("never_expires = 1 AND is_global = 1")
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
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 (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 LogInfo("Loaded [{}] zone_instances", m_zone_static_instances.size());
for (auto &i: m_zone_instances) {
if (i.zone == zone_id && i.version == z.version) { return this;
}
// 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)
{
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) {
if (instance_id > 0 && i.id != instance_id) {
continue;
}
LogInfo( LogInfo(
"Routed player to instance [{}] of zone [{}] ({}) version [{}] long_name [{}] notes [{}]", "Routed player to public static instance [{}] of zone [{}] ({}) version [{}] long_name [{}] notes [{}]",
i.id, i.id,
z.short_name, z.short_name,
z.zoneidnumber, z.zoneidnumber,
@@ -335,23 +320,6 @@ WorldContentService::FindZoneResult WorldContentService::FindZone(uint32 zone_id
}; };
} }
} }
LogInfo(
"Routed player to non-instance zone [{}] ({}) version [{}] long_name [{}] notes [{}]",
z.short_name,
z.zoneidnumber,
z.version,
z.long_name,
z.note
);
return WorldContentService::FindZoneResult{
.zone_id = static_cast<uint32>(z.zoneidnumber),
.instance = InstanceListRepository::NewEntity(),
.zone = z
};
}
}
} }
return WorldContentService::FindZoneResult{.zone_id = 0}; return WorldContentService::FindZoneResult{.zone_id = 0};
@@ -359,7 +327,7 @@ WorldContentService::FindZoneResult WorldContentService::FindZone(uint32 zone_id
bool WorldContentService::IsInPublicStaticInstance(uint32 instance_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) { if (i.id == instance_id) {
return true; return true;
} }
@@ -367,3 +335,15 @@ bool WorldContentService::IsInPublicStaticInstance(uint32 instance_id)
return false; 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(); WorldContentService * SetExpansionContext();
bool DoesPassContentFiltering(const ContentFlags& f); bool DoesPassContentFiltering(const ContentFlags& f);
bool DoesZonePassContentFiltering(const ZoneRepository::Zone& z);
WorldContentService * SetDatabase(Database *database); WorldContentService * SetDatabase(Database *database);
Database *GetDatabase() const; Database *GetDatabase() const;
@@ -189,10 +190,8 @@ private:
Database *m_content_database; Database *m_content_database;
// holds a record of the zone table from the database // holds a record of the zone table from the database
std::vector<ZoneRepository::Zone> m_zones = {};
WorldContentService *LoadStaticGlobalZoneInstances(); WorldContentService *LoadStaticGlobalZoneInstances();
std::vector<InstanceListRepository::InstanceList> m_zone_instances; std::vector<InstanceListRepository::InstanceList> m_zone_static_instances;
WorldContentService * LoadZones();
}; };
extern WorldContentService content_service; extern WorldContentService content_service;
+48 -11
View File
@@ -66,6 +66,7 @@
#endif #endif
#include "database.h" #include "database.h"
#include "data_verification.h"
#include "eq_packet_structs.h" #include "eq_packet_structs.h"
#include "extprofile.h" #include "extprofile.h"
#include "strings.h" #include "strings.h"
@@ -78,6 +79,7 @@
#include "repositories/merchantlist_temp_repository.h" #include "repositories/merchantlist_temp_repository.h"
#include "repositories/bot_data_repository.h" #include "repositories/bot_data_repository.h"
#include "repositories/trader_repository.h" #include "repositories/trader_repository.h"
#include "repositories/buyer_repository.h"
extern Client client; extern Client client;
@@ -283,16 +285,31 @@ bool Database::SetAccountStatus(const std::string& account_name, int16 status)
bool Database::ReserveName(uint32 account_id, const std::string& name) bool Database::ReserveName(uint32 account_id, const std::string& name)
{ {
const auto& l = CharacterDataRepository::GetWhere( const std::string& where_filter = fmt::format(
*this,
fmt::format(
"`name` = '{}'", "`name` = '{}'",
Strings::Escape(name) Strings::Escape(name)
)
); );
if (!l.empty()) { if (RuleB(Bots, Enabled)) {
LogInfo("Account: [{}] tried to request name: [{}], but it is already taken", account_id, name); 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; return false;
} }
@@ -307,13 +324,15 @@ bool Database::ReserveName(uint32 account_id, const std::string& name)
return false; 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 (guild_id != 0) {
if (e.id) { if (e.id) {
auto g = GuildMembersRepository::NewEntity(); auto g = GuildMembersRepository::NewEntity();
g.char_id = e.id; g.char_id = e.id;
g.guild_id = guild_id; g.guild_id = guild_id;
g.rank_ = guild_rank;
GuildMembersRepository::InsertOne(*this, g); GuildMembersRepository::InsertOne(*this, g);
} }
@@ -1629,16 +1648,29 @@ uint32 Database::GetGuildIDByCharID(uint32 character_id)
uint32 Database::GetGroupIDByCharID(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) 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() int64 Database::CountInvSnapshots()
@@ -2110,3 +2142,8 @@ void Database::ClearTraderDetails()
{ {
TraderRepository::Truncate(*this); TraderRepository::Truncate(*this);
} }
void Database::ClearBuyerDetails()
{
BuyerRepository::DeleteBuyer(*this, 0);
}
+1
View File
@@ -245,6 +245,7 @@ public:
void PurgeAllDeletedDataBuckets(); void PurgeAllDeletedDataBuckets();
void ClearGuildOnlineStatus(); void ClearGuildOnlineStatus();
void ClearTraderDetails(); void ClearTraderDetails();
void ClearBuyerDetails();
/* Database Variables */ /* Database Variables */
+87 -1
View File
@@ -4947,7 +4947,7 @@ UPDATE `aa_ability` SET `auto_grant_enabled` = 1 WHERE `grant_only` = 0 AND `cha
.version = 9237, .version = 9237,
.description = "2023_10_15_import_13th_floor.sql", .description = "2023_10_15_import_13th_floor.sql",
.check = "SHOW COLUMNS FROM `items` LIKE 'bardeffect';", .check = "SHOW COLUMNS FROM `items` LIKE 'bardeffect';",
.condition = "contains", .condition = "missing",
.match = "mediumint", .match = "mediumint",
.sql = R"( .sql = R"(
ALTER TABLE `items` ALTER TABLE `items`
@@ -5660,6 +5660,92 @@ ALTER TABLE `trader`
DROP PRIMARY KEY, DROP PRIMARY KEY,
ADD PRIMARY KEY (`id`), ADD PRIMARY KEY (`id`),
ADD INDEX `charid_slotid` (`char_id`, `slot_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`;
)" )"
} }
// -- template; copy/paste this when you need to create a new entry // -- 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"( .sql = R"(
ALTER TABLE `bot_data` ALTER TABLE `bot_data`
ADD COLUMN `extra_haste` mediumint(8) NOT NULL DEFAULT 0 AFTER `wis`; 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 // -- template; copy/paste this when you need to create a new entry
+5 -1
View File
@@ -36,7 +36,6 @@ namespace DatabaseSchema {
{ {
return { return {
{"adventure_stats", "player_id"}, {"adventure_stats", "player_id"},
{"buyer", "charid"},
{"char_recipe_list", "char_id"}, {"char_recipe_list", "char_id"},
{"character_activities", "charid"}, {"character_activities", "charid"},
{"character_alt_currency", "char_id"}, {"character_alt_currency", "char_id"},
@@ -107,6 +106,8 @@ namespace DatabaseSchema {
"adventure_details", "adventure_details",
"adventure_stats", "adventure_stats",
"buyer", "buyer",
"buyer_buy_lines",
"buyer_trade_items",
"char_recipe_list", "char_recipe_list",
"character_activities", "character_activities",
"character_alt_currency", "character_alt_currency",
@@ -325,6 +326,9 @@ namespace DatabaseSchema {
"banned_ips", "banned_ips",
"bug_reports", "bug_reports",
"bugs", "bugs",
"buyer",
"buyer_buy_lines",
"buyer_trade_items",
"completed_shared_task_activity_state", "completed_shared_task_activity_state",
"completed_shared_task_members", "completed_shared_task_members",
"completed_shared_tasks", "completed_shared_tasks",
+9 -29
View File
@@ -80,39 +80,19 @@ bool Bug::IsValid(uint32 category_id)
return bug_category_names.find(category_id) != bug_category_names.end(); return bug_category_names.find(category_id) != bug_category_names.end();
} }
const char *EQ::constants::GetStanceName(StanceType stance_type) { std::string Stance::GetName(uint8 stance_id)
switch (stance_type) { {
case stanceUnknown: return IsValid(stance_id) ? stance_names[stance_id] : "UNKNOWN STANCE";
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";
}
} }
int EQ::constants::ConvertStanceTypeToIndex(StanceType stance_type) { bool Stance::IsValid(uint8 stance_id)
if (EQ::ValueWithin(stance_type, EQ::constants::stancePassive, EQ::constants::stanceBurnAE)) { {
return (stance_type - EQ::constants::stancePassive); 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() const std::map<uint8, std::string>& EQ::constants::GetLanguageMap()
+36 -21
View File
@@ -274,19 +274,6 @@ namespace EQ
const size_t SAY_LINK_CLOSER_SIZE = 1; 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); 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 { enum BotSpellIDs : int {
Warrior = 3001, Warrior = 3001,
Cleric, Cleric,
@@ -362,9 +349,6 @@ namespace EQ
Proximity Proximity
}; };
const char *GetStanceName(StanceType stance_type);
int ConvertStanceTypeToIndex(StanceType stance_type);
extern const std::map<uint8, std::string>& GetLanguageMap(); extern const std::map<uint8, std::string>& GetLanguageMap();
std::string GetLanguageName(uint8 language_id); std::string GetLanguageName(uint8 language_id);
@@ -401,10 +385,6 @@ namespace EQ
extern const std::map<uint32, std::string>& GetConsiderColorMap(); extern const std::map<uint32, std::string>& GetConsiderColorMap();
std::string GetConsiderColorName(uint32 consider_color); 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*/ } /*constants*/
namespace profile { namespace profile {
@@ -471,7 +451,7 @@ namespace EQ
Raid, Raid,
Guild Guild
}; };
}; // namespace consent };
} /*EQEmu*/ } /*EQEmu*/
enum ServerLockType : int { enum ServerLockType : int {
@@ -741,4 +721,39 @@ static std::map<uint32, std::string> bug_category_names = {
{ Bug::Category::Mercenaries, "Mercenaries" } { 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;
}
#endif /*COMMON_EMU_CONSTANTS_H*/ #endif /*COMMON_EMU_CONSTANTS_H*/
+1
View File
@@ -67,6 +67,7 @@ N(OP_Buff),
N(OP_BuffCreate), N(OP_BuffCreate),
N(OP_BuffRemoveRequest), N(OP_BuffRemoveRequest),
N(OP_Bug), N(OP_Bug),
N(OP_BuyerItems),
N(OP_CameraEffect), N(OP_CameraEffect),
N(OP_Camp), N(OP_Camp),
N(OP_CancelSneakHide), N(OP_CancelSneakHide),
+7 -4
View File
@@ -758,10 +758,10 @@ typedef enum {
FilterFocusEffects = 22, //0=show, 1=hide FilterFocusEffects = 22, //0=show, 1=hide
FilterPetSpells = 23, //0=show, 1=hide FilterPetSpells = 23, //0=show, 1=hide
FilterHealOverTime = 24, //0=show, 1=mine only, 2=hide FilterHealOverTime = 24, //0=show, 1=mine only, 2=hide
FilterUnknown25 = 25, FilterItemSpeech = 25, //0=show, 1=hide // RoF2 Confirmed
FilterUnknown26 = 26, FilterStrikethrough = 26, //0=show, 1=hide // RoF2 Confirmed
FilterUnknown27 = 27, FilterStuns = 27, //0=show, 1=hide // RoF2 Confirmed
FilterUnknown28 = 28, FilterBardSongsOnPets = 28, //0=show, 1=hide // RoF2 Confirmed
_FilterCount _FilterCount
} eqFilterType; } eqFilterType;
@@ -1129,4 +1129,7 @@ enum ExpSource
#define PARCEL_LIMIT 5 #define PARCEL_LIMIT 5
#define PARCEL_BEGIN_SLOT 1 #define PARCEL_BEGIN_SLOT 1
namespace DoorType {
constexpr uint32 BuyerStall = 155;
}
#endif /*COMMON_EQ_CONSTANTS_H*/ #endif /*COMMON_EQ_CONSTANTS_H*/
+367 -48
View File
@@ -323,6 +323,7 @@ union
bool show_name; bool show_name;
bool guild_show; bool guild_show;
bool trader; bool trader;
bool buyer;
}; };
struct PlayerState_Struct { struct PlayerState_Struct {
@@ -1620,6 +1621,32 @@ struct MoveItem_Struct
/*0012*/ /*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 // 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 // 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) // info at the moment..but, it is forwarded on to the server for handling/future use)
@@ -3113,11 +3140,14 @@ struct BazaarSearchResults_Struct {
// Barter/Buyer // Barter/Buyer
// //
// //
enum { #define MAX_BUYER_COMPENSATION_ITEMS 10
enum BarterBuyerActions {
Barter_BuyerSearch = 0, Barter_BuyerSearch = 0,
Barter_SellerSearch = 1, Barter_SellerSearch = 1,
Barter_BuyerModeOn = 2, Barter_BuyerModeOn = 2,
Barter_BuyerModeOff = 3, Barter_BuyerModeOff = 3,
Barter_BuyerItemStart = 4,
Barter_BuyerItemUpdate = 5, Barter_BuyerItemUpdate = 5,
Barter_BuyerItemRemove = 6, Barter_BuyerItemRemove = 6,
Barter_SellItem = 7, Barter_SellItem = 7,
@@ -3132,41 +3162,347 @@ enum {
Barter_BuyerSearchResults = 16, Barter_BuyerSearchResults = 16,
Barter_Welcome = 17, Barter_Welcome = 17,
Barter_WelcomeMessageUpdate = 19, Barter_WelcomeMessageUpdate = 19,
Barter_Greeting = 20,
Barter_BuyerItemInspect = 21, Barter_BuyerItemInspect = 21,
Barter_Unknown23 = 23 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 { struct BuyerWelcomeMessageUpdate_Struct {
/*000*/ uint32 Action; uint32 action;
/*004*/ char WelcomeMessage[256]; char welcome_message[256];
}; };
struct BuyerItemSearch_Struct { struct BuyerLineTradeItems_Struct {
/*000*/ uint32 Unknown000; uint32 item_id;
/*004*/ char SearchString[64]; 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 { struct BuyerLineItems_Struct {
/*000*/ char ItemName[64]; uint32 slot;
/*064*/ uint32 ItemID; uint8 enabled;
/*068*/ uint32 Unknown068; uint32 item_id;
/*072*/ uint32 Unknown072; 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 { template<class Archive>
uint32 Action; void serialize(Archive &archive)
uint32 ResultCount; {
BuyerItemSearchResultEntry_Struct Results[MAX_BUYER_ITEMSEARCH_RESULTS]; 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 { struct BarterSearchRequest_Struct {
uint32 Action; uint32 action;
char SearchString[64]; char search_string[64];
uint32 SearchID; 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 { struct BuyerItemSearchLinkRequest_Struct {
/*000*/ uint32 Action; // 0x00000015 /*000*/ uint32 Action; // 0x00000015
/*004*/ uint32 ItemID; /*004*/ uint32 ItemID;
@@ -3174,31 +3510,6 @@ struct BuyerItemSearchLinkRequest_Struct {
/*012*/ uint32 Unknown012; /*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 { struct ServerSideFilters_Struct {
uint8 clientattackfilters; // 0) No, 1) All (players) but self, 2) All (players) but group 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) 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 { enum BazaarPurchaseActions {
ByVendor = 0, BazaarByVendor = 0,
ByParcel = 1, BazaarByParcel = 1,
ByDirectToInventory = 2 BazaarByDirectToInventory = 2,
BarterByVendor = 0,
BarterInBazaar = 1,
BarterOutsideBazaar = 2
}; };
enum BazaarPurchaseSubActions { enum BazaarPurchaseSubActions {
@@ -6116,6 +6430,11 @@ struct BazaarSearchMessaging_Struct {
} }
}; };
struct BuylineItemDetails_Struct {
uint64 item_cost;
uint32 item_quantity;
};
// Restore structure packing to default // Restore structure packing to default
#pragma pack() #pragma pack()
+1
View File
@@ -706,6 +706,7 @@ void PlayerEventLogs::SetSettingsDefaults()
m_settings[PlayerEvent::PARCEL_SEND].event_enabled = 1; m_settings[PlayerEvent::PARCEL_SEND].event_enabled = 1;
m_settings[PlayerEvent::PARCEL_RETRIEVE].event_enabled = 1; m_settings[PlayerEvent::PARCEL_RETRIEVE].event_enabled = 1;
m_settings[PlayerEvent::PARCEL_DELETE].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++) { for (int i = PlayerEvent::GM_COMMAND; i != PlayerEvent::MAX; i++) {
m_settings[i].retention_days = RETENTION_DAYS_DEFAULT; m_settings[i].retention_days = RETENTION_DAYS_DEFAULT;
+29 -1
View File
@@ -61,6 +61,7 @@ namespace PlayerEvent {
PARCEL_SEND, PARCEL_SEND,
PARCEL_RETRIEVE, PARCEL_RETRIEVE,
PARCEL_DELETE, PARCEL_DELETE,
BARTER_TRANSACTION,
MAX // dont remove MAX // dont remove
}; };
@@ -122,7 +123,8 @@ namespace PlayerEvent {
"Guild Tribute Donate Platinum", "Guild Tribute Donate Platinum",
"Parcel Item Sent", "Parcel Item Sent",
"Parcel Item Retrieved", "Parcel Item Retrieved",
"Parcel Prune Routine" "Parcel Prune Routine",
"Barter Transaction"
}; };
// Generic struct used by all events // Generic struct used by all events
@@ -1081,6 +1083,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 #endif //EQEMU_PLAYER_EVENTS_H
+65
View File
@@ -1743,3 +1743,68 @@ std::vector<uint32> EQ::InventoryProfile::GetAugmentIDsBySlotID(int16 slot_id)
return augments; 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 // Locate an available inventory slot
int16 FindFreeSlot(bool for_bag, bool try_cursor, uint8 min_size = 0, bool is_arrow = false); 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); 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 // Calculate slot_id for an item within a bag
static int16 CalcSlotId(int16 slot_id); // Calc parent bag's slot_id static int16 CalcSlotId(int16 slot_id); // Calc parent bag's slot_id
+53 -19
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 #ifdef _WIN32
* @return #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() std::string IpUtil::GetLocalIPAddress()
{ {
char my_ip_address[16]; #ifdef _WIN32
unsigned int my_port; 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 server_address{};
struct sockaddr_in my_address{}; struct sockaddr_in my_address{};
int sockfd; int sockfd;
// Connect to server // Create a UDP socket
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { #ifdef _WIN32
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == INVALID_SOCKET) {
WSACleanup();
return ""; 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)); memset(&server_address, 0, sizeof(server_address));
server_address.sin_family = AF_INET; server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = inet_addr("172.217.160.99"); server_address.sin_addr.s_addr = inet_addr("8.8.8.8"); // Google DNS
server_address.sin_port = htons(80); server_address.sin_port = htons(53); // DNS port
// Connect to server // Perform a dummy connection to the server (UDP)
if (connect(sockfd, (struct sockaddr *) &server_address, sizeof(server_address)) < 0) { connect(sockfd, (struct sockaddr *) &server_address, sizeof(server_address));
close(sockfd);
return "";
}
// Get my ip address and port // Get my IP address
memset(&my_address, 0, sizeof(my_address)); memset(&my_address, 0, sizeof(my_address));
socklen_t len = sizeof(my_address); socklen_t len = sizeof(my_address);
getsockname(sockfd, (struct sockaddr *) &my_address, &len); getsockname(sockfd, (struct sockaddr *) &my_address, &len);
inet_ntop(AF_INET, &my_address.sin_addr, my_ip_address, sizeof(my_ip_address)); 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 * Gets public address
* Uses various websites as options to return raw public IP back to the client * Uses various websites as options to return raw public IP back to the client
+24 -8
View File
@@ -32,10 +32,11 @@
//#include <iostream> //#include <iostream>
int32 NextItemInstSerialNumber = 1; int32 next_item_serial_number = 1;
std::unordered_set<uint64> guids{};
static inline int32 GetNextItemInstSerialNumber() {
static inline int32 GetNextItemInstSerialNumber()
{
// The Bazaar relies on each item a client has up for Trade having a unique // 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 // 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. // 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. // 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. // 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) if (next_item_serial_number >= INT32_MAX) {
NextItemInstSerialNumber = 1; next_item_serial_number = 1;
else }
NextItemInstSerialNumber++; 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; return stat;
} }
void EQ::ItemInstance::AddGUIDToMap(uint64 existing_serial_number)
{
guids.emplace(existing_serial_number);
}
void EQ::ItemInstance::ClearGUIDMap()
{
guids.clear();
}
// //
// class EvolveInfo // class EvolveInfo
// //
+2
View File
@@ -309,6 +309,8 @@ namespace EQ
int GetItemSkillsStat(EQ::skills::SkillType skill, bool augments = false) const; int GetItemSkillsStat(EQ::skills::SkillType skill, bool augments = false) const;
uint32 GetItemGuildFavor() const; uint32 GetItemGuildFavor() const;
std::vector<uint32> GetAugmentIDs() const; std::vector<uint32> GetAugmentIDs() const;
static void AddGUIDToMap(uint64 existing_serial_number);
static void ClearGUIDMap();
protected: protected:
////////////////////////// //////////////////////////
+512 -21
View File
@@ -359,38 +359,88 @@ namespace RoF2
EQApplicationPacket *in = *p; EQApplicationPacket *in = *p;
*p = nullptr; *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) auto outapp = new EQApplicationPacket(OP_Barter, sizeof(structs::Buyer_SetAppearance_Struct));
{ auto eq = (structs::Buyer_SetAppearance_Struct *) outapp->pBuffer;
dest->FastQueuePacket(&in, ack_req);
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;
unsigned char *__emu_buffer = in->pBuffer; auto outapp = new EQApplicationPacket(OP_BuyerItems, sizeof(structs::BuyerRemoveItem_Struct));
auto eq = (structs::BuyerRemoveItem_Struct *) outapp->pBuffer;
in->size = 80; eq->action = structs::RoF2BuyerActions::BuyerModifyBuyLine;
eq->slot_id = emu->buy_slot_id;
eq->toggle = 0;
in->pBuffer = new unsigned char[in->size]; dest->FastQueuePacket(&outapp);
safe_delete(in);
char *OutBuffer = (char *)in->pBuffer; 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);
char Name[64]; LogTradingDetail("Sending item search results <green>[{}]", bisr.result_count);
VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, SubAction); uint32 packet_size = bisr.result_count * sizeof(structs::BuyerItemSearchResultEntry_Struct) + 8;
uint32 EntityID = VARSTRUCT_DECODE_TYPE(uint32, Buffer); auto outapp = std::make_unique<EQApplicationPacket>(OP_Barter, packet_size);
VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, EntityID); auto eq = (char *) outapp->pBuffer;
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; VARSTRUCT_ENCODE_TYPE(uint32, eq, structs::RoF2BuyerActions::BuyerSearchResults);
dest->FastQueuePacket(&in, ack_req); 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);
}
}
} }
ENCODE(OP_BazaarSearch) ENCODE(OP_BazaarSearch)
@@ -682,6 +732,243 @@ namespace RoF2
FINISH_ENCODE(); 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(OP_CancelTrade)
{ {
ENCODE_LENGTH_EXACT(CancelTrade_Struct); ENCODE_LENGTH_EXACT(CancelTrade_Struct);
@@ -4348,6 +4635,9 @@ namespace RoF2
if (emu->DestructibleObject) { if (emu->DestructibleObject) {
OtherData = OtherData | 0xe1; // Live has 0xe1 for OtherData OtherData = OtherData | 0xe1; // Live has 0xe1 for OtherData
} }
if (emu->buyer) {
OtherData = OtherData | 0x01;
}
VARSTRUCT_ENCODE_TYPE(uint8, Buffer, OtherData); VARSTRUCT_ENCODE_TYPE(uint8, Buffer, OtherData);
// float EmitterScalingRadius // float EmitterScalingRadius
@@ -4664,6 +4954,66 @@ namespace RoF2
FINISH_DIRECT_DECODE(); 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) DECODE(OP_BazaarSearch)
{ {
char *Buffer = (char *)__packet->pBuffer; char *Buffer = (char *)__packet->pBuffer;
@@ -4742,6 +5092,147 @@ namespace RoF2
FINISH_DIRECT_DECODE(); 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(OP_CastSpell)
{ {
DECODE_LENGTH_EXACT(structs::CastSpell_Struct); DECODE_LENGTH_EXACT(structs::CastSpell_Struct);
+3
View File
@@ -48,6 +48,7 @@ E(OP_BeginCast)
E(OP_BlockedBuffs) E(OP_BlockedBuffs)
E(OP_Buff) E(OP_Buff)
E(OP_BuffCreate) E(OP_BuffCreate)
E(OP_BuyerItems)
E(OP_CancelTrade) E(OP_CancelTrade)
E(OP_CastSpell) E(OP_CastSpell)
E(OP_ChannelMessage) E(OP_ChannelMessage)
@@ -149,11 +150,13 @@ D(OP_Animation)
D(OP_ApplyPoison) D(OP_ApplyPoison)
D(OP_AugmentInfo) D(OP_AugmentInfo)
D(OP_AugmentItem) D(OP_AugmentItem)
D(OP_Barter)
D(OP_BazaarSearch) D(OP_BazaarSearch)
D(OP_BlockedBuffs) D(OP_BlockedBuffs)
D(OP_BookButton) D(OP_BookButton)
D(OP_Buff) D(OP_Buff)
D(OP_BuffRemoveRequest) D(OP_BuffRemoveRequest)
D(OP_BuyerItems)
D(OP_CastSpell) D(OP_CastSpell)
D(OP_ChannelMessage) D(OP_ChannelMessage)
D(OP_CharacterCreate) D(OP_CharacterCreate)
+129 -9
View File
@@ -354,15 +354,15 @@ struct Spawn_Struct_Bitfields
/*29*/ unsigned showname:1; /*29*/ unsigned showname:1;
/*30*/ unsigned idleanimationsoff:1; // what we called statue? /*30*/ unsigned idleanimationsoff:1; // what we called statue?
/*31*/ unsigned untargetable:1; // bClickThrough /*31*/ unsigned untargetable:1; // bClickThrough
/* do these later // byte 5
32 unsigned buyer:1; /*32 unsigned buyer:1;
33 unsigned offline:1; /*33 unsigned offline:1;
34 unsigned interactiveobject:1; /*34 unsigned interactiveobject:1;
35 unsigned flung:1; // hmm this vfunc appears to do stuff with leve and flung variables /*35 unsigned flung:1; // hmm this vfunc appears to do stuff with leve and flung variables
36 unsigned title:1; /*36 unsigned title:1;
37 unsigned suffix:1; /*37 unsigned suffix:1;
38 unsigned padding1:1; /*38 unsigned padding1:1;
39 unsigned padding2:1; /*39 unsigned padding2:1;
40 unsinged padding3:1; 40 unsinged padding3:1;
*/ */
/* /*
@@ -3122,6 +3122,126 @@ enum RoF2BazaarTraderBuyerActions {
ReconcileItems = 20 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 { enum {
BazaarTrader_StartTraderMode = 1, BazaarTrader_StartTraderMode = 1,
BazaarTrader_EndTraderMode = 2, BazaarTrader_EndTraderMode = 2,
@@ -21,7 +21,7 @@ public:
struct BotSpellsEntries { struct BotSpellsEntries {
uint32_t id; uint32_t id;
int32_t npc_spells_id; int32_t npc_spells_id;
int16_t spellid; uint16_t spell_id;
uint32_t type; uint32_t type;
uint8_t minlevel; uint8_t minlevel;
uint8_t maxlevel; uint8_t maxlevel;
@@ -46,7 +46,7 @@ public:
return { return {
"id", "id",
"npc_spells_id", "npc_spells_id",
"spellid", "spell_id",
"type", "type",
"minlevel", "minlevel",
"maxlevel", "maxlevel",
@@ -67,7 +67,7 @@ public:
return { return {
"id", "id",
"npc_spells_id", "npc_spells_id",
"spellid", "spell_id",
"type", "type",
"minlevel", "minlevel",
"maxlevel", "maxlevel",
@@ -122,7 +122,7 @@ public:
e.id = 0; e.id = 0;
e.npc_spells_id = 0; e.npc_spells_id = 0;
e.spellid = 0; e.spell_id = 0;
e.type = 0; e.type = 0;
e.minlevel = 0; e.minlevel = 0;
e.maxlevel = 255; e.maxlevel = 255;
@@ -173,7 +173,7 @@ public:
e.id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0; 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.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.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.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; e.maxlevel = row[5] ? static_cast<uint8_t>(strtoul(row[5], nullptr, 10)) : 255;
@@ -220,7 +220,7 @@ public:
auto columns = Columns(); auto columns = Columns();
v.push_back(columns[1] + " = " + std::to_string(e.npc_spells_id)); 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[3] + " = " + std::to_string(e.type));
v.push_back(columns[4] + " = " + std::to_string(e.minlevel)); v.push_back(columns[4] + " = " + std::to_string(e.minlevel));
v.push_back(columns[5] + " = " + std::to_string(e.maxlevel)); 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.id));
v.push_back(std::to_string(e.npc_spells_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.type));
v.push_back(std::to_string(e.minlevel)); v.push_back(std::to_string(e.minlevel));
v.push_back(std::to_string(e.maxlevel)); 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.id));
v.push_back(std::to_string(e.npc_spells_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.type));
v.push_back(std::to_string(e.minlevel)); v.push_back(std::to_string(e.minlevel));
v.push_back(std::to_string(e.maxlevel)); 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.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.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.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.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; 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.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.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.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.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; 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.id));
v.push_back(std::to_string(e.npc_spells_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.type));
v.push_back(std::to_string(e.minlevel)); v.push_back(std::to_string(e.minlevel));
v.push_back(std::to_string(e.maxlevel)); 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.id));
v.push_back(std::to_string(e.npc_spells_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.type));
v.push_back(std::to_string(e.minlevel)); v.push_back(std::to_string(e.minlevel));
v.push_back(std::to_string(e.maxlevel)); 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 { class BaseBuyerRepository {
public: public:
struct Buyer { struct Buyer {
int32_t charid; uint64_t id;
int32_t buyslot; uint32_t char_id;
int32_t itemid; uint32_t char_entity_id;
std::string itemname; std::string char_name;
int32_t quantity; uint32_t char_zone_id;
int32_t price; uint32_t char_zone_instance_id;
time_t transaction_date;
std::string welcome_message;
}; };
static std::string PrimaryKey() static std::string PrimaryKey()
{ {
return std::string("charid"); return std::string("id");
} }
static std::vector<std::string> Columns() static std::vector<std::string> Columns()
{ {
return { return {
"charid", "id",
"buyslot", "char_id",
"itemid", "char_entity_id",
"itemname", "char_name",
"quantity", "char_zone_id",
"price", "char_zone_instance_id",
"transaction_date",
"welcome_message",
}; };
} }
static std::vector<std::string> SelectColumns() static std::vector<std::string> SelectColumns()
{ {
return { return {
"charid", "id",
"buyslot", "char_id",
"itemid", "char_entity_id",
"itemname", "char_name",
"quantity", "char_zone_id",
"price", "char_zone_instance_id",
"UNIX_TIMESTAMP(transaction_date)",
"welcome_message",
}; };
} }
@@ -93,12 +99,14 @@ public:
{ {
Buyer e{}; Buyer e{};
e.charid = 0; e.id = 0;
e.buyslot = 0; e.char_id = 0;
e.itemid = 0; e.char_entity_id = 0;
e.itemname = ""; e.char_name = "";
e.quantity = 0; e.char_zone_id = 0;
e.price = 0; e.char_zone_instance_id = 0;
e.transaction_date = 0;
e.welcome_message = "";
return e; return e;
} }
@@ -109,7 +117,7 @@ public:
) )
{ {
for (auto &buyer : buyers) { for (auto &buyer : buyers) {
if (buyer.charid == buyer_id) { if (buyer.id == buyer_id) {
return buyer; return buyer;
} }
} }
@@ -135,12 +143,14 @@ public:
if (results.RowCount() == 1) { if (results.RowCount() == 1) {
Buyer e{}; Buyer e{};
e.charid = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0; e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0;
e.buyslot = row[1] ? static_cast<int32_t>(atoi(row[1])) : 0; e.char_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.itemid = row[2] ? static_cast<int32_t>(atoi(row[2])) : 0; e.char_entity_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.itemname = row[3] ? row[3] : ""; e.char_name = row[3] ? row[3] : "";
e.quantity = row[4] ? static_cast<int32_t>(atoi(row[4])) : 0; e.char_zone_id = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.price = row[5] ? static_cast<int32_t>(atoi(row[5])) : 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; return e;
} }
@@ -174,12 +184,13 @@ public:
auto columns = Columns(); auto columns = Columns();
v.push_back(columns[0] + " = " + std::to_string(e.charid)); v.push_back(columns[1] + " = " + std::to_string(e.char_id));
v.push_back(columns[1] + " = " + std::to_string(e.buyslot)); v.push_back(columns[2] + " = " + std::to_string(e.char_entity_id));
v.push_back(columns[2] + " = " + std::to_string(e.itemid)); v.push_back(columns[3] + " = '" + Strings::Escape(e.char_name) + "'");
v.push_back(columns[3] + " = '" + Strings::Escape(e.itemname) + "'"); v.push_back(columns[4] + " = " + std::to_string(e.char_zone_id));
v.push_back(columns[4] + " = " + std::to_string(e.quantity)); v.push_back(columns[5] + " = " + std::to_string(e.char_zone_instance_id));
v.push_back(columns[5] + " = " + std::to_string(e.price)); 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( auto results = db.QueryDatabase(
fmt::format( fmt::format(
@@ -187,7 +198,7 @@ public:
TableName(), TableName(),
Strings::Implode(", ", v), Strings::Implode(", ", v),
PrimaryKey(), PrimaryKey(),
e.charid e.id
) )
); );
@@ -201,12 +212,14 @@ public:
{ {
std::vector<std::string> v; std::vector<std::string> v;
v.push_back(std::to_string(e.charid)); v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.buyslot)); v.push_back(std::to_string(e.char_id));
v.push_back(std::to_string(e.itemid)); v.push_back(std::to_string(e.char_entity_id));
v.push_back("'" + Strings::Escape(e.itemname) + "'"); v.push_back("'" + Strings::Escape(e.char_name) + "'");
v.push_back(std::to_string(e.quantity)); v.push_back(std::to_string(e.char_zone_id));
v.push_back(std::to_string(e.price)); 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( auto results = db.QueryDatabase(
fmt::format( fmt::format(
@@ -217,7 +230,7 @@ public:
); );
if (results.Success()) { if (results.Success()) {
e.charid = results.LastInsertedID(); e.id = results.LastInsertedID();
return e; return e;
} }
@@ -236,12 +249,14 @@ public:
for (auto &e: entries) { for (auto &e: entries) {
std::vector<std::string> v; std::vector<std::string> v;
v.push_back(std::to_string(e.charid)); v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.buyslot)); v.push_back(std::to_string(e.char_id));
v.push_back(std::to_string(e.itemid)); v.push_back(std::to_string(e.char_entity_id));
v.push_back("'" + Strings::Escape(e.itemname) + "'"); v.push_back("'" + Strings::Escape(e.char_name) + "'");
v.push_back(std::to_string(e.quantity)); v.push_back(std::to_string(e.char_zone_id));
v.push_back(std::to_string(e.price)); 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) + ")"); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
} }
@@ -275,12 +290,14 @@ public:
for (auto row = results.begin(); row != results.end(); ++row) { for (auto row = results.begin(); row != results.end(); ++row) {
Buyer e{}; Buyer e{};
e.charid = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0; e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0;
e.buyslot = row[1] ? static_cast<int32_t>(atoi(row[1])) : 0; e.char_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.itemid = row[2] ? static_cast<int32_t>(atoi(row[2])) : 0; e.char_entity_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.itemname = row[3] ? row[3] : ""; e.char_name = row[3] ? row[3] : "";
e.quantity = row[4] ? static_cast<int32_t>(atoi(row[4])) : 0; e.char_zone_id = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.price = row[5] ? static_cast<int32_t>(atoi(row[5])) : 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); all_entries.push_back(e);
} }
@@ -305,12 +322,14 @@ public:
for (auto row = results.begin(); row != results.end(); ++row) { for (auto row = results.begin(); row != results.end(); ++row) {
Buyer e{}; Buyer e{};
e.charid = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0; e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0;
e.buyslot = row[1] ? static_cast<int32_t>(atoi(row[1])) : 0; e.char_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.itemid = row[2] ? static_cast<int32_t>(atoi(row[2])) : 0; e.char_entity_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.itemname = row[3] ? row[3] : ""; e.char_name = row[3] ? row[3] : "";
e.quantity = row[4] ? static_cast<int32_t>(atoi(row[4])) : 0; e.char_zone_id = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.price = row[5] ? static_cast<int32_t>(atoi(row[5])) : 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); all_entries.push_back(e);
} }
@@ -385,12 +404,14 @@ public:
{ {
std::vector<std::string> v; std::vector<std::string> v;
v.push_back(std::to_string(e.charid)); v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.buyslot)); v.push_back(std::to_string(e.char_id));
v.push_back(std::to_string(e.itemid)); v.push_back(std::to_string(e.char_entity_id));
v.push_back("'" + Strings::Escape(e.itemname) + "'"); v.push_back("'" + Strings::Escape(e.char_name) + "'");
v.push_back(std::to_string(e.quantity)); v.push_back(std::to_string(e.char_zone_id));
v.push_back(std::to_string(e.price)); 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( auto results = db.QueryDatabase(
fmt::format( fmt::format(
@@ -413,12 +434,14 @@ public:
for (auto &e: entries) { for (auto &e: entries) {
std::vector<std::string> v; std::vector<std::string> v;
v.push_back(std::to_string(e.charid)); v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.buyslot)); v.push_back(std::to_string(e.char_id));
v.push_back(std::to_string(e.itemid)); v.push_back(std::to_string(e.char_entity_id));
v.push_back("'" + Strings::Escape(e.itemname) + "'"); v.push_back("'" + Strings::Escape(e.char_name) + "'");
v.push_back(std::to_string(e.quantity)); v.push_back(std::to_string(e.char_zone_id));
v.push_back(std::to_string(e.price)); 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) + ")"); 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 ornamenticon;
uint32_t ornamentidfile; uint32_t ornamentidfile;
int32_t ornament_hero_model; int32_t ornament_hero_model;
uint64_t guid;
}; };
static std::string PrimaryKey() static std::string PrimaryKey()
@@ -61,6 +62,7 @@ public:
"ornamenticon", "ornamenticon",
"ornamentidfile", "ornamentidfile",
"ornament_hero_model", "ornament_hero_model",
"guid",
}; };
} }
@@ -83,6 +85,7 @@ public:
"ornamenticon", "ornamenticon",
"ornamentidfile", "ornamentidfile",
"ornament_hero_model", "ornament_hero_model",
"guid",
}; };
} }
@@ -139,6 +142,7 @@ public:
e.ornamenticon = 0; e.ornamenticon = 0;
e.ornamentidfile = 0; e.ornamentidfile = 0;
e.ornament_hero_model = 0; e.ornament_hero_model = 0;
e.guid = 0;
return e; return e;
} }
@@ -191,6 +195,7 @@ public:
e.ornamenticon = row[13] ? static_cast<uint32_t>(strtoul(row[13], nullptr, 10)) : 0; 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.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.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; return e;
} }
@@ -240,6 +245,7 @@ public:
v.push_back(columns[13] + " = " + std::to_string(e.ornamenticon)); v.push_back(columns[13] + " = " + std::to_string(e.ornamenticon));
v.push_back(columns[14] + " = " + std::to_string(e.ornamentidfile)); 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[15] + " = " + std::to_string(e.ornament_hero_model));
v.push_back(columns[16] + " = " + std::to_string(e.guid));
auto results = db.QueryDatabase( auto results = db.QueryDatabase(
fmt::format( fmt::format(
@@ -277,6 +283,7 @@ public:
v.push_back(std::to_string(e.ornamenticon)); v.push_back(std::to_string(e.ornamenticon));
v.push_back(std::to_string(e.ornamentidfile)); 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.ornament_hero_model));
v.push_back(std::to_string(e.guid));
auto results = db.QueryDatabase( auto results = db.QueryDatabase(
fmt::format( fmt::format(
@@ -322,6 +329,7 @@ public:
v.push_back(std::to_string(e.ornamenticon)); v.push_back(std::to_string(e.ornamenticon));
v.push_back(std::to_string(e.ornamentidfile)); 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.ornament_hero_model));
v.push_back(std::to_string(e.guid));
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); 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.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.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.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); 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.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.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.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); 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.ornamenticon));
v.push_back(std::to_string(e.ornamentidfile)); 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.ornament_hero_model));
v.push_back(std::to_string(e.guid));
auto results = db.QueryDatabase( auto results = db.QueryDatabase(
fmt::format( fmt::format(
@@ -539,6 +550,7 @@ public:
v.push_back(std::to_string(e.ornamenticon)); v.push_back(std::to_string(e.ornamenticon));
v.push_back(std::to_string(e.ornamentidfile)); 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.ornament_hero_model));
v.push_back(std::to_string(e.guid));
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
} }
@@ -36,6 +36,7 @@ public:
uint32_t ornamenticon; uint32_t ornamenticon;
uint32_t ornamentidfile; uint32_t ornamentidfile;
int32_t ornament_hero_model; int32_t ornament_hero_model;
uint64_t guid;
}; };
static std::string PrimaryKey() static std::string PrimaryKey()
@@ -63,6 +64,7 @@ public:
"ornamenticon", "ornamenticon",
"ornamentidfile", "ornamentidfile",
"ornament_hero_model", "ornament_hero_model",
"guid",
}; };
} }
@@ -86,6 +88,7 @@ public:
"ornamenticon", "ornamenticon",
"ornamentidfile", "ornamentidfile",
"ornament_hero_model", "ornament_hero_model",
"guid",
}; };
} }
@@ -143,6 +146,7 @@ public:
e.ornamenticon = 0; e.ornamenticon = 0;
e.ornamentidfile = 0; e.ornamentidfile = 0;
e.ornament_hero_model = 0; e.ornament_hero_model = 0;
e.guid = 0;
return e; return e;
} }
@@ -196,6 +200,7 @@ public:
e.ornamenticon = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0; 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.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.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; return e;
} }
@@ -246,6 +251,7 @@ public:
v.push_back(columns[14] + " = " + std::to_string(e.ornamenticon)); v.push_back(columns[14] + " = " + std::to_string(e.ornamenticon));
v.push_back(columns[15] + " = " + std::to_string(e.ornamentidfile)); 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[16] + " = " + std::to_string(e.ornament_hero_model));
v.push_back(columns[17] + " = " + std::to_string(e.guid));
auto results = db.QueryDatabase( auto results = db.QueryDatabase(
fmt::format( fmt::format(
@@ -284,6 +290,7 @@ public:
v.push_back(std::to_string(e.ornamenticon)); v.push_back(std::to_string(e.ornamenticon));
v.push_back(std::to_string(e.ornamentidfile)); 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.ornament_hero_model));
v.push_back(std::to_string(e.guid));
auto results = db.QueryDatabase( auto results = db.QueryDatabase(
fmt::format( fmt::format(
@@ -330,6 +337,7 @@ public:
v.push_back(std::to_string(e.ornamenticon)); v.push_back(std::to_string(e.ornamenticon));
v.push_back(std::to_string(e.ornamentidfile)); 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.ornament_hero_model));
v.push_back(std::to_string(e.guid));
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); 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.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.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.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); 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.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.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.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); 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.ornamenticon));
v.push_back(std::to_string(e.ornamentidfile)); 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.ornament_hero_model));
v.push_back(std::to_string(e.guid));
auto results = db.QueryDatabase( auto results = db.QueryDatabase(
fmt::format( fmt::format(
@@ -551,6 +562,7 @@ public:
v.push_back(std::to_string(e.ornamenticon)); v.push_back(std::to_string(e.ornamenticon));
v.push_back(std::to_string(e.ornamentidfile)); 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.ornament_hero_model));
v.push_back(std::to_string(e.guid));
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
} }
@@ -19,31 +19,34 @@
class BaseSpellBucketsRepository { class BaseSpellBucketsRepository {
public: public:
struct SpellBuckets { struct SpellBuckets {
uint64_t spellid; uint32_t spell_id;
std::string key_; std::string bucket_name;
std::string value; std::string bucket_value;
uint8_t bucket_comparison;
}; };
static std::string PrimaryKey() static std::string PrimaryKey()
{ {
return std::string("spellid"); return std::string("spell_id");
} }
static std::vector<std::string> Columns() static std::vector<std::string> Columns()
{ {
return { return {
"spellid", "spell_id",
"`key`", "bucket_name",
"value", "bucket_value",
"bucket_comparison",
}; };
} }
static std::vector<std::string> SelectColumns() static std::vector<std::string> SelectColumns()
{ {
return { return {
"spellid", "spell_id",
"`key`", "bucket_name",
"value", "bucket_value",
"bucket_comparison",
}; };
} }
@@ -84,9 +87,10 @@ public:
{ {
SpellBuckets e{}; SpellBuckets e{};
e.spellid = 0; e.spell_id = 0;
e.key_ = ""; e.bucket_name = "";
e.value = ""; e.bucket_value = "";
e.bucket_comparison = 0;
return e; return e;
} }
@@ -97,7 +101,7 @@ public:
) )
{ {
for (auto &spell_buckets : spell_bucketss) { for (auto &spell_buckets : spell_bucketss) {
if (spell_buckets.spellid == spell_buckets_id) { if (spell_buckets.spell_id == spell_buckets_id) {
return spell_buckets; return spell_buckets;
} }
} }
@@ -123,9 +127,10 @@ public:
if (results.RowCount() == 1) { if (results.RowCount() == 1) {
SpellBuckets e{}; SpellBuckets e{};
e.spellid = row[0] ? strtoull(row[0], nullptr, 10) : 0; e.spell_id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
e.key_ = row[1] ? row[1] : ""; e.bucket_name = row[1] ? row[1] : "";
e.value = row[2] ? row[2] : ""; e.bucket_value = row[2] ? row[2] : "";
e.bucket_comparison = row[3] ? static_cast<uint8_t>(strtoul(row[3], nullptr, 10)) : 0;
return e; return e;
} }
@@ -159,9 +164,10 @@ public:
auto columns = Columns(); auto columns = Columns();
v.push_back(columns[0] + " = " + std::to_string(e.spellid)); v.push_back(columns[0] + " = " + std::to_string(e.spell_id));
v.push_back(columns[1] + " = '" + Strings::Escape(e.key_) + "'"); v.push_back(columns[1] + " = '" + Strings::Escape(e.bucket_name) + "'");
v.push_back(columns[2] + " = '" + Strings::Escape(e.value) + "'"); 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( auto results = db.QueryDatabase(
fmt::format( fmt::format(
@@ -169,7 +175,7 @@ public:
TableName(), TableName(),
Strings::Implode(", ", v), Strings::Implode(", ", v),
PrimaryKey(), PrimaryKey(),
e.spellid e.spell_id
) )
); );
@@ -183,9 +189,10 @@ public:
{ {
std::vector<std::string> v; std::vector<std::string> v;
v.push_back(std::to_string(e.spellid)); v.push_back(std::to_string(e.spell_id));
v.push_back("'" + Strings::Escape(e.key_) + "'"); v.push_back("'" + Strings::Escape(e.bucket_name) + "'");
v.push_back("'" + Strings::Escape(e.value) + "'"); v.push_back("'" + Strings::Escape(e.bucket_value) + "'");
v.push_back(std::to_string(e.bucket_comparison));
auto results = db.QueryDatabase( auto results = db.QueryDatabase(
fmt::format( fmt::format(
@@ -196,7 +203,7 @@ public:
); );
if (results.Success()) { if (results.Success()) {
e.spellid = results.LastInsertedID(); e.spell_id = results.LastInsertedID();
return e; return e;
} }
@@ -215,9 +222,10 @@ public:
for (auto &e: entries) { for (auto &e: entries) {
std::vector<std::string> v; std::vector<std::string> v;
v.push_back(std::to_string(e.spellid)); v.push_back(std::to_string(e.spell_id));
v.push_back("'" + Strings::Escape(e.key_) + "'"); v.push_back("'" + Strings::Escape(e.bucket_name) + "'");
v.push_back("'" + Strings::Escape(e.value) + "'"); v.push_back("'" + Strings::Escape(e.bucket_value) + "'");
v.push_back(std::to_string(e.bucket_comparison));
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
} }
@@ -251,9 +259,10 @@ public:
for (auto row = results.begin(); row != results.end(); ++row) { for (auto row = results.begin(); row != results.end(); ++row) {
SpellBuckets e{}; SpellBuckets e{};
e.spellid = row[0] ? strtoull(row[0], nullptr, 10) : 0; e.spell_id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
e.key_ = row[1] ? row[1] : ""; e.bucket_name = row[1] ? row[1] : "";
e.value = row[2] ? row[2] : ""; 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); all_entries.push_back(e);
} }
@@ -278,9 +287,10 @@ public:
for (auto row = results.begin(); row != results.end(); ++row) { for (auto row = results.begin(); row != results.end(); ++row) {
SpellBuckets e{}; SpellBuckets e{};
e.spellid = row[0] ? strtoull(row[0], nullptr, 10) : 0; e.spell_id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
e.key_ = row[1] ? row[1] : ""; e.bucket_name = row[1] ? row[1] : "";
e.value = row[2] ? row[2] : ""; 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); all_entries.push_back(e);
} }
@@ -355,9 +365,10 @@ public:
{ {
std::vector<std::string> v; std::vector<std::string> v;
v.push_back(std::to_string(e.spellid)); v.push_back(std::to_string(e.spell_id));
v.push_back("'" + Strings::Escape(e.key_) + "'"); v.push_back("'" + Strings::Escape(e.bucket_name) + "'");
v.push_back("'" + Strings::Escape(e.value) + "'"); v.push_back("'" + Strings::Escape(e.bucket_value) + "'");
v.push_back(std::to_string(e.bucket_comparison));
auto results = db.QueryDatabase( auto results = db.QueryDatabase(
fmt::format( fmt::format(
@@ -380,9 +391,10 @@ public:
for (auto &e: entries) { for (auto &e: entries) {
std::vector<std::string> v; std::vector<std::string> v;
v.push_back(std::to_string(e.spellid)); v.push_back(std::to_string(e.spell_id));
v.push_back("'" + Strings::Escape(e.key_) + "'"); v.push_back("'" + Strings::Escape(e.bucket_name) + "'");
v.push_back("'" + Strings::Escape(e.value) + "'"); v.push_back("'" + Strings::Escape(e.bucket_value) + "'");
v.push_back(std::to_string(e.bucket_comparison));
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
} }
@@ -44,7 +44,24 @@ public:
*/ */
// Custom extended repository methods here // 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 #endif //EQEMU_BASE_DATA_REPOSITORY_H
@@ -0,0 +1,356 @@
#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
)
);
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
+89
View File
@@ -4,6 +4,8 @@
#include "../database.h" #include "../database.h"
#include "../strings.h" #include "../strings.h"
#include "base/base_buyer_repository.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 { class BuyerRepository: public BaseBuyerRepository {
public: public:
@@ -45,6 +47,93 @@ public:
// Custom extended repository methods here // 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));
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 #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 = "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 = "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 = "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 = "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 = "task", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "findtask"},
{.parent_command = "find", .sub_command = "zone", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "fz|findzone"}, {.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", .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 = "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 = "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 = "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 = "freeze", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "freeze|unfreeze"},
{.parent_command = "set", .sub_command = "gender", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "gender"}, {.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 // 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 #endif //EQEMU_DB_STR_REPOSITORY_H
@@ -44,7 +44,23 @@ public:
*/ */
// Custom extended repository methods here // 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 #endif //EQEMU_SKILL_CAPS_REPOSITORY_H
@@ -44,7 +44,25 @@ public:
*/ */
// Custom extended repository methods here // 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 #endif //EQEMU_SPELLS_NEW_REPOSITORY_H
+2
View File
@@ -189,6 +189,8 @@ void RuleManager::ResetRules(bool reload) {
m_RuleRealValues[ Real__##rule_name ] = default_value; m_RuleRealValues[ Real__##rule_name ] = default_value;
#define RULE_BOOL(category_name, rule_name, default_value, notes) \ #define RULE_BOOL(category_name, rule_name, default_value, notes) \
m_RuleBoolValues[ Bool__##rule_name ] = default_value; 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" #include "ruletypes.h"
// restore these rules to their pre-reset values // restore these rules to their pre-reset values
+18 -1
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, 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_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, 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, ProcessFearedProximity, false, "Processes proximity checks when feared")
RULE_BOOL(Character, EnableCharacterEXPMods, false, "Enables character zone-based experience modifiers.") 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.") 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_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_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_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_END()
RULE_CATEGORY(Mercs) 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, 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_INT(Mercs, ScaleRate, 100, "Merc scale factor")
RULE_BOOL(Mercs, AllowMercSuspendInCombat, true, "Allow merc suspend in combat") 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_END()
RULE_CATEGORY(Guild) RULE_CATEGORY(Guild)
@@ -619,7 +624,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, 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, 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_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, 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, ArcheryBaseDamage, 0, "Archery base damage, default is 0")
RULE_INT(Combat, BackstabBaseDamage, 0, "Backstab base damage, default is 0") RULE_INT(Combat, BackstabBaseDamage, 0, "Backstab base damage, default is 0")
@@ -667,6 +673,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_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_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, 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_END()
RULE_CATEGORY(Aggro) RULE_CATEGORY(Aggro)
@@ -692,6 +701,7 @@ RULE_BOOL(Aggro, UndeadAlwaysAggro, true, "should undead always aggro?")
RULE_INT(Aggro, BardAggroCap, 40, "per song bard aggro cap.") RULE_INT(Aggro, BardAggroCap, 40, "per song bard aggro cap.")
RULE_INT(Aggro, InitialAggroBonus, 100, "Initial Aggro Bonus, Default: 100") RULE_INT(Aggro, InitialAggroBonus, 100, "Initial Aggro Bonus, Default: 100")
RULE_INT(Aggro, InitialPetAggroBonus, 100, "Initial Pet 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_END()
RULE_CATEGORY(TaskSystem) RULE_CATEGORY(TaskSystem)
@@ -706,6 +716,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, 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_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_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_END()
RULE_CATEGORY(Range) RULE_CATEGORY(Range)
@@ -717,6 +728,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, 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, 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, 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, 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, CriticalDamage, 80, "The packet range in which critical hit messages are sent")
RULE_INT(Range, MobCloseScanDistance, 600, "Close scan distance") RULE_INT(Range, MobCloseScanDistance, 600, "Close scan distance")
@@ -758,6 +770,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_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, 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, 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_END()
RULE_CATEGORY(Chat) RULE_CATEGORY(Chat)
@@ -804,6 +819,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_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_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_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_END()
RULE_CATEGORY(Mail) RULE_CATEGORY(Mail)
@@ -895,6 +911,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, 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_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_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_END()
RULE_CATEGORY(Client) RULE_CATEGORY(Client)
+1
View File
@@ -140,6 +140,7 @@
#define ServerOP_TraderMessaging 0x0120 #define ServerOP_TraderMessaging 0x0120
#define ServerOP_BazaarPurchase 0x0121 #define ServerOP_BazaarPurchase 0x0121
#define ServerOP_BuyerMessaging 0x0122
#define ServerOP_InstanceUpdateTime 0x014F #define ServerOP_InstanceUpdateTime 0x014F
#define ServerOP_AdventureRequest 0x0150 #define ServerOP_AdventureRequest 0x0150
+96 -64
View File
@@ -46,6 +46,7 @@
#include "repositories/character_item_recast_repository.h" #include "repositories/character_item_recast_repository.h"
#include "repositories/character_corpses_repository.h" #include "repositories/character_corpses_repository.h"
#include "repositories/skill_caps_repository.h" #include "repositories/skill_caps_repository.h"
#include "repositories/inventory_repository.h"
namespace ItemField namespace ItemField
{ {
@@ -300,15 +301,15 @@ bool SharedDatabase::UpdateInventorySlot(uint32 char_id, const EQ::ItemInstance*
// Update/Insert item // Update/Insert item
const std::string query = StringFormat("REPLACE INTO inventory " const std::string query = StringFormat("REPLACE INTO inventory "
"(charid, slotid, itemid, charges, instnodrop, custom_data, color, " "(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, " "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>(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), static_cast<unsigned long>(charges), static_cast<unsigned long>(inst->IsAttuned() ? 1 : 0),
inst->GetCustomDataString().c_str(), static_cast<unsigned long>(inst->GetColor()), 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[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>(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); const auto results = QueryDatabase(query);
// Save bag contents, if slot supports bag contents // Save bag contents, if slot supports bag contents
@@ -651,48 +652,67 @@ bool SharedDatabase::GetInventory(uint32 char_id, EQ::InventoryProfile *inv)
return false; return false;
// Retrieve character inventory // Retrieve character inventory
const std::string query = auto results = InventoryRepository::GetWhere(*this, fmt::format("`charid` = '{}' ORDER BY `slotid`;", char_id));
StringFormat("SELECT slotid, itemid, charges, color, augslot1, augslot2, augslot3, augslot4, augslot5, " if (results.empty()) {
"augslot6, instnodrop, custom_data, ornamenticon, ornamentidfile, ornament_hero_model FROM " LogError("Error loading inventory for char_id {} from the database.", char_id);
"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");
return false; return false;
} }
const auto timestamps = GetItemRecastTimestamps(char_id); for (auto const &row: results) {
if (row.guid != 0) {
EQ::ItemInstance::AddGUIDToMap(row.guid);
}
}
const auto timestamps = GetItemRecastTimestamps(char_id);
auto cv_conflict = false; auto cv_conflict = false;
const auto pmask = inv->GetLookup()->PossessionsBitmask; const auto pmask = inv->GetLookup()->PossessionsBitmask;
const auto bank_size = inv->GetLookup()->InventoryTypeSize.Bank; const auto bank_size = inv->GetLookup()->InventoryTypeSize.Bank;
for (auto& row = results.begin(); row != results.end(); ++row) { std::vector<InventoryRepository::Inventory> queue{};
int16 slot_id = Strings::ToInt(row[0]); 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;
if (slot_id <= EQ::invslot::POSSESSIONS_END && slot_id >= EQ::invslot::POSSESSIONS_BEGIN) { // Titanium thru UF check 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 (((static_cast<uint64>(1) << slot_id) & pmask) == 0) { if (((static_cast<uint64>(1) << slot_id) & pmask) == 0) {
cv_conflict = true; cv_conflict = true;
continue; continue;
} }
} }
else if (slot_id <= EQ::invbag::GENERAL_BAGS_END && slot_id >= EQ::invbag::GENERAL_BAGS_BEGIN) { // Titanium thru UF check else if (slot_id <= EQ::invbag::GENERAL_BAGS_END && slot_id >= EQ::invbag::GENERAL_BAGS_BEGIN) {
const auto parent_slot = EQ::invslot::GENERAL_BEGIN + ((slot_id - EQ::invbag::GENERAL_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT); // 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) { if (((static_cast<uint64>(1) << parent_slot) & pmask) == 0) {
cv_conflict = true; cv_conflict = true;
continue; 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) { if ((slot_id - EQ::invslot::BANK_BEGIN) >= bank_size) {
cv_conflict = true; cv_conflict = true;
continue; 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); const auto parent_index = ((slot_id - EQ::invbag::BANK_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT);
if (parent_index >= bank_size) { if (parent_index >= bank_size) {
cv_conflict = true; cv_conflict = true;
@@ -700,64 +720,55 @@ bool SharedDatabase::GetInventory(uint32 char_id, EQ::InventoryProfile *inv)
} }
} }
uint32 item_id = Strings::ToUnsignedInt(row[1]); auto *item = GetItem(item_id);
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);
if (!item) { if (!item) {
LogError("Warning: charid [{}] has an invalid item_id [{}] in inventory slot [{}]", char_id, item_id, LogError(
slot_id); "Warning: charid [{}] has an invalid item_id [{}] in inventory slot [{}]",
char_id,
item_id,
slot_id
);
continue; continue;
} }
EQ::ItemInstance *inst = CreateBaseItem(item, charges); auto *inst = CreateBaseItem(item, charges);
if (!inst) {
if (inst == nullptr)
continue; continue;
}
if (row[11]) { if (!row.custom_data.empty()) {
std::string data_str(row[11]); inst->SetCustomDataString(row.custom_data);
inst->SetCustomDataString(data_str);
} }
inst->SetOrnamentIcon(ornament_icon); inst->SetOrnamentIcon(ornament_icon);
inst->SetOrnamentationIDFile(ornament_idfile); inst->SetOrnamentationIDFile(ornament_idfile);
inst->SetOrnamentHeroModel(item->HerosForgeModel); 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); inst->SetAttuned(true);
}
if (color > 0) if (color > 0) {
inst->SetColor(color); inst->SetColor(color);
}
if (charges == 0x7FFF) if (charges == 0x7FFF) {
inst->SetCharges(-1); 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); inst->SetCharges(1);
else }
else {
inst->SetCharges(charges); inst->SetCharges(charges);
}
if (item->RecastDelay) { if (item->RecastDelay) {
if (item->RecastType != RECAST_TYPE_UNLINKED_ITEM && timestamps.count(item->RecastType)) { if (item->RecastType != RECAST_TYPE_UNLINKED_ITEM && timestamps.count(item->RecastType)) {
inst->SetRecastTimestamp(timestamps.at(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)); inst->SetRecastTimestamp(timestamps.at(item->ID));
} }
else { else {
@@ -767,35 +778,50 @@ bool SharedDatabase::GetInventory(uint32 char_id, EQ::InventoryProfile *inv)
if (item->IsClassCommon()) { if (item->IsClassCommon()) {
for (int i = EQ::invaug::SOCKET_BEGIN; i <= EQ::invaug::SOCKET_END; i++) { 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]); inst->PutAugment(this, i, aug[i]);
} }
} }
}
int16 put_slot_id; int16 put_slot_id;
if (slot_id >= 8000 && slot_id <= 8999) { if (slot_id >= 8000 && slot_id <= 8999) {
put_slot_id = inv->PushCursor(*inst); 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 // Admins: please report any occurrences of this error
LogError("Warning: Defunct location for item in inventory: charid={}, item_id={}, slot_id={} .. pushing to cursor...", LogError(
char_id, item_id, slot_id); "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); put_slot_id = inv->PushCursor(*inst);
} else { }
else {
put_slot_id = inv->PutItem(slot_id, *inst); put_slot_id = inv->PutItem(slot_id, *inst);
} }
row.guid = inst->GetSerialNumber();
queue.push_back(row);
safe_delete(inst); safe_delete(inst);
// Save ptr to item in inventory // Save ptr to item in inventory
if (put_slot_id == INVALID_INDEX) { if (put_slot_id == INVALID_INDEX) {
LogError("Warning: Invalid slot_id for item in inventory: charid=[{}], item_id=[{}], slot_id=[{}]", LogError(
char_id, item_id, slot_id); "Warning: Invalid slot_id for item in inventory: charid=[{}], item_id=[{}], slot_id=[{}]",
char_id,
item_id,
slot_id
);
} }
} }
if (cv_conflict) { if (cv_conflict) {
const std::string &char_name = GetCharName(char_id); const std::string &char_name = GetCharName(char_id);
LogError("ClientVersion/Expansion conflict during inventory load at zone entry for [{}] (charid: [{}], inver: [{}], gmi: [{}])", LogError(
"ClientVersion/Expansion conflict during inventory load at zone entry for [{}] (charid: [{}], inver: [{}], gmi: [{}])",
char_name, char_name,
char_id, char_id,
EQ::versions::MobVersionName(inv->InventoryVersion()), EQ::versions::MobVersionName(inv->InventoryVersion()),
@@ -803,6 +829,12 @@ bool SharedDatabase::GetInventory(uint32 char_id, EQ::InventoryProfile *inv)
); );
} }
if (!queue.empty()) {
InventoryRepository::ReplaceMany(*this, queue);
}
EQ::ItemInstance::ClearGUIDMap();
// Retrieve shared inventory // Retrieve shared inventory
return GetSharedBank(char_id, inv, true); return GetSharedBank(char_id, inv, true);
} }
+105
View File
@@ -2325,3 +2325,108 @@ bool IsCastNotStandingSpell(uint16 spell_id) {
*/ */
return spells[spell_id].cast_not_standing; 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); const char *GetSpellName(uint16 spell_id);
int GetSpellStatValue(uint16 spell_id, const char* stat_identifier, uint8 slot = 0); int GetSpellStatValue(uint16 spell_id, const char* stat_identifier, uint8 slot = 0);
bool IsCastRestrictedSpell(uint16 spell_id); bool IsCastRestrictedSpell(uint16 spell_id);
bool IsAegolismSpell(uint16 spell_id);
bool AegolismStackingIsSymbolSpell(uint16 spell_id);
bool AegolismStackingIsArmorClassSpell(uint16 spell_id);
#endif #endif
+3 -3
View File
@@ -25,7 +25,7 @@
// Build variables // Build variables
// these get injected during the build pipeline // these get injected during the build pipeline
#define CURRENT_VERSION "22.53.1-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 LOGIN_VERSION "0.8.0"
#define COMPILE_DATE __DATE__ #define COMPILE_DATE __DATE__
#define COMPILE_TIME __TIME__ #define COMPILE_TIME __TIME__
@@ -42,8 +42,8 @@
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt * Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
*/ */
#define CURRENT_BINARY_DATABASE_VERSION 9280 #define CURRENT_BINARY_DATABASE_VERSION 9283
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9044 #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9045
#endif #endif
+17
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)) { if (server.db->GetLoginDataFromAccountInfo(user, db_loginserver, db_account_password_hash, db_account_id)) {
result = VerifyLoginHash(user, db_loginserver, cred, db_account_password_hash); 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")); LogDebug("Success [{0}]", (result ? "true" : "false"));
} }
else { else {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "eqemu-server", "name": "eqemu-server",
"version": "22.53.1", "version": "22.56.3",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/EQEmu/Server.git" "url": "https://github.com/EQEmu/Server.git"
+1
View File
@@ -433,6 +433,7 @@ OP_TraderShop=0x31df
OP_TraderBulkSend=0x6a96 OP_TraderBulkSend=0x6a96
OP_Trader=0x4ef5 OP_Trader=0x4ef5
OP_Barter=0x243a OP_Barter=0x243a
OP_BuyerItems=0x1a6a
OP_TraderBuy=0x0000 OP_TraderBuy=0x0000
OP_ShopItem=0x0000 OP_ShopItem=0x0000
OP_BazaarInspect=0x0000 OP_BazaarInspect=0x0000
+1
View File
@@ -287,6 +287,7 @@ void Adventure::Finished(AdventureWinStatus ws)
auto character_id = database.GetCharacterID(*iter); auto character_id = database.GetCharacterID(*iter);
if (character_id == 0) { if (character_id == 0) {
++iter;
continue; continue;
} }
+2
View File
@@ -291,6 +291,8 @@ bool WorldBoot::DatabaseLoadRoutines(int argc, char **argv)
LogInfo("Loading items"); LogInfo("Loading items");
LogInfo("Clearing trader table details"); LogInfo("Clearing trader table details");
database.ClearTraderDetails(); database.ClearTraderDetails();
database.ClearBuyerDetails();
LogInfo("Clearing buyer table details");
if (!content_db.LoadItems(hotfix_name)) { if (!content_db.LoadItems(hotfix_name)) {
LogError("Error: Could not load item data. But ignoring"); 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); zoneserver_list.SendPacketToBootedZones(pack);
break; break;
} }
case ServerOP_BazaarPurchase: { case ServerOP_BazaarPurchase: {
@@ -1753,9 +1752,40 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
"ServerOP_BazaarPurchase", "ServerOP_BazaarPurchase",
in->trader_buy_struct.trader_id in->trader_buy_struct.trader_id
); );
return;
} }
zoneserver_list.SendPacket(Zones::BAZAAR, pack); 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: { default: {
LogInfo("Unknown ServerOPcode from zone {:#04x}, size [{}]", pack->opcode, pack->size); LogInfo("Unknown ServerOPcode from zone {:#04x}, size [{}]", pack->opcode, pack->size);
+5 -1
View File
@@ -2185,7 +2185,7 @@ void Client::AutoGrantAAPoints() {
SendAlternateAdvancementStats(); SendAlternateAdvancementStats();
} }
void Client::GrantAllAAPoints(uint8 unlock_level) void Client::GrantAllAAPoints(uint8 unlock_level, bool skip_grant_only)
{ {
//iterate through every AA //iterate through every AA
for (auto& aa : zone->aa_abilities) { for (auto& aa : zone->aa_abilities) {
@@ -2195,6 +2195,10 @@ void Client::GrantAllAAPoints(uint8 unlock_level)
continue; continue;
} }
if (ability->grant_only && skip_grant_only) {
continue;
}
const uint8 level = unlock_level ? unlock_level : GetLevel(); const uint8 level = unlock_level ? unlock_level : GetLevel();
AA::Rank* rank = ability->first; AA::Rank* rank = ability->first;
+90 -2
View File
@@ -452,8 +452,26 @@ bool Mob::CheckWillAggro(Mob *mob) {
return false; return false;
} }
// Don't aggro new clients if we are already engaged unless SpecialAbility::ProximityAggro is set // Don't aggro new clients if we are already engaged unless PROX_AGGRO is set
if (IsEngaged() && (!GetSpecialAbility(SpecialAbility::ProximityAggro) || (GetSpecialAbility(SpecialAbility::ProximityAggro) && !CombatRange(mob)))) { // 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( LogAggro(
"[{}] is in combat, and does not have prox_aggro, or does and is out of combat range with [{}]", "[{}] is in combat, and does not have prox_aggro, or does and is out of combat range with [{}]",
GetName(), GetName(),
@@ -564,6 +582,76 @@ bool Mob::CheckWillAggro(Mob *mob) {
return false; 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) 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 // Return a list of how many non-feared, non-mezzed, non-green mobs, within aggro range, hate *attacker
-1
View File
@@ -651,7 +651,6 @@ Json::Value ApiGetClientListDetail(EQ::Net::WebsocketServerConnection *connectio
row["base_wis"] = client->GetBaseWIS(); row["base_wis"] = client->GetBaseWIS();
row["become_npc_level"] = client->GetBecomeNPCLevel(); row["become_npc_level"] = client->GetBecomeNPCLevel();
row["boat_id"] = client->GetBoatID(); row["boat_id"] = client->GetBoatID();
row["buyer_welcome_message"] = client->GetBuyerWelcomeMessage();
row["calc_atk"] = client->CalcATK(); row["calc_atk"] = client->CalcATK();
row["calc_base_mana"] = client->CalcBaseMana(); row["calc_base_mana"] = client->CalcBaseMana();
row["calc_current_weight"] = client->CalcCurrentWeight(); row["calc_current_weight"] = client->CalcCurrentWeight();
+264 -181
View File
@@ -1478,8 +1478,10 @@ int64 Mob::DoDamageCaps(int64 base_damage)
//SYNC WITH: tune.cpp, mob.h TuneDoAttack //SYNC WITH: tune.cpp, mob.h TuneDoAttack
void Mob::DoAttack(Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts, bool FromRiposte) void Mob::DoAttack(Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts, bool FromRiposte)
{ {
if (!other) if (!other) {
return; return;
}
LogCombat("[{}]::DoAttack vs [{}] base [{}] min [{}] offense [{}] tohit [{}] skill [{}]", GetName(), LogCombat("[{}]::DoAttack vs [{}] base [{}] min [{}] offense [{}] tohit [{}] skill [{}]", GetName(),
other->GetName(), hit.base_damage, hit.min_damage, hit.offense, hit.tohit, hit.skill); other->GetName(), hit.base_damage, hit.min_damage, hit.offense, hit.tohit, hit.skill);
@@ -1491,14 +1493,22 @@ void Mob::DoAttack(Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts, boo
if (!FromRiposte && other->AvoidDamage(this, hit)) { if (!FromRiposte && other->AvoidDamage(this, hit)) {
if (int strike_through = itembonuses.StrikeThrough + spellbonuses.StrikeThrough + aabonuses.StrikeThrough; if (int strike_through = itembonuses.StrikeThrough + spellbonuses.StrikeThrough + aabonuses.StrikeThrough;
strike_through && zone->random.Roll(strike_through)) { strike_through && zone->random.Roll(strike_through)) {
MessageString(Chat::StrikeThrough,
STRIKETHROUGH_STRING); // You strike through your opponents defenses! FilteredMessageString(
this, /* Sender */
Chat::StrikeThrough, /* Type: 339 */
FilterStrikethrough, /* FilterType: 12 */
STRIKETHROUGH_STRING /* You strike through your opponent's defenses! */
);
hit.damage_done = 1; // set to one, we will check this to continue hit.damage_done = 1; // set to one, we will check this to continue
} }
if (hit.damage_done == DMG_RIPOSTED) { if (hit.damage_done == DMG_RIPOSTED) {
DoRiposte(other); DoRiposte(other);
return; return;
} }
LogCombat("Avoided/strikethrough damage with code [{}]", hit.damage_done); LogCombat("Avoided/strikethrough damage with code [{}]", hit.damage_done);
} }
@@ -1510,9 +1520,19 @@ void Mob::DoAttack(Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts, boo
int stun_resist2 = other->spellbonuses.FrontalStunResist + other->itembonuses.FrontalStunResist + other->aabonuses.FrontalStunResist; int stun_resist2 = other->spellbonuses.FrontalStunResist + other->itembonuses.FrontalStunResist + other->aabonuses.FrontalStunResist;
int stun_resist = other->spellbonuses.StunResist + other->itembonuses.StunResist + other->aabonuses.StunResist; int stun_resist = other->spellbonuses.StunResist + other->itembonuses.StunResist + other->aabonuses.StunResist;
if (zone->random.Roll(stun_resist2)) { if (zone->random.Roll(stun_resist2)) {
other->MessageString(Chat::Stun, AVOID_STUNNING_BLOW); other->FilteredMessageString(
this,
Chat::Stun,
FilterStuns,
AVOID_STUNNING_BLOW
);
} else if (zone->random.Roll(stun_resist)) { } else if (zone->random.Roll(stun_resist)) {
other->MessageString(Chat::Stun, SHAKE_OFF_STUN); other->FilteredMessageString(
this,
Chat::Stun,
FilterStuns,
SHAKE_OFF_STUN
);
} else { } else {
other->Stun(3000); // yuck -- 3 seconds other->Stun(3000); // yuck -- 3 seconds
} }
@@ -2186,6 +2206,19 @@ bool Client::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::Skil
return true; return true;
} }
bool Client::CheckIfAlreadyDead()
{
if (!ClientFinishedLoading()) {
return false;
}
if (dead) {
return false; //cant die more than once...
}
return true;
}
//SYNC WITH: tune.cpp, mob.h TuneNPCAttack //SYNC WITH: tune.cpp, mob.h TuneNPCAttack
bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts) bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts)
{ {
@@ -2460,11 +2493,6 @@ void NPC::Damage(Mob* other, int64 damage, uint16 spell_id, EQ::skills::SkillTyp
//do a majority of the work... //do a majority of the work...
CommonDamage(other, damage, spell_id, attack_skill, avoidable, buffslot, iBuffTic, special); CommonDamage(other, damage, spell_id, attack_skill, avoidable, buffslot, iBuffTic, special);
if (damage > 0) {
//see if we are gunna start fleeing
if (!IsPet()) CheckFlee();
}
} }
bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillType attack_skill, KilledByTypes killed_by, bool is_buff_tic) bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillType attack_skill, KilledByTypes killed_by, bool is_buff_tic)
@@ -4132,6 +4160,7 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
AddToHateList(attacker, 0, damage, true, false, iBuffTic, spell_id); AddToHateList(attacker, 0, damage, true, false, iBuffTic, spell_id);
} }
bool died = false;
if (damage > 0) { if (damage > 0) {
//if there is some damage being done and theres an attacker involved //if there is some damage being done and theres an attacker involved
int previous_hp_ratio = GetHPRatio(); int previous_hp_ratio = GetHPRatio();
@@ -4256,7 +4285,7 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
} }
//final damage has been determined. //final damage has been determined.
SetHP(int64(GetHP() - damage)); int old_hp_ratio = (int)GetHPRatio();
const auto has_bot_given_event = parse->BotHasQuestSub(EVENT_DAMAGE_GIVEN); const auto has_bot_given_event = parse->BotHasQuestSub(EVENT_DAMAGE_GIVEN);
@@ -4303,30 +4332,7 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
); );
std::vector<std::any> args; std::vector<std::any> args;
int64 damage_override = 0;
if (has_taken_event) {
const auto export_string = fmt::format(
"{} {} {} {} {} {} {} {} {}",
attacker ? attacker->GetID() : 0,
damage,
spell_id,
static_cast<int>(skill_used),
FromDamageShield ? 1 : 0,
avoidable ? 1 : 0,
buffslot,
iBuffTic ? 1 : 0,
static_cast<int>(special)
);
if (IsBot() && has_bot_taken_event) {
parse->EventBot(EVENT_DAMAGE_TAKEN, CastToBot(), attacker ? attacker : nullptr, export_string, 0);
} else if (IsClient() && has_player_taken_event) {
args.push_back(attacker ? attacker : nullptr);
parse->EventPlayer(EVENT_DAMAGE_TAKEN, CastToClient(), export_string, 0, &args);
} else if (IsNPC() && has_npc_taken_event) {
parse->EventNPC(EVENT_DAMAGE_TAKEN, CastToNPC(), attacker ? attacker : nullptr, export_string, 0);
}
}
if (has_given_event && attacker) { if (has_given_event && attacker) {
const auto export_string = fmt::format( const auto export_string = fmt::format(
@@ -4352,14 +4358,56 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
} }
} }
if (has_taken_event) {
const auto export_string = fmt::format(
"{} {} {} {} {} {} {} {} {}",
attacker ? attacker->GetID() : 0,
damage,
spell_id,
static_cast<int>(skill_used),
FromDamageShield ? 1 : 0,
avoidable ? 1 : 0,
buffslot,
iBuffTic ? 1 : 0,
static_cast<int>(special)
);
if (IsBot() && has_bot_taken_event) {
damage_override = parse->EventBot(EVENT_DAMAGE_TAKEN, CastToBot(), attacker ? attacker : nullptr, export_string, 0);
} else if (IsClient() && has_player_taken_event) {
args.push_back(attacker ? attacker : nullptr);
damage_override = parse->EventPlayer(EVENT_DAMAGE_TAKEN, CastToClient(), export_string, 0, &args);
} else if (IsNPC() && has_npc_taken_event) {
damage_override = parse->EventNPC(EVENT_DAMAGE_TAKEN, CastToNPC(), attacker ? attacker : nullptr, export_string, 0);
}
}
if (damage_override > 0) {
damage = damage_override;
} else if (damage_override < 0) {
damage = 0;
}
SetHP(int64(GetHP() - damage));
if (HasDied()) { if (HasDied()) {
bool IsSaved = false; bool IsSaved = false;
if (TryDivineSave()) if (TryDivineSave()) {
IsSaved = true; IsSaved = true;
}
if (!IsSaved && !TrySpellOnDeath()) { if (!IsSaved && !TrySpellOnDeath()) {
if (IsNPC()) {
died = !CastToNPC()->GetDepop();
} else if (IsClient()) {
died = CastToClient()->CheckIfAlreadyDead();
}
if (died) {
SetHP(-500); SetHP(-500);
}
// killedByType is clarified in Client::Death if we are client. // killedByType is clarified in Client::Death if we are client.
if (Death(attacker, damage, spell_id, skill_used, KilledByTypes::Killed_NPC, iBuffTic)) { if (Death(attacker, damage, spell_id, skill_used, KilledByTypes::Killed_NPC, iBuffTic)) {
return; return;
@@ -4485,34 +4533,61 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
Stun(RuleI(Combat, StunDuration)); Stun(RuleI(Combat, StunDuration));
if (RuleB(Combat, ClientStunMessage) && attacker->IsClient()) { if (RuleB(Combat, ClientStunMessage) && attacker->IsClient()) {
if (attacker) { if (attacker) {
entity_list.MessageClose(this, true, 500, Chat::Emote, "%s is stunned after being bashed by %s.", GetCleanName(), attacker->GetCleanName()); entity_list.FilteredMessageClose(
} this,
else { true,
entity_list.MessageClose(this, true, 500, Chat::Emote, "%s is stunned by a bash to the head.", GetCleanName()); RuleI(Range, StunMessages),
Chat::Stun,
FilterStuns,
"%s is stunned after being bashed by %s.",
GetCleanName(),
attacker->GetCleanName()
);
} else {
entity_list.FilteredMessageClose(
this,
true,
RuleI(Range, StunMessages),
Chat::Stun,
FilterStuns,
"%s is stunned by a bash to the head.",
GetCleanName()
);
} }
} }
} } else {
else {
// stun resist passed! // stun resist passed!
if (IsClient()) if (IsClient()) {
MessageString(Chat::Stun, SHAKE_OFF_STUN); FilteredMessageString(
this,
Chat::Stun,
FilterStuns,
SHAKE_OFF_STUN
);
} }
} }
else { } else {
// stun resist 2 passed! // stun resist 2 passed!
if (IsClient()) if (IsClient()) {
MessageString(Chat::Stun, AVOID_STUNNING_BLOW); FilteredMessageString(
this,
Chat::Stun,
FilterStuns,
AVOID_STUNNING_BLOW
);
} }
} }
else { } else {
// main stun failed -- extra interrupt roll // main stun failed -- extra interrupt roll
if (IsCasting() && // these spells are excluded
!EQ::ValueWithin(casting_spell_id, 859, 1023)) // these spells are excluded
// 90% chance >< -- stun immune won't reach this branch though :( // 90% chance >< -- stun immune won't reach this branch though :(
if (zone->random.Int(0, 9) > 1) if (IsCasting() && !EQ::ValueWithin(casting_spell_id, 859, 1023)) {
if (zone->random.Int(0, 9) > 1) {
InterruptSpell(); InterruptSpell();
} }
} }
}
}
if (IsValidSpell(spell_id) && !iBuffTic) { if (IsValidSpell(spell_id) && !iBuffTic) {
//see if root will break //see if root will break
@@ -4529,8 +4604,21 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
} }
//send an HP update if we are hurt //send an HP update if we are hurt
if (GetHP() < GetMaxHP()) { if(GetHP() < GetMaxHP())
SendHPUpdate(); // the OP_Damage actually updates the client in these cases, so we skip the HP update for them {
// Don't send a HP update for melee damage unless we've damaged ourself.
if (IsNPC()) {
int cur_hp_ratio = (int)GetHPRatio();
if (cur_hp_ratio != old_hp_ratio) {
SendHPUpdate(true);
}
} else if (!iBuffTic || died) { // Let regen handle buff tics unless this tic killed us.
SendHPUpdate(true);
}
if (!died && IsNPC()) {
CheckFlee();
}
} }
} //end `if damage was done` } //end `if damage was done`
@@ -5388,101 +5476,6 @@ void Mob::TryPetCriticalHit(Mob *defender, DamageHitInfo &hit)
} }
} }
bool Mob::RollMeleeCritCheck(Mob *defender, EQ::skills::SkillType skill)
{
// We either require an innate crit chance or some SPA 169 to crit
bool innate_crit = false;
int crit_chance = GetCriticalChanceBonus(skill);
// Paladin check
if (defender->IsUndeadForSlay()) {
crit_chance = crit_chance + GetUndeadSlayRate();
}
if (GetLevel() >= 12) {
if (
GetClass() == Class::Warrior ||
(GetClass() == Class::Ranger && skill == EQ::skills::SkillArchery) ||
(GetClass() == Class::Rogue && skill == EQ::skills::SkillThrowing) ||
GetClass() == Class::Berserker
) {
innate_crit = true;
}
}
// we have a chance to crit!
if (innate_crit || crit_chance) {
int difficulty = 0;
if (skill == EQ::skills::SkillArchery) {
difficulty = RuleI(Combat, ArcheryCritDifficulty);
} else if (skill == EQ::skills::SkillThrowing) {
difficulty = RuleI(Combat, ThrowingCritDifficulty);
} else {
difficulty = RuleI(Combat, MeleeCritDifficulty);
}
int roll = zone->random.Int(1, difficulty);
int dex_bonus = GetDEX();
if (dex_bonus > 255) {
dex_bonus = 255 + ((dex_bonus - 255) / 5);
}
dex_bonus += 45; // chances did not match live without a small boost
// so if we have an innate crit we have a better chance, except for ber throwing
if (!innate_crit || (GetClass() == Class::Berserker && skill == EQ::skills::SkillThrowing)) {
dex_bonus = dex_bonus * 3 / 5;
}
LogCombat("Crit Chance: dex_bonus ({}) * crit_chance ({}) / 100", dex_bonus, crit_chance);
if (crit_chance) {
dex_bonus += dex_bonus * crit_chance / 100;
}
// check if we crited
LogCombat("Final Roll! Difficulty = [{}] -- Dex_Bonus = [{}] ", difficulty, dex_bonus);
return (roll < dex_bonus);
}
return false;
}
int Mob::GetUndeadSlayRate()
{
return aabonuses.SlayUndead[0] + itembonuses.SlayUndead[0] + spellbonuses.SlayUndead[0];
}
void Mob::DoUndeadSlay(DamageHitInfo &hit, int crit_mod)
{
int slay_damage_bonus = std::max(
{ aabonuses.SlayUndead[1], itembonuses.SlayUndead[1], spellbonuses.SlayUndead[1] });
LogCombatDetail("Slayundead bonus [{}]", slay_damage_bonus);
hit.damage_done = std::max(hit.damage_done, hit.base_damage) + 5;
hit.damage_done = (hit.damage_done * slay_damage_bonus * crit_mod) / 100;
hit.damage_done = static_cast<int>(hit.damage_done * RuleR(Combat, SlayDamageAdjustment));
LogCombatDetail("Slayundead damage [{}]", hit.damage_done);
int slay_sex = GetGender() == Gender::Female ? FEMALE_SLAYUNDEAD : MALE_SLAYUNDEAD;
entity_list.FilteredMessageString(
this, /* Sender */
false, /* Skip Sender */
Chat::MeleeCrit, /* Type: 301 */
FilterMeleeCrits, /* FilterType: 12 */
slay_sex, /* MessageFormat: %1's holy blade cleanses her target!(%2) */
GetCleanName(), /* Message1 */
itoa(hit.damage_done) /* Message2 */
);
}
// a lot of good info: http://giline.versus.jp/shiden/damage_e.htm, http://giline.versus.jp/shiden/su.htm
void Mob::TryCriticalHit(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions *opts) void Mob::TryCriticalHit(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions *opts)
{ {
#ifdef LUA_EQEMU #ifdef LUA_EQEMU
@@ -5494,8 +5487,9 @@ void Mob::TryCriticalHit(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions *
} }
#endif #endif
if (hit.damage_done < 1 || !defender) if (hit.damage_done < 1 || !defender) {
return; return;
}
// decided to branch this into it's own function since it's going to be duplicating a lot of the // decided to branch this into it's own function since it's going to be duplicating a lot of the
// code in here, but could lead to some confusion otherwise // code in here, but could lead to some confusion otherwise
@@ -5513,43 +5507,127 @@ void Mob::TryCriticalHit(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions *
return; return;
} }
// Step 1: Check if we are critting // 1: Try Slay Undead
if (!RollMeleeCritCheck(defender, hit.skill)) { if (defender->GetBodyType() == BodyType::Undead || defender->GetBodyType() == BodyType::SummonedUndead ||
defender->GetBodyType() == BodyType::Vampire) {
int slay_rate_bonus = aabonuses.SlayUndead[SBIndex::SLAYUNDEAD_RATE_MOD] + itembonuses.SlayUndead[SBIndex::SLAYUNDEAD_RATE_MOD] + spellbonuses.SlayUndead[SBIndex::SLAYUNDEAD_RATE_MOD];
LogCombatDetail("Slayundead hit rate [{}]", slay_rate_bonus);
if (slay_rate_bonus) {
float slay_chance = ((static_cast<float>(slay_rate_bonus) / 10000.0f) * RuleR(Combat, SlayRateMultiplier));
if (zone->random.Roll(slay_chance)) {
int slay_damage_bonus = aabonuses.SlayUndead[SBIndex::SLAYUNDEAD_DMG_MOD] + itembonuses.SlayUndead[SBIndex::SLAYUNDEAD_DMG_MOD] + spellbonuses.SlayUndead[SBIndex::SLAYUNDEAD_DMG_MOD];
LogCombatDetail("Slayundead damage bonus [{}]", slay_damage_bonus);
hit.damage_done = std::max(hit.damage_done, hit.base_damage) + 5;
hit.damage_done = (hit.damage_done * slay_damage_bonus) / 100;
hit.damage_done = static_cast<int>(hit.damage_done * RuleR(Combat, SlayDamageMultiplier));
int min_slay = (hit.min_damage + 5) * slay_damage_bonus / 100;
LogCombatDetail(" Calculated Slayundead damage [{}] - Min Slay Undead Damage [{}]", hit.damage_done, min_slay);
if (hit.damage_done < min_slay) {
hit.damage_done = min_slay;
}
LogCombatDetail("Final Slayundead damage [{}]", hit.damage_done);
int slay_sex = GetGender() == Gender::Female ? FEMALE_SLAYUNDEAD : MALE_SLAYUNDEAD;
entity_list.FilteredMessageCloseString(
this, /* Sender */
false, /* Skip Sender */
RuleI(Range, CriticalDamage),
Chat::MeleeCrit, /* Type: 301 */
FilterMeleeCrits, /* FilterType: 12 */
slay_sex,
0,
GetCleanName(), /* Message1 */
itoa(hit.damage_done) /* Message2 */
);
return;
}
}
}
// 2: Try Melee Critical
// a lot of good info: http://giline.versus.jp/shiden/damage_e.htm, http://giline.versus.jp/shiden/su.htm
// We either require an innate crit chance or some SPA 169 to crit
bool innate_crit = false;
int crit_chance = GetCriticalChanceBonus(hit.skill);
if ((GetClass() == Class::Warrior || GetClass() == Class::Berserker) && GetLevel() >= 12) {
innate_crit = true;
} else if (GetClass() == Class::Ranger && GetLevel() >= 12 && hit.skill == EQ::skills::SkillArchery) {
innate_crit = true;
} else if (GetClass() == Class::Rogue && GetLevel() >= 12 && hit.skill == EQ::skills::SkillThrowing) {
innate_crit = true;
}
// we have a chance to crit!
if (innate_crit || crit_chance) {
int difficulty = 0;
if (hit.skill == EQ::skills::SkillArchery) {
difficulty = RuleI(Combat, ArcheryCritDifficulty);
} else if (hit.skill == EQ::skills::SkillThrowing) {
difficulty = RuleI(Combat, ThrowingCritDifficulty);
} else {
difficulty = RuleI(Combat, MeleeCritDifficulty);
}
int roll = zone->random.Int(1, difficulty);
int dex_bonus = GetDEX();
if (dex_bonus > 255) {
dex_bonus = 255 + ((dex_bonus - 255) / 5);
}
dex_bonus += 45; // chances did not match live without a small boost
// so if we have an innate crit we have a better chance, except for ber throwing
if (!innate_crit || (GetClass() == Class::Berserker && hit.skill == EQ::skills::SkillThrowing)) {
dex_bonus = dex_bonus * 3 / 5;
}
if (crit_chance) {
dex_bonus += dex_bonus * crit_chance / 100;
}
// check if we crited
if (roll < dex_bonus) {
// step 1: check for finishing blow
if (TryFinishingBlow(defender, hit.damage_done)) {
return; return;
} }
int crit_mod = EQ::ClampLower((170 + GetCritDmgMod(hit.skill)), 100); // step 2: calculate damage
// Step 2: Calculate damage
hit.damage_done = std::max(hit.damage_done, hit.base_damage) + 5; hit.damage_done = std::max(hit.damage_done, hit.base_damage) + 5;
int og_damage = hit.damage_done; int og_damage = hit.damage_done;
int crit_mod = 170 + GetCritDmgMod(hit.skill);
if (crit_mod < 100) {
crit_mod = 100;
}
hit.damage_done = hit.damage_done * crit_mod / 100; hit.damage_done = hit.damage_done * crit_mod / 100;
LogCombatDetail("Crit success roll [{}] dex chance [{}] og dmg [{}] crit_mod [{}] new dmg [{}]", roll, dex_bonus, og_damage, crit_mod, hit.damage_done);
LogCombatDetail("Crit info: [{}] scaled from: [{}] - IsUndeadForSlay: [{}]", hit.damage_done, og_damage, IsUndeadForSlay() ? "true" : "false"); // step 3: check deadly strike
if (GetClass() == Class::Rogue && hit.skill == EQ::skills::SkillThrowing) {
// Try Slay Undead if (BehindMob(defender, GetX(), GetY())) {
if (defender->IsUndeadForSlay()) {
float chance = GetUndeadSlayRate() / 100.0f;
LogCombatDetail("Trying Undead slay: Chance: [{}]", chance);
if(zone->random.Roll(chance)) {
DoUndeadSlay(hit, crit_mod);
return;
}
}
// Step 3: Check deadly strike
if (GetClass() == Class::Rogue && hit.skill == EQ::skills::SkillThrowing && BehindMob(defender, GetX(), GetY())) {
int chance = GetLevel() * 12; int chance = GetLevel() * 12;
if (zone->random.Int(1, 1000) < chance) { if (zone->random.Int(1, 1000) < chance) {
// Check assassinate // step 3a: check assassinate
int assassinate_damage = TryAssassinate(defender, hit.skill); int assassinate_damage = TryAssassinate(defender, hit.skill); // I don't think this is right
if (assassinate_damage) { if (assassinate_damage) {
hit.damage_done = assassinate_damage; hit.damage_done = assassinate_damage;
return; return;
} }
hit.damage_done = hit.damage_done * 200 / 100; hit.damage_done = hit.damage_done * 200 / 100;
entity_list.FilteredMessageCloseString( entity_list.FilteredMessageCloseString(
@@ -5561,22 +5639,25 @@ void Mob::TryCriticalHit(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions *
DEADLY_STRIKE, /* MessageFormat: %1 scores a Deadly Strike!(%2) */ DEADLY_STRIKE, /* MessageFormat: %1 scores a Deadly Strike!(%2) */
0, 0,
GetCleanName(), /* Message1 */ GetCleanName(), /* Message1 */
itoa(hit.damage_done) /* Message2 */ itoa(hit.damage_done + hit.min_damage) /* Message2 */
); );
return; return;
} }
} }
}
// Step 4: check cripple // step 4: check crips
// this SPA was reused on live ...
bool berserk = spellbonuses.BerserkSPA || itembonuses.BerserkSPA || aabonuses.BerserkSPA; bool berserk = spellbonuses.BerserkSPA || itembonuses.BerserkSPA || aabonuses.BerserkSPA;
if (!berserk) {
if (!berserk && zone->random.Roll(GetCrippBlowChance())) { if (zone->random.Roll(GetCrippBlowChance())) {
berserk = true; berserk = true;
} }
}
if (IsBerserk() || berserk) { if (IsBerserk() || berserk) {
hit.damage_done += og_damage * 119 / 100; hit.damage_done += og_damage * 119 / 100;
LogCombatDetail("Crippling damage [{}]", hit.damage_done); LogCombat("Crip damage [{}]", hit.damage_done);
entity_list.FilteredMessageCloseString( entity_list.FilteredMessageCloseString(
this, /* Sender */ this, /* Sender */
@@ -5587,13 +5668,13 @@ void Mob::TryCriticalHit(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions *
CRIPPLING_BLOW, /* MessageFormat: %1 lands a Crippling Blow!(%2) */ CRIPPLING_BLOW, /* MessageFormat: %1 lands a Crippling Blow!(%2) */
0, 0,
GetCleanName(), /* Message1 */ GetCleanName(), /* Message1 */
itoa(hit.damage_done) /* Message2 */ itoa(hit.damage_done + hit.min_damage) /* Message2 */
); );
// Crippling blows also have a chance to stun // Crippling blows also have a chance to stun
// Kayen: Crippling Blow would cause a chance to interrupt for npcs < 55, with a // Kayen: Crippling Blow would cause a chance to interrupt for npcs < 55, with a
// staggers message. // staggers message.
if (defender->GetLevel() <= RuleI(Combat, MaximumLevelStunsCripplingBlow) && !defender->GetSpecialAbility(SpecialAbility::StunImmunity)) { if (defender->GetLevel() <= 55 && !defender->GetSpecialAbility(SpecialAbility::StunImmunity)) {
entity_list.MessageCloseString( entity_list.MessageCloseString(
defender, defender,
true, true,
@@ -5617,9 +5698,11 @@ void Mob::TryCriticalHit(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions *
CRITICAL_HIT, /* MessageFormat: %1 scores a critical hit! (%2) */ CRITICAL_HIT, /* MessageFormat: %1 scores a critical hit! (%2) */
0, 0,
GetCleanName(), /* Message1 */ GetCleanName(), /* Message1 */
itoa(hit.damage_done) /* Message2 */ itoa(hit.damage_done + hit.min_damage) /* Message2 */
); );
} }
}
}
bool Mob::TryFinishingBlow(Mob *defender, int64 &damage) bool Mob::TryFinishingBlow(Mob *defender, int64 &damage)
{ {
+14 -8
View File
@@ -182,14 +182,20 @@ void Mob::CalcItemBonuses(StatBonuses* b) {
SetDualWeaponsEquipped(true); SetDualWeaponsEquipped(true);
} }
if (IsOfClientBot()) { if (IsClient()) {
for (i = EQ::invslot::TRIBUTE_BEGIN; i <= EQ::invslot::TRIBUTE_END; i++) { if (CastToClient()->GetPP().tribute_active) {
const EQ::ItemInstance* inst = m_inv[i]; for (auto const &t: CastToClient()->GetPP().tributes) {
auto item_id = CastToClient()->LookupTributeItemID(t.tribute, t.tier);
if (item_id) {
const EQ::ItemInstance *inst = database.CreateItem(item_id);
if (!inst) { if (!inst) {
continue; continue;
} }
AddItemBonuses(inst, b, false, true); AddItemBonuses(inst, b, false, true);
safe_delete(inst);
}
}
} }
} }
@@ -1439,8 +1445,8 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon)
case SE_SlayUndead: { case SE_SlayUndead: {
if (newbon->SlayUndead[SBIndex::SLAYUNDEAD_DMG_MOD] < base_value) { if (newbon->SlayUndead[SBIndex::SLAYUNDEAD_DMG_MOD] < base_value) {
newbon->SlayUndead[SBIndex::SLAYUNDEAD_RATE_MOD] = base_value; // Rate newbon->SlayUndead[SBIndex::SLAYUNDEAD_DMG_MOD] = base_value; // Rate
newbon->SlayUndead[SBIndex::SLAYUNDEAD_DMG_MOD] = limit_value; // Damage Modifier newbon->SlayUndead[SBIndex::SLAYUNDEAD_RATE_MOD] = limit_value; // Damage Modifier
} }
break; break;
} }
@@ -3360,7 +3366,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne
break; break;
case SE_Blind: case SE_Blind:
if (RuleB(Combat, AllowRaidTargetBlind) && IsRaidTarget()) { // do not blind raid targets if (!RuleB(Combat, AllowRaidTargetBlind) && IsRaidTarget()) { // do not blind raid targets
break; break;
} }
@@ -3589,8 +3595,8 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne
case SE_SlayUndead: { case SE_SlayUndead: {
if (new_bonus->SlayUndead[SBIndex::SLAYUNDEAD_DMG_MOD] < effect_value) { if (new_bonus->SlayUndead[SBIndex::SLAYUNDEAD_DMG_MOD] < effect_value) {
new_bonus->SlayUndead[SBIndex::SLAYUNDEAD_RATE_MOD] = effect_value; // Rate new_bonus->SlayUndead[SBIndex::SLAYUNDEAD_RATE_MOD] = limit_value; // Rate
new_bonus->SlayUndead[SBIndex::SLAYUNDEAD_DMG_MOD] = limit_value; // Damage Modifier new_bonus->SlayUndead[SBIndex::SLAYUNDEAD_DMG_MOD] = effect_value; // Damage Modifier
} }
break; break;
} }
+11 -15
View File
@@ -204,7 +204,7 @@ Bot::Bot(
); );
} }
SetTaunting((GetClass() == Class::Warrior || GetClass() == Class::Paladin || GetClass() == Class::ShadowKnight) && (GetBotStance() == EQ::constants::stanceAggressive)); SetTaunting((GetClass() == Class::Warrior || GetClass() == Class::Paladin || GetClass() == Class::ShadowKnight) && (GetBotStance() == Stance::Aggressive));
SetPauseAI(false); SetPauseAI(false);
m_auto_defend_timer.Disable(); m_auto_defend_timer.Disable();
@@ -1899,8 +1899,8 @@ void Bot::AI_Process()
#define NOT_GUARDING (!GetGuardFlag()) #define NOT_GUARDING (!GetGuardFlag())
#define HOLDING (GetHoldFlag()) #define HOLDING (GetHoldFlag())
#define NOT_HOLDING (!GetHoldFlag()) #define NOT_HOLDING (!GetHoldFlag())
#define PASSIVE (GetBotStance() == EQ::constants::stancePassive) #define PASSIVE (GetBotStance() == Stance::Passive)
#define NOT_PASSIVE (GetBotStance() != EQ::constants::stancePassive) #define NOT_PASSIVE (GetBotStance() != Stance::Passive)
Client* bot_owner = (GetBotOwner() && GetBotOwner()->IsClient() ? GetBotOwner()->CastToClient() : nullptr); Client* bot_owner = (GetBotOwner() && GetBotOwner()->IsClient() ? GetBotOwner()->CastToClient() : nullptr);
@@ -6912,16 +6912,16 @@ bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, fl
if ((botCasterClass == Class::Paladin || botCasterClass == Class::Beastlord || botCasterClass == Class::Ranger) && (caster->HasGroup() || caster->IsRaidGrouped())) { if ((botCasterClass == Class::Paladin || botCasterClass == Class::Beastlord || botCasterClass == Class::Ranger) && (caster->HasGroup() || caster->IsRaidGrouped())) {
float hpRatioToHeal = 25.0f; float hpRatioToHeal = 25.0f;
switch(caster->GetBotStance()) { switch(caster->GetBotStance()) {
case EQ::constants::stanceReactive: case Stance::Reactive:
case EQ::constants::stanceBalanced: case Stance::Balanced:
hpRatioToHeal = 50.0f; hpRatioToHeal = 50.0f;
break; break;
case EQ::constants::stanceBurn: case Stance::Burn:
case EQ::constants::stanceBurnAE: case Stance::AEBurn:
hpRatioToHeal = 20.0f; hpRatioToHeal = 20.0f;
break; break;
case EQ::constants::stanceAggressive: case Stance::Aggressive:
case EQ::constants::stanceEfficient: case Stance::Efficient:
default: default:
hpRatioToHeal = 25.0f; hpRatioToHeal = 25.0f;
break; break;
@@ -7655,11 +7655,7 @@ bool Bot::HasOrMayGetAggro() {
} }
void Bot::SetDefaultBotStance() { void Bot::SetDefaultBotStance() {
EQ::constants::StanceType defaultStance = EQ::constants::stanceBalanced; _botStance = GetClass() == Class::Warrior ? Stance::Aggressive : Stance::Balanced;
if (GetClass() == Class::Warrior)
defaultStance = EQ::constants::stanceAggressive;
_botStance = defaultStance;
} }
void Bot::BotGroupSay(Mob* speaker, const char* msg, ...) { void Bot::BotGroupSay(Mob* speaker, const char* msg, ...) {
@@ -9233,4 +9229,4 @@ void Bot::DoItemClick(const EQ::ItemData *item, uint16 slot_id)
} }
uint8 Bot::spell_casting_chances[SPELL_TYPE_COUNT][Class::PLAYER_CLASS_COUNT][EQ::constants::STANCE_TYPE_COUNT][cntHSND] = { 0 }; uint8 Bot::spell_casting_chances[SPELL_TYPE_COUNT][Class::PLAYER_CLASS_COUNT][Stance::AEBurn][cntHSND] = { 0 };
+4 -9
View File
@@ -484,7 +484,7 @@ public:
bool IsOfClientBotMerc() const override { return true; } bool IsOfClientBotMerc() const override { return true; }
bool GetRangerAutoWeaponSelect() { return _rangerAutoWeaponSelect; } bool GetRangerAutoWeaponSelect() { return _rangerAutoWeaponSelect; }
EQ::constants::StanceType GetBotStance() { return _botStance; } uint8 GetBotStance() { return _botStance; }
uint8 GetChanceToCastBySpellType(uint32 spellType); uint8 GetChanceToCastBySpellType(uint32 spellType);
bool GetBotEnforceSpellSetting() { return m_enforce_spell_settings; } bool GetBotEnforceSpellSetting() { return m_enforce_spell_settings; }
float GetBotCasterMaxRange(float melee_distance_max); float GetBotCasterMaxRange(float melee_distance_max);
@@ -605,12 +605,7 @@ public:
void SetPetChooser(bool p) { _petChooser = p; } void SetPetChooser(bool p) { _petChooser = p; }
void SetBotOwner(Mob* botOwner) { this->_botOwner = botOwner; } void SetBotOwner(Mob* botOwner) { this->_botOwner = botOwner; }
void SetRangerAutoWeaponSelect(bool enable) { GetClass() == Class::Ranger ? _rangerAutoWeaponSelect = enable : _rangerAutoWeaponSelect = false; } void SetRangerAutoWeaponSelect(bool enable) { GetClass() == Class::Ranger ? _rangerAutoWeaponSelect = enable : _rangerAutoWeaponSelect = false; }
void SetBotStance(EQ::constants::StanceType botStance) { void SetBotStance(uint8 stance_id) { _botStance = Stance::IsValid(stance_id) ? stance_id : Stance::Passive; }
if (botStance >= EQ::constants::stancePassive && botStance <= EQ::constants::stanceBurnAE)
_botStance = botStance;
else
_botStance = EQ::constants::stancePassive;
}
void SetBotCasterRange(uint32 bot_caster_range) { m_bot_caster_range = bot_caster_range; } void SetBotCasterRange(uint32 bot_caster_range) { m_bot_caster_range = bot_caster_range; }
uint32 GetSpellRecastTimer(uint16 spell_id = 0); uint32 GetSpellRecastTimer(uint16 spell_id = 0);
bool CheckSpellRecastTimer(uint16 spell_id = 0); bool CheckSpellRecastTimer(uint16 spell_id = 0);
@@ -753,7 +748,7 @@ public:
//Raid additions //Raid additions
Raid* p_raid_instance; Raid* p_raid_instance;
static uint8 spell_casting_chances[SPELL_TYPE_COUNT][Class::PLAYER_CLASS_COUNT][EQ::constants::STANCE_TYPE_COUNT][cntHSND]; static uint8 spell_casting_chances[SPELL_TYPE_COUNT][Class::PLAYER_CLASS_COUNT][Stance::AEBurn][cntHSND];
bool BotCastMez(Mob* tar, uint8 botLevel, bool checked_los, BotSpell& botSpell, Raid* raid); bool BotCastMez(Mob* tar, uint8 botLevel, bool checked_los, BotSpell& botSpell, Raid* raid);
bool BotCastHeal(Mob* tar, uint8 botLevel, uint8 botClass, BotSpell& botSpell, Raid* raid); bool BotCastHeal(Mob* tar, uint8 botLevel, uint8 botClass, BotSpell& botSpell, Raid* raid);
@@ -870,7 +865,7 @@ private:
std::string _suffix; std::string _suffix;
uint32 _lastZoneId; uint32 _lastZoneId;
bool _rangerAutoWeaponSelect; bool _rangerAutoWeaponSelect;
EQ::constants::StanceType _botStance; uint8 _botStance;
unsigned int RestRegenHP; unsigned int RestRegenHP;
unsigned int RestRegenMana; unsigned int RestRegenMana;
unsigned int RestRegenEndurance; unsigned int RestRegenEndurance;
+1 -1
View File
@@ -40,7 +40,7 @@ void bot_command_attack(Client *c, const Seperator *sep)
sbl.remove(nullptr); sbl.remove(nullptr);
for (auto bot_iter : sbl) { for (auto bot_iter : sbl) {
if (bot_iter->GetAppearance() != eaDead && bot_iter->GetBotStance() != EQ::constants::stancePassive) { if (bot_iter->GetAppearance() != eaDead && bot_iter->GetBotStance() != Stance::Passive) {
if (!first_attacker) { if (!first_attacker) {
first_attacker = bot_iter; first_attacker = bot_iter;
+32 -18
View File
@@ -198,7 +198,7 @@ void bot_command_clone(Client *c, const Seperator *sep)
return; return;
} }
int clone_stance = EQ::constants::stancePassive; int clone_stance = Stance::Passive;
if (!database.botdb.LoadStance(my_bot->GetBotID(), clone_stance)) { if (!database.botdb.LoadStance(my_bot->GetBotID(), clone_stance)) {
c->Message( c->Message(
Chat::White, Chat::White,
@@ -1058,33 +1058,47 @@ void bot_command_stance(Client *c, const Seperator *sep)
return; return;
if (helper_is_help_or_usage(sep->arg[1])) { if (helper_is_help_or_usage(sep->arg[1])) {
c->Message(Chat::White, "usage: %s [current | value: 1-9] ([actionable: target | byname] ([actionable_name]))", sep->arg[0]); c->Message(Chat::White, "usage: %s [current | value: 1-9] ([actionable: target | byname] ([actionable_name]))", sep->arg[0]);
c->Message(Chat::White, "value: %u(%s), %u(%s), %u(%s), %u(%s), %u(%s), %u(%s), %u(%s)", c->Message(
EQ::constants::stancePassive, EQ::constants::GetStanceName(EQ::constants::stancePassive), Chat::White,
EQ::constants::stanceBalanced, EQ::constants::GetStanceName(EQ::constants::stanceBalanced), fmt::format(
EQ::constants::stanceEfficient, EQ::constants::GetStanceName(EQ::constants::stanceEfficient), "Value: {} ({}), {} ({}), {} ({}), {} ({}), {} ({}), {} ({}), {} ({}), {} ({}), {} ({})",
EQ::constants::stanceReactive, EQ::constants::GetStanceName(EQ::constants::stanceReactive), Stance::Passive,
EQ::constants::stanceAggressive, EQ::constants::GetStanceName(EQ::constants::stanceAggressive), Stance::GetName(Stance::Passive),
EQ::constants::stanceAssist, EQ::constants::GetStanceName(EQ::constants::stanceAssist), Stance::Balanced,
EQ::constants::stanceBurn, EQ::constants::GetStanceName(EQ::constants::stanceBurn), Stance::GetName(Stance::Balanced),
EQ::constants::stanceEfficient2, EQ::constants::GetStanceName(EQ::constants::stanceEfficient2), Stance::Efficient,
EQ::constants::stanceBurnAE, EQ::constants::GetStanceName(EQ::constants::stanceBurnAE) Stance::GetName(Stance::Efficient),
Stance::Reactive,
Stance::GetName(Stance::Reactive),
Stance::Aggressive,
Stance::GetName(Stance::Aggressive),
Stance::Assist,
Stance::GetName(Stance::Assist),
Stance::Burn,
Stance::GetName(Stance::Burn),
Stance::Efficient2,
Stance::GetName(Stance::Efficient2),
Stance::AEBurn,
Stance::GetName(Stance::AEBurn)
).c_str()
); );
return; return;
} }
int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_ByName); int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_ByName);
bool current_flag = false; bool current_flag = false;
auto bst = EQ::constants::stanceUnknown; uint8 bst = Stance::Unknown;
if (!strcasecmp(sep->arg[1], "current")) if (!strcasecmp(sep->arg[1], "current"))
current_flag = true; current_flag = true;
else if (sep->IsNumber(1)) { else if (sep->IsNumber(1)) {
bst = (EQ::constants::StanceType)Strings::ToInt(sep->arg[1]); bst = static_cast<uint8>(Strings::ToUnsignedInt(sep->arg[1]));
if (bst < EQ::constants::stanceUnknown || bst > EQ::constants::stanceBurnAE) if (!Stance::IsValid(bst)) {
bst = EQ::constants::stanceUnknown; bst = Stance::Unknown;
}
} }
if (!current_flag && bst == EQ::constants::stanceUnknown) { if (!current_flag && bst == Stance::Unknown) {
c->Message(Chat::White, "A [current] argument or valid numeric [value] is required to use this command"); c->Message(Chat::White, "A [current] argument or valid numeric [value] is required to use this command");
return; return;
} }
@@ -1106,8 +1120,8 @@ void bot_command_stance(Client *c, const Seperator *sep)
bot_iter, bot_iter,
fmt::format( fmt::format(
"My current stance is {} ({}).", "My current stance is {} ({}).",
EQ::constants::GetStanceName(bot_iter->GetBotStance()), Stance::GetName(bot_iter->GetBotStance()),
static_cast<int>(bot_iter->GetBotStance()) bot_iter->GetBotStance()
).c_str() ).c_str()
); );
} }
+1 -1
View File
@@ -34,7 +34,7 @@ void bot_command_pull(Client *c, const Seperator *sep)
Bot* bot_puller = nullptr; Bot* bot_puller = nullptr;
for (auto bot_iter : sbl) { for (auto bot_iter : sbl) {
if (bot_iter->GetAppearance() == eaDead || bot_iter->GetBotStance() == EQ::constants::stancePassive) { if (bot_iter->GetAppearance() == eaDead || bot_iter->GetBotStance() == Stance::Passive) {
continue; continue;
} }
+4 -4
View File
@@ -135,7 +135,7 @@ bool BotDatabase::LoadBotSpellCastingChances()
if ( if (
e.spell_type_index >= Bot::SPELL_TYPE_COUNT || e.spell_type_index >= Bot::SPELL_TYPE_COUNT ||
!IsPlayerClass(e.class_id) || !IsPlayerClass(e.class_id) ||
e.stance_index >= EQ::constants::STANCE_TYPE_COUNT e.stance_index >= Stance::AEBurn
) { ) {
continue; continue;
} }
@@ -761,7 +761,7 @@ bool BotDatabase::LoadStance(Bot* b, bool& stance_flag)
auto e = l.front(); auto e = l.front();
b->SetBotStance(static_cast<EQ::constants::StanceType>(e.stance_id)); b->SetBotStance(e.stance_id);
stance_flag = true; stance_flag = true;
@@ -793,7 +793,7 @@ bool BotDatabase::SaveStance(Bot* b)
database, database,
BotStancesRepository::BotStances{ BotStancesRepository::BotStances{
.bot_id = b->GetBotID(), .bot_id = b->GetBotID(),
.stance_id = static_cast<uint8_t>(b->GetBotStance()) .stance_id = b->GetBotStance()
} }
); );
} }
@@ -2208,7 +2208,7 @@ uint8 BotDatabase::GetSpellCastingChance(uint8 spell_type_index, uint8 class_ind
if ( if (
spell_type_index >= Bot::SPELL_TYPE_COUNT || spell_type_index >= Bot::SPELL_TYPE_COUNT ||
class_index >= Class::PLAYER_CLASS_COUNT || class_index >= Class::PLAYER_CLASS_COUNT ||
stance_index >= EQ::constants::STANCE_TYPE_COUNT || stance_index >= Stance::AEBurn ||
conditional_index >= cntHSND conditional_index >= cntHSND
) { ) {
return 0; return 0;
+32 -30
View File
@@ -629,16 +629,16 @@ bool Bot::BotCastCombatBuff(Mob* tar, uint8 botLevel, uint8 botClass) {
float manaRatioToCast = 75.0f; float manaRatioToCast = 75.0f;
switch(GetBotStance()) { switch(GetBotStance()) {
case EQ::constants::stanceEfficient: case Stance::Efficient:
manaRatioToCast = 90.0f; manaRatioToCast = 90.0f;
break; break;
case EQ::constants::stanceBalanced: case Stance::Balanced:
case EQ::constants::stanceAggressive: case Stance::Aggressive:
manaRatioToCast = 75.0f; manaRatioToCast = 75.0f;
break; break;
case EQ::constants::stanceReactive: case Stance::Reactive:
case EQ::constants::stanceBurn: case Stance::Burn:
case EQ::constants::stanceBurnAE: case Stance::AEBurn:
manaRatioToCast = 50.0f; manaRatioToCast = 50.0f;
break; break;
default: default:
@@ -746,18 +746,18 @@ bool Bot::BotCastNuke(Mob* tar, uint8 botLevel, uint8 botClass, BotSpell& botSpe
float manaRatioToCast = 75.0f; float manaRatioToCast = 75.0f;
switch(GetBotStance()) { switch(GetBotStance()) {
case EQ::constants::stanceEfficient: case Stance::Efficient:
manaRatioToCast = 90.0f; manaRatioToCast = 90.0f;
break; break;
case EQ::constants::stanceBalanced: case Stance::Balanced:
manaRatioToCast = 75.0f; manaRatioToCast = 75.0f;
break; break;
case EQ::constants::stanceReactive: case Stance::Reactive:
case EQ::constants::stanceAggressive: case Stance::Aggressive:
manaRatioToCast = 50.0f; manaRatioToCast = 50.0f;
break; break;
case EQ::constants::stanceBurn: case Stance::Burn:
case EQ::constants::stanceBurnAE: case Stance::AEBurn:
manaRatioToCast = 25.0f; manaRatioToCast = 25.0f;
break; break;
default: default:
@@ -924,16 +924,16 @@ bool Bot::BotCastBuff(Mob* tar, uint8 botLevel, uint8 botClass) {
float manaRatioToCast = 75.0f; float manaRatioToCast = 75.0f;
switch (GetBotStance()) { switch (GetBotStance()) {
case EQ::constants::stanceEfficient: case Stance::Efficient:
manaRatioToCast = 90.0f; manaRatioToCast = 90.0f;
break; break;
case EQ::constants::stanceBalanced: case Stance::Balanced:
case EQ::constants::stanceAggressive: case Stance::Aggressive:
manaRatioToCast = 75.0f; manaRatioToCast = 75.0f;
break; break;
case EQ::constants::stanceReactive: case Stance::Reactive:
case EQ::constants::stanceBurn: case Stance::Burn:
case EQ::constants::stanceBurnAE: case Stance::AEBurn:
manaRatioToCast = 50.0f; manaRatioToCast = 50.0f;
break; break;
default: default:
@@ -1088,18 +1088,18 @@ bool Bot::BotCastHeal(Mob* tar, uint8 botLevel, uint8 botClass, BotSpell& botSpe
float hpRatioToCast = 0.0f; float hpRatioToCast = 0.0f;
switch (GetBotStance()) { switch (GetBotStance()) {
case EQ::constants::stanceEfficient: case Stance::Efficient:
case EQ::constants::stanceAggressive: case Stance::Aggressive:
hpRatioToCast = isPrimaryHealer ? 90.0f : 50.0f; hpRatioToCast = isPrimaryHealer ? 90.0f : 50.0f;
break; break;
case EQ::constants::stanceBalanced: case Stance::Balanced:
hpRatioToCast = isPrimaryHealer ? 95.0f : 75.0f; hpRatioToCast = isPrimaryHealer ? 95.0f : 75.0f;
break; break;
case EQ::constants::stanceReactive: case Stance::Reactive:
hpRatioToCast = isPrimaryHealer ? 100.0f : 90.0f; hpRatioToCast = isPrimaryHealer ? 100.0f : 90.0f;
break; break;
case EQ::constants::stanceBurn: case Stance::Burn:
case EQ::constants::stanceBurnAE: case Stance::AEBurn:
hpRatioToCast = isPrimaryHealer ? 75.0f : 25.0f; hpRatioToCast = isPrimaryHealer ? 75.0f : 25.0f;
break; break;
default: default:
@@ -2939,11 +2939,12 @@ uint8 Bot::GetChanceToCastBySpellType(uint32 spellType)
return 0; return 0;
--class_index; --class_index;
EQ::constants::StanceType stance_type = GetBotStance(); uint32 stance_id = GetBotStance();
if (stance_type < EQ::constants::stancePassive || stance_type > EQ::constants::stanceBurnAE) if (!Stance::IsValid(stance_id)) {
return 0; return 0;
}
uint8 stance_index = EQ::constants::ConvertStanceTypeToIndex(stance_type); uint8 stance_index = Stance::GetIndex(stance_id);
uint8 type_index = nHSND; uint8 type_index = nHSND;
if (HasGroup()) { if (HasGroup()) {
@@ -3323,7 +3324,8 @@ DBbotspells_Struct* ZoneDatabase::GetBotSpells(uint32 bot_spell_id)
if (!bse.empty()) { if (!bse.empty()) {
for (const auto& e : bse) { for (const auto& e : bse) {
DBbotspells_entries_Struct entry; DBbotspells_entries_Struct entry;
entry.spellid = e.spellid;
entry.spellid = e.spell_id;
entry.type = e.type; entry.type = e.type;
entry.minlevel = e.minlevel; entry.minlevel = e.minlevel;
entry.maxlevel = e.maxlevel; entry.maxlevel = e.maxlevel;
@@ -3344,8 +3346,8 @@ DBbotspells_Struct* ZoneDatabase::GetBotSpells(uint32 bot_spell_id)
if (e.resist_adjust) { if (e.resist_adjust) {
entry.resist_adjust = e.resist_adjust; entry.resist_adjust = e.resist_adjust;
} else if (IsValidSpell(e.spellid)) { } else if (IsValidSpell(e.spell_id)) {
entry.resist_adjust = spells[e.spellid].resist_difficulty; entry.resist_adjust = spells[e.spell_id].resist_difficulty;
} }
spell_set.entries.push_back(entry); spell_set.entries.push_back(entry);
+187 -17
View File
@@ -185,7 +185,9 @@ Client::Client(EQStreamInterface *ieqs) : Mob(
position_update_timer(10000), position_update_timer(10000),
consent_throttle_timer(2000), consent_throttle_timer(2000),
tmSitting(0), tmSitting(0),
parcel_timer(RuleI(Parcel, ParcelDeliveryDelay)) parcel_timer(RuleI(Parcel, ParcelDeliveryDelay)),
lazy_load_bank_check_timer(1000),
bandolier_throttle_timer(0)
{ {
for (auto client_filter = FilterNone; client_filter < _FilterCount; client_filter = eqFilterType(client_filter + 1)) { for (auto client_filter = FilterNone; client_filter < _FilterCount; client_filter = eqFilterType(client_filter + 1)) {
SetFilter(client_filter, FilterShow); SetFilter(client_filter, FilterShow);
@@ -203,7 +205,6 @@ Client::Client(EQStreamInterface *ieqs) : Mob(
port = ntohs(eqs->GetRemotePort()); port = ntohs(eqs->GetRemotePort());
client_state = CLIENT_CONNECTING; client_state = CLIENT_CONNECTING;
SetTrader(false); SetTrader(false);
Buyer = false;
Haste = 0; Haste = 0;
SetCustomerID(0); SetCustomerID(0);
SetTraderID(0); SetTraderID(0);
@@ -215,6 +216,7 @@ Client::Client(EQStreamInterface *ieqs) : Mob(
guild_id = GUILD_NONE; guild_id = GUILD_NONE;
guildrank = 0; guildrank = 0;
guild_tribute_opt_in = 0; guild_tribute_opt_in = 0;
SetGuildListDirty(false);
GuildBanker = false; GuildBanker = false;
memset(lskey, 0, sizeof(lskey)); memset(lskey, 0, sizeof(lskey));
strcpy(account_name, ""); strcpy(account_name, "");
@@ -240,7 +242,6 @@ Client::Client(EQStreamInterface *ieqs) : Mob(
runmode = false; runmode = false;
linkdead_timer.Disable(); linkdead_timer.Disable();
zonesummon_id = 0; zonesummon_id = 0;
zonesummon_instance_id = 0;
zonesummon_ignorerestrictions = 0; zonesummon_ignorerestrictions = 0;
bZoning = false; bZoning = false;
m_lock_save_position = false; m_lock_save_position = false;
@@ -285,6 +286,7 @@ Client::Client(EQStreamInterface *ieqs) : Mob(
memset(&m_epp, 0, sizeof(m_epp)); memset(&m_epp, 0, sizeof(m_epp));
PendingTranslocate = false; PendingTranslocate = false;
PendingSacrifice = false; PendingSacrifice = false;
sacrifice_caster_id = 0;
controlling_boat_id = 0; controlling_boat_id = 0;
controlled_mob_id = 0; controlled_mob_id = 0;
qGlobals = nullptr; qGlobals = nullptr;
@@ -386,11 +388,12 @@ Client::Client(EQStreamInterface *ieqs) : Mob(
m_parcel_merchant_engaged = false; m_parcel_merchant_engaged = false;
m_parcels.clear(); m_parcels.clear();
m_buyer_id = 0;
SetBotPulling(false); SetBotPulling(false);
SetBotPrecombat(false); SetBotPrecombat(false);
AI_Init(); AI_Init();
} }
Client::~Client() { Client::~Client() {
@@ -410,8 +413,12 @@ Client::~Client() {
zone->ClearEXPModifier(this); zone->ClearEXPModifier(this);
} }
if(IsInAGuild()) if (!IsZoning()) {
if(IsInAGuild()) {
guild_mgr.UpdateDbMemberOnline(CharacterID(), false);
guild_mgr.SendGuildMemberUpdateToWorld(GetName(), GuildID(), 0, time(nullptr)); guild_mgr.SendGuildMemberUpdateToWorld(GetName(), GuildID(), 0, time(nullptr));
}
}
Mob* horse = entity_list.GetMob(CastToClient()->GetHorseId()); Mob* horse = entity_list.GetMob(CastToClient()->GetHorseId());
if (horse) if (horse)
@@ -425,8 +432,9 @@ Client::~Client() {
TraderEndTrader(); TraderEndTrader();
} }
if(Buyer) if(IsBuyer()) {
ToggleBuyerMode(false); ToggleBuyerMode(false);
}
if(conn_state != ClientConnectFinished) { if(conn_state != ClientConnectFinished) {
LogDebug("Client [{}] was destroyed before reaching the connected state:", GetName()); LogDebug("Client [{}] was destroyed before reaching the connected state:", GetName());
@@ -2165,6 +2173,7 @@ void Client::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho)
ns->spawn.gm = GetGM() ? 1 : 0; ns->spawn.gm = GetGM() ? 1 : 0;
ns->spawn.guildID = GuildID(); ns->spawn.guildID = GuildID();
ns->spawn.trader = IsTrader(); ns->spawn.trader = IsTrader();
ns->spawn.buyer = IsBuyer();
// ns->spawn.linkdead = IsLD() ? 1 : 0; // ns->spawn.linkdead = IsLD() ? 1 : 0;
// ns->spawn.pvp = GetPVP(false) ? 1 : 0; // ns->spawn.pvp = GetPVP(false) ? 1 : 0;
ns->spawn.show_name = true; ns->spawn.show_name = true;
@@ -3396,6 +3405,11 @@ void Client::ServerFilter(SetServerFilter_Struct* filter){
} else { // these clients don't have a 'self only' filter } else { // these clients don't have a 'self only' filter
Filter1(FilterHealOverTime); Filter1(FilterHealOverTime);
} }
Filter1(FilterItemSpeech);
Filter1(FilterStrikethrough);
Filter1(FilterStuns);
Filter1(FilterBardSongsOnPets);
} }
// this version is for messages with no parameters // this version is for messages with no parameters
@@ -3958,7 +3972,7 @@ void Client::SetEndurance(int32 newEnd)
CheckManaEndUpdate(); CheckManaEndUpdate();
} }
void Client::SacrificeConfirm(Client *caster) void Client::SacrificeConfirm(Mob *caster)
{ {
auto outapp = new EQApplicationPacket(OP_Sacrifice, sizeof(Sacrifice_Struct)); auto outapp = new EQApplicationPacket(OP_Sacrifice, sizeof(Sacrifice_Struct));
Sacrifice_Struct *ss = (Sacrifice_Struct *)outapp->pBuffer; Sacrifice_Struct *ss = (Sacrifice_Struct *)outapp->pBuffer;
@@ -3985,14 +3999,14 @@ void Client::SacrificeConfirm(Client *caster)
ss->Confirm = 0; ss->Confirm = 0;
QueuePacket(outapp); QueuePacket(outapp);
safe_delete(outapp); safe_delete(outapp);
// We store the Caster's name, because when the packet comes back, it only has the victim's entityID in it, // We store the Caster's id, because when the packet comes back, it only has the victim's entityID in it,
// not the caster. // not the caster.
SacrificeCaster += caster->GetName(); sacrifice_caster_id = caster->GetID();
PendingSacrifice = true; PendingSacrifice = true;
} }
//Essentially a special case death function //Essentially a special case death function
void Client::Sacrifice(Client *caster) void Client::Sacrifice(Mob *caster)
{ {
if (GetLevel() >= RuleI(Spells, SacrificeMinLevel) && GetLevel() <= RuleI(Spells, SacrificeMaxLevel)) { if (GetLevel() >= RuleI(Spells, SacrificeMinLevel) && GetLevel() <= RuleI(Spells, SacrificeMaxLevel)) {
int exploss = (int)(GetLevel() * (GetLevel() / 18.0) * 12000); int exploss = (int)(GetLevel() * (GetLevel() / 18.0) * 12000);
@@ -4040,8 +4054,11 @@ void Client::Sacrifice(Client *caster)
} }
Save(); Save();
GoToDeath(); GoToDeath();
if (caster) // I guess it's possible? if (caster && caster->IsClient()) {
caster->SummonItem(RuleI(Spells, SacrificeItemID)); caster->CastToClient()->SummonItem(RuleI(Spells, SacrificeItemID));
} else if (caster && caster->IsNPC()) {
caster->CastToNPC()->AddItem(RuleI(Spells, SacrificeItemID), 1, false);
}
} }
} else { } else {
caster->MessageString(Chat::Red, SAC_TOO_LOW); // This being is not a worthy sacrifice. caster->MessageString(Chat::Red, SAC_TOO_LOW); // This being is not a worthy sacrifice.
@@ -9333,6 +9350,7 @@ void Client::ShowDevToolsMenu()
std::string menu_reload_eight; std::string menu_reload_eight;
std::string menu_reload_nine; std::string menu_reload_nine;
std::string menu_toggle; std::string menu_toggle;
std::string window_toggle;
/** /**
* Search entity commands * Search entity commands
@@ -9398,9 +9416,14 @@ void Client::ShowDevToolsMenu()
/** /**
* Show window status * Show window status
*/ */
menu_toggle = Saylink::Silent("#devtools enable", "Enable"); menu_toggle = Saylink::Silent("#devtools menu enable", "Enable");
if (IsDevToolsEnabled()) { if (IsDevToolsEnabled()) {
menu_toggle = Saylink::Silent("#devtools disable", "Disable"); menu_toggle = Saylink::Silent("#devtools menu disable", "Disable");
}
window_toggle = Saylink::Silent("#devtools window enable", "Enable");
if (GetDisplayMobInfoWindow()) {
window_toggle = Saylink::Silent("#devtools window disable", "Disable");
} }
/** /**
@@ -9421,11 +9444,19 @@ void Client::ShowDevToolsMenu()
Message( Message(
Chat::White, Chat::White,
fmt::format( fmt::format(
"Toggle | {}", "Toggle Menu | {}",
menu_toggle menu_toggle
).c_str() ).c_str()
); );
Message(
Chat::White,
fmt::format(
"Toggle Window | {}",
window_toggle
).c_str()
);
Message( Message(
Chat::White, Chat::White,
fmt::format( fmt::format(
@@ -11066,7 +11097,9 @@ void Client::SaveDisciplines()
{ {
std::vector<CharacterDisciplinesRepository::CharacterDisciplines> v; std::vector<CharacterDisciplinesRepository::CharacterDisciplines> v;
for (int slot_id = 0; slot_id < MAX_PP_DISCIPLINES; slot_id++) { std::vector<std::string> delete_slots;
for (uint16 slot_id = 0; slot_id < MAX_PP_DISCIPLINES; slot_id++) {
if (IsValidSpell(m_pp.disciplines.values[slot_id])) { if (IsValidSpell(m_pp.disciplines.values[slot_id])) {
auto e = CharacterDisciplinesRepository::NewEntity(); auto e = CharacterDisciplinesRepository::NewEntity();
@@ -11075,9 +11108,22 @@ void Client::SaveDisciplines()
e.disc_id = m_pp.disciplines.values[slot_id]; e.disc_id = m_pp.disciplines.values[slot_id];
v.emplace_back(e); v.emplace_back(e);
} else {
delete_slots.emplace_back(std::to_string(slot_id));
} }
} }
if (!delete_slots.empty()) {
CharacterDisciplinesRepository::DeleteWhere(
database,
fmt::format(
"`id` = {} AND `slot_id` IN ({})",
CharacterID(),
Strings::Join(delete_slots, ", ")
)
);
}
if (!v.empty()) { if (!v.empty()) {
CharacterDisciplinesRepository::ReplaceMany(database, v); CharacterDisciplinesRepository::ReplaceMany(database, v);
} }
@@ -11975,7 +12021,7 @@ void Client::SendPath(Mob* target)
target->IsClient() && target->IsClient() &&
( (
target->CastToClient()->IsTrader() || target->CastToClient()->IsTrader() ||
target->CastToClient()->Buyer target->CastToClient()->IsBuyer()
) )
) { ) {
Message( Message(
@@ -12579,3 +12625,127 @@ void Client::RemoveItemBySerialNumber(uint32 serial_number, uint32 quantity)
} }
} }
} }
void Client::AddMoneyToPPWithOverflow(uint64 copper, bool update_client)
{
//I noticed in the ROF2 client that the client auto updates the currency values using overflow
//Therefore, I created this method to ensure that the db matches and clients don't see 10 pp 5 gp
//becoming 9pp 15 gold with the current AddMoneyToPP method.
auto add_pp = copper / 1000;
auto add_gp = (copper - add_pp * 1000) / 100;
auto add_sp = (copper - add_pp * 1000 - add_gp * 100) / 10;
auto add_cp = copper - add_pp * 1000 - add_gp * 100 - add_sp * 10;
m_pp.copper += add_cp;
if (m_pp.copper >= 10) {
m_pp.silver += m_pp.copper / 10;
m_pp.copper = m_pp.copper % 10;
}
m_pp.silver += add_sp;
if (m_pp.silver >= 10) {
m_pp.gold += m_pp.silver / 10;
m_pp.silver = m_pp.silver % 10;
}
m_pp.gold += add_gp;
if (m_pp.gold >= 10) {
m_pp.platinum += m_pp.gold / 10;
m_pp.gold = m_pp.gold % 10;
}
m_pp.platinum += add_pp;
if (update_client) {
SendMoneyUpdate();
}
RecalcWeight();
SaveCurrency();
LogDebug("Client::AddMoneyToPPWithOverflow() [{}] should have: plat:[{}] gold:[{}] silver:[{}] copper:[{}]",
GetName(),
m_pp.platinum,
m_pp.gold,
m_pp.silver,
m_pp.copper
);
}
bool Client::TakeMoneyFromPPWithOverFlow(uint64 copper, bool update_client)
{
int32 remove_pp = copper / 1000;
int32 remove_gp = (copper - remove_pp * 1000) / 100;
int32 remove_sp = (copper - remove_pp * 1000 - remove_gp * 100) / 10;
int32 remove_cp = copper - remove_pp * 1000 - remove_gp * 100 - remove_sp * 10;
uint64 current_money = GetCarriedMoney();
if (copper > current_money) {
return false; //client does not have enough money on them
}
m_pp.copper -= remove_cp;
if (m_pp.copper < 0) {
m_pp.silver -= 1;
m_pp.copper = m_pp.copper + 10;
if (m_pp.copper >= 10) {
m_pp.silver += m_pp.copper / 10;
m_pp.copper = m_pp.copper % 10;
}
}
m_pp.silver -= remove_sp;
if (m_pp.silver < 0) {
m_pp.gold -= 1;
m_pp.silver = m_pp.silver + 10;
if (m_pp.silver >= 10) {
m_pp.gold += m_pp.silver / 10;
m_pp.silver = m_pp.silver % 10;
}
}
m_pp.gold -= remove_gp;
if (m_pp.gold < 0) {
m_pp.platinum -= 1;
m_pp.gold = m_pp.gold + 10;
if (m_pp.gold >= 10) {
m_pp.platinum += m_pp.gold / 10;
m_pp.gold = m_pp.gold % 10;
}
}
m_pp.platinum -= remove_pp;
if (update_client) {
SendMoneyUpdate();
}
SaveCurrency();
RecalcWeight();
return true;
}
void Client::SendTopLevelInventory()
{
EQ::ItemInstance* inst = nullptr;
static const int16 slots[][2] = {
{ EQ::invslot::POSSESSIONS_BEGIN, EQ::invslot::POSSESSIONS_END },
{ EQ::invbag::GENERAL_BAGS_BEGIN, EQ::invbag::GENERAL_BAGS_END },
{ EQ::invbag::CURSOR_BAG_BEGIN, EQ::invbag::CURSOR_BAG_END }
};
const auto& inv = GetInv();
const size_t slot_index_count = sizeof(slots) / sizeof(slots[0]);
for (int slot_index = 0; slot_index < slot_index_count; ++slot_index) {
for (int slot_id = slots[slot_index][0]; slot_id <= slots[slot_index][1]; ++slot_id) {
inst = inv.GetItem(slot_id);
if (inst) {
SendItemPacket(slot_id, inst, ItemPacketType::ItemPacketTrade);
}
}
}
}
+58 -15
View File
@@ -70,6 +70,8 @@ namespace EQ
#include "../common/data_verification.h" #include "../common/data_verification.h"
#include "../common/repositories/character_parcels_repository.h" #include "../common/repositories/character_parcels_repository.h"
#include "../common/repositories/trader_repository.h" #include "../common/repositories/trader_repository.h"
#include "../common/guild_base.h"
#include "../common/repositories/buyer_buy_lines_repository.h"
#ifdef _WINDOWS #ifdef _WINDOWS
// since windows defines these within windef.h (which windows.h include) // since windows defines these within windef.h (which windows.h include)
@@ -276,6 +278,8 @@ public:
std::vector<Mob*> GetRaidOrGroupOrSelf(bool clients_only = false); std::vector<Mob*> GetRaidOrGroupOrSelf(bool clients_only = false);
bool CheckIfAlreadyDead();
void AI_Init(); void AI_Init();
void AI_Start(uint32 iMoveDelay = 0); void AI_Start(uint32 iMoveDelay = 0);
void AI_Stop(); void AI_Stop();
@@ -288,6 +292,7 @@ public:
void TraderPriceUpdate(const EQApplicationPacket *app); void TraderPriceUpdate(const EQApplicationPacket *app);
void SendBazaarDone(uint32 trader_id); void SendBazaarDone(uint32 trader_id);
void SendBulkBazaarTraders(); void SendBulkBazaarTraders();
void SendBulkBazaarBuyers();
void DoBazaarInspect(const BazaarInspect_Struct &in); void DoBazaarInspect(const BazaarInspect_Struct &in);
void SendBazaarDeliveryCosts(); void SendBazaarDeliveryCosts();
static std::string DetermineMoneyString(uint64 copper); static std::string DetermineMoneyString(uint64 copper);
@@ -307,8 +312,10 @@ public:
bool TryStacking(EQ::ItemInstance* item, uint8 type = ItemPacketTrade, bool try_worn = true, bool try_cursor = true); bool TryStacking(EQ::ItemInstance* item, uint8 type = ItemPacketTrade, bool try_worn = true, bool try_cursor = true);
void SendTraderPacket(Client* trader, uint32 Unknown72 = 51); void SendTraderPacket(Client* trader, uint32 Unknown72 = 51);
void SendBuyerPacket(Client* Buyer); void SendBuyerPacket(Client* Buyer);
void SendBuyerToBarterWindow(Client* buyer, uint32 action);
GetItems_Struct* GetTraderItems(); GetItems_Struct* GetTraderItems();
void SendBazaarWelcome(); void SendBazaarWelcome();
void SendBarterWelcome();
void DyeArmor(EQ::TintProfile* dye); void DyeArmor(EQ::TintProfile* dye);
void DyeArmorBySlot(uint8 slot, uint8 red, uint8 green, uint8 blue, uint8 use_tint = 0x00); void DyeArmorBySlot(uint8 slot, uint8 red, uint8 green, uint8 blue, uint8 use_tint = 0x00);
uint8 SlotConvert(uint8 slot,bool bracer=false); uint8 SlotConvert(uint8 slot,bool bracer=false);
@@ -375,14 +382,35 @@ public:
uint32 GetCustomerID() { return customer_id; } uint32 GetCustomerID() { return customer_id; }
void SetCustomerID(uint32 id) { customer_id = id; } void SetCustomerID(uint32 id) { customer_id = id; }
void SendBuyerResults(char *SearchQuery, uint32 SearchID); void SetBuyerID(uint32 id) { m_buyer_id = id; }
uint32 GetBuyerID() { return m_buyer_id; }
bool IsBuyer() { return m_buyer_id != 0 ? true : false; }
void SetBarterTime() { m_barter_time = time(nullptr); }
uint32 GetBarterTime() { return m_barter_time; }
void SetBuyerWelcomeMessage(const char* welcome_message);
void SendBuyerGreeting(uint32 char_id);
void SendSellerBrowsing(const std::string &browser);
void SendBuyerMode(bool status);
bool IsInBuyerSpace();
void SendBuyLineUpdate(const BuyerLineItems_Struct &buy_line);
void CheckIfMovedItemIsPartOfBuyLines(uint32 item_id);
void SendBuyerResults(BarterSearchRequest_Struct& bsr);
void ShowBuyLines(const EQApplicationPacket *app); void ShowBuyLines(const EQApplicationPacket *app);
void SellToBuyer(const EQApplicationPacket *app); void SellToBuyer(const EQApplicationPacket *app);
void ToggleBuyerMode(bool TurnOn); void ToggleBuyerMode(bool TurnOn);
void UpdateBuyLine(const EQApplicationPacket *app); void ModifyBuyLine(const EQApplicationPacket *app);
void CreateStartingBuyLines(const EQApplicationPacket *app);
void BuyerItemSearch(const EQApplicationPacket *app); void BuyerItemSearch(const EQApplicationPacket *app);
void SetBuyerWelcomeMessage(const char* WelcomeMessage) { BuyerWelcomeMessage = WelcomeMessage; } void SendWindowUpdatesToSellerAndBuyer(BuyerLineSellItem_Struct& blsi);
const char* GetBuyerWelcomeMessage() { return BuyerWelcomeMessage.c_str(); } void SendBarterBuyerClientMessage(BuyerLineSellItem_Struct& blsi, BarterBuyerActions action, BarterBuyerSubActions sub_action, BarterBuyerSubActions error_code);
bool BuildBuyLineMap(std::map<uint32, BuylineItemDetails_Struct>& item_map, BuyerBuyLines_Struct& bl);
bool BuildBuyLineMapFromVector(std::map<uint32, BuylineItemDetails_Struct>& item_map, std::vector<BuyerLineItems_Struct>& bl);
void RemoveItemFromBuyLineMap(std::map<uint32, BuylineItemDetails_Struct>& item_map, const BuyerLineItems_Struct& bl);
bool ValidateBuyLineItems(std::map<uint32, BuylineItemDetails_Struct>& item_map);
int64 ValidateBuyLineCost(std::map<uint32, BuylineItemDetails_Struct>& item_map);
bool DoBarterBuyerChecks(BuyerLineSellItem_Struct& sell_line);
bool DoBarterSellerChecks(BuyerLineSellItem_Struct& sell_line);
void FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho); void FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho);
bool ShouldISpawnFor(Client *c) { return !GMHideMe(c) && !IsHoveringForRespawn(); } bool ShouldISpawnFor(Client *c) { return !GMHideMe(c) && !IsHoveringForRespawn(); }
@@ -734,11 +762,12 @@ public:
void GetRaidAAs(RaidLeadershipAA_Struct *into) const; void GetRaidAAs(RaidLeadershipAA_Struct *into) const;
void ClearGroupAAs(); void ClearGroupAAs();
void UpdateGroupAAs(int32 points, uint32 type); void UpdateGroupAAs(int32 points, uint32 type);
void SacrificeConfirm(Client* caster); void SacrificeConfirm(Mob* caster);
void Sacrifice(Client* caster); void Sacrifice(Mob* caster);
void GoToDeath(); void GoToDeath();
inline const int32 GetInstanceID() const { return zone->GetInstanceID(); } inline const int32 GetInstanceID() const { return zone->GetInstanceID(); }
void SetZoning(bool in) { bZoning = in; } void SetZoning(bool in) { bZoning = in; }
bool IsZoning() { return bZoning; }
void ShowSpells(Client* c, ShowSpellType show_spell_type); void ShowSpells(Client* c, ShowSpellType show_spell_type);
@@ -825,9 +854,11 @@ public:
void QuestReadBook(const char* text, uint8 type); void QuestReadBook(const char* text, uint8 type);
void SendMoneyUpdate(); void SendMoneyUpdate();
bool TakeMoneyFromPP(uint64 copper, bool update_client = false); bool TakeMoneyFromPP(uint64 copper, bool update_client = false);
bool TakeMoneyFromPPWithOverFlow(uint64 copper, bool update_client);
bool TakePlatinum(uint32 platinum, bool update_client = false); bool TakePlatinum(uint32 platinum, bool update_client = false);
void AddMoneyToPP(uint64 copper, bool update_client = false); void AddMoneyToPP(uint64 copper, bool update_client = false);
void AddMoneyToPP(uint32 copper, uint32 silver, uint32 gold, uint32 platinum, bool update_client = false); void AddMoneyToPP(uint32 copper, uint32 silver, uint32 gold, uint32 platinum, bool update_client = false);
void AddMoneyToPPWithOverflow(uint64 copper, bool update_client);
void AddPlatinum(uint32 platinu, bool update_client = false); void AddPlatinum(uint32 platinu, bool update_client = false);
bool HasMoney(uint64 copper); bool HasMoney(uint64 copper);
uint64 GetCarriedMoney(); uint64 GetCarriedMoney();
@@ -966,6 +997,8 @@ public:
void ChangeTributeSettings(TributeInfo_Struct *t); void ChangeTributeSettings(TributeInfo_Struct *t);
void SendTributeTimer(); void SendTributeTimer();
void ToggleTribute(bool enabled); void ToggleTribute(bool enabled);
std::map<uint32, TributeData> GetTributeList();
uint32 LookupTributeItemID(uint32 tribute_id, uint32 tier);
void SendPathPacket(const std::vector<FindPerson_Point> &path); void SendPathPacket(const std::vector<FindPerson_Point> &path);
inline PTimerList &GetPTimers() { return(p_timers); } inline PTimerList &GetPTimers() { return(p_timers); }
@@ -992,7 +1025,7 @@ public:
int GetSpentAA() { return m_pp.aapoints_spent; } int GetSpentAA() { return m_pp.aapoints_spent; }
uint32 GetRequiredAAExperience(); uint32 GetRequiredAAExperience();
void AutoGrantAAPoints(); void AutoGrantAAPoints();
void GrantAllAAPoints(uint8 unlock_level = 0); void GrantAllAAPoints(uint8 unlock_level = 0, bool skip_grant_only = false);
bool HasAlreadyPurchasedRank(AA::Rank* rank); bool HasAlreadyPurchasedRank(AA::Rank* rank);
void ListPurchasedAAs(Client *to, std::string search_criteria = std::string()); void ListPurchasedAAs(Client *to, std::string search_criteria = std::string());
@@ -1054,6 +1087,8 @@ public:
int32 GetItemIDAt(int16 slot_id); int32 GetItemIDAt(int16 slot_id);
int32 GetAugmentIDAt(int16 slot_id, uint8 augslot); int32 GetAugmentIDAt(int16 slot_id, uint8 augslot);
bool PutItemInInventory(int16 slot_id, const EQ::ItemInstance& inst, bool client_update = false); bool PutItemInInventory(int16 slot_id, const EQ::ItemInstance& inst, bool client_update = false);
bool PutItemInInventoryWithStacking(EQ::ItemInstance* inst);
bool FindNumberOfFreeInventorySlotsWithSizeCheck(std::vector<BuyerLineTradeItems_Struct> items);
bool PushItemOnCursor(const EQ::ItemInstance& inst, bool client_update = false); bool PushItemOnCursor(const EQ::ItemInstance& inst, bool client_update = false);
void SendCursorBuffer(); void SendCursorBuffer();
void DeleteItemInInventory(int16 slot_id, int16 quantity = 0, bool client_update = false, bool update_db = true); void DeleteItemInInventory(int16 slot_id, int16 quantity = 0, bool client_update = false, bool update_db = true);
@@ -1092,7 +1127,6 @@ public:
uint16 GetTraderID() { return trader_id; } uint16 GetTraderID() { return trader_id; }
void SetTraderID(uint16 id) { trader_id = id; } void SetTraderID(uint16 id) { trader_id = id; }
inline bool IsBuyer() const { return(Buyer); }
eqFilterMode GetFilter(eqFilterType filter_id) const { return ClientFilters[filter_id]; } eqFilterMode GetFilter(eqFilterType filter_id) const { return ClientFilters[filter_id]; }
void SetFilter(eqFilterType filter_id, eqFilterMode filter_mode) { ClientFilters[filter_id] = filter_mode; } void SetFilter(eqFilterType filter_id, eqFilterMode filter_mode) { ClientFilters[filter_id] = filter_mode; }
@@ -1129,7 +1163,7 @@ public:
void RemoveNoRent(bool client_update = true); void RemoveNoRent(bool client_update = true);
void RemoveDuplicateLore(bool client_update = true); void RemoveDuplicateLore(bool client_update = true);
void MoveSlotNotAllowed(bool client_update = true); void MoveSlotNotAllowed(bool client_update = true);
virtual void RangedAttack(Mob* other, bool CanDoubleAttack = false); virtual bool RangedAttack(Mob* other, bool CanDoubleAttack = false);
virtual void ThrowingAttack(Mob* other, bool CanDoubleAttack = false); virtual void ThrowingAttack(Mob* other, bool CanDoubleAttack = false);
void DoClassAttacks(Mob *ca_target, uint16 skill = -1, bool IsRiposte=false); void DoClassAttacks(Mob *ca_target, uint16 skill = -1, bool IsRiposte=false);
@@ -1211,7 +1245,7 @@ public:
bool PendingTranslocate; bool PendingTranslocate;
time_t TranslocateTime; time_t TranslocateTime;
bool PendingSacrifice; bool PendingSacrifice;
std::string SacrificeCaster; uint16 sacrifice_caster_id;
PendingTranslocate_Struct PendingTranslocateData; PendingTranslocate_Struct PendingTranslocateData;
void SendOPTranslocateConfirm(Mob *Caster, uint16 SpellID); void SendOPTranslocateConfirm(Mob *Caster, uint16 SpellID);
@@ -1386,7 +1420,11 @@ public:
{ {
return (task_state ? task_state->EnabledTaskCount(task_set_id) : -1); return (task_state ? task_state->EnabledTaskCount(task_set_id) : -1);
} }
inline int IsTaskCompleted(int task_id) { return (task_state ? task_state->IsTaskCompleted(task_id) : -1); } inline bool IsTaskCompleted(int task_id) { return (task_state ? task_state->IsTaskCompleted(task_id) : false); }
inline bool AreTasksCompleted(std::vector<int> task_ids)
{
return (task_state ? task_state->AreTasksCompleted(task_ids) : false);
}
inline void ShowClientTasks(Client *client) { if (task_state) { task_state->ShowClientTasks(this, client); }} inline void ShowClientTasks(Client *client) { if (task_state) { task_state->ShowClientTasks(this, client); }}
inline void CancelAllTasks() { if (task_state) { task_state->CancelAllTasks(this); }} inline void CancelAllTasks() { if (task_state) { task_state->CancelAllTasks(this); }}
inline int GetActiveTaskCount() { return (task_state ? task_state->GetActiveTaskCount() : 0); } inline int GetActiveTaskCount() { return (task_state ? task_state->GetActiveTaskCount() : 0); }
@@ -1909,8 +1947,8 @@ private:
uint8 firstlogon; uint8 firstlogon;
uint32 mercid; // current merc uint32 mercid; // current merc
uint8 mercSlot; // selected merc slot uint8 mercSlot; // selected merc slot
bool Buyer; uint32 m_buyer_id;
std::string BuyerWelcomeMessage; uint32 m_barter_time;
int32 m_parcel_platinum; int32 m_parcel_platinum;
int32 m_parcel_gold; int32 m_parcel_gold;
int32 m_parcel_silver; int32 m_parcel_silver;
@@ -1974,9 +2012,10 @@ private:
void ZonePC(uint32 zoneID, uint32 instance_id, float x, float y, float z, float heading, uint8 ignorerestrictions, ZoneMode zm); void ZonePC(uint32 zoneID, uint32 instance_id, float x, float y, float z, float heading, uint8 ignorerestrictions, ZoneMode zm);
void ProcessMovePC(uint32 zoneID, uint32 instance_id, float x, float y, float z, float heading, uint8 ignorerestrictions = 0, ZoneMode zm = ZoneSolicited); void ProcessMovePC(uint32 zoneID, uint32 instance_id, float x, float y, float z, float heading, uint8 ignorerestrictions = 0, ZoneMode zm = ZoneSolicited);
void SendTopLevelInventory();
glm::vec4 m_ZoneSummonLocation; glm::vec4 m_ZoneSummonLocation;
uint16 zonesummon_id; uint16 zonesummon_id;
uint8 zonesummon_instance_id;
uint8 zonesummon_ignorerestrictions; uint8 zonesummon_ignorerestrictions;
ZoneMode zone_mode; ZoneMode zone_mode;
@@ -2019,6 +2058,11 @@ private:
Timer task_request_timer; Timer task_request_timer;
Timer pick_lock_timer; Timer pick_lock_timer;
Timer parcel_timer; //Used to limit the number of parcels to one every 30 seconds (default). Changable via rule. Timer parcel_timer; //Used to limit the number of parcels to one every 30 seconds (default). Changable via rule.
Timer lazy_load_bank_check_timer;
Timer bandolier_throttle_timer;
bool m_lazy_load_bank = false;
int m_lazy_load_sent_bank_slots = 0;
glm::vec3 m_Proximity; glm::vec3 m_Proximity;
glm::vec4 last_position_before_bulk_update; glm::vec4 last_position_before_bulk_update;
@@ -2138,7 +2182,6 @@ private:
bool m_has_quest_compass = false; bool m_has_quest_compass = false;
std::vector<uint32_t> m_dynamic_zone_ids; std::vector<uint32_t> m_dynamic_zone_ids;
public: public:
enum BotOwnerOption : size_t { enum BotOwnerOption : size_t {
booDeathMarquee, booDeathMarquee,
+233 -95
View File
@@ -65,6 +65,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include "../common/repositories/account_repository.h" #include "../common/repositories/account_repository.h"
#include "../common/repositories/character_corpses_repository.h" #include "../common/repositories/character_corpses_repository.h"
#include "../common/repositories/guild_tributes_repository.h" #include "../common/repositories/guild_tributes_repository.h"
#include "../common/repositories/buyer_buy_lines_repository.h"
#include "../common/events/player_event_logs.h" #include "../common/events/player_event_logs.h"
#include "../common/repositories/character_stats_record_repository.h" #include "../common/repositories/character_stats_record_repository.h"
@@ -368,13 +369,7 @@ void MapOpcodes()
ConnectedOpcodes[OP_Save] = &Client::Handle_OP_Save; ConnectedOpcodes[OP_Save] = &Client::Handle_OP_Save;
ConnectedOpcodes[OP_SaveOnZoneReq] = &Client::Handle_OP_SaveOnZoneReq; ConnectedOpcodes[OP_SaveOnZoneReq] = &Client::Handle_OP_SaveOnZoneReq;
ConnectedOpcodes[OP_SelectTribute] = &Client::Handle_OP_SelectTribute; ConnectedOpcodes[OP_SelectTribute] = &Client::Handle_OP_SelectTribute;
ConnectedOpcodes[OP_SenseHeading] = &Client::Handle_OP_SenseHeading;
// Use or Ignore sense heading based on rule.
bool train = RuleB(Skills, TrainSenseHeading);
ConnectedOpcodes[OP_SenseHeading] =
(train) ? &Client::Handle_OP_SenseHeading : &Client::Handle_OP_Ignore;
ConnectedOpcodes[OP_SenseTraps] = &Client::Handle_OP_SenseTraps; ConnectedOpcodes[OP_SenseTraps] = &Client::Handle_OP_SenseTraps;
ConnectedOpcodes[OP_SetGuildMOTD] = &Client::Handle_OP_SetGuildMOTD; ConnectedOpcodes[OP_SetGuildMOTD] = &Client::Handle_OP_SetGuildMOTD;
ConnectedOpcodes[OP_SetRunMode] = &Client::Handle_OP_SetRunMode; ConnectedOpcodes[OP_SetRunMode] = &Client::Handle_OP_SetRunMode;
@@ -764,8 +759,6 @@ void Client::CompleteConnect()
entity_list.SendIllusionWearChange(this); entity_list.SendIllusionWearChange(this);
entity_list.SendTraders(this);
SendWearChangeAndLighting(EQ::textures::LastTexture); SendWearChangeAndLighting(EQ::textures::LastTexture);
Mob* pet = GetPet(); Mob* pet = GetPet();
if (pet) { if (pet) {
@@ -793,6 +786,8 @@ void Client::CompleteConnect()
parse->EventPlayer(EVENT_ENTER_ZONE, this, "", 0); parse->EventPlayer(EVENT_ENTER_ZONE, this, "", 0);
} }
DeleteEntityVariable(SEE_BUFFS_FLAG);
// the way that the client deals with positions during the initial spawn struct // the way that the client deals with positions during the initial spawn struct
// is subtly different from how it deals with getting a position update // is subtly different from how it deals with getting a position update
// if a mob is slightly in the wall or slightly clipping a floor they will be // if a mob is slightly in the wall or slightly clipping a floor they will be
@@ -939,6 +934,7 @@ void Client::CompleteConnect()
} }
database.LoadAuras(this); // this ends up spawning them so probably safer to load this later (here) database.LoadAuras(this); // this ends up spawning them so probably safer to load this later (here)
database.LoadCharacterDisciplines(this);
entity_list.RefreshClientXTargets(this); entity_list.RefreshClientXTargets(this);
@@ -1323,7 +1319,6 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
database.LoadCharacterInspectMessage(cid, &m_inspect_message); /* Load Character Inspect Message */ database.LoadCharacterInspectMessage(cid, &m_inspect_message); /* Load Character Inspect Message */
database.LoadCharacterSpellBook(cid, &m_pp); /* Load Character Spell Book */ database.LoadCharacterSpellBook(cid, &m_pp); /* Load Character Spell Book */
database.LoadCharacterMemmedSpells(cid, &m_pp); /* Load Character Memorized Spells */ database.LoadCharacterMemmedSpells(cid, &m_pp); /* Load Character Memorized Spells */
database.LoadCharacterDisciplines(cid, &m_pp); /* Load Character Disciplines */
database.LoadCharacterLanguages(cid, &m_pp); /* Load Character Languages */ database.LoadCharacterLanguages(cid, &m_pp); /* Load Character Languages */
database.LoadCharacterLeadershipAbilities(cid, &m_pp); /* Load Character Leadership AA's */ database.LoadCharacterLeadershipAbilities(cid, &m_pp); /* Load Character Leadership AA's */
database.LoadCharacterTribute(this); /* Load CharacterTribute */ database.LoadCharacterTribute(this); /* Load CharacterTribute */
@@ -3290,6 +3285,10 @@ void Client::Handle_OP_AugmentItem(const EQApplicationPacket *app)
} }
} }
if (new_aug->GetItem()->Attuneable) {
new_aug->SetAttuned(true);
}
tobe_auged->PutAugment(in_augment->augment_index, *new_aug); tobe_auged->PutAugment(in_augment->augment_index, *new_aug);
tobe_auged->UpdateOrnamentationInfo(); tobe_auged->UpdateOrnamentationInfo();
@@ -3643,17 +3642,15 @@ void Client::Handle_OP_AutoFire(const EQApplicationPacket *app)
void Client::Handle_OP_Bandolier(const EQApplicationPacket *app) void Client::Handle_OP_Bandolier(const EQApplicationPacket *app)
{ {
// Although there are three different structs for OP_Bandolier, they are all the same size. // Although there are three different structs for OP_Bandolier, they are all the same size.
//
if (app->size != sizeof(BandolierCreate_Struct)) { if (app->size != sizeof(BandolierCreate_Struct)) {
LogDebug("Size mismatch in OP_Bandolier expected [{}] got [{}]", sizeof(BandolierCreate_Struct), app->size); LogDebug("Size mismatch in OP_Bandolier expected [{}] got [{}]", sizeof(BandolierCreate_Struct), app->size);
DumpPacket(app); DumpPacket(app);
return; return;
} }
BandolierCreate_Struct *bs = (BandolierCreate_Struct*)app->pBuffer; auto bs = (BandolierCreate_Struct*) app->pBuffer;
switch (bs->Action) switch (bs->Action) {
{
case bandolierCreate: case bandolierCreate:
CreateBandolier(app); CreateBandolier(app);
break; break;
@@ -3661,6 +3658,18 @@ void Client::Handle_OP_Bandolier(const EQApplicationPacket *app)
RemoveBandolier(app); RemoveBandolier(app);
break; break;
case bandolierSet: case bandolierSet:
if (bandolier_throttle_timer.GetDuration() && !bandolier_throttle_timer.Check()) {
Message(
Chat::White,
fmt::format(
"You may only modify your bandolier once every {}.",
Strings::ToLower(Strings::MillisecondsToTime(RuleI(Character, BandolierSwapDelay)))
).c_str()
);
SendTopLevelInventory();
break;
}
SetBandolier(app); SetBandolier(app);
break; break;
default: default:
@@ -3763,116 +3772,104 @@ void Client::Handle_OP_Barter(const EQApplicationPacket *app)
} }
char* Buf = (char *)app->pBuffer; char* Buf = (char *)app->pBuffer;
auto in = (BuyerGeneric_Struct *)app->pBuffer;
// The first 4 bytes of the packet determine the action. A lot of Barter packets require the // The first 4 bytes of the packet determine the action. A lot of Barter packets require the
// packet the client sent, sent back to it as an acknowledgement. // packet the client sent, sent back to it as an acknowledgement.
// //
uint32 Action = VARSTRUCT_DECODE_TYPE(uint32, Buf); //uint32 Action = VARSTRUCT_DECODE_TYPE(uint32, Buf);
switch (Action) switch (in->action) {
{
case Barter_BuyerSearch: case Barter_BuyerSearch: {
{
BuyerItemSearch(app); BuyerItemSearch(app);
break; break;
} }
case Barter_SellerSearch: case Barter_SellerSearch: {
{ auto bsr = (BarterSearchRequest_Struct *) app->pBuffer;
BarterSearchRequest_Struct *bsr = (BarterSearchRequest_Struct*)app->pBuffer; SendBuyerResults(*bsr);
SendBuyerResults(bsr->SearchString, bsr->SearchID);
break; break;
} }
case Barter_BuyerModeOn: case Barter_BuyerModeOn: {
{
if (!IsTrader()) { if (!IsTrader()) {
ToggleBuyerMode(true); ToggleBuyerMode(true);
} }
else { else {
Buf = (char *)app->pBuffer; ToggleBuyerMode(false);
VARSTRUCT_ENCODE_TYPE(uint32, Buf, Barter_BuyerModeOff);
Message(Chat::Red, "You cannot be a Trader and Buyer at the same time."); Message(Chat::Red, "You cannot be a Trader and Buyer at the same time.");
} }
QueuePacket(app);
break; break;
} }
case Barter_BuyerModeOff: case Barter_BuyerModeOff: {
{
QueuePacket(app);
ToggleBuyerMode(false); ToggleBuyerMode(false);
break; break;
} }
case Barter_BuyerItemUpdate: case Barter_BuyerItemUpdate: {
{ ModifyBuyLine(app);
UpdateBuyLine(app);
break; break;
} }
case Barter_BuyerItemRemove: case Barter_BuyerItemStart: {
{ CreateStartingBuyLines(app);
BuyerRemoveItem_Struct* bris = (BuyerRemoveItem_Struct*)app->pBuffer; break;
database.RemoveBuyLine(CharacterID(), bris->BuySlot); }
case Barter_BuyerItemRemove: {
auto bris = (BuyerRemoveItem_Struct *) app->pBuffer;
BuyerBuyLinesRepository::DeleteBuyLine(database, CharacterID(), bris->buy_slot_id);
QueuePacket(app); QueuePacket(app);
break; break;
} }
case Barter_SellItem: case Barter_SellItem: {
{
SellToBuyer(app); SellToBuyer(app);
break; break;
} }
case Barter_BuyerInspectBegin: case Barter_BuyerInspectBegin: {
{
ShowBuyLines(app); ShowBuyLines(app);
break; break;
} }
case Barter_BuyerInspectEnd: case Barter_BuyerInspectEnd: {
{ auto bir = (BuyerInspectRequest_Struct *) app->pBuffer;
BuyerInspectRequest_Struct* bir = (BuyerInspectRequest_Struct*)app->pBuffer; auto buyer = entity_list.GetClientByID(bir->buyer_id);
Client *Buyer = entity_list.GetClientByID(bir->BuyerID); if (buyer) {
if (Buyer) buyer->WithCustomer(0);
Buyer->WithCustomer(0); }
break; break;
} }
case Barter_BarterItemInspect: {
auto bislr = (BarterItemSearchLinkRequest_Struct *) app->pBuffer;
const EQ::ItemData *item = database.GetItem(bislr->item_id);
case Barter_BarterItemInspect: if (!item) {
{
BarterItemSearchLinkRequest_Struct* bislr = (BarterItemSearchLinkRequest_Struct*)app->pBuffer;
const EQ::ItemData* item = database.GetItem(bislr->ItemID);
if (!item)
Message(Chat::Red, "Error: This item does not exist!"); Message(Chat::Red, "Error: This item does not exist!");
else return;
{ }
EQ::ItemInstance *inst = database.CreateItem(item); EQ::ItemInstance *inst = database.CreateItem(item);
if (inst) if (inst) {
{
SendItemPacket(0, inst, ItemPacketViewLink); SendItemPacket(0, inst, ItemPacketViewLink);
safe_delete(inst); safe_delete(inst);
} }
}
break; break;
} }
case Barter_Welcome: case Barter_Welcome:
{ {
//SendBazaarWelcome(); SendBarterWelcome();
break; break;
} }
case Barter_WelcomeMessageUpdate: case Barter_WelcomeMessageUpdate: {
{ auto bwmu = (BuyerWelcomeMessageUpdate_Struct *) app->pBuffer;
BuyerWelcomeMessageUpdate_Struct* bwmu = (BuyerWelcomeMessageUpdate_Struct*)app->pBuffer; SetBuyerWelcomeMessage(bwmu->welcome_message);
SetBuyerWelcomeMessage(bwmu->WelcomeMessage);
break; break;
} }
@@ -3896,15 +3893,20 @@ void Client::Handle_OP_Barter(const EQApplicationPacket *app)
break; break;
} }
case Barter_Unknown23: case Barter_Greeting:
{ {
// Sent by SoD client for no discernible reason. auto data = (BuyerGreeting_Struct *)app->pBuffer;
SendBuyerGreeting(data->buyer_id);
}
case Barter_OpenBarterWindow:
{
SendBulkBazaarBuyers();
break; break;
} }
default: default:
Message(Chat::Red, "Unrecognised Barter action."); Message(Chat::Red, "Unrecognised Barter action.");
LogTrading("Unrecognised Barter Action [{}]", Action); LogTrading("Unrecognised Barter Action [{}]", in->action);
} }
} }
@@ -4986,6 +4988,10 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) {
TraderEndTrader(); TraderEndTrader();
} }
if (IsBuyer()) {
ToggleBuyerMode(false);
}
/* Break Hide if moving without sneaking and set rewind timer if moved */ /* Break Hide if moving without sneaking and set rewind timer if moved */
if ((hidden || improved_hidden) && !sneaking) { if ((hidden || improved_hidden) && !sneaking) {
hidden = false; hidden = false;
@@ -10514,7 +10520,7 @@ void Client::Handle_OP_MercenaryCommand(const EQApplicationPacket *app)
//check to see if selected option is a valid stance slot (option is the slot the stance is in, not the actual stance) //check to see if selected option is a valid stance slot (option is the slot the stance is in, not the actual stance)
if (option >= 0 && option < numStances) if (option >= 0 && option < numStances)
{ {
merc->SetStance((EQ::constants::StanceType)mercTemplate->Stances[option]); merc->SetStance(mercTemplate->Stances[option]);
GetMercInfo().Stance = mercTemplate->Stances[option]; GetMercInfo().Stance = mercTemplate->Stances[option];
Log(Logs::General, Logs::Mercenaries, "Set Stance: %u for %s (%s)", merc->GetStance(), merc->GetName(), GetName()); Log(Logs::General, Logs::Mercenaries, "Set Stance: %u for %s (%s)", merc->GetStance(), merc->GetName(), GetName());
@@ -10913,7 +10919,121 @@ void Client::Handle_OP_MoveItem(const EQApplicationPacket *app)
void Client::Handle_OP_MoveMultipleItems(const EQApplicationPacket *app) void Client::Handle_OP_MoveMultipleItems(const EQApplicationPacket *app)
{ {
Kick("Unimplemented move multiple items"); // TODO: lets not desync though // This packet is only sent from the client if we ctrl click items in inventory
if (m_ClientVersionBit & EQ::versions::maskRoF2AndLater) {
if (!CharacterID()) {
LinkDead();
return;
}
if (app->size < sizeof(MultiMoveItem_Struct)) {
LinkDead();
return; // Not enough data to be a valid packet
}
const MultiMoveItem_Struct* multi_move = reinterpret_cast<const MultiMoveItem_Struct*>(app->pBuffer);
if (app->size != sizeof(MultiMoveItem_Struct) + sizeof(MultiMoveItemSub_Struct) * multi_move->count) {
LinkDead();
return; // Packet size does not match expected size
}
const int16 from_parent = multi_move->moves[0].from_slot.Slot;
const int16 to_parent = multi_move->moves[0].to_slot.Slot;
// CTRL + left click drops an item into a bag without opening it.
// This can be a bag, in which case it tries to fill the target bag with the contents of the bag on the cursor
// CTRL + right click swaps the contents of two bags if a bag is on your cursor and you ctrl-right click on another bag.
// We need to check if this is a swap or just an addition (left click or right click)
// Check if any component of this transaction is coming from anywhere other than the cursor
bool left_click = true;
for (int i = 0; i < multi_move->count; i++) {
if (multi_move->moves[i].from_slot.Slot != EQ::invslot::slotCursor) {
left_click = false;
}
}
// This is a left click which is purely additive. This should always be cursor object or cursor bag contents into general\bank\whatever bag
if (left_click) {
for (int i = 0; i < multi_move->count; i++) {
MoveItem_Struct* mi = new MoveItem_Struct();
mi->from_slot = multi_move->moves[i].from_slot.SubIndex == -1 ? multi_move->moves[i].from_slot.Slot : m_inv.CalcSlotId(multi_move->moves[i].from_slot.Slot, multi_move->moves[i].from_slot.SubIndex);
mi->to_slot = m_inv.CalcSlotId(multi_move->moves[i].to_slot.Slot, multi_move->moves[i].to_slot.SubIndex);
if (multi_move->moves[i].to_slot.Type == EQ::invtype::typeBank) { // Target is bank inventory
mi->to_slot = m_inv.CalcSlotId(multi_move->moves[i].to_slot.Slot + EQ::invslot::BANK_BEGIN, multi_move->moves[i].to_slot.SubIndex);
} else if (multi_move->moves[i].to_slot.Type == EQ::invtype::typeSharedBank) { // Target is shared bank inventory
mi->to_slot = m_inv.CalcSlotId(multi_move->moves[i].to_slot.Slot + EQ::invslot::SHARED_BANK_BEGIN, multi_move->moves[i].to_slot.SubIndex);
}
// This sends '1' as the stack count for unstackable items, which our titanium-era SwapItem blows up
if (m_inv.GetItem(mi->from_slot)->IsStackable()) {
mi->number_in_stack = multi_move->moves[i].number_in_stack;
} else {
mi->number_in_stack = 0;
}
if (!SwapItem(mi) && IsValidSlot(mi->from_slot) && IsValidSlot(mi->to_slot)) {
bool error = false;
SwapItemResync(mi);
InterrogateInventory(this, false, true, false, error, false);
if (error) {
InterrogateInventory(this, true, false, true, error);
}
}
}
// This is the swap.
// Client behavior is just to move stacks without combining them
// Items get rearranged to fill the 'top' of the bag first
} else {
struct MoveInfo {
EQ::ItemInstance* item;
uint16 to_slot;
};
std::vector<MoveInfo> items;
items.reserve(multi_move->count);
for (int i = 0; i < multi_move->count; i++) {
// These are always bags, so we don't need to worry about raw items in slotCursor
uint16 from_slot = m_inv.CalcSlotId(multi_move->moves[i].from_slot.Slot, multi_move->moves[i].from_slot.SubIndex);
if (multi_move->moves[i].from_slot.Type == EQ::invtype::typeBank) { // Target is bank inventory
from_slot = m_inv.CalcSlotId(multi_move->moves[i].from_slot.Slot + EQ::invslot::BANK_BEGIN, multi_move->moves[i].from_slot.SubIndex);
} else if (multi_move->moves[i].from_slot.Type == EQ::invtype::typeSharedBank) { // Target is shared bank inventory
from_slot = m_inv.CalcSlotId(multi_move->moves[i].from_slot.Slot + EQ::invslot::SHARED_BANK_BEGIN, multi_move->moves[i].from_slot.SubIndex);
}
uint16 to_slot = m_inv.CalcSlotId(multi_move->moves[i].to_slot.Slot, multi_move->moves[i].to_slot.SubIndex);
if (multi_move->moves[i].to_slot.Type == EQ::invtype::typeBank) { // Target is bank inventory
to_slot = m_inv.CalcSlotId(multi_move->moves[i].to_slot.Slot + EQ::invslot::BANK_BEGIN, multi_move->moves[i].to_slot.SubIndex);
} else if (multi_move->moves[i].to_slot.Type == EQ::invtype::typeSharedBank) { // Target is shared bank inventory
to_slot = m_inv.CalcSlotId(multi_move->moves[i].to_slot.Slot + EQ::invslot::SHARED_BANK_BEGIN, multi_move->moves[i].to_slot.SubIndex);
}
// I wasn't able to produce any error states on purpose.
MoveInfo move{
.item = m_inv.PopItem(from_slot), // Don't delete the instance here
.to_slot = to_slot
};
if (move.item) {
items.push_back(move);
database.SaveInventory(CharacterID(), NULL, from_slot); // We have to manually save inventory here.
} else {
LinkDead();
return; // Prevent inventory desync here. Forcing a resync would be better, but we don't have a MoveItem struct to work with.
}
}
for (const MoveInfo& move : items) {
PutItemInInventory(move.to_slot, *move.item); // This saves inventory too
}
}
} else {
LinkDead(); // This packet should not be sent by an older client
return;
}
} }
void Client::Handle_OP_OpenContainer(const EQApplicationPacket *app) void Client::Handle_OP_OpenContainer(const EQApplicationPacket *app)
@@ -13561,11 +13681,11 @@ void Client::Handle_OP_Sacrifice(const EQApplicationPacket *app)
} }
if (ss->Confirm) { if (ss->Confirm) {
Client *Caster = entity_list.GetClientByName(SacrificeCaster.c_str()); Mob *Caster = entity_list.GetMob(sacrifice_caster_id);
if (Caster) Sacrifice(Caster); if (Caster) Sacrifice(Caster);
} }
PendingSacrifice = false; PendingSacrifice = false;
SacrificeCaster.clear(); sacrifice_caster_id = 0;
} }
void Client::Handle_OP_SafeFallSuccess(const EQApplicationPacket *app) // bit of a misnomer, sent whenever safe fall is used (success of fail) void Client::Handle_OP_SafeFallSuccess(const EQApplicationPacket *app) // bit of a misnomer, sent whenever safe fall is used (success of fail)
@@ -13608,16 +13728,26 @@ void Client::Handle_OP_SelectTribute(const EQApplicationPacket *app)
void Client::Handle_OP_SenseHeading(const EQApplicationPacket *app) void Client::Handle_OP_SenseHeading(const EQApplicationPacket *app)
{ {
if (!HasSkill(EQ::skills::SkillSenseHeading)) if (!HasSkill(EQ::skills::SkillSenseHeading)) {
return; return;
}
int chancemod = 0; if (RuleB(Skills, TrainSenseHeading)) {
CheckIncreaseSkill(EQ::skills::SkillSenseHeading, nullptr, 0);
CheckIncreaseSkill(EQ::skills::SkillSenseHeading, nullptr, chancemod);
return; return;
} }
if (parse->PlayerHasQuestSub(EVENT_USE_SKILL)) {
const auto& export_string = fmt::format(
"{} {}",
EQ::skills::SkillSenseHeading,
GetRawSkill(EQ::skills::SkillSenseHeading)
);
parse->EventPlayer(EVENT_USE_SKILL, this, export_string, 0);
}
}
void Client::Handle_OP_SenseTraps(const EQApplicationPacket *app) void Client::Handle_OP_SenseTraps(const EQApplicationPacket *app)
{ {
if (!HasSkill(EQ::skills::SkillSenseTraps)) if (!HasSkill(EQ::skills::SkillSenseTraps))
@@ -14868,14 +14998,15 @@ void Client::Handle_OP_Split(const EQApplicationPacket *app)
Group *group = nullptr; Group *group = nullptr;
Raid *raid = nullptr; Raid *raid = nullptr;
if (IsRaidGrouped()) if (IsRaidGrouped()) {
raid = GetRaid(); raid = GetRaid();
else if (IsGrouped()) } else if (IsGrouped()) {
group = GetGroup(); group = GetGroup();
}
// is there an actual error message for this? // is there an actual error message for this?
if (raid == nullptr && group == nullptr) { if (raid == nullptr && group == nullptr) {
Message(Chat::Red, "You can not split money if you're not in a group."); MessageString(Chat::Red, SPLIT_NO_GROUP);
return; return;
} }
@@ -14883,14 +15014,15 @@ void Client::Handle_OP_Split(const EQApplicationPacket *app)
10 * static_cast<uint64>(split->silver) + 10 * static_cast<uint64>(split->silver) +
100 * static_cast<uint64>(split->gold) + 100 * static_cast<uint64>(split->gold) +
1000 * static_cast<uint64>(split->platinum))) { 1000 * static_cast<uint64>(split->platinum))) {
Message(Chat::Red, "You do not have enough money to do that split."); MessageString(Chat::Red, SPLIT_FAIL);
return; return;
} }
if (raid) if (raid) {
raid->SplitMoney(raid->GetGroup(this), split->copper, split->silver, split->gold, split->platinum); raid->SplitMoney(raid->GetGroup(this), split->copper, split->silver, split->gold, split->platinum);
else if (group) } else if (group) {
group->SplitMoney(split->copper, split->silver, split->gold, split->platinum, this); group->SplitMoney(split->copper, split->silver, split->gold, split->platinum, this, true);
}
return; return;
@@ -15518,7 +15650,7 @@ void Client::Handle_OP_Trader(const EQApplicationPacket *app)
break; break;
} }
case TraderOn: { case TraderOn: {
if (Buyer) { if (IsBuyer()) {
TraderEndTrader(); TraderEndTrader();
Message(Chat::Red, "You cannot be a Trader and Buyer at the same time."); Message(Chat::Red, "You cannot be a Trader and Buyer at the same time.");
return; return;
@@ -15564,7 +15696,7 @@ void Client::Handle_OP_TraderBuy(const EQApplicationPacket *app)
auto trader = entity_list.GetClientByID(in->trader_id); auto trader = entity_list.GetClientByID(in->trader_id);
switch (in->method) { switch (in->method) {
case ByVendor: { case BazaarByVendor: {
if (trader) { if (trader) {
LogTrading("Buy item directly from vendor id <green>[{}] item_id <green>[{}] quantity <green>[{}] " LogTrading("Buy item directly from vendor id <green>[{}] item_id <green>[{}] quantity <green>[{}] "
"serial_number <green>[{}]", "serial_number <green>[{}]",
@@ -15577,7 +15709,7 @@ void Client::Handle_OP_TraderBuy(const EQApplicationPacket *app)
} }
break; break;
} }
case ByParcel: { case BazaarByParcel: {
if (!RuleB(Parcel, EnableParcelMerchants) || !RuleB(Bazaar, EnableParcelDelivery)) { if (!RuleB(Parcel, EnableParcelMerchants) || !RuleB(Bazaar, EnableParcelDelivery)) {
LogTrading( LogTrading(
"Bazaar purchase attempt by parcel delivery though 'Parcel:EnableParcelMerchants' or " "Bazaar purchase attempt by parcel delivery though 'Parcel:EnableParcelMerchants' or "
@@ -15587,7 +15719,7 @@ void Client::Handle_OP_TraderBuy(const EQApplicationPacket *app)
Chat::Yellow, Chat::Yellow,
"The bazaar parcel delivey system is not enabled on this server. Please visit the vendor directly in the Bazaar." "The bazaar parcel delivey system is not enabled on this server. Please visit the vendor directly in the Bazaar."
); );
in->method = ByParcel; in->method = BazaarByParcel;
in->sub_action = Failed; in->sub_action = Failed;
TradeRequestFailed(app); TradeRequestFailed(app);
return; return;
@@ -15602,7 +15734,7 @@ void Client::Handle_OP_TraderBuy(const EQApplicationPacket *app)
BuyTraderItemOutsideBazaar(in, app); BuyTraderItemOutsideBazaar(in, app);
break; break;
} }
case ByDirectToInventory: { case BazaarByDirectToInventory: {
if (!RuleB(Parcel, EnableDirectToInventoryDelivery)) { if (!RuleB(Parcel, EnableDirectToInventoryDelivery)) {
LogTrading("Bazaar purchase attempt by direct inventory delivery though " LogTrading("Bazaar purchase attempt by direct inventory delivery though "
"'Parcel:EnableDirectToInventoryDelivery' not enabled." "'Parcel:EnableDirectToInventoryDelivery' not enabled."
@@ -15611,7 +15743,7 @@ void Client::Handle_OP_TraderBuy(const EQApplicationPacket *app)
Chat::Yellow, Chat::Yellow,
"Direct inventory delivey is not enabled on this server. Please visit the vendor directly." "Direct inventory delivey is not enabled on this server. Please visit the vendor directly."
); );
in->method = ByDirectToInventory; in->method = BazaarByDirectToInventory;
in->sub_action = Failed; in->sub_action = Failed;
TradeRequestFailed(app); TradeRequestFailed(app);
return; return;
@@ -15628,7 +15760,7 @@ void Client::Handle_OP_TraderBuy(const EQApplicationPacket *app)
Chat::Yellow, Chat::Yellow,
"Direct inventory delivey is not yet implemented. Please visit the vendor directly or purchase via parcel delivery." "Direct inventory delivey is not yet implemented. Please visit the vendor directly or purchase via parcel delivery."
); );
in->method = ByDirectToInventory; in->method = BazaarByDirectToInventory;
in->sub_action = Failed; in->sub_action = Failed;
TradeRequestFailed(app); TradeRequestFailed(app);
break; break;
@@ -16027,7 +16159,8 @@ void Client::Handle_OP_WearChange(const EQApplicationPacket *app)
wc->hero_forge_model = GetHerosForgeModel(wc->wear_slot_id); wc->hero_forge_model = GetHerosForgeModel(wc->wear_slot_id);
// we could maybe ignore this and just send our own from moveitem // we could maybe ignore this and just send our own from moveitem
entity_list.QueueClients(this, app, false); // We probably need to skip this entirely when it is send as an ack, but not sure how to ID that.
entity_list.QueueClients(this, app, true);
} }
void Client::Handle_OP_WhoAllRequest(const EQApplicationPacket *app) void Client::Handle_OP_WhoAllRequest(const EQApplicationPacket *app)
@@ -16774,9 +16907,14 @@ void Client::Handle_OP_RaidClearNPCMarks(const EQApplicationPacket* app)
void Client::RecordStats() void Client::RecordStats()
{ {
const uint32 character_id = CharacterID();
if (!character_id) {
return;
}
auto r = CharacterStatsRecordRepository::FindOne( auto r = CharacterStatsRecordRepository::FindOne(
database, database,
CharacterID() character_id
); );
r.status = Admin(); r.status = Admin();
@@ -16854,7 +16992,7 @@ void Client::RecordStats()
if (r.character_id > 0) { if (r.character_id > 0) {
CharacterStatsRecordRepository::UpdateOne(database, r); CharacterStatsRecordRepository::UpdateOne(database, r);
} else { } else {
r.character_id = CharacterID(); r.character_id = character_id;
r.created_at = std::time(nullptr); r.created_at = std::time(nullptr);
CharacterStatsRecordRepository::InsertOne(database, r); CharacterStatsRecordRepository::InsertOne(database, r);
} }
+87 -26
View File
@@ -289,6 +289,37 @@ bool Client::Process() {
entity_list.ScanCloseMobs(close_mobs, this, IsMoving()); entity_list.ScanCloseMobs(close_mobs, this, IsMoving());
} }
if (RuleB(Inventory, LazyLoadBank)) {
// poll once a second to see if we are close to a banker and we haven't loaded the bank yet
if (!m_lazy_load_bank && lazy_load_bank_check_timer.Check()) {
if (m_lazy_load_sent_bank_slots <= EQ::invslot::SHARED_BANK_END && IsCloseToBanker()) {
m_lazy_load_bank = true;
lazy_load_bank_check_timer.Disable();
}
}
if (m_lazy_load_bank && m_lazy_load_sent_bank_slots <= EQ::invslot::SHARED_BANK_END) {
const EQ::ItemInstance *inst = nullptr;
// Jump the gaps
if (m_lazy_load_sent_bank_slots < EQ::invslot::BANK_BEGIN) {
m_lazy_load_sent_bank_slots = EQ::invslot::BANK_BEGIN;
}
else if (m_lazy_load_sent_bank_slots > EQ::invslot::BANK_END &&
m_lazy_load_sent_bank_slots < EQ::invslot::SHARED_BANK_BEGIN) {
m_lazy_load_sent_bank_slots = EQ::invslot::SHARED_BANK_BEGIN;
}
else {
m_lazy_load_sent_bank_slots++;
}
inst = m_inv[m_lazy_load_sent_bank_slots];
if (inst) {
SendItemPacket(m_lazy_load_sent_bank_slots, inst, ItemPacketType::ItemPacketTrade);
}
}
}
bool may_use_attacks = false; bool may_use_attacks = false;
/* /*
Things which prevent us from attacking: Things which prevent us from attacking:
@@ -321,48 +352,46 @@ bool Client::Process() {
auto_fire = false; auto_fire = false;
} }
EQ::ItemInstance *ranged = GetInv().GetItem(EQ::invslot::slotRange); EQ::ItemInstance *ranged = GetInv().GetItem(EQ::invslot::slotRange);
if (ranged) if (ranged) {
{
if (ranged->GetItem() && ranged->GetItem()->ItemType == EQ::item::ItemTypeBow) { if (ranged->GetItem() && ranged->GetItem()->ItemType == EQ::item::ItemTypeBow) {
if (ranged_timer.Check(false)) { if (ranged_timer.Check(false)) {
if (GetTarget() && (GetTarget()->IsNPC() || GetTarget()->IsClient()) && IsAttackAllowed(GetTarget())) { if (GetTarget() && (GetTarget()->IsNPC() || GetTarget()->IsClient()) && IsAttackAllowed(GetTarget())) {
if (GetTarget()->InFrontMob(this, GetTarget()->GetX(), GetTarget()->GetY())) { if (GetTarget()->InFrontMob(this, GetTarget()->GetX(), GetTarget()->GetY())) {
if (CheckLosFN(GetTarget()) && CheckWaterAutoFireLoS(GetTarget())) { if (CheckLosFN(GetTarget()) && CheckWaterAutoFireLoS(GetTarget())) {
//client has built in los check, but auto fire does not.. done last. //client has built in los check, but auto fire does not.. done last.
RangedAttack(GetTarget()); if (RangedAttack(GetTarget()) && CheckDoubleRangedAttack()) {
if (CheckDoubleRangedAttack())
RangedAttack(GetTarget(), true); RangedAttack(GetTarget(), true);
} }
else } else {
ranged_timer.Start(); ranged_timer.Start();
} }
else } else {
ranged_timer.Start(); ranged_timer.Start();
} }
else } else {
ranged_timer.Start(); ranged_timer.Start();
} }
} }
else if (ranged->GetItem() && (ranged->GetItem()->ItemType == EQ::item::ItemTypeLargeThrowing || ranged->GetItem()->ItemType == EQ::item::ItemTypeSmallThrowing)) { } else if (ranged->GetItem() && (ranged->GetItem()->ItemType == EQ::item::ItemTypeLargeThrowing || ranged->GetItem()->ItemType == EQ::item::ItemTypeSmallThrowing)) {
if (ranged_timer.Check(false)) { if (ranged_timer.Check(false)) {
if (GetTarget() && (GetTarget()->IsNPC() || GetTarget()->IsClient()) && IsAttackAllowed(GetTarget())) { if (GetTarget() && (GetTarget()->IsNPC() || GetTarget()->IsClient()) && IsAttackAllowed(GetTarget())) {
if (GetTarget()->InFrontMob(this, GetTarget()->GetX(), GetTarget()->GetY())) { if (GetTarget()->InFrontMob(this, GetTarget()->GetX(), GetTarget()->GetY())) {
if (CheckLosFN(GetTarget()) && CheckWaterAutoFireLoS(GetTarget())) { if (CheckLosFN(GetTarget()) && CheckWaterAutoFireLoS(GetTarget())) {
//client has built in los check, but auto fire does not.. done last. //client has built in los check, but auto fire does not.. done last.
ThrowingAttack(GetTarget()); ThrowingAttack(GetTarget());
} } else {
else
ranged_timer.Start(); ranged_timer.Start();
} }
else } else {
ranged_timer.Start(); ranged_timer.Start();
} }
else } else {
ranged_timer.Start(); ranged_timer.Start();
} }
} }
} }
} }
}
Mob *auto_attack_target = GetTarget(); Mob *auto_attack_target = GetTarget();
if (auto_attack && auto_attack_target != nullptr && may_use_attacks && attack_timer.Check()) { if (auto_attack && auto_attack_target != nullptr && may_use_attacks && attack_timer.Check()) {
@@ -782,6 +811,7 @@ void Client::BulkSendInventoryItems()
last_pos = ob.tellp(); last_pos = ob.tellp();
} }
if (!RuleB(Inventory, LazyLoadBank)) {
// Bank items // Bank items
for (int16 slot_id = EQ::invslot::BANK_BEGIN; slot_id <= EQ::invslot::BANK_END; slot_id++) { for (int16 slot_id = EQ::invslot::BANK_BEGIN; slot_id <= EQ::invslot::BANK_END; slot_id++) {
const EQ::ItemInstance* inst = m_inv[slot_id]; const EQ::ItemInstance* inst = m_inv[slot_id];
@@ -809,6 +839,7 @@ void Client::BulkSendInventoryItems()
last_pos = ob.tellp(); last_pos = ob.tellp();
} }
}
auto outapp = new EQApplicationPacket(OP_CharInventory); auto outapp = new EQApplicationPacket(OP_CharInventory);
outapp->size = ob.size(); outapp->size = ob.size();
@@ -1034,32 +1065,62 @@ void Client::OPRezzAnswer(uint32 Action, uint32 SpellID, uint16 ZoneID, uint16 I
name, (uint16)spells[SpellID].base_value[0], name, (uint16)spells[SpellID].base_value[0],
SpellID, ZoneID, InstanceID); SpellID, ZoneID, InstanceID);
if (RuleB(Spells, BuffsFadeOnDeath)) { const bool use_old_resurrection = (
BuffFadeNonPersistDeath(); RuleB(Character, UseOldRaceRezEffects) &&
} (
GetRace() == Race::Barbarian ||
GetRace() == Race::Dwarf ||
GetRace() == Race::Troll ||
GetRace() == Race::Ogre
)
);
const uint16 resurrection_sickness_spell_id = (
use_old_resurrection ?
RuleI(Character, OldResurrectionSicknessSpellID) :
RuleI(Character, ResurrectionSicknessSpellID)
);
int SpellEffectDescNum = GetSpellEffectDescriptionNumber(SpellID); int SpellEffectDescNum = GetSpellEffectDescriptionNumber(SpellID);
// Rez spells with Rez effects have this DescNum (first is Titanium, second is 6.2 Client) // Rez spells with Rez effects have this DescNum (first is Titanium, second is 6.2 Client)
if(RuleB(Character, UseResurrectionSickness) && SpellEffectDescNum == 82 || SpellEffectDescNum == 39067) { if(RuleB(Character, UseResurrectionSickness) && SpellEffectDescNum == 82 || SpellEffectDescNum == 39067) {
SetHP(GetMaxHP() / 5); SetHP(GetMaxHP() / 5);
SetMana(0); SetMana(0);
int resurrection_sickness_spell_id = (
RuleB(Character, UseOldRaceRezEffects) && if (RuleB(Spells, BuffsFadeOnDeath)) {
( BuffFadeNonPersistDeath();
GetRace() == BARBARIAN || }
GetRace() == DWARF ||
GetRace() == TROLL ||
GetRace() == OGRE
) ?
RuleI(Character, OldResurrectionSicknessSpellID) :
RuleI(Character, ResurrectionSicknessSpellID)
);
SpellOnTarget(resurrection_sickness_spell_id, this); SpellOnTarget(resurrection_sickness_spell_id, this);
} else if (SpellID == SPELL_DIVINE_REZ) { } else if (SpellID == SPELL_DIVINE_REZ) {
if (RuleB(Spells, BuffsFadeOnDeath)) {
BuffFadeNonPersistDeath();
}
RestoreHealth(); RestoreHealth();
RestoreMana(); RestoreMana();
RestoreEndurance(); RestoreEndurance();
} else { } else {
if (RuleB(Character, UseResurrectionSickness)) {
bool has_resurrection_sickness = false;
for (int slot = 0; slot < GetMaxTotalSlots(); slot++) {
if (IsValidSpell(buffs[slot].spellid) && IsResurrectionSicknessSpell(buffs[slot].spellid)){
has_resurrection_sickness = true;
break;
}
}
// Need to wipe buffs after checking if client had rez effects.
if (RuleB(Spells, BuffsFadeOnDeath)) {
BuffFadeNonPersistDeath();
}
if (has_resurrection_sickness) {
SpellOnTarget(resurrection_sickness_spell_id, this);
}
}
SetHP(GetMaxHP() / 20); SetHP(GetMaxHP() / 20);
SetMana(GetMaxMana() / 20); SetMana(GetMaxMana() / 20);
SetEndurance(GetMaxEndurance() / 20); SetEndurance(GetMaxEndurance() / 20);
+3 -1
View File
@@ -112,7 +112,7 @@ int command_init(void)
command_add("delpetition", "[petition number] - Delete a petition", AccountStatus::ApprenticeGuide, command_delpetition) || command_add("delpetition", "[petition number] - Delete a petition", AccountStatus::ApprenticeGuide, command_delpetition) ||
command_add("depop", "[Start Spawn Timer] - Depop your NPC target and optionally start their spawn timer (false by default)", AccountStatus::Guide, command_depop) || command_add("depop", "[Start Spawn Timer] - Depop your NPC target and optionally start their spawn timer (false by default)", AccountStatus::Guide, command_depop) ||
command_add("depopzone", "[Start Spawn Timers] - Depop the zone and optionally start spawn timers (false by default)", AccountStatus::GMAdmin, command_depopzone) || command_add("depopzone", "[Start Spawn Timers] - Depop the zone and optionally start spawn timers (false by default)", AccountStatus::GMAdmin, command_depopzone) ||
command_add("devtools", "[Enable|Disable] - Manages Developer Tools (send no parameter for menu)", AccountStatus::GMMgmt, command_devtools) || command_add("devtools", "[menu|window] [enable|disable] - Manages Developer Tools (send no parameter for menu)", AccountStatus::GMMgmt, command_devtools) ||
command_add("disablerecipe", "[Recipe ID] - Disables a Recipe", AccountStatus::QuestTroupe, command_disablerecipe) || command_add("disablerecipe", "[Recipe ID] - Disables a Recipe", AccountStatus::QuestTroupe, command_disablerecipe) ||
command_add("disarmtrap", "Analog for ldon disarm trap for the newer clients since we still don't have it working.", AccountStatus::QuestTroupe, command_disarmtrap) || command_add("disarmtrap", "Analog for ldon disarm trap for the newer clients since we still don't have it working.", AccountStatus::QuestTroupe, command_disarmtrap) ||
command_add("door", "Door editing command", AccountStatus::QuestTroupe, command_door) || command_add("door", "Door editing command", AccountStatus::QuestTroupe, command_door) ||
@@ -134,6 +134,7 @@ int command_init(void)
command_add("fish", "Fish for an item", AccountStatus::QuestTroupe, command_fish) || command_add("fish", "Fish for an item", AccountStatus::QuestTroupe, command_fish) ||
command_add("fixmob", "[race|gender|texture|helm|face|hair|haircolor|beard|beardcolor|heritage|tattoo|detail] [next|prev] - Manipulate appearance of your target", AccountStatus::QuestTroupe, command_fixmob) || command_add("fixmob", "[race|gender|texture|helm|face|hair|haircolor|beard|beardcolor|heritage|tattoo|detail] [next|prev] - Manipulate appearance of your target", AccountStatus::QuestTroupe, command_fixmob) ||
command_add("flagedit", "Edit zone flags on your target. Use #flagedit help for more info.", AccountStatus::GMAdmin, command_flagedit) || command_add("flagedit", "Edit zone flags on your target. Use #flagedit help for more info.", AccountStatus::GMAdmin, command_flagedit) ||
command_add("fleeinfo", "- Gives info about whether a NPC will flee or not, using the command issuer as top hate.", AccountStatus::QuestTroupe, command_fleeinfo) ||
command_add("forage", "Forage an item", AccountStatus::QuestTroupe, command_forage) || command_add("forage", "Forage an item", AccountStatus::QuestTroupe, command_forage) ||
command_add("gearup", "Developer tool to quickly equip yourself or your target", AccountStatus::GMMgmt, command_gearup) || command_add("gearup", "Developer tool to quickly equip yourself or your target", AccountStatus::GMMgmt, command_gearup) ||
command_add("giveitem", "[itemid] [charges] - Summon an item onto your target's cursor. Charges are optional.", AccountStatus::GMMgmt, command_giveitem) || command_add("giveitem", "[itemid] [charges] - Summon an item onto your target's cursor. Charges are optional.", AccountStatus::GMMgmt, command_giveitem) ||
@@ -829,6 +830,7 @@ void command_bot(Client *c, const Seperator *sep)
#include "gm_commands/fish.cpp" #include "gm_commands/fish.cpp"
#include "gm_commands/fixmob.cpp" #include "gm_commands/fixmob.cpp"
#include "gm_commands/flagedit.cpp" #include "gm_commands/flagedit.cpp"
#include "gm_commands/fleeinfo.cpp"
#include "gm_commands/forage.cpp" #include "gm_commands/forage.cpp"
#include "gm_commands/gearup.cpp" #include "gm_commands/gearup.cpp"
#include "gm_commands/giveitem.cpp" #include "gm_commands/giveitem.cpp"
+1
View File
@@ -87,6 +87,7 @@ void command_find(Client *c, const Seperator *sep);
void command_fish(Client* c, const Seperator* sep); void command_fish(Client* c, const Seperator* sep);
void command_fixmob(Client *c, const Seperator *sep); void command_fixmob(Client *c, const Seperator *sep);
void command_flagedit(Client *c, const Seperator *sep); void command_flagedit(Client *c, const Seperator *sep);
void command_fleeinfo(Client *c, const Seperator *sep);
void command_forage(Client* c, const Seperator* sep); void command_forage(Client* c, const Seperator* sep);
void command_gearup(Client *c, const Seperator *sep); void command_gearup(Client *c, const Seperator *sep);
void command_giveitem(Client *c, const Seperator *sep); void command_giveitem(Client *c, const Seperator *sep);
+2 -2
View File
@@ -633,8 +633,8 @@ namespace SBIndex {
constexpr uint16 SKILLATK_PROC_SPELL_ID = 0; // SPA 288 constexpr uint16 SKILLATK_PROC_SPELL_ID = 0; // SPA 288
constexpr uint16 SKILLATK_PROC_CHANCE = 1; // SPA 288 constexpr uint16 SKILLATK_PROC_CHANCE = 1; // SPA 288
constexpr uint16 SKILLATK_PROC_SKILL = 2; // SPA 288 constexpr uint16 SKILLATK_PROC_SKILL = 2; // SPA 288
constexpr uint16 SLAYUNDEAD_RATE_MOD = 0; // SPA 219 constexpr uint16 SLAYUNDEAD_DMG_MOD = 0; // SPA 219
constexpr uint16 SLAYUNDEAD_DMG_MOD = 1; // SPA 219 constexpr uint16 SLAYUNDEAD_RATE_MOD = 1; // SPA 219
constexpr uint16 DOUBLE_RIPOSTE_CHANCE = 0; // SPA 223 constexpr uint16 DOUBLE_RIPOSTE_CHANCE = 0; // SPA 223
constexpr uint16 DOUBLE_RIPOSTE_SKILL_ATK_CHANCE = 1; // SPA 223 constexpr uint16 DOUBLE_RIPOSTE_SKILL_ATK_CHANCE = 1; // SPA 223
constexpr uint16 DOUBLE_RIPOSTE_SKILL = 2; // SPA 223 constexpr uint16 DOUBLE_RIPOSTE_SKILL = 2; // SPA 223
-4
View File
@@ -428,8 +428,6 @@ void DataBucket::BulkLoadEntities(DataBucketLoadType::Type t, std::vector<uint32
return; return;
} }
size_t size_before = g_data_bucket_cache.size();
LogDataBucketsDetail("cache size before [{}] l size [{}]", g_data_bucket_cache.size(), l.size()); LogDataBucketsDetail("cache size before [{}] l size [{}]", g_data_bucket_cache.size(), l.size());
uint32 added_count = 0; uint32 added_count = 0;
@@ -440,8 +438,6 @@ void DataBucket::BulkLoadEntities(DataBucketLoadType::Type t, std::vector<uint32
} }
} }
g_data_bucket_cache.reserve(g_data_bucket_cache.size() + added_count);
for (const auto &e: l) { for (const auto &e: l) {
if (!ExistsInCache(e)) { if (!ExistsInCache(e)) {
LogDataBucketsDetail("bucket id [{}] bucket key [{}] bucket value [{}]", e.id, e.key_, e.value); LogDataBucketsDetail("bucket id [{}] bucket key [{}] bucket value [{}]", e.id, e.key_, e.value);
+2 -2
View File
@@ -542,8 +542,8 @@ void Doors::HandleClick(Client *sender, uint8 trigger)
if (EQ::ValueWithin(m_open_type, 57, 58) && HasDestinationZone()) { if (EQ::ValueWithin(m_open_type, 57, 58) && HasDestinationZone()) {
bool has_key_required = (required_key_item && required_key_item == player_key); bool has_key_required = (required_key_item && required_key_item == player_key);
if (sender->GetGM() && has_key_required) { if (sender->GetGM() && !has_key_required) {
has_key_required = false; has_key_required = true;
sender->Message(Chat::White, "Your GM flag allows you to open this door without a key."); sender->Message(Chat::White, "Your GM flag allows you to open this door without a key.");
} }
+6
View File
@@ -60,6 +60,12 @@ public:
void SetPosition(const glm::vec4 &position); void SetPosition(const glm::vec4 &position);
void SetSize(uint16 size); void SetSize(uint16 size);
void ToggleState(Mob *sender); void ToggleState(Mob *sender);
inline std::string GetDestinationZoneName() { return m_destination_zone_name; }
inline int GetDestinationInstanceID() { return m_destination_instance_id; }
inline float GetDestinationX() { return m_destination.x; }
inline float GetDestinationY() { return m_destination.y; }
inline float GetDestinationZ() { return m_destination.z; }
inline float GetDestinationHeading() { return m_destination.w; }
float GetX(); float GetX();
float GetY(); float GetY();
+63 -105
View File
@@ -1028,17 +1028,8 @@ void Client::SendDisciplineTimer(uint32 timer_id, uint32 duration)
} }
} }
/** void EntityList::AETaunt(Client* taunter, float range, int bonus_hate)
* @param taunter
* @param range
* @param bonus_hate
*/
void EntityList::AETaunt(Client *taunter, float range, int32 bonus_hate)
{ {
/**
* Live AE taunt range - Hardcoded.
*/
if (range == 0) { if (range == 0) {
range = 40; range = 40;
} }
@@ -1060,9 +1051,11 @@ void EntityList::AETaunt(Client *taunter, float range, int32 bonus_hate)
z_difference *= -1; z_difference *= -1;
} }
if (z_difference < 10 if (
&& taunter->IsAttackAllowed(them) z_difference < 10 &&
&& DistanceSquaredNoZ(taunter->GetPosition(), them->GetPosition()) <= range_squared) { taunter->IsAttackAllowed(them) &&
DistanceSquaredNoZ(taunter->GetPosition(), them->GetPosition()) <= range_squared
) {
if (taunter->CheckLosFN(them)) { if (taunter->CheckLosFN(them)) {
taunter->Taunt(them->CastToNPC(), true, 0, true, bonus_hate); taunter->Taunt(them->CastToNPC(), true, 0, true, bonus_hate);
} }
@@ -1070,36 +1063,29 @@ void EntityList::AETaunt(Client *taunter, float range, int32 bonus_hate)
} }
} }
/**
* Causes caster to hit every mob within dist range of center with spell_id
*
* @param caster_mob
* @param center_mob
* @param spell_id
* @param affect_caster
* @param resist_adjust
* @param max_targets
*/
void EntityList::AESpell( void EntityList::AESpell(
Mob* caster_mob, Mob* caster_mob,
Mob* center_mob, Mob* center_mob,
uint16 spell_id, uint16 spell_id,
bool affect_caster, bool affect_caster,
int16 resist_adjust, int16 resist_adjust,
int *max_targets int* max_targets,
bool is_scripted
) )
{ {
const auto &cast_target_position = const auto& cast_target_position = (
spells[spell_id].target_type == ST_Ring ? (!is_scripted && spells[spell_id].target_type == ST_Ring) ?
caster_mob->GetTargetRingLocation() : caster_mob->GetTargetRingLocation() :
static_cast<glm::vec3>(center_mob->GetPosition()); static_cast<glm::vec3>(center_mob->GetPosition())
);
Mob* current_mob = nullptr; Mob* current_mob = nullptr;
bool is_detrimental_spell = IsDetrimentalSpell(spell_id); bool is_detrimental_spell = IsDetrimentalSpell(spell_id);
bool is_npc = caster_mob->IsNPC(); bool is_npc = caster_mob->IsNPC();
float distance = caster_mob->GetAOERange(spell_id); float distance = caster_mob->GetAOERange(spell_id);
float distance_squared = distance * distance; float distance_squared = distance * distance;
float min_range2 = spells[spell_id].min_range * spells[spell_id].min_range; float min_range_squared = spells[spell_id].min_range * spells[spell_id].min_range;
glm::vec2 min = { cast_target_position.x - distance, cast_target_position.y - distance }; glm::vec2 min = { cast_target_position.x - distance, cast_target_position.y - distance };
glm::vec2 max = { cast_target_position.x + distance, cast_target_position.y + distance }; glm::vec2 max = { cast_target_position.x + distance, cast_target_position.y + distance };
@@ -1110,17 +1096,18 @@ void EntityList::AESpell(
max_targets = nullptr; max_targets = nullptr;
} }
/**
* Max AOE targets
*/
int max_targets_allowed = RuleI(Range, AOEMaxTargets); // unlimited int max_targets_allowed = RuleI(Range, AOEMaxTargets); // unlimited
if (max_targets) { // rains pass this in since they need to preserve the count through waves if (max_targets) { // rains pass this in since they need to preserve the count through waves
max_targets_allowed = *max_targets; max_targets_allowed = *max_targets;
} } else if (spells[spell_id].aoe_max_targets) {
else if (spells[spell_id].aoe_max_targets) {
max_targets_allowed = spells[spell_id].aoe_max_targets; max_targets_allowed = spells[spell_id].aoe_max_targets;
} } else if (
else if (IsTargetableAESpell(spell_id) && is_detrimental_spell && !is_npc && !IsEffectInSpell(spell_id, SE_Lull) && !IsEffectInSpell(spell_id, SE_Mez)) { IsTargetableAESpell(spell_id) &&
is_detrimental_spell &&
!is_npc &&
!IsEffectInSpell(spell_id, SE_Lull) &&
!IsEffectInSpell(spell_id, SE_Mez)
) {
max_targets_allowed = 4; max_targets_allowed = 4;
} }
@@ -1161,16 +1148,11 @@ void EntityList::AESpell(
continue; continue;
} }
/** if (spells[spell_id].pcnpc_only_flag == PCNPCOnlyFlagType::PC && !current_mob->IsOfClientBotMerc()) {
* Check PC / NPC
* 1 = PC
* 2 = NPC
*/
if (spells[spell_id].pcnpc_only_flag == 1 && !current_mob->IsOfClientBotMerc()) {
continue; continue;
} }
if (spells[spell_id].pcnpc_only_flag == 2 && current_mob->IsOfClientBotMerc()) { if (spells[spell_id].pcnpc_only_flag == PCNPCOnlyFlagType::NPC && current_mob->IsOfClientBotMerc()) {
continue; continue;
} }
@@ -1184,41 +1166,40 @@ void EntityList::AESpell(
continue; continue;
} }
if (distance_to_target < min_range2) { if (distance_to_target < min_range_squared) {
continue; continue;
} }
if (is_npc && current_mob->IsNPC() && if (
spells[spell_id].target_type != ST_AreaNPCOnly) { //check npc->npc casting is_npc &&
FACTION_VALUE faction_value = current_mob->GetReverseFactionCon(caster_mob); current_mob->IsNPC() &&
spells[spell_id].target_type != ST_AreaNPCOnly
) {
const auto faction_value = current_mob->GetReverseFactionCon(caster_mob);
if (is_detrimental_spell) { if (is_detrimental_spell) {
//affect mobs that are on our hate list, or
//which have bad faction with us
if ( if (
!(caster_mob->CheckAggro(current_mob) || !(caster_mob->CheckAggro(current_mob) ||
faction_value == FACTION_THREATENINGLY || faction_value == FACTION_THREATENINGLY ||
faction_value == FACTION_SCOWLS)) { faction_value == FACTION_SCOWLS)
) {
continue; continue;
} }
} } else {
else {
//only affect mobs we would assist.
if (!(faction_value <= FACTION_AMIABLY)) { if (!(faction_value <= FACTION_AMIABLY)) {
continue; continue;
} }
} }
} }
/**
* Finally, make sure they are within range
*/
if (is_detrimental_spell) { if (is_detrimental_spell) {
if (!caster_mob->IsAttackAllowed(current_mob, true)) { if (!caster_mob->IsAttackAllowed(current_mob, true)) {
continue; continue;
} }
if (center_mob && !spells[spell_id].npc_no_los && !center_mob->CheckLosFN(current_mob)) { if (center_mob && !spells[spell_id].npc_no_los && !center_mob->CheckLosFN(current_mob)) {
continue; continue;
} }
if (!center_mob && !spells[spell_id].npc_no_los && !caster_mob->CheckLosFN( if (!center_mob && !spells[spell_id].npc_no_los && !caster_mob->CheckLosFN(
caster_mob->GetTargetRingX(), caster_mob->GetTargetRingX(),
caster_mob->GetTargetRingY(), caster_mob->GetTargetRingY(),
@@ -1226,9 +1207,7 @@ void EntityList::AESpell(
current_mob->GetSize())) { current_mob->GetSize())) {
continue; continue;
} }
} } else {
else {
/** /**
* Check to stop casting beneficial ae buffs (to wit: bard songs) on enemies... * Check to stop casting beneficial ae buffs (to wit: bard songs) on enemies...
* This does not check faction for beneficial AE buffs... only agro and attackable. * This does not check faction for beneficial AE buffs... only agro and attackable.
@@ -1238,6 +1217,7 @@ void EntityList::AESpell(
if (caster_mob->IsAttackAllowed(current_mob, true)) { if (caster_mob->IsAttackAllowed(current_mob, true)) {
continue; continue;
} }
if (caster_mob->CheckAggro(current_mob)) { if (caster_mob->CheckAggro(current_mob)) {
continue; continue;
} }
@@ -1264,17 +1244,12 @@ void EntityList::AESpell(
} }
} }
/**
* @param caster
* @param center
* @param spell_id
* @param affect_caster
*/
void EntityList::MassGroupBuff( void EntityList::MassGroupBuff(
Mob* caster, Mob* caster,
Mob* center, Mob* center,
uint16 spell_id, uint16 spell_id,
bool affect_caster) bool affect_caster
)
{ {
Mob* current_mob = nullptr; Mob* current_mob = nullptr;
float distance = caster->GetAOERange(spell_id); float distance = caster->GetAOERange(spell_id);
@@ -1287,17 +1262,11 @@ void EntityList::MassGroupBuff(
continue; continue;
} }
/** if (current_mob == center) { // Skip Center
* Skip center
*/
if (current_mob == center) {
continue; continue;
} }
/** if (current_mob == caster && !affect_caster) { // Skip Caster
* Skip self
*/
if (current_mob == caster && !affect_caster) {
continue; continue;
} }
@@ -1305,17 +1274,13 @@ void EntityList::MassGroupBuff(
continue; continue;
} }
/**
* Pets
*/
if (current_mob->IsNPC()) { if (current_mob->IsNPC()) {
Mob* owner = current_mob->GetOwner(); Mob* owner = current_mob->GetOwner();
if (owner) { if (owner) {
if (!owner->IsOfClientBot()) { if (!owner->IsOfClientBot()) {
continue; continue;
} }
} } else {
else {
continue; continue;
} }
} }
@@ -1328,27 +1293,18 @@ void EntityList::MassGroupBuff(
} }
} }
/**
* Rampage - Normal and Duration rampages
* NPCs handle it differently in Mob::Rampage
*
* @param attacker
* @param distance
* @param Hand
* @param count
* @param is_from_spell
*/
void EntityList::AEAttack( void EntityList::AEAttack(
Mob* attacker, Mob* attacker,
float distance, float distance,
int Hand, int16 slot_id,
int count, int hit_count,
bool is_from_spell, bool is_from_spell,
int attack_rounds) int attack_rounds
)
{ {
Mob* current_mob = nullptr; Mob* current_mob = nullptr;
float distance_squared = distance * distance; float distance_squared = distance * distance;
int hit_count = 0; int current_hits = 0;
for (auto& it: entity_list.GetCloseMobList(attacker, distance)) { for (auto& it: entity_list.GetCloseMobList(attacker, distance)) {
current_mob = it.second; current_mob = it.second;
@@ -1356,27 +1312,29 @@ void EntityList::AEAttack(
continue; continue;
} }
if (current_mob->IsNPC() if (
&& current_mob != attacker //this is not needed unless NPCs can use this current_mob->IsNPC() &&
&& (attacker->IsAttackAllowed(current_mob)) current_mob != attacker &&
&& !current_mob->IsHorse() /* dont attack mounts */ attacker->IsAttackAllowed(current_mob) &&
&& (DistanceSquared(current_mob->GetPosition(), attacker->GetPosition()) <= distance_squared) !current_mob->IsHorse() &&
DistanceSquared(current_mob->GetPosition(), attacker->GetPosition()) <= distance_squared
) { ) {
for (int i = 0; i < attack_rounds; i++) { for (int i = 0; i < attack_rounds; i++) {
if (!attacker->IsClient() || attacker->GetClass() == Class::Monk || attacker->GetClass() == Class::Ranger) { if (
attacker->Attack(current_mob, Hand, false, false, is_from_spell); !attacker->IsClient() ||
attacker->GetClass() == Class::Monk ||
attacker->GetClass() == Class::Ranger
) {
attacker->Attack(current_mob, slot_id, false, false, is_from_spell);
} else { } else {
attacker->CastToClient()->DoAttackRounds(current_mob, Hand, is_from_spell); attacker->CastToClient()->DoAttackRounds(current_mob, slot_id, is_from_spell);
} }
} }
hit_count++; current_hits++;
if (count != 0 && hit_count >= count) { if (hit_count != 0 && current_hits >= hit_count) {
return; return;
} }
} }
} }
} }
+5 -4
View File
@@ -2064,9 +2064,14 @@ void PerlembParser::ExportEventVariables(
Corpse* corpse = std::any_cast<Corpse*>(extra_pointers->at(0)); Corpse* corpse = std::any_cast<Corpse*>(extra_pointers->at(0));
if (corpse) { if (corpse) {
ExportVar(package_name.c_str(), "killed_corpse_id", corpse->GetID()); ExportVar(package_name.c_str(), "killed_corpse_id", corpse->GetID());
ExportVar(package_name.c_str(), "killed_x", corpse->GetX());
ExportVar(package_name.c_str(), "killed_y", corpse->GetY());
ExportVar(package_name.c_str(), "killed_z", corpse->GetZ());
ExportVar(package_name.c_str(), "killed_h", corpse->GetHeading());
} }
} }
// EVENT_DEATH_ZONE only
if (extra_pointers && extra_pointers->size() >= 2) { if (extra_pointers && extra_pointers->size() >= 2) {
NPC* killed = std::any_cast<NPC*>(extra_pointers->at(1)); NPC* killed = std::any_cast<NPC*>(extra_pointers->at(1));
if (killed) { if (killed) {
@@ -2076,10 +2081,6 @@ void PerlembParser::ExportEventVariables(
killed->IsBot() ? killed->CastToBot()->GetBotID() : 0 killed->IsBot() ? killed->CastToBot()->GetBotID() : 0
); );
ExportVar(package_name.c_str(), "killed_npc_id", killed->IsNPC() ? killed->GetNPCTypeID() : 0); ExportVar(package_name.c_str(), "killed_npc_id", killed->IsNPC() ? killed->GetNPCTypeID() : 0);
ExportVar(package_name.c_str(), "killed_x", killed->GetX());
ExportVar(package_name.c_str(), "killed_y", killed->GetY());
ExportVar(package_name.c_str(), "killed_z", killed->GetZ());
ExportVar(package_name.c_str(), "killed_h", killed->GetHeading());
} }
} }
break; break;
+32 -2
View File
@@ -34,11 +34,14 @@
#include "questmgr.h" #include "questmgr.h"
#include "zone.h" #include "zone.h"
#include "data_bucket.h" #include "data_bucket.h"
#include "../common/events/player_event_logs.h"
#include "worldserver.h"
#include <cctype> #include <cctype>
extern Zone *zone; extern Zone *zone;
extern QueryServ *QServ; extern QueryServ *QServ;
extern WorldServer worldserver;
#ifdef EMBPERL_XS_CLASSES #ifdef EMBPERL_XS_CLASSES
@@ -1273,7 +1276,7 @@ int Perl__tasktimeleft(int task_id)
return quest_manager.tasktimeleft(task_id); return quest_manager.tasktimeleft(task_id);
} }
int Perl__istaskcompleted(int task_id) bool Perl__istaskcompleted(int task_id)
{ {
return quest_manager.istaskcompleted(task_id); return quest_manager.istaskcompleted(task_id);
} }
@@ -5946,7 +5949,33 @@ bool Perl__send_parcel(perl::reference table_ref)
e.note = note; e.note = note;
e.sent_date = std::time(nullptr); e.sent_date = std::time(nullptr);
return CharacterParcelsRepository::InsertOne(database, e).id; auto out = CharacterParcelsRepository::InsertOne(database, e).id;
if (out) {
Parcel_Struct ps{};
ps.item_slot = e.slot_id;
strn0cpy(ps.send_to, name.c_str(), sizeof(ps.send_to));
std::unique_ptr<ServerPacket> server_packet(new ServerPacket(ServerOP_ParcelDelivery, sizeof(Parcel_Struct)));
auto data = (Parcel_Struct *) server_packet->pBuffer;
data->item_slot = ps.item_slot;
strn0cpy(data->send_to, ps.send_to, sizeof(data->send_to));
worldserver.SendPacket(server_packet.get());
}
return out;
}
bool Perl__aretaskscompleted(perl::array task_ids)
{
std::vector<int> v;
for (const auto& e : task_ids) {
v.emplace_back(static_cast<int>(e));
}
return quest_manager.aretaskscompleted(v);
} }
void perl_register_quest() void perl_register_quest()
@@ -6272,6 +6301,7 @@ void perl_register_quest()
package.add("addloot", (void(*)(int, int))&Perl__addloot); package.add("addloot", (void(*)(int, int))&Perl__addloot);
package.add("addloot", (void(*)(int, int, bool))&Perl__addloot); package.add("addloot", (void(*)(int, int, bool))&Perl__addloot);
package.add("addskill", &Perl__addskill); package.add("addskill", &Perl__addskill);
package.add("aretaskscompleted", &Perl__aretaskscompleted);
package.add("assigntask", (void(*)(int))&Perl__assigntask); package.add("assigntask", (void(*)(int))&Perl__assigntask);
package.add("assigntask", (void(*)(int, bool))&Perl__assigntask); package.add("assigntask", (void(*)(int, bool))&Perl__assigntask);
package.add("attack", &Perl__attack); package.add("attack", &Perl__attack);
+33
View File
@@ -1655,7 +1655,10 @@ void EntityList::QueueClientsByTarget(Mob *sender, const EQApplicationPacket *ap
Send = clear_target_window; Send = clear_target_window;
if (c->GetGM() || RuleB(Spells, AlwaysSendTargetsBuffs)) { if (c->GetGM() || RuleB(Spells, AlwaysSendTargetsBuffs)) {
if (c->GetGM()) { if (c->GetGM()) {
if (!c->EntityVariableExists(SEE_BUFFS_FLAG)) {
c->Message(Chat::White, "Your GM flag allows you to always see your targets' buffs."); c->Message(Chat::White, "Your GM flag allows you to always see your targets' buffs.");
c->SetEntityVariable(SEE_BUFFS_FLAG, "1");
}
} }
Send = !clear_target_window; Send = !clear_target_window;
@@ -4480,6 +4483,36 @@ void EntityList::QuestJournalledSayClose(
delete outapp; delete outapp;
} }
bool Entity::CheckCoordLosNoZLeaps(float cur_x, float cur_y, float cur_z,
float trg_x, float trg_y, float trg_z, float perwalk)
{
if (zone->zonemap == nullptr) {
return true;
}
glm::vec3 myloc;
glm::vec3 oloc;
glm::vec3 hit;
myloc.x = cur_x;
myloc.y = cur_y;
myloc.z = cur_z+5;
oloc.x = trg_x;
oloc.y = trg_y;
oloc.z = trg_z+5;
if (myloc.x == oloc.x && myloc.y == oloc.y && myloc.z == oloc.z) {
return true;
}
if (!zone->zonemap->LineIntersectsZoneNoZLeaps(myloc,oloc,perwalk,&hit)) {
return true;
}
return false;
}
Corpse *EntityList::GetClosestCorpse(Mob *sender, const char *Name) Corpse *EntityList::GetClosestCorpse(Mob *sender, const char *Name)
{ {
if (!sender) if (!sender)
+9 -4
View File
@@ -60,6 +60,8 @@ class Bot;
extern EntityList entity_list; extern EntityList entity_list;
constexpr const char* SEE_BUFFS_FLAG = "see_buffs_flag";
class Entity class Entity
{ {
public: public:
@@ -114,6 +116,7 @@ public:
inline const time_t& GetSpawnTimeStamp() const { return spawn_timestamp; } inline const time_t& GetSpawnTimeStamp() const { return spawn_timestamp; }
virtual const char* GetName() { return ""; } virtual const char* GetName() { return ""; }
bool CheckCoordLosNoZLeaps(float cur_x, float cur_y, float cur_z, float trg_x, float trg_y, float trg_z, float perwalk=1);
Bot* CastToBot(); Bot* CastToBot();
const Bot* CastToBot() const; const Bot* CastToBot() const;
@@ -435,19 +438,20 @@ public:
void AEAttack( void AEAttack(
Mob* attacker, Mob* attacker,
float distance, float distance,
int Hand = EQ::invslot::slotPrimary, int16 slot_id = EQ::invslot::slotPrimary,
int count = 0, int hit_count = 0,
bool is_from_spell = false, bool is_from_spell = false,
int attack_rounds = 1 int attack_rounds = 1
); );
void AETaunt(Client *caster, float range = 0, int32 bonus_hate = 0); void AETaunt(Client* caster, float range = 0, int bonus_hate = 0);
void AESpell( void AESpell(
Mob* caster, Mob* caster,
Mob* center, Mob* center,
uint16 spell_id, uint16 spell_id,
bool affect_caster = true, bool affect_caster = true,
int16 resist_adjust = 0, int16 resist_adjust = 0,
int *max_targets = nullptr int* max_targets = nullptr,
bool is_scripted = false
); );
void MassGroupBuff(Mob* caster, Mob* center, uint16 spell_id, bool affect_caster = true); void MassGroupBuff(Mob* caster, Mob* center, uint16 spell_id, bool affect_caster = true);
@@ -501,6 +505,7 @@ public:
Mob* GetTargetForMez(Mob* caster); Mob* GetTargetForMez(Mob* caster);
uint32 CheckNPCsClose(Mob *center); uint32 CheckNPCsClose(Mob *center);
int FleeAllyCount(Mob* attacker, Mob* skipped);
Corpse* GetClosestCorpse(Mob* sender, const char *Name); Corpse* GetClosestCorpse(Mob* sender, const char *Name);
void TryWakeTheDead(Mob* sender, Mob* target, int32 spell_id, uint32 max_distance, uint32 duration, uint32 amount_pets); void TryWakeTheDead(Mob* sender, Mob* target, int32 spell_id, uint32 max_distance, uint32 duration, uint32 amount_pets);
NPC* GetClosestBanker(Mob* sender, uint32 &distance); NPC* GetClosestBanker(Mob* sender, uint32 &distance);
+56 -48
View File
@@ -527,7 +527,7 @@ void Client::AddEXP(ExpSource exp_source, uint64 in_add_exp, uint8 conlevel, boo
// Are we also doing linear AA acceleration? // Are we also doing linear AA acceleration?
if (RuleB(AA, ModernAAScalingEnabled) && aaexp > 0) if (RuleB(AA, ModernAAScalingEnabled) && aaexp > 0)
{ {
aaexp = ScaleAAXPBasedOnCurrentAATotal(GetAAPoints(), aaexp); aaexp = ScaleAAXPBasedOnCurrentAATotal(GetSpentAA() + GetAAPoints(), aaexp);
} }
// Check for AA XP Cap // Check for AA XP Cap
@@ -607,10 +607,10 @@ void Client::SetEXP(ExpSource exp_source, uint64 set_exp, uint64 set_aaxp, bool
Message(Chat::Red, "Error in Client::SetEXP. EXP not set."); Message(Chat::Red, "Error in Client::SetEXP. EXP not set.");
return; // Must be invalid class/race return; // Must be invalid class/race
} }
uint32 i = 0; uint32 i = 0;
uint32 membercount = 0; uint32 membercount = 0;
if(GetGroup()) if(GetGroup()) {
{
for (i = 0; i < MAX_GROUP_MEMBERS; i++) { for (i = 0; i < MAX_GROUP_MEMBERS; i++) {
if (GetGroup()->members[i] != nullptr) { if (GetGroup()->members[i] != nullptr) {
membercount++; membercount++;
@@ -627,25 +627,29 @@ void Client::SetEXP(ExpSource exp_source, uint64 set_exp, uint64 set_aaxp, bool
if (RuleI(Character, ShowExpValues) >= 1) { if (RuleI(Character, ShowExpValues) >= 1) {
if (exp_gained > 0 && aa_exp_gained > 0) { if (exp_gained > 0 && aa_exp_gained > 0) {
exp_amount_message = fmt::format("({}) ({})", exp_gained, aa_exp_gained); exp_amount_message = fmt::format("({}) ({})", exp_gained, aa_exp_gained);
} } else if (exp_gained > 0) {
else if (exp_gained > 0) {
exp_amount_message = fmt::format("({})", exp_gained); exp_amount_message = fmt::format("({})", exp_gained);
} } else {
else {
exp_amount_message = fmt::format("({}) AA", aa_exp_gained); exp_amount_message = fmt::format("({}) AA", aa_exp_gained);
} }
} }
std::string exp_percent_message = ""; std::string exp_percent_message = "";
if (RuleI(Character, ShowExpValues) >= 2) { if (RuleI(Character, ShowExpValues) >= 2) {
if (exp_gained > 0 && aa_exp_gained > 0) exp_percent_message = StringFormat("(%.3f%%, %.3f%%AA)", exp_percent, aa_exp_percent); if (exp_gained > 0 && aa_exp_gained > 0) {
else if (exp_gained > 0) exp_percent_message = StringFormat("(%.3f%%)", exp_percent); exp_percent_message = StringFormat("(%.3f%%, %.3f%%AA)", exp_percent, aa_exp_percent);
else exp_percent_message = StringFormat("(%.3f%%AA)", aa_exp_percent); } else if (exp_gained > 0) {
exp_percent_message = StringFormat("(%.3f%%)", exp_percent);
} else {
exp_percent_message = StringFormat("(%.3f%%AA)", aa_exp_percent);
}
} }
if (isrezzexp) { if (isrezzexp) {
if (RuleI(Character, ShowExpValues) > 0) if (RuleI(Character, ShowExpValues) > 0) {
Message(Chat::Experience, "You regain %s experience from resurrection. %s", exp_amount_message.c_str(), exp_percent_message.c_str()); Message(Chat::Experience, "You regain %s experience from resurrection. %s", exp_amount_message.c_str(), exp_percent_message.c_str());
else MessageString(Chat::Experience, REZ_REGAIN); } else {
MessageString(Chat::Experience, REZ_REGAIN);
}
} else { } else {
if (membercount > 1) { if (membercount > 1) {
if (RuleI(Character, ShowExpValues) > 0) { if (RuleI(Character, ShowExpValues) > 0) {
@@ -673,14 +677,17 @@ void Client::SetEXP(ExpSource exp_source, uint64 set_exp, uint64 set_aaxp, bool
} }
} }
} }
} } else if(total_add_exp < total_current_exp) { //only loss message if you lose exp, no message if you gained/lost nothing.
else if(total_add_exp < total_current_exp){ //only loss message if you lose exp, no message if you gained/lost nothing.
uint64 exp_lost = current_exp - set_exp; uint64 exp_lost = current_exp - set_exp;
float exp_percent = (float)((float)exp_lost / (float)(GetEXPForLevel(GetLevel() + 1) - GetEXPForLevel(GetLevel())))*(float)100; float exp_percent = (float)((float)exp_lost / (float)(GetEXPForLevel(GetLevel() + 1) - GetEXPForLevel(GetLevel())))*(float)100;
if (RuleI(Character, ShowExpValues) == 1 && exp_lost > 0) Message(Chat::Yellow, "You have lost %i experience.", exp_lost); if (RuleI(Character, ShowExpValues) == 1 && exp_lost > 0) {
else if (RuleI(Character, ShowExpValues) == 2 && exp_lost > 0) Message(Chat::Yellow, "You have lost %i experience. (%.3f%%)", exp_lost, exp_percent); Message(Chat::Yellow, "You have lost %i experience.", exp_lost);
else Message(Chat::Yellow, "You have lost experience."); } else if (RuleI(Character, ShowExpValues) == 2 && exp_lost > 0) {
Message(Chat::Yellow, "You have lost %i experience. (%.3f%%)", exp_lost, exp_percent);
} else {
Message(Chat::Yellow, "You have lost experience.");
}
} }
//check_level represents the level we should be when we have //check_level represents the level we should be when we have
@@ -698,9 +705,10 @@ void Client::SetEXP(ExpSource exp_source, uint64 set_exp, uint64 set_aaxp, bool
} }
level_count++; level_count++;
if(GetMercenaryID()) if (GetMercenaryID()) {
UpdateMercLevel(); UpdateMercLevel();
} }
}
//see if we lost any levels //see if we lost any levels
while (set_exp < GetEXPForLevel(check_level-1)) { while (set_exp < GetEXPForLevel(check_level-1)) {
check_level--; check_level--;
@@ -709,9 +717,10 @@ void Client::SetEXP(ExpSource exp_source, uint64 set_exp, uint64 set_aaxp, bool
break; break;
} }
level_increase = false; level_increase = false;
if(GetMercenaryID()) if (GetMercenaryID()) {
UpdateMercLevel(); UpdateMercLevel();
} }
}
check_level--; check_level--;
@@ -747,12 +756,14 @@ void Client::SetEXP(ExpSource exp_source, uint64 set_exp, uint64 set_aaxp, bool
//Message(Chat::Yellow, "You have gained %d skill points!!", m_pp.aapoints - last_unspentAA); //Message(Chat::Yellow, "You have gained %d skill points!!", m_pp.aapoints - last_unspentAA);
char val1[20] = { 0 }; char val1[20] = { 0 };
char val2[20] = { 0 }; char val2[20] = { 0 };
if (gained == 1 && m_pp.aapoints == 1)
if (gained == 1 && m_pp.aapoints == 1) {
MessageString(Chat::Experience, GAIN_SINGLE_AA_SINGLE_AA, ConvertArray(m_pp.aapoints, val1)); //You have gained an ability point! You now have %1 ability point. MessageString(Chat::Experience, GAIN_SINGLE_AA_SINGLE_AA, ConvertArray(m_pp.aapoints, val1)); //You have gained an ability point! You now have %1 ability point.
else if (gained == 1 && m_pp.aapoints > 1) } else if (gained == 1 && m_pp.aapoints > 1) {
MessageString(Chat::Experience, GAIN_SINGLE_AA_MULTI_AA, ConvertArray(m_pp.aapoints, val1)); //You have gained an ability point! You now have %1 ability points. MessageString(Chat::Experience, GAIN_SINGLE_AA_MULTI_AA, ConvertArray(m_pp.aapoints, val1)); //You have gained an ability point! You now have %1 ability points.
else } else {
MessageString(Chat::Experience, GAIN_MULTI_AA_MULTI_AA, ConvertArray(gained, val1), ConvertArray(m_pp.aapoints, val2)); //You have gained %1 ability point(s)! You now have %2 ability point(s). MessageString(Chat::Experience, GAIN_MULTI_AA_MULTI_AA, ConvertArray(gained, val1), ConvertArray(m_pp.aapoints, val2)); //You have gained %1 ability point(s)! You now have %2 ability point(s).
}
if (RuleB(AA, SoundForAAEarned)) { if (RuleB(AA, SoundForAAEarned)) {
SendSound(); SendSound();
@@ -774,48 +785,45 @@ void Client::SetEXP(ExpSource exp_source, uint64 set_exp, uint64 set_aaxp, bool
//Message(Chat::Yellow, "You now have %d skill points available to spend.", m_pp.aapoints); //Message(Chat::Yellow, "You now have %d skill points available to spend.", m_pp.aapoints);
} }
uint8 maxlevel = RuleI(Character, MaxExpLevel) + 1; uint8 max_level = RuleI(Character, MaxExpLevel) + 1;
if(maxlevel <= 1) if (max_level <= 1) {
maxlevel = RuleI(Character, MaxLevel) + 1; max_level = RuleI(Character, MaxLevel) + 1;
if(check_level > maxlevel) {
check_level = maxlevel;
if(RuleB(Character, KeepLevelOverMax)) {
set_exp = GetEXPForLevel(GetLevel()+1);
}
else {
set_exp = GetEXPForLevel(maxlevel);
}
} }
auto client_max_level = GetClientMaxLevel(); auto client_max_level = GetClientMaxLevel();
if (client_max_level) { if (client_max_level) {
if (GetLevel() >= client_max_level) { max_level = client_max_level + 1;
auto exp_needed = GetEXPForLevel(client_max_level);
if (set_exp > exp_needed) {
set_exp = exp_needed;
} }
if (check_level > max_level) {
check_level = max_level;
if (RuleB(Character, KeepLevelOverMax)) {
set_exp = GetEXPForLevel(GetLevel()+1);
} else {
set_exp = GetEXPForLevel(max_level);
} }
} }
if ((GetLevel() != check_level) && !(check_level >= maxlevel)) { if ((GetLevel() != check_level) && !(check_level >= max_level)) {
char val1[20]={0}; char val1[20]={0};
if (level_increase) if (level_increase) {
{ if (level_count == 1) {
if (level_count == 1)
MessageString(Chat::Experience, GAIN_LEVEL, ConvertArray(check_level, val1)); MessageString(Chat::Experience, GAIN_LEVEL, ConvertArray(check_level, val1));
else } else {
Message(Chat::Yellow, "Welcome to level %i!", check_level); Message(Chat::Yellow, "Welcome to level %i!", check_level);
}
if (check_level == RuleI(Character, DeathItemLossLevel) && if (check_level == RuleI(Character, DeathItemLossLevel) &&
m_ClientVersionBit & EQ::versions::maskUFAndEarlier) m_ClientVersionBit & EQ::versions::maskUFAndEarlier) {
MessageString(Chat::Yellow, CORPSE_ITEM_LOST); MessageString(Chat::Yellow, CORPSE_ITEM_LOST);
}
if (check_level == RuleI(Character, DeathExpLossLevel)) if (check_level == RuleI(Character, DeathExpLossLevel)) {
MessageString(Chat::Yellow, CORPSE_EXP_LOST); MessageString(Chat::Yellow, CORPSE_EXP_LOST);
} }
}
uint8 myoldlevel = GetLevel(); uint8 myoldlevel = GetLevel();
@@ -828,8 +836,8 @@ void Client::SetEXP(ExpSource exp_source, uint64 set_exp, uint64 set_aaxp, bool
} }
//If were at max level then stop gaining experience if we make it to the cap //If were at max level then stop gaining experience if we make it to the cap
if(GetLevel() == maxlevel - 1){ if (GetLevel() == max_level - 1){
uint32 expneeded = GetEXPForLevel(maxlevel); uint32 expneeded = GetEXPForLevel(max_level);
if (set_exp > expneeded) { if (set_exp > expneeded) {
set_exp = expneeded; set_exp = expneeded;
} }
+281 -57
View File
@@ -19,6 +19,7 @@
#include "../common/rulesys.h" #include "../common/rulesys.h"
#include "map.h" #include "map.h"
#include "water_map.h"
#include "zone.h" #include "zone.h"
#ifdef _WINDOWS #ifdef _WINDOWS
@@ -29,17 +30,52 @@ extern Zone* zone;
#define FEAR_PATHING_DEBUG #define FEAR_PATHING_DEBUG
int Mob::GetFleeRatio(Mob* other)
{
int flee_ratio = GetSpecialAbility(SpecialAbility::FleePercent); // if a special SpecialAbility::FleePercent exists
Mob *hate_top = GetHateTop();
if (other != nullptr) {
hate_top = other;
}
if (!hate_top) {
return 0;
}
// If no special flee_percent check for Gray or Other con rates
if (flee_ratio == 0) {
flee_ratio = RuleI(Combat, FleeHPRatio);
if (GetLevelCon(hate_top->GetLevel(), GetLevel()) == ConsiderColor::Gray && RuleB(Combat, FleeGray) &&
GetLevel() <= RuleI(Combat, FleeGrayMaxLevel)) {
flee_ratio = RuleI(Combat, FleeGrayHPRatio);
LogFlee("Mob [{}] using combat flee gray flee_ratio [{}]", GetCleanName(), flee_ratio);
}
}
return flee_ratio;
}
//this is called whenever we are damaged to process possible fleeing //this is called whenever we are damaged to process possible fleeing
void Mob::CheckFlee() void Mob::CheckFlee()
{ {
if (IsPet() || IsCasting() || GetHP() == 0 || GetBodyType() == BodyType::Undead || (IsNPC() && CastToNPC()->IsUnderwaterOnly())) {
// if mob is dead why would you run?
if (GetHP() == 0) {
return; return;
} }
// if were already fleeing, don't need to check more... //if were already fleeing, we only need to check speed. Speed changes will trigger pathing updates.
if (flee_mode && currently_fleeing) { if (flee_mode && currently_fleeing) {
int flee_speed = GetFearSpeed();
if (flee_speed < 1) {
flee_speed = 0;
}
SetRunAnimSpeed(flee_speed);
if (IsMoving() && flee_speed < 1) {
StopNavigation();
}
return; return;
} }
@@ -49,38 +85,17 @@ void Mob::CheckFlee()
return; return;
} }
// Undead do not flee
if (GetBodyType() == BodyType::Undead) {
return;
}
// Check if Flee Timer is cleared
if (!flee_timer.Check()) {
return;
}
int hp_ratio = GetIntHPRatio(); int hp_ratio = GetIntHPRatio();
int flee_ratio = GetSpecialAbility(SpecialAbility::FleePercent); // if a special SpecialAbility::FleePercent exists int flee_ratio = GetFleeRatio();
Mob *hate_top = GetHateTop(); Mob *hate_top = GetHateTop();
LogFlee("Mob [{}] hp_ratio [{}] flee_ratio [{}]", GetCleanName(), hp_ratio, flee_ratio);
// Sanity Check for race conditions // Sanity Check for race conditions
if (hate_top == nullptr) { if(!hate_top) {
//this should never happen...
StartFleeing();
return; return;
} }
// If no special flee_percent check for Gray or Other con rates
if (GetLevelCon(hate_top->GetLevel(), GetLevel()) == ConsiderColor::Gray && flee_ratio == 0 && RuleB(Combat, FleeGray) &&
GetLevel() <= RuleI(Combat, FleeGrayMaxLevel)) {
flee_ratio = RuleI(Combat, FleeGrayHPRatio);
LogFlee("Mob [{}] using combat flee gray hp_ratio [{}] flee_ratio [{}]", GetCleanName(), hp_ratio, flee_ratio);
}
else if (flee_ratio == 0) {
flee_ratio = RuleI(Combat, FleeHPRatio);
LogFlee("Mob [{}] using combat flee hp_ratio [{}] flee_ratio [{}]", GetCleanName(), hp_ratio, flee_ratio);
}
bool mob_has_low_enough_health_to_flee = hp_ratio >= flee_ratio; bool mob_has_low_enough_health_to_flee = hp_ratio >= flee_ratio;
if (mob_has_low_enough_health_to_flee) { if (mob_has_low_enough_health_to_flee) {
LogFlee( LogFlee(
@@ -141,7 +156,7 @@ void Mob::CheckFlee()
// if FleeIfNotAlone is true, we skip alone check // if FleeIfNotAlone is true, we skip alone check
// roll chance // roll chance
if (GetSpecialAbility(SpecialAbility::AlwaysFlee) || if (GetSpecialAbility(SpecialAbility::AlwaysFlee) ||
((RuleB(Combat, FleeIfNotAlone) || entity_list.GetHatedCount(hate_top, this, true) == 0) && ((RuleB(Combat, FleeIfNotAlone) || entity_list.FleeAllyCount(hate_top, this) == 0) &&
zone->random.Roll(flee_chance))) { zone->random.Roll(flee_chance))) {
LogFlee( LogFlee(
@@ -158,9 +173,63 @@ void Mob::CheckFlee()
} }
} }
void Mob::StopFleeing()
{
if (!flee_mode) {
return;
}
flee_mode = false;
//see if we are legitimately feared or blind now
if (!spellbonuses.IsFeared && !IsBlind()) {
currently_fleeing = false;
StopNavigation();
}
}
void Mob::FleeInfo(Mob* client)
{
float other_ratio = client->GetHPRatio();
bool wontflee = false;
std::string reason;
std::string flee;
int allycount = entity_list.FleeAllyCount(client, this);
if (flee_mode && currently_fleeing) {
wontflee = true;
reason = "NPC is already fleeing!";
} else if (GetSpecialAbility(SpecialAbility::FleeingImmunity)) {
wontflee = true;
reason = "NPC is immune to fleeing.";
} else if (other_ratio < 20) {
wontflee = true;
reason = "Player has low health.";
} else if (GetSpecialAbility(SpecialAbility::AlwaysFlee)) {
flee = "NPC has ALWAYS_FLEE set.";
} else if (RuleB(Combat, FleeIfNotAlone) || (!RuleB(Combat, FleeIfNotAlone) && allycount == 0)) {
flee = "NPC has no allies nearby or the rule to flee when not alone is enabled.";
} else {
wontflee = true;
reason = "NPC likely has allies nearby.";
}
if (!wontflee) {
client->Message(Chat::Green, "%s will flee at %d percent because %s", GetName(), GetFleeRatio(client), flee.c_str());
} else {
client->Message(Chat::Red, "%s will not flee because %s", GetName(), reason.c_str());
}
client->Message(Chat::Default, "NPC ally count %d", allycount);
}
void Mob::ProcessFlee() void Mob::ProcessFlee()
{ {
if (!flee_mode) {
return;
}
//Stop fleeing if effect is applied after they start to run. //Stop fleeing if effect is applied after they start to run.
//When ImmuneToFlee effect fades it will turn fear back on and check if it can still flee. //When ImmuneToFlee effect fades it will turn fear back on and check if it can still flee.
@@ -170,46 +239,201 @@ void Mob::ProcessFlee()
return; return;
} }
int hpratio = GetIntHPRatio();
int fleeratio = GetSpecialAbility(SpecialAbility::FleePercent); // if a special SpecialAbility::FleePercent exists
Mob *hate_top = GetHateTop(); Mob *hate_top = GetHateTop();
bool dying = GetIntHPRatio() < GetFleeRatio();
// If no special flee_percent check for Gray or Other con rates // We have stopped fleeing for an unknown reason (couldn't find a node is possible) restart.
if(hate_top != nullptr && GetLevelCon(hate_top->GetLevel(), GetLevel()) == ConsiderColor::Gray && fleeratio == 0 && RuleB(Combat, FleeGray)) { if (flee_mode && !currently_fleeing) {
fleeratio = RuleI(Combat, FleeGrayHPRatio); if(dying) {
} else if(fleeratio == 0) { StartFleeing();
fleeratio = RuleI(Combat, FleeHPRatio ); }
} }
// Mob is still too low. Keep Running //see if we are still dying, if so, do nothing
if(hpratio < fleeratio) { if (dying) {
return; return;
} }
//we are not dying anymore... see what we do next //we are not dying anymore, check to make sure we're not blind or feared and cancel flee.
StopFleeing();
flee_mode = false;
//see if we are legitimately feared or blind now
if (!spellbonuses.IsFeared && !spellbonuses.IsBlind) {
//not feared or blind... were done...
currently_fleeing = false;
return;
}
} }
void Mob::CalculateNewFearpoint() void Mob::CalculateNewFearpoint()
{ {
if (RuleB(Pathing, Fear) && zone->pathing) { // blind waypoint logic isn't the same as fear's. Has a chance to run toward the player
auto Node = zone->pathing->GetRandomLocation(glm::vec3(GetX(), GetY(), GetZ())); // chance is very high if the player is moving, otherwise it's low
if (Node.x != 0.0f || Node.y != 0.0f || Node.z != 0.0f) { if (IsBlind() && !IsFeared() && GetTarget()) {
m_FearWalkTarget = Node; int roll = 20;
if (GetTarget()->GetCurrentSpeed() > 0.1f || (GetTarget()->IsClient() && GetTarget()->animation != 0)) {
roll = 80;
}
if (zone->random.Roll(roll)) {
m_FearWalkTarget = glm::vec3(GetTarget()->GetPosition());
currently_fleeing = true; currently_fleeing = true;
return;
}
}
if (RuleB(Pathing, Fear) && zone->pathing) {
glm::vec3 Node;
int flags = PathingNotDisabled ^ PathingZoneLine;
if (IsNPC() && CastToNPC()->IsUnderwaterOnly() && !zone->IsWaterZone(GetZOffset())) {
Node = glm::vec3(0.0f);
} else {
Node = zone->pathing->GetRandomLocation(glm::vec3(GetX(), GetY(), GetZOffset()), flags);
}
if (Node.x != 0.0f || Node.y != 0.0f || Node.z != 0.0f) {
Node.z = GetFixedZ(Node);
PathfinderOptions opts;
opts.smooth_path = true;
opts.step_size = RuleR(Pathing, NavmeshStepSize);
opts.offset = GetZOffset();
opts.flags = flags;
auto partial = false;
auto stuck = false;
auto route = zone->pathing->FindPath(
glm::vec3(GetX(), GetY(), GetZOffset()),
glm::vec3(Node.x, Node.y, Node.z),
partial,
stuck,
opts
);
glm::vec3 last_good_loc = Node;
int route_size = route.size();
int route_count = 0;
bool have_los = true;
if (route_size == 2) {
// FindPath() often fails to compute a route in some places, so to prevent running through walls we need to check LOS on all 2 node routes
// size 2 route usually means FindPath() bugged out. sometimes it returns locs outside the geometry
if (CheckLosFN(Node.x, Node.y, Node.z, 6.0)) {
LogPathingDetail("Direct route to fearpoint [{}], [{}], [{}] calculated for [{}]", last_good_loc.x, last_good_loc.y, last_good_loc.z, GetName());
m_FearWalkTarget = last_good_loc;
currently_fleeing = true;
return;
} else {
LogPathingDetail("FindRoute() returned single hop route to destination without LOS: [{}], [{}], [{}] for [{}]", last_good_loc.x, last_good_loc.y, last_good_loc.z, GetName());
}
// use fallback logic if LOS fails
} else if (!stuck) {
// check route for LOS failures to prevent mobs ending up outside of playable area
// only checking the last few hops because LOS will often fail in a valid route which can result in mobs getting undesirably trapped
auto iter = route.begin();
glm::vec3 previous_pos(GetX(), GetY(), GetZOffset());
while (iter != route.end() && have_los == true) {
auto &current_node = (*iter);
iter++;
route_count++;
if (iter == route.end()) {
continue;
}
previous_pos = current_node.pos;
auto &next_node = (*iter);
if (next_node.teleport) {
continue;
}
if ((route_size - route_count) < 5 && !zone->zonemap->CheckLoS(previous_pos, next_node.pos)) {
have_los = false;
break;
} else {
last_good_loc = next_node.pos;
}
}
if (have_los || route_count > 2) {
if (have_los) {
LogPathingDetail("Route to fearpoint [{}], [{}], [{}] calculated for [{}]; route size: [{}]", last_good_loc.x, last_good_loc.y, last_good_loc.z, GetName(), route_size);
} else {
LogPathingDetail("Using truncated route to fearpoint [{}], [{}], [{}] for [{}]; node count: [{}]; route size [{}]", last_good_loc.x, last_good_loc.y, last_good_loc.z, GetName(), route_count, route_size);
}
m_FearWalkTarget = last_good_loc;
currently_fleeing = true;
return;
}
}
}
}
// fallback logic if pathing system can't be used
bool inliquid = zone->HasWaterMap() && zone->watermap->InLiquid(glm::vec3(GetPosition())) || zone->IsWaterZone(GetZ());
bool stay_inliquid = (inliquid && IsNPC() && CastToNPC()->IsUnderwaterOnly());
bool levitating = IsClient() && (FindType(SE_Levitate) || flymode != GravityBehavior::Ground);
bool open_outdoor_zone = !zone->CanCastOutdoor() && !zone->IsCity();
int loop = 0;
float ranx, rany, ranz;
currently_fleeing = false;
glm::vec3 myloc(GetX(), GetY(), GetZ());
glm::vec3 myceil = myloc;
float ceil = zone->zonemap->FindCeiling(myloc, &myceil);
if (ceil != BEST_Z_INVALID) {
ceil -= 1.0f;
}
while (loop < 100) { //Max 100 tries
int ran = 250 - (loop * 2);
loop++;
if (open_outdoor_zone && loop < 20) { // try a distant loc first; other way will likely pick a close loc
ranx = zone->random.Int(0, ran);
rany = zone->random.Int(0, ran);
if (ranx + rany < 200) {
continue;
}
ranx = GetX() + (zone->random.Int(0, 1) == 1 ? ranx : -ranx);
rany = GetY() + (zone->random.Int(0, 1) == 1 ? rany : -rany);
} else {
ranx = GetX() + zone->random.Int(0, ran - 1) - zone->random.Int(0, ran - 1);
rany = GetY() + zone->random.Int(0, ran - 1) - zone->random.Int(0, ran - 1);
}
ranz = BEST_Z_INVALID;
glm::vec3 newloc(ranx, rany, ceil != BEST_Z_INVALID ? ceil : GetZ());
if (stay_inliquid || levitating || (loop > 50 && inliquid)) {
if (zone->zonemap->CheckLoS(myloc, newloc)) {
ranz = GetZ();
currently_fleeing = true;
break;
}
} else {
if (ceil != BEST_Z_INVALID) {
ranz = zone->zonemap->FindGround(newloc, &myceil);
} else {
ranz = zone->zonemap->FindBestZ(newloc, &myceil);
}
if (ranz != BEST_Z_INVALID) {
ranz = SetBestZ(ranz);
}
}
if (ranz == BEST_Z_INVALID) {
continue;
}
float fdist = ranz - GetZ();
if (fdist >= -50 && fdist <= 50 && CheckCoordLosNoZLeaps(GetX(), GetY(), GetZ(), ranx, rany, ranz)) {
currently_fleeing = true;
break;
}
}
if (currently_fleeing) {
m_FearWalkTarget = glm::vec3(ranx, rany, ranz);
LogPathingDetail("Non-pathed fearpoint [{}], [{}], [{}] selected for [{}]", ranx, rany, ranz, GetName());
}
return; return;
} }
LogPathing("No path found to selected node during CalculateNewFearpoint.");
}
}
+12 -4
View File
@@ -3,11 +3,19 @@
void command_devtools(Client *c, const Seperator *sep) void command_devtools(Client *c, const Seperator *sep)
{ {
bool is_disable = !strcasecmp(sep->arg[1], "disable"); const uint16 arguments = sep->argnum;
bool is_enable = !strcasecmp(sep->arg[1], "enable"); if (arguments != 2) {
c->ShowDevToolsMenu();
return;
}
if (is_disable || is_enable) { const std::string& type = sep->arg[1];
c->SetDevToolsEnabled(is_enable); const bool toggle = Strings::ToBool(sep->arg[2]);
if (Strings::EqualFold(type, "menu")) {
c->SetDevToolsEnabled(toggle);
} else if (Strings::EqualFold(type, "window")) {
c->SetDisplayMobInfoWindow(toggle);
} }
c->ShowDevToolsMenu(); c->ShowDevToolsMenu();
+2
View File
@@ -16,6 +16,7 @@
#include "find/race.cpp" #include "find/race.cpp"
#include "find/recipe.cpp" #include "find/recipe.cpp"
#include "find/skill.cpp" #include "find/skill.cpp"
#include "find/stance.cpp"
#include "find/spell.cpp" #include "find/spell.cpp"
#include "find/special_ability.cpp" #include "find/special_ability.cpp"
#include "find/task.cpp" #include "find/task.cpp"
@@ -58,6 +59,7 @@ void command_find(Client *c, const Seperator *sep)
Cmd{.cmd = "recipe", .u = "recipe [Search Criteria]", .fn = FindRecipe, .a = {"#findrecipe"}}, Cmd{.cmd = "recipe", .u = "recipe [Search Criteria]", .fn = FindRecipe, .a = {"#findrecipe"}},
Cmd{.cmd = "skill", .u = "skill [Search Criteria]", .fn = FindSkill, .a = {"#findskill"}}, Cmd{.cmd = "skill", .u = "skill [Search Criteria]", .fn = FindSkill, .a = {"#findskill"}},
Cmd{.cmd = "special_ability", .u = "special_ability [Search Criteria]", .fn = FindSpecialAbility, .a = {"#fsa", "#findspecialability"}}, Cmd{.cmd = "special_ability", .u = "special_ability [Search Criteria]", .fn = FindSpecialAbility, .a = {"#fsa", "#findspecialability"}},
Cmd{.cmd = "stance", .u = "stance [Search Criteria]", .fn = FindStance, .a = {"#findstance"}},
Cmd{.cmd = "spell", .u = "spell [Search Criteria]", .fn = FindSpell, .a = {"#fs", "#findspell"}}, Cmd{.cmd = "spell", .u = "spell [Search Criteria]", .fn = FindSpell, .a = {"#fs", "#findspell"}},
Cmd{.cmd = "task", .u = "task [Search Criteria]", .fn = FindTask, .a = {"#findtask"}}, Cmd{.cmd = "task", .u = "task [Search Criteria]", .fn = FindTask, .a = {"#findtask"}},
Cmd{.cmd = "zone", .u = "zone [Search Criteria]", .fn = FindZone, .a = {"#fz", "#findzone"}}, Cmd{.cmd = "zone", .u = "zone [Search Criteria]", .fn = FindZone, .a = {"#fz", "#findzone"}},
+63
View File
@@ -0,0 +1,63 @@
#include "../../client.h"
void FindStance(Client *c, const Seperator *sep)
{
if (sep->IsNumber(2)) {
const uint8 stance_id = static_cast<uint8>(Strings::ToUnsignedInt(sep->arg[2]));
const std::string& stance_name = Stance::GetName(stance_id);
if (Strings::EqualFold(stance_name, "UNKNOWN STANCE")) {
c->Message(
Chat::White,
fmt::format(
"Stance ID {} does not exist.",
stance_id
).c_str()
);
return;
}
c->Message(
Chat::White,
fmt::format(
"Stance {} | {}",
stance_id,
stance_name
).c_str()
);
return;
}
const std::string& search_criteria = Strings::ToLower(sep->argplus[2]);
uint32 found_count = 0;
for (const auto& e : stance_names) {
const std::string& stance_name_lower = Strings::ToLower(e.second);
if (!Strings::Contains(stance_name_lower, search_criteria)) {
continue;
}
c->Message(
Chat::White,
fmt::format(
"Stance {} | {}",
e.first,
e.second
).c_str()
);
found_count++;
}
c->Message(
Chat::White,
fmt::format(
"{} Stance{} found matching '{}'.",
found_count,
found_count != 1 ? "s" : "",
sep->argplus[2]
).c_str()
);
}
+14
View File
@@ -0,0 +1,14 @@
#include "../client.h"
void command_fleeinfo(Client *c, const Seperator *sep)
{
if (c->GetTarget() && c->GetTarget()->IsNPC()) {
Mob* client = entity_list.GetMob(c->GetID());
if (client) {
c->GetTarget()->FleeInfo(client);
}
} else {
c->Message(Chat::Red, "Please target a NPC to use this command on.");
}
}
+5 -3
View File
@@ -8,14 +8,15 @@ void command_grantaa(Client *c, const Seperator *sep)
} }
const uint8 unlock_level = sep->IsNumber(1) ? static_cast<uint8>(Strings::ToUnsignedInt(sep->arg[1])) : 0; const uint8 unlock_level = sep->IsNumber(1) ? static_cast<uint8>(Strings::ToUnsignedInt(sep->arg[1])) : 0;
const bool skip_grant_only = sep->IsNumber(2) ? Strings::ToBool(sep->arg[2]) : false;
auto t = c->GetTarget()->CastToClient(); auto t = c->GetTarget()->CastToClient();
t->GrantAllAAPoints(unlock_level); t->GrantAllAAPoints(unlock_level, skip_grant_only);
c->Message( c->Message(
Chat::White, Chat::White,
fmt::format( fmt::format(
"Successfully granted all Alternate Advancements for {}{}.", "Successfully granted all Alternate Advancements for {}{}{}.",
c->GetTargetDescription(t), c->GetTargetDescription(t),
( (
unlock_level ? unlock_level ?
@@ -24,7 +25,8 @@ void command_grantaa(Client *c, const Seperator *sep)
unlock_level unlock_level
) : ) :
"" ""
) ),
skip_grant_only ? "except for grant only AAs" : ""
).c_str() ).c_str()
); );
} }
+2 -2
View File
@@ -163,7 +163,7 @@ void command_parcels(Client *c, const Seperator *sep)
return; return;
} }
CharacterParcelsRepository::CharacterParcels parcel_out; auto parcel_out = CharacterParcelsRepository::NewEntity();
parcel_out.from_name = c->GetName(); parcel_out.from_name = c->GetName();
parcel_out.note = note; parcel_out.note = note;
parcel_out.sent_date = time(nullptr); parcel_out.sent_date = time(nullptr);
@@ -241,7 +241,7 @@ void command_parcels(Client *c, const Seperator *sep)
? inst->GetItem()->MaxCharges : (int16) quantity; ? inst->GetItem()->MaxCharges : (int16) quantity;
} }
CharacterParcelsRepository::CharacterParcels parcel_out; auto parcel_out = CharacterParcelsRepository::NewEntity();
parcel_out.from_name = c->GetName(); parcel_out.from_name = c->GetName();
parcel_out.note = note.empty() ? "" : note; parcel_out.note = note.empty() ? "" : note;
parcel_out.sent_date = time(nullptr); parcel_out.sent_date = time(nullptr);
+7
View File
@@ -25,6 +25,13 @@ void SetLevel(Client *c, const Seperator *sep)
t->SetLevel(level, true); t->SetLevel(level, true);
if (t->IsClient()) { if (t->IsClient()) {
for (const auto& s : EQ::skills::GetSkillTypeMap()) {
const uint16 max_skill_value = t->CastToClient()->MaxSkill(s.first);
if (t->GetSkill(s.first) > max_skill_value) {
t->CastToClient()->SetSkill(s.first, max_skill_value);
}
}
t->CastToClient()->SendLevelAppearance(); t->CastToClient()->SendLevelAppearance();
if (RuleB(Bots, Enabled) && RuleB(Bots, BotLevelsWithOwner)) { if (RuleB(Bots, Enabled) && RuleB(Bots, BotLevelsWithOwner)) {
+5 -6
View File
@@ -160,23 +160,22 @@ void ShowInventory(Client *c, const Seperator *sep)
linker.SetItemInst(inst_main); linker.SetItemInst(inst_main);
if (item_data) { if (item_data && inst_main) {
//auto inst = c->GetInv().GetItem(scope_bit & peekWorld ? EQ::invslot::WORLD_BEGIN + index_main : index_main);
c->Message( c->Message(
Chat::White, Chat::White,
fmt::format( fmt::format(
"Slot {} | {} ({}/{}){}", "Slot {} | {} ({}/{}){}",
((scope_bit & peekWorld) ? (EQ::invslot::WORLD_BEGIN + index_main) : index_main), scope_bit & peekWorld ? EQ::invslot::WORLD_BEGIN + index_main : index_main,
linker.GenerateLink(), linker.GenerateLink(),
item_data->ID, item_data->ID,
c->GetInv().GetItem(((scope_bit &peekWorld) ? (EQ::invslot::WORLD_BEGIN + index_main) : index_main))->GetSerialNumber(), inst_main->GetSerialNumber(),
(
inst_main->IsStackable() && inst_main->GetCharges() > 0 ? inst_main->IsStackable() && inst_main->GetCharges() > 0 ?
fmt::format( fmt::format(
" (Stack of {})", " (Stack of {})",
inst_main->GetCharges() inst_main->GetCharges()
) : ) :
"" ""
)
).c_str() ).c_str()
); );
} }
@@ -239,7 +238,7 @@ void ShowInventory(Client *c, const Seperator *sep)
sub_index, sub_index,
linker.GenerateLink(), linker.GenerateLink(),
item_data->ID, item_data->ID,
c->GetInv().GetItem(EQ::InventoryProfile::CalcSlotId(index_main, sub_index))->GetSerialNumber(), inst_sub->GetSerialNumber(),
( (
inst_sub->IsStackable() && inst_sub->GetCharges() > 0 ? inst_sub->IsStackable() && inst_sub->GetCharges() > 0 ?
fmt::format( fmt::format(
+23 -11
View File
@@ -115,7 +115,7 @@ Group::~Group()
} }
//Split money used in OP_Split (/split and /autosplit). //Split money used in OP_Split (/split and /autosplit).
void Group::SplitMoney(uint32 copper, uint32 silver, uint32 gold, uint32 platinum, Client *splitter) void Group::SplitMoney(uint32 copper, uint32 silver, uint32 gold, uint32 platinum, Client *splitter, bool share)
{ {
// Return early if no money to split. // Return early if no money to split.
if (!copper && !silver && !gold && !platinum) { if (!copper && !silver && !gold && !platinum) {
@@ -152,6 +152,8 @@ void Group::SplitMoney(uint32 copper, uint32 silver, uint32 gold, uint32 platinu
return; return;
} }
uint8 random_member = zone->random.Int(0, member_count - 1);
// Calculate split and remainder for each coin type // Calculate split and remainder for each coin type
uint32 copper_split = copper / member_count; uint32 copper_split = copper / member_count;
uint32 copper_remainder = copper % member_count; uint32 copper_remainder = copper % member_count;
@@ -172,16 +174,33 @@ void Group::SplitMoney(uint32 copper, uint32 silver, uint32 gold, uint32 platinu
uint32 receive_gold = gold_split; uint32 receive_gold = gold_split;
uint32 receive_platinum = platinum_split; uint32 receive_platinum = platinum_split;
// splitter gets the remainders of coin // if /split is used then splitter gets the remainder + split.
if (member_client == splitter) { // if /autosplit is used then random players in the group will get the remainder + split.
if(share ? member_client == splitter : member_client == members[random_member]) {
receive_copper += copper_remainder; receive_copper += copper_remainder;
receive_silver += silver_remainder; receive_silver += silver_remainder;
receive_gold += gold_remainder; receive_gold += gold_remainder;
receive_platinum += platinum_remainder; receive_platinum += platinum_remainder;
} }
// Add the coins to the player's purse. // the group member other than the character doing the /split only gets this message "(splitter) shares the money with the group"
if (share && member_client != splitter) {
member_client->MessageString(
YOU_RECEIVE_AS_SPLIT,
SHARE_MONEY,
splitter->GetCleanName()
);
}
// Check if there are any coins to add to the player's purse.
if (receive_copper || receive_silver || receive_gold || receive_platinum) {
member_client->AddMoneyToPP(receive_copper, receive_silver, receive_gold, receive_platinum, true); member_client->AddMoneyToPP(receive_copper, receive_silver, receive_gold, receive_platinum, true);
member_client->MessageString(
Chat::MoneySplit,
YOU_RECEIVE_AS_SPLIT,
Strings::Money(receive_platinum, receive_gold, receive_silver, receive_copper).c_str()
);
}
// If logging of player money transactions is enabled, record the transaction. // If logging of player money transactions is enabled, record the transaction.
if (player_event_logs.IsEventEnabled(PlayerEvent::SPLIT_MONEY)) { if (player_event_logs.IsEventEnabled(PlayerEvent::SPLIT_MONEY)) {
@@ -194,13 +213,6 @@ void Group::SplitMoney(uint32 copper, uint32 silver, uint32 gold, uint32 platinu
}; };
RecordPlayerEventLogWithClient(member_client, PlayerEvent::SPLIT_MONEY, e); RecordPlayerEventLogWithClient(member_client, PlayerEvent::SPLIT_MONEY, e);
} }
// Notify the player of their received coins.
member_client->MessageString(
Chat::MoneySplit,
YOU_RECEIVE_AS_SPLIT,
Strings::Money(receive_platinum, receive_gold, receive_silver, receive_copper).c_str()
);
} }
} }
} }
+1 -1
View File
@@ -80,7 +80,7 @@ public:
void GroupMessage(Mob* sender,uint8 language,uint8 lang_skill,const char* message); void GroupMessage(Mob* sender,uint8 language,uint8 lang_skill,const char* message);
void GroupMessageString(Mob* sender, uint32 type, uint32 string_id, const char* message,const char* message2=0,const char* message3=0,const char* message4=0,const char* message5=0,const char* message6=0,const char* message7=0,const char* message8=0,const char* message9=0, uint32 distance = 0); void GroupMessageString(Mob* sender, uint32 type, uint32 string_id, const char* message,const char* message2=0,const char* message3=0,const char* message4=0,const char* message5=0,const char* message6=0,const char* message7=0,const char* message8=0,const char* message9=0, uint32 distance = 0);
uint32 GetTotalGroupDamage(Mob* other); uint32 GetTotalGroupDamage(Mob* other);
void SplitMoney(uint32 copper, uint32 silver, uint32 gold, uint32 platinum, Client *splitter = nullptr); void SplitMoney(uint32 copper, uint32 silver, uint32 gold, uint32 platinum, Client *splitter = nullptr, bool share = false);
inline void SetLeader(Mob* c){ leader = c; }; inline void SetLeader(Mob* c){ leader = c; };
inline Mob* GetLeader() { return leader; }; inline Mob* GetLeader() { return leader; };
std::string GetLeaderName(); std::string GetLeaderName();
+69
View File
@@ -1864,6 +1864,10 @@ bool Client::SwapItem(MoveItem_Struct* move_in) {
LogInventory("Dest slot [{}] has item [{}] ([{}]) with [{}] charges in it", dst_slot_id, dst_inst->GetItem()->Name, dst_inst->GetItem()->ID, dst_inst->GetCharges()); LogInventory("Dest slot [{}] has item [{}] ([{}]) with [{}] charges in it", dst_slot_id, dst_inst->GetItem()->Name, dst_inst->GetItem()->ID, dst_inst->GetCharges());
dstitemid = dst_inst->GetItem()->ID; dstitemid = dst_inst->GetItem()->ID;
} }
if (IsBuyer() && srcitemid > 0) {
CheckIfMovedItemIsPartOfBuyLines(srcitemid);
}
if (IsTrader() && srcitemid>0){ if (IsTrader() && srcitemid>0){
EQ::ItemInstance* srcbag; EQ::ItemInstance* srcbag;
EQ::ItemInstance* dstbag; EQ::ItemInstance* dstbag;
@@ -3427,6 +3431,11 @@ void Client::SetBandolier(const EQApplicationPacket *app)
} }
} }
} }
if (RuleI(Character, BandolierSwapDelay) > 0) {
bandolier_throttle_timer.Start(RuleI(Character, BandolierSwapDelay));
}
// finally, recalculate any stat bonuses from the item change // finally, recalculate any stat bonuses from the item change
CalcBonuses(); CalcBonuses();
} }
@@ -4850,3 +4859,63 @@ bool Client::HasItemOnCorpse(uint32 item_id)
return false; return false;
} }
bool Client::PutItemInInventoryWithStacking(EQ::ItemInstance *inst)
{
auto free_id = GetInv().FindFirstFreeSlotThatFitsItem(inst->GetItem());
if (inst->IsStackable()) {
if (TryStacking(inst, ItemPacketTrade, true, false)) {
return true;
}
}
if (free_id != INVALID_INDEX) {
if (PutItemInInventory(free_id, *inst, true)) {
return true;
}
}
return false;
};
bool Client::FindNumberOfFreeInventorySlotsWithSizeCheck(std::vector<BuyerLineTradeItems_Struct> items)
{
uint32 count = 0;
for (int16 i = EQ::invslot::GENERAL_BEGIN; i <= EQ::invslot::GENERAL_END; i++) {
if ((((uint64) 1 << i) & GetInv().GetLookup()->PossessionsBitmask) == 0) {
continue;
}
EQ::ItemInstance *inv_item = GetInv().GetItem(i);
if (!inv_item) {
// Found available slot in personal inventory. Fits all sizes
count++;
}
if (count >= items.size()) {
return true;
}
if (inv_item->IsClassBag()) {
for (auto const& item:items) {
auto item_tmp = database.GetItem(item.item_id);
if (EQ::InventoryProfile::CanItemFitInContainer(item_tmp, 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 = GetInv().GetItem(base_slot_id + bag_slot);
if (!bag_item) {
// Found a bag slot that fits the item
count++;
}
}
if (count >= items.size()) {
return true;
}
}
}
}
}
return false;
};
+77 -17
View File
@@ -1214,25 +1214,23 @@ void Lua_Client::AddPVPPoints(uint32 points) {
self->AddPVPPoints(points); self->AddPVPPoints(points);
} }
void Lua_Client::AddCrystals(uint32 radiant, uint32 ebon) { void Lua_Client::AddCrystals(uint32 radiant_count, uint32 ebon_count) {
Lua_Safe_Call_Void(); Lua_Safe_Call_Void();
if (ebon != 0) { if (ebon_count != 0) {
if (ebon > 0) { if (ebon_count > 0) {
self->AddEbonCrystals(ebon); self->AddEbonCrystals(ebon_count);
return; } else {
self->RemoveEbonCrystals(ebon_count);
}
} }
self->RemoveEbonCrystals(ebon); if (radiant_count != 0) {
if (radiant_count > 0) {
self->AddRadiantCrystals(radiant_count);
} else {
self->RemoveRadiantCrystals(radiant_count);
} }
if (radiant != 0) {
if (radiant > 0) {
self->AddRadiantCrystals(radiant);
return;
}
self->RemoveRadiantCrystals(radiant);
} }
} }
@@ -1411,9 +1409,9 @@ void Lua_Client::FailTask(int task) {
self->FailTask(task); self->FailTask(task);
} }
bool Lua_Client::IsTaskCompleted(int task) { bool Lua_Client::IsTaskCompleted(int task_id) {
Lua_Safe_Call_Bool(); Lua_Safe_Call_Bool();
return self->IsTaskCompleted(task) != 0; return self->IsTaskCompleted(task_id);
} }
bool Lua_Client::IsTaskActive(int task) { bool Lua_Client::IsTaskActive(int task) {
@@ -1771,7 +1769,7 @@ int Lua_Client::CalcATK() {
return self->CalcATK(); return self->CalcATK();
} }
void Lua_Client::FilteredMessage(Mob *sender, uint32 type, int filter, const char *message) void Lua_Client::FilteredMessage(Lua_Mob sender, uint32 type, int filter, const char *message)
{ {
Lua_Safe_Call_Void(); Lua_Safe_Call_Void();
self->FilteredMessage(sender, type, (eqFilterType)filter, message); self->FilteredMessage(sender, type, (eqFilterType)filter, message);
@@ -3218,6 +3216,12 @@ void Lua_Client::GrantAllAAPoints(uint8 unlock_level)
self->GrantAllAAPoints(unlock_level); self->GrantAllAAPoints(unlock_level);
} }
void Lua_Client::GrantAllAAPoints(uint8 unlock_level, bool skip_grant_only)
{
Lua_Safe_Call_Void();
self->GrantAllAAPoints(unlock_level, skip_grant_only);
}
void Lua_Client::AddEbonCrystals(uint32 amount) void Lua_Client::AddEbonCrystals(uint32 amount)
{ {
Lua_Safe_Call_Void(); Lua_Safe_Call_Void();
@@ -3381,6 +3385,57 @@ uint8 Lua_Client::GetSkillTrainLevel(int skill_id)
return self->GetSkillTrainLevel(static_cast<EQ::skills::SkillType>(skill_id), self->GetClass()); return self->GetSkillTrainLevel(static_cast<EQ::skills::SkillType>(skill_id), self->GetClass());
} }
bool Lua_Client::AreTasksCompleted(luabind::object task_ids)
{
Lua_Safe_Call_Int();
if (luabind::type(task_ids) != LUA_TTABLE) {
return false;
}
std::vector<int> v;
int index = 1;
while (luabind::type(task_ids[index]) != LUA_TNIL) {
auto current_id = task_ids[index];
int task_id = 0;
if (luabind::type(current_id) != LUA_TNIL) {
try {
task_id = luabind::object_cast<int>(current_id);
} catch(luabind::cast_failed &) {
}
} else {
break;
}
v.push_back(task_id);
++index;
}
if (v.empty()) {
return false;
}
return self->AreTasksCompleted(v);
}
void Lua_Client::AreaTaunt()
{
Lua_Safe_Call_Void();
entity_list.AETaunt(self);
}
void Lua_Client::AreaTaunt(float range)
{
Lua_Safe_Call_Void();
entity_list.AETaunt(self, range);
}
void Lua_Client::AreaTaunt(float range, int bonus_hate)
{
Lua_Safe_Call_Void();
entity_list.AETaunt(self, range, bonus_hate);
}
luabind::scope lua_register_client() { luabind::scope lua_register_client() {
return luabind::class_<Lua_Client, Lua_Mob>("Client") return luabind::class_<Lua_Client, Lua_Mob>("Client")
.def(luabind::constructor<>()) .def(luabind::constructor<>())
@@ -3427,6 +3482,10 @@ luabind::scope lua_register_client() {
.def("ApplySpellRaid", (void(Lua_Client::*)(int,int,int,bool))&Lua_Client::ApplySpellRaid) .def("ApplySpellRaid", (void(Lua_Client::*)(int,int,int,bool))&Lua_Client::ApplySpellRaid)
.def("ApplySpellRaid", (void(Lua_Client::*)(int,int,int,bool,bool))&Lua_Client::ApplySpellRaid) .def("ApplySpellRaid", (void(Lua_Client::*)(int,int,int,bool,bool))&Lua_Client::ApplySpellRaid)
.def("ApplySpellRaid", (void(Lua_Client::*)(int,int,int,bool,bool,bool))&Lua_Client::ApplySpellRaid) .def("ApplySpellRaid", (void(Lua_Client::*)(int,int,int,bool,bool,bool))&Lua_Client::ApplySpellRaid)
.def("AreTasksCompleted", (bool(Lua_Client::*)(luabind::object))&Lua_Client::AreTasksCompleted)
.def("AreaTaunt", (void(Lua_Client::*)(void))&Lua_Client::AreaTaunt)
.def("AreaTaunt", (void(Lua_Client::*)(float))&Lua_Client::AreaTaunt)
.def("AreaTaunt", (void(Lua_Client::*)(float, int))&Lua_Client::AreaTaunt)
.def("AssignTask", (void(Lua_Client::*)(int))&Lua_Client::AssignTask) .def("AssignTask", (void(Lua_Client::*)(int))&Lua_Client::AssignTask)
.def("AssignTask", (void(Lua_Client::*)(int,int))&Lua_Client::AssignTask) .def("AssignTask", (void(Lua_Client::*)(int,int))&Lua_Client::AssignTask)
.def("AssignTask", (void(Lua_Client::*)(int,int,bool))&Lua_Client::AssignTask) .def("AssignTask", (void(Lua_Client::*)(int,int,bool))&Lua_Client::AssignTask)
@@ -3646,6 +3705,7 @@ luabind::scope lua_register_client() {
.def("GoFish", (void(Lua_Client::*)(void))&Lua_Client::GoFish) .def("GoFish", (void(Lua_Client::*)(void))&Lua_Client::GoFish)
.def("GrantAllAAPoints", (void(Lua_Client::*)(void))&Lua_Client::GrantAllAAPoints) .def("GrantAllAAPoints", (void(Lua_Client::*)(void))&Lua_Client::GrantAllAAPoints)
.def("GrantAllAAPoints", (void(Lua_Client::*)(uint8))&Lua_Client::GrantAllAAPoints) .def("GrantAllAAPoints", (void(Lua_Client::*)(uint8))&Lua_Client::GrantAllAAPoints)
.def("GrantAllAAPoints", (void(Lua_Client::*)(uint8,bool))&Lua_Client::GrantAllAAPoints)
.def("GrantAlternateAdvancementAbility", (bool(Lua_Client::*)(int, int))&Lua_Client::GrantAlternateAdvancementAbility) .def("GrantAlternateAdvancementAbility", (bool(Lua_Client::*)(int, int))&Lua_Client::GrantAlternateAdvancementAbility)
.def("GrantAlternateAdvancementAbility", (bool(Lua_Client::*)(int, int, bool))&Lua_Client::GrantAlternateAdvancementAbility) .def("GrantAlternateAdvancementAbility", (bool(Lua_Client::*)(int, int, bool))&Lua_Client::GrantAlternateAdvancementAbility)
.def("GuildID", (uint32(Lua_Client::*)(void))&Lua_Client::GuildID) .def("GuildID", (uint32(Lua_Client::*)(void))&Lua_Client::GuildID)
+8 -3
View File
@@ -316,7 +316,7 @@ public:
void KeyRingAdd(uint32 item); void KeyRingAdd(uint32 item);
bool KeyRingCheck(uint32 item); bool KeyRingCheck(uint32 item);
void AddPVPPoints(uint32 points); void AddPVPPoints(uint32 points);
void AddCrystals(uint32 radiant, uint32 ebon); void AddCrystals(uint32 radiant_count, uint32 ebon_count);
void SetEbonCrystals(uint32 value); void SetEbonCrystals(uint32 value);
void SetRadiantCrystals(uint32 value); void SetRadiantCrystals(uint32 value);
uint32 GetPVPPoints(); uint32 GetPVPPoints();
@@ -360,7 +360,7 @@ public:
void AssignTask(int task_id, int npc_id); void AssignTask(int task_id, int npc_id);
void AssignTask(int task_id, int npc_id, bool enforce_level_requirement); void AssignTask(int task_id, int npc_id, bool enforce_level_requirement);
void FailTask(int task); void FailTask(int task);
bool IsTaskCompleted(int task); bool IsTaskCompleted(int task_id);
bool IsTaskActive(int task); bool IsTaskActive(int task);
bool IsTaskActivityActive(int task, int activity); bool IsTaskActivityActive(int task, int activity);
void LockSharedTask(bool lock); void LockSharedTask(bool lock);
@@ -424,7 +424,7 @@ public:
bool IsDead(); bool IsDead();
int CalcCurrentWeight(); int CalcCurrentWeight();
int CalcATK(); int CalcATK();
void FilteredMessage(Mob *sender, uint32 type, int filter, const char* message); void FilteredMessage(Lua_Mob sender, uint32 type, int filter, const char* message);
void EnableAreaHPRegen(int value); void EnableAreaHPRegen(int value);
void DisableAreaHPRegen(); void DisableAreaHPRegen();
void EnableAreaManaRegen(int value); void EnableAreaManaRegen(int value);
@@ -487,6 +487,7 @@ public:
void SetBucket(std::string bucket_name, std::string bucket_value, std::string expiration); void SetBucket(std::string bucket_name, std::string bucket_value, std::string expiration);
void GrantAllAAPoints(); void GrantAllAAPoints();
void GrantAllAAPoints(uint8 unlock_level); void GrantAllAAPoints(uint8 unlock_level);
void GrantAllAAPoints(uint8 unlock_level, bool skip_grant_only);
void AddEbonCrystals(uint32 amount); void AddEbonCrystals(uint32 amount);
void AddRadiantCrystals(uint32 amount); void AddRadiantCrystals(uint32 amount);
void RemoveEbonCrystals(uint32 amount); void RemoveEbonCrystals(uint32 amount);
@@ -505,6 +506,9 @@ public:
void DescribeSpecialAbilities(Lua_NPC n); void DescribeSpecialAbilities(Lua_NPC n);
void ResetLeadershipAA(); void ResetLeadershipAA();
uint8 GetSkillTrainLevel(int skill_id); uint8 GetSkillTrainLevel(int skill_id);
void AreaTaunt();
void AreaTaunt(float range);
void AreaTaunt(float range, int bonus_hate);
void ApplySpell(int spell_id); void ApplySpell(int spell_id);
void ApplySpell(int spell_id, int duration); void ApplySpell(int spell_id, int duration);
@@ -577,6 +581,7 @@ public:
void CampAllBots(uint8 class_id); void CampAllBots(uint8 class_id);
bool RemoveAAPoints(uint32 points); bool RemoveAAPoints(uint32 points);
bool RemoveAlternateCurrencyValue(uint32 currency_id, uint32 amount); bool RemoveAlternateCurrencyValue(uint32 currency_id, uint32 amount);
bool AreTasksCompleted(luabind::object task_ids);
void DialogueWindow(std::string markdown); void DialogueWindow(std::string markdown);
+108
View File
@@ -180,6 +180,96 @@ uint32 Lua_Door::GetID() {
return self->GetID(); return self->GetID();
} }
uint8 Lua_Door::GetTriggerDoorID() {
Lua_Safe_Call_Int();
return self->GetTriggerDoorID();
}
uint8 Lua_Door::GetTriggerType() {
Lua_Safe_Call_Int();
return self->GetTriggerType();
}
bool Lua_Door::IsLDoNDoor() {
Lua_Safe_Call_Bool();
return self->IsLDoNDoor();
}
uint32 Lua_Door::GetClientVersionMask() {
Lua_Safe_Call_Int();
return self->GetClientVersionMask();
}
int Lua_Door::GetDoorParam() {
Lua_Safe_Call_Int();
return self->GetDoorParam();
}
bool Lua_Door::HasDestinationZone() {
Lua_Safe_Call_Bool();
return self->HasDestinationZone();
}
bool Lua_Door::IsDestinationZoneSame() {
Lua_Safe_Call_Bool();
return self->IsDestinationZoneSame();
}
bool Lua_Door::IsDoorBlacklisted() {
Lua_Safe_Call_Bool();
return self->IsDoorBlacklisted();
}
std::string Lua_Door::GetDestinationZoneName() {
Lua_Safe_Call_String();
return self->GetDestinationZoneName();
}
int Lua_Door::GetDestinationInstanceID() {
Lua_Safe_Call_Int();
return self->GetDestinationInstanceID();
}
float Lua_Door::GetDestinationX() {
Lua_Safe_Call_Real();
return self->GetDestinationX();
}
float Lua_Door::GetDestinationY() {
Lua_Safe_Call_Real();
return self->GetDestinationY();
}
float Lua_Door::GetDestinationZ() {
Lua_Safe_Call_Real();
return self->GetDestinationZ();
}
float Lua_Door::GetDestinationHeading() {
Lua_Safe_Call_Real();
return self->GetDestinationHeading();
}
int Lua_Door::GetDzSwitchID() {
Lua_Safe_Call_Int();
return self->GetDzSwitchID();
}
int Lua_Door::GetInvertState() {
Lua_Safe_Call_Int();
return self->GetInvertState();
}
void Lua_Door::SetInvertState(int invert_state) {
Lua_Safe_Call_Void();
self->SetInvertState(invert_state);
}
uint32 Lua_Door::GetGuildID() {
Lua_Safe_Call_Int();
return self->GetGuildID();
}
luabind::scope lua_register_door() { luabind::scope lua_register_door() {
return luabind::class_<Lua_Door, Lua_Entity>("Door") return luabind::class_<Lua_Door, Lua_Entity>("Door")
.def(luabind::constructor<>()) .def(luabind::constructor<>())
@@ -191,24 +281,42 @@ luabind::scope lua_register_door() {
.def("ForceOpen", (void(Lua_Door::*)(Lua_Mob))&Lua_Door::ForceOpen) .def("ForceOpen", (void(Lua_Door::*)(Lua_Mob))&Lua_Door::ForceOpen)
.def("ForceOpen", (void(Lua_Door::*)(Lua_Mob,bool))&Lua_Door::ForceOpen) .def("ForceOpen", (void(Lua_Door::*)(Lua_Mob,bool))&Lua_Door::ForceOpen)
.def("GetDisableTimer", (bool(Lua_Door::*)(void))&Lua_Door::GetDisableTimer) .def("GetDisableTimer", (bool(Lua_Door::*)(void))&Lua_Door::GetDisableTimer)
.def("GetClientVersionMask", (uint32(Lua_Door::*)(void))&Lua_Door::GetClientVersionMask)
.def("GetDestinationHeading", (float(Lua_Door::*)(void))&Lua_Door::GetDestinationHeading)
.def("GetDestinationInstanceID", (int(Lua_Door::*)(void))&Lua_Door::GetDestinationInstanceID)
.def("GetDestinationX", (float(Lua_Door::*)(void))&Lua_Door::GetDestinationX)
.def("GetDestinationY", (float(Lua_Door::*)(void))&Lua_Door::GetDestinationY)
.def("GetDestinationZ", (float(Lua_Door::*)(void))&Lua_Door::GetDestinationZ)
.def("GetDestinationZoneName", (std::string(Lua_Door::*)(void))&Lua_Door::GetDestinationZoneName)
.def("GetDoorDBID", (uint32(Lua_Door::*)(void))&Lua_Door::GetDoorDBID) .def("GetDoorDBID", (uint32(Lua_Door::*)(void))&Lua_Door::GetDoorDBID)
.def("GetDoorID", (uint32(Lua_Door::*)(void))&Lua_Door::GetDoorID) .def("GetDoorID", (uint32(Lua_Door::*)(void))&Lua_Door::GetDoorID)
.def("GetDoorName", (const char*(Lua_Door::*)(void))&Lua_Door::GetDoorName) .def("GetDoorName", (const char*(Lua_Door::*)(void))&Lua_Door::GetDoorName)
.def("GetDoorParam", (int(Lua_Door::*)(void))&Lua_Door::GetDoorParam)
.def("GetDzSwitchID", (int(Lua_Door::*)(void))&Lua_Door::GetDzSwitchID)
.def("GetGuildID", (uint32(Lua_Door::*)(void))&Lua_Door::GetGuildID)
.def("GetHeading", (float(Lua_Door::*)(void))&Lua_Door::GetHeading) .def("GetHeading", (float(Lua_Door::*)(void))&Lua_Door::GetHeading)
.def("GetID", (uint32(Lua_Door::*)(void))&Lua_Door::GetID) .def("GetID", (uint32(Lua_Door::*)(void))&Lua_Door::GetID)
.def("GetIncline", (uint32(Lua_Door::*)(void))&Lua_Door::GetIncline) .def("GetIncline", (uint32(Lua_Door::*)(void))&Lua_Door::GetIncline)
.def("GetInvertState", (int(Lua_Door::*)(void))&Lua_Door::GetInvertState)
.def("GetKeyItem", (uint32(Lua_Door::*)(void))&Lua_Door::GetKeyItem) .def("GetKeyItem", (uint32(Lua_Door::*)(void))&Lua_Door::GetKeyItem)
.def("GetLockPick", (uint32(Lua_Door::*)(void))&Lua_Door::GetLockPick) .def("GetLockPick", (uint32(Lua_Door::*)(void))&Lua_Door::GetLockPick)
.def("GetNoKeyring", (int(Lua_Door::*)(void))&Lua_Door::GetNoKeyring) .def("GetNoKeyring", (int(Lua_Door::*)(void))&Lua_Door::GetNoKeyring)
.def("GetOpenType", (uint32(Lua_Door::*)(void))&Lua_Door::GetOpenType) .def("GetOpenType", (uint32(Lua_Door::*)(void))&Lua_Door::GetOpenType)
.def("GetSize", (uint32(Lua_Door::*)(void))&Lua_Door::GetSize) .def("GetSize", (uint32(Lua_Door::*)(void))&Lua_Door::GetSize)
.def("GetTriggerDoorID", (uint8(Lua_Door::*)(void))&Lua_Door::GetTriggerDoorID)
.def("GetTriggerType", (uint8(Lua_Door::*)(void))&Lua_Door::GetTriggerType)
.def("GetX", (float(Lua_Door::*)(void))&Lua_Door::GetX) .def("GetX", (float(Lua_Door::*)(void))&Lua_Door::GetX)
.def("GetY", (float(Lua_Door::*)(void))&Lua_Door::GetY) .def("GetY", (float(Lua_Door::*)(void))&Lua_Door::GetY)
.def("GetZ", (float(Lua_Door::*)(void))&Lua_Door::GetZ) .def("GetZ", (float(Lua_Door::*)(void))&Lua_Door::GetZ)
.def("HasDestinationZone", (bool(Lua_Door::*)(void))&Lua_Door::HasDestinationZone)
.def("IsDestinationZoneSame", (bool(Lua_Door::*)(void))&Lua_Door::IsDestinationZoneSame)
.def("IsDoorBlacklisted", (bool(Lua_Door::*)(void))&Lua_Door::IsDoorBlacklisted)
.def("IsLDoNDoor", (bool(Lua_Door::*)(void))&Lua_Door::IsLDoNDoor)
.def("SetDisableTimer", (void(Lua_Door::*)(bool))&Lua_Door::SetDisableTimer) .def("SetDisableTimer", (void(Lua_Door::*)(bool))&Lua_Door::SetDisableTimer)
.def("SetDoorName", (void(Lua_Door::*)(const char*))&Lua_Door::SetDoorName) .def("SetDoorName", (void(Lua_Door::*)(const char*))&Lua_Door::SetDoorName)
.def("SetHeading", (void(Lua_Door::*)(float))&Lua_Door::SetHeading) .def("SetHeading", (void(Lua_Door::*)(float))&Lua_Door::SetHeading)
.def("SetIncline", (void(Lua_Door::*)(uint32))&Lua_Door::SetIncline) .def("SetIncline", (void(Lua_Door::*)(uint32))&Lua_Door::SetIncline)
.def("SetInvertState", (void(Lua_Door::*)(int))&Lua_Door::SetInvertState)
.def("SetKeyItem", (void(Lua_Door::*)(uint32))&Lua_Door::SetKeyItem) .def("SetKeyItem", (void(Lua_Door::*)(uint32))&Lua_Door::SetKeyItem)
.def("SetLocation", (void(Lua_Door::*)(float,float,float))&Lua_Door::SetLocation) .def("SetLocation", (void(Lua_Door::*)(float,float,float))&Lua_Door::SetLocation)
.def("SetLockPick", (void(Lua_Door::*)(uint32))&Lua_Door::SetLockPick) .def("SetLockPick", (void(Lua_Door::*)(uint32))&Lua_Door::SetLockPick)
+18
View File
@@ -63,6 +63,24 @@ public:
void ForceClose(Lua_Mob sender); void ForceClose(Lua_Mob sender);
void ForceClose(Lua_Mob sender, bool alt_mode); void ForceClose(Lua_Mob sender, bool alt_mode);
uint32 GetID(); uint32 GetID();
uint8 GetTriggerDoorID();
uint8 GetTriggerType();
bool IsLDoNDoor();
uint32 GetClientVersionMask();
int GetDoorParam();
bool HasDestinationZone();
bool IsDestinationZoneSame();
bool IsDoorBlacklisted();
std::string GetDestinationZoneName();
int GetDestinationInstanceID();
float GetDestinationX();
float GetDestinationY();
float GetDestinationZ();
float GetDestinationHeading();
int GetDzSwitchID();
int GetInvertState();
void SetInvertState(int invert_state);
uint32 GetGuildID();
}; };
#endif #endif
+98
View File
@@ -680,11 +680,107 @@ Lua_Mob_List Lua_EntityList::GetCloseMobList(Lua_Mob mob, float distance, bool i
return ret; return ret;
} }
void Lua_EntityList::AreaAttack(Lua_Mob attacker, float distance)
{
Lua_Safe_Call_Void();
self->AEAttack(attacker, distance);
}
void Lua_EntityList::AreaAttack(Lua_Mob attacker, float distance, int16 slot_id)
{
Lua_Safe_Call_Void();
self->AEAttack(attacker, distance, slot_id);
}
void Lua_EntityList::AreaAttack(Lua_Mob attacker, float distance, int16 slot_id, int count)
{
Lua_Safe_Call_Void();
self->AEAttack(attacker, distance, slot_id, count);
}
void Lua_EntityList::AreaAttack(Lua_Mob attacker, float distance, int16 slot_id, int count, bool is_from_spell)
{
Lua_Safe_Call_Void();
self->AEAttack(attacker, distance, slot_id, count, is_from_spell);
}
void Lua_EntityList::AreaAttack(Lua_Mob attacker, float distance, int16 slot_id, int count, bool is_from_spell, int attack_rounds)
{
Lua_Safe_Call_Void();
self->AEAttack(attacker, distance, slot_id, count, is_from_spell, attack_rounds);
}
void Lua_EntityList::AreaSpell(Lua_Mob caster, Lua_Mob center, uint16 spell_id)
{
Lua_Safe_Call_Void();
self->AESpell(caster, center, spell_id);
}
void Lua_EntityList::AreaSpell(Lua_Mob caster, Lua_Mob center, uint16 spell_id, bool affect_caster)
{
Lua_Safe_Call_Void();
self->AESpell(caster, center, spell_id, affect_caster);
}
void Lua_EntityList::AreaSpell(Lua_Mob caster, Lua_Mob center, uint16 spell_id, bool affect_caster, int16 resist_adjust)
{
Lua_Safe_Call_Void();
self->AESpell(caster, center, spell_id, affect_caster, resist_adjust);
}
void Lua_EntityList::AreaSpell(Lua_Mob caster, Lua_Mob center, uint16 spell_id, bool affect_caster, int16 resist_adjust, int max_targets)
{
Lua_Safe_Call_Void();
self->AESpell(caster, center, spell_id, affect_caster, resist_adjust, &max_targets);
}
void Lua_EntityList::AreaTaunt(Lua_Client caster)
{
Lua_Safe_Call_Void();
self->AETaunt(caster);
}
void Lua_EntityList::AreaTaunt(Lua_Client caster, float range)
{
Lua_Safe_Call_Void();
self->AETaunt(caster, range);
}
void Lua_EntityList::AreaTaunt(Lua_Client caster, float range, int bonus_hate)
{
Lua_Safe_Call_Void();
self->AETaunt(caster, range, bonus_hate);
}
void Lua_EntityList::MassGroupBuff(Lua_Mob caster, Lua_Mob center, uint16 spell_id)
{
Lua_Safe_Call_Void();
self->MassGroupBuff(caster, center, spell_id);
}
void Lua_EntityList::MassGroupBuff(Lua_Mob caster, Lua_Mob center, uint16 spell_id, bool affect_caster)
{
Lua_Safe_Call_Void();
self->MassGroupBuff(caster, center, spell_id, affect_caster);
}
luabind::scope lua_register_entity_list() { luabind::scope lua_register_entity_list() {
return luabind::class_<Lua_EntityList>("EntityList") return luabind::class_<Lua_EntityList>("EntityList")
.def(luabind::constructor<>()) .def(luabind::constructor<>())
.property("null", &Lua_EntityList::Null) .property("null", &Lua_EntityList::Null)
.property("valid", &Lua_EntityList::Valid) .property("valid", &Lua_EntityList::Valid)
.def("AreaAttack", (void(Lua_EntityList::*)(Lua_Mob, float))&Lua_EntityList::AreaAttack)
.def("AreaAttack", (void(Lua_EntityList::*)(Lua_Mob, float, int16))&Lua_EntityList::AreaAttack)
.def("AreaAttack", (void(Lua_EntityList::*)(Lua_Mob, float, int16, int))&Lua_EntityList::AreaAttack)
.def("AreaAttack", (void(Lua_EntityList::*)(Lua_Mob, float, int16, int, bool))&Lua_EntityList::AreaAttack)
.def("AreaAttack", (void(Lua_EntityList::*)(Lua_Mob, float, int16, int, bool, int))&Lua_EntityList::AreaAttack)
.def("AreaSpell", (void(Lua_EntityList::*)(Lua_Mob, Lua_Mob, uint16))&Lua_EntityList::AreaSpell)
.def("AreaSpell", (void(Lua_EntityList::*)(Lua_Mob, Lua_Mob, uint16, bool))&Lua_EntityList::AreaSpell)
.def("AreaSpell", (void(Lua_EntityList::*)(Lua_Mob, Lua_Mob, uint16, bool, int16))&Lua_EntityList::AreaSpell)
.def("AreaSpell", (void(Lua_EntityList::*)(Lua_Mob, Lua_Mob, uint16, bool, int16, int))&Lua_EntityList::AreaSpell)
.def("AreaTaunt", (void(Lua_EntityList::*)(Lua_Client))&Lua_EntityList::AreaTaunt)
.def("AreaTaunt", (void(Lua_EntityList::*)(Lua_Client, float))&Lua_EntityList::AreaTaunt)
.def("AreaTaunt", (void(Lua_EntityList::*)(Lua_Client, float, int))&Lua_EntityList::AreaTaunt)
.def("CanAddHateForMob", (bool(Lua_EntityList::*)(Lua_Mob))&Lua_EntityList::CanAddHateForMob) .def("CanAddHateForMob", (bool(Lua_EntityList::*)(Lua_Mob))&Lua_EntityList::CanAddHateForMob)
.def("ChannelMessage", (void(Lua_EntityList::*)(Lua_Mob, int, uint8, const char*))&Lua_EntityList::ChannelMessage) .def("ChannelMessage", (void(Lua_EntityList::*)(Lua_Mob, int, uint8, const char*))&Lua_EntityList::ChannelMessage)
.def("ClearClientPetitionQueue", (void(Lua_EntityList::*)(void))&Lua_EntityList::ClearClientPetitionQueue) .def("ClearClientPetitionQueue", (void(Lua_EntityList::*)(void))&Lua_EntityList::ClearClientPetitionQueue)
@@ -759,6 +855,8 @@ luabind::scope lua_register_entity_list() {
.def("Marquee", (void(Lua_EntityList::*)(uint32, std::string))&Lua_EntityList::Marquee) .def("Marquee", (void(Lua_EntityList::*)(uint32, std::string))&Lua_EntityList::Marquee)
.def("Marquee", (void(Lua_EntityList::*)(uint32, std::string, uint32))&Lua_EntityList::Marquee) .def("Marquee", (void(Lua_EntityList::*)(uint32, std::string, uint32))&Lua_EntityList::Marquee)
.def("Marquee", (void(Lua_EntityList::*)(uint32, uint32, uint32, uint32, uint32, std::string))&Lua_EntityList::Marquee) .def("Marquee", (void(Lua_EntityList::*)(uint32, uint32, uint32, uint32, uint32, std::string))&Lua_EntityList::Marquee)
.def("MassGroupBuff", (void(Lua_EntityList::*)(Lua_Mob, Lua_Mob, uint16))&Lua_EntityList::MassGroupBuff)
.def("MassGroupBuff", (void(Lua_EntityList::*)(Lua_Mob, Lua_Mob, uint16, bool))&Lua_EntityList::MassGroupBuff)
.def("Message", (void(Lua_EntityList::*)(uint32, uint32, const char*))&Lua_EntityList::Message) .def("Message", (void(Lua_EntityList::*)(uint32, uint32, const char*))&Lua_EntityList::Message)
.def("MessageClose", (void(Lua_EntityList::*)(Lua_Mob, bool, float, uint32, const char*))&Lua_EntityList::MessageClose) .def("MessageClose", (void(Lua_EntityList::*)(Lua_Mob, bool, float, uint32, const char*))&Lua_EntityList::MessageClose)
.def("MessageGroup", (void(Lua_EntityList::*)(Lua_Mob, bool, uint32, const char*))&Lua_EntityList::MessageGroup) .def("MessageGroup", (void(Lua_EntityList::*)(Lua_Mob, bool, uint32, const char*))&Lua_EntityList::MessageGroup)
+15
View File
@@ -142,6 +142,21 @@ public:
Lua_Mob_List GetCloseMobList(Lua_Mob mob); Lua_Mob_List GetCloseMobList(Lua_Mob mob);
Lua_Mob_List GetCloseMobList(Lua_Mob mob, float distance); Lua_Mob_List GetCloseMobList(Lua_Mob mob, float distance);
Lua_Mob_List GetCloseMobList(Lua_Mob mob, float distance, bool ignore_self); Lua_Mob_List GetCloseMobList(Lua_Mob mob, float distance, bool ignore_self);
void AreaAttack(Lua_Mob attacker, float distance);
void AreaAttack(Lua_Mob attacker, float distance, int16 slot_id);
void AreaAttack(Lua_Mob attacker, float distance, int16 slot_id, int count);
void AreaAttack(Lua_Mob attacker, float distance, int16 slot_id, int count, bool is_from_spell);
void AreaAttack(Lua_Mob attacker, float distance, int16 slot_id, int count, bool is_from_spell, int attack_rounds);
void AreaSpell(Lua_Mob caster, Lua_Mob center, uint16 spell_id);
void AreaSpell(Lua_Mob caster, Lua_Mob center, uint16 spell_id, bool affect_caster);
void AreaSpell(Lua_Mob caster, Lua_Mob center, uint16 spell_id, bool affect_caster, int16 resist_adjust);
void AreaSpell(Lua_Mob caster, Lua_Mob center, uint16 spell_id, bool affect_caster, int16 resist_adjust, int max_targets);
void AreaTaunt(Lua_Client caster);
void AreaTaunt(Lua_Client caster, float range);
void AreaTaunt(Lua_Client caster, float range, int bonus_hate);
void MassGroupBuff(Lua_Mob caster, Lua_Mob center, uint16 spell_id);
void MassGroupBuff(Lua_Mob caster, Lua_Mob center, uint16 spell_id, bool affect_caster);
}; };
#endif #endif

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