Compare commits

...

129 Commits

Author SHA1 Message Date
KimLS 4befe32c61 Handful of opcodes identified. 2025-01-07 23:01:00 -08:00
KimLS ef8e1cf6e6 Several opcodes 2025-01-05 18:17:50 -08:00
KimLS c718ddbbf9 Fix compile issue with new casting slot conversion added the other day. 2025-01-05 15:02:14 -08:00
KimLS 8eb20efabb -Stun
-Player Trading
-Item Recast
-CancelSneakHide
-Item Verification
2025-01-03 22:19:15 -08:00
KimLS 9a18b7772a Some more work on merchant system. 2025-01-01 13:38:02 -08:00
KimLS 33164dc47a Added rof2 style discipline opcodes. Might need work but looking at assembly looks like it might work too. 2024-12-30 19:39:26 -08:00
KimLS 4d05e4b53e Add encodes/decodes for delete charge and delete item 2024-12-30 19:16:49 -08:00
KimLS 24dbe6da0e Add tradeskill opcodes, some encodes and decodes to match rof2; i think they're all still the same but i could be wrong. 2024-12-30 19:10:56 -08:00
KimLS 0241a90505 -ApplyPoison
-Encode CastSpell (needs to be checked more)
-Decode animation
2024-12-29 19:37:15 -08:00
KimLS bd2798f2cc Add support for augment items. 2024-12-28 22:59:32 -08:00
KimLS a42d6e8ee1 Reorder decodes in alpha order too. 2024-12-28 09:24:32 -08:00
KimLS 745eeb6eae A few more ops; some need server support still.
Reorder encodes to be alphabetical for better maintanability.
2024-12-27 23:22:33 -08:00
KimLS 91bd9ccf26 Minor group work 2024-12-24 16:02:18 -08:00
KimLS 45db09303f Add a few opcodes
Add onlevelmessage encode
2024-12-24 14:29:51 -08:00
KimLS d9132e84ab Illusion and money on corpse 2024-12-24 12:03:30 -08:00
KimLS 2cb4c55613 Some opcodes. 2024-12-19 19:15:27 -08:00
KimLS f040bbdb03 -ZoneToBind struct
-Added opcode for ZoneToBind
-Added opcode for RespawnWnd (it looks like this is the same as rof2 so no struct work right now unless it doesn't end up working)
2024-12-18 22:16:09 -08:00
KimLS 852667016d Blocked buffs 2024-12-16 22:13:37 -08:00
KimLS 62a84388c0 AA struct work 2024-12-16 18:08:27 -08:00
KimLS 33ab28c1e0 Fix for corpses not serializing correctly (hopefully). 2024-12-14 23:23:55 -08:00
KimLS be0c6b5e05 Work on skill updates. 2024-12-14 19:37:10 -08:00
KimLS 6070e73b16 GM training wip 2024-12-12 22:54:09 -08:00
KimLS ef5fdafdbe GM training request decode. 2024-12-12 21:03:41 -08:00
KimLS 6d5424e7b0 Update a few opcodes we don't check contents of. 2024-12-12 20:38:37 -08:00
KimLS 0fdfda9c53 updated patch file too 2024-12-12 20:02:26 -08:00
KimLS 0da381c272 Add OP_MobHealth encode. 2024-12-12 19:56:13 -08:00
KimLS b761f1cdf9 -Updated pp buff doc
-Support for Server->Laurion item links.
-Laurion->Server item links still do not work right.
2024-12-09 22:56:11 -08:00
KimLS ce98118cc8 Basic spellcasting works a bit. 2024-12-08 22:56:34 -08:00
KimLS 13f57f3c5d WIP; buff doesn't seem to full work; need to fix op_action 2024-12-07 19:42:40 -08:00
KimLS 7083a74b31 WIP spell casting for laurion 2024-12-07 13:45:27 -08:00
KimLS d3ac751dd1 Merchant window startup 2024-12-05 19:39:59 -08:00
KimLS 2db1b1b9b0 More work on item packet 2024-12-04 22:28:07 -08:00
KimLS c4a7fcc063 -Added several opcodes; not all confirmed working yet.
-Partial support for item packet (several of the types have been shuffled so this will take a while to get working).
2024-12-04 21:07:50 -08:00
KimLS 2c48ec39ef Impl SendAATable 2024-12-03 20:37:44 -08:00
KimLS 352f52e65d -Add function for converting laurion links (non functional)
-Fix move items on cursor
-Fill out more of the filters even if we don't use them atm
2024-12-03 19:51:37 -08:00
KimLS 32a1d7c43e -Exp update almost works, but inital at zonein is broken
-MoveItem has opcode now but is not translating correctly.
2024-12-02 23:36:13 -08:00
KimLS af8f85cfd9 -Basic move item support.
-Update some Laurion limits to better support new item slots.
-Camping on Laurion will now work like it does on live.
-Fixed a few of the Laurion exp messages (this will need some work).  Packet calc is still all fked up.
2024-12-02 22:41:20 -08:00
KimLS 392998325b -Damage mostly works
-Death kinda sorta works; need to fix an ordering issue in source (we send death before damage on killing blow)
-Fix animation
-Fix auto attack
-Camp
2024-12-01 23:42:28 -08:00
KimLS 591fa0eb1d -Set laurion commands to $ from . since that's closer to muscle memory.
-Add translation for OP_SpawnAppearance (later to support guilds we will need to extend it a bit server side.)
2024-11-30 23:52:00 -08:00
KimLS 1e35d30c8f Now send membership for Laurion in zone right before player profile like live. 2024-11-30 19:04:57 -08:00
KimLS 2d8b777120 Fix for door clicking, add a few more opcodes. 2024-11-30 15:17:46 -08:00
KimLS b95cd989c4 Merge branch 'master' into larion 2024-11-27 18:04:08 -08:00
Alex King fe9df46a24 [Bug Fix] Fix EVENT_COMBAT on NPC Death (#4558) 2024-11-27 20:30:29 -05:00
KimLS f9918d47d7 Work in progress on some packets; consider and HP update look right but I'm still testing. 2024-11-26 22:52:45 -08:00
KimLS e74d4b6e67 Add formatted and simple message. npc say should work now; though we should consider moving them to what live uses "OP_SpecialMesg" 2024-11-25 22:48:20 -08:00
KimLS 11636225b1 Rename Larion -> Laurion; later we'll rename the branch too. Make sure to update your patch file name. 2024-11-25 20:25:09 -08:00
Alex King 3d7cf4235c [Release] 22.60.0 (#4555) 2024-11-25 17:17:03 -06:00
hg 187ee10218 [Tasks] Update tasks in all zones if invalid zone set (#4550)
This allows task elements to update in any zone when it has an invalid
zone id <= 0. This has the same effect as leaving the zones field empty
except "Unknown Zone" instead of "ALL" will be shown in task windows.

Note that Titanium shows "ALL" for a zone id of 0 despite it being an
invalid zone id. This could be manipulated server side to match newer
clients but there isn't much benefit since any other invalid zone id
below 0 can be used to do the same.
2024-11-25 18:02:14 -05:00
Alex King 6bd758b3dd [Rules] Add Rule to Disable NPCs Facing Target (#4543) 2024-11-24 17:30:44 -06:00
Alex King 9938755517 [Bug Fix] Fix Possible Item Loss in Trades (#4554) 2024-11-24 17:29:27 -06:00
Chris Miles 630da0eee6 [Config] Fix World TCP Address Configuration Default (#4551) 2024-11-24 17:27:31 -06:00
Alex King 3158386aa3 [Bug Fix] Fix Issue with Perl EVENT_PAYLOAD (#4545) 2024-11-24 17:19:40 -06:00
hg 12ada57ee8 [Code] Fix build with older C++ libraries (#4549)
This adds a compile time concept to determine if from_chars has
floating-point support and uses fallbacks if not.

This is a C++17 feature but support for floats was only added to
libstdc++ with GCC 11.1 and LLVM libc++ in 20.0 (unreleased).
2024-11-24 17:18:36 -06:00
Mitch Freeman 7a841c11c5 [Bug Fix] Players could become flagged as a Trader when they were not trading (#4553) 2024-11-24 17:17:01 -06:00
Mitch Freeman a49d1446b7 [Bug Fix] Fix for sending money via Parcel, then changing your mind (#4552) 2024-11-24 17:15:18 -06:00
KimLS 4e53f5464f Set focus effects enabled to 1 in op_logserver 2024-11-23 22:53:22 -08:00
KimLS 233a0dda6a Fix op_charinventory (mostly) 2024-11-23 22:28:17 -08:00
KimLS 1ab0e78f00 WIP; close if not there already. 2024-11-22 23:15:20 -08:00
KimLS 9d5a9ee6df More work in progress 2024-11-22 22:58:43 -08:00
KimLS d713ff69bf WIP items 2024-11-22 19:06:18 -08:00
carolus21rex b2d0fa6a2f [Bug Fix] Fix Strings::Commify bug with #mystats (#4547)
* Fix a formatting bug with #mystats

When using values larger than 1,000, we were calling commify on a string that already had commas. This resulted in the value 1005 looking like 1,,005.

* Update mob.cpp

---------

Co-authored-by: Alex King <89047260+Kinglykrab@users.noreply.github.com>
2024-11-22 16:23:48 -05:00
KimLS 8947058465 NewSpawn support 2024-11-21 23:51:20 -08:00
KimLS 429d6fd87d WIP chat message work 2024-11-21 23:06:44 -08:00
KimLS 0eedbea060 Added alternate command sequence for clients that don't support # commands. Incoming channel messages work I think. Outgoing special msg still pretty screwed up but working on it. 2024-11-20 22:35:09 -08:00
KimLS 63331b678b Fix movement (client->server) packets 2024-11-20 20:31:32 -08:00
Mitch Freeman 62ac015fff [Bug Fix] Fix an edge case with augmented items inside parceled containers (#4546) 2024-11-20 21:17:04 -05:00
KimLS ebb657153a Zoning works but saving and player movement packets dont so it's a little limited, wear change works.
Chat is a work in progress but not yet working.
2024-11-19 21:06:53 -08:00
KimLS 2c5c28b808 Movement works, mostly now. 2024-11-18 20:00:03 -08:00
KimLS 3a7afb48cb Issue entering zone is they changed op_enterworld a bit; right now ignore the go home and tutorial features for now 2024-11-17 22:26:16 -08:00
KimLS b167f05006 Missing fields from spawn struct 2024-11-17 21:58:27 -08:00
KimLS f6f9d275e8 Close to zone; spawn struct isn't right. 2024-11-17 19:53:09 -08:00
KimLS 004b0e1176 Ground spawn for larion 2024-11-17 17:10:41 -08:00
KimLS 8ff0e5614c Add door encode. 2024-11-17 16:42:50 -08:00
KimLS 027d95bbb8 WIP on door struct 2024-11-17 15:44:05 -08:00
KimLS b85344f779 Fix for OP_ZoneEntry spawn struct sometimes containing garbage data in flags causing random crashes. 2024-11-16 22:04:44 -08:00
KimLS 018308bfca Going to work on op mapping; we're making it past add player at least. 2024-11-16 17:08:24 -08:00
KimLS 29066cf847 Zone header needs a lot of love later, hopefully this is close enough 2024-11-16 14:05:31 -08:00
Mitch Freeman 4977a7c2e0 [Bazaar] Further refinements for instanced bazaar (#4544)
Resolves
- Parcels being delivered with incorrect item
- Inspecting an item from the bazaar window showing the incorrect item
2024-11-16 15:14:17 -06:00
KimLS da9a95fd83 WIP zone header 2024-11-16 12:13:55 -08:00
KimLS 7888fb2655 initial implementation of zone entry packet. 2024-11-16 11:52:06 -08:00
KimLS 45d39f44f2 WIP on the larion phsyics struct 2024-11-15 23:53:08 -08:00
KimLS 2f46da5d99 WIP Spawn struct, need to go confirm struct for cphysics 2024-11-15 22:52:52 -08:00
KimLS 200b7fa604 Add prelim support for CharacterGuid to spawn struct. 2024-11-14 22:32:26 -08:00
KimLS c20439dbb4 Merge fix 2024-11-14 22:19:27 -08:00
KimLS fa91559a85 Starting work on character guids 2024-11-14 22:13:36 -08:00
Mitch Freeman 9967384ab8 [Fix] Fix for mult-instanced bazaar zones (#4541)
* Enable bazaar for multiple instances.

* Enable buyer for multiple instances.

* Update to buyer/barter for multiple instances and attuned items.
2024-11-14 19:44:03 -06:00
Mitch Freeman d3da2e5501 [Fix] Fix for bazaar search of containers. (#4540) 2024-11-14 19:32:19 -06:00
Chris Miles 33f5c4c6a7 [Bug Fix] Fix issue where NPC's are being hidden as traders (#4539)
* [Fix] Fix issue where NPC's are being hidden as traders

* Fix

* Update mob.cpp
2024-11-14 19:15:03 -05:00
Akkadius e4aa6a6957 [Release] 22.59.1 2024-11-13 20:52:46 -06:00
Chris Miles e4d812f4b4 [Release] 22.59.0 (#4538) 2024-11-13 20:08:03 -06:00
hg bcedfe7032 [Quest API] Add Native Database Querying Interface (#4531)
* Add database quest API

API functions are named to be similar to LuaSQL and perl DBI

New connections are made for Database objects. These can either use
credentials from the server eqemu_config or manual connections.

* Add option to use zone db connections
2024-11-12 20:01:18 -06:00
Paul Johnson c1df3fbcb0 [Rules] Add Rule for restricting client versions to world server (#4527)
* add rule for supported clients, unsupported client packet

* whitespace

* PR feedback - Update client.cpp

* PR Feedback - Update client.cpp

* Update client.cpp

* Update client.cpp

---------

Co-authored-by: Paul Johnson <Paul@PJOHNSOMAC-6366.digi.box>
2024-11-12 11:00:22 -05:00
Akkadius 011e1d05e7 [Hotfix] Check if the mob is already in the close mobs list before inserting 2024-11-10 23:19:40 -06:00
KimLS ce728c4d0d This is close but no cigar yet. 2024-11-10 10:07:32 -08:00
Akkadius 3f0f95976c [Hotfix] ScanCloseMobs - Ensure scanning mob has an entity ID 2024-11-10 06:47:42 -06:00
Chris Miles 77de9619b5 [Databuckets] Add database index to data_buckets (#4535)
* [Databuckets] Add database index to data_buckets

* Update database_update_manifest.cpp
2024-11-08 22:26:00 -05:00
Mitch Freeman 20d3ab2ac5 [Bug Fix] Bazaar two edge case issues resolved (#4533)
This update resolves two bazaar issues that have been reported.
- If parcel delivery is used to purchase an item, and the seller has several of the same items, that have various charges, the item would not be removed from the db.  This allowed for incorrect purchases.
- If a player 'reclaims' an alt currency item that they also have for sale with an active trader,  the item would remain for sale, and be reclaimed.  This impacted custom alt currency items that were no trade.
2024-11-08 22:15:12 -05:00
Chris Miles 0ea47fadee [Performance] Improvements to ScanCloseMobs logic (#4534)
* [Performance] Minor improvements to ScanCloseMobs

* Remove timer checks one level up to reduce branching

* Reserve memory in m_close_mobs to avoid frequent re-allocations if not already reserved.
2024-11-08 17:48:39 -06:00
KimLS d29937cba6 WIP player profile 2024-11-07 23:03:38 -08:00
KimLS 799593c394 Working on zone packets. 2024-11-05 21:38:10 -08:00
Chris Miles 1ce51ca3b0 [Release] 22.58.0 (#4532) 2024-11-05 22:02:32 -06:00
hg 25ef3d2cdb [Code] Update perlbind to 1.1.0 (#4529)
- Adds a perl::ref alias for perl::reference

- Optimizes array return pushes by accessing SV* values directly instead
  of using operator[] scalar_proxy

- Allows functions with a perl::hash parameter to accept hashes with any
  scalar key type instead of requiring explicit string keys

  e.g., foo(123 => 1) will now work on functions accepting a perl::hash
2024-11-05 20:14:29 -06:00
hg 95249889a6 [Code] Add mysql prepared statement support (#4530)
This adds support for using prepared statements for MySQL queries. It is
intended for use in a database quest API but it can be used in source
with some caveats:

 - It uses exceptions for error handling instead of returning a fake
   result that needs checked. Usage must be wrapped in try/catch.

 - DBcore has a connection mutex which indicates the connection might be
   shared with other threads. This mutex is locked for certain stmt
   operations in an attempt to make it safe to use with multi threaded
   connections.

 - Prepared statements should only be used on the main thread since the
   internal logging is not synchronized.

 - Unlike the current query API which retrieves all results as strings,
   results are stored in buffers that represent the db field type.
   Getter functions are available to retrieve values as desired types.
2024-11-05 20:12:17 -06:00
mmcgarvey 428cccfa50 [Feature] Focus Skill Attack Spells (#4528)
* Add Rule Spells:AllowFocusOnSkillDamageSpells

* Currently, focus mods defaults to 0 when processing spell effect 193.
* The default value for this rule is false.
* When false, the rule will retain the current default behavior.
* When true, the aforementioned focus effects will allow focus effects (185, 459, and 482) to modify spell effect 193.

* Removed undesirable whitespace

* Update spell_effects.cpp

---------

Co-authored-by: Alex King <89047260+Kinglykrab@users.noreply.github.com>
2024-10-31 08:13:16 -04:00
KimLS b7c93e12de Add some sanity checking for stream_parser so it's less likely to pickup other protocols as an everquest protocol. 2024-10-26 10:03:57 -07:00
KimLS c1651b7dca Fix char select 2024-10-25 22:47:27 -07:00
KimLS 4093b505d3 wip char select 2024-10-24 21:11:01 -07:00
KimLS dbb0288a13 Filled out a lot of the version stuff for a new client version (oh my lawd this could be easier!) 2024-10-24 20:35:53 -07:00
KimLS 0a9a941b6b WIP on membership 2024-10-23 21:53:24 -07:00
Alex King 41dd8a5754 [Quest API] Add Spawn Circle/Grid Methods to Perl/Lua (#4524)
* [Quest API] Add Spawn Circle/Grid Methods to Perl/Lua

* Update lua_general.cpp

* Update questmgr.cpp

* Update questmgr.cpp
2024-10-23 23:40:25 -04:00
Alex King d02d766563 [Bug Fix] Fix cross_zone_set_entity_variable_by_char_id in Lua (#4526) 2024-10-23 22:47:02 -04:00
Alex King dfd2729b28 [Bug Fix] Add Missing Lua Registers (#4525) 2024-10-23 22:37:21 -04:00
KimLS 702024fe04 Encode OP_LogServer 2024-10-23 18:15:40 -07:00
KimLS acdd65b55c Distracted by world server issues 2024-10-22 20:08:31 -07:00
KimLS 6db7c624e9 Larion expansion data 2024-10-22 17:52:14 -07:00
Chris Miles b92eafd21b [Release] 22.57.1 (#4523) 2024-10-22 00:02:14 -05:00
nytmyr d6d5d992cb [Bots] Fix pet buffs from saving duplicates every save (#4520)
* [Bots] Fix pet buffs from saving duplicates every save

Previously we were not checking the pet index properly when clearing buffs in the database before saving which resulted in no prior data being deleted.

This corrects the logic for the save and also will clean up any buffs for pets that don't exist in the table.

* Changes

* Update world_boot.cpp

---------

Co-authored-by: Akkadius <akkadius1@gmail.com>
2024-10-21 23:57:42 -05:00
Alex King d524cb6a5a [Bots] Enable Bot Commands Only if Rule Enabled (#4519) 2024-10-21 23:49:36 -05:00
Alex e6469878ce [Loginserver] Automatifc Opcode File Creation (#4521)
* Loginserver will auto create the opcodes file if it doesn't exist on load.

* Use path manager in login opcodes.

---------

Co-authored-by: KimLS <KimLS@peqtgc.com>
2024-10-21 23:48:43 -05:00
KimLS f485dfd8d3 WIP logserver struct 2024-10-21 19:31:45 -07:00
KimLS 823bacf08a Starting to poke at op_logserver 2024-10-20 19:18:32 -07:00
Chris Miles 9583099ace [Release] 22.57.0 (#4517) 2024-10-20 16:17:15 -05:00
nytmyr cf3483b402 [Bots] Fix timers loading on spawn and zone (#4516)
Timers were not properly checking their expiration time on spawn and load and could cause invalid timers to load if the server was restarted resulting in improper lockouts.
2024-10-20 10:44:30 -04:00
KimLS b8ecb297ec Beginning of world opcodes 2024-10-19 22:34:43 -07:00
carolus21rex 311af7bbe9 [Cleanup] Fixed a typo in Zoning.cpp (#4515)
* Fixed a typo in Zoning.cpp changed reguest to request.

* Update zoning.cpp

---------

Co-authored-by: Alex King <89047260+Kinglykrab@users.noreply.github.com>
2024-10-19 21:59:10 -04:00
KimLS 7f7c80eab7 Merge branch 'stream_parser' into lsong_world 2024-10-19 18:54:20 -07:00
KimLS a7c1c85f71 Initial 2024-10-18 17:18:37 -07:00
Alex King be42b73f5c [Rules] Add Rule to disable PVP Regions (#4513) 2024-10-17 01:48:19 -05:00
Mitch Freeman f76c798910 [BugFix] Fix a display error regarding a few trader/buyer query errors (#4514) 2024-10-17 01:43:24 -05:00
96 changed files with 10749 additions and 297 deletions
+154
View File
@@ -1,3 +1,157 @@
## [22.60.0] 11/25/2024
### Bazaar
* Further refinements for instanced bazaar ([#4544](https://github.com/EQEmu/Server/pull/4544)) @neckkola 2024-11-16
### Code
* Fix build with older C++ libraries ([#4549](https://github.com/EQEmu/Server/pull/4549)) @hgtw 2024-11-24
### Config
* Fix World TCP Address Configuration Default ([#4551](https://github.com/EQEmu/Server/pull/4551)) @Akkadius 2024-11-24
### Fixes
* Fix Issue with Perl EVENT_PAYLOAD ([#4545](https://github.com/EQEmu/Server/pull/4545)) @Kinglykrab 2024-11-24
* Fix Possible Item Loss in Trades ([#4554](https://github.com/EQEmu/Server/pull/4554)) @Kinglykrab 2024-11-24
* Fix Strings::Commify bug with #mystats ([#4547](https://github.com/EQEmu/Server/pull/4547)) @carolus21rex 2024-11-22
* Fix an edge case with augmented items inside parceled containers ([#4546](https://github.com/EQEmu/Server/pull/4546)) @neckkola 2024-11-21
* Fix for bazaar search of containers. ([#4540](https://github.com/EQEmu/Server/pull/4540)) @neckkola 2024-11-15
* Fix for mult-instanced bazaar zones ([#4541](https://github.com/EQEmu/Server/pull/4541)) @neckkola 2024-11-15
* Fix for sending money via Parcel, then changing your mind ([#4552](https://github.com/EQEmu/Server/pull/4552)) @neckkola 2024-11-24
* Fix issue where NPC's are being hidden as traders ([#4539](https://github.com/EQEmu/Server/pull/4539)) @Akkadius 2024-11-15
* Players could become flagged as a Trader when they were not trading ([#4553](https://github.com/EQEmu/Server/pull/4553)) @neckkola 2024-11-24
### Rules
* Add Rule to Disable NPCs Facing Target ([#4543](https://github.com/EQEmu/Server/pull/4543)) @Kinglykrab 2024-11-24
### Tasks
* Update tasks in all zones if invalid zone set ([#4550](https://github.com/EQEmu/Server/pull/4550)) @hgtw 2024-11-25
## [22.59.1] 11/13/2024
### Hotfix
* Fix faulty database migration condition with databuckets (9285)
## [22.59.0] 11/13/2024
### Databuckets
* Add database index to data_buckets ([#4535](https://github.com/EQEmu/Server/pull/4535)) @Akkadius 2024-11-09
### Fixes
* Bazaar two edge case issues resolved ([#4533](https://github.com/EQEmu/Server/pull/4533)) @neckkola 2024-11-09
* Check if the mob is already in the close mobs list before inserting @Akkadius 2024-11-11
* ScanCloseMobs - Ensure scanning mob has an entity ID @Akkadius 2024-11-10
### Performance
* Improvements to ScanCloseMobs logic ([#4534](https://github.com/EQEmu/Server/pull/4534)) @Akkadius 2024-11-08
### Quest API
* Add Native Database Querying Interface ([#4531](https://github.com/EQEmu/Server/pull/4531)) @hgtw 2024-11-13
### Rules
* Add Rule for restricting client versions to world server ([#4527](https://github.com/EQEmu/Server/pull/4527)) @knervous 2024-11-12
## [22.58.0] 11/5/2024
### Code
* Add mysql prepared statement support ([#4530](https://github.com/EQEmu/Server/pull/4530)) @hgtw 2024-11-06
* Update perlbind to 1.1.0 ([#4529](https://github.com/EQEmu/Server/pull/4529)) @hgtw 2024-11-06
### Feature
* Focus Skill Attack Spells ([#4528](https://github.com/EQEmu/Server/pull/4528)) @mmcgarvey 2024-10-31
### Fixes
* Add Missing Lua Registers ([#4525](https://github.com/EQEmu/Server/pull/4525)) @Kinglykrab 2024-10-24
* Fix cross_zone_set_entity_variable_by_char_id in Lua ([#4526](https://github.com/EQEmu/Server/pull/4526)) @Kinglykrab 2024-10-24
### Loginserver
* Automatifc Opcode File Creation ([#4521](https://github.com/EQEmu/Server/pull/4521)) @KimLS 2024-10-22
### Quest API
* Add Spawn Circle/Grid Methods to Perl/Lua ([#4524](https://github.com/EQEmu/Server/pull/4524)) @Kinglykrab 2024-10-24
## [22.57.1] 10/22/2024
### Bots
* Enable Bot Commands Only if Rule Enabled ([#4519](https://github.com/EQEmu/Server/pull/4519)) @Kinglykrab 2024-10-22
* Fix pet buffs from saving duplicates every save ([#4520](https://github.com/EQEmu/Server/pull/4520)) @nytmyr 2024-10-22
### Loginserver
* Automatic Opcode File Creation ([#4521](https://github.com/EQEmu/Server/pull/4521)) @KimLS 2024-10-22
## [22.57.0] 10/20/2024
### Bots
* Add "silent" option to ^spawn and mute raid spawn ([#4494](https://github.com/EQEmu/Server/pull/4494)) @nytmyr 2024-10-05
* Add attack flag when told to attack ([#4490](https://github.com/EQEmu/Server/pull/4490)) @nytmyr 2024-09-29
* Fix timers loading on spawn and zone ([#4516](https://github.com/EQEmu/Server/pull/4516)) @nytmyr 2024-10-20
### Code
* Fixed a typo in Zoning.cpp ([#4515](https://github.com/EQEmu/Server/pull/4515)) @carolus21rex 2024-10-20
* Optimization Code Cleanup ([#4489](https://github.com/EQEmu/Server/pull/4489)) @Akkadius 2024-09-30
* Remove Extra Skill in EQ::skills::GetExtraDamageSkills() ([#4486](https://github.com/EQEmu/Server/pull/4486)) @Kinglykrab 2024-10-03
### Crash
* Fixes a crash when the faction_list db table is empty. ([#4511](https://github.com/EQEmu/Server/pull/4511)) @KimLS 2024-10-14
### Fixes
* Add character_instance_safereturns to tables_to_zero_id ([#4485](https://github.com/EQEmu/Server/pull/4485)) @Morzain 2024-09-26
* Correctly limit max targets of PBAOE ([#4507](https://github.com/EQEmu/Server/pull/4507)) @catapultam-habeo 2024-10-11
* FindBestZ selecting false zone floor as bestz - Results in roambox failures ([#4504](https://github.com/EQEmu/Server/pull/4504)) @fryguy503 2024-10-13
* Fix #set motd Crash ([#4495](https://github.com/EQEmu/Server/pull/4495)) @Kinglykrab 2024-10-05
* Fix `character_exp_modifiers` Default Values ([#4502](https://github.com/EQEmu/Server/pull/4502)) @Kinglykrab 2024-10-09
* Fix a display error regarding a few trader/buyer query errors ([#4514](https://github.com/EQEmu/Server/pull/4514)) @neckkola 2024-10-17
* Fix Group ID 0 in Group::SaveGroupLeaderAA() ([#4487](https://github.com/EQEmu/Server/pull/4487)) @Kinglykrab 2024-10-03
* Fix Mercenary Encounter Crash ([#4509](https://github.com/EQEmu/Server/pull/4509)) @Kinglykrab 2024-10-12
* Fix NPC::CanTalk() Crash ([#4499](https://github.com/EQEmu/Server/pull/4499)) @Kinglykrab 2024-10-07
* Fix Spells:DefaultAOEMaxTargets Default Value ([#4508](https://github.com/EQEmu/Server/pull/4508)) @Kinglykrab 2024-10-12
* Fix Targeted AOE Max Targets Rule ([#4488](https://github.com/EQEmu/Server/pull/4488)) @Kinglykrab 2024-10-03
* fixed a bug where it would use npc value instead of faction value in the database. ([#4491](https://github.com/EQEmu/Server/pull/4491)) @regneq 2024-09-29
* Master of Disguise should apply to illusions casted by others. ([#4506](https://github.com/EQEmu/Server/pull/4506)) @fryguy503 2024-10-11
* Spells - Self Only (Yellow) cast when non group member is targeted ([#4503](https://github.com/EQEmu/Server/pull/4503)) @fryguy503 2024-10-11
### Loginserver
* Larion loginserver support ([#4492](https://github.com/EQEmu/Server/pull/4492)) @KimLS 2024-10-03
* Login Fatal Error Spamming ([#4476](https://github.com/EQEmu/Server/pull/4476)) @KimLS 2024-10-09
### Logs
* Add NPC Trades to Player Events ([#4505](https://github.com/EQEmu/Server/pull/4505)) @Kinglykrab 2024-10-13
### Quest API
* Add Buff Fade Methods to Perl/Lua ([#4501](https://github.com/EQEmu/Server/pull/4501)) @Kinglykrab 2024-10-09
* Add EVENT_READ_ITEM to Perl/Lua ([#4497](https://github.com/EQEmu/Server/pull/4497)) @Kinglykrab 2024-10-08
* Add NPC List Filter Methods to Perl/Lua ([#4493](https://github.com/EQEmu/Server/pull/4493)) @Kinglykrab 2024-10-04
* Add Scripting Support to Mercenaries ([#4500](https://github.com/EQEmu/Server/pull/4500)) @Kinglykrab 2024-10-11
### Rules
* Add Rule to disable PVP Regions ([#4513](https://github.com/EQEmu/Server/pull/4513)) @Kinglykrab 2024-10-17
## [22.56.3] 9/23/2024
### Fixes
+15 -1
View File
@@ -62,6 +62,7 @@ SET(common_sources
mutex.cpp
mysql_request_result.cpp
mysql_request_row.cpp
mysql_stmt.cpp
opcode_map.cpp
opcodemgr.cpp
packet_dump.cpp
@@ -111,6 +112,8 @@ SET(common_sources
net/websocket_server.cpp
net/websocket_server_connection.cpp
patches/patches.cpp
patches/laurion.cpp
patches/laurion_limits.cpp
patches/sod.cpp
patches/sod_limits.cpp
patches/sof.cpp
@@ -586,6 +589,7 @@ SET(common_headers
mutex.h
mysql_request_result.h
mysql_request_row.h
mysql_stmt.h
op_codes.h
opcode_dispatch.h
opcodemgr.h
@@ -653,7 +657,11 @@ SET(common_headers
net/websocket_server.h
net/websocket_server_connection.h
patches/patches.h
patches/sod.h
patches/laurion.h
patches/laurion_limits.h
patches/laurion_ops.h
patches/laurion_structs.h
patches/sod.h
patches/sod_limits.h
patches/sod_ops.h
patches/sod_structs.h
@@ -739,6 +747,10 @@ SOURCE_GROUP(Net FILES
SOURCE_GROUP(Patches FILES
patches/patches.h
patches/laurion.h
patches/laurion_limits.h
patches/laurion_ops.h
patches/laurion_structs.h
patches/sod.h
patches/sod_limits.h
patches/sod_ops.h
@@ -767,6 +779,8 @@ SOURCE_GROUP(Patches FILES
patches/uf_ops.h
patches/uf_structs.h
patches/patches.cpp
patches/laurion.cpp
patches/laurion_limits.cpp
patches/sod.cpp
patches/sod_limits.cpp
patches/sof.cpp
+1
View File
@@ -59,6 +59,7 @@ public:
void WriteUInt8(uint8 value) { *(uint8 *)(pBuffer + _wpos) = value; _wpos += sizeof(uint8); }
void WriteUInt32(uint32 value) { *(uint32 *)(pBuffer + _wpos) = value; _wpos += sizeof(uint32); }
void WriteUInt64(uint64 value) { *(uint64 *)(pBuffer + _wpos) = value; _wpos += sizeof(uint64); }
void WriteSInt16(int32 value) { *(int16*)(pBuffer + _wpos) = value; _wpos += sizeof(int16); }
void WriteUInt16(uint32 value) { *(uint16 *)(pBuffer + _wpos) = value; _wpos += sizeof(uint16); }
void WriteSInt32(int32 value) { *(int32 *)(pBuffer + _wpos) = value; _wpos += sizeof(int32); }
void WriteFloat(float value) { *(float *)(pBuffer + _wpos) = value; _wpos += sizeof(float); }
+2 -1
View File
@@ -235,7 +235,8 @@ Bazaar::GetSearchResults(
std::vector<ItemSearchType> item_search_types = {
{EQ::item::ItemType::ItemTypeAll, true},
{EQ::item::ItemType::ItemTypeBook, item->ItemClass == EQ::item::ItemType::ItemTypeBook},
{EQ::item::ItemType::ItemTypeContainer, item->ItemClass == EQ::item::ItemType::ItemTypeContainer},
{EQ::item::ItemType::ItemTypeContainer, item->ItemClass == EQ::item::ItemType::ItemTypeContainer ||
item->IsClassBag()},
{EQ::item::ItemType::ItemTypeAllEffects, item->Scroll.Effect > 0 && item->Scroll.Effect < 65000},
{EQ::item::ItemType::ItemTypeUnknown9, item->Worn.Effect == 998},
{EQ::item::ItemType::ItemTypeUnknown10, item->Worn.Effect >= 1298 && item->Worn.Effect <= 1307},
+3
View File
@@ -71,6 +71,9 @@ namespace Class {
constexpr uint8 FellowshipMaster = 69;
constexpr uint8 AlternateCurrencyMerchant = 70;
constexpr uint8 MercenaryLiaison = 71;
constexpr uint8 RealEstateMerchant = 72;
constexpr uint8 LoyaltyMerchant = 73;
constexpr uint8 TributeMaster2 = 74;
constexpr uint8 PLAYER_CLASS_COUNT = 16;
constexpr uint16 ALL_CLASSES_BITMASK = 65535;
+18
View File
@@ -101,6 +101,24 @@ void CRC32::SetEQChecksum(uchar* in_data, uint32 in_length, uint32 start_at)
memcpy(in_data, (char*)&check, 4);
}
unsigned long CRC32::GetEQChecksum(uchar* in_data, uint32 in_length, uint32 start_at)
{
unsigned long data;
unsigned long check = 0xffffffff;
for (uint32 i = start_at; i < in_length; i++)
{
data = in_data[i];
data = data ^ (check);
data = data & 0x000000ff;
check = check >> 8;
data = CRC32Table[data];
check = check ^ data;
}
return check;
}
uint32 CRC32::Update(const uint8* buf, uint32 bufsize, uint32 crc32var) {
for(uint32 i=0; i < bufsize; i++)
Calc(buf[i], crc32var);
+1
View File
@@ -8,6 +8,7 @@ public:
static uint32 Generate(const uint8* buf, uint32 bufsize);
static uint32 GenerateNoFlip(const uint8* buf, uint32 bufsize); // Same as Generate(), but without the ~
static void SetEQChecksum(uchar* in_data, uint32 in_length, uint32 start_at=4);
static unsigned long GetEQChecksum(uchar* in_data, uint32 in_length, uint32 start_at = 4);
// Multiple buffer CRC32
static uint32 Update(const uint8* buf, uint32 bufsize, uint32 crc32 = 0xFFFFFFFF);
@@ -5758,6 +5758,18 @@ ALTER TABLE `inventory_snapshots`
ALTER TABLE `character_exp_modifiers`
MODIFY COLUMN `aa_modifier` float NOT NULL DEFAULT 1.0 AFTER `instance_version`,
MODIFY COLUMN `exp_modifier` float NOT NULL DEFAULT 1.0 AFTER `aa_modifier`;
)"
},
ManifestEntry{
.version = 9285,
.description = "2024_11_08_data_buckets_indexes.sql",
.check = "SHOW CREATE TABLE `data_buckets`",
.condition = "missing",
.match = "idx_character_expires",
.sql = R"(
CREATE INDEX idx_character_expires ON data_buckets (character_id, expires);
CREATE INDEX idx_npc_expires ON data_buckets (npc_id, expires);
CREATE INDEX idx_bot_expires ON data_buckets (bot_id, expires);
)"
}
// -- template; copy/paste this when you need to create a new entry
+6
View File
@@ -7,6 +7,7 @@
#include "timer.h"
#include "dbcore.h"
#include "mysql_stmt.h"
#include <fstream>
#include <iostream>
@@ -436,3 +437,8 @@ MySQLRequestResult DBcore::QueryDatabaseMulti(const std::string &query)
return r;
}
mysql::PreparedStmt DBcore::Prepare(std::string query)
{
return mysql::PreparedStmt(*mysql, std::move(query), m_mutex);
}
+7
View File
@@ -17,6 +17,8 @@
#define CR_SERVER_GONE_ERROR 2006
#define CR_SERVER_LOST 2013
namespace mysql { class PreparedStmt; }
class DBcore {
public:
enum eStatus {
@@ -48,6 +50,11 @@ public:
}
void SetMutex(Mutex *mutex);
// only safe on connections shared with other threads if results buffered
// unsafe to use off main thread due to internal server logging
// throws std::runtime_error on failure
mysql::PreparedStmt Prepare(std::string query);
protected:
bool Open(
const char *iHost,
+2 -2
View File
@@ -1,8 +1,6 @@
// system use
N(OP_ExploreUnknown),
// start (please add new opcodes in descending order and re-order any name changes where applicable)
N(OP_0x0193),
N(OP_0x0347),
N(OP_AAAction),
N(OP_AAExpUpdate),
N(OP_AcceptNewTask),
@@ -356,6 +354,7 @@ N(OP_MercenaryTimer),
N(OP_MercenaryTimerRequest),
N(OP_MercenaryUnknown1),
N(OP_MercenaryUnsuspendResponse),
N(OP_MerchantBulkItems),
N(OP_MobEnduranceUpdate),
N(OP_MobHealth),
N(OP_MobManaUpdate),
@@ -374,6 +373,7 @@ N(OP_MultiLineMsg),
N(OP_NewSpawn),
N(OP_NewTitlesAvailable),
N(OP_NewZone),
N(OP_NPCMoveUpdate),
N(OP_OnLevelMessage),
N(OP_OpenContainer),
N(OP_OpenDiscordMerchant),
+103
View File
@@ -56,6 +56,8 @@ const char* EQ::versions::ClientVersionName(ClientVersion client_version)
return "RoF";
case ClientVersion::RoF2:
return "RoF2";
case ClientVersion::Laurion:
return "Laurion";
default:
return "Invalid Version";
};
@@ -76,6 +78,8 @@ uint32 EQ::versions::ConvertClientVersionToClientVersionBit(ClientVersion client
return bitRoF;
case ClientVersion::RoF2:
return bitRoF2;
case ClientVersion::Laurion:
return bitLaurion;
default:
return bitUnknown;
}
@@ -96,6 +100,8 @@ EQ::versions::ClientVersion EQ::versions::ConvertClientVersionBitToClientVersion
return ClientVersion::RoF;
case ((uint32)1 << (static_cast<unsigned int>(ClientVersion::RoF2) - 1)) :
return ClientVersion::RoF2;
case ((uint32)1 << (static_cast<unsigned int>(ClientVersion::Laurion) - 1)):
return ClientVersion::Laurion;
default:
return ClientVersion::Unknown;
}
@@ -184,6 +190,8 @@ const char* EQ::versions::MobVersionName(MobVersion mob_version)
return "RoF";
case MobVersion::RoF2:
return "RoF2";
case MobVersion::Laurion:
return "Laurion";
case MobVersion::NPC:
return "NPC";
case MobVersion::NPCMerchant:
@@ -212,6 +220,8 @@ const char* EQ::versions::MobVersionName(MobVersion mob_version)
return "Offline RoF";
case MobVersion::OfflineRoF2:
return "Offline RoF2";
case MobVersion::OfflineLaurion:
return "Offline Laurion";
default:
return "Invalid Version";
};
@@ -235,6 +245,8 @@ EQ::versions::ClientVersion EQ::versions::ConvertMobVersionToClientVersion(MobVe
return ClientVersion::RoF;
case MobVersion::RoF2:
return ClientVersion::RoF2;
case MobVersion::Laurion:
return ClientVersion::Laurion;
default:
return ClientVersion::Unknown;
}
@@ -258,6 +270,8 @@ EQ::versions::MobVersion EQ::versions::ConvertClientVersionToMobVersion(ClientVe
return MobVersion::RoF;
case ClientVersion::RoF2:
return MobVersion::RoF2;
case ClientVersion::Laurion:
return MobVersion::Laurion;
default:
return MobVersion::Unknown;
}
@@ -278,6 +292,8 @@ EQ::versions::MobVersion EQ::versions::ConvertPCMobVersionToOfflinePCMobVersion(
return MobVersion::OfflineRoF;
case MobVersion::RoF2:
return MobVersion::OfflineRoF2;
case MobVersion::Laurion:
return MobVersion::OfflineLaurion;
default:
return MobVersion::Unknown;
}
@@ -298,6 +314,8 @@ EQ::versions::MobVersion EQ::versions::ConvertOfflinePCMobVersionToPCMobVersion(
return MobVersion::RoF;
case MobVersion::OfflineRoF2:
return MobVersion::RoF2;
case MobVersion::OfflineLaurion:
return MobVersion::Laurion;
default:
return MobVersion::Unknown;
}
@@ -318,6 +336,8 @@ EQ::versions::ClientVersion EQ::versions::ConvertOfflinePCMobVersionToClientVers
return ClientVersion::RoF;
case MobVersion::OfflineRoF2:
return ClientVersion::RoF2;
case MobVersion::OfflineLaurion:
return ClientVersion::Laurion;
default:
return ClientVersion::Unknown;
}
@@ -338,6 +358,8 @@ EQ::versions::MobVersion EQ::versions::ConvertClientVersionToOfflinePCMobVersion
return MobVersion::OfflineRoF;
case ClientVersion::RoF2:
return MobVersion::OfflineRoF2;
case ClientVersion::Laurion:
return MobVersion::OfflineLaurion;
default:
return MobVersion::Unknown;
}
@@ -388,6 +410,27 @@ const char* EQ::expansions::ExpansionName(Expansion expansion)
return "Rain of Fear";
case Expansion::CotF:
return "Call of the Forsaken";
case Expansion::TDS:
return "The Darkened Sea";
case Expansion::TBM:
return "The Broken Mirror";
case Expansion::EoK:
return "Empires of Kunark";
case Expansion::RoS:
return "Ring of Scale";
case Expansion::TBL:
return "The Burning Lands";
case Expansion::ToV:
return "Torment of Velious";
case Expansion::CoV:
return "Claws of Veeshan";
case Expansion::ToL:
return "Terror of Luclin";
case Expansion::NoS:
return "Night of Shadows";
case Expansion::LS:
return "Laurion's Song";
default:
return "Invalid Expansion";
}
@@ -441,6 +484,26 @@ uint32 EQ::expansions::ConvertExpansionToExpansionBit(Expansion expansion)
return bitRoF;
case Expansion::CotF:
return bitCotF;
case Expansion::TDS:
return bitTDS;
case Expansion::TBM:
return bitTBM;
case Expansion::EoK:
return bitEoK;
case Expansion::RoS:
return bitRoS;
case Expansion::TBL:
return bitTBL;
case Expansion::ToV:
return bitToV;
case Expansion::CoV:
return bitCoV;
case Expansion::ToL:
return bitToL;
case Expansion::NoS:
return bitNoS;
case Expansion::LS:
return bitLS;
default:
return bitEverQuest;
}
@@ -489,6 +552,26 @@ EQ::expansions::Expansion EQ::expansions::ConvertExpansionBitToExpansion(uint32
return Expansion::RoF;
case bitCotF:
return Expansion::CotF;
case bitTDS:
return Expansion::TDS;
case bitTBM:
return Expansion::TBM;
case bitEoK:
return Expansion::EoK;
case bitRoS:
return Expansion::RoS;
case bitTBL:
return Expansion::TBL;
case bitToV:
return Expansion::ToV;
case bitCoV:
return Expansion::CoV;
case bitToL:
return Expansion::ToL;
case bitNoS:
return Expansion::NoS;
case bitLS:
return Expansion::LS;
default:
return Expansion::EverQuest;
}
@@ -537,6 +620,26 @@ uint32 EQ::expansions::ConvertExpansionToExpansionsMask(Expansion expansion)
return maskRoF;
case Expansion::CotF:
return maskCotF;
case Expansion::TDS:
return maskTDS;
case Expansion::TBM:
return maskTBM;
case Expansion::EoK:
return maskEoK;
case Expansion::RoS:
return maskRoS;
case Expansion::TBL:
return maskTBL;
case Expansion::ToV:
return maskToV;
case Expansion::CoV:
return maskCoV;
case Expansion::ToL:
return maskToL;
case Expansion::NoS:
return maskNoS;
case Expansion::LS:
return maskLS;
default:
return maskEverQuest;
}
+45 -9
View File
@@ -36,7 +36,8 @@ namespace EQ
SoD, // Build: 'Dec 19 2008 15:22:49'
UF, // Build: 'Jun 8 2010 16:44:32'
RoF, // Build: 'Dec 10 2012 17:35:44'
RoF2 // Build: 'May 10 2013 23:30:08'
RoF2, // Build: 'May 10 2013 23:30:08'
Laurion
};
enum ClientVersionBitmask : uint32 {
@@ -48,6 +49,7 @@ namespace EQ
bitUF = 0x00000010,
bitRoF = 0x00000020,
bitRoF2 = 0x00000040,
bitLaurion = 0x00000080,
maskUnknown = 0x00000000,
maskTitaniumAndEarlier = 0x00000003,
maskSoFAndEarlier = 0x00000007,
@@ -59,10 +61,11 @@ namespace EQ
maskUFAndLater = 0xFFFFFFF0,
maskRoFAndLater = 0xFFFFFFE0,
maskRoF2AndLater = 0xFFFFFFC0,
maskLaurionAndLater = 0xFFFFFF80,
maskAllClients = 0xFFFFFFFF
};
const ClientVersion LastClientVersion = ClientVersion::RoF2;
const ClientVersion LastClientVersion = ClientVersion::Laurion;
const size_t ClientVersionCount = (static_cast<size_t>(LastClientVersion) + 1);
bool IsValidClientVersion(ClientVersion client_version);
@@ -80,6 +83,7 @@ namespace EQ
UF,
RoF,
RoF2,
Laurion,
NPC,
NPCMerchant,
Merc,
@@ -93,13 +97,14 @@ namespace EQ
OfflineSoD,
OfflineUF,
OfflineRoF,
OfflineRoF2
OfflineRoF2,
OfflineLaurion
};
const MobVersion LastMobVersion = MobVersion::OfflineRoF2;
const MobVersion LastPCMobVersion = MobVersion::RoF2;
const MobVersion LastMobVersion = MobVersion::OfflineLaurion;
const MobVersion LastPCMobVersion = MobVersion::Laurion;
const MobVersion LastNonPCMobVersion = MobVersion::BotPet;
const MobVersion LastOfflinePCMobVersion = MobVersion::OfflineRoF2;
const MobVersion LastOfflinePCMobVersion = MobVersion::OfflineLaurion;
const size_t MobVersionCount = (static_cast<size_t>(LastMobVersion) + 1);
bool IsValidMobVersion(MobVersion mob_version);
@@ -131,7 +136,8 @@ namespace EQ
ucsSoDCombined = 'D',
ucsUFCombined = 'E',
ucsRoFCombined = 'F',
ucsRoF2Combined = 'G'
ucsRoF2Combined = 'G',
ucsLaurionCombined = 'G'
};
} /*versions*/
@@ -158,7 +164,17 @@ namespace EQ
HoT,
VoA,
RoF,
CotF
CotF,
TDS,
TBM,
EoK,
RoS,
TBL,
ToV,
CoV,
ToL,
NoS,
LS
};
enum ExpansionBitmask : uint32 {
@@ -183,6 +199,16 @@ namespace EQ
bitVoA = 0x00020000,
bitRoF = 0x00040000,
bitCotF = 0x00080000,
bitTDS = 0x00100000,
bitTBM = 0x00200000,
bitEoK = 0x00400000,
bitRoS = 0x00800000,
bitTBL = 0x01000000,
bitToV = 0x02000000,
bitCoV = 0x04000000,
bitToL = 0x08000000,
bitNoS = 0x10000000,
bitLS = 0x20000000,
maskEverQuest = 0x00000000,
maskRoK = 0x00000001,
maskSoV = 0x00000003,
@@ -203,7 +229,17 @@ namespace EQ
maskHoT = 0x0001FFFF,
maskVoA = 0x0003FFFF,
maskRoF = 0x0007FFFF,
maskCotF = 0x000FFFFF
maskCotF = 0x000FFFFF,
maskTDS = 0x001FFFFF,
maskTBM = 0x003FFFFF,
maskEoK = 0x007FFFFF,
maskRoS = 0x00FFFFFF,
maskTBL = 0x01FFFFFF,
maskToV = 0x03FFFFFF,
maskCoV = 0x07FFFFFF,
maskToL = 0x0FFFFFFF,
maskNoS = 0x1FFFFFFF,
maskLS = 0x3FFFFFFF,
};
const char* ExpansionName(Expansion expansion);
+40
View File
@@ -762,6 +762,46 @@ typedef enum {
FilterStrikethrough = 26, //0=show, 1=hide // RoF2 Confirmed
FilterStuns = 27, //0=show, 1=hide // RoF2 Confirmed
FilterBardSongsOnPets = 28, //0=show, 1=hide // RoF2 Confirmed
FilterSwarmPetDeath = 29,
FilterFellowshipChat = 30,
FilterMercenaryMessages = 31,
FilterSpam = 32,
FilterAchievements = 33,
FilterPvPMessages = 34,
FilterSpellNameInCast = 35,
FilterRandomMine = 36,
FilterRandomGroupRaid = 37,
FilterRandomOthers = 38,
FilterEnvironmentalDamage = 39,
FilterMessages = 40,
FilterOverwriteDetrimental = 41,
FilterOverwriteBeneficial = 42,
FilterCantUseCommand = 43,
FilterCombatAbilityReuse = 44,
FilterAAAbilityReuse = 45,
FilterProcBeginCasting = 46,
FilterDestroyedItems = 47,
FilterYourAuras = 48,
FilterOtherAuras = 49,
FilterYourHeals = 50,
FilterOtherHeals = 51,
FilterYourDoTs = 52,
FilterOtherDoTs = 53,
FilterOtherDirectDamage = 54,
FilterSpellEmotes = 55,
FilterFactionMessages = 56,
FilterTauntMessages = 57,
FilterYourDisciplines = 58,
FilterOtherDisplines = 59,
FilterAchievementsOthers = 60,
FilterRaidVictory = 61,
FilterOtherDirectDamageCrits = 62,
FilterDoTYoursCritical = 63,
FilterDoTOthersCritical = 64,
FilterDoTDamageTaken = 65,
FilterHealsReceived = 66,
FilterHealsYoursCritical = 67,
FilterHealsOthersCritical = 68,
_FilterCount
} eqFilterType;
+83
View File
@@ -104,6 +104,14 @@ static const EQ::constants::LookupEntry constants_static_lookup_entries[EQ::vers
RoF2::constants::EXPANSIONS_MASK,
RoF2::constants::CHARACTER_CREATION_LIMIT,
RoF2::constants::SAY_LINK_BODY_SIZE
),
/*[ClientVersion::Laurion] =*/
EQ::constants::LookupEntry(
Laurion::constants::EXPANSION,
Laurion::constants::EXPANSION_BIT,
Laurion::constants::EXPANSIONS_MASK,
Laurion::constants::CHARACTER_CREATION_LIMIT,
Laurion::constants::SAY_LINK_BODY_SIZE
)
};
@@ -370,6 +378,33 @@ static const EQ::inventory::LookupEntry inventory_static_lookup_entries[EQ::vers
RoF2::inventory::ConcatenateInvTypeLimbo,
RoF2::inventory::AllowOverLevelEquipment
),
/*[MobVersion::LS] =*/
EQ::inventory::LookupEntry(
EQ::inventory::LookupEntry::InventoryTypeSize_Struct(
EQ::invtype::POSSESSIONS_SIZE, RoF2::invtype::BANK_SIZE, RoF2::invtype::SHARED_BANK_SIZE,
RoF2::invtype::TRADE_SIZE, RoF2::invtype::WORLD_SIZE, RoF2::invtype::LIMBO_SIZE,
RoF2::invtype::TRIBUTE_SIZE, RoF2::invtype::TROPHY_TRIBUTE_SIZE, RoF2::invtype::GUILD_TRIBUTE_SIZE,
RoF2::invtype::MERCHANT_SIZE, RoF2::invtype::DELETED_SIZE, RoF2::invtype::CORPSE_SIZE,
RoF2::invtype::BAZAAR_SIZE, RoF2::invtype::INSPECT_SIZE, RoF2::invtype::REAL_ESTATE_SIZE,
RoF2::invtype::VIEW_MOD_PC_SIZE, RoF2::invtype::VIEW_MOD_BANK_SIZE, RoF2::invtype::VIEW_MOD_SHARED_BANK_SIZE,
RoF2::invtype::VIEW_MOD_LIMBO_SIZE, RoF2::invtype::ALT_STORAGE_SIZE, RoF2::invtype::ARCHIVED_SIZE,
RoF2::invtype::MAIL_SIZE, RoF2::invtype::GUILD_TROPHY_TRIBUTE_SIZE, RoF2::invtype::KRONO_SIZE,
RoF2::invtype::OTHER_SIZE
),
RoF2::invslot::EQUIPMENT_BITMASK,
RoF2::invslot::GENERAL_BITMASK,
RoF2::invslot::CURSOR_BITMASK,
RoF2::invslot::POSSESSIONS_BITMASK,
RoF2::invslot::CORPSE_BITMASK,
RoF2::invbag::SLOT_COUNT,
RoF2::invaug::SOCKET_COUNT,
RoF2::inventory::AllowEmptyBagInBag,
RoF2::inventory::AllowClickCastFromBag,
RoF2::inventory::ConcatenateInvTypeLimbo,
RoF2::inventory::AllowOverLevelEquipment
),
/*[MobVersion::NPC] =*/
EQ::inventory::LookupEntry(
EQ::inventory::LookupEntry::InventoryTypeSize_Struct(
@@ -743,6 +778,33 @@ static const EQ::inventory::LookupEntry inventory_static_lookup_entries[EQ::vers
RoF2::invbag::SLOT_COUNT,
RoF2::invaug::SOCKET_COUNT,
false,
false,
false,
false
),
/*[MobVersion::OfflineLS] =*/
EQ::inventory::LookupEntry(
EQ::inventory::LookupEntry::InventoryTypeSize_Struct(
RoF2::INULL, RoF2::INULL, RoF2::INULL,
RoF2::invtype::TRADE_SIZE, RoF2::INULL, RoF2::INULL,
RoF2::INULL, RoF2::INULL, RoF2::INULL,
RoF2::invtype::MERCHANT_SIZE, RoF2::INULL, RoF2::INULL,
RoF2::invtype::BAZAAR_SIZE, RoF2::invtype::INSPECT_SIZE, RoF2::INULL,
RoF2::invtype::VIEW_MOD_PC_SIZE, RoF2::invtype::VIEW_MOD_BANK_SIZE, RoF2::invtype::VIEW_MOD_SHARED_BANK_SIZE,
RoF2::invtype::VIEW_MOD_LIMBO_SIZE, RoF2::INULL, RoF2::INULL,
RoF2::INULL, RoF2::INULL, RoF2::INULL,
RoF2::INULL
),
RoF2::INULL,
RoF2::INULL,
RoF2::INULL,
RoF2::INULL,
RoF2::INULL,
RoF2::invbag::SLOT_COUNT,
RoF2::invaug::SOCKET_COUNT,
false,
false,
false,
@@ -994,6 +1056,10 @@ static const EQ::behavior::LookupEntry behavior_static_lookup_entries[EQ::versio
EQ::behavior::LookupEntry(
RoF2::behavior::CoinHasWeight
),
/*[MobVersion::LS] =*/
EQ::behavior::LookupEntry(
RoF2::behavior::CoinHasWeight
),
/*[MobVersion::NPC] =*/
EQ::behavior::LookupEntry(
EQ::behavior::CoinHasWeight
@@ -1047,6 +1113,10 @@ static const EQ::behavior::LookupEntry behavior_static_lookup_entries[EQ::versio
RoF::behavior::CoinHasWeight
),
/*[MobVersion::OfflineRoF2] =*/
EQ::behavior::LookupEntry(
RoF2::behavior::CoinHasWeight
),
/*[MobVersion::OfflineLS] =*/
EQ::behavior::LookupEntry(
RoF2::behavior::CoinHasWeight
)
@@ -1202,6 +1272,19 @@ static const EQ::spells::LookupEntry spells_static_lookup_entries[EQ::versions::
RoF2::spells::NPC_BUFFS,
RoF2::spells::PET_BUFFS,
RoF2::spells::MERC_BUFFS
),
/*[ClientVersion::Laurion] =*/
EQ::spells::LookupEntry(
Laurion::spells::SPELL_ID_MAX,
Laurion::spells::SPELLBOOK_SIZE,
UF::spells::SPELL_GEM_COUNT, // client translators are setup to allow the max value a client supports..however, the top 4 indices are not valid in this case
Laurion::spells::LONG_BUFFS,
Laurion::spells::SHORT_BUFFS,
Laurion::spells::DISC_BUFFS,
Laurion::spells::TOTAL_BUFFS,
Laurion::spells::NPC_BUFFS,
Laurion::spells::PET_BUFFS,
Laurion::spells::MERC_BUFFS
)
};
+1 -1
View File
@@ -29,7 +29,7 @@
#include "../common/patches/uf_limits.h"
#include "../common/patches/rof_limits.h"
#include "../common/patches/rof2_limits.h"
#include "../common/patches/laurion_limits.h"
namespace EQ
{
+9
View File
@@ -47,6 +47,13 @@ static const uint32 ADVANCED_LORE_LENGTH = 8192;
*/
#pragma pack(1)
struct EqGuid
{
uint32_t Id;
uint16_t WorldId;
uint16_t Reserved;
};
struct LoginInfo_Struct {
/*000*/ char login_info[64];
/*064*/ uint8 unknown064[124];
@@ -324,6 +331,7 @@ union
bool guild_show;
bool trader;
bool buyer;
EqGuid CharacterGuid;
};
struct PlayerState_Struct {
@@ -3221,6 +3229,7 @@ struct BuyerMessaging_Struct {
char item_name[64];
uint32 slot;
uint32 seller_quantity;
uint32 purchase_method; // 0 direct merchant, 1 via /barter window
};
struct BuyerAddBuyertoBarterWindow_Struct {
+2 -1
View File
@@ -94,7 +94,7 @@ void EQEmuConfig::parse_config()
auto_database_updates = true;
}
WorldIP = _root["server"]["world"]["tcp"].get("host", "127.0.0.1").asString();
WorldIP = _root["server"]["world"]["tcp"].get("ip", "127.0.0.1").asString();
WorldTCPPort = Strings::ToUnsignedInt(_root["server"]["world"]["tcp"].get("port", "9000").asString());
TelnetIP = _root["server"]["world"]["telnet"].get("ip", "127.0.0.1").asString();
@@ -171,6 +171,7 @@ void EQEmuConfig::parse_config()
PluginDir = _root["server"]["directories"].get("plugins", "plugins/").asString();
LuaModuleDir = _root["server"]["directories"].get("lua_modules", "lua_modules/").asString();
PatchDir = _root["server"]["directories"].get("patches", "./").asString();
OpcodeDir = _root["server"]["directories"].get("opcodes", "./").asString();
SharedMemDir = _root["server"]["directories"].get("shared_memory", "shared/").asString();
LogDir = _root["server"]["directories"].get("logs", "logs/").asString();
+1
View File
@@ -95,6 +95,7 @@ class EQEmuConfig
std::string PluginDir;
std::string LuaModuleDir;
std::string PatchDir;
std::string OpcodeDir;
std::string SharedMemDir;
std::string LogDir;
+600
View File
@@ -0,0 +1,600 @@
#include "mysql_stmt.h"
#include "eqemu_logsys.h"
#include "mutex.h"
#include "timer.h"
#include <charconv>
namespace mysql
{
void PreparedStmt::StmtDeleter::operator()(MYSQL_STMT* stmt) noexcept
{
// The connection must be locked when closing the stmt to avoid mysql errors
// in case another thread tries to use it during the close. If the mutex is
// changed to one that throws then exceptions need to be caught here.
LockMutex lock(mutex);
mysql_stmt_close(stmt);
}
PreparedStmt::PreparedStmt(MYSQL& mysql, std::string query, Mutex* mutex, StmtOptions opts)
: m_stmt(mysql_stmt_init(&mysql), { mutex }), m_query(std::move(query)), m_mutex(mutex), m_options(opts)
{
LockMutex lock(m_mutex);
if (mysql_stmt_prepare(m_stmt.get(), m_query.c_str(), static_cast<unsigned long>(m_query.size())) != 0)
{
ThrowError(fmt::format("Prepare error: {}", GetStmtError()));
}
m_params.resize(mysql_stmt_param_count(m_stmt.get()));
m_inputs.resize(m_params.size());
}
void PreparedStmt::ThrowError(const std::string& error)
{
LogMySQLError("{}", error);
throw std::runtime_error(error);
}
std::string PreparedStmt::GetStmtError()
{
auto err = mysql_stmt_errno(m_stmt.get());
auto str = mysql_stmt_error(m_stmt.get());
return fmt::format("({}) [{}] for query [{}]", err, str, m_query);
}
template <typename T>
void PreparedStmt::BindInput(size_t index, T value)
{
if (index >= m_inputs.size())
{
ThrowError(fmt::format("Cannot bind input, index {} out of range", index));
}
impl::Bind& arg = m_inputs[index];
arg.is_null = std::is_same_v<T, std::nullptr_t>;
MYSQL_BIND& bind = m_params[index];
bind.is_unsigned = std::is_unsigned_v<T>;
bind.is_null = &arg.is_null;
bind.length = &arg.length;
auto old_type = bind.buffer_type;
if constexpr (std::is_arithmetic_v<T>)
{
if (arg.buffer.size() < sizeof(T))
{
arg.buffer.resize(std::max(sizeof(T), sizeof(int64_t)));
bind.buffer = arg.buffer.data();
m_need_bind = true;
}
memcpy(arg.buffer.data(), &value, sizeof(T));
}
if constexpr (std::is_same_v<T, int8_t> || std::is_same_v<T, uint8_t> || std::is_same_v<T, bool>)
{
bind.buffer_type = MYSQL_TYPE_TINY;
}
else if constexpr (std::is_same_v<T, int16_t> || std::is_same_v<T, uint16_t>)
{
bind.buffer_type = MYSQL_TYPE_SHORT;
}
else if constexpr (std::is_same_v<T, int32_t> || std::is_same_v<T, uint32_t>)
{
bind.buffer_type = MYSQL_TYPE_LONG;
}
else if constexpr (std::is_same_v<T, int64_t> || std::is_same_v<T, uint64_t>)
{
bind.buffer_type = MYSQL_TYPE_LONGLONG;
}
else if constexpr (std::is_same_v<T, float>)
{
bind.buffer_type = MYSQL_TYPE_FLOAT;
}
else if constexpr (std::is_same_v<T, double>)
{
bind.buffer_type = MYSQL_TYPE_DOUBLE;
}
else if constexpr (std::is_same_v<T, std::string_view>)
{
bind.buffer_type = MYSQL_TYPE_STRING;
if (arg.buffer.empty() || arg.buffer.size() < value.size())
{
arg.buffer.resize(static_cast<size_t>((value.size() + 1) * 1.5));
bind.buffer = arg.buffer.data();
bind.buffer_length = static_cast<unsigned long>(arg.buffer.size());
m_need_bind = true;
}
std::copy(value.begin(), value.end(), arg.buffer.begin());
arg.length = static_cast<unsigned long>(value.size());
}
else if constexpr (!std::is_same_v<T, std::nullptr_t>)
{
static_assert(false_v<T>, "Cannot bind unsupported type");
}
if (old_type != bind.buffer_type)
{
m_need_bind = true;
}
}
void PreparedStmt::BindInput(size_t index, const char* str)
{
BindInput(index, std::string_view(str));
}
void PreparedStmt::BindInput(size_t index, const std::string& str)
{
BindInput(index, std::string_view(str));
}
StmtResult PreparedStmt::Execute()
{
CheckArgs(0);
return DoExecute();
}
StmtResult PreparedStmt::Execute(const std::vector<param_t>& args)
{
CheckArgs(args.size());
for (size_t i = 0; i < args.size(); ++i)
{
std::visit([&](const auto& arg) { BindInput(i, arg); }, args[i]);
}
return DoExecute();
}
template <typename T>
StmtResult PreparedStmt::Execute(const std::vector<T>& args)
{
CheckArgs(args.size());
for (size_t i = 0; i < args.size(); ++i)
{
BindInput(i, args[i]);
}
return DoExecute();
}
void PreparedStmt::CheckArgs(size_t argc)
{
if (argc != m_params.size())
{
ThrowError(fmt::format("Bad arg count (got {}, expected {}) for [{}]", argc, m_params.size(), m_query));
}
}
StmtResult PreparedStmt::DoExecute()
{
BenchTimer timer;
LockMutex lock(m_mutex);
if (m_need_bind && mysql_stmt_bind_param(m_stmt.get(), m_params.data()) != 0)
{
ThrowError(fmt::format("Bind param error: {}", GetStmtError()));
}
m_need_bind = false;
if (mysql_stmt_execute(m_stmt.get()) != 0)
{
ThrowError(fmt::format("Execute error: {}", GetStmtError()));
}
my_bool attr = m_options.use_max_length;
mysql_stmt_attr_set(m_stmt.get(), STMT_ATTR_UPDATE_MAX_LENGTH, &attr);
if (m_options.buffer_results && mysql_stmt_store_result(m_stmt.get()) != 0)
{
ThrowError(fmt::format("Store result error: {}", GetStmtError()));
}
// Result buffers are bound on first execute and re-used if needed
if (m_results.empty())
{
BindResults();
}
StmtResult res(m_stmt.get(), m_results.size());
if (m_results.empty())
{
LogMySQLQuery("{} -- ({} row(s) affected) ({:.6f}s)", m_query, res.RowsAffected(), timer.elapsed());
}
else
{
LogMySQLQuery("{} -- ({} row(s) returned) ({:.6f}s)", m_query, res.RowCount(), timer.elapsed());
}
return res;
}
void PreparedStmt::BindResults()
{
MYSQL_RES* res = mysql_stmt_result_metadata(m_stmt.get());
if (!res)
{
return; // did not produce a result set
}
MYSQL_FIELD* fields = mysql_fetch_fields(res);
m_columns.resize(mysql_num_fields(res));
m_results.resize(m_columns.size());
for (int i = 0; i < static_cast<int>(m_columns.size()); ++i)
{
impl::BindColumn& col = m_columns[i].m_col;
MYSQL_BIND& bind = m_results[i];
col.index = i;
col.name = fields[i].name;
col.buffer_type = fields[i].type;
col.is_unsigned = (fields[i].flags & UNSIGNED_FLAG) != 0;
col.buffer.resize(GetResultBufferSize(fields[i]));
bind.buffer_type = col.buffer_type;
bind.buffer = col.buffer.data();
bind.buffer_length = static_cast<unsigned long>(col.buffer.size());
bind.is_unsigned = col.is_unsigned;
bind.is_null = &col.is_null;
bind.length = &col.length;
bind.error = &col.error;
}
mysql_free_result(res);
if (!m_results.empty() && mysql_stmt_bind_result(m_stmt.get(), m_results.data()) != 0)
{
ThrowError(fmt::format("Bind result error: {}", GetStmtError()));
}
}
int PreparedStmt::GetResultBufferSize(const MYSQL_FIELD& field) const
{
switch (field.type)
{
case MYSQL_TYPE_TINY:
return sizeof(int8_t);
case MYSQL_TYPE_SHORT:
return sizeof(int16_t);
case MYSQL_TYPE_INT24:
case MYSQL_TYPE_LONG:
return sizeof(int32_t);
case MYSQL_TYPE_LONGLONG:
return sizeof(int64_t);
case MYSQL_TYPE_FLOAT:
return sizeof(float);
case MYSQL_TYPE_DOUBLE:
return sizeof(double);
case MYSQL_TYPE_TIME:
case MYSQL_TYPE_DATE:
case MYSQL_TYPE_DATETIME:
case MYSQL_TYPE_TIMESTAMP:
return sizeof(MYSQL_TIME);
default: // if max_length is unavailable for strings buffers are resized on fetch
return field.max_length + 1; // ensure valid buffer created
}
}
StmtRow PreparedStmt::Fetch()
{
StmtRow row;
if (!m_columns.empty())
{
int rc = mysql_stmt_fetch(m_stmt.get());
if (rc == 1)
{
ThrowError(fmt::format("Fetch error: {}", GetStmtError()));
}
if (rc != MYSQL_NO_DATA)
{
if (rc == MYSQL_DATA_TRUNCATED)
{
FetchTruncated();
}
row = StmtRow(m_columns);
}
}
return row;
}
void PreparedStmt::FetchTruncated()
{
for (int i = 0; i < static_cast<int>(m_columns.size()); ++i)
{
impl::BindColumn& col = m_columns[i].m_col;
if (col.error)
{
MYSQL_BIND& bind = m_results[i];
col.buffer.resize(static_cast<size_t>(col.length * 1.5));
bind.buffer = col.buffer.data();
bind.buffer_length = static_cast<unsigned long>(col.buffer.size());
mysql_stmt_fetch_column(m_stmt.get(), &bind, i, 0);
}
}
if (mysql_stmt_bind_result(m_stmt.get(), m_results.data()) != 0)
{
ThrowError(fmt::format("Fetch rebind result error: {}", GetStmtError()));
}
}
// ---------------------------------------------------------------------------
StmtResult::StmtResult(MYSQL_STMT* stmt, size_t columns)
{
m_num_cols = static_cast<int>(columns);
m_num_rows = mysql_stmt_num_rows(stmt); // requires buffered results
m_affected = mysql_stmt_affected_rows(stmt);
m_insert_id = mysql_stmt_insert_id(stmt);
}
// ---------------------------------------------------------------------------
const StmtColumn* StmtRow::GetColumn(size_t index) const
{
return index < m_columns.size() ? &m_columns[index] : nullptr;
}
const StmtColumn* StmtRow::GetColumn(std::string_view name) const
{
auto it = std::ranges::find_if(m_columns,
[name](const StmtColumn& col) { return col.Name() == name; });
return it != m_columns.end() ? &(*it) : nullptr;
}
std::optional<std::string> StmtRow::operator[](size_t index) const
{
return GetStr(index);
}
std::optional<std::string> StmtRow::operator[](std::string_view name) const
{
return GetStr(name);
}
std::optional<std::string> StmtRow::GetStr(size_t index) const
{
const StmtColumn* col = GetColumn(index);
return col ? col->GetStr() : std::nullopt;
}
std::optional<std::string> StmtRow::GetStr(std::string_view name) const
{
const StmtColumn* col = GetColumn(name);
return col ? col->GetStr() : std::nullopt;
}
template <typename T> requires std::is_arithmetic_v<T>
std::optional<T> StmtRow::Get(size_t index) const
{
const StmtColumn* col = GetColumn(index);
return col ? col->Get<T>() : std::nullopt;
}
template <typename T> requires std::is_arithmetic_v<T>
std::optional<T> StmtRow::Get(std::string_view name) const
{
const StmtColumn* col = GetColumn(name);
return col ? col->Get<T>() : std::nullopt;
}
// ---------------------------------------------------------------------------
static time_t MakeTime(const MYSQL_TIME& mt)
{
// buffer mt given in mysql session time zone (assumes local)
std::tm tm{};
tm.tm_year = mt.year - 1900;
tm.tm_mon = mt.month - 1;
tm.tm_mday = mt.day;
tm.tm_hour = mt.hour;
tm.tm_min = mt.minute;
tm.tm_sec = mt.second;
tm.tm_isdst = -1;
return std::mktime(&tm);
}
static int MakeSeconds(const MYSQL_TIME& mt)
{
return (mt.neg ? -1 : 1) * static_cast<int>(mt.hour * 3600 + mt.minute * 60 + mt.second);
}
static uint64_t MakeBits(std::span<const uint8_t> data)
{
// byte stream for bits is in big endian
uint64_t bits = 0;
for (size_t i = 0; i < data.size() && i < sizeof(uint64_t); ++i)
{
bits |= static_cast<uint64_t>(data[data.size() - i - 1] & 0xff) << (i * 8);
}
return bits;
}
template <typename T>
concept has_from_chars = requires (const char* first, const char* last, T value)
{
std::from_chars(first, last, value);
};
template <typename T>
static T FromString(std::string_view sv)
{
if constexpr (std::is_same_v<T, bool>)
{
// return false for empty (zero-length) strings
return !sv.empty();
}
else if constexpr (std::is_same_v<T, float> && !has_from_chars<T>)
{
return std::strtof(std::string(sv).c_str(), nullptr);
}
else if constexpr (std::is_same_v<T, double> && !has_from_chars<T>)
{
return std::strtod(std::string(sv).c_str(), nullptr);
}
else
{
// non numbers return a zero initialized T (could return nullopt instead)
T value = {};
std::from_chars(sv.data(), sv.data() + sv.size(), value);
return value;
}
}
static std::string FormatTime(enum_field_types type, const MYSQL_TIME& mt)
{
switch (type)
{
case MYSQL_TYPE_TIME: // hhh:mm:ss '-838:59:59' to '838:59:59'
return fmt::format("{}{:02d}:{:02d}:{:02d}", mt.neg ? "-" : "", mt.hour, mt.minute, mt.second);
case MYSQL_TYPE_DATE: // YYYY-MM-DD '1000-01-01' to '9999-12-31'
return fmt::format("{}-{:02d}-{:02d}", mt.year, mt.month, mt.day);
case MYSQL_TYPE_DATETIME: // YYYY-MM-DD hh:mm:ss '1000-01-01 00:00:00' to '9999-12-31 23:59:59'
case MYSQL_TYPE_TIMESTAMP: // YYYY-MM-DD hh:mm:ss '1970-01-01 00:00:01' UTC to '2038-01-19 03:14:07' UTC
return fmt::format("{}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}", mt.year, mt.month, mt.day, mt.hour, mt.minute, mt.second);
default:
return std::string();
}
}
std::optional<std::string_view> StmtColumn::GetStrView() const
{
if (m_col.is_null)
{
return std::nullopt;
}
switch (m_col.buffer_type)
{
case MYSQL_TYPE_NEWDECIMAL:
case MYSQL_TYPE_TINY_BLOB:
case MYSQL_TYPE_MEDIUM_BLOB:
case MYSQL_TYPE_LONG_BLOB:
case MYSQL_TYPE_BLOB:
case MYSQL_TYPE_VAR_STRING:
case MYSQL_TYPE_STRING:
return std::make_optional<std::string_view>(reinterpret_cast<const char*>(m_col.buffer.data()), m_col.length);
default:
return std::nullopt;
}
}
std::optional<std::string> StmtColumn::GetStr() const
{
if (m_col.is_null)
{
return std::nullopt;
}
switch (m_col.buffer_type)
{
case MYSQL_TYPE_TINY:
return m_col.is_unsigned ? fmt::format_int(BitCast<uint8_t>()).c_str() : fmt::format_int(BitCast<int8_t>()).c_str();
case MYSQL_TYPE_SHORT:
return m_col.is_unsigned ? fmt::format_int(BitCast<uint16_t>()).c_str() : fmt::format_int(BitCast<int16_t>()).c_str();
case MYSQL_TYPE_INT24:
case MYSQL_TYPE_LONG:
return m_col.is_unsigned ? fmt::format_int(BitCast<uint32_t>()).c_str() : fmt::format_int(BitCast<int32_t>()).c_str();
case MYSQL_TYPE_LONGLONG:
return m_col.is_unsigned ? fmt::format_int(BitCast<uint64_t>()).c_str() : fmt::format_int(BitCast<int64_t>()).c_str();
case MYSQL_TYPE_FLOAT:
return fmt::format("{}", BitCast<float>());
case MYSQL_TYPE_DOUBLE:
return fmt::format("{}", BitCast<double>());
case MYSQL_TYPE_TIME:
case MYSQL_TYPE_DATE:
case MYSQL_TYPE_DATETIME:
case MYSQL_TYPE_TIMESTAMP:
return FormatTime(m_col.buffer_type, BitCast<MYSQL_TIME>());
case MYSQL_TYPE_BIT:
return fmt::format_int(*Get<uint64_t>()).c_str();
case MYSQL_TYPE_NEWDECIMAL:
case MYSQL_TYPE_TINY_BLOB:
case MYSQL_TYPE_MEDIUM_BLOB:
case MYSQL_TYPE_LONG_BLOB:
case MYSQL_TYPE_BLOB:
case MYSQL_TYPE_VAR_STRING:
case MYSQL_TYPE_STRING:
return std::make_optional<std::string>(reinterpret_cast<const char*>(m_col.buffer.data()), m_col.length);
default:
return std::nullopt;
}
}
template <typename T> requires std::is_arithmetic_v<T>
std::optional<T> StmtColumn::Get() const
{
if (m_col.is_null)
{
return std::nullopt;
}
switch (m_col.buffer_type)
{
case MYSQL_TYPE_TINY:
return m_col.is_unsigned ? static_cast<T>(BitCast<uint8_t>()) : static_cast<T>(BitCast<int8_t>());
case MYSQL_TYPE_SHORT:
return m_col.is_unsigned ? static_cast<T>(BitCast<uint16_t>()) : static_cast<T>(BitCast<int16_t>());
case MYSQL_TYPE_INT24:
case MYSQL_TYPE_LONG:
return m_col.is_unsigned ? static_cast<T>(BitCast<uint32_t>()) : static_cast<T>(BitCast<int32_t>());
case MYSQL_TYPE_LONGLONG:
return m_col.is_unsigned ? static_cast<T>(BitCast<uint64_t>()) : static_cast<T>(BitCast<int64_t>());
case MYSQL_TYPE_FLOAT:
return static_cast<T>(BitCast<float>());
case MYSQL_TYPE_DOUBLE:
return static_cast<T>(BitCast<double>());
case MYSQL_TYPE_TIME: // return as total seconds
return static_cast<T>(MakeSeconds(BitCast<MYSQL_TIME>()));
case MYSQL_TYPE_DATE:
case MYSQL_TYPE_DATETIME:
case MYSQL_TYPE_TIMESTAMP: // return as epoch timestamp
return static_cast<T>(MakeTime(BitCast<MYSQL_TIME>()));
case MYSQL_TYPE_BIT:
return static_cast<T>(MakeBits({ m_col.buffer.data(), m_col.length }));
case MYSQL_TYPE_NEWDECIMAL:
case MYSQL_TYPE_TINY_BLOB:
case MYSQL_TYPE_MEDIUM_BLOB:
case MYSQL_TYPE_LONG_BLOB:
case MYSQL_TYPE_BLOB:
case MYSQL_TYPE_VAR_STRING:
case MYSQL_TYPE_STRING:
return FromString<T>({ reinterpret_cast<const char*>(m_col.buffer.data()), m_col.length });
default:
return std::nullopt;
}
}
// ---------------------------------------------------------------------------
// explicit template instantiations for supported types
template void PreparedStmt::BindInput(size_t, std::string_view);
template void PreparedStmt::BindInput(size_t, std::nullptr_t);
template StmtResult PreparedStmt::Execute(const std::vector<std::string_view>&);
template StmtResult PreparedStmt::Execute(const std::vector<std::string>&);
template StmtResult PreparedStmt::Execute(const std::vector<const char*>&);
#define INSTANTIATE(T) \
template void PreparedStmt::BindInput(size_t, T); \
template StmtResult PreparedStmt::Execute(const std::vector<T>&); \
template std::optional<T> StmtRow::Get(size_t) const; \
template std::optional<T> StmtRow::Get(std::string_view) const; \
template std::optional<T> StmtColumn::Get() const;
INSTANTIATE(bool);
INSTANTIATE(int8_t);
INSTANTIATE(uint8_t);
INSTANTIATE(int16_t);
INSTANTIATE(uint16_t);
INSTANTIATE(int32_t);
INSTANTIATE(uint32_t);
INSTANTIATE(int64_t);
INSTANTIATE(uint64_t);
INSTANTIATE(float);
INSTANTIATE(double);
} // namespace mysql
+221
View File
@@ -0,0 +1,221 @@
#pragma once
#include "mysql.h"
#include <cassert>
#include <cstring>
#include <memory>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <variant>
#include <vector>
class Mutex;
namespace mysql
{
// support MySQL 8.0.1+ API which removed the my_bool type
#if !defined(MARIADB_VERSION_ID) && MYSQL_VERSION_ID >= 80001
using my_bool = bool;
#endif
template <typename>
inline constexpr bool false_v = false;
namespace impl
{
struct Bind
{
std::vector<uint8_t> buffer;
unsigned long length = 0;
my_bool is_null = false;
my_bool error = false;
};
struct BindColumn : Bind
{
int index = 0;
std::string name;
bool is_unsigned = false;
enum_field_types buffer_type = {};
};
} // namespace impl
// ---------------------------------------------------------------------------
struct StmtOptions
{
// Enable buffering (storing) entire result set after executing a statement
bool buffer_results = true;
// Enable MySQL to update max_length of fields in execute result set (requires buffering)
bool use_max_length = true;
};
// ---------------------------------------------------------------------------
// Holds ownership of bound column value buffer
class StmtColumn
{
public:
int Index() const { return m_col.index; }
bool IsNull() const { return m_col.is_null; }
bool IsUnsigned() const { return m_col.is_unsigned; }
enum_field_types Type() const { return m_col.buffer_type; }
const std::string& Name() const { return m_col.name; }
// Get view of column value buffer
std::span<const uint8_t> GetBuf() const { return { m_col.buffer.data(), m_col.length }; }
// Get view of column string value. Returns nullopt if value is NULL or not a string
std::optional<std::string_view> GetStrView() const;
// Get column value as string. Returns nullopt if value is NULL or field type unsupported
std::optional<std::string> GetStr() const;
// Get column value as numeric T. Returns nullopt if value NULL or field type unsupported
template <typename T> requires std::is_arithmetic_v<T>
std::optional<T> Get() const;
private:
// uses memcpy for type punning buffer data to avoid UB with strict aliasing
template <typename T>
T BitCast() const
{
T val;
assert(sizeof(T) == m_col.length);
memcpy(&val, m_col.buffer.data(), sizeof(T));
return val;
}
friend class PreparedStmt; // access to allocate and bind buffers
friend class StmtResult; // access to resize truncated buffers
impl::BindColumn m_col;
};
// ---------------------------------------------------------------------------
// Provides a non-owning view of PreparedStmt column value buffers
// Evaluates false if it does not contain a valid row
class StmtRow
{
public:
StmtRow() = default;
StmtRow(std::span<const StmtColumn> columns) : m_columns(columns) {};
explicit operator bool() const noexcept { return !m_columns.empty(); }
int ColumnCount() const { return static_cast<int>(m_columns.size()); }
const StmtColumn* GetColumn(size_t index) const;
const StmtColumn* GetColumn(std::string_view name) const;
// Get specified column value as string
// Returns nullopt if column invalid, value is NULL, or field type unsupported
std::optional<std::string> operator[](size_t index) const;
std::optional<std::string> operator[](std::string_view name) const;
std::optional<std::string> GetStr(size_t index) const;
std::optional<std::string> GetStr(std::string_view name) const;
// Get specified column value as numeric T
// Returns nullopt if column invalid, value is NULL, or field type unsupported
template <typename T> requires std::is_arithmetic_v<T>
std::optional<T> Get(size_t index) const;
template <typename T> requires std::is_arithmetic_v<T>
std::optional<T> Get(std::string_view name) const;
auto begin() const { return m_columns.begin(); }
auto end() const { return m_columns.end(); }
private:
std::span<const StmtColumn> m_columns;
};
// ---------------------------------------------------------------------------
// Result meta data for an executed prepared statement
class StmtResult
{
public:
StmtResult() = default;
StmtResult(MYSQL_STMT* stmt, size_t columns);
int ColumnCount() const { return m_num_cols; }
uint64_t RowCount() const { return m_num_rows; }
uint64_t RowsAffected() const { return m_affected; }
uint64_t LastInsertID() const { return m_insert_id; }
private:
int m_num_cols = 0;
uint64_t m_num_rows = 0;
uint64_t m_affected = 0;
uint64_t m_insert_id = 0;
};
// ---------------------------------------------------------------------------
class PreparedStmt
{
public:
// Supported argument types for execute
using param_t = std::variant<int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t,
int64_t, uint64_t, float, double, bool, std::string_view, std::nullptr_t>;
PreparedStmt() = delete;
PreparedStmt(MYSQL& mysql, std::string query, Mutex* mutex, StmtOptions opts = {});
const std::string& GetQuery() const { return m_query; }
StmtOptions GetOptions() const { return m_options; }
void SetOptions(StmtOptions options) { m_options = options; }
void FreeResult() { mysql_stmt_free_result(m_stmt.get()); }
// Execute the prepared statement with specified arguments
// Throws exception on error
template <typename T>
StmtResult Execute(const std::vector<T>& args);
StmtResult Execute(const std::vector<param_t>& args);
StmtResult Execute();
// Fetch the next row into column buffers (overwrites previous row values)
// Return value evaluates false if no more rows to fetch
// Throws exception on error
StmtRow Fetch();
private:
void CheckArgs(size_t argc);
StmtResult DoExecute();
void BindResults();
void FetchTruncated();
int GetResultBufferSize(const MYSQL_FIELD& field) const;
void ThrowError(const std::string& error);
std::string GetStmtError();
// bind an input value to a query parameter by index
template <typename T>
void BindInput(size_t index, T value);
void BindInput(size_t index, const char* str);
void BindInput(size_t index, const std::string& str);
struct StmtDeleter
{
Mutex* mutex = nullptr;
void operator()(MYSQL_STMT* stmt) noexcept;
};
private:
std::unique_ptr<MYSQL_STMT, StmtDeleter> m_stmt;
std::vector<MYSQL_BIND> m_params; // input binds
std::vector<MYSQL_BIND> m_results; // result binds
std::vector<impl::Bind> m_inputs; // execute buffers (addresses bound)
std::vector<StmtColumn> m_columns; // fetch buffers (addresses bound)
std::string m_query;
StmtOptions m_options = {};
bool m_need_bind = true;
Mutex* m_mutex = nullptr; // connection mutex
};
} // namespace mysql
File diff suppressed because it is too large Load Diff
+55
View File
@@ -0,0 +1,55 @@
/* EQEMu: Everquest Server Emulator
Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.net)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY except by those people which sell it, which
are required to give you total support for your newly bought product;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef COMMON_LAURION_H
#define COMMON_LAURION_H
#include "../struct_strategy.h"
class EQStreamIdentifier;
namespace Laurion
{
//these are the only public member of this namespace.
extern void Register(EQStreamIdentifier& into);
extern void Reload();
//you should not directly access anything below..
//I just dont feel like making a seperate header for it.
class Strategy : public StructStrategy {
public:
Strategy();
protected:
virtual std::string Describe() const;
virtual const EQ::versions::ClientVersion ClientVersion() const;
//magic macro to declare our opcode processors
#include "ss_declare.h"
#include "laurion_ops.h"
};
}; /*Laurion*/
#endif /*COMMON_LAURION_H*/
+284
View File
@@ -0,0 +1,284 @@
/* EQEMu: Everquest Server Emulator
Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.net)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY except by those people which sell it, which
are required to give you total support for your newly bought product;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "laurion_limits.h"
#include "../strings.h"
int16 Laurion::invtype::GetInvTypeSize(int16 inv_type)
{
switch (inv_type) {
case invtype::typePossessions:
return invtype::POSSESSIONS_SIZE;
case invtype::typeBank:
return invtype::BANK_SIZE;
case invtype::typeSharedBank:
return invtype::SHARED_BANK_SIZE;
case invtype::typeTrade:
return invtype::TRADE_SIZE;
case invtype::typeWorld:
return invtype::WORLD_SIZE;
case invtype::typeLimbo:
return invtype::LIMBO_SIZE;
case invtype::typeTribute:
return invtype::TRIBUTE_SIZE;
case invtype::typeTrophyTribute:
return invtype::TROPHY_TRIBUTE_SIZE;
case invtype::typeGuildTribute:
return invtype::GUILD_TRIBUTE_SIZE;
case invtype::typeMerchant:
return invtype::MERCHANT_SIZE;
case invtype::typeDeleted:
return invtype::DELETED_SIZE;
case invtype::typeCorpse:
return invtype::CORPSE_SIZE;
case invtype::typeBazaar:
return invtype::BAZAAR_SIZE;
case invtype::typeInspect:
return invtype::INSPECT_SIZE;
case invtype::typeRealEstate:
return invtype::REAL_ESTATE_SIZE;
case invtype::typeViewMODPC:
return invtype::VIEW_MOD_PC_SIZE;
case invtype::typeViewMODBank:
return invtype::VIEW_MOD_BANK_SIZE;
case invtype::typeViewMODSharedBank:
return invtype::VIEW_MOD_SHARED_BANK_SIZE;
case invtype::typeViewMODLimbo:
return invtype::VIEW_MOD_LIMBO_SIZE;
case invtype::typeAltStorage:
return invtype::ALT_STORAGE_SIZE;
case invtype::typeArchived:
return invtype::ARCHIVED_SIZE;
case invtype::typeMail:
return invtype::MAIL_SIZE;
case invtype::typeGuildTrophyTribute:
return invtype::GUILD_TROPHY_TRIBUTE_SIZE;
case invtype::typeKrono:
return invtype::KRONO_SIZE;
case invtype::typeOther:
return invtype::OTHER_SIZE;
default:
return INULL;
}
}
const char* Laurion::invtype::GetInvTypeName(int16 inv_type)
{
switch (inv_type) {
case invtype::TYPE_INVALID:
return "Invalid Type";
case invtype::typePossessions:
return "Possessions";
case invtype::typeBank:
return "Bank";
case invtype::typeSharedBank:
return "Shared Bank";
case invtype::typeTrade:
return "Trade";
case invtype::typeWorld:
return "World";
case invtype::typeLimbo:
return "Limbo";
case invtype::typeTribute:
return "Tribute";
case invtype::typeTrophyTribute:
return "Trophy Tribute";
case invtype::typeGuildTribute:
return "Guild Tribute";
case invtype::typeMerchant:
return "Merchant";
case invtype::typeDeleted:
return "Deleted";
case invtype::typeCorpse:
return "Corpse";
case invtype::typeBazaar:
return "Bazaar";
case invtype::typeInspect:
return "Inspect";
case invtype::typeRealEstate:
return "Real Estate";
case invtype::typeViewMODPC:
return "View MOD PC";
case invtype::typeViewMODBank:
return "View MOD Bank";
case invtype::typeViewMODSharedBank:
return "View MOD Shared Bank";
case invtype::typeViewMODLimbo:
return "View MOD Limbo";
case invtype::typeAltStorage:
return "Alt Storage";
case invtype::typeArchived:
return "Archived";
case invtype::typeMail:
return "Mail";
case invtype::typeGuildTrophyTribute:
return "Guild Trophy Tribute";
case invtype::typeKrono:
return "Krono";
case invtype::typeOther:
return "Other";
default:
return "Unknown Type";
}
}
bool Laurion::invtype::IsInvTypePersistent(int16 inv_type)
{
switch (inv_type) {
case invtype::typePossessions:
case invtype::typeBank:
case invtype::typeSharedBank:
case invtype::typeTrade:
case invtype::typeWorld:
case invtype::typeLimbo:
case invtype::typeTribute:
case invtype::typeTrophyTribute:
case invtype::typeGuildTribute:
return true;
default:
return false;
}
}
const char* Laurion::invslot::GetInvPossessionsSlotName(int16 inv_slot)
{
switch (inv_slot) {
case invslot::SLOT_INVALID:
return "Invalid Slot";
case invslot::slotCharm:
return "Charm";
case invslot::slotEar1:
return "Ear 1";
case invslot::slotHead:
return "Head";
case invslot::slotFace:
return "Face";
case invslot::slotEar2:
return "Ear 2";
case invslot::slotNeck:
return "Neck";
case invslot::slotShoulders:
return "Shoulders";
case invslot::slotArms:
return "Arms";
case invslot::slotBack:
return "Back";
case invslot::slotWrist1:
return "Wrist 1";
case invslot::slotWrist2:
return "Wrist 2";
case invslot::slotRange:
return "Range";
case invslot::slotHands:
return "Hands";
case invslot::slotPrimary:
return "Primary";
case invslot::slotSecondary:
return "Secondary";
case invslot::slotFinger1:
return "Finger 1";
case invslot::slotFinger2:
return "Finger 2";
case invslot::slotChest:
return "Chest";
case invslot::slotLegs:
return "Legs";
case invslot::slotFeet:
return "Feet";
case invslot::slotWaist:
return "Waist";
case invslot::slotPowerSource:
return "Power Source";
case invslot::slotAmmo:
return "Ammo";
case invslot::slotGeneral1:
return "General 1";
case invslot::slotGeneral2:
return "General 2";
case invslot::slotGeneral3:
return "General 3";
case invslot::slotGeneral4:
return "General 4";
case invslot::slotGeneral5:
return "General 5";
case invslot::slotGeneral6:
return "General 6";
case invslot::slotGeneral7:
return "General 7";
case invslot::slotGeneral8:
return "General 8";
case invslot::slotGeneral9:
return "General 9";
case invslot::slotGeneral10:
return "General 10";
case invslot::slotCursor:
return "Cursor";
default:
return "Unknown Slot";
}
}
const char* Laurion::invslot::GetInvSlotName(int16 inv_type, int16 inv_slot)
{
if (inv_type == invtype::typePossessions)
return invslot::GetInvPossessionsSlotName(inv_slot);
int16 type_size = invtype::GetInvTypeSize(inv_type);
if (!type_size || inv_slot == invslot::SLOT_INVALID)
return "Invalid Slot";
if ((inv_slot + 1) >= type_size)
return "Unknown Slot";
static std::string ret_str;
ret_str = StringFormat("Slot %i", (inv_slot + 1));
return ret_str.c_str();
}
const char* Laurion::invbag::GetInvBagIndexName(int16 bag_index)
{
if (bag_index == invbag::SLOT_INVALID)
return "Invalid Bag";
if (bag_index >= invbag::SLOT_COUNT)
return "Unknown Bag";
static std::string ret_str;
ret_str = StringFormat("Bag %i", (bag_index + 1));
return ret_str.c_str();
}
const char* Laurion::invaug::GetInvAugIndexName(int16 aug_index)
{
if (aug_index == invaug::SOCKET_INVALID)
return "Invalid Augment";
if (aug_index >= invaug::SOCKET_COUNT)
return "Unknown Augment";
static std::string ret_str;
ret_str = StringFormat("Augment %i", (aug_index + 1));
return ret_str.c_str();
}
+355
View File
@@ -0,0 +1,355 @@
/* EQEMu: Everquest Server Emulator
Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.net)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY except by those people which sell it, which
are required to give you total support for your newly bought product;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef COMMON_LAURION_LIMITS_H
#define COMMON_LAURION_LIMITS_H
#include "../types.h"
#include "../emu_versions.h"
#include "../skills.h"
namespace Laurion
{
const int16 IINVALID = -1;
const int16 INULL = 0;
namespace inventory {
inline EQ::versions::ClientVersion GetInventoryRef() { return EQ::versions::ClientVersion::Laurion; }
const bool ConcatenateInvTypeLimbo = false;
const bool AllowOverLevelEquipment = true;
const bool AllowEmptyBagInBag = true;
const bool AllowClickCastFromBag = true;
} /*inventory*/
namespace invtype {
inline EQ::versions::ClientVersion GetInvTypeRef() { return EQ::versions::ClientVersion::Laurion; }
namespace enum_ {
enum InventoryTypes : int16 {
typePossessions = INULL,
typeBank,
typeSharedBank,
typeTrade,
typeWorld,
typeLimbo,
typeTribute,
typeTrophyTribute,
typeGuildTribute,
typeMerchant,
typeDeleted,
typeCorpse,
typeBazaar,
typeInspect,
typeRealEstate,
typeViewMODPC,
typeViewMODBank,
typeViewMODSharedBank,
typeViewMODLimbo,
typeAltStorage,
typeArchived,
typeMail,
typeGuildTrophyTribute,
typeKrono,
typeOther,
typeMercenaryItems,
typeViewModMercenaryItems,
typeMountKeyRingItems,
typeViewModMountKeyRingItems,
typeIllusionKeyRingItems,
typeViewModIllusionKeyRingItems,
typeFamiliarKeyRingItems,
typeViewModFamiliarKeyRingItems,
typeHeroForgeKeyRingItems,
typeViewModHeroForgeKeyRingItems,
typeTeleportationKeyRingItems,
typeViewModTeleportationKeyRingItems,
typeOverflow,
typeDragonHoard,
typeTradeskillDepot,
typeGuildTradeskillDepot
};
} // namespace enum_
using namespace enum_;
const int16 POSSESSIONS_SIZE = 34;
const int16 BANK_SIZE = 24;
const int16 SHARED_BANK_SIZE = 2;
const int16 TRADE_SIZE = 8;
const int16 WORLD_SIZE = 10;
const int16 LIMBO_SIZE = 36;
const int16 TRIBUTE_SIZE = 5;
const int16 TROPHY_TRIBUTE_SIZE = 0;//unknown
const int16 GUILD_TRIBUTE_SIZE = 2;//unverified
const int16 MERCHANT_SIZE = 200;
const int16 DELETED_SIZE = 0;//unknown - "Recovery Tab"
const int16 CORPSE_SIZE = POSSESSIONS_SIZE;
const int16 BAZAAR_SIZE = 200;
const int16 INSPECT_SIZE = 23;
const int16 REAL_ESTATE_SIZE = 0;//unknown
const int16 VIEW_MOD_PC_SIZE = POSSESSIONS_SIZE;
const int16 VIEW_MOD_BANK_SIZE = BANK_SIZE;
const int16 VIEW_MOD_SHARED_BANK_SIZE = SHARED_BANK_SIZE;
const int16 VIEW_MOD_LIMBO_SIZE = LIMBO_SIZE;
const int16 ALT_STORAGE_SIZE = 0;//unknown - "Shroud Bank"
const int16 ARCHIVED_SIZE = 0;//unknown
const int16 MAIL_SIZE = 0;//unknown
const int16 GUILD_TROPHY_TRIBUTE_SIZE = 0;//unknown
const int16 KRONO_SIZE = 0;//unknown
const int16 OTHER_SIZE = 0;//unknown
const int16 TRADE_NPC_SIZE = 4; // defined by implication
const int16 TYPE_INVALID = IINVALID;
const int16 TYPE_BEGIN = typePossessions;
const int16 TYPE_END = typeOther;
const int16 TYPE_COUNT = (TYPE_END - TYPE_BEGIN) + 1;
int16 GetInvTypeSize(int16 inv_type);
const char* GetInvTypeName(int16 inv_type);
bool IsInvTypePersistent(int16 inv_type);
} /*invtype*/
namespace invslot {
inline EQ::versions::ClientVersion GetInvSlotRef() { return EQ::versions::ClientVersion::Laurion; }
namespace enum_ {
enum InventorySlots : int16 {
slotCharm = INULL,
slotEar1,
slotHead,
slotFace,
slotEar2,
slotNeck,
slotShoulders,
slotArms,
slotBack,
slotWrist1,
slotWrist2,
slotRange,
slotHands,
slotPrimary,
slotSecondary,
slotFinger1,
slotFinger2,
slotChest,
slotLegs,
slotFeet,
slotWaist,
slotPowerSource,
slotAmmo,
slotGeneral1,
slotGeneral2,
slotGeneral3,
slotGeneral4,
slotGeneral5,
slotGeneral6,
slotGeneral7,
slotGeneral8,
slotGeneral9,
slotGeneral10,
slotGeneral11,
slotGeneral12,
slotCursor
};
constexpr int16 format_as(InventorySlots slot) { return static_cast<int16>(slot); }
} // namespace enum_
using namespace enum_;
const int16 SLOT_INVALID = IINVALID;
const int16 SLOT_BEGIN = INULL;
const int16 POSSESSIONS_BEGIN = slotCharm;
const int16 POSSESSIONS_END = slotCursor;
const int16 POSSESSIONS_COUNT = (POSSESSIONS_END - POSSESSIONS_BEGIN) + 1;
const int16 EQUIPMENT_BEGIN = slotCharm;
const int16 EQUIPMENT_END = slotAmmo;
const int16 EQUIPMENT_COUNT = (EQUIPMENT_END - EQUIPMENT_BEGIN) + 1;
//We support more if enabled but for now lets leave it at the 10 slots
const int16 GENERAL_BEGIN = slotGeneral1;
const int16 GENERAL_END = slotGeneral10;
const int16 GENERAL_COUNT = (GENERAL_END - GENERAL_BEGIN) + 1;
const int16 BONUS_BEGIN = invslot::slotCharm;
const int16 BONUS_STAT_END = invslot::slotPowerSource;
const int16 BONUS_SKILL_END = invslot::slotAmmo;
const int16 CORPSE_BEGIN = invslot::slotGeneral1;
const int16 CORPSE_END = invslot::slotGeneral1 + invslot::slotCursor;
const uint64 EQUIPMENT_BITMASK = 0x00000000007FFFFF;
const uint64 GENERAL_BITMASK = 0x00000007FF800000;
const uint64 CURSOR_BITMASK = 0x0000000800000000;
const uint64 POSSESSIONS_BITMASK = (EQUIPMENT_BITMASK | GENERAL_BITMASK | CURSOR_BITMASK); // based on 36-slot count (Laurion+)
const uint64 CORPSE_BITMASK = (GENERAL_BITMASK | CURSOR_BITMASK | (EQUIPMENT_BITMASK << 36)); // based on 36-slot count (Laurion+)
const char* GetInvPossessionsSlotName(int16 inv_slot);
const char* GetInvSlotName(int16 inv_type, int16 inv_slot);
} /*invslot*/
namespace invbag {
inline EQ::versions::ClientVersion GetInvBagRef() { return EQ::versions::ClientVersion::Laurion; }
const int16 SLOT_INVALID = IINVALID;
const int16 SLOT_BEGIN = INULL;
const int16 SLOT_END = 9; //254;
const int16 SLOT_COUNT = 10; //255; // server Size will be 255..unsure what actual client is (test)
const char* GetInvBagIndexName(int16 bag_index);
} /*invbag*/
namespace invaug {
inline EQ::versions::ClientVersion GetInvAugRef() { return EQ::versions::ClientVersion::Laurion; }
const int16 SOCKET_INVALID = IINVALID;
const int16 SOCKET_BEGIN = INULL;
const int16 SOCKET_END = 5;
const int16 SOCKET_COUNT = 6;
const char* GetInvAugIndexName(int16 aug_index);
} /*invaug*/
namespace item {
inline EQ::versions::ClientVersion GetItemRef() { return EQ::versions::ClientVersion::Laurion; }
//enum Unknown : int { // looks like item class..but, RoF has it too - nothing in UF-
// Unknown1 = 0,
// Unknown2 = 1,
// Unknown3 = 2,
// Unknown4 = 5 // krono?
//};
enum ItemPacketType : int {
ItemPacketMerchant = 0x64,
ItemPacketTradeView = 0x65,
ItemPacketLoot = 0x66,
ItemPacketTrade = 0x67,
//looks like they added something at 0x68 that didn't exist before and shifted everything after it up by 1
ItemPacketUnknown068 = 0x68, //Not sure but it seems to deal with the cursor somehow.
ItemPacketCharInventory = 0x6A, //Rof 0x69 -> Larion 0x6a (requires translation)
ItemPacketLimbo = 0x6B, //0x6A -> 0x6B
ItemPacketWorldContainer = 0x6C,
ItemPacketTributeItem = 0x6D,
ItemPacketGuildTribute = 0x6E,
ItemPacketCharmUpdate = 0x6f,
ItemPacketRecovery = 0x72,
ItemPacketParcel = 0x74,
ItemPacketUnknown075 = 0x75, //Not sure but uses a lot of the same logic as the trade and char inventory types
ItemPacketOverflow = 0x76,
ItemPacketDragonHoard = 0x77,
ItemPacketTradeskill = 0x78,
ItemPacketTradeskillDepot = 0x79,
ItemPacketInvalid = 0xFF
};
} /*item*/
namespace profile {
inline EQ::versions::ClientVersion GetProfileRef() { return EQ::versions::ClientVersion::Laurion; }
const int16 BANDOLIERS_SIZE = 20; // number of bandolier instances
const int16 BANDOLIER_ITEM_COUNT = 4; // number of equipment slots in bandolier instance
const int16 POTION_BELT_SIZE = 5;
const int16 SKILL_ARRAY_SIZE = 100;
} /*profile*/
namespace constants {
inline EQ::versions::ClientVersion GetConstantsRef() { return EQ::versions::ClientVersion::Laurion; }
const EQ::expansions::Expansion EXPANSION = EQ::expansions::Expansion::LS;
const uint32 EXPANSION_BIT = EQ::expansions::bitLS;
const uint32 EXPANSIONS_MASK = EQ::expansions::maskLS;
const size_t CHARACTER_CREATION_LIMIT = 12;
const size_t SAY_LINK_BODY_SIZE = 56;
const uint32 MAX_GUILD_ID = 50000;
} /*constants*/
namespace behavior {
inline EQ::versions::ClientVersion GetBehaviorRef() { return EQ::versions::ClientVersion::Laurion; }
const bool CoinHasWeight = false;
} /*behavior*/
namespace skills {
inline EQ::versions::ClientVersion GetSkillsRef() { return EQ::versions::ClientVersion::Laurion; }
const size_t LastUsableSkill = EQ::skills::Skill2HPiercing;
} /*skills*/
namespace spells {
inline EQ::versions::ClientVersion GetSkillsRef() { return EQ::versions::ClientVersion::Laurion; }
enum class CastingSlot : uint32 {
Gem1 = 0,
Gem2 = 1,
Gem3 = 2,
Gem4 = 3,
Gem5 = 4,
Gem6 = 5,
Gem7 = 6,
Gem8 = 7,
Gem9 = 8,
Gem10 = 9,
Gem11 = 10,
Gem12 = 11,
MaxGems = 18, // fallacy..only 12 slot are useable...
Item = 12,
Discipline = 13,
AltAbility = 0xFF
};
const int SPELL_ID_MAX = 71999;
const int SPELLBOOK_SIZE = 1120;
const int SPELL_GEM_COUNT = static_cast<uint32>(CastingSlot::MaxGems);
const int SPELL_GEM_RECAST_TIMER = 15;
const int LONG_BUFFS = 42;
const int SHORT_BUFFS = 30;
const int DISC_BUFFS = 1;
const int TOTAL_BUFFS = LONG_BUFFS + SHORT_BUFFS + DISC_BUFFS;
const int NPC_BUFFS = 400;
const int PET_BUFFS = NPC_BUFFS;
const int MERC_BUFFS = LONG_BUFFS;
} /*spells*/
}; /* Laurion */
#endif /*COMMON_LAURION_LIMITS_H*/
+96
View File
@@ -0,0 +1,96 @@
//list of packets we need to encode on the way out:
E(OP_Action)
E(OP_Animation)
E(OP_ApplyPoison)
E(OP_AugmentInfo)
E(OP_BeginCast)
E(OP_BlockedBuffs)
E(OP_Buff)
E(OP_BuffCreate)
E(OP_CancelTrade)
E(OP_CastSpell)
E(OP_ChannelMessage)
E(OP_CharInventory)
E(OP_ClickObjectAction)
E(OP_ClientUpdate)
E(OP_Consider)
E(OP_Damage)
E(OP_Death)
E(OP_DeleteCharge)
E(OP_DeleteItem)
E(OP_DeleteSpawn)
E(OP_DisciplineUpdate)
E(OP_ExpansionInfo)
E(OP_ExpUpdate)
E(OP_FormattedMessage)
E(OP_GMTraining)
E(OP_GMTrainSkillConfirm)
E(OP_GroundSpawn)
E(OP_HPUpdate)
E(OP_Illusion)
E(OP_ItemPacket)
E(OP_LogServer)
E(OP_ManaChange)
E(OP_MobHealth)
E(OP_MoneyOnCorpse)
E(OP_MoveItem)
E(OP_NewSpawn)
E(OP_NewZone)
E(OP_OnLevelMessage)
E(OP_PlayerProfile)
E(OP_RemoveBlockedBuffs)
E(OP_RespondAA)
E(OP_RequestClientZoneChange)
E(OP_RecipeAutoCombine)
E(OP_SendAATable)
E(OP_SendCharInfo)
E(OP_SendMaxCharacters)
E(OP_SendMembership)
E(OP_SendMembershipDetails)
E(OP_SendZonepoints)
E(OP_ShopPlayerBuy)
E(OP_ShopPlayerSell)
E(OP_ShopRequest)
E(OP_SkillUpdate)
E(OP_SpecialMesg)
E(OP_SpawnAppearance)
E(OP_SpawnDoor)
E(OP_Stun)
E(OP_WearChange)
E(OP_ZoneChange)
E(OP_ZoneEntry)
E(OP_ZonePlayerToBind)
E(OP_ZoneSpawns)
//list of packets we need to decode on the way in:
D(OP_Animation)
D(OP_ApplyPoison)
D(OP_AugmentInfo)
D(OP_AugmentItem)
D(OP_BlockedBuffs)
D(OP_CastSpell)
D(OP_ChannelMessage)
D(OP_ClientUpdate)
D(OP_ClickDoor)
D(OP_Consider)
D(OP_ConsiderCorpse)
D(OP_DeleteItem)
D(OP_EnterWorld)
D(OP_GMTraining)
D(OP_GroupDisband)
D(OP_GroupInvite)
D(OP_GroupInvite2)
D(OP_MoveItem)
D(OP_RemoveBlockedBuffs)
D(OP_SetServerFilter)
D(OP_ShopPlayerBuy)
D(OP_ShopPlayerSell)
D(OP_ShopRequest)
D(OP_SpawnAppearance)
D(OP_TradeSkillCombine)
D(OP_WearChange)
D(OP_ZoneEntry)
D(OP_ZoneChange)
#undef E
#undef D
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -26,7 +26,7 @@
#include "sod.h"
#include "rof.h"
#include "rof2.h"
#include "laurion.h"
void RegisterAllPatches(EQStreamIdentifier &into)
{
@@ -36,6 +36,7 @@ void RegisterAllPatches(EQStreamIdentifier &into)
UF::Register(into);
RoF::Register(into);
RoF2::Register(into);
Laurion::Register(into);
}
void ReloadAllPatches()
@@ -46,4 +47,5 @@ void ReloadAllPatches()
UF::Reload();
RoF::Reload();
RoF2::Reload();
Laurion::Reload();
}
+31 -1
View File
@@ -2680,7 +2680,7 @@ namespace RoF2
{
float instrument_mod = 0.0f;
uint8 effect_type = emu->buffs[r].effect_type;
uint32 player_id = emu->buffs[r].player_id;;
uint32 player_id = emu->buffs[r].player_id;
if (emu->buffs[r].spellid != 0xFFFF && emu->buffs[r].spellid != 0)
{
@@ -3602,6 +3602,21 @@ namespace RoF2
FINISH_ENCODE();
}
ENCODE(OP_ShopRequest)
{
ENCODE_LENGTH_EXACT(MerchantClick_Struct);
SETUP_DIRECT_ENCODE(MerchantClick_Struct, structs::MerchantClick_Struct);
OUT(npc_id);
OUT(player_id);
OUT(command);
OUT(rate);
OUT(tab_display);
eq->unknown02 = emu->unknown020;
FINISH_ENCODE();
}
ENCODE(OP_SkillUpdate)
{
@@ -5993,6 +6008,21 @@ namespace RoF2
FINISH_DIRECT_DECODE();
}
DECODE(OP_ShopRequest)
{
DECODE_LENGTH_EXACT(structs::MerchantClick_Struct);
SETUP_DIRECT_DECODE(MerchantClick_Struct, structs::MerchantClick_Struct);
IN(npc_id);
IN(player_id);
IN(command);
IN(rate);
IN(tab_display);
emu->unknown020 = 0;
FINISH_DIRECT_DECODE();
}
DECODE(OP_Save)
{
DECODE_LENGTH_EXACT(structs::Save_Struct);
+2
View File
@@ -118,6 +118,7 @@ E(OP_SendZonepoints)
E(OP_SetGuildRank)
E(OP_ShopPlayerBuy)
E(OP_ShopPlayerSell)
E(OP_ShopRequest)
E(OP_SkillUpdate)
E(OP_SomeItemPacketMaybe)
E(OP_SpawnAppearance)
@@ -203,6 +204,7 @@ D(OP_Save)
D(OP_SetServerFilter)
D(OP_ShopPlayerBuy)
D(OP_ShopPlayerSell)
D(OP_ShopRequest)
D(OP_ShopSendParcel)
D(OP_Trader)
D(OP_TraderBuy)
+11
View File
@@ -74,6 +74,11 @@ void PathManager::LoadPaths()
m_patch_path = fs::relative(fs::path{m_server_path + "/" + c->PatchDir}).string();
}
// patches
if (File::Exists(fs::path{ m_server_path + "/" + c->OpcodeDir }.string())) {
m_opcode_path = fs::relative(fs::path{ m_server_path + "/" + c->OpcodeDir }).string();
}
// shared_memory_path
if (File::Exists(fs::path{m_server_path + "/" + c->SharedMemDir}.string())) {
m_shared_memory_path = fs::relative(fs::path{ m_server_path + "/" + c->SharedMemDir }).string();
@@ -89,6 +94,7 @@ void PathManager::LoadPaths()
LogInfo("lua_modules path [{}]", m_lua_modules_path);
LogInfo("maps path [{}]", m_maps_path);
LogInfo("patches path [{}]", m_patch_path);
LogInfo("opcode path [{}]", m_opcode_path);
LogInfo("plugins path [{}]", m_plugins_path);
LogInfo("quests path [{}]", m_quests_path);
LogInfo("shared_memory path [{}]", m_shared_memory_path);
@@ -129,6 +135,11 @@ const std::string &PathManager::GetPatchPath() const
return m_patch_path;
}
const std::string &PathManager::GetOpcodePath() const
{
return m_opcode_path;
}
const std::string &PathManager::GetLuaModulesPath() const
{
return m_lua_modules_path;
+2
View File
@@ -13,6 +13,7 @@ public:
[[nodiscard]] const std::string &GetLuaModulesPath() const;
[[nodiscard]] const std::string &GetMapsPath() const;
[[nodiscard]] const std::string &GetPatchPath() const;
[[nodiscard]] const std::string &GetOpcodePath() const;
[[nodiscard]] const std::string &GetPluginsPath() const;
[[nodiscard]] const std::string &GetQuestsPath() const;
[[nodiscard]] const std::string &GetServerPath() const;
@@ -24,6 +25,7 @@ private:
std::string m_lua_modules_path;
std::string m_maps_path;
std::string m_patch_path;
std::string m_opcode_path;
std::string m_plugins_path;
std::string m_quests_path;
std::string m_server_path;
@@ -236,6 +236,10 @@ public:
)
);
if (buyers.empty()) {
return all_entries;
}
std::vector<std::string> char_ids{};
for (auto const &bl : buyers) {
char_ids.push_back((std::to_string(bl.char_id)));
+4
View File
@@ -120,6 +120,10 @@ public:
}
DeleteWhere(db, fmt::format("`char_id` = '{}';", char_id));
if (buy_line_ids.empty()) {
return false;
}
BaseBuyerBuyLinesRepository::DeleteWhere(
db,
fmt::format("`id` IN({})", Strings::Implode(", ", buy_line_ids))
+12 -10
View File
@@ -164,37 +164,35 @@ public:
return UpdateOne(db, m);
}
static Trader GetItemBySerialNumber(Database &db, uint32 serial_number)
static Trader GetItemBySerialNumber(Database &db, uint32 serial_number, uint32 trader_id)
{
Trader e{};
const auto trader_item = GetWhere(
db,
fmt::format("`item_sn` = '{}' LIMIT 1", serial_number)
fmt::format("`char_id` = '{}' AND `item_sn` = '{}' LIMIT 1", trader_id, serial_number)
);
if (trader_item.empty()) {
return e;
}
else {
return trader_item.at(0);
}
return trader_item.at(0);
}
static Trader GetItemBySerialNumber(Database &db, std::string serial_number)
static Trader GetItemBySerialNumber(Database &db, std::string serial_number, uint32 trader_id)
{
Trader e{};
auto sn = Strings::ToUnsignedBigInt(serial_number);
const auto trader_item = GetWhere(
db,
fmt::format("`item_sn` = '{}' LIMIT 1", sn)
fmt::format("`char_id` = '{}' AND `item_sn` = '{}' LIMIT 1", trader_id, sn)
);
if (trader_item.empty()) {
return e;
}
else {
return trader_item.at(0);
}
return trader_item.at(0);
}
static int UpdateActiveTransaction(Database &db, uint32 id, bool status)
@@ -217,6 +215,10 @@ public:
delete_ids.push_back(std::to_string(e.id));
}
if (delete_ids.empty()) {
return 0;
}
return DeleteWhere(db, fmt::format("`id` IN({})", Strings::Implode(",", delete_ids)));
}
};
+5
View File
@@ -338,6 +338,9 @@ RULE_STRING(World, IPExemptionZones, "", "Comma-delimited list of zones to exclu
RULE_STRING(World, MOTD, "", "Server MOTD sent on login, change from empty to have this be used instead of variables table 'motd' value")
RULE_STRING(World, Rules, "", "Server Rules, change from empty to have this be used instead of variables table 'rules' value, lines are pipe (|) separated, example: A|B|C")
RULE_BOOL(World, EnableAutoLogin, false, "Enables or disables auto login of characters, allowing people to log characters in directly from loginserver to ingame")
RULE_BOOL(World, EnablePVPRegions, true, "Enables or disables PVP Regions automatically setting your PVP flag")
RULE_STRING(World, SupportedClients, "", "Comma-delimited list of clients to restrict to. Supported values are Titanium | SoF | SoD | UF | RoF | RoF2. Example: Titanium,RoF2")
RULE_INT(World, Id, 100, "Used by later clients to create GUIDs, expected to be Unique to the world but ultimately not that important")
RULE_CATEGORY_END()
RULE_CATEGORY(Zone)
@@ -517,6 +520,7 @@ RULE_BOOL(Spells, SnareOverridesSpeedBonuses, false, "Enabling will allow snares
RULE_INT(Spells, TargetedAOEMaxTargets, 4, "Max number of targets a Targeted AOE spell can cast on. Set to 0 for no limit.")
RULE_INT(Spells, PointBlankAOEMaxTargets, 0, "Max number of targets a Point-Blank AOE spell can cast on. Set to 0 for no limit.")
RULE_INT(Spells, DefaultAOEMaxTargets, 0, "Max number of targets that an AOE spell which does not meet other descriptions can cast on. Set to 0 for no limit.")
RULE_BOOL(Spells, AllowFocusOnSkillDamageSpells, false, "Allow focus effects 185, 459, and 482 to enhance SkillAttack spell effect 193")
RULE_CATEGORY_END()
RULE_CATEGORY(Combat)
@@ -678,6 +682,7 @@ 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_STRING(NPC, ExcludedFaceTargetRaces, "52,72,73,141,233,328,329,372,376,377,378,379,380,381,382,383,404,422,423,424,425,426,428,429,445,449,460,462,463,500,501,502,503,504,505,506,507,508,509,510,511,513,514,515,516,533,534,535,536,537,538,539,540,541,542,543,544,545,546,550,551,552,553,554,555,556,557,567,573,577,586,589,590,591,592,593,595,596,599,601,616,619,621,628,629,630,633,634,635,636,665,683,684,685,691,692,693,694,702,703,705,706,707,710,711,714,720,2250,2254", "Race IDs excluded from facing target when hailed")
RULE_CATEGORY_END()
RULE_CATEGORY(Aggro)
+1
View File
@@ -1945,6 +1945,7 @@ struct ServerOP_GuildMessage_Struct {
struct TraderMessaging_Struct {
uint32 action;
uint32 zone_id;
uint32 instance_id;
uint32 trader_id;
uint32 entity_id;
char trader_name[64];
+4 -3
View File
@@ -83,7 +83,8 @@ struct ActivityInformation {
if (zone_ids.empty()) {
return true;
}
bool found_zone = std::find(zone_ids.begin(), zone_ids.end(), zone_id) != zone_ids.end();
bool found_zone = std::any_of(zone_ids.begin(), zone_ids.end(),
[zone_id](int id) { return id <= 0 || id == zone_id; });
return found_zone && (zone_version == version || zone_version == -1);
}
@@ -100,7 +101,7 @@ struct ActivityInformation {
out.WriteInt32(activity_type == TaskActivityType::GiveCash ? 1 : goal_count);
out.WriteLengthString(skill_list); // used in SkillOn objective type string, "-1" for none
out.WriteLengthString(spell_list); // used in CastOn objective type string, "0" for none
out.WriteString(zones); // used in objective zone column and task select "begins in" (may have multiple, "0" for "unknown zone", empty for "ALL")
out.WriteString(zones); // used in ui zone columns and task select "begins in" (may have multiple, invalid id for "Unknown Zone", empty for "ALL")
}
else
{
@@ -114,7 +115,7 @@ struct ActivityInformation {
out.WriteString(description_override);
if (client_version >= EQ::versions::ClientVersion::RoF) {
out.WriteString(zones); // serialized again after description (seems unused)
out.WriteString(zones); // target zone version internal id (unused client side)
}
}
+2 -2
View File
@@ -25,7 +25,7 @@
// Build variables
// these get injected during the build pipeline
#define CURRENT_VERSION "22.56.3-dev" // always append -dev to the current version for custom-builds
#define CURRENT_VERSION "22.60.0-dev" // always append -dev to the current version for custom-builds
#define LOGIN_VERSION "0.8.0"
#define COMPILE_DATE __DATE__
#define COMPILE_TIME __TIME__
@@ -42,7 +42,7 @@
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
*/
#define CURRENT_BINARY_DATABASE_VERSION 9284
#define CURRENT_BINARY_DATABASE_VERSION 9285
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9045
#endif
+2
View File
@@ -251,4 +251,6 @@ private:
scalar m_value;
};
using ref = reference;
} // namespace perlbind
+4 -3
View File
@@ -28,8 +28,8 @@ struct pusher
++m_pushed;
}
void push(const std::string& value) { mPUSHp(value.c_str(), value.size()); ++m_pushed; }
void push(scalar value) { mPUSHs(value.release()); ++m_pushed; };
void push(reference value) { mPUSHs(value.release()); ++m_pushed; };
void push(scalar value) { mPUSHs(value.release()); ++m_pushed; }
void push(reference value) { mPUSHs(value.release()); ++m_pushed; }
void push(array value)
{
@@ -38,7 +38,8 @@ struct pusher
for (int i = 0; i < count; ++i)
{
// mortalizes one reference to array element to avoid copying
PUSHs(sv_2mortal(SvREFCNT_inc(value[i].sv())));
SV** sv = av_fetch(static_cast<AV*>(value), i, true);
mPUSHs(SvREFCNT_inc(*sv));
}
m_pushed += count;
}
+1 -1
View File
@@ -242,7 +242,7 @@ struct read_as<hash>
static bool check(PerlInterpreter* my_perl, int i, int ax, int items)
{
int remaining = items - i;
return remaining > 0 && remaining % 2 == 0 && SvTYPE(ST(i)) == SVt_PV;
return remaining > 0 && remaining % 2 == 0 && SvTYPE(ST(i)) < SVt_PVAV;
}
static hash get(PerlInterpreter* my_perl, int i, int ax, int items)
+1 -1
View File
@@ -1,7 +1,7 @@
#pragma once
constexpr int perlbind_version_major = 1;
constexpr int perlbind_version_minor = 0;
constexpr int perlbind_version_minor = 1;
constexpr int perlbind_version_patch = 0;
constexpr int perlbind_version()
+25 -3
View File
@@ -573,10 +573,13 @@ void Client::SendExpansionPacketData(PlayerLoginReply_Struct& plrs)
{
SerializeBuffer buf;
//from eqlsstr_us.txt id of each expansion, excluding 'Everquest'
int ExpansionLookup[20] = { 3007, 3008, 3009, 3010, 3012,
int ExpansionLookup[30] = { 3007, 3008, 3009, 3010, 3012,
3014, 3031, 3033, 3036, 3040,
3045, 3046, 3047, 3514, 3516,
3518, 3520, 3522, 3524 };
3518, 3520, 3522, 3524, 3526,
3528, 3530, 3532, 3534, 3535,
3536, 3537, 3538, 3539, 3540
};
if (server.options.IsDisplayExpansions()) {
@@ -584,7 +587,26 @@ void Client::SendExpansionPacketData(PlayerLoginReply_Struct& plrs)
int32_t expansion = server.options.GetMaxExpansions();
int32_t owned_expansion = (expansion << 1) | 1;
if (m_client_version == cv_sod) {
if (m_client_version == cv_laurion) {
buf.WriteInt32(0x00);
buf.WriteInt32(0x00);
buf.WriteInt16(0x00);
buf.WriteInt32(30);
for (int i = 0; i < 30; i++)
{
buf.WriteUInt32(i + 1);
buf.WriteUInt8(1);
buf.WriteInt32(ExpansionLookup[i]);
buf.WriteInt32(6046);
buf.WriteUInt32(0xFFFFFFFF);
buf.WriteUInt32(0);
}
auto out = std::make_unique<EQApplicationPacket>(OP_LoginExpansionPacketData, buf);
m_connection->QueuePacket(out.get());
}
else if (m_client_version == cv_sod) {
// header info of packet. Requires OP_LoginExpansionPacketData=0x0031 to be in login_opcodes_sod.conf
buf.WriteInt32(0x00);
+96 -29
View File
@@ -7,6 +7,79 @@ extern bool run_server;
#include "../common/eqemu_logsys.h"
#include "../common/misc.h"
#include "../common/path_manager.h"
#include "../common/file.h"
void CheckTitaniumOpcodeFile(const std::string &path) {
if (File::Exists(path)) {
return;
}
auto f = fopen(path.c_str(), "w");
if (f) {
fprintf(f, "#EQEmu Public Login Server OPCodes\n");
fprintf(f, "OP_SessionReady=0x0001\n");
fprintf(f, "OP_Login=0x0002\n");
fprintf(f, "OP_ServerListRequest=0x0004\n");
fprintf(f, "OP_PlayEverquestRequest=0x000d\n");
fprintf(f, "OP_PlayEverquestResponse=0x0021\n");
fprintf(f, "OP_ChatMessage=0x0016\n");
fprintf(f, "OP_LoginAccepted=0x0017\n");
fprintf(f, "OP_ServerListResponse=0x0018\n");
fprintf(f, "OP_Poll=0x0029\n");
fprintf(f, "OP_EnterChat=0x000f\n");
fprintf(f, "OP_PollResponse=0x0011\n");
fclose(f);
}
}
void CheckSoDOpcodeFile(const std::string& path) {
if (File::Exists(path)) {
return;
}
auto f = fopen(path.c_str(), "w");
if (f) {
fprintf(f, "#EQEmu Public Login Server OPCodes\n");
fprintf(f, "OP_SessionReady=0x0001\n");
fprintf(f, "OP_Login=0x0002\n");
fprintf(f, "OP_ServerListRequest=0x0004\n");
fprintf(f, "OP_PlayEverquestRequest=0x000d\n");
fprintf(f, "OP_PlayEverquestResponse=0x0022\n");
fprintf(f, "OP_ChatMessage=0x0017\n");
fprintf(f, "OP_LoginAccepted=0x0018\n");
fprintf(f, "OP_ServerListResponse=0x0019\n");
fprintf(f, "OP_Poll=0x0029\n");
fprintf(f, "OP_LoginExpansionPacketData=0x0031\n");
fprintf(f, "OP_EnterChat=0x000f\n");
fprintf(f, "OP_PollResponse=0x0011\n");
fclose(f);
}
}
void CheckLaurionOpcodeFile(const std::string& path) {
if (File::Exists(path)) {
return;
}
auto f = fopen(path.c_str(), "w");
if (f) {
fprintf(f, "#EQEmu Public Login Server OPCodes\n");
fprintf(f, "OP_SessionReady=0x0001\n");
fprintf(f, "OP_Login=0x0002\n");
fprintf(f, "OP_ServerListRequest=0x0004\n");
fprintf(f, "OP_PlayEverquestRequest=0x000d\n");
fprintf(f, "OP_PlayEverquestResponse=0x0022\n");
fprintf(f, "OP_ChatMessage=0x0017\n");
fprintf(f, "OP_LoginAccepted=0x0018\n");
fprintf(f, "OP_ServerListResponse=0x0019\n");
fprintf(f, "OP_Poll=0x0029\n");
fprintf(f, "OP_EnterChat=0x000f\n");
fprintf(f, "OP_PollResponse=0x0011\n");
fprintf(f, "OP_SystemFingerprint=0x0016\n");
fprintf(f, "OP_ExpansionList=0x0030\n");
fclose(f);
}
}
ClientManager::ClientManager()
{
@@ -19,14 +92,12 @@ ClientManager::ClientManager()
std::string opcodes_path = fmt::format(
"{}/{}",
path.GetServerPath(),
server.config.GetVariableString(
"client_configuration",
"titanium_opcodes",
"login_opcodes.conf"
)
path.GetOpcodePath(),
"login_opcodes.conf"
);
CheckTitaniumOpcodeFile(opcodes_path);
if (!titanium_ops->LoadOpcodes(opcodes_path.c_str())) {
LogError(
"ClientManager fatal error: couldn't load opcodes for Titanium file [{0}]",
@@ -58,14 +129,12 @@ ClientManager::ClientManager()
opcodes_path = fmt::format(
"{}/{}",
path.GetServerPath(),
server.config.GetVariableString(
"client_configuration",
"sod_opcodes",
"login_opcodes.conf"
)
path.GetOpcodePath(),
"login_opcodes_sod.conf"
);
CheckSoDOpcodeFile(opcodes_path);
if (!sod_ops->LoadOpcodes(opcodes_path.c_str())) {
LogError(
"ClientManager fatal error: couldn't load opcodes for SoD file {0}",
@@ -89,42 +158,40 @@ ClientManager::ClientManager()
}
);
int larion_port = server.config.GetVariableInt("client_configuration", "larion_port", 15900);
int laurion_port = server.config.GetVariableInt("client_configuration", "laurion_port", 15900);
EQStreamManagerInterfaceOptions larion_opts(larion_port, false, false);
EQStreamManagerInterfaceOptions laurion_opts(laurion_port, false, false);
larion_stream = new EQ::Net::EQStreamManager(larion_opts);
larion_ops = new RegularOpcodeManager;
laurion_stream = new EQ::Net::EQStreamManager(laurion_opts);
laurion_ops = new RegularOpcodeManager;
opcodes_path = fmt::format(
"{}/{}",
path.GetServerPath(),
server.config.GetVariableString(
"client_configuration",
"larion_opcodes",
"login_opcodes.conf"
)
path.GetOpcodePath(),
"login_opcodes_laurion.conf"
);
if (!larion_ops->LoadOpcodes(opcodes_path.c_str())) {
CheckLaurionOpcodeFile(opcodes_path);
if (!laurion_ops->LoadOpcodes(opcodes_path.c_str())) {
LogError(
"ClientManager fatal error: couldn't load opcodes for Larion file [{0}]",
server.config.GetVariableString("client_configuration", "larion_opcodes", "login_opcodes.conf")
"ClientManager fatal error: couldn't load opcodes for Laurion file [{0}]",
server.config.GetVariableString("client_configuration", "laurion_opcodes", "login_opcodes.conf")
);
run_server = false;
}
larion_stream->OnNewConnection(
laurion_stream->OnNewConnection(
[this](std::shared_ptr<EQ::Net::EQStream> stream) {
LogInfo(
"New Larion client connection from [{0}:{1}]",
"New Laurion client connection from [{0}:{1}]",
long2ip(stream->GetRemoteIP()),
stream->GetRemotePort()
);
stream->SetOpcodeManager(&larion_ops);
Client* c = new Client(stream, cv_larion);
stream->SetOpcodeManager(&laurion_ops);
Client* c = new Client(stream, cv_laurion);
clients.push_back(c);
}
);
+2 -2
View File
@@ -55,8 +55,8 @@ private:
EQ::Net::EQStreamManager *titanium_stream;
OpcodeManager *sod_ops;
EQ::Net::EQStreamManager *sod_stream;
OpcodeManager *larion_ops;
EQ::Net::EQStreamManager* larion_stream;
OpcodeManager *laurion_ops;
EQ::Net::EQStreamManager* laurion_stream;
};
#endif
+1 -1
View File
@@ -84,7 +84,7 @@ struct PlayEverquestResponse_Struct {
enum LSClientVersion {
cv_titanium,
cv_sod,
cv_larion
cv_laurion
};
enum LSClientStatus {
+58 -15
View File
@@ -977,8 +977,7 @@ bool WorldServer::ValidateWorldServerAdminLogin(
return false;
}
void WorldServer::SerializeForClientServerList(SerializeBuffer& out, bool use_local_ip, LSClientVersion version) const
{
void WorldServer::SerializeForClientServerListLegacy(class SerializeBuffer& out, bool use_local_ip) const {
// see LoginClientServerData_Struct
if (use_local_ip) {
out.WriteString(GetLocalIP());
@@ -987,10 +986,6 @@ void WorldServer::SerializeForClientServerList(SerializeBuffer& out, bool use_lo
out.WriteString(GetRemoteIP());
}
if (version == cv_larion) {
out.WriteUInt32(9000);
}
switch (GetServerListID()) {
case LS::ServerType::Legends:
out.WriteInt32(LS::ServerTypeFlags::Legends);
@@ -1002,15 +997,8 @@ void WorldServer::SerializeForClientServerList(SerializeBuffer& out, bool use_lo
out.WriteInt32(LS::ServerTypeFlags::Standard);
break;
}
if (version == cv_larion) {
auto server_id = GetServerId();
//if this is 0, the client will not show the server in the list
out.WriteUInt32(1);
out.WriteUInt32(server_id);
}
else {
out.WriteUInt32(GetServerId());
}
out.WriteUInt32(GetServerId());
out.WriteString(GetServerLongName());
out.WriteString("us"); // country code
@@ -1032,6 +1020,61 @@ void WorldServer::SerializeForClientServerList(SerializeBuffer& out, bool use_lo
out.WriteUInt32(GetPlayersOnline());
}
void WorldServer::SerializeForClientServerListLaurion(class SerializeBuffer& out, bool use_local_ip) const {
if (use_local_ip) {
out.WriteString(GetLocalIP());
}
else {
out.WriteString(GetRemoteIP());
}
out.WriteUInt32(9000);
out.WriteUInt32(0);
uint32_t flags = 32; //all servers i saw had this set
switch (GetServerListID()) {
case LS::ServerType::Legends:
flags += LS::ServerTypeFlags::Legends;
break;
case LS::ServerType::Preferred:
flags += LS::ServerTypeFlags::Preferred;
break;
default:
flags += LS::ServerTypeFlags::Standard;
break;
}
out.WriteUInt32(flags);
out.WriteUInt32(GetServerId());
out.WriteString(GetServerLongName());
out.WriteString("EN");
out.WriteString("US");
if (GetStatus() < 0) {
if (GetZonesBooted() == 0) {
out.WriteInt32(LS::ServerStatusFlags::Down);
}
else {
out.WriteInt32(LS::ServerStatusFlags::Locked);
}
}
else {
out.WriteInt32(LS::ServerStatusFlags::Up);
}
out.WriteUInt32(GetPlayersOnline());
}
void WorldServer::SerializeForClientServerList(SerializeBuffer& out, bool use_local_ip, LSClientVersion version) const
{
if (version == cv_laurion) {
SerializeForClientServerListLaurion(out, use_local_ip);
}
else {
SerializeForClientServerListLegacy(out, use_local_ip);
}
}
/**
* @param in_server_list_id
* @return
+4 -1
View File
@@ -149,7 +149,10 @@ public:
bool HandleNewLoginserverRegisteredOnly(Database::DbWorldRegistration &world_registration);
bool HandleNewLoginserverInfoUnregisteredAllowed(Database::DbWorldRegistration &world_registration);
private:
void SerializeForClientServerListLegacy(class SerializeBuffer& out, bool use_local_ip) const;
void SerializeForClientServerListLaurion(class SerializeBuffer& out, bool use_local_ip) const;
public:
void SerializeForClientServerList(class SerializeBuffer& out, bool use_local_ip, LSClientVersion version) const;
private:
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "eqemu-server",
"version": "22.56.3",
"version": "22.60.0",
"repository": {
"type": "git",
"url": "https://github.com/EQEmu/Server.git"
+699
View File
@@ -0,0 +1,699 @@
# ShowEQ Import Notes:
# ZERO THE FILE first
# perl -pi -e 's/0x[0-9a-fA-F]{4}/0x0000/g' opcodes.conf
# Unknown Mapping:
# OP_Action2 -> OP_Damage
# OP_EnvDamage -> OP_Damage ---> might have been a one time mistake
# Name Differences:
# OP_CancelInvite -> OP_GroupCancelInvite
# OP_GMFind -> OP_FindPersonRequest
# OP_CommonMessage -> OP_ChannelMessage
OP_Unknown=0x0000
OP_ExploreUnknown=0x0000 # used for unknown explorer
# world packets
# Required to reach Char Select:
OP_SendLoginInfo=0x2fca
OP_ApproveWorld=0x0000
OP_LogServer=0x6d4d
OP_SendCharInfo=0x832
OP_ExpansionInfo=0x066d
OP_EnterWorld=0x6691
OP_PostEnterWorld=0x2062
OP_World_Client_CRC1=0x74c8
OP_World_Client_CRC2=0x3984
OP_World_Client_CRC3=0x6516
OP_SendSpellChecksum=0x0000
OP_SendSkillCapsChecksum=0x0000
# Character Select Related:
OP_SendMaxCharacters=0x13af
OP_SendMembership=0x2aca
OP_SendMembershipDetails=0x2608
OP_CharacterCreateRequest=0x2df4
OP_CharacterCreate=0x6a3c
OP_DeleteCharacter=x67d7
OP_RandomNameGenerator=0x49d9
OP_ApproveName=0x11e5
OP_MOTD=0x0be4
OP_SetChatServer=0x0000
OP_SetChatServer2=0x2726
OP_ZoneServerInfo=0x2273
OP_WorldComplete=0x195c
OP_WorldUnknown001=0x2049
OP_FloatListThing=0x66fd
# Reasons for Disconnect:
OP_ZoneUnavail=0x582d
OP_WorldClientReady=0x7ed8
OP_CharacterStillInZone=0x0000
OP_WorldChecksumFailure=0x0000
OP_WorldLoginFailed=0x0000
OP_WorldLogout=0x0000
OP_WorldLevelTooHigh=0x0000
OP_CharInacessable=0x0000
OP_UserCompInfo=0x0000
OP_SendExeChecksum=0x0000
OP_SendBaseDataChecksum=0x0000
# Zone in opcodes
OP_AckPacket=0x77c9
OP_ZoneEntry=0x784a
OP_ReqNewZone=0x3895
OP_NewZone=0x4341
OP_ZoneSpawns=0x17d9
OP_PlayerProfile=0x1c76
OP_TimeOfDay=0x3736
OP_LevelUpdate=0x0eb2
OP_Stamina=0x1563
OP_RequestClientZoneChange=0x0191
OP_ZoneChange=0x17a3
OP_LockoutTimerInfo=0x0000
OP_ZoneServerReady=0x0000
OP_ZoneInUnknown=0x0000
OP_LogoutReply=0x0000
OP_PreLogoutReply=0x0000
# Required to fully log in
OP_SpawnAppearance=0x4eb0
OP_ChangeSize=0x2fdc
OP_Weather=0x6fe6
OP_ReqClientSpawn=0x6732
OP_SpawnDoor=0x4273
OP_GroundSpawn=0x49c5
OP_SendZonepoints=0x279f
OP_BlockedBuffs=0x4fdb
OP_RemoveBlockedBuffs=0x53cd
OP_ClearBlockedBuffs=0x5752
OP_WorldObjectsSent=0x2879
OP_SendExpZonein=0x02b4
OP_SendAATable=0x5f30
OP_ClearAA=0x3498
OP_ClearLeadershipAbilities=0x0000 #removed; leadership abilities are baked in and always on
OP_RespondAA=0x4c67
OP_UpdateAA=0x3b30
OP_SendAAStats=0x7d65 #i'll be honest i think this was removed at some point but this is the op at the spot in the list
OP_AAExpUpdate=0x642f #need to look into whether this has changed; exp did
OP_ExpUpdate=0x611d
OP_HPUpdate=0x775c
OP_ManaChange=0x700f
OP_TGB=0x0000 #removed; tgb is baked in and always on
OP_SpecialMesg=0x7d93
OP_CharInventory=0x21d6
OP_WearChange=0x44c0
OP_ClientUpdate=0x3a4b
OP_ClientReady=0x0831
OP_SetServerFilter=0x6b7f
# Guild Opcodes
OP_GuildsList=0x0000
OP_GuildMemberList=0x0000
OP_GuildMOTD=0x0000
OP_GetGuildMOTD=0x0000
OP_GetGuildMOTDReply=0x0000
OP_GuildMemberUpdate=0x0000
OP_GuildInvite=0x0000
OP_GuildRemove=0x0000
OP_GuildPeace=0x0000
OP_SetGuildMOTD=0x0000
OP_GuildWar=0x0000
OP_GuildLeader=0x0000
OP_GuildDelete=0x0000
OP_GuildInviteAccept=0x0000
OP_GuildDemote=0x0000
OP_GuildPromote=0x0000
OP_GuildPublicNote=0x0000
OP_GuildManageBanker=0x0000
OP_GuildBank=0x0000
OP_GuildBankItemList=0x0000
OP_SetGuildRank=0x0000
OP_GuildUpdate=0x0000
OP_GuildStatus=0x0000
OP_GuildCreate=0x0000
OP_GuildOpenGuildWindow=0x0000
OP_GuildMemberLevel=0x0000
OP_GuildMemberRankAltBanker=0x0000
OP_GuildMemberPublicNote=0x0000
OP_GuildMemberAdd=0x0000
OP_GuildMemberRename=0x0000
OP_GuildMemberDelete=0x0000
OP_GuildMemberDetails=0x0000
OP_GuildRenameGuild=0x0000
OP_LFGuild=0x0000
OP_GuildDeleteGuild=0x0000
# GM/Guide Opcodes
OP_GMServers=0x0000
OP_GMBecomeNPC=0x0000
OP_GMZoneRequest=0x0000
OP_GMZoneRequest2=0x0000
OP_GMGoto=0x0000
OP_GMSearchCorpse=0x0000
OP_GMHideMe=0x0000
OP_GMDelCorpse=0x0000
OP_GMApproval=0x0000
OP_GMToggle=0x0000
OP_GMSummon=0x0000
OP_GMEmoteZone=0x0000
OP_GMEmoteWorld=0x0000
OP_GMFind=0x0000
OP_GMKick=0x0000
OP_GMKill=0x0000
OP_GMNameChange=0x0000
OP_GMLastName=0x0000
# Misc Opcodes
OP_QueryUCSServerStatus=0x2570
OP_InspectRequest=0x0000
OP_InspectAnswer=0x0000
OP_InspectMessageUpdate=0x0000
OP_BeginCast=0x31f9
OP_ColoredText=0x0f3c
OP_ConsentResponse=0x3229
OP_MemorizeSpell=0x1d31
OP_LinkedReuse=0x7a8e
OP_SwapSpell=0x63c7
OP_CastSpell=0x325b
OP_Consider=0x53e3
OP_FormattedMessage=0x7f7f
OP_SimpleMessage=0x1943
OP_Buff=0x6ce5
OP_Illusion=0x5a3f
OP_MoneyOnCorpse=0x39d3
OP_RandomReply=0x6603
OP_DenyResponse=0x3f2c
OP_SkillUpdate=0x6735
OP_GMTrainSkillConfirm=0x6fbc
OP_RandomReq=0x528a
OP_Death=0x429a
OP_GMTraining=0x7c7a
OP_GMEndTraining=0x3ec6
OP_GMTrainSkill=0x54e1
OP_Animation=0x79c7
OP_Begging=0x7ded
OP_Consent=0x44fc
OP_ConsentDeny=0x3df9
OP_AutoFire=0x5280
OP_PetCommands=0x0000
OP_PetCommandState=0x0000
OP_PetHoTT=0x0000
OP_DeleteSpell=0x4281
OP_Surname=0x0000
OP_ClearSurname=0x0000
OP_FaceChange=0x0000
OP_SetFace=0x0000
OP_SenseHeading=0x6fcf
OP_Action=0x4c13
OP_ConsiderCorpse=0x6092
OP_HideCorpse=0x3f5c
OP_CorpseDrag=0x234d
OP_CorpseDrop=0x4342
OP_Bug=0x770b
OP_Feedback=0x0000
OP_Report=0x5bcf
OP_Damage=0x7d07
OP_ChannelMessage=0x6adc
OP_Assist=0x51f1
OP_AssistGroup=0x3f23
OP_MoveCoin=0x4e6a
OP_ZonePlayerToBind=0x5643
OP_KeyRing=0x0000
OP_WhoAllRequest=0x2a09
OP_WhoAllResponse=0x6404
OP_FriendsWho=0x75a2
OP_ConfirmDelete=0x4dd0
OP_Logout=0x771d
OP_Rewind=0x2b19
OP_TargetCommand=0x3b18
OP_Hide=0x1cdf
OP_Jump=0x6fa0
OP_Camp=0x326f
OP_Emote=0x0000
OP_SetRunMode=0x1449
OP_BankerChange=0x2a33
OP_TargetMouse=0x5741
OP_MobHealth=0x5b77
OP_InitialMobHealth=0x0000 # Unused?
OP_TargetHoTT=0x0000
OP_TargetBuffs=0x0000
OP_XTargetResponse=0x0000
OP_XTargetRequest=0x0000
OP_XTargetAutoAddHaters=0x0000
OP_XTargetOpen=0x0000
OP_XTargetOpenResponse=0x0000
OP_BuffCreate=0x27a1
OP_BuffRemoveRequest=0x4507
OP_DeleteSpawn=0x7712
OP_AutoAttack=0x3f03
OP_AutoAttack2=0x1c31
OP_Consume=0x5ef7
OP_MoveItem=0x11e3
OP_MoveMultipleItems=0x5205
OP_DeleteItem=0x0150
OP_DeleteCharge=0x1b7e
OP_ItemPacket=0x7d43
OP_ItemLinkResponse=0x0000
OP_ItemLinkClick=0x0000
OP_ItemPreview=0x0000
OP_NewSpawn=0x3ea8
OP_Track=0x5351
OP_TrackTarget=0x611a
OP_TrackUnknown=0x2c7a
OP_ClickDoor=0x733c
OP_MoveDoor=0x567c
OP_RemoveAllDoors=0x73e8
OP_EnvDamage=0x1ffd
OP_BoardBoat=0x7015
OP_LeaveBoat=0x2486
OP_ControlBoat=0x166f
OP_Forage=0x4c52
OP_SafeFallSuccess=0x6690
OP_RezzComplete=0x0000
OP_RezzRequest=0x0000
OP_RezzAnswer=0x0000
OP_Shielding=0x0000
OP_RequestDuel=0x0000
OP_MobRename=0x0000
OP_AugmentItem=0x3a1b
OP_WeaponEquip1=0x0000
OP_PlayerStateAdd=0x2178
OP_PlayerStateRemove=0x178e
OP_ApplyPoison=0x55b9
OP_Save=0x6da2
OP_TestBuff=0x0000
OP_CustomTitles=0x0000
OP_Split=0x7f6e
OP_YellForHelp=0x5fc9
OP_LoadSpellSet=0x0000
OP_Bandolier=0x0000
OP_PotionBelt=0x0000
OP_DuelDecline=0x0000
OP_DuelAccept=0x0000
OP_SaveOnZoneReq=0x3bfe
OP_ReadBook=0x51af
OP_Dye=0x0000
OP_InterruptCast=0x1d71
OP_AAAction=0x71BB
OP_LeadershipExpToggle=0x0000 #removed, these act as if all purchased now
OP_LeadershipExpUpdate=0x0000 #removed, these act as if all purchased now
OP_PurchaseLeadershipAA=0x0000 #removed, these act as if all purchased now
OP_UpdateLeadershipAA=0x0000 #removed, these act as if all purchased now
OP_MarkNPC=0x0000
OP_ClearNPCMarks=0x0000
OP_DelegateAbility=0x0000
OP_SetGroupTarget=0x0000
OP_Charm=0x66bb
OP_Stun=0x34be
OP_SendFindableNPCs=0x0000
OP_FindPersonRequest=0x0000
OP_FindPersonReply=0x0000
OP_Sound=0x2fa8
OP_CashReward=0x5e23
OP_PetBuffWindow=0x0000
OP_LevelAppearance=0x5d24
OP_Translocate=0x2772
OP_Sacrifice=0x2cbf
OP_PopupResponse=0x6be9
OP_OnLevelMessage=0x2a41
OP_AugmentInfo=0x2e11
OP_Petition=0x0000
OP_SomeItemPacketMaybe=0x0000
OP_PVPStats=0x0000
OP_PVPLeaderBoardRequest=0x0000
OP_PVPLeaderBoardReply=0x0000
OP_PVPLeaderBoardDetailsRequest=0x0000
OP_PVPLeaderBoardDetailsReply=0x0000
OP_RestState=0x0a92
OP_RespawnWindow=0x55ed
OP_LDoNButton=0x0000
OP_SetStartCity=0x0000
OP_VoiceMacroIn=0x703f
OP_VoiceMacroOut=0x72d1
OP_ItemViewUnknown=0x0000
OP_VetRewardsAvaliable=0x0000
OP_VetClaimRequest=0x0000
OP_VetClaimReply=0x0000
OP_DisciplineUpdate=0x6ce4
OP_DisciplineTimer=0x7436
OP_BecomeCorpse=0x0000 # Unused?
OP_Action2=0x0000 # Unused?
OP_MobUpdate=0x0000
OP_NPCMoveUpdate=0x0000
OP_CameraEffect=0x2f01
OP_SpellEffect=0x7378
OP_AddNimbusEffect=0x069f
OP_RemoveNimbusEffect=0x19ee
OP_AltCurrency=0x0000
OP_AltCurrencyMerchantRequest=0x0000
OP_AltCurrencyMerchantReply=0x0000
OP_AltCurrencyPurchase=0x0000
OP_AltCurrencySell=0x0000
OP_AltCurrencySellSelection=0x0000
OP_AltCurrencyReclaim=0x0000
OP_CrystalCountUpdate=0x0000
OP_CrystalCreate=0x0000
OP_CrystalReclaim=0x0000
OP_Untargetable=0x026f
OP_IncreaseStats=0x1005
OP_Weblink=0x16a3
OP_OpenContainer=0x6758
OP_Marquee=0x6bca
OP_ItemRecastDelay=0x547a
#OP_OpenInventory=0x0000 # Likely does not exist in RoF -U
OP_ResetAA=0x53c0
OP_Fling=0x3731
OP_CancelSneakHide=0x7452
OP_AggroMeterLockTarget=0x0000
OP_AggroMeterTargetInfo=0x0000
OP_AggroMeterUpdate=0x0000
OP_UnderWorld=0x4ca9 # clients sends up when they detect an underworld issue, might be useful for cheat detection
OP_KickPlayers=0x7154
OP_BookButton=0x014d
# Expeditions
OP_DzQuit=0x0000
OP_DzListTimers=0x0000
OP_DzAddPlayer=0x0000
OP_DzRemovePlayer=0x0000
OP_DzSwapPlayer=0x0000
OP_DzMakeLeader=0x0000
OP_DzPlayerList=0x0000
OP_DzExpeditionInvite=0x0000
OP_DzExpeditionInviteResponse=0x0000
OP_DzExpeditionInfo=0x0000
OP_DzExpeditionLockoutTimers=0x0000
OP_DzMemberList=0x0000
OP_DzMemberListName=0x0000
OP_DzMemberListStatus=0x0000
OP_DzSetLeaderName=0x0000
OP_DzExpeditionEndsWarning=0x0000
OP_DzCompass=0x0000
OP_DzChooseZone=0x0000
OP_DzChooseZoneReply=0x0000
# New Opcodes
OP_SpawnPositionUpdate=0x0000 # Actually OP_MobUpdate ?
OP_ManaUpdate=0x0000
OP_EnduranceUpdate=0x0000
OP_MobManaUpdate=0x0000
OP_MobEnduranceUpdate=0x0000
# Mercenary Opcodes
OP_MercenaryDataUpdateRequest=0x0000
OP_MercenaryDataUpdate=0x0000
OP_MercenaryDataRequest=0x0000
OP_MercenaryDataResponse=0x0000
OP_MercenaryHire=0x0000
OP_MercenaryDismiss=0x0000
OP_MercenaryTimerRequest=0x0000
OP_MercenaryTimer=0x0000
OP_MercenaryUnknown1=0x0000
OP_MercenaryCommand=0x0000
OP_MercenarySuspendRequest=0x0000
OP_MercenarySuspendResponse=0x0000
OP_MercenaryUnsuspendResponse=0x0000
# Looting
OP_LootRequest=0x60e5
OP_EndLootRequest=0x35f6
OP_LootItem=0x0856
OP_LootComplete=0x1f5e
# bazaar trader stuff:
OP_BazaarSearch=0x0000
OP_TraderDelItem=0x0000
OP_BecomeTrader=0x0000
OP_TraderShop=0x0000
OP_TraderBulkSend=0x0000
OP_Trader=0x0000
OP_Barter=0x0000
OP_BuyerItems=0x0000
OP_TraderBuy=0x0000
OP_ShopItem=0x0000
OP_BazaarInspect=0x0000
OP_Bazaar=0x0000
OP_TraderItemUpdate=0x0000
# pc/npc trading
OP_TradeRequest=0x7066
OP_TradeAcceptClick=0x34ad
OP_TradeRequestAck=0x1c6b
OP_TradeCoins=0x44fe
OP_FinishTrade=0x0ec6
OP_CancelTrade=0x5839
OP_TradeMoneyUpdate=0x5fb3
OP_MoneyUpdate=0x70bb
OP_TradeBusy=0x109f
# Sent after canceling trade or after closing tradeskill object
OP_FinishWindow=0x50d4
OP_FinishWindow2=0x6b03
# Sent on Live for what seems to be item existance verification
# Ex. Before Right Click Effect happens from items
OP_ItemVerifyRequest=0x2003
OP_ItemVerifyReply=0x43d0
OP_ItemAdvancedLoreText=0x0000
# merchant stuff
OP_ShopPlayerSell=0x6489
OP_ShopRequest=0x840
OP_ShopEnd=0x74bb
OP_ShopEndConfirm=0x2ed1
OP_ShopPlayerBuy=0x625e
OP_ShopDelItem=0x4ce4
OP_ShopSendParcel=0x0f16
OP_ShopDeleteParcel=0x4e2a
OP_ShopRetrieveParcel=0x27d1
OP_ShopParcelIcon=0x4f27
# tradeskill stuff:
OP_ClickObject=0x687e
OP_ClickObjectAction=0x110f
OP_ClearObject=0x6155
OP_RecipeDetails=0x01e7
OP_RecipesFavorite=0x0495
OP_RecipesSearch=0x2f4e
OP_RecipeReply=0x2cd2
OP_RecipeAutoCombine=0x5dba
OP_TradeSkillCombine=0x4ed8
# Tribute Packets:
OP_TributeUpdate=0x0000
OP_TributeTimer=0x0000
OP_SendTributes=0x0000
OP_RequestGuildTributes=0x0000
OP_TributeInfo=0x0000
OP_OpenTributeMaster=0x0000
OP_SelectTribute=0x0000
OP_TributeItem=0x0000
OP_TributeMoney=0x0000
OP_TributeToggle=0x0000
OP_TributePointUpdate=0x0000
OP_TributeNPC=0x0000
OP_GuildTributeInfo=0x0000
OP_OpenTributeReply=0x0000
OP_GuildTributeStatus=0x0000
OP_GuildSaveActiveTributes=0x0000
OP_GuildSendActiveTributes=0x0000
OP_GuildTributeToggleReq=0x0000
OP_GuildTributeToggleReply=0x0000
OP_GuildTributeFavorAndTimer=0x0000
OP_GuildTributeDonateItem=0x0000
OP_GuildTributeDonatePlat=0x0000
OP_GuildSelectTribute=0x0000
OP_GuildModifyBenefits=0x0000
OP_GuildOptInOut=0x0000
OP_SendGuildTributes=0x0000
OP_OpenGuildTributeMaster=0x0000
# Adventure packets:
OP_LeaveAdventure=0x0000
OP_AdventureFinish=0x0000
OP_AdventureInfoRequest=0x0000
OP_AdventureInfo=0x0000
OP_AdventureRequest=0x0000
OP_AdventureDetails=0x0000
OP_AdventureData=0x0000
OP_AdventureUpdate=0x0000
OP_AdventureMerchantRequest=0x0000
OP_AdventureMerchantResponse=0x0000
OP_AdventureMerchantPurchase=0x0000
OP_AdventureMerchantSell=0x0000
OP_AdventurePointsUpdate=0x0000
OP_AdventureStatsRequest=0x0000
OP_AdventureStatsReply=0x0000
OP_AdventureLeaderboardRequest=0x0000
OP_AdventureLeaderboardReply=0x0000
# Group Opcodes
OP_GroupDisband=0x78ef
OP_GroupInvite=0x1d90
OP_GroupFollow=0x0000
OP_GroupUpdate=0x0000
OP_GroupUpdateB=0x0000
OP_GroupCancelInvite=0x0000
OP_GroupAcknowledge=0x0000
OP_GroupDelete=0x0000
OP_CancelInvite=0x0000
OP_GroupFollow2=0x0000
OP_GroupInvite2=0x1e7e
OP_GroupDisbandYou=0x0000
OP_GroupDisbandOther=0x0000
OP_GroupLeaderChange=0x0000
OP_GroupRoles=0x0000
OP_GroupMakeLeader=0x0000
OP_DoGroupLeadershipAbility=0x0000
OP_GroupLeadershipAAUpdate=0x0000 # removed these act as if you have always purchased them
OP_GroupMentor=0x0000
OP_InspectBuffs=0x0000
# LFG/LFP Opcodes
OP_LFGCommand=0x0000
OP_LFGGetMatchesRequest=0x0000
OP_LFGGetMatchesResponse=0x0000
OP_LFPGetMatchesRequest=0x0000
OP_LFPGetMatchesResponse=0x0000
OP_LFPCommand=0x0000
OP_LFGAppearance=0x0000
OP_LFGResponse=0x0000
# Raid Opcodes
OP_RaidInvite=0x0000
OP_RaidUpdate=0x0000
OP_RaidJoin=0x0000
OP_RaidDelegateAbility=0x0000
OP_MarkRaidNPC=0x0000
OP_RaidClearNPCMarks=0x0000
# Button-push commands
OP_Taunt=0x5064
OP_CombatAbility=0xbf
OP_SenseTraps=0x579c
OP_PickPocket=0x53d1
OP_DisarmTraps=0x21bf
OP_Disarm=0x31e9
OP_Sneak=0x78a7
OP_Fishing=0x57cc
OP_InstillDoubt=0x57cc
OP_FeignDeath=0x14b8
OP_Mend=0x6b8
OP_Bind_Wound=0x650e
OP_LDoNOpen=0x448
OP_LDoNPickLock=0x61c8
OP_LDoNInspect=0xc1c
# Task packets
OP_TaskDescription=0x0000
OP_TaskActivity=0x0000
OP_CompletedTasks=0x0000
OP_TaskActivityComplete=0x0000
OP_AcceptNewTask=0x0000
OP_CancelTask=0x0000
OP_AvaliableTask=0x0000
OP_TaskHistoryRequest=0x0000
OP_TaskHistoryReply=0x0000
OP_DeclineAllTasks=0x0000
OP_TaskRequestTimer=0x0000
OP_TaskSelectWindow=0x0000
# Shared Tasks
OP_SharedTaskMemberList=0x0000 #
OP_SharedTaskRemovePlayer=0x0000 # /taskremoveplayer
OP_SharedTaskAddPlayer=0x0000 # /taskaddplayer
OP_SharedTaskMakeLeader=0x0000 # /taskmakeleader
OP_SharedTaskInvite=0x0000 # Dialog window
OP_SharedTaskInviteResponse=0x0000 # Dialog window response
OP_SharedTaskAcceptNew=0x0000 #
OP_SharedTaskMemberChange=0x0000 #
OP_TaskTimers=0x0000 # /tasktimers
OP_SharedTaskQuit=0x0000 # /taskquit
OP_SharedTaskSelectWindow=0x0000
OP_SharedTaskPlayerList=0x0000 # /taskplayerlist
# Title opcodes
OP_NewTitlesAvailable=0x0000
OP_RequestTitles=0x0000
OP_SendTitleList=0x0000
OP_SetTitle=0x0000
OP_SetTitleReply=0x0000
# mail opcodes
OP_Command=0x0000
OP_MailboxHeader=0x0000
OP_MailHeader=0x0000
OP_MailBody=0x0000
OP_NewMail=0x0000
OP_SentConfirm=0x0000
########### Below this point should not be needed ###########
# This section are all unknown in Titanium
OP_ForceFindPerson=0x0000
OP_LocInfo=0x0000
OP_ReloadUI=0x0000
OP_ItemName=0x0000
OP_ItemLinkText=0x0000
OP_MultiLineMsg=0x0000
OP_MendHPUpdate=0x0000
OP_TargetReject=0x0000
OP_SafePoint=0x0000
OP_ApproveZone=0x0000
OP_ZoneComplete=0x0000
OP_ClientError=0x0000
OP_DumpName=0x0000
OP_Heartbeat=0x0000
OP_CrashDump=0x0000
OP_LoginComplete=0x0000
# discovered opcodes not yet used:
OP_PickLockSuccess=0x0000
OP_PlayMP3=0x6451
OP_ReclaimCrystals=0x0000
OP_DynamicWall=0x0000
OP_OpenDiscordMerchant=0x0000
OP_DiscordMerchantInventory=0x0000
OP_GiveMoney=0x0000
OP_RequestKnowledgeBase=0x0000
OP_KnowledgeBase=0x0000
OP_SlashAdventure=0x0000 # /adventure
OP_BecomePVPPrompt=0x0000
OP_MoveLogRequest=0x0000 # gone I think
OP_MoveLogDisregard=0x0000 # gone I think
# named unknowns, to make looking for real unknown easier
OP_AnnoyingZoneUnknown=0x0000
OP_Some6ByteHPUpdate=0x0000 #seems to happen when you target group members
OP_QueryResponseThing=0x0000
# realityincarnate: these are just here to stop annoying several thousand byte packet dumps
#OP_LoginUnknown1=0x0000 # OP_SendSpellChecksum
#OP_LoginUnknown2=0x0000 # OP_SendSkillCapsChecksum
# Petition Opcodes
OP_PetitionSearch=0x0000 #search term for petition
OP_PetitionSearchResults=0x0000 #(list of?) matches from search
OP_PetitionSearchText=0x0000 #text results of search
OP_PetitionUpdate=0x0000
OP_PetitionCheckout=0x0000
OP_PetitionCheckIn=0x0000
OP_PetitionQue=0x0000
OP_PetitionUnCheckout=0x0000
OP_PetitionDelete=0x0000
OP_DeletePetition=0x0000
OP_PetitionResolve=0x0000
OP_PDeletePetition=0x0000
OP_PetitionBug=0x0000
OP_PetitionRefresh=0x0000
OP_PetitionCheckout2=0x0000
OP_PetitionViewPetition=0x0000
#aura related
OP_UpdateAura=0x0000
OP_RemoveTrap=0x0000
OP_Fingerprint=0x7a5b
@@ -176,6 +176,11 @@ namespace StreamParser.Common.Daybreak
case Opcode.SessionResponse:
if (_connect_code == 0)
{
//if(data.Length != 21)
//{
// return;
//}
_connect_code = BitConverter.ToUInt32(data.Slice(2, 4));
_encode_key = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data.Slice(6, 4)));
_crc_bytes = data[10];
@@ -443,27 +448,33 @@ namespace StreamParser.Common.Daybreak
private bool ValidateCRC(ReadOnlySpan<byte> p)
{
if (_crc_bytes == 0)
try
{
return true;
}
if (_crc_bytes == 0)
{
return true;
}
int actual = 0;
int calculated = _crc_generator.Calculate(p.Slice(0, p.Length - _crc_bytes), _encode_key);
switch (_crc_bytes)
int actual = 0;
int calculated = _crc_generator.Calculate(p.Slice(0, p.Length - _crc_bytes), _encode_key);
switch (_crc_bytes)
{
case 2:
actual = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(p.Slice(p.Length - 2, 2))) & 0xFFFF;
calculated = calculated & 0xFFFF;
break;
case 4:
actual = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(p.Slice(p.Length - 4, 4)));
break;
default:
return false;
}
return actual == calculated;
} catch(Exception ex)
{
case 2:
actual = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(p.Slice(p.Length - 2, 2))) & 0xFFFF;
calculated = calculated & 0xFFFF;
break;
case 4:
actual = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(p.Slice(p.Length - 4, 4)));
break;
default:
return false;
return false;
}
return actual == calculated;
}
private byte[] Decompress(byte[] p, int offset, int length)
@@ -47,7 +47,7 @@ namespace StreamParser.Common.Daybreak
private void OnPacketCapture(object sender, PacketCapture capture)
{
var raw = capture.GetPacket();
if (raw.LinkLayerType == PacketDotNet.LinkLayers.Ethernet)
if (raw.LinkLayerType == PacketDotNet.LinkLayers.Ethernet || raw.LinkLayerType == PacketDotNet.LinkLayers.Null)
{
var packet = PacketDotNet.Packet.ParsePacket(raw.LinkLayerType, raw.Data);
var ipPacket = packet.Extract<PacketDotNet.IPv4Packet>();
@@ -87,6 +87,12 @@ namespace StreamParser.Common.Daybreak
}
else if (data[0] == 0 && data[1] == Opcode.SessionRequest)
{
if(data.Length != 24)
{
_logger.LogTrace("Tossing packet, {0} was not the right size for a SessionRequest", data.Length);
return;
}
c = new Connection(this, srcAddr, srcPort, dstAddr, dstPort);
_connections.Add(c);
c.ProcessPacket(srcAddr, srcPort, packetTime, data);
@@ -57,42 +57,54 @@ namespace StreamParser
{
var args = Environment.GetCommandLineArgs();
CommandLine.Parser.Default.ParseArguments<ConsoleHostedServiceOptions>(args)
.WithParsed<ConsoleHostedServiceOptions>(o =>
{
_parser.OnNewConnection += OnNewConnection;
_parser.OnLostConnection += OnLostConnection;
foreach(var f in o.Input)
try
{
CommandLine.Parser.Default.ParseArguments<ConsoleHostedServiceOptions>(args)
.WithParsed<ConsoleHostedServiceOptions>(o =>
{
_logger.LogInformation("Parsing {0}...", f);
_parser.Parse(f);
}
_parser.OnNewConnection += OnNewConnection;
_parser.OnLostConnection += OnLostConnection;
foreach(var c in _connections)
{
if(o.Text)
foreach (var f in o.Input)
{
DumpConnectionToTextFile(c.Value, o.Output, o.Decrypt, o.DecompressOpcodes);
_logger.LogInformation("Parsing {0}...", f);
_parser.Parse(f);
}
}
_applicationLifetime.StopApplication();
})
.WithNotParsed<ConsoleHostedServiceOptions>(e =>
{
bool stops_processing = false;
foreach(var err in e)
{
_logger.LogError("Error: {0}", err.Tag);
stops_processing = stops_processing || err.StopsProcessing;
}
foreach (var c in _connections)
{
if (c.Value.ConnectionType == ConnectionType.Unknown && !o.DumpUnknownStreams)
{
continue;
}
if (o.Text)
{
DumpConnectionToTextFile(c.Value, o.Output, o.Decrypt, o.DecompressOpcodes);
}
}
if(stops_processing)
{
_applicationLifetime.StopApplication();
}
});
})
.WithNotParsed<ConsoleHostedServiceOptions>(e =>
{
bool stops_processing = false;
foreach (var err in e)
{
stops_processing = stops_processing || err.StopsProcessing || err is MissingRequiredOptionError;
}
if (stops_processing)
{
_applicationLifetime.StopApplication();
}
});
}
catch(Exception ex)
{
_logger.LogError(ex, "Error parsing command line arguments");
_applicationLifetime.StopApplication();
}
});
return Task.CompletedTask;
@@ -22,5 +22,8 @@ namespace StreamParser
[Option("decompress", Default = null, HelpText = "Which opcodes to attempt to decompress")]
public IEnumerable<int> DecompressOpcodes { get; set; }
[Option("enable-unknown-streams", Default = false, HelpText = "Enable dumping of unknown streams; they will be skipped otherwise.")]
public bool DumpUnknownStreams { get; set; }
}
}
+57 -5
View File
@@ -526,9 +526,27 @@ bool Client::HandleSendLoginInfoPacket(const EQApplicationPacket *app)
SendEnterWorld(cle->name());
SendPostEnterWorld();
if (!is_player_zoning) {
SendExpansionInfo();
SendCharInfo();
database.LoginIP(cle->AccountID(), long2ip(GetIP()));
const auto supported_clients = RuleS(World, SupportedClients);
bool skip_char_info = false;
if (!supported_clients.empty()) {
const std::string& name = EQ::versions::ClientVersionName(m_ClientVersion);
const auto& clients = Strings::Split(supported_clients, ",");
if (std::find(clients.begin(), clients.end(), name) == clients.end()) {
SendUnsupportedClientPacket(
fmt::format(
"Client Not In Supported List [{}]",
supported_clients
)
);
skip_char_info = true;
}
}
if (!skip_char_info) {
SendExpansionInfo();
SendCharInfo();
database.LoginIP(cle->AccountID(), long2ip(GetIP()));
}
}
cle->SetIP(GetIP());
@@ -1191,8 +1209,11 @@ bool Client::Process() {
}
if(connect.Check()){
SendGuildList();// Send OPCode: OP_GuildsList
SendApproveWorld();
if (!(m_ClientVersionBit & EQ::versions::maskLaurionAndLater)) {
SendGuildList();// Send OPCode: OP_GuildsList
SendApproveWorld();
}
connect.Disable();
}
@@ -2453,3 +2474,34 @@ void Client::SendGuildTributeOptInToggle(const GuildTributeMemberToggle *in)
QueuePacket(outapp);
safe_delete(outapp);
}
void Client::SendUnsupportedClientPacket(const std::string& message)
{
EQApplicationPacket packet(OP_SendCharInfo, sizeof(CharacterSelect_Struct) + sizeof(CharacterSelectEntry_Struct));
unsigned char* buff_ptr = packet.pBuffer;
auto cs = (CharacterSelect_Struct*) buff_ptr;
cs->CharCount = 1;
cs->TotalChars = 1;
buff_ptr += sizeof(CharacterSelect_Struct);
auto e = (CharacterSelectEntry_Struct*) buff_ptr;
strcpy(e->Name, message.c_str());
e->Race = Race::Human;
e->Class = Class::Warrior;
e->Level = 1;
e->ShroudClass = e->Class;
e->ShroudRace = e->Race;
e->Zone = std::numeric_limits<uint16>::max();
e->Instance = 0;
e->Gender = Gender::Male;
e->GoHome = 0;
e->Tutorial = 0;
e->Enabled = 0;
QueuePacket(&packet);
}
+1
View File
@@ -120,6 +120,7 @@ private:
EQStreamInterface* eqs;
bool CanTradeFVNoDropItem();
void RecordPossibleHack(const std::string& message);
void SendUnsupportedClientPacket(const std::string& message);
};
bool CheckCharCreateInfoSoF(CharCreate_Struct *cc);
+7
View File
@@ -294,6 +294,13 @@ bool WorldBoot::DatabaseLoadRoutines(int argc, char **argv)
database.ClearBuyerDetails();
LogInfo("Clearing buyer table details");
if (RuleB(Bots, Enabled)) {
LogInfo("Clearing [bot_pet_buffs] table of stale entries");
database.QueryDatabase(
"DELETE FROM bot_pet_buffs WHERE NOT EXISTS (SELECT * FROM bot_pets WHERE bot_pets.pets_index = bot_pet_buffs.pets_index)"
);
}
if (!content_db.LoadItems(hotfix_name)) {
LogError("Error: Could not load item data. But ignoring");
}
+15 -3
View File
@@ -1755,7 +1755,11 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
return;
}
zoneserver_list.SendPacket(Zones::BAZAAR, pack);
auto trader = client_list.FindCLEByCharacterID(in->trader_buy_struct.trader_id);
if (trader) {
zoneserver_list.SendPacket(trader->zone(), trader->instance(), pack);
}
break;
}
case ServerOP_BuyerMessaging: {
@@ -1775,12 +1779,20 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
break;
}
case Barter_SellItem: {
zoneserver_list.SendPacket(Zones::BAZAAR, pack);
auto buyer = client_list.FindCharacter(in->buyer_name);
if (buyer) {
zoneserver_list.SendPacket(buyer->zone(), buyer->instance(), pack);
}
break;
}
case Barter_FailedTransaction:
case Barter_BuyerTransactionComplete: {
zoneserver_list.SendPacket(in->zone_id, pack);
auto seller = client_list.FindCharacter(in->seller_name);
if (seller) {
zoneserver_list.SendPacket(seller->zone(), seller->instance(), pack);
}
break;
}
default:
+6
View File
@@ -54,6 +54,7 @@ SET(zone_sources
lua_buff.cpp
lua_corpse.cpp
lua_client.cpp
lua_database.cpp
lua_door.cpp
lua_encounter.cpp
lua_entity.cpp
@@ -110,6 +111,7 @@ SET(zone_sources
perl_bot.cpp
perl_buff.cpp
perl_client.cpp
perl_database.cpp
perl_doors.cpp
perl_entity.cpp
perl_expedition.cpp
@@ -135,6 +137,7 @@ SET(zone_sources
qglobals.cpp
queryserv.cpp
questmgr.cpp
quest_db.cpp
quest_parser_collection.cpp
raids.cpp
raycast_mesh.cpp
@@ -215,6 +218,7 @@ SET(zone_headers
lua_buff.h
lua_client.h
lua_corpse.h
lua_database.h
lua_door.h
lua_encounter.h
lua_entity.h
@@ -251,6 +255,7 @@ SET(zone_headers
pathfinder_interface.h
pathfinder_nav_mesh.h
pathfinder_null.h
perl_database.h
perlpacket.h
petitions.h
pets.h
@@ -260,6 +265,7 @@ SET(zone_headers
queryserv.h
quest_interface.h
questmgr.h
quest_db.h
quest_parser_collection.h
raids.h
raycast_mesh.h
+4 -1
View File
@@ -1578,7 +1578,10 @@ bool Bot::Process()
return false;
}
ScanCloseMobProcess();
if (m_scan_close_mobs_timer.Check()) {
entity_list.ScanCloseMobs(this);
}
SpellProcess();
if (tic_timer.Check()) {
+2 -2
View File
@@ -828,7 +828,7 @@ bool BotDatabase::LoadTimers(Bot* b)
BotTimer_Struct t{ };
for (const auto& e : l) {
if (t.timer_value < (Timer::GetCurrentTime() + t.recast_time)) {
if (e.timer_value < (Timer::GetCurrentTime() + e.recast_time)) {
t.timer_id = e.timer_id;
t.timer_value = e.timer_value;
t.recast_time = e.recast_time;
@@ -1451,7 +1451,7 @@ bool BotDatabase::DeletePetBuffs(const uint32 bot_id)
return true;
}
BotPetBuffsRepository::DeleteOne(database, saved_pet_index);
BotPetBuffsRepository::DeleteWhere(database, fmt::format("pets_index = {}", saved_pet_index));
return true;
}
+50 -8
View File
@@ -534,7 +534,7 @@ void Client::SendZoneInPackets()
if (GetLevel() >= 51)
SendAlternateAdvancementStats();
// Send exp packets
outapp = new EQApplicationPacket(OP_ExpUpdate, sizeof(ExpUpdate_Struct));
ExpUpdate_Struct* eu = (ExpUpdate_Struct*)outapp->pBuffer;
uint32 tmpxp1 = GetEXPForLevel(GetLevel() + 1);
@@ -1168,7 +1168,7 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s
}
}
if (message[0] == COMMAND_CHAR) {
if (message[0] == COMMAND_CHAR || message[0] == COMMAND_CHAR_NON_HASH) {
if (command_dispatch(this, message, false) == -2) {
if (parse->PlayerHasQuestSub(EVENT_COMMAND)) {
int i = parse->EventPlayer(EVENT_COMMAND, this, message, 0);
@@ -7518,7 +7518,7 @@ void Client::GarbleMessage(char *message, uint8 variance)
int delimiter_count = 0;
// Don't garble # commands
if (message[0] == COMMAND_CHAR || message[0] == BOT_COMMAND_CHAR) {
if (message[0] == COMMAND_CHAR || message[0] == COMMAND_CHAR_NON_HASH || message[0] == BOT_COMMAND_CHAR) {
return;
}
@@ -8593,6 +8593,44 @@ void Client::SendHPUpdateMarquee(){
SendMarqueeMessage(Chat::Yellow, 510, 0, 3000, 3000, health_update_notification);
}
void Client::SendMembership() {
if (m_ClientVersion >= EQ::versions::ClientVersion::Laurion) {
auto outapp = new EQApplicationPacket(OP_SendMembership, sizeof(Membership_Struct));
Membership_Struct* mc = (Membership_Struct*)outapp->pBuffer;
mc->membership = 2; //Hardcode to gold for now. We don't use anything else.
mc->races = 0x1ffff; // Available Races (4110 for silver)
mc->classes = 0x1ffff; // Available Classes (4614 for silver) - Was 0x101ffff
mc->entrysize = 21; // Number of membership setting entries below
mc->entries[0] = 0xffffffff; // Max AA Restriction
mc->entries[1] = 0xffffffff; // Max Level Restriction
mc->entries[2] = 0xffffffff; // Max Char Slots per Account (not used by client?)
mc->entries[3] = 0xffffffff; // 1 for Silver
mc->entries[4] = 0xffffffff; // Main Inventory Size (0xffffffff on Live for Gold, but limiting to 8 until 10 is supported)
mc->entries[5] = 0xffffffff; // Max Platinum per level
mc->entries[6] = 1; // 0 for Silver
mc->entries[7] = 1; // 0 for Silver
mc->entries[8] = 1; // 1 for Silver
mc->entries[9] = 0xffffffff; // Unknown - Maybe Loyalty Points every 12 hours? 60 per week for Silver
mc->entries[10] = 1; // 1 for Silver
mc->entries[11] = 0xffffffff; // Shared Bank Slots
mc->entries[12] = 0xffffffff; // Unknown - Maybe Max Active Tasks?
mc->entries[13] = 1; // 1 for Silver
mc->entries[14] = 1; // 0 for Silver
mc->entries[15] = 1; // 0 for Silver
mc->entries[16] = 1; // 1 for Silver
mc->entries[17] = 1; // 0 for Silver
mc->entries[18] = 1; // 0 for Silver
mc->entries[19] = 0xffffffff; // 0 for Silver
mc->entries[20] = 0xffffffff; // 0 for Silver
mc->exit_url_length = 0;
//mc->exit_url = 0; // Used on Live: "http://www.everquest.com/free-to-play/exit-silver"
QueuePacket(outapp);
safe_delete(outapp);
}
}
uint32 Client::GetMoney(uint8 type, uint8 subtype) {
uint32 value = 0;
@@ -8677,14 +8715,16 @@ int Client::GetAccountAge() {
void Client::CheckRegionTypeChanges()
{
if (!zone->HasWaterMap())
if (!zone->HasWaterMap()) {
return;
}
auto new_region = zone->watermap->ReturnRegionType(glm::vec3(m_Position));
// still same region, do nothing
if (last_region_type == new_region)
if (last_region_type == new_region) {
return;
}
// If we got out of water clear any water aggro for water only npcs
if (last_region_type == RegionTypeWater) {
@@ -8695,13 +8735,15 @@ void Client::CheckRegionTypeChanges()
last_region_type = new_region;
// PVP is the only state we need to keep track of, so we can just return now for PVP servers
if (RuleI(World, PVPSettings) > 0)
if (RuleI(World, PVPSettings) > 0) {
return;
}
if (last_region_type == RegionTypePVP)
if (last_region_type == RegionTypePVP && RuleB(World, EnablePVPRegions)) {
temp_pvp = true;
else if (temp_pvp)
} else if (temp_pvp) {
temp_pvp = false;
}
}
void Client::ProcessAggroMeter()
+1
View File
@@ -1786,6 +1786,7 @@ public:
void ResetHPUpdateTimer() { hpupdate_timer.Start(); }
void SendHPUpdateMarquee();
void SendMembership();
void CheckRegionTypeChanges();
+34 -26
View File
@@ -118,7 +118,6 @@ void MapOpcodes()
ConnectingOpcodes[OP_ZoneEntry] = &Client::Handle_Connect_OP_ZoneEntry;
// connected opcode handler assignments:
ConnectedOpcodes[OP_0x0193] = &Client::Handle_0x0193;
ConnectedOpcodes[OP_AAAction] = &Client::Handle_OP_AAAction;
ConnectedOpcodes[OP_AcceptNewTask] = &Client::Handle_OP_AcceptNewTask;
ConnectedOpcodes[OP_AdventureInfoRequest] = &Client::Handle_OP_AdventureInfoRequest;
@@ -1203,10 +1202,6 @@ void Client::Handle_Connect_OP_WorldObjectsSent(const EQApplicationPacket *app)
void Client::Handle_Connect_OP_ZoneComplete(const EQApplicationPacket *app)
{
auto outapp = new EQApplicationPacket(OP_0x0347, 0);
QueuePacket(outapp);
safe_delete(outapp);
return;
}
void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
@@ -1655,9 +1650,7 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
if ((m_pp.RestTimer > RuleI(Character, RestRegenTimeToActivate)) && (m_pp.RestTimer > RuleI(Character, RestRegenRaidTimeToActivate)))
m_pp.RestTimer = 0;
/* This checksum should disappear once dynamic structs are in... each struct strategy will do it */ // looks to be in place now
//CRC32::SetEQChecksum((unsigned char*)&m_pp, sizeof(PlayerProfile_Struct) - sizeof(m_pp.m_player_profile_version) - 4);
// m_pp.checksum = 0; // All server out-bound player profile packets are now translated - no need to waste cycles calculating this...
SendMembership();
outapp = new EQApplicationPacket(OP_PlayerProfile, sizeof(PlayerProfile_Struct));
@@ -1834,16 +1827,6 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
return;
}
// connected opcode handlers
void Client::Handle_0x0193(const EQApplicationPacket *app)
{
// Not sure what this opcode does. It started being sent when OP_ClientUpdate was
// changed to pump OP_ClientUpdate back out instead of OP_MobUpdate
// 2 bytes: 00 00
return;
}
void Client::Handle_OP_AAAction(const EQApplicationPacket *app)
{
LogAA("Received OP_AAAction");
@@ -2731,6 +2714,14 @@ void Client::Handle_OP_AltCurrencyReclaim(const EQApplicationPacket *app)
return;
}
if (IsTrader()) {
TraderEndTrader();
}
if (IsBuyer()) {
ToggleBuyerMode(false);
}
/* Item to Currency Storage */
if (reclaim->reclaim_flag == 1) {
uint32 removed = NukeItem(item_id, invWhereWorn | invWherePersonal | invWhereCursor);
@@ -4326,13 +4317,23 @@ void Client::Handle_OP_Camp(const EQApplicationPacket *app)
if (IsLFP())
worldserver.StopLFP(CharacterID());
if (GetGM())
{
OnDisconnect(true);
return;
if (ClientVersion() >= EQ::versions::ClientVersion::Laurion) {
if (!GetGM()) {
camp_timer.Start(29000, true);
}
auto outapp = new EQApplicationPacket(OP_Camp, 1);
FastQueuePacket(&outapp);
}
else {
if (GetGM())
{
OnDisconnect(true);
return;
}
camp_timer.Start(29000, true);
}
camp_timer.Start(29000, true);
return;
}
void Client::Handle_OP_CancelTask(const EQApplicationPacket *app)
@@ -5013,7 +5014,11 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) {
SetMoving(!(cy == m_Position.y && cx == m_Position.x));
CheckClientToNpcAggroTimer();
CheckScanCloseMobsMovingTimer();
if (m_mob_check_moving_timer.Check()) {
CheckScanCloseMobsMovingTimer();
}
CheckSendBulkClientPositionUpdate();
int32 new_animation = ppu->animation;
@@ -14634,7 +14639,10 @@ void Client::Handle_OP_ShopRequest(const EQApplicationPacket *app)
mco->rate = 1 / buy_cost_mod;
}
outapp->priority = 6;
if (m_ClientVersion >= EQ::versions::ClientVersion::Laurion) {
mco->player_id = GetID();
}
QueuePacket(outapp);
safe_delete(outapp);
-2
View File
@@ -21,8 +21,6 @@
void Handle_Connect_OP_ZoneComplete(const EQApplicationPacket *app);
void Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app);
/* Connected opcode handlers*/
void Handle_0x0193(const EQApplicationPacket *app);
void Handle_0x01e7(const EQApplicationPacket *app);
void Handle_OP_AAAction(const EQApplicationPacket *app);
void Handle_OP_AcceptNewTask(const EQApplicationPacket *app);
void Handle_OP_AdventureInfoRequest(const EQApplicationPacket *app);
+3 -1
View File
@@ -281,7 +281,9 @@ bool Client::Process() {
}
}
ScanCloseMobProcess();
if (m_scan_close_mobs_timer.Check()) {
entity_list.ScanCloseMobs(this);
}
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
+2 -2
View File
@@ -40,7 +40,7 @@ extern FastMath g_Math;
void CatchSignal(int sig_num);
int command_count; // how many commands we have
int command_count; // how many commands we have
// this is the pointer to the dispatch function, updated once
// init has been performed to point at the real function
@@ -96,7 +96,7 @@ int command_init(void)
command_add("augmentitem", "Force augments an item. Must have the augment item window open.", AccountStatus::GMImpossible, command_augmentitem) ||
command_add("ban", "[Character Name] [Reason] - Ban by character name", AccountStatus::GMLeadAdmin, command_ban) ||
command_add("bugs", "[Close|Delete|Review|Search|View] - Handles player bug reports", AccountStatus::QuestTroupe, command_bugs) ||
command_add("bot", "Type \"#bot help\" or \"^help\" to the see the list of available commands for bots.", AccountStatus::Player, command_bot) ||
(RuleB(Bots, Enabled) && command_add("bot", "Type \"#bot help\" or \"^help\" to the see the list of available commands for bots.", AccountStatus::Player, command_bot)) ||
command_add("camerashake", "[Duration (Milliseconds)] [Intensity (1-10)] - Shakes the camera on everyone's screen globally.", AccountStatus::QuestTroupe, command_camerashake) ||
command_add("castspell", "[Spell ID] [Instant (0 = False, 1 = True, Default is 1 if Unused)] - Cast a spell", AccountStatus::Guide, command_castspell) ||
command_add("chat", "[Channel ID] [Message] - Send a channel message to all zones", AccountStatus::GMMgmt, command_chat) ||
+1
View File
@@ -8,6 +8,7 @@ class Seperator;
#include <string>
#define COMMAND_CHAR '#'
#define COMMAND_CHAR_NON_HASH '$'
typedef void (*CmdFuncPtr)(Client *, const Seperator *);
+3 -1
View File
@@ -58,6 +58,7 @@ void perl_register_expedition_lock_messages();
void perl_register_bot();
void perl_register_buff();
void perl_register_merc();
void perl_register_database();
#endif // EMBPERL_XS_CLASSES
#endif // EMBPERL_XS
@@ -1185,6 +1186,7 @@ void PerlembParser::MapFunctions()
perl_register_bot();
perl_register_buff();
perl_register_merc();
perl_register_database();
#endif // EMBPERL_XS_CLASSES
}
@@ -1734,7 +1736,7 @@ void PerlembParser::ExportEventVariables(
case EVENT_PAYLOAD: {
Seperator sep(data);
ExportVar(package_name.c_str(), "payload_id", sep.arg[0]);
ExportVar(package_name.c_str(), "payload_value", sep.arg[1]);
ExportVar(package_name.c_str(), "payload_value", sep.argplus[1]);
break;
}
+12
View File
@@ -5978,6 +5978,16 @@ bool Perl__aretaskscompleted(perl::array task_ids)
return quest_manager.aretaskscompleted(v);
}
void Perl__SpawnCircle(uint32 npc_id, float x, float y, float z, float heading, float radius, uint32 points)
{
quest_manager.SpawnCircle(npc_id, glm::vec4(x, y, z, heading), radius, points);
}
void Perl__SpawnGrid(uint32 npc_id, float x, float y, float z, float heading, float spacing, uint32 spawn_count)
{
quest_manager.SpawnGrid(npc_id, glm::vec4(x, y, z, heading), spacing, spawn_count);
}
void perl_register_quest()
{
perl::interpreter perl(PERL_GET_THX);
@@ -6287,6 +6297,8 @@ void perl_register_quest()
package.add("SendMail", &Perl__SendMail);
package.add("SetAutoLoginCharacterNameByAccountID", &Perl__SetAutoLoginCharacterNameByAccountID);
package.add("SetRunning", &Perl__SetRunning);
package.add("SpawnCircle", &Perl__SpawnCircle);
package.add("SpawnGrid", &Perl__SpawnGrid);
package.add("activespeakactivity", &Perl__activespeakactivity);
package.add("activespeaktask", &Perl__activespeaktask);
package.add("activetasksinset", &Perl__activetasksinset);
+2
View File
@@ -21,6 +21,8 @@ Eglin
#include <perlbind/perlbind.h>
namespace perl = perlbind;
#undef connect
#undef bind
#undef Null
#ifdef WIN32
+21 -18
View File
@@ -2945,8 +2945,22 @@ void EntityList::RemoveAuraFromMobs(Mob *aura)
// entity list (zone wide)
void EntityList::ScanCloseMobs(Mob *scanning_mob)
{
if (!scanning_mob) {
return;
}
if (scanning_mob->GetID() <= 0) {
return;
}
float scan_range = RuleI(Range, MobCloseScanDistance) * RuleI(Range, MobCloseScanDistance);
// Reserve memory in m_close_mobs to avoid frequent re-allocations if not already reserved.
// Assuming mob_list.size() as an upper bound for reservation.
if (scanning_mob->m_close_mobs.bucket_count() < mob_list.size()) {
scanning_mob->m_close_mobs.reserve(mob_list.size());
}
scanning_mob->m_close_mobs.clear();
for (auto &e : mob_list) {
@@ -2957,28 +2971,17 @@ void EntityList::ScanCloseMobs(Mob *scanning_mob)
float distance = DistanceSquared(scanning_mob->GetPosition(), mob->GetPosition());
if (distance <= scan_range || mob->GetAggroRange() >= scan_range) {
scanning_mob->m_close_mobs.emplace(std::pair<uint16, Mob *>(mob->GetID(), mob));
// add self to other mobs close list
if (scanning_mob->GetID() > 0) {
bool has_mob = false;
for (auto &cm: mob->m_close_mobs) {
if (scanning_mob->GetID() == cm.first) {
has_mob = true;
break;
}
}
if (!has_mob) {
mob->m_close_mobs.insert(std::pair<uint16, Mob *>(scanning_mob->GetID(), scanning_mob));
}
// add mob to scanning_mob's close list and vice versa
// check if the mob is already in the close mobs list before inserting
if (mob->m_close_mobs.find(scanning_mob->GetID()) == mob->m_close_mobs.end()) {
mob->m_close_mobs[scanning_mob->GetID()] = scanning_mob;
}
scanning_mob->m_close_mobs[mob->GetID()] = mob;
}
}
LogAIScanCloseDetail(
"[{}] Scanning Close List | list_size [{}] moving [{}]",
LogAIScanClose(
"[{}] Scanning close list > list_size [{}] moving [{}]",
scanning_mob->GetCleanName(),
scanning_mob->m_close_mobs.size(),
scanning_mob->IsMoving() ? "true" : "false"
+18 -3
View File
@@ -657,7 +657,12 @@ void Client::SetEXP(ExpSource exp_source, uint64 set_exp, uint64 set_aaxp, bool
} else if (zone->IsHotzone()) {
Message(Chat::Experience, "You gain party experience (with a bonus)!");
} else {
MessageString(Chat::Experience, GAIN_GROUPXP);
if (m_ClientVersion >= EQ::versions::ClientVersion::Laurion) {
MessageString(Chat::Experience, GAIN_GROUPXP, exp_percent_message.c_str());
}
else {
MessageString(Chat::Experience, GAIN_GROUPXP);
}
}
} else if (IsRaidGrouped()) {
if (RuleI(Character, ShowExpValues) > 0) {
@@ -665,7 +670,12 @@ void Client::SetEXP(ExpSource exp_source, uint64 set_exp, uint64 set_aaxp, bool
} else if (zone->IsHotzone()) {
Message(Chat::Experience, "You gained raid experience (with a bonus)!");
} else {
MessageString(Chat::Experience, GAIN_RAIDEXP);
if (m_ClientVersion >= EQ::versions::ClientVersion::Laurion) {
MessageString(Chat::Experience, GAIN_RAIDEXP, exp_percent_message.c_str());
}
else {
MessageString(Chat::Experience, GAIN_RAIDEXP);
}
}
} else {
if (RuleI(Character, ShowExpValues) > 0) {
@@ -673,7 +683,12 @@ void Client::SetEXP(ExpSource exp_source, uint64 set_exp, uint64 set_aaxp, bool
} else if (zone->IsHotzone()) {
Message(Chat::Experience, "You gain experience (with a bonus)!");
} else {
MessageString(Chat::Experience, GAIN_XP);
if (m_ClientVersion >= EQ::versions::ClientVersion::Laurion) {
MessageString(Chat::Experience, GAIN_XP, exp_percent_message.c_str());
}
else {
MessageString(Chat::Experience, GAIN_XP);
}
}
}
}
+214
View File
@@ -0,0 +1,214 @@
#ifdef LUA_EQEMU
#include "lua_database.h"
#include "zonedb.h"
#include <luabind/luabind.hpp>
#include <luabind/adopt_policy.hpp>
// Luabind adopts the PreparedStmt wrapper object allocated with new and deletes it via GC
// Lua GC is non-deterministic so handles should be closed explicitly to free db resources
// Script errors/exceptions will hold resources until GC deletes the wrapper object
Lua_MySQLPreparedStmt* Lua_Database::Prepare(lua_State* L, std::string query)
{
return m_db ? new Lua_MySQLPreparedStmt(L, m_db->Prepare(std::move(query))) : nullptr;
}
void Lua_Database::Close()
{
m_db.reset();
}
// ---------------------------------------------------------------------------
void Lua_MySQLPreparedStmt::Close()
{
m_stmt.reset();
}
void Lua_MySQLPreparedStmt::Execute(lua_State* L)
{
if (m_stmt)
{
m_res = m_stmt->Execute();
}
}
void Lua_MySQLPreparedStmt::Execute(lua_State* L, luabind::object args)
{
if (m_stmt)
{
std::vector<mysql::PreparedStmt::param_t> inputs;
// iterate table until nil like ipairs to guarantee traversal order
for (int i = 1, type; (type = luabind::type(args[i])) != LUA_TNIL; ++i)
{
switch (type)
{
case LUA_TBOOLEAN:
inputs.emplace_back(luabind::object_cast<bool>(args[i]));
break;
case LUA_TNUMBER: // all numbers are doubles in lua before 5.3
inputs.emplace_back(luabind::object_cast<lua_Number>(args[i]));
break;
case LUA_TSTRING:
inputs.emplace_back(luabind::object_cast<const char*>(args[i]));
break;
case LUA_TTABLE: // let tables substitute for null since nils can't exist
inputs.emplace_back(nullptr);
break;
default:
break;
}
}
m_res = m_stmt->Execute(inputs);
}
}
void Lua_MySQLPreparedStmt::SetOptions(luabind::object table)
{
if (m_stmt)
{
mysql::StmtOptions opts = m_stmt->GetOptions();
if (luabind::type(table["buffer_results"]) == LUA_TBOOLEAN)
{
opts.buffer_results = luabind::object_cast<bool>(table["buffer_results"]);
}
if (luabind::type(table["use_max_length"]) == LUA_TBOOLEAN)
{
opts.use_max_length = luabind::object_cast<bool>(table["use_max_length"]);
}
m_stmt->SetOptions(opts);
}
}
static void PushValue(lua_State* L, const mysql::StmtColumn& col)
{
if (col.IsNull())
{
lua_pushnil(L); // clear entry in cache from any previous row
return;
}
// 64-bit ints are pushed as strings since lua 5.1 only has 53-bit precision
switch (col.Type())
{
case MYSQL_TYPE_TINY:
case MYSQL_TYPE_SHORT:
case MYSQL_TYPE_INT24:
case MYSQL_TYPE_LONG:
case MYSQL_TYPE_FLOAT:
case MYSQL_TYPE_DOUBLE:
lua_pushnumber(L, col.Get<lua_Number>().value());
break;
case MYSQL_TYPE_LONGLONG:
case MYSQL_TYPE_BIT:
case MYSQL_TYPE_TIME:
case MYSQL_TYPE_DATE:
case MYSQL_TYPE_DATETIME:
case MYSQL_TYPE_TIMESTAMP:
{
std::string str = col.GetStr().value();
lua_pushlstring(L, str.data(), str.size());
}
break;
default: // string types, push raw buffer to avoid copy
{
std::string_view str = col.GetStrView().value();
lua_pushlstring(L, str.data(), str.size());
}
break;
}
}
luabind::object Lua_MySQLPreparedStmt::FetchArray(lua_State* L)
{
auto row = m_stmt ? m_stmt->Fetch() : mysql::StmtRow();
if (!row)
{
return luabind::object();
}
// perf: bypass luabind operator[]
m_row_array.push(L);
for (const mysql::StmtColumn& col : row)
{
PushValue(L, col);
lua_rawseti(L, -2, col.Index() + 1);
}
lua_pop(L, 1);
return m_row_array;
}
luabind::object Lua_MySQLPreparedStmt::FetchHash(lua_State* L)
{
auto row = m_stmt ? m_stmt->Fetch() : mysql::StmtRow();
if (!row)
{
return luabind::object();
}
// perf: bypass luabind operator[]
m_row_hash.push(L);
for (const mysql::StmtColumn& col : row)
{
PushValue(L, col);
lua_setfield(L, -2, col.Name().c_str());
}
lua_pop(L, 1);
return m_row_hash;
}
int Lua_MySQLPreparedStmt::ColumnCount()
{
return m_res.ColumnCount();
}
uint64_t Lua_MySQLPreparedStmt::LastInsertID()
{
return m_res.LastInsertID();
}
uint64_t Lua_MySQLPreparedStmt::RowCount()
{
return m_res.RowCount();
}
uint64_t Lua_MySQLPreparedStmt::RowsAffected()
{
return m_res.RowsAffected();
}
luabind::scope lua_register_database()
{
return luabind::class_<Lua_Database>("Database")
.enum_("constants")
[(
luabind::value("Default", static_cast<int>(QuestDB::Connection::Default)),
luabind::value("Content", static_cast<int>(QuestDB::Connection::Content))
)]
.def(luabind::constructor<>())
.def(luabind::constructor<QuestDB::Connection>())
.def(luabind::constructor<QuestDB::Connection, bool>())
.def(luabind::constructor<const char*, const char*, const char*, const char*, uint32_t>())
.def("close", &Lua_Database::Close)
.def("prepare", &Lua_Database::Prepare, luabind::adopt(luabind::result)),
luabind::class_<Lua_MySQLPreparedStmt>("MySQLPreparedStmt")
.def("close", &Lua_MySQLPreparedStmt::Close)
.def("execute", static_cast<void(Lua_MySQLPreparedStmt::*)(lua_State*)>(&Lua_MySQLPreparedStmt::Execute))
.def("execute", static_cast<void(Lua_MySQLPreparedStmt::*)(lua_State*, luabind::object)>(&Lua_MySQLPreparedStmt::Execute))
.def("fetch", &Lua_MySQLPreparedStmt::FetchArray)
.def("fetch_array", &Lua_MySQLPreparedStmt::FetchArray)
.def("fetch_hash", &Lua_MySQLPreparedStmt::FetchHash)
.def("insert_id", &Lua_MySQLPreparedStmt::LastInsertID)
.def("num_fields", &Lua_MySQLPreparedStmt::ColumnCount)
.def("num_rows", &Lua_MySQLPreparedStmt::RowCount)
.def("rows_affected", &Lua_MySQLPreparedStmt::RowsAffected)
.def("set_options", &Lua_MySQLPreparedStmt::SetOptions);
}
#endif // LUA_EQEMU
+51
View File
@@ -0,0 +1,51 @@
#pragma once
#ifdef LUA_EQEMU
#include "quest_db.h"
#include "../common/mysql_stmt.h"
#include <luabind/object.hpp>
namespace luabind { struct scope; }
luabind::scope lua_register_database();
class Lua_MySQLPreparedStmt;
class Lua_Database : public QuestDB
{
public:
using QuestDB::QuestDB;
void Close();
Lua_MySQLPreparedStmt* Prepare(lua_State*, std::string query);
};
class Lua_MySQLPreparedStmt
{
public:
Lua_MySQLPreparedStmt(lua_State* L, mysql::PreparedStmt&& stmt)
: m_stmt(std::make_unique<mysql::PreparedStmt>(std::move(stmt)))
, m_row_array(luabind::newtable(L))
, m_row_hash(luabind::newtable(L)) {}
void Close();
void Execute(lua_State*);
void Execute(lua_State*, luabind::object args);
void SetOptions(luabind::object table_opts);
luabind::object FetchArray(lua_State*);
luabind::object FetchHash(lua_State*);
// StmtResult functions accessible through this class to simplify api
int ColumnCount();
uint64_t LastInsertID();
uint64_t RowCount();
uint64_t RowsAffected();
private:
std::unique_ptr<mysql::PreparedStmt> m_stmt;
mysql::StmtResult m_res = {};
luabind::object m_row_array; // perf: table cache for fetches
luabind::object m_row_hash;
};
#endif // LUA_EQEMU
+13 -3
View File
@@ -5635,6 +5635,16 @@ int lua_are_tasks_completed(luabind::object task_ids)
return quest_manager.aretaskscompleted(v);
}
void lua_spawn_circle(uint32 npc_id, float x, float y, float z, float heading, float radius, uint32 points)
{
quest_manager.SpawnCircle(npc_id, glm::vec4(x, y, z, heading), radius, points);
}
void lua_spawn_grid(uint32 npc_id, float x, float y, float z, float heading, float spacing, uint32 spawn_count)
{
quest_manager.SpawnGrid(npc_id, glm::vec4(x, y, z, heading), spacing, spawn_count);
}
#define LuaCreateNPCParse(name, c_type, default_value) do { \
cur = table[#name]; \
if(luabind::type(cur) != LUA_TNIL) { \
@@ -6442,6 +6452,8 @@ luabind::scope lua_register_general() {
luabind::def("send_parcel", &lua_send_parcel),
luabind::def("get_zone_uptime", &lua_get_zone_uptime),
luabind::def("are_tasks_completed", &lua_are_tasks_completed),
luabind::def("spawn_circle", &lua_spawn_circle),
luabind::def("spawn_grid", &lua_spawn_grid),
/*
Cross Zone
*/
@@ -6583,7 +6595,7 @@ luabind::scope lua_register_general() {
luabind::def("cross_zone_reset_activity_by_guild_id", &lua_cross_zone_reset_activity_by_guild_id),
luabind::def("cross_zone_reset_activity_by_expedition_id", &lua_cross_zone_reset_activity_by_expedition_id),
luabind::def("cross_zone_reset_activity_by_client_name", &lua_cross_zone_reset_activity_by_client_name),
luabind::def("cross_zone_set_entity_variable_by_client_name", &lua_cross_zone_set_entity_variable_by_client_name),
luabind::def("cross_zone_set_entity_variable_by_char_id", &lua_cross_zone_set_entity_variable_by_char_id),
luabind::def("cross_zone_set_entity_variable_by_group_id", &lua_cross_zone_set_entity_variable_by_group_id),
luabind::def("cross_zone_set_entity_variable_by_raid_id", &lua_cross_zone_set_entity_variable_by_raid_id),
luabind::def("cross_zone_set_entity_variable_by_guild_id", &lua_cross_zone_set_entity_variable_by_guild_id),
@@ -6772,7 +6784,6 @@ luabind::scope lua_register_random() {
)];
}
luabind::scope lua_register_events() {
return luabind::class_<Events>("Event")
.enum_("constants")
@@ -8008,7 +8019,6 @@ luabind::scope lua_register_journal_mode() {
)];
}
luabind::scope lua_register_exp_source() {
return luabind::class_<ExpSource>("ExpSource")
.enum_("constants")
+2
View File
@@ -21,8 +21,10 @@ luabind::scope lua_register_rules_const();
luabind::scope lua_register_rulei();
luabind::scope lua_register_ruler();
luabind::scope lua_register_ruleb();
luabind::scope lua_register_rules();
luabind::scope lua_register_journal_speakmode();
luabind::scope lua_register_journal_mode();
luabind::scope lua_register_exp_source();
#endif
#endif
-2
View File
@@ -674,8 +674,6 @@ luabind::scope lua_register_packet_opcodes() {
luabind::value("ShopEndConfirm", static_cast<int>(OP_ShopEndConfirm)),
luabind::value("AdventureMerchantRequest", static_cast<int>(OP_AdventureMerchantRequest)),
luabind::value("Sound", static_cast<int>(OP_Sound)),
luabind::value("0x0193", static_cast<int>(OP_0x0193)),
luabind::value("0x0347", static_cast<int>(OP_0x0347)),
luabind::value("WorldComplete", static_cast<int>(OP_WorldComplete)),
luabind::value("MobRename", static_cast<int>(OP_MobRename)),
luabind::value("TaskDescription", static_cast<int>(OP_TaskDescription)),
+5 -1
View File
@@ -42,6 +42,7 @@
#include "lua_spawn.h"
#include "lua_spell.h"
#include "lua_stat_bonuses.h"
#include "lua_database.h"
const char *LuaEvents[_LargestEventID] = {
"event_say",
@@ -1312,11 +1313,14 @@ void LuaParser::MapFunctions(lua_State *L) {
lua_register_rulei(),
lua_register_ruler(),
lua_register_ruleb(),
lua_register_rules(),
lua_register_journal_speakmode(),
lua_register_journal_mode(),
lua_register_expedition(),
lua_register_expedition_lock_messages(),
lua_register_buff()
lua_register_buff(),
lua_register_exp_source(),
lua_register_database()
)];
} catch(std::exception &ex) {
+44 -48
View File
@@ -1266,8 +1266,6 @@ void Mob::CreateSpawnPacket(EQApplicationPacket* app, NewSpawn_Struct* ns) {
} else {
strcpy(ns2->spawn.lastName, ns->spawn.lastName);
}
memset(&app->pBuffer[sizeof(Spawn_Struct)-7], 0xFF, 7);
}
void Mob::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho)
@@ -1277,6 +1275,14 @@ void Mob::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho)
strcpy(ns->spawn.name, name);
if(IsClient()) {
strn0cpy(ns->spawn.lastName, lastname, sizeof(ns->spawn.lastName));
ns->spawn.CharacterGuid.Id = CastToClient()->CharacterID();
ns->spawn.CharacterGuid.WorldId = RuleI(World, Id);
ns->spawn.CharacterGuid.Reserved = 0;
}
else {
ns->spawn.CharacterGuid.Id = 0;
ns->spawn.CharacterGuid.WorldId = 0;
ns->spawn.CharacterGuid.Reserved = 0;
}
ns->spawn.heading = FloatToEQ12(m_Position.w);
@@ -2050,19 +2056,19 @@ void Mob::SendStatsWindow(Client* c, bool use_window)
case 0: {
mod2a_name = "Avoidance";
mod2b_name = "Combat Effects";
mod2a_cap = Strings::Commify(RuleI(Character, ItemAvoidanceCap));
mod2b_cap = Strings::Commify(RuleI(Character, ItemCombatEffectsCap));
mod2a_cap = RuleI(Character, ItemAvoidanceCap);
mod2b_cap = RuleI(Character, ItemCombatEffectsCap);
if (IsBot()) {
mod2a = Strings::Commify(CastToBot()->GetAvoidance());
mod2a = CastToBot()->GetAvoidance();
} else if (IsClient()) {
mod2a = Strings::Commify(CastToClient()->GetAvoidance());
mod2a = CastToClient()->GetAvoidance();
}
if (IsBot()) {
mod2b = Strings::Commify(CastToBot()->GetCombatEffects());
mod2b = CastToBot()->GetCombatEffects();
} else if (IsClient()) {
mod2b = Strings::Commify(CastToClient()->GetCombatEffects());
mod2b = CastToClient()->GetCombatEffects();
}
break;
@@ -2070,19 +2076,19 @@ void Mob::SendStatsWindow(Client* c, bool use_window)
case 1: {
mod2a_name = "Accuracy";
mod2b_name = "Strikethrough";
mod2a_cap = Strings::Commify(RuleI(Character, ItemAccuracyCap));
mod2b_cap = Strings::Commify(RuleI(Character, ItemStrikethroughCap));
mod2a_cap = RuleI(Character, ItemAccuracyCap);
mod2b_cap = RuleI(Character, ItemStrikethroughCap);
if (IsBot()) {
mod2a = Strings::Commify(CastToBot()->GetAccuracy());
mod2a = CastToBot()->GetAccuracy();
} else if (IsClient()) {
mod2a = Strings::Commify(CastToClient()->GetAccuracy());
mod2a = CastToClient()->GetAccuracy();
}
if (IsBot()) {
mod2b = Strings::Commify(CastToBot()->GetStrikeThrough());
mod2b = CastToBot()->GetStrikeThrough();
} else if (IsClient()) {
mod2b = Strings::Commify(CastToClient()->GetStrikeThrough());
mod2b = CastToClient()->GetStrikeThrough();
}
break;
@@ -2090,20 +2096,20 @@ void Mob::SendStatsWindow(Client* c, bool use_window)
case 2: {
mod2a_name = "Shielding";
mod2b_name = "Spell Shielding";
mod2a_cap = Strings::Commify(RuleI(Character, ItemShieldingCap));
mod2b_cap = Strings::Commify(RuleI(Character, ItemSpellShieldingCap));
mod2a_cap = RuleI(Character, ItemShieldingCap);
mod2b_cap = RuleI(Character, ItemSpellShieldingCap);
if (IsBot()) {
mod2a = Strings::Commify(CastToBot()->GetShielding());
mod2a = CastToBot()->GetShielding();
} else if (IsClient()) {
mod2a = Strings::Commify(CastToClient()->GetShielding());
mod2a = CastToClient()->GetShielding();
}
if (IsBot()) {
mod2b = Strings::Commify(CastToBot()->GetSpellShield());
mod2b = CastToBot()->GetSpellShield();
} else if (IsClient()) {
mod2b = Strings::Commify(CastToClient()->GetSpellShield());
mod2b = CastToClient()->GetSpellShield();
}
break;
@@ -2111,19 +2117,19 @@ void Mob::SendStatsWindow(Client* c, bool use_window)
case 3: {
mod2a_name = "Stun Resist";
mod2b_name = "DOT Shielding";
mod2a_cap = Strings::Commify(RuleI(Character, ItemStunResistCap));
mod2b_cap = Strings::Commify(RuleI(Character, ItemDoTShieldingCap));
mod2a_cap = RuleI(Character, ItemStunResistCap);
mod2b_cap = RuleI(Character, ItemDoTShieldingCap);
if (IsBot()) {
mod2a = Strings::Commify(CastToBot()->GetStunResist());
mod2a = CastToBot()->GetStunResist();
} else if (IsClient()) {
mod2a = Strings::Commify(CastToClient()->GetStunResist());
mod2a = CastToClient()->GetStunResist();
}
if (IsBot()) {
mod2b = Strings::Commify(CastToBot()->GetDoTShield());
mod2b = CastToBot()->GetDoTShield();
} else if (IsClient()) {
mod2b = Strings::Commify(CastToClient()->GetDoTShield());
mod2b = CastToClient()->GetDoTShield();
}
break;
@@ -8584,6 +8590,7 @@ bool Mob::HasBotAttackFlag(Mob* tar) {
const uint16 scan_close_mobs_timer_moving = 6000; // 6 seconds
const uint16 scan_close_mobs_timer_idle = 60000; // 60 seconds
// If the moving timer triggers, lets see if we are moving or idle to restart the appropriate dynamic timer
void Mob::CheckScanCloseMobsMovingTimer()
{
LogAIScanCloseDetail(
@@ -8593,31 +8600,20 @@ void Mob::CheckScanCloseMobsMovingTimer()
m_scan_close_mobs_timer.GetRemainingTime()
);
// If the moving timer triggers, lets see if we are moving or idle to restart the appropriate
// dynamic timer
if (m_mob_check_moving_timer.Check()) {
// If the mob is still moving, restart the moving timer
if (moving) {
if (m_scan_close_mobs_timer.GetRemainingTime() > scan_close_mobs_timer_moving) {
LogAIScanCloseDetail("Mob [{}] Restarting with moving timer", GetCleanName());
m_scan_close_mobs_timer.Disable();
m_scan_close_mobs_timer.Start(scan_close_mobs_timer_moving);
m_scan_close_mobs_timer.Trigger();
}
}
// If the mob is not moving, restart the idle timer
else if (m_scan_close_mobs_timer.GetDuration() == scan_close_mobs_timer_moving) {
LogAIScanCloseDetail("Mob [{}] Restarting with idle timer", GetCleanName());
// If the mob is still moving, restart the moving timer
if (moving) {
if (m_scan_close_mobs_timer.GetRemainingTime() > scan_close_mobs_timer_moving) {
LogAIScanCloseDetail("Mob [{}] Restarting with moving timer", GetCleanName());
m_scan_close_mobs_timer.Disable();
m_scan_close_mobs_timer.Start(scan_close_mobs_timer_idle);
m_scan_close_mobs_timer.Start(scan_close_mobs_timer_moving);
m_scan_close_mobs_timer.Trigger();
}
}
}
void Mob::ScanCloseMobProcess()
{
if (m_scan_close_mobs_timer.Check()) {
entity_list.ScanCloseMobs(this);
// If the mob is not moving, restart the idle timer
else if (m_scan_close_mobs_timer.GetDuration() == scan_close_mobs_timer_moving) {
LogAIScanCloseDetail("Mob [{}] Restarting with idle timer", GetCleanName());
m_scan_close_mobs_timer.Disable();
m_scan_close_mobs_timer.Start(scan_close_mobs_timer_idle);
}
}
-1
View File
@@ -1488,7 +1488,6 @@ public:
bool IsCloseToBanker();
void ScanCloseMobProcess();
std::unordered_map<uint16, Mob *> &GetCloseMobList(float distance = 0.0f);
void CheckScanCloseMobsMovingTimer();
+11 -3
View File
@@ -1815,12 +1815,18 @@ void Mob::AI_Event_NoLongerEngaged() {
StopNavigation();
ClearRampage();
parse->EventBotMercNPC(EVENT_COMBAT, this, nullptr, [&]() { return "0"; });
if (IsNPC()) {
SetPrimaryAggro(false);
SetAssistAggro(false);
if (CastToNPC()->GetCombatEvent() && GetHP() > 0) {
if (
CastToNPC()->GetCombatEvent() &&
GetHP() > 0 &&
entity_list.GetNPCByID(GetID())
) {
if (parse->HasQuestSub(GetNPCTypeID(), EVENT_COMBAT)) {
parse->EventNPC(EVENT_COMBAT, CastToNPC(), nullptr, "0", 0);
}
const uint32 emote_id = CastToNPC()->GetEmoteID();
if (emote_id) {
CastToNPC()->DoNPCEmote(EQ::constants::EmoteEventTypes::LeaveCombat, emote_id);
@@ -1829,6 +1835,8 @@ void Mob::AI_Event_NoLongerEngaged() {
m_combat_record.Stop();
CastToNPC()->SetCombatEvent(false);
}
} else {
parse->EventBotMerc(EVENT_COMBAT, this, nullptr, [&]() { return "0"; });
}
}
+42 -10
View File
@@ -601,8 +601,13 @@ bool NPC::Process()
DepopSwarmPets();
}
ScanCloseMobProcess();
CheckScanCloseMobsMovingTimer();
if (m_scan_close_mobs_timer.Check()) {
entity_list.ScanCloseMobs(this);
}
if (m_mob_check_moving_timer.Check()) {
CheckScanCloseMobsMovingTimer();
}
if (hp_regen_per_second > 0 && hp_regen_per_second_timer.Check()) {
if (GetHP() < GetMaxHP()) {
@@ -2151,6 +2156,7 @@ void NPC::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho)
UpdateActiveLight();
ns->spawn.light = GetActiveLightType();
ns->spawn.show_name = NPCTypedata->show_name;
ns->spawn.trader = false;
}
void NPC::PetOnSpawn(NewSpawn_Struct* ns)
@@ -3291,16 +3297,28 @@ uint32 NPC::GetSpawnKillCount()
return(0);
}
void NPC::DoQuestPause(Mob *other) {
if(IsMoving() && !IsOnHatelist(other)) {
PauseWandering(RuleI(NPC, SayPauseTimeInSec));
if (other && !other->sneaking)
FaceTarget(other);
} else if(!IsMoving()) {
if (other && !other->sneaking && GetAppearance() != eaSitting && GetAppearance() != eaDead)
FaceTarget(other);
void NPC::DoQuestPause(Mob* m)
{
if (!m) {
return;
}
if (IsMoving() && !IsOnHatelist(m)) {
PauseWandering(RuleI(NPC, SayPauseTimeInSec));
if (FacesTarget() && !m->sneaking) {
FaceTarget(m);
}
} else if (!IsMoving()) {
if (
FacesTarget() &&
!m->sneaking &&
GetAppearance() != eaSitting &&
GetAppearance() != eaDead
) {
FaceTarget(m);
}
}
}
void NPC::ChangeLastName(std::string last_name)
@@ -4232,3 +4250,17 @@ void NPC::DoNpcToNpcAggroScan()
false
);
}
bool NPC::FacesTarget()
{
const std::string& excluded_races_rule = RuleS(NPC, ExcludedFaceTargetRaces);
if (excluded_races_rule.empty()) {
return true;
}
const auto& v = Strings::Split(excluded_races_rule, ",");
return std::find(v.begin(), v.end(), std::to_string(GetBaseRace())) == v.end();
}
+2 -1
View File
@@ -482,7 +482,8 @@ public:
NPC_Emote_Struct* GetNPCEmote(uint32 emote_id, uint8 event_);
void DoNPCEmote(uint8 event_, uint32 emote_id, Mob* t = nullptr);
bool CanTalk();
void DoQuestPause(Mob *other);
void DoQuestPause(Mob* m);
bool FacesTarget();
inline void SetSpellScale(float amt) { spellscale = amt; }
inline float GetSpellScale() { return spellscale; }
+14 -2
View File
@@ -278,6 +278,19 @@ void Client::DoParcelSend(const Parcel_Struct *parcel_in)
return;
}
if (parcel_in->money_flag && parcel_in->item_slot != INVALID_INDEX) {
Message(
Chat::Yellow,
fmt::format(
"{} tells you, 'I am confused! Do you want to send money or an item?'",
merchant->GetCleanName()
).c_str()
);
DoParcelCancel();
SendParcelAck();
return;
}
auto num_of_parcels = GetParcelCount();
if (num_of_parcels >= RuleI(Parcel, ParcelMaxItems)) {
SendParcelIconStatus();
@@ -406,9 +419,8 @@ void Client::DoParcelSend(const Parcel_Struct *parcel_in)
std::vector<CharacterParcelsContainersRepository::CharacterParcelsContainers> all_entries{};
if (inst->IsNoneEmptyContainer()) {
CharacterParcelsContainersRepository::CharacterParcelsContainers cpc{};
for (auto const &kv: *inst->GetContents()) {
CharacterParcelsContainersRepository::CharacterParcelsContainers cpc{};
cpc.parcels_id = result.id;
cpc.slot_id = kv.first;
cpc.item_id = kv.second->GetID();
+255
View File
@@ -0,0 +1,255 @@
#include "../common/features.h"
#ifdef EMBPERL_XS_CLASSES
#include "embperl.h"
#include "perl_database.h"
#include "zonedb.h"
// Perl takes ownership of returned objects allocated with new and deletes
// them via the DESTROY method when the last perl reference goes out of scope
void Perl_Database::Destroy(Perl_Database* ptr)
{
delete ptr;
}
Perl_Database* Perl_Database::Connect()
{
return new Perl_Database();
}
Perl_Database* Perl_Database::Connect(Connection type)
{
return new Perl_Database(type);
}
Perl_Database* Perl_Database::Connect(Connection type, bool connect)
{
return new Perl_Database(type, connect);
}
Perl_Database* Perl_Database::Connect(const char* host, const char* user, const char* pass, const char* db, uint32_t port)
{
return new Perl_Database(host, user, pass, db, port);
}
Perl_MySQLPreparedStmt* Perl_Database::Prepare(std::string query)
{
return m_db ? new Perl_MySQLPreparedStmt(m_db->Prepare(std::move(query))) : nullptr;
}
void Perl_Database::Close()
{
m_db.reset();
}
// ---------------------------------------------------------------------------
void Perl_MySQLPreparedStmt::Destroy(Perl_MySQLPreparedStmt* ptr)
{
delete ptr;
}
void Perl_MySQLPreparedStmt::Close()
{
m_stmt.reset();
}
void Perl_MySQLPreparedStmt::Execute()
{
if (m_stmt)
{
m_res = m_stmt->Execute();
}
}
void Perl_MySQLPreparedStmt::Execute(perl::array args)
{
// passes all script args as strings
if (m_stmt)
{
std::vector<mysql::PreparedStmt::param_t> inputs;
for (const perl::scalar& arg : args)
{
if (arg.is_null())
{
inputs.emplace_back(nullptr);
}
else
{
inputs.emplace_back(arg.c_str());
}
}
m_res = m_stmt->Execute(inputs);
}
}
void Perl_MySQLPreparedStmt::SetOptions(perl::hash hash)
{
if (m_stmt)
{
mysql::StmtOptions opts = m_stmt->GetOptions();
if (hash.exists("buffer_results"))
{
opts.buffer_results = hash["buffer_results"].as<bool>();
}
if (hash.exists("use_max_length"))
{
opts.use_max_length = hash["use_max_length"].as<bool>();
}
m_stmt->SetOptions(opts);
}
}
static void PushValue(PerlInterpreter* my_perl, SV* sv, const mysql::StmtColumn& col)
{
if (col.IsNull())
{
sv_setsv(sv, &PL_sv_undef);
return;
}
switch (col.Type())
{
case MYSQL_TYPE_TINY:
case MYSQL_TYPE_SHORT:
case MYSQL_TYPE_INT24:
case MYSQL_TYPE_LONG:
case MYSQL_TYPE_LONGLONG:
case MYSQL_TYPE_BIT:
if (col.IsUnsigned())
{
sv_setuv(sv, col.Get<UV>().value());
}
else
{
sv_setiv(sv, col.Get<IV>().value());
}
break;
case MYSQL_TYPE_FLOAT:
case MYSQL_TYPE_DOUBLE:
sv_setnv(sv, col.Get<NV>().value());
break;
case MYSQL_TYPE_TIME:
case MYSQL_TYPE_DATE:
case MYSQL_TYPE_DATETIME:
case MYSQL_TYPE_TIMESTAMP:
{
std::string str = col.GetStr().value();
sv_setpvn(sv, str.data(), str.size());
}
break;
default: // string types, push raw buffer to avoid copy
{
std::string_view str = col.GetStrView().value();
sv_setpvn(sv, str.data(), str.size());
}
break;
}
}
perl::array Perl_MySQLPreparedStmt::FetchArray()
{
auto row = m_stmt ? m_stmt->Fetch() : mysql::StmtRow();
if (!row)
{
return perl::array();
}
// perf: bypass perlbind operator[]/push and use cache to limit SV allocs
dTHX;
AV* av = static_cast<AV*>(m_row_array);
for (const mysql::StmtColumn& col : row)
{
SV** sv = av_fetch(av, col.Index(), true);
PushValue(my_perl, *sv, col);
}
SvREFCNT_inc(av); // return a ref to our cache (no copy)
return perl::array(std::move(av));
}
perl::reference Perl_MySQLPreparedStmt::FetchArrayRef()
{
perl::array array = FetchArray();
return array.size() == 0 ? perl::reference() : perl::reference(array);
}
perl::reference Perl_MySQLPreparedStmt::FetchHashRef()
{
auto row = m_stmt ? m_stmt->Fetch() : mysql::StmtRow();
if (!row)
{
return perl::reference();
}
// perf: bypass perlbind operator[] and use cache to limit SV allocs
dTHX;
HV* hv = static_cast<HV*>(m_row_hash);
for (const mysql::StmtColumn& col : row)
{
SV** sv = hv_fetch(hv, col.Name().c_str(), static_cast<I32>(col.Name().size()), true);
PushValue(my_perl, *sv, col);
}
SvREFCNT_inc(hv); // return a ref to our cache (no copy)
return perl::reference(std::move(hv));
}
int Perl_MySQLPreparedStmt::ColumnCount()
{
return m_res.ColumnCount();
}
uint64_t Perl_MySQLPreparedStmt::LastInsertID()
{
return m_res.LastInsertID();
}
uint64_t Perl_MySQLPreparedStmt::RowCount()
{
return m_res.RowCount();
}
uint64_t Perl_MySQLPreparedStmt::RowsAffected()
{
return m_res.RowsAffected();
}
void perl_register_database()
{
perl::interpreter perl(PERL_GET_THX);
{
auto package = perl.new_class<Perl_Database>("Database");
package.add_const("Default", static_cast<int>(QuestDB::Connection::Default));
package.add_const("Content", static_cast<int>(QuestDB::Connection::Content));
package.add("DESTROY", &Perl_Database::Destroy);
package.add("new", static_cast<Perl_Database*(*)()>(&Perl_Database::Connect));
package.add("new", static_cast<Perl_Database*(*)(QuestDB::Connection)>(&Perl_Database::Connect));
package.add("new", static_cast<Perl_Database*(*)(QuestDB::Connection, bool)>(&Perl_Database::Connect));
package.add("new", static_cast<Perl_Database*(*)(const char*, const char*, const char*, const char*, uint32_t)>(&Perl_Database::Connect));
package.add("close", &Perl_Database::Close);
package.add("prepare", &Perl_Database::Prepare);
}
{
auto package = perl.new_class<Perl_MySQLPreparedStmt>("MySQLPreparedStmt");
package.add("DESTROY", &Perl_MySQLPreparedStmt::Destroy);
package.add("close", &Perl_MySQLPreparedStmt::Close);
package.add("execute", static_cast<void(Perl_MySQLPreparedStmt::*)()>(&Perl_MySQLPreparedStmt::Execute));
package.add("execute", static_cast<void(Perl_MySQLPreparedStmt::*)(perl::array)>(&Perl_MySQLPreparedStmt::Execute));
package.add("fetch", &Perl_MySQLPreparedStmt::FetchArray);
package.add("fetch_array", &Perl_MySQLPreparedStmt::FetchArray);
package.add("fetch_arrayref", &Perl_MySQLPreparedStmt::FetchArrayRef);
package.add("fetch_hashref", &Perl_MySQLPreparedStmt::FetchHashRef);
package.add("insert_id", &Perl_MySQLPreparedStmt::LastInsertID);
package.add("num_fields", &Perl_MySQLPreparedStmt::ColumnCount);
package.add("num_rows", &Perl_MySQLPreparedStmt::RowCount);
package.add("rows_affected", &Perl_MySQLPreparedStmt::RowsAffected);
package.add("set_options", &Perl_MySQLPreparedStmt::SetOptions);
}
}
#endif // EMBPERL_XS_CLASSES
+50
View File
@@ -0,0 +1,50 @@
#pragma once
#include "quest_db.h"
#include "../common/mysql_stmt.h"
class Perl_MySQLPreparedStmt;
class Perl_Database : public QuestDB
{
public:
using QuestDB::QuestDB;
static void Destroy(Perl_Database* ptr);
static Perl_Database* Connect();
static Perl_Database* Connect(Connection type);
static Perl_Database* Connect(Connection type, bool connect);
static Perl_Database* Connect(const char* host, const char* user, const char* pass, const char* db, uint32_t port);
void Close();
Perl_MySQLPreparedStmt* Prepare(std::string query);
};
class Perl_MySQLPreparedStmt
{
public:
Perl_MySQLPreparedStmt(mysql::PreparedStmt&& stmt)
: m_stmt(std::make_unique<mysql::PreparedStmt>(std::move(stmt))) {}
static void Destroy(Perl_MySQLPreparedStmt* ptr);
void Close();
void Execute();
void Execute(perl::array args);
void SetOptions(perl::hash hash_opts);
perl::array FetchArray();
perl::reference FetchArrayRef();
perl::reference FetchHashRef();
// StmtResult functions accessible through this class to simplify api
int ColumnCount();
uint64_t LastInsertID();
uint64_t RowCount();
uint64_t RowsAffected();
private:
std::unique_ptr<mysql::PreparedStmt> m_stmt;
mysql::StmtResult m_res = {};
perl::array m_row_array; // perf: cache for fetches
perl::hash m_row_hash;
};
+57
View File
@@ -0,0 +1,57 @@
#include "quest_db.h"
#include "zonedb.h"
#include "zone_config.h"
// New connections avoid concurrency issues and allow use of unbuffered results
// with prepared statements. Using zone connections w/o buffering would cause
// "Commands out of sync" errors if any queries occur before results consumed.
QuestDB::QuestDB(Connection type, bool connect)
{
if (connect)
{
m_db = std::unique_ptr<Database, Deleter>(new Database(), Deleter(true));
const auto config = EQEmuConfig::get();
if (type == Connection::Default || type == Connection::Content && config->ContentDbHost.empty())
{
m_db->Connect(config->DatabaseHost, config->DatabaseUsername, config->DatabasePassword,
config->DatabaseDB, config->DatabasePort, "questdb");
}
else if (type == Connection::Content)
{
m_db->Connect(config->ContentDbHost, config->ContentDbUsername, config->ContentDbPassword,
config->ContentDbName, config->ContentDbPort, "questdb");
}
}
else if (type == Connection::Default)
{
m_db = std::unique_ptr<Database, Deleter>(&database, Deleter(false));
}
else if (type == Connection::Content)
{
m_db = std::unique_ptr<Database, Deleter>(&content_db, Deleter(false));
}
if (!m_db || (connect && m_db->GetStatus() != DBcore::Connected))
{
throw std::runtime_error(fmt::format("Failed to connect to db type [{}]", static_cast<int>(type)));
}
}
QuestDB::QuestDB(const char* host, const char* user, const char* pass, const char* db, uint32_t port)
: m_db(new Database(), Deleter(true))
{
if (!m_db->Connect(host, user, pass, db, port, "questdb"))
{
throw std::runtime_error(fmt::format("Failed to connect to db [{}:{}]", host, port));
}
}
void QuestDB::Deleter::operator()(Database* ptr) noexcept
{
if (owner)
{
delete ptr;
}
};
+30
View File
@@ -0,0 +1,30 @@
#pragma once
#include <memory>
class Database;
// Base class for quest apis to manage connection to a MySQL database
class QuestDB
{
public:
enum class Connection { Default = 0, Content };
// Throws std::runtime_error on connection failure
QuestDB() : QuestDB(Connection::Default) {}
QuestDB(Connection type) : QuestDB(type, false) {}
QuestDB(Connection type, bool connect);
QuestDB(const char* host, const char* user, const char* pass, const char* db, uint32_t port);
protected:
// allow optional ownership of pointer to support using zone db connections
struct Deleter
{
Deleter() : owner(true) {}
Deleter(bool owner_) : owner(owner_) {}
bool owner = true;
void operator()(Database* ptr) noexcept;
};
std::unique_ptr<Database, Deleter> m_db;
};
+75
View File
@@ -4623,3 +4623,78 @@ bool QuestManager::SetAutoLoginCharacterNameByAccountID(uint32 account_id, const
{
return AccountRepository::SetAutoLoginCharacterNameByAccountID(database, account_id, character_name);
}
void QuestManager::SpawnCircle(uint32 npc_id, glm::vec4 position, float radius, uint32 points)
{
const NPCType* t = content_db.LoadNPCTypesData(npc_id);
if (!t) {
return;
}
glm::vec4 npc_position = position;
for (uint32 i = 0; i < points; i++) {
float angle = 2 * M_PI * i / points;
npc_position.x = position.x + radius * std::cos(angle);
npc_position.y = position.y + radius * std::sin(angle);
NPC* n = new NPC(t, nullptr, npc_position, GravityBehavior::Water);
n->FixZ();
n->AddLootTable();
if (n->DropsGlobalLoot()) {
n->CheckGlobalLootTables();
}
entity_list.AddNPC(n, true, true);
}
}
void QuestManager::SpawnGrid(uint32 npc_id, glm::vec4 position, float spacing, uint32 spawn_count)
{
const NPCType* t = content_db.LoadNPCTypesData(npc_id);
if (!t) {
return;
}
glm::vec4 npc_position = position;
uint32 columns = std::ceil(std::sqrt(spawn_count));
uint32 rows = std::ceil(spawn_count / columns);
float total_width = ((columns - 1) * spacing);
float total_height = ((rows - 1) * spacing);
float start_x = position.x - total_width / 2;
float start_y = position.y - total_height / 2;
uint32 spawned = 0;
for (uint32 row = 0; row < rows; row++) {
for (uint32 column = 0; column < columns; column++) {
if (spawned >= spawn_count) {
break;
}
npc_position.x = start_x + column * spacing;
npc_position.y = start_y + row * spacing;
NPC* n = new NPC(t, nullptr, npc_position, GravityBehavior::Water);
n->FixZ();
n->AddLootTable();
if (n->DropsGlobalLoot()) {
n->CheckGlobalLootTables();
}
entity_list.AddNPC(n, true, true);
spawned++;
}
}
}
+2
View File
@@ -357,6 +357,8 @@ public:
void SendChannelMessage(Client* from, const char* to, uint8 channel_number, uint32 guild_id, uint8 language_id, uint8 language_skill, const char* message);
std::string GetAutoLoginCharacterNameByAccountID(uint32 account_id);
bool SetAutoLoginCharacterNameByAccountID(uint32 account_id, const std::string& character_name);
void SpawnCircle(uint32 npc_id, glm::vec4 position, float radius, uint32 points);
void SpawnGrid(uint32 npc_id, glm::vec4 position, float spacing, uint32 spawn_count);
Bot *GetBot() const;
Client *GetInitiator() const;
+11 -9
View File
@@ -2416,16 +2416,18 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
break;
}
int16 focus = RuleB(Spells, AllowFocusOnSkillDamageSpells) ? caster->GetMeleeDamageMod_SE(spells[spell_id].skill) : 0;
switch(spells[spell_id].skill) {
case EQ::skills::SkillThrowing:
caster->DoThrowingAttackDmg(this, nullptr, nullptr, spells[spell_id].base_value[i],spells[spell_id].limit_value[i], 0, ReuseTime, 0, 0, 4.0f, true);
break;
case EQ::skills::SkillArchery:
caster->DoArcheryAttackDmg(this, nullptr, nullptr, spells[spell_id].base_value[i],spells[spell_id].limit_value[i], 0, ReuseTime, 0, 0, nullptr, 0, 4.0f, true);
break;
default:
caster->DoMeleeSkillAttackDmg(this, spells[spell_id].base_value[i], spells[spell_id].skill, spells[spell_id].limit_value[i], 0, false, ReuseTime);
break;
case EQ::skills::SkillThrowing:
caster->DoThrowingAttackDmg(this, nullptr, nullptr, spells[spell_id].base_value[i],spells[spell_id].limit_value[i], focus, ReuseTime, 0, 0, 4.0f, true);
break;
case EQ::skills::SkillArchery:
caster->DoArcheryAttackDmg(this, nullptr, nullptr, spells[spell_id].base_value[i],spells[spell_id].limit_value[i], focus, ReuseTime, 0, 0, nullptr, 0, 4.0f, true);
break;
default:
caster->DoMeleeSkillAttackDmg(this, spells[spell_id].base_value[i], spells[spell_id].skill, spells[spell_id].limit_value[i], focus, false, ReuseTime);
break;
}
break;
}
+22 -7
View File
@@ -777,6 +777,8 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st
tradingWith->SayString(TRADE_BACK, GetCleanName());
PushItemOnCursor(*inst, true);
}
items.clear();
}
// Only enforce trade rules if the NPC doesn't have an EVENT_TRADE
// subroutine. That overrides all.
@@ -2606,6 +2608,7 @@ void Client::SellToBuyer(const EQApplicationPacket *app)
data->zone_id = GetZoneID();
data->slot = sell_line.slot;
data->seller_quantity = sell_line.seller_quantity;
data->purchase_method = sell_line.purchase_method;
strn0cpy(data->item_name, sell_line.item_name, sizeof(data->item_name));
strn0cpy(data->buyer_name, sell_line.buyer_name.c_str(), sizeof(data->buyer_name));
strn0cpy(data->seller_name, GetCleanName(), sizeof(data->seller_name));
@@ -2912,10 +2915,11 @@ void Client::SendBecomeTraderToWorld(Client *trader, BazaarTraderBarterActions a
auto outapp = new ServerPacket(ServerOP_TraderMessaging, sizeof(TraderMessaging_Struct));
auto data = (TraderMessaging_Struct *) outapp->pBuffer;
data->action = action;
data->entity_id = trader->GetID();
data->trader_id = trader->CharacterID();
data->zone_id = trader->GetZoneID();
data->action = action;
data->entity_id = trader->GetID();
data->trader_id = trader->CharacterID();
data->zone_id = trader->GetZoneID();
data->instance_id = trader->GetInstanceID();
strn0cpy(data->trader_name, trader->GetName(), sizeof(data->trader_name));
worldserver.SendPacket(outapp);
@@ -3234,7 +3238,10 @@ void Client::SendBulkBazaarTraders()
void Client::DoBazaarInspect(const BazaarInspect_Struct &in)
{
auto items = TraderRepository::GetWhere(database, fmt::format("item_sn = {}", in.serial_number));
auto items = TraderRepository::GetWhere(
database, fmt::format("`char_id` = '{}' AND `item_sn` = '{}'", in.trader_id, in.serial_number)
);
if (items.empty()) {
LogInfo("Failed to find item with serial number [{}]", in.serial_number);
return;
@@ -3303,7 +3310,7 @@ std::string Client::DetermineMoneyString(uint64 cp)
void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicationPacket *app)
{
auto in = (TraderBuy_Struct *) app->pBuffer;
auto trader_item = TraderRepository::GetItemBySerialNumber(database, tbs->serial_number);
auto trader_item = TraderRepository::GetItemBySerialNumber(database, tbs->serial_number, tbs->trader_id);
if (!trader_item.id) {
LogTrading("Attempt to purchase an item outside of the Bazaar trader_id <red>[{}] item serial_number "
"<red>[{}] The Traders data was outdated.",
@@ -3497,7 +3504,7 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati
ps.item_slot = parcel_out.slot_id;
strn0cpy(ps.send_to, GetCleanName(), sizeof(ps.send_to));
if (trader_item.item_charges <= static_cast<int32>(tbs->quantity)) {
if (trader_item.item_charges <= static_cast<int32>(tbs->quantity) || !buy_item->IsStackable()) {
TraderRepository::DeleteOne(database, trader_item.id);
} else {
TraderRepository::UpdateQuantity(
@@ -4252,6 +4259,14 @@ bool Client::DoBarterSellerChecks(BuyerLineSellItem_Struct &sell_line)
Message(Chat::Red, "The item that you are trying to sell is augmented. Please remove augments first");
}
if (sell_item && !sell_item->IsDroppable()) {
seller_error = true;
LogTradingDetail("Seller item <red>[{}] is non-tradeable therefore cannot be sold.",
sell_line.item_name
);
Message(Chat::Red, "The item that you are trying to sell is non-tradeable and therefore cannot be sold.");
}
if (seller_error) {
LogTradingDetail("Seller Error <red>[{}] Barter Sell/Buy Transaction Failed.", seller_error);
SendBarterBuyerClientMessage(sell_line, Barter_SellerTransactionComplete, Barter_Failure, Barter_Failure);
+2 -1
View File
@@ -3942,7 +3942,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
c.second->QueuePacket(outapp);
safe_delete(outapp);
}
if (zone && zone->GetZoneID() == Zones::BAZAAR) {
if (zone && zone->GetZoneID() == Zones::BAZAAR && in->instance_id == zone->GetInstanceID()) {
if (in->action == TraderOn) {
c.second->SendBecomeTrader(TraderOn, in->entity_id);
}
@@ -4044,6 +4044,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
sell_line.buyer_name = in->buyer_name;
sell_line.seller_quantity = in->seller_quantity;
sell_line.slot = in->slot;
sell_line.purchase_method = in->purchase_method;
strn0cpy(sell_line.item_name, in->item_name, sizeof(sell_line.item_name));
uint64 total_cost = (uint64) sell_line.item_cost * (uint64) sell_line.seller_quantity;
+1 -1
View File
@@ -737,7 +737,7 @@ void Client::ProcessMovePC(uint32 zoneID, uint32 instance_id, float x, float y,
ZonePC(zoneID, instance_id, x, y, z, heading, ignorerestrictions, zm);
break;
default:
LogError("Client::ProcessMovePC received a reguest to perform an unsupported client zone operation");
LogError("Received a request to perform an unsupported client zone operation");
break;
}
}