254 Commits
1.1 ... 1.2

Author SHA1 Message Date
10534886b8 Cleanup for 1.2.
Cleaned up whitespace, comments, brace style, etc.
Updated tdata reference.
2020-10-19 20:48:57 +02:00
Gent
d713fafb1c Add missing MSS routes 2020-10-18 22:35:17 -04:00
e97b58ccaf Fixed private instance memory leaks. 2020-10-19 04:30:12 +02:00
55be58cc24 /summonW now supports summoning non-mob NPCs.
/npcr will now rotate gruntwork NPCs in-place instead of making separate
rotation entries that wouldn't have been loaded properly anyway.
2020-10-19 03:45:58 +02:00
JadeShrineMaiden
deb3e5b897 Added /minfo and /tasks (#137)
* /minfo returns your current active mission's: id, task id, task type, task objective marker id and enemy ids if applicable.
* /tasks returns all the active mission ids and task ids.
2020-10-18 23:02:51 +02:00
4a5857a126 QoL improvements.
* Use macros for extracting map numbers and player IDs from instance IDs
* Add docstrings to all commands
* Remove /test command
* Sync with tdata
2020-10-18 22:43:22 +02:00
Gent
f7e7f99017 Skyway tweaks. 2020-10-17 23:23:35 -04:00
Gent
6d9d66954e M O N K E 2020-10-17 22:33:59 -04:00
bbd695cad1 Minor tweaks.
* The server now refuses to start if any JSONs fail to load
* Mobs now take height into account when losing aggro on a player
* Mobs now aggro on the closest player in range, rather then on the
earliest one to connect to the server of the ones in range
* /summonW now works in IZs and Lairs as well
* Lowered the extra height that mobs spawn at with /summonW to prevent
glitching problems
2020-10-18 04:17:52 +02:00
3ce8cf2129 Cleaned up item drop logic.
* Replaced bad exception logic with C-style error returns in ItemManager
* Removed unnecessary instances of objects being passed by value
* Fixed whitespace problems
* Added new config options to the default config.ini
* Updated tabledata reference
2020-10-18 01:30:37 +02:00
7f716c7278 Fixed exception usage in TableData. 2020-10-18 01:30:37 +02:00
kamilprzyb
dd54668697 functional crates (no more plungers) (#133)
* FM, Taros and Boosts awards from killing mobs should be pretty
accurate now. A temporary formula for adjusting player/mob level gap is
implemented, but it will probably need to be adjusted in the future

* Mobs now drop correct crates

* Crates can be opened and give you correct items This includes
regular mob crates, world boss crates, mission crates, IZ race crates,
E.G.G.E.R.s, golden Eggs, and Event Crates.  Keep in mind that neither
IZ races or golden Eggs are implemented, but if you spawn such a crate
it can be opened.

* All data is read from a json file, for which I'm going to release a
tool soon so it's easily adjustable

* There is a new setting for enabling events, which enables dropping
extra event crates These are Knishmas, Halloween and Easter
2020-10-18 01:30:37 +02:00
Gent
ab5857e7e2 Fix re-crocpotting (#134) 2020-10-16 19:54:05 -04:00
Gent
da725d21e6 Delete instances with no players 2020-10-16 11:50:03 -04:00
Gent
6473951b9a Instance tweaks + fixes
Instanced mobs tick, fusions stay dead, missions reset on enter
2020-10-15 17:07:58 -04:00
Gent
3050801399 Make /warp and /goto put you in the overworld 2020-10-15 17:07:58 -04:00
Gent
efd729710f Kick players out of instances when they leave 2020-10-15 17:07:58 -04:00
Gent
85530ef57f Implement instance copying
and make respawn points match the player instance
2020-10-15 17:07:58 -04:00
Gent
b87f20e2dc Make instance missions reset upon warping out 2020-10-15 17:04:38 -04:00
Gent
d4aed0abf4 Add support for unique instances 2020-10-15 11:46:53 -04:00
Gent
c1fd51b721 Expand gruntwork to instances
Add NPC instance gruntwork data

Add instance-related commands
- /instance (instance ID)
- /npci [instance ID]
2020-10-15 11:44:15 -04:00
Zenpock
bd34bb294c Instance Stuff
custom instance commands
warping takes you into instances
npcs show up only in the instance they are supposed to be inside.
2020-10-15 11:44:15 -04:00
5a80c53e79 Remove the pointer to the deallocated chunk from the current player/NPC.
This fixes the Great Crash Bug(tm).
2020-10-15 04:36:38 +02:00
5784e77654 Misc changes.
The first two fixes were caught by infer. The Big Bug(tm) remains
unfixed.

* Fixed the Leech nano power
* Fixed an unlikely nullptr dereference in ItemManager
* /toggleai now makes mobs retreat immediately, instead of waiting for
their next movement tick
* Static path mobs will now stop in place instead of retreating to
their spawn points when AI is disabled
* Changed the misleading config option name from "chunksize" to
"viewdistance", since it's actually only a third of the chunk size
2020-10-14 23:15:02 +02:00
6ee5e6d1ae Load NPC angles from NPCs.json. 2020-10-14 02:34:00 +02:00
3586b76888 Fixed the slider not being in the overworld instance. 2020-10-14 02:34:00 +02:00
843b2e6e38 chunks are now cleaned up when not in use 2020-10-13 17:30:19 -05:00
a8c8065920 Ah, yes. Windows. 2020-10-13 21:57:41 +02:00
63414ea9a2 The /summon command can now spawn up to 100 NPCs at a time.
Note that /unsummon can only remove one at a time.
2020-10-13 21:50:14 +02:00
599bbedd8c Implemented static paths for mobs (like Don Doom and Bad Max).
* Mobs now retreat to where they were when they were roaming; not their
spawn point
* /toggleai still sends them back to their spawn point however
* Fixed a bug where mobs would respawn where they were killed instead of
their proper spawn point
* Fixed mobs roaming despite simulatemobs being set to false
* Updated tdata submodule
2020-10-13 21:44:43 +02:00
c792fb9d0d Clang lied to me.
It's reasonable that you can't use inline like this, but in my defense
it worked until MobManager.o was recomplied, I think.
2020-10-12 21:18:03 +02:00
f8d64234d7 Spawn mobs above ground to alleviate hill/building phasing issues. 2020-10-12 21:08:19 +02:00
901e011740 Mobs roam proper distances now.
* Mob roaming is now integrated into the TransportManager
* Doubled the roaming distance, since it was clearly too small
* Tripled retreat speed
* Made use of NPCManager::sendToViewable() in TransportManager
2020-10-12 21:08:19 +02:00
Gent
a53f38b87d Fix spawned vehicles expiring instantly 2020-10-12 14:06:28 -04:00
f3b6f9619b added /help 2020-10-12 02:08:10 -05:00
4d687a82ea Merge pull request #132 from JadeShrineMaiden/test
Bugfixes + Damage Formula Adjustment
2020-10-11 20:59:40 +02:00
d99dad261c Bugfixes + Damage Formula Adjustment
* Entering/exiting groups should apply/remove group buffs.
* Fixed issues with nanos losing all stamina from various revives/warp away.
* Heavily tweaked damage formula, nano types now boost/decrease damage.
* Weapons can consume battery returning extra damage, battery consumed depends on enemy level.
2020-10-11 18:39:52 +01:00
Gent
1564cc7724 Make proximity NPC search a helper function 2020-10-10 23:18:01 -04:00
2b9d0f6bab Fix GROUP_JOIN_FAIL being initialized as a GROUP_INVITE_FAIL.
This was a vulnerability, since JOIN_FAIL is larger than INVITE_FAIL.
2020-10-09 18:24:16 +02:00
9f280c2c31 Validate arguments to /level and /summonW; fix retreatStep() bug. 2020-10-08 20:20:12 +02:00
bae834fefa The login server now sends live checks.
This should stop server providers from kicking tutorial players as
inactive TCP connections. The interval is generous for that reason.

Also snuck in a change that makes mobs retreat all the way to their
spawn points, to aid during mob gruntwork with /toggleai.
2020-10-08 01:21:48 +02:00
1fe23b97fd Fixed RunningNPCRotations being forgotten across server invocations. 2020-10-07 21:13:26 +02:00
0d0332e551 Added /toggleai command for easier mob placement and testing. 2020-10-07 20:38:32 +02:00
7a83a3b45c Merge pull request #131 from JadeShrineMaiden/bugfix
Bugfixes
2020-10-07 19:52:11 +02:00
606384445c Added /summonW and /unsummonW gruntwork commands.
Also:
* Filled in the battery fileds in the REWARD_ITEM packets in
MissionManager.
* Removed redundant NPC_ENTER in npcSummonHandler()
2020-10-07 19:29:59 +02:00
7caa73caca Bugfixes
* Groups can now be abandoned using the "leave group" button.
* Non-group passive powers no longer buff the group leader instead of buffing the summoner.
* Adjusted various warning causing code pieces.
* Adjusted potions and battery prices.
2020-10-07 18:25:49 +01:00
6e3d0868cb Merge pull request #130 from gsemaj/npc
NPC Rotation Gruntwork
2020-10-07 02:33:39 +02:00
Gent
5ed332d836 Make /npcr use the nearest NPC 2020-10-06 20:09:18 -04:00
Gent
42fc018097 Add chunk reload command /refresh 2020-10-06 18:00:17 -04:00
Gent
1b68b5e2e2 Add NPC rotation command /npcr 2020-10-06 17:59:33 -04:00
Gent
5009fe1994 Implement temporary NPC rotations 2020-10-06 17:59:12 -04:00
Gent
4873eba160 Add overload to updateNPCPosition with rotation arg 2020-10-06 17:57:46 -04:00
d4d0f388c4 Implemented saving gruntwork results to file.
* Monkey Skyway paths are now saved in a format compatible with
paths.json
* flush() is called on every periodic DB save in addition to the /flush
and /mss N export commands
* Monkeys now accept WIP routes
2020-10-06 21:53:21 +02:00
ce58411ff8 Added /population and /level commands.
Also made /mss an access level 30 command, for safety.
2020-10-05 02:33:30 +02:00
661070dc3a Cleaned up whitespace.
Incantation: sed -i 's/[ \t]*$//g' src/*.[ch]pp

Also switched BuddyManager from tabs to spaces using vim's :retab command.
2020-10-05 02:03:13 +02:00
JadeShrineMaiden
b8f586bc10 Groups, Group Nano powers and Revive (#129)
* Initial Group Implementation

* Request/refuse/join and leave groups.
* Chat into groups.
* Get status updates on every group member each tick.
* Owner leaving the group destroys the entire group.

* Added more nano powers

* Revive for both variants work.
* Many nano powers now have a group variant working.
* Enemy checks for aggro before retreating.
* Enemies keep aggro on dead players with revive nanos out.

* Further Nano powers + Bugfixes

* Infection damage now relies on bitcondition flags.
* Antidote power now works.
* Improved how groups handle leaving players.
* Fixed mob aggro range.
* Group Healing is now functional.
* Possibly fixed the player being unselectable bug.
* Fixed indentations.

* Dismiss nano when starting a MSS ride

* Sneak, Invisibility and Bugfixes

* Sneak and invisibility affect mob aggro.
* Possibly bugfixed equips not showing to other players.
* Aggro checking is less likely to cause nullptr related crashes.

* Group PR cleanup.

* Made sure to label all hacky workarounds
* Implemented the Antidote nano power the right way
* Cleaned up the way various little things are written

Didn't have the opportunity to actually test groups.

Co-authored-by: CakeLancelot <CakeLancelot@users.noreply.github.com>
Co-authored-by: CPunch <sethtstubbs@gmail.com>
Co-authored-by: dongresource <dongresource@protonmail.com>
2020-10-05 01:54:08 +02:00
131eb94919 removed stale comment 2020-10-04 12:52:16 -05:00
755bb75306 U8toU16 now respects buffer sizes 2020-10-04 12:50:58 -05:00
5015e2575d Merge pull request #128 from gsemaj/monkeys
Add MSS gruntwork commands
2020-10-04 18:33:32 +02:00
Gent
a9837d6c1b Finish MSS commands + convert stack to vector 2020-10-03 11:24:45 -04:00
Gent
47da895544 Add basic MSS gruntwork commands 2020-10-03 11:20:51 -04:00
a852c26e5e minor command refactor 2020-10-02 19:20:59 -05:00
316239dadc comments + better test command 2020-10-02 18:59:07 -05:00
f5939353b1 added basic command handler 2020-10-02 18:50:47 -05:00
f82d203377 Merge pull request #127 from gsemaj/nanos
Fix nano buffs remaining after nano stamina runs out
2020-10-02 23:26:36 +02:00
Gent
cbd04c2ce6 Unsummon nano when stamina runs out 2020-10-02 17:03:11 -04:00
Gent
1b55ab44e3 Add helper function to get socket from player ID
Co-authored-by: Jade <jadeshrinemaiden@gmail.com>
2020-10-02 17:02:31 -04:00
21b7500e13 Define NOMINMAX globally to work around VS nonsense. 2020-10-02 22:04:23 +02:00
35a2110698 Save connected players to DB when gracefully terminating the server. 2020-10-02 19:34:09 +02:00
8a144a359f It hasn't been a landwalker for a while now. 2020-10-02 19:34:09 +02:00
2fe4b2bac1 Rewrote quest bitfield char[128] <-> int64_t[16] logic.
* This should fix the problem with some completed missions being
forgotten
* We no longer explicitly shuffle bits around. Instead we just cast
and copy the full buffer
* Character creation initializes the quest blob with 128 zeroes, since
that happens later on anyway, but we're robust against different quest
flag sizes just in case
* I haven't looked at the actual flag-setting logic, so if the bug is in
there, this won't fix that one, but it does fix the one where the least
significant bit of every 64-bit flag doesn't get saved to the blob
* I'm still cautious about storing the various bitfields as signed
values even though the client does it that way, since while shifting
into the sign bit is undefined behaviour in C/C++, it may *not* be in
C#. And of course the client implementation may just be buggy as well.
2020-10-02 19:34:09 +02:00
839f9a813c remove player from map in weird edgecase 2020-10-01 19:20:44 -05:00
4fe4aeb0d3 Properly clean up players that have been kicked as duplicates.
This should fix the issue with null pointers in PlayerManager::players.
2020-10-02 01:37:50 +02:00
600c26024b Merge pull request #124 from gsemaj/bugfix1
Fixed crash on killing summoned NPC
2020-10-01 17:03:40 +02:00
Gent
3c734e3e76 Fixed crash on killing summoned NPC 2020-10-01 10:36:52 -04:00
4cd7b7cb53 added basework for instancing 2020-09-30 20:44:37 -05:00
8ff97ec0b3 Merge pull request #123 from gsemaj/items
Add simple item stacking logic
2020-09-30 18:37:31 -05:00
5f65c1530b Merge pull request #122 from JadeShrineMaiden/bugfix
Various bugfixes and adjustments
2020-09-30 18:37:16 -05:00
Gent
2c831ee115 Match maxed stack behavior to OG 2020-09-30 16:23:46 -04:00
Gent
941e986ee1 Fix incorrectly set item cost 2020-09-30 13:30:19 -04:00
Gent
1eb806af58 Add item stacking logic 2020-09-30 13:29:56 -04:00
ab990116a2 Various bugfixes and adjustments
* Nano missions should now stop repeating.
* Bitwise operators are now used to handle buff/debuff bitfields.
* Changing nano powers will no longer grant you infinite buffs.
* Mobs now heal up client-side after retreating, this comes with candy effect being played however.
* Lower level mobs now hit harder.
* Nanos drain stamina quicker when they grant passive powers.
* Healing, damage and leech powers scale up with your level.
* Player on player damage now accounts for damage and armor.
2020-09-29 22:27:48 +01:00
fb281b0237 Lock all Database operations.
All DB functions that are called outside of Database.cpp are now locked
by the same mutex. This might be a bit overkill, but it's not a hot code
path, so it doesn't matter. Better to avoid the potential deadlocks if
we made it too granular.

From now on a clear distinction must be made between external and
internal functions in Database.cpp, or else deadlock will occur.

Note that sqlite database operations are already locked, but if execute
multiple transactions within the same operation, it could have still
caused problems.

I also removed the DbPlayer fetch when writing to DB by making it a part
of the Player struct. This, by itself, should have fixed the crash we found.
2020-09-29 16:47:39 +02:00
3f35d2e960 sanity check to fix possible FPE 2020-09-28 15:31:01 -05:00
884b844d65 minor refactoring 2020-09-28 13:11:13 -05:00
4079806436 Support the /batteryN and /batteryW commands.
Also up the PVP damage to 700.
2020-09-28 18:54:39 +02:00
efb3df7133 Implemented GM SpecialState Handling
Co-authored-by: dongresource <dongresource@protonmail.com>
2020-09-27 22:19:21 +02:00
c9be0e5402 Do not tick dead players.
This fixes the "dancing in Fusion Matter" bug.
Also (temporarily?) added updateNPCPosition() to the suppression list.
Might want to take it out again (along with some of the other ones) if
we do end up implementing Chunk GC.
2020-09-27 20:29:26 +02:00
97c2c532f1 Merge pull request #117 from gsemaj/slider
Load sliders from paths.json
2020-09-27 20:03:41 +02:00
a324f3fda9 Merge pull request #118 from JadeShrineMaiden/moblocomotion
Greased up enemies
2020-09-27 20:02:52 +02:00
6ea47ddb56 Fixes.
* Mobs should account for height when aggroing on nearby players
* We don't need to keep track of lastHealTime for each player separately
* Warp attendants no longer steal the players weapon and money
2020-09-27 19:58:37 +02:00
2b4a1387f9 stubbed BuddyManager 2020-09-27 12:23:49 -05:00
dacae8d6de Merge pull request #120 from gsemaj/bugfix1
Combat bug fixes
2020-09-27 02:09:01 -05:00
Gent Semaj
b03cc563eb Fix mobs not aggroing 2020-09-27 03:05:55 -04:00
Gent Semaj
dd374b2ea1 Fix boosts and potions not updating on reward 2020-09-27 03:05:55 -04:00
f8f2088e38 fixed player health regen 2020-09-27 01:13:27 -05:00
062302a7aa fixed potential threading/socket issue 2020-09-27 00:12:26 -05:00
a5d3160588 Mob movement smoothening + Bugfixes
* Mobs now move at a tickrate per second of 2 as opposed to less than 1 before.
* How lerping works was changed slightly, mobs are bumped down to half the speed to account for the higher tickrate.
* Damage formula was altered to more closely match the OG game.
2020-09-27 05:43:50 +01:00
4fea2ae896 Variable damage to/from mobs
* Player weapons and armor ratings are taken into account when damaging/getting damaged by mobs.
* Players have a 5% chance to critical strike mobs, this doubles the player's weapon power.
* Aside from player and mob stat based damage variance, there is also an inherent 20% variance to any damage.
2020-09-27 02:53:03 +01:00
Gent
56a92d302f Add curve parameter to lerp 2020-09-26 19:24:07 -04:00
Gent
0ea5712f8c Load sliders from paths.json 2020-09-26 19:24:07 -04:00
b4fb449e69 Fix VS build. 2020-09-27 01:00:31 +02:00
4fa6618abb Implemented player tick (health/nano stamina).
* The player now heals while not in combat
* Nanos lose stamina while active, regain it while resting
* Using active nano powers drains stamina
* Standing in FM patches/lakes now deals damage
* Fixed a memory error in npcAttackPc()
* Mobs now aggro when a player gets close
* Mobs now give up the chase if the player gets out of the combat zone;
they no longer try chasing until they themselves have left it
* Added a few missing break statements in the loops in BuddyManager

Other players are not yet instantly notified of health/stamina updates,
as finding the correct way to do this has proven tricky. FM patch damage
updates other player's views just fine, though.
2020-09-27 00:16:15 +02:00
43d268e142 Enough with the plungers already.
At least from killed mobs.
2020-09-26 16:45:32 +02:00
9657aaf202 Tuned various values.
* The player no longer receives the Blossom nano mission until they've
left the future
* As a temporary measure for the sake of the public server, Fusions in
respawn in their lairs after 2.5 minutes
* Changed default player damage value to 150
* Mobs now drop the correct amount of FM, as well as a close
approximation of the correct amount of taros
* You can no longer break the FM cap in the Future zone
* Fixed the updateFusionMatter() bug the right way this time
* Completing a nano mission now subtracts FM as it should
* Setting a Nano's power no longer reports 0 FM to the client
2020-09-26 03:48:45 +02:00
dccd92aff9 The Future is now playable.
* Sync'd tdata so it has all the lair NPCs
* Bypassed Eduardo escort task
* Fixed the level 36 updateFusionMatter() memory error
* Fixed qitems being deleted even if the player fails to complete the
mission due to not having any inventory space, thus softlocking the
mission
2020-09-25 23:10:02 +02:00
1b35aab958 Merge pull request #115 from kamilprzyb/master
Quick fix for saving nano missions
2020-09-25 15:06:14 -05:00
6b577ed642 Merge pull request #116 from gsemaj/bugfix1
Fix client/server summoned nano discrepancy on revive
2020-09-25 15:05:40 -05:00
Gent
0931cf1fbc Fix client/server summoned nano discrepancy on revive 2020-09-25 10:57:32 -04:00
kamilprzyb
805c64eff0 Merge branch 'master' of https://github.com/kamilprzyb/OpenFusion 2020-09-25 11:32:38 +02:00
kamilprzyb
8c63cd575c quick fix for saving running nano missions 2020-09-25 11:30:59 +02:00
5068b38c5e Merge pull request #100 from SengokuNadeko/master
Buddy System
2020-09-25 00:48:01 -05:00
231a4a441b Merge pull request #112 from gsemaj/slider
Load NPC paths from JSON + update submodule ref
2020-09-25 00:37:53 -05:00
kamilprzyb
d4f1515f5d Time to go, gumballs and nano potions (#113)
* set up "go to the time machine" button working

* warping to the past now sets PayZoneFlag and removes all active missions

* added gumballs functionality

* added nano potions functionality

* formatting fix
2020-09-25 00:35:27 -05:00
e5a24bcb70 Merge pull request #114 from gsemaj/bugfix1
Prevent player position from updating when flying on monkey
2020-09-25 00:33:42 -05:00
874479d1cf fixed NPC spawning bug 2020-09-24 21:32:14 -05:00
8f84c4c2f8 updateNPCPosition now tracks chunks 2020-09-24 20:58:20 -05:00
kamilprzyb
320a82997a formatting fix 2020-09-25 03:00:50 +02:00
kamilprzyb
d87306930d added nano potions functionality 2020-09-25 02:39:30 +02:00
kamilprzyb
86c1cbd0f2 added gumballs functionality 2020-09-25 02:14:11 +02:00
279cb78d5f Mob-related cleanup.
* NPCs now keep track of their chunk information like PlayerView does
for players
* NPCManager::sendToViewable() parallels PlayerManager::sendToViewable()
* Nano damage and debuffs now count as attacking a mob
* Mobs will de-aggro if something else killed their target
2020-09-25 02:00:26 +02:00
72d625fd8d Summoned mobs are now actually treated as mobs.
Unfortunetly, this necessitated keeping around yet more JSON objects for
the duration of the server's runtime.
It also involved unifying the way NPC IDs are handled, such that they
may be allocated and deallocated out of order.

If any NPCID-related bugs occour, this commit should be regarded as
the prime suspect.
2020-09-25 00:51:18 +02:00
Gent
db33ca2bbb Prevent player position from updating when flying
Only chunk is updated instead to maintain visibility
2020-09-24 18:43:58 -04:00
cfb3d25bc5 Lerp mob movement a little.
We'll lerp a little harder later. Also retreat if kited
too far.
2020-09-24 23:11:14 +02:00
1f18104a6f Mobs fight back now.
There is still a lot of tuning, lerping and cleanup to do.
2020-09-24 23:11:14 +02:00
006d1000c7 Add Address Sanitizer suppression list.
This replaces the unnecessary deallocations on program termination.
Passing in the suppression list environment variable via setenv()
doesn't seem to work, so I've added a comment in the Makefile to explain
invocation.
2020-09-24 23:11:14 +02:00
kamilprzyb
1874f1081b warping to the past now sets PayZoneFlag and removes all active missions 2020-09-24 22:32:09 +02:00
kamilprzyb
df936e8c9c set up "go to the time machine" button working 2020-09-24 21:51:25 +02:00
Gent
72c16587e0 Load NPC paths from JSON 2020-09-24 10:32:46 -04:00
c33f218e56 updateFusionMatter now sets fusion matter 2020-09-23 16:04:58 -05:00
4caca07856 Merge pull request #109 from gsemaj/slider
Basic slider implementation
2020-09-23 14:49:26 -05:00
63c14aff58 Merge pull request #110 from kamilprzyb/vehicles_pr
Fixed Time and implemented vehicles expiring
2020-09-23 14:48:38 -05:00
78930916ad added support for nano missions 2020-09-23 14:44:27 -05:00
SengokuNadeko
9cfced88c9 Stubbed buddy warping
After testing warping a lot I saw how buggy it is. I decided to stub it for this PR and work on it in a later PR.
2020-09-23 13:52:21 -04:00
Gent
f2596bfb6a Add NPC chunk management + Generalize lerp 2020-09-23 12:06:25 -04:00
Gent
65bd2d120b Add NPC pathing queues 2020-09-23 10:29:29 -04:00
kamilprzyb
7bcdc111da fixed comments formating, added zeroing out player->toRemoveVehicle after removing vehicle 2020-09-23 11:21:32 +02:00
kamilprzyb
016c48645e adjusted timestamp function to always return value in seconds 2020-09-23 11:05:18 +02:00
kamilprzyb
09f1f67778 implemented checking vehicle time limit on use 2020-09-23 10:20:47 +02:00
Gent
7dfc888552 Basic slider implementation 2020-09-23 00:08:51 -04:00
Gent
6f05f0f2c8 Add NPCClass enum 2020-09-22 23:41:43 -04:00
c722044bf5 The Sleep, Stun and Snare powers now affect mobs.
They currently don't expire after a set amount of time.
Attacking a sleeping monster does wake it up, but doesn't remove the
debuff status client-side yet.
2020-09-22 22:22:10 +02:00
kamilprzyb
076f89927d changed vehicle iTimeLimit to be stored in seconds instead of miliseconds 2020-09-22 21:49:24 +02:00
95a79ec815 Switched MobManager::step() to the timer.
It doesn't really need to run on every shard step. This makes it a lot
less hot.
2020-09-22 21:31:08 +02:00
kamilprzyb
7ba9b9a54f replaced high_resolution_clock with system_time for timestamps 2020-09-22 21:15:47 +02:00
ba5998d53a Added a config option to disable mob movement and combat.
This will come in handy when gruntwork starts.
Also fixed a bug where the ACCLEVEL setting was read as a boolean.
2020-09-22 20:53:44 +02:00
ac1fd1e5be Implemented mob roaming.
Will likely need further tuning.
Mobs in vacant chunks are skipped.
2020-09-22 20:33:10 +02:00
94ab5b8b64 Seed the random number generator. 2020-09-22 20:32:40 +02:00
SengokuNadeko
e0e474924d Implemented buddy warping
Buddy warping now works, if anything else needs to be added/redone in regards to this feel free to let me know ^
2020-09-22 13:02:00 -04:00
8896a103ba Merge pull request #107 from gsemaj/monkeys
Sync tabledata submodule + fix transport bugs
2020-09-22 16:38:07 +02:00
kamilprzyb
0931c88541 fixed size bug 2020-09-22 13:41:28 +02:00
kamilprzyb
5a58908462 vehicle has a proper expiration day when bought, implemented checking expired vehicles while login 2020-09-22 13:16:09 +02:00
kamilprzyb
00f64ce992 added sanity check for killing mission mobs 2020-09-22 10:18:29 +02:00
Gent
153b3a9ef5 Sync tabledata submodule + fix transportation bugs 2020-09-22 00:42:46 -04:00
CakeLancelot
adf017b07c Move from isGM to Account Levels (#106)
Co-authored-by: dongresource <dongresource@protonmail.com>
2020-09-22 04:26:12 +02:00
5d8bb7f8a5 Merge pull request #95 from gsemaj/monkeys
Paths framework + Monkey Skyway System framework
2020-09-22 03:54:01 +02:00
Gent
2c8243e136 General MSS fixes + tweaks
Add alert message for unpathed skyway routes
Fix overflow during lerp + add Future routes
Add documentation for MSS
Fix potential MSS registration bug
Minor tweaks + styling
Update packet broadcast
2020-09-21 21:43:39 -04:00
12fbdc9621 Wait about 2s before despawning killed mobs.
This gives them enough time to play their death animations before
they disappear.
2020-09-22 00:23:18 +02:00
a768a4f539 Fixed halved NPC distance and renamed config option to chunksize. 2020-09-21 23:30:05 +02:00
d6357197d3 Use the chunk's visibility lists when respawning mobs. 2020-09-21 23:13:13 +02:00
4cc1cf4f7e minor chunk refactor 2020-09-21 14:55:34 -05:00
b67a0b6946 removed usewebapi 2020-09-21 14:49:08 -05:00
kamilprzyb
5e0948ea93 Database saving update (#104)
* implemented saving BatteryN and BatteryW

* implemented saving mentor

* moved int64->blob parsing to a separate function

* moved parsing blob->int64 to a separate function

* added functions for parsing int32->blob and vice versa

* added functions for parsing int16->blob and vice versa

* WIP saving quest items and active tasks

* Quest items are stored in inventory table instead of blob

* added sanity check for missionId

* saving active missions works

* removed unneccesary include

* implemented saving warplocationflag, skywaylocationflag and currentmissionid in database

* INFO DB message now shows how many accounts and player characters are in the database

* fixed dbsaveinterval being in [login] instead of [shard]

* fixed mission quit:
- fixed wrong json name, causing qitems not deleting properly
- quitting mission now resets npc kill count

* adjusted saving active missions

* removed blob parsing functions that ended up being unused

* removed accidentaly added include

* removed sending PCStyle2 on Player Enter

* added a sanity check in itemMoveHandler

* removed MapNum from PCLoad, as client doesn't even read it

* set BuddyWarpCooldown to 60s on PCLoad

* fixed a bug causing EXIT DUPLICATE not working

* added creation and last login timestamps to accounts and players

* added a sanity check for P_CL2LS_REQ_PC_EXIT_DUPLICATE

* implemented web api support, toggled by new setting (off by default)

* add usewebapi to config

Co-authored-by: Gent <gentsemaj@live.com>
2020-09-21 14:43:53 -05:00
SengokuNadeko
90134cd1fa Buddy deleting/blocking works and code clean up
Added in buddy deleting and buddy blocking. I also cleaned up some of the code and added some comments to the code.
2020-09-21 14:28:04 -04:00
321dca3f79 Use PC_MAXHEALTH() in the rest of the codebase.
+ minor fixups
2020-09-21 19:51:30 +02:00
113ecc8f60 Refactored passive nano powers. 2020-09-21 19:51:30 +02:00
dc9de5a54a Cleaned up implementation of active nano powers. 2020-09-21 19:51:30 +02:00
0fc072d591 Initial implementation of nano powers. 2020-09-21 19:51:30 +02:00
24341c578a Merge pull request #105 from FinnHornhoover/mingw-version-check-fix
Fix Make Linux GCC warning and Windows GIT_VERSION error
2020-09-20 20:14:48 -05:00
FinnHornhoover
a05bb15697 fixed makefile append and mingw bugs 2020-09-21 02:12:34 +03:00
Gent
135424b855 Update player visibility on skyway 2020-09-20 15:24:17 -04:00
Gent
cb984c029b Add paths JSON file 2020-09-20 15:24:17 -04:00
Gent
a5ffe26c44 Move lerp to td init, add variable gap size 2020-09-20 15:24:17 -04:00
Gent
6a78a301c9 Skyway lerp tuning 2020-09-20 15:24:17 -04:00
Gent
a5c40b66f5 Add basic Monkey Skyway functionality 2020-09-20 15:24:17 -04:00
CakeLancelot
94583e534b Merge pull request #102 from darkredtitan/LoadisGMfromConfig.ini
Respect config.ini GM settings
Upon character creation, the GM value from the config will now be used instead of always being set to false. The default in Settings.cpp has also been changed to be consistent with the config.ini in the repo.
2020-09-20 14:04:33 -05:00
darkredtitan
ff7c78d545 Change default value for GM to true 2020-09-20 20:50:58 +02:00
CakeLancelot
77df7b7160 Merge pull request #92 from gsemaj/crocpot
Implement Croc Pot item combining
Fix vehicle type override
2020-09-20 13:40:36 -05:00
darkredtitan
6eb21e6d67 Respect config.ini GM settings 2020-09-20 19:53:33 +02:00
228a181b74 removed ChunkManager::removeNPC() 2020-09-20 10:34:43 -05:00
FinnHornhoover
27df1bd7d0 fixed indent 2020-09-20 01:24:42 +03:00
Raymonf
6a05ce4504 Add BuddyManager to Makefile 2020-09-19 17:16:47 -04:00
Gent
c6ec1c46c2 Add documentation for getItemType() 2020-09-19 16:38:50 -04:00
Gent
d1c5e272a8 Fix vehicle type override 2020-09-19 15:26:16 -04:00
SengokuNadeko
9bb19efc99 Updated main.cpp for buddy manager init function 2020-09-19 15:08:03 -04:00
SengokuNadeko
7757238a47 Buddy System draft (W.I.P)
The w.i.p buddy system
2020-09-19 15:07:09 -04:00
CakeLancelot
4d437bcb34 Merge pull request #97 from gsemaj/bugfix1
Preventing spawning of invalid items using /itemN
Fix fusion matter amount not displaying on enter
2020-09-19 07:46:56 -05:00
Gent
5dbca0b7b1 Fix fusion matter amount not displaying on enter 2020-09-18 23:26:20 -04:00
ce9285bab5 support for /unsummon 2020-09-18 16:24:15 -05:00
Gent
cd7fec2d5b Fix invalid item bug 2020-09-18 14:45:51 -04:00
d9d781c37d whoops, everyone had gm 2020-09-18 02:10:30 -05:00
Gent
b929d12902 Implement Croc Pot item combining 2020-09-17 23:55:57 -04:00
9f78735caa fixed socket reference bug 2020-09-17 22:44:37 -05:00
31ef03610d temp fix and small refactor 2020-09-17 21:41:09 -05:00
22e3e9e4de fixed 'player clone' bug 2020-09-17 18:03:23 -05:00
f4db0830ba huge refactoring, chunking added 2020-09-17 17:45:43 -05:00
001564a257 Hotfix to stop crashing the server.
This will need to be fixed properly; probably while we implement
chunking.
2020-09-17 21:22:31 +02:00
e79f179628 Mobs respawn now.
Began work on mob logic. Also cleaned up TableData a little.
2020-09-17 01:43:48 +02:00
027b783571 Visual studio doesn't use -ldl. 2020-09-16 23:13:06 +02:00
e03da83ff3 Rearranged the codebase a little.
* Deleted empty Player.cpp
* Moved the helper functions from the obsolete CNStructs.cpp into
main.cpp and deleted it
* Renamed CombatManager to MobManager, as that will likely become it's
main focus soon
2020-09-16 21:46:15 +02:00
5efc8ac089 Switched getTime() to std::chrono on all platforms. 2020-09-16 20:14:00 +02:00
efda6673b5 Print server version when starting up.
Also added -ldl to fix cmake compilation on Unix systems.
2020-09-16 20:12:56 +02:00
f7571607ba Only send live checks when the connection has been silent for a while.
Also:
* Made the timeout configurable
* Removed the stale randomcharacters config option
* Switched to time_t for time values, even though it doesn't really
matter that much
* Halved the keepAliveTimer frequency
2020-09-16 17:45:53 +02:00
CakeLancelot
501d153894 Update README.md
Inventories are now saved to DB
2020-09-15 06:02:39 -05:00
4d21410980 Merge PR #87 2020-09-14 18:42:03 +02:00
Gent
148d90f4f1 "Boosts and potions!"
Fixed crate opening such that the item has an iOpt of 1.
2020-09-14 18:36:50 +02:00
Gent
a976fef2b4 Implement vendor stack logic 2020-09-14 10:25:15 -04:00
Gent
da8c833587 Implement buyback
Sellability tweak


Add additional item categories
2020-09-14 10:25:15 -04:00
Gent
c91022030c Load item tables + price implementation 2020-09-14 10:25:15 -04:00
Gent
f55cc8f36d Load vendor tables 2020-09-14 10:24:55 -04:00
9cc5f3e4d5 Cleaned up comment formatting.
The incantation was: sed -i 's://\([^ ]\):// \1:' src/*.[ch]pp

Lines of code that was commented out were then manually reverted.
2020-09-14 16:07:05 +02:00
131997f34f Cleaned up all whitespace issues.
The incantation was: sed -i 's/[ \t]*$//g' src/*.[ch]pp
2020-09-14 16:07:05 +02:00
ed86bc9160 Assorted cleanups and fixes.
* Clean up spacing/indentation
* Proper enum formatting
* Fix nano dismissal (for real this time)
* Do not copy Player struct when a pointer is right there
* Stop looking after the trade partner has been found
* Make sure we're shifting unsigned values (and 64-bit when they need to be)
* Look for JSONs in tdata/
* Add a dbsaveinterval to the example config.ini, in the login category
2020-09-14 16:07:05 +02:00
darkredtitan
38d5998a6e Sanity checks for creating and deleting characters.
Co-authored-by: kamilprzyb <kamilprzybspam@wp.pl>
2020-09-14 16:07:02 +02:00
kamilprzyb
c7189a5cef added saving missions 2020-09-14 16:07:02 +02:00
kamilprzyb
de15e2004b added bank functionality, refactored itemMoveHandler
Co-authored-by: Cake Lancelot <CakeLancelot@users.noreply.github.com>
2020-09-14 16:07:02 +02:00
darkredtitan
480cca82fa Make name checking case insensitive.
Co-authored-by: kamilprzyb <kamilprzybspam@wp.pl>
2020-09-14 16:06:57 +02:00
kamilprzyb
3d83f93167 added periodic DB saves, fixed some settings 2020-09-14 01:38:27 +02:00
kamilprzyb
1d9a7139a8 deleteing player deletes inventory and nanos, minor tweaks to settings 2020-09-14 01:38:27 +02:00
kamilprzyb
2fd7a8c6fc Implemented saving inventory and nanos 2020-09-14 01:38:27 +02:00
kamilprzyb
fc57cae37d added saving character when a connection with shard is killed 2020-09-14 01:38:04 +02:00
Gent Semaj
3cfec7aab3 Implement transportation framework + full S.C.A.M.P.E.R. functionality (#86)
* Add transportation registration
* Add S.C.A.M.P.E.R. functionality

Co-authored-by: dongresource <dongresource@protonmail.com>
2020-09-13 22:26:16 +02:00
CakeLancelot
29e53117e7 Merge pull request #72 from FinnHornhoover/msys-mingw-compile-fix
Warnings and compile errors for MSYS2 MinGW64
2020-09-13 07:35:32 -05:00
c1ac2250a0 Minor changes.
This was me fixing a few bugs and finding that Gent was working on some
of the same ones, then awkwardly merging the changes together.
2020-09-12 22:43:04 +02:00
a4716b0164 Merge pull request #82 from gsemaj/nanos
Improve revival code
2020-09-12 22:10:49 +02:00
FinnHornhoover
91f512d740 added version checking for GCC 2020-09-12 22:27:03 +03:00
Gent
4880e4af12 Revamp revival code 2020-09-12 14:21:36 -04:00
fe370df534 Merge pull request #81 from gsemaj/auth
Validate cookie data
2020-09-12 18:03:26 +02:00
Gent
2b1a028b3d Validate cookie data 2020-09-12 11:41:31 -04:00
be99714495 Possible fix for the login error bug. 2020-09-12 17:09:11 +02:00
4c06163b51 Properly validate vendor logic.
Also, iTimeLimit should be 0.
2020-09-12 16:10:53 +02:00
0c97969757 Merge pull request #80 from gsemaj/vendor2
Add sell functionality to vendors
2020-09-12 16:01:23 +02:00
Gent
4e7352da66 Add sell functionality to vendors 2020-09-11 22:04:23 -04:00
5747c24479 [bugfix] Don't set iType on empty item slots.
This confuses ItemManager::findFreeSlot().
2020-09-12 02:25:45 +02:00
579aa9d31d Save pointer to Player struct in CNSocket.
This is an insignificant optimization now, but will be necessary when we
start switching around the the algorithms and datastructures used in
proximity detection.
2020-09-12 01:22:58 +02:00
Ariii
3865249387 Vendors, set nano skill command + serverside command issues fixed (#74)
Added basic shopkeeper functions, a player can buy the preset 3 items (cannonbolt set), all shopkeepers have the same items atm (need to check the shopkeeper tabledata), setting itemprice is something I didn't figure out.
Added set nano skill command
Implemented a switch for certain commands like health/taros/fusionmatter etc to be handled on the serverside aswell

Co-authored-by: dongresource <dongresource@protonmail.com>
2020-09-11 23:19:03 +02:00
468840c9ea Fixed gcc vs clang preprocessor tomfoolery. 2020-09-11 00:19:21 +02:00
52f02168bc Implemented quest item cleanup and fixed a few bugs. 2020-09-11 00:19:21 +02:00
ddb5f782b7 Refactored mission data and implemented quest item mob drops.
Most future missions are now playable.
Quest items aren't being properly cleaned up yet.
2020-09-11 00:19:21 +02:00
3665dc2c93 [WIP] Incremental mission save 2
This commit (and the previous one) exist to document the first approach I
took to storing mission data. It's only here for posterity. This comment
was added while rebasing.
2020-09-11 00:19:19 +02:00
ae654f996c [WIP] Incremental mission save 1
This commit (and the next one) exist to document the first approach I
took to storing mission data. It's only here for posterity. This comment
was added while rebasing.
2020-09-11 00:19:16 +02:00
e33b7f20e9 [bugfix] Preserve Taros and FM when opening Crates. 2020-09-11 00:08:26 +02:00
5b49e71de7 Implemented mission rewards.
Might need to refactor item drops, especially after implementing
task-result quest item drops.
2020-09-11 00:08:26 +02:00
3172724596 Implemented the mob kill counter in missions and fixed a bug. 2020-09-11 00:08:26 +02:00
8887c6349b Add support for cookie-based login back in. 2020-09-11 00:08:26 +02:00
CakeLancelot
6e0b101a76 Merge pull request #78 from JadeShrineMaiden/additions2
Levelling up added to /nano
2020-09-10 12:02:28 -05:00
29cde56fb1 Levelling up added to /nano
- Using /nano levels up Player, a player cannot level down.
2020-09-10 17:51:52 +01:00
e65f07780b Merge branch 'master' of https://github.com/OpenFusionProject/OpenFusion 2020-09-09 12:06:35 -05:00
eb1ad6bb37 switched to dumped XDT & moved to a submodule 2020-09-09 12:06:22 -05:00
CakeLancelot
e409b8bb39 Add .gitattributes file, exclude src/contrib from linguist (#73) 2020-09-08 19:55:59 -04:00
45a33758a5 Account for the size of packet length and ID in validation functions. 2020-09-08 03:06:55 +02:00
FinnHornhoover
266fddbffa fixed warnings and compile errors for msys2 2020-09-08 03:41:13 +03:00
e90ae10746 Summoned NPC are now visible to other players. 2020-09-07 22:12:53 +02:00
b797993014 updated readme 2020-09-07 13:07:04 -05:00
60 changed files with 8235 additions and 1253 deletions

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
src/contrib/* linguist-vendored

2
.gitignore vendored
View File

@@ -11,3 +11,5 @@ build/
.vs/
.idea/
*.db
version.h
infer-out

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "tdata"]
path = tdata
url = https://github.com/OpenFusionProject/tabledata.git

View File

@@ -3,6 +3,8 @@ project(OpenFusion)
set(CMAKE_CXX_STANDARD 17)
execute_process(COMMAND git describe --tags OUTPUT_VARIABLE GIT_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE)
# OpenFusion supports multiple packet/struct versions
# 104 is the default version to build which can be changed
# For example: cmake -B build -DPROTOCOL_VERSION=728
@@ -33,7 +35,9 @@ endif()
include_directories(src)
file(GLOB_RECURSE SOURCES src/**.cpp src/**.hpp src/**.c src/**.h)
file(GLOB_RECURSE SOURCES src/**.cpp src/**.hpp src/**.c src/**.h version.h)
configure_file(version.h.in ${CMAKE_SOURCE_DIR}/version.h @ONLY)
add_executable(openfusion ${SOURCES})
@@ -45,4 +49,5 @@ set_target_properties(openfusion PROPERTIES OUTPUT_NAME ${BIN_NAME})
if (NOT CMAKE_GENERATOR MATCHES "Visual Studio" AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND NOT CMAKE_GENERATOR MATCHES "MinGW Makefiles")
find_package(Threads REQUIRED)
target_link_libraries(openfusion pthread)
target_link_libraries(openfusion dl)
endif()

View File

@@ -1,8 +1,9 @@
CC=clang
CXX=clang++
# -w suppresses all warnings (the part that's commented out helps me find memory leaks, it ruins performance though!)
# If compiling with ASAN, invoke like this: $ LSAN_OPTIONS=suppressions=suppr.txt bin/fusion
CFLAGS=-O3 #-g3 -fsanitize=address
CXXFLAGS=-Wall -Wno-unknown-pragmas -std=c++17 -O2 -DPROTOCOL_VERSION=$(PROTOCOL_VERSION) #-g3 -fsanitize=address
CXXFLAGS=-Wall -Wno-unknown-pragmas -std=c++17 -O2 -DPROTOCOL_VERSION=$(PROTOCOL_VERSION) -DGIT_VERSION=\"$(shell git describe --tags)\" #-g3 -fsanitize=address
LDFLAGS=-lpthread -ldl #-g3 -fsanitize=address
# specifies the name of our exectuable
SERVER=bin/fusion
@@ -15,7 +16,9 @@ PROTOCOL_VERSION?=104
WIN_CC=x86_64-w64-mingw32-gcc
WIN_CXX=x86_64-w64-mingw32-g++
WIN_CFLAGS=-O3 #-g3 -fsanitize=address
WIN_CXXFLAGS=-Wall -Wno-unknown-pragmas -std=c++17 -O3 -fno-tree-dce -fno-inline-small-functions -DPROTOCOL_VERSION=$(PROTOCOL_VERSION) #-g3 -fsanitize=address
WIN_CXX_VANILLA_MINGW_OPT_DISABLES=-fno-tree-dce -fno-inline-small-functions
WIN_CXX_MSYS2_MINGW_OPT_DISABLES=-fno-tree-dce -fno-tree-fre -fno-tree-vrp -fno-ipa-sra
WIN_CXXFLAGS=-Wall -Wno-unknown-pragmas -std=c++17 -O3 $(WIN_CXX_OPT_DISABLES) -DPROTOCOL_VERSION=$(PROTOCOL_VERSION) -DGIT_VERSION=\"$(shell git describe --tags)\" #-g3 -fsanitize=address
WIN_LDFLAGS=-static -lws2_32 -lwsock32 #-g3 -fsanitize=address
WIN_SERVER=bin/winfusion.exe
@@ -28,23 +31,25 @@ CSRC=\
CXXSRC=\
src/ChatManager.cpp\
src/CombatManager.cpp\
src/CNLoginServer.cpp\
src/CNProtocol.cpp\
src/CNShardServer.cpp\
src/CNShared.cpp\
src/CNStructs.cpp\
src/Database.cpp\
src/Defines.cpp\
src/main.cpp\
src/MissionManager.cpp\
src/MobManager.cpp\
src/NanoManager.cpp\
src/ItemManager.cpp\
src/NPCManager.cpp\
src/Player.cpp\
src/PlayerManager.cpp\
src/settings.cpp\
src/TransportManager.cpp\
src/TableData.cpp\
src/ChunkManager.cpp\
src/BuddyManager.cpp\
src/GroupManager.cpp\
# headers (for timestamp purposes)
CHDR=\
@@ -61,7 +66,6 @@ CXXHDR=\
src/contrib/INIReader.hpp\
src/contrib/JSON.hpp\
src/ChatManager.hpp\
src/CombatManager.hpp\
src/CNLoginServer.hpp\
src/CNProtocol.hpp\
src/CNShardServer.hpp\
@@ -72,6 +76,7 @@ CXXHDR=\
src/contrib/INIReader.hpp\
src/contrib/JSON.hpp\
src/MissionManager.hpp\
src/MobManager.hpp\
src/NanoManager.hpp\
src/ItemManager.hpp\
src/NPCManager.hpp\
@@ -79,6 +84,10 @@ CXXHDR=\
src/PlayerManager.hpp\
src/settings.hpp\
src/TransportManager.hpp\
src/TableData.hpp\
src/ChunkManager.hpp\
src/BuddyManager.hpp\
src/GroupManager.hpp\
COBJ=$(CSRC:.c=.o)
CXXOBJ=$(CXXSRC:.cpp=.o)
@@ -98,6 +107,7 @@ windows : CFLAGS=$(WIN_CFLAGS)
windows : CXXFLAGS=$(WIN_CXXFLAGS)
windows : LDFLAGS=$(WIN_LDFLAGS)
windows : SERVER=$(WIN_SERVER)
windows : WIN_CXX_OPT_DISABLES=$(if $(filter-out 10, $(shell $(WIN_CXX) -dumpversion | egrep -o ^[0-9]+)), $(WIN_CXX_VANILLA_MINGW_OPT_DISABLES), $(WIN_CXX_MSYS2_MINGW_OPT_DISABLES))
.SUFFIX: .o .c .cpp .h .hpp
@@ -114,13 +124,19 @@ $(SERVER): $(OBJ) $(CHDR) $(CXXHDR)
mkdir -p bin
$(CXX) $(OBJ) $(LDFLAGS) -o $(SERVER)
# compatibility with how cmake injects GIT_VERSION
version.h:
touch version.h
src/main.o: version.h
.PHONY: all windows clean nuke
# only gets rid of OpenFusion objects, so we don't need to
# recompile the libs every time
clean:
rm -f src/*.o $(SERVER) $(WIN_SERVER)
rm -f src/*.o $(SERVER) $(WIN_SERVER) version.h
# gets rid of all compiled objects, including the libraries
nuke:
rm -f $(OBJ) $(SERVER) $(WIN_SERVER)
rm -f $(OBJ) $(SERVER) $(WIN_SERVER) version.h

View File

@@ -3,7 +3,7 @@
[![AppVeyor](https://ci.appveyor.com/api/projects/status/github/OpenFusionProject/OpenFusion?svg=true)](https://ci.appveyor.com/project/OpenFusionProject/openfusion)
[![Discord](https://img.shields.io/badge/chat-on%20discord-7289da.svg?logo=discord)](https://discord.gg/DYavckB)
OpenFusion is a landwalker server for FusionFall. It currently supports versions `beta-20100104` and `beta-20100728` of the original game.
OpenFusion is a reverse-engineered server for FusionFall. It currently primarily targets version `beta-20100104` and has some support for version `beta-20100728` of the original game.
Further documentation pending.
@@ -11,7 +11,7 @@ Further documentation pending.
tl;dr:
1. Download the client+server bundle from [here](https://github.com/OpenFusionProject/OpenFusion/releases/download/1.0/OpenFusion.zip).
1. Download the client+server bundle from [here](https://github.com/OpenFusionProject/OpenFusion/releases/download/1.1/OpenFusion.zip).
2. Run `FreeClient/installUnity.bat` once
From then on, any time you want to run the "game":
@@ -91,15 +91,15 @@ A detailed guide is available [in the wiki](https://github.com/OpenFusionProject
If you'd like to contribute to this project, please read [CONTRIBUTING.md](CONTRIBUTING.md).
## "Gameplay"
## Gameplay
Notice the quotes. This is not a full-fledged game that can be played.
It's what's called a landwalker; enough of the server has been implemented to allow players to run around in the game world, and not much else.
The goal of the project is to faithfully recreate the game as it was at the time of the targeted build.
The server is not yet complete, however, and some functionality is still missing.
Because the server is still in development, ordinary players are allowed access to a few admin commands:
![](res/sane_upsell.png)
To make your landwalking experience more pleasant, you can make use of a few admin commands to get around easier:
### Movement commands
* A `/speed` of around 2400 or 3000 is nice.
* A `/jump` of about 50 will send you soaring
@@ -118,4 +118,4 @@ To make your landwalking experience more pleasant, you can make use of a few adm
## Accounts
A basic account system has been added, when logging in if the username doesn't exist in the database, a new account with the provided password will be made and you'll be automatically logged in. Otherwise a login attempt will be made. A username must be between 4 and 32 characters, and a password must be between 8 and 32 characters otherwise the account will be rejected. Characters currently save only upon creation, any items add/traded will not be saved.
A basic account system has been added, when logging in if the username doesn't exist in the database, a new account with the provided password will be made and you'll be automatically logged in. Otherwise a login attempt will be made. A username must be between 4 and 32 characters, and a password must be between 8 and 32 characters otherwise the account will be rejected.

View File

@@ -9,29 +9,56 @@ verbosity=1
[login]
# must be kept in sync with loginInfo.php
port=8001
# enables two randomly generated characters in the
# character selection menu for convenience
randomcharacters=true
# will all custom names be approved instantly?
acceptallcustomnames=true
# how often should everything be flushed to the database?
# the default is 4 minutes
dbsaveinterval=240
# Shard Server configuration
[shard]
port=8002
ip=127.0.0.1
# distance at which other players and NPCs become visible
playerdistance=20000
npcdistance=16000
# distance at which other players and NPCs become visible.
# this value is used for calculating chunk size
viewdistance=30000
# time, in milliseconds, to wait before kicking a non-responsive client
# default is 1 minute
timeout=60000
# should mobs move around and fight back?
# can be disabled for easier mob placement
simulatemobs=true
# little message players see when they enter the game
motd=Welcome to OpenFusion!
# NPC json data
npcdata=data/NPCs.json
# warp target json data
warpdata=data/warps.json
npcdata=tdata/NPCs.json
# xdt json data
xdtdata=tdata/xdt.json
# mob json
mobdata=data/mobs.json
# is everyone a GM?
gm=true
mobdata=tdata/mobs.json
# path json
pathdata=tdata/paths.json
# drop json
dropdata=tdata/drops.json
# gruntwork output (this is what you submit)
gruntwork=tdata/gruntwork.json
# account permission level that will be set upon character creation
# 1 = default, will allow *all* commands
# 30 = allow some more "abusable" commands such as /summon
# 50 = only allow cheat commands, like /itemN and /speed
# 99 = standard user account, no cheats allowed
# any number higher than 50 will disable commands
accountlevel=1
# should mobs drop event crates?
# 0 = no event
# 1 = Knishmas
# 2 = Halloween
# 3 = Easter
eventmode=0
# percent chance of an event crate dropping each kill
eventcratechance=10
# spawn coordinates (Z is height)
# the supplied defaults are at Sector V (future)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

441
src/BuddyManager.cpp Normal file
View File

@@ -0,0 +1,441 @@
#include "CNShardServer.hpp"
#include "CNStructs.hpp"
#include "ChatManager.hpp"
#include "PlayerManager.hpp"
#include "BuddyManager.hpp"
#include <iostream>
#include <chrono>
#include <algorithm>
#include <thread>
void BuddyManager::init() {
REGISTER_SHARD_PACKET(P_CL2FE_REQ_REQUEST_MAKE_BUDDY, requestBuddy);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_FIND_NAME_MAKE_BUDDY, reqBuddyByName);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_ACCEPT_MAKE_BUDDY, reqAcceptBuddy);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_FIND_NAME_ACCEPT_BUDDY, reqFindNameBuddyAccept);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_SEND_BUDDY_FREECHAT_MESSAGE, reqBuddyFreechat);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_SEND_BUDDY_MENUCHAT_MESSAGE, reqBuddyMenuchat);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_GET_BUDDY_STATE, reqPktGetBuddyState);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_SET_BUDDY_BLOCK, reqBuddyBlock);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_REMOVE_BUDDY, reqBuddyDelete);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_BUDDY_WARP, reqBuddyWarp);
}
// Buddy request
void BuddyManager::requestBuddy(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_REQUEST_MAKE_BUDDY))
return; // malformed packet
Player* plrReq = PlayerManager::getPlayer(sock);
if (plrReq == nullptr)
return;
sP_CL2FE_REQ_REQUEST_MAKE_BUDDY* pkt = (sP_CL2FE_REQ_REQUEST_MAKE_BUDDY*)data->buf;
INITSTRUCT(sP_FE2CL_REP_REQUEST_MAKE_BUDDY_SUCC, resp);
CNSocket* otherSock = sock;
for (auto pair : PlayerManager::players) {
if (pair.second.plr->PCStyle.iPC_UID == pkt->iBuddyPCUID) {
otherSock = pair.first;
break;
}
}
PlayerView& plr = PlayerManager::players[otherSock];
resp.iRequestID = plr.plr->iID;
resp.iBuddyID = plr.plr->iID;
resp.iBuddyPCUID = plr.plr->PCStyle.iPC_UID;
sock->sendPacket((void*)&resp, P_FE2CL_REP_REQUEST_MAKE_BUDDY_SUCC, sizeof(sP_FE2CL_REP_REQUEST_MAKE_BUDDY_SUCC)); // informs the player that the request was sent
requestedBuddy(otherSock, plrReq, plr); // The other player will see the request
}
// Sending buddy request by player name
void BuddyManager::reqBuddyByName(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_PC_FIND_NAME_MAKE_BUDDY)) {
return; // malformed packet
}
sP_CL2FE_REQ_PC_FIND_NAME_MAKE_BUDDY* pkt = (sP_CL2FE_REQ_PC_FIND_NAME_MAKE_BUDDY*)data->buf;
Player* plrReq = PlayerManager::getPlayer(sock);
if (plrReq == nullptr)
return;
INITSTRUCT(sP_FE2CL_REP_PC_FIND_NAME_MAKE_BUDDY_SUCC, resp);
CNSocket* otherSock = sock;
int sizeOfRes = sizeof(pkt->szFirstName) / 9; // Maximum size of a player's first name
int sizeOfLNRes = sizeof(pkt->szLastName) / 17; // Maximum size of a player's last name
for (auto pair : PlayerManager::players) {
int sizeOfReq = sizeof(pair.second.plr->PCStyle.szFirstName) / 9;
int sizeOfLNReq = sizeof(pair.second.plr->PCStyle.szLastName) / 17;
if (BuddyManager::firstNameCheck(pair.second.plr->PCStyle.szFirstName, pkt->szFirstName, sizeOfReq, sizeOfRes) == true && BuddyManager::lastNameCheck(pair.second.plr->PCStyle.szLastName, pkt->szLastName, sizeOfLNReq, sizeOfLNRes) == true) { // This long line of gorgeous parameters is to check if the player's name matches :eyes:
otherSock = pair.first;
break;
}
}
resp.iPCUID = plrReq->PCStyle.iPC_UID;
resp.iNameCheckFlag = plrReq->PCStyle.iNameCheck;
memcpy(resp.szFirstName, plrReq->PCStyle.szFirstName, sizeof(plrReq->PCStyle.szFirstName));
memcpy(resp.szLastName, plrReq->PCStyle.szLastName, sizeof(plrReq->PCStyle.szLastName));
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_PC_FIND_NAME_MAKE_BUDDY_SUCC, sizeof(sP_FE2CL_REP_PC_FIND_NAME_MAKE_BUDDY_SUCC));
}
// Accepting buddy request
void BuddyManager::reqAcceptBuddy(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_ACCEPT_MAKE_BUDDY))
return; // malformed packet
sP_CL2FE_REQ_ACCEPT_MAKE_BUDDY* pkt = (sP_CL2FE_REQ_ACCEPT_MAKE_BUDDY*)data->buf;
Player* plrReq = PlayerManager::getPlayer(sock);
if (plrReq == nullptr)
return;
INITSTRUCT(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC, resp);
CNSocket* otherSock = sock;
for (auto pair : PlayerManager::players) {
if (pair.second.plr->PCStyle.iPC_UID == pkt->iBuddyPCUID) {
otherSock = pair.first;
break;
}
}
PlayerView& plr = PlayerManager::players[otherSock];
if (pkt->iAcceptFlag == 1) {
//resp.iBuddySlot = 0; // hard-coding this for now
resp.BuddyInfo.iID = pkt->iBuddyID;
resp.BuddyInfo.iPCUID = pkt->iBuddyPCUID;
resp.BuddyInfo.iNameCheckFlag = plr.plr->PCStyle.iNameCheck;
resp.BuddyInfo.iPCState = plr.plr->iPCState;
resp.BuddyInfo.iGender = plr.plr->PCStyle.iGender;
resp.BuddyInfo.bBlocked = 0;
resp.BuddyInfo.bFreeChat = 1;
memcpy(resp.BuddyInfo.szFirstName, plr.plr->PCStyle.szFirstName, sizeof(plr.plr->PCStyle.szFirstName));
memcpy(resp.BuddyInfo.szLastName, plr.plr->PCStyle.szLastName, sizeof(plr.plr->PCStyle.szLastName));
sock->sendPacket((void*)&resp, P_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC, sizeof(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC));
buddyList(sock, resp.BuddyInfo); // saves buddy data to player's buddylist
if (plr.plr->PCStyle.iPC_UID == pkt->iBuddyPCUID) {
resp.BuddyInfo.iID = plrReq->iID;
resp.BuddyInfo.iPCUID = plrReq->PCStyle.iPC_UID;
resp.BuddyInfo.iNameCheckFlag = plrReq->PCStyle.iNameCheck;
resp.BuddyInfo.iPCState = plrReq->iPCState;
resp.BuddyInfo.iGender = plrReq->PCStyle.iGender;
resp.BuddyInfo.bBlocked = 0;
resp.BuddyInfo.bFreeChat = 1;
memcpy(resp.BuddyInfo.szFirstName, plrReq->PCStyle.szFirstName, sizeof(plrReq->PCStyle.szFirstName));
memcpy(resp.BuddyInfo.szLastName, plrReq->PCStyle.szLastName, sizeof(plrReq->PCStyle.szLastName));
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC, sizeof(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC));
buddyList(otherSock, resp.BuddyInfo); // saves requester's data to this player's buddylist
}
} else {
INITSTRUCT(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_FAIL, declineResp);
declineResp.iErrorCode = 6; // Buddy declined notification
declineResp.iBuddyID = pkt->iBuddyID;
declineResp.iBuddyPCUID = pkt->iBuddyPCUID;
otherSock->sendPacket((void*)&declineResp, P_FE2CL_REP_ACCEPT_MAKE_BUDDY_FAIL, sizeof(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_FAIL)); // tells the requester that the player declined
}
}
// Accepting buddy request from the find name request
void BuddyManager::reqFindNameBuddyAccept(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_PC_FIND_NAME_ACCEPT_BUDDY)) {
return; // malformed packet
}
sP_CL2FE_REQ_PC_FIND_NAME_ACCEPT_BUDDY* pkt = (sP_CL2FE_REQ_PC_FIND_NAME_ACCEPT_BUDDY*)data->buf;
Player* plrReq = PlayerManager::getPlayer(sock);
if (plrReq == nullptr)
return;
INITSTRUCT(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC, resp);
CNSocket* otherSock = sock;
int sizeOfRes = sizeof(pkt->szFirstName) / 9;
int sizeOfLNRes = sizeof(pkt->szLastName) / 17;
for (auto pair : PlayerManager::players) {
int sizeOfReq = sizeof(pair.second.plr->PCStyle.szFirstName) / 9;
int sizeOfLNReq = sizeof(pair.second.plr->PCStyle.szLastName) / 17;
if (BuddyManager::firstNameCheck(pair.second.plr->PCStyle.szFirstName, pkt->szFirstName, sizeOfReq, sizeOfRes) == true && BuddyManager::lastNameCheck(pair.second.plr->PCStyle.szLastName, pkt->szLastName, sizeOfLNReq, sizeOfLNRes) == true) {
otherSock = pair.first;
break;
}
}
PlayerView& plr = PlayerManager::players[otherSock];
if (pkt->iAcceptFlag == 1) {
//resp.iBuddySlot = 0; // hard-coding this for now
//resp.BuddyInfo.iID = plrReq->iID;
resp.BuddyInfo.iPCUID = pkt->iBuddyPCUID;
resp.BuddyInfo.iNameCheckFlag = plr.plr->PCStyle.iNameCheck;
resp.BuddyInfo.iPCState = plr.plr->iPCState;
resp.BuddyInfo.iGender = plr.plr->PCStyle.iGender;
resp.BuddyInfo.bBlocked = 0;
resp.BuddyInfo.bFreeChat = 1;
memcpy(resp.BuddyInfo.szFirstName, plr.plr->PCStyle.szFirstName, sizeof(plr.plr->PCStyle.szFirstName));
memcpy(resp.BuddyInfo.szLastName, plr.plr->PCStyle.szLastName, sizeof(plr.plr->PCStyle.szLastName));
sock->sendPacket((void*)&resp, P_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC, sizeof(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC));
buddyList(sock, resp.BuddyInfo);
if (plr.plr->PCStyle.iPC_UID == pkt->iBuddyPCUID) {
resp.BuddyInfo.iID = plrReq->iID;
resp.BuddyInfo.iPCUID = plrReq->PCStyle.iPC_UID;
resp.BuddyInfo.iNameCheckFlag = plrReq->PCStyle.iNameCheck;
resp.BuddyInfo.iPCState = plrReq->iPCState;
resp.BuddyInfo.iGender = plrReq->PCStyle.iGender;
resp.BuddyInfo.bBlocked = 0;
resp.BuddyInfo.bFreeChat = 1;
memcpy(resp.BuddyInfo.szFirstName, plrReq->PCStyle.szFirstName, sizeof(plrReq->PCStyle.szFirstName));
memcpy(resp.BuddyInfo.szLastName, plrReq->PCStyle.szLastName, sizeof(plrReq->PCStyle.szLastName));
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC, sizeof(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC));
buddyList(otherSock, resp.BuddyInfo);
}
} else {
INITSTRUCT(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_FAIL, declineResp);
declineResp.iErrorCode = 6; // Buddy declined notification
declineResp.iBuddyID = plr.plr->iID;
declineResp.iBuddyPCUID = pkt->iBuddyPCUID;
otherSock->sendPacket((void*)&declineResp, P_FE2CL_REP_ACCEPT_MAKE_BUDDY_FAIL, sizeof(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_FAIL));
}
}
// Buddy freechatting
void BuddyManager::reqBuddyFreechat(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_SEND_BUDDY_FREECHAT_MESSAGE))
return; // malformed packet
sP_CL2FE_REQ_SEND_BUDDY_FREECHAT_MESSAGE* pkt = (sP_CL2FE_REQ_SEND_BUDDY_FREECHAT_MESSAGE*)data->buf;
Player* plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return;
INITSTRUCT(sP_FE2CL_REP_SEND_BUDDY_FREECHAT_MESSAGE_SUCC, resp);
CNSocket* otherSock = sock;
resp.iFromPCUID = plr->PCStyle.iPC_UID;
resp.iToPCUID = pkt->iBuddyPCUID;
resp.iEmoteCode = pkt->iEmoteCode;
memcpy(resp.szFreeChat, pkt->szFreeChat, sizeof(pkt->szFreeChat));
sock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_BUDDY_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_BUDDY_FREECHAT_MESSAGE_SUCC)); // shows the player that they sent the message to their buddy
for (auto pair : PlayerManager::players) {
if (pair.second.plr->PCStyle.iPC_UID != plr->PCStyle.iPC_UID) {
otherSock = pair.first;
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_BUDDY_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_BUDDY_FREECHAT_MESSAGE_SUCC)); // sends the message to the buddy.
}
}
}
// Buddy menuchat
void BuddyManager::reqBuddyMenuchat(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_SEND_BUDDY_MENUCHAT_MESSAGE))
return; // malformed packet
sP_CL2FE_REQ_SEND_BUDDY_MENUCHAT_MESSAGE* pkt = (sP_CL2FE_REQ_SEND_BUDDY_MENUCHAT_MESSAGE*)data->buf;
Player* plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return;
INITSTRUCT(sP_FE2CL_REP_SEND_BUDDY_MENUCHAT_MESSAGE_SUCC, resp);
CNSocket* otherSock = sock;
resp.iFromPCUID = plr->PCStyle.iPC_UID;
resp.iToPCUID = pkt->iBuddyPCUID;
resp.iEmoteCode = pkt->iEmoteCode;
memcpy(resp.szFreeChat, pkt->szFreeChat, sizeof(pkt->szFreeChat));
sock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_BUDDY_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_BUDDY_MENUCHAT_MESSAGE_SUCC)); // shows the player that they sent the message to their buddy
for (auto pair : PlayerManager::players) {
if (pair.second.plr->PCStyle.iPC_UID != plr->PCStyle.iPC_UID) {
otherSock = pair.first;
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_BUDDY_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_BUDDY_MENUCHAT_MESSAGE_SUCC)); // sends the message to the buddy.
}
}
}
// Getting buddy state
void BuddyManager::reqPktGetBuddyState(CNSocket* sock, CNPacketData* data) {
INITSTRUCT(sP_FE2CL_REP_GET_BUDDY_STATE_SUCC, resp);
INITSTRUCT(sBuddyBaseInfo, buddyInfo);
for (int BuddySlot = 0; BuddySlot < 50; BuddySlot++) {
resp.aBuddyState[BuddySlot] = 1; // this sets every buddy to online. Will get the pcstate right directly from the DB.
resp.aBuddyID[BuddySlot] = buddyInfo.iID;
sock->sendPacket((void*)&resp, P_FE2CL_REP_GET_BUDDY_STATE_SUCC, sizeof(sP_FE2CL_REP_GET_BUDDY_STATE_SUCC));
}
}
// Blocking the buddy
void BuddyManager::reqBuddyBlock(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_SET_BUDDY_BLOCK))
return; // malformed packet
sP_CL2FE_REQ_SET_BUDDY_BLOCK* pkt = (sP_CL2FE_REQ_SET_BUDDY_BLOCK*)data->buf;
INITSTRUCT(sP_FE2CL_REP_SET_BUDDY_BLOCK_SUCC, resp);
resp.iBuddyPCUID = pkt->iBuddyPCUID;
resp.iBuddySlot = pkt->iBuddySlot;
sock->sendPacket((void*)&resp, P_FE2CL_REP_SET_BUDDY_BLOCK_SUCC, sizeof(sP_FE2CL_REP_SET_BUDDY_BLOCK_SUCC));
}
// Deleting the buddy
void BuddyManager::reqBuddyDelete(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_REMOVE_BUDDY))
return; // malformed packet
sP_CL2FE_REQ_REMOVE_BUDDY* pkt = (sP_CL2FE_REQ_REMOVE_BUDDY*)data->buf;
INITSTRUCT(sP_FE2CL_REP_REMOVE_BUDDY_SUCC, resp);
resp.iBuddyPCUID = pkt->iBuddyPCUID;
resp.iBuddySlot = pkt->iBuddySlot;
sock->sendPacket((void*)&resp, P_FE2CL_REP_REMOVE_BUDDY_SUCC, sizeof(sP_FE2CL_REP_REMOVE_BUDDY_SUCC));
}
// Warping to buddy
void BuddyManager::reqBuddyWarp(CNSocket* sock, CNPacketData* data) {} // stub
#pragma region Helper methods
void BuddyManager::requestedBuddy(CNSocket* sock, Player* plrReq, PlayerView& plr) {
INITSTRUCT(sP_FE2CL_REP_REQUEST_MAKE_BUDDY_SUCC_TO_ACCEPTER, resp);
resp.iRequestID = plrReq->iID;
resp.iBuddyID = plr.plr->iID;
memcpy(resp.szFirstName, plrReq->PCStyle.szFirstName, sizeof(plrReq->PCStyle.szFirstName));
memcpy(resp.szLastName, plrReq->PCStyle.szLastName, sizeof(plrReq->PCStyle.szLastName));
sock->sendPacket((void*)&resp, P_FE2CL_REP_REQUEST_MAKE_BUDDY_SUCC_TO_ACCEPTER, sizeof(sP_FE2CL_REP_REQUEST_MAKE_BUDDY_SUCC_TO_ACCEPTER)); // player get the buddy request.
}
// Buddy list load
void BuddyManager::buddyList(CNSocket* sock, sBuddyBaseInfo BuddyInfo) {
size_t resplen = sizeof(sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC) + sizeof(sBuddyBaseInfo);
uint8_t respbuf[4096];
memset(respbuf, 0, resplen);
sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC* resp = (sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC*)respbuf;
sBuddyBaseInfo* respdata = (sBuddyBaseInfo*)(respbuf + sizeof(sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC));
resp->iID = BuddyInfo.iID;
resp->iPCUID = BuddyInfo.iPCUID;
for (int i = 0; i < resp->iBuddyCnt && i < resp->iListNum; i++) {
respdata->iID = BuddyInfo.iID;
respdata->iPCUID = BuddyInfo.iPCUID;
respdata->iGender = BuddyInfo.iGender;
respdata->iPCState = BuddyInfo.iPCState;
respdata->iNameCheckFlag = BuddyInfo.iNameCheckFlag;
respdata->bBlocked = 0;
respdata->bFreeChat = 1;
memcpy(respdata->szFirstName, BuddyInfo.szFirstName, sizeof(BuddyInfo.szFirstName));
memcpy(respdata->szLastName, BuddyInfo.szLastName, sizeof(BuddyInfo.szLastName));
}
sock->sendPacket((void*)respbuf, P_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC, resplen); // updates/loads player's buddy list
}
// If the requested player accepts the buddy request, the requester's buddylist will get loaded up.
void BuddyManager::otherAcceptBuddy(CNSocket* sock, int32_t BuddyID, int64_t BuddyPCUID, sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC resp, Player* plr) {
// resp.iBuddySlot = 0; //hard-coding this for now
resp.BuddyInfo.iID = BuddyID;
resp.BuddyInfo.iPCUID = BuddyPCUID;
resp.BuddyInfo.iNameCheckFlag = plr->PCStyle.iNameCheck;
resp.BuddyInfo.iPCState = plr->iPCState;
resp.BuddyInfo.iGender = plr->PCStyle.iGender;
resp.BuddyInfo.bBlocked = 0;
resp.BuddyInfo.bFreeChat = 1;
memcpy(resp.BuddyInfo.szFirstName, plr->PCStyle.szFirstName, sizeof(plr->PCStyle.szFirstName));
memcpy(resp.BuddyInfo.szLastName, plr->PCStyle.szLastName, sizeof(plr->PCStyle.szLastName));
sock->sendPacket((void*)&resp, P_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC, sizeof(sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC));
buddyList(sock, resp.BuddyInfo);
}
// Check if the requested name matches the requested player's name
bool BuddyManager::firstNameCheck(char16_t reqFirstName[], char16_t resFirstName[], int sizeOfReq, int sizeOfRes) {
// If lengths of array are not equal means
// array are not equal
if (sizeOfReq != sizeOfRes)
return false;
// Sort both arrays
std::sort(reqFirstName, reqFirstName + sizeOfReq);
std::sort(resFirstName, resFirstName + sizeOfRes);
// Linearly compare elements
for (int i = 0; i < sizeOfReq; i++)
if (reqFirstName[i] != resFirstName[i])
return false;
// If all elements were same.
return true;
}
bool BuddyManager::lastNameCheck(char16_t reqLastName[], char16_t resLastName[], int sizeOfLNReq, int sizeOfLNRes) {
// If lengths of array are not equal means
// array are not equal
if (sizeOfLNReq != sizeOfLNRes)
return false;
// Sort both arrays
std::sort(reqLastName, reqLastName + sizeOfLNReq);
std::sort(resLastName, resLastName + sizeOfLNRes);
// Linearly compare elements
for (int i = 0; i < sizeOfLNReq; i++)
if (reqLastName[i] != resLastName[i])
return false;
// If all elements were same.
return true;
}
#pragma endregion

44
src/BuddyManager.hpp Normal file
View File

@@ -0,0 +1,44 @@
#pragma once
#include "Player.hpp"
#include "CNProtocol.hpp"
#include "CNStructs.hpp"
#include "CNShardServer.hpp"
#include <map>
#include <list>
namespace BuddyManager {
void init();
// Buddy requests
void requestBuddy(CNSocket* sock, CNPacketData* data);
void reqBuddyByName(CNSocket* sock, CNPacketData* data);
// Buddy accepting
void reqAcceptBuddy(CNSocket* sock, CNPacketData* data);
void reqFindNameBuddyAccept(CNSocket* sock, CNPacketData* data);
// Buddy Messaging
void reqBuddyFreechat(CNSocket* sock, CNPacketData* data);
void reqBuddyMenuchat(CNSocket* sock, CNPacketData* data);
// Getting buddy state
void reqPktGetBuddyState(CNSocket* sock, CNPacketData* data);
// Blocking/removing buddies
void reqBuddyBlock(CNSocket* sock, CNPacketData* data);
void reqBuddyDelete(CNSocket* sock, CNPacketData* data);
// Buddy warping
void reqBuddyWarp(CNSocket* sock, CNPacketData* data);
// helper methods
void requestedBuddy(CNSocket* sock, Player* plrReq, PlayerView& plr);
void buddyList(CNSocket* sock, sBuddyBaseInfo BuddyInfo); // updates the buddylist
void otherAcceptBuddy(CNSocket* sock, int32_t BuddyID, int64_t BuddyPCUID, sP_FE2CL_REP_ACCEPT_MAKE_BUDDY_SUCC resp, Player* plr); // tells the other player that they are now buddies with the requester.
// Name checks
bool firstNameCheck(char16_t reqFirstName[], char16_t resFirstName[], int sizeOfReq, int sizeOfRes); // checks if the request and requested player's first names match
bool lastNameCheck(char16_t reqLastName[], char16_t resLastName[], int sizeOfLNReq, int sizeOfLNRes); // checks if the request and requested player's last names match
}

View File

@@ -24,57 +24,62 @@ void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2LS_REQ_LOGIN))
return; // ignore the malformed packet
sP_CL2LS_REQ_LOGIN* login = (sP_CL2LS_REQ_LOGIN*)data->buf;
//TODO: implement better way of sending credentials
std::string userLogin = U16toU8(login->szID);
std::string userPassword = U16toU8(login->szPassword);
sP_CL2LS_REQ_LOGIN* login = (sP_CL2LS_REQ_LOGIN*)data->buf;
// TODO: implement better way of sending credentials
std::string userLogin((char*)login->szCookie_TEGid);
std::string userPassword((char*)login->szCookie_authid);
/*
* Sometimes the client sends garbage cookie data.
* Validate it as normal credentials instead of using a length check before falling back.
*/
if (!CNLoginServer::isLoginDataGood(userLogin, userPassword)) {
/*
* The std::string -> char* -> std::string maneuver should remove any
* trailing garbage after the null terminator.
*/
userLogin = std::string(U16toU8(login->szID).c_str());
userPassword = std::string(U16toU8(login->szPassword).c_str());
}
bool success = false;
int errorCode = 0;
//checking regex
if (!CNLoginServer::isLoginDataGood(userLogin, userPassword))
{
errorCode = (int)LOGINERRORID::login_error;
}
else
{
// checking regex
if (!CNLoginServer::isLoginDataGood(userLogin, userPassword)) {
errorCode = (int)LoginError::LOGIN_ERROR;
} else {
std::unique_ptr<Database::Account> findUser = Database::findAccount(userLogin);
//if account not found, create it
if (findUser == nullptr)
{
loginSessions[sock] = CNLoginData();
loginSessions[sock].userID = Database::addAccount(userLogin, userPassword);
loginSessions[sock].slot = 1;
success = true;
}
//if user exists, check if password is correct
else if (CNLoginServer::isPasswordCorrect(findUser->Password, userPassword))
{
//check if account isn't currently in use
if (CNLoginServer::isAccountInUse(findUser->AccountID) ||
PlayerManager::isAccountInUse(findUser->AccountID))
{
errorCode = (int)LOGINERRORID::id_already_in_use;
}
//if not, login success
else
{
// if account not found, make new one
if (findUser == nullptr) {
loginSessions[sock] = CNLoginData();
loginSessions[sock].userID = Database::addAccount(userLogin, userPassword);
loginSessions[sock].slot = 1;
loginSessions[sock].lastHeartbeat = getTime();
success = true;
// if user exists, check if password is correct
} else if (CNLoginServer::isPasswordCorrect(findUser->Password, userPassword)) {
/*calling this here to timestamp login attempt,
* in order to make duplicate exit sanity check work*/
Database::updateSelected(findUser->AccountID, findUser->Selected);
// check if account isn't currently in use
if (CNLoginServer::isAccountInUse(findUser->AccountID)) {
errorCode = (int)LoginError::ID_ALREADY_IN_USE;
} else { // if not, login success
loginSessions[sock] = CNLoginData();
loginSessions[sock].userID = findUser->AccountID;
loginSessions[sock].slot = findUser->Selected;
loginSessions[sock].lastHeartbeat = getTime();
success = true;
}
}
else
{
errorCode = (int)LOGINERRORID::id_and_password_do_not_match;
} else {
errorCode = (int)LoginError::ID_AND_PASSWORD_DO_NOT_MATCH;
}
}
if (success)
{
if (success) {
std::vector<Player> characters = Database::getCharacters(loginSessions[sock].userID);
int charCount = characters.size();
@@ -87,18 +92,17 @@ void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
resp.iPaymentFlag = 1;
resp.iOpenBetaFlag = 0;
resp.uiSvrTime = getTime();
// send the resp in with original key
sock->sendPacket((void*)&resp, P_LS2CL_REP_LOGIN_SUCC, sizeof(sP_LS2CL_REP_LOGIN_SUCC));
// update keys
sock->setEKey(CNSocketEncryption::createNewKey(resp.uiSvrTime, resp.iCharCount + 1, resp.iSlotNum + 1));
sock->setFEKey(CNSocketEncryption::createNewKey((uint64_t)(*(uint64_t*)&CNSocketEncryption::defaultKey[0]), login->iClientVerC, 1));
// now send the characters :)
// now send the characters :)
std::vector<Player>::iterator it;
for (it = characters.begin(); it != characters.end(); it++)
{
for (it = characters.begin(); it != characters.end(); it++) {
sP_LS2CL_REP_CHAR_INFO charInfo = sP_LS2CL_REP_CHAR_INFO();
charInfo.iSlot = (int8_t)it->slot;
@@ -111,86 +115,80 @@ void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
charInfo.iY = it->y;
charInfo.iZ = it->z;
//save character in session (for char select)
// save character in session (for char select)
int UID = it->iID;
loginSessions[sock].characters[UID] = Player(*it);
loginSessions[sock].characters[UID] = Player(*it);
loginSessions[sock].characters[UID].FEKey = sock->getFEKey();
//temporary inventory stuff
for (int i = 0; i < 4; i++) {
//equip char creation clothes and lightning rifle
// Equip info
for (int i = 0; i < AEQUIP_COUNT; i++) {
charInfo.aEquip[i] = it->Equip[i];
}
for (int i = 5; i < AEQUIP_COUNT; i++) {
// empty equips
charInfo.aEquip[i].iID = 0;
charInfo.aEquip[i].iType = i;
charInfo.aEquip[i].iOpt = 0;
}
// set default to the first character
if (it == characters.begin())
loginSessions[sock].selectedChar = UID;
sock->sendPacket((void*)&charInfo, P_LS2CL_REP_CHAR_INFO, sizeof(sP_LS2CL_REP_CHAR_INFO));
sock->sendPacket((void*)&charInfo, P_LS2CL_REP_CHAR_INFO, sizeof(sP_LS2CL_REP_CHAR_INFO));
}
}
//Failure
else {
} else {
INITSTRUCT(sP_LS2CL_REP_LOGIN_FAIL, resp);
memcpy(resp.szID, login->szID, sizeof(char16_t) * 33);
U8toU16(userLogin, resp.szID, sizeof(resp.szID));
resp.iErrorCode = errorCode;
sock->sendPacket((void*)&resp, P_LS2CL_REP_LOGIN_FAIL, sizeof(sP_LS2CL_REP_LOGIN_FAIL));
}
break;
}
case P_CL2LS_REP_LIVE_CHECK: {
// stubbed, the client really doesn't care LOL
loginSessions[sock].lastHeartbeat = getTime();
break;
}
case P_CL2LS_REQ_CHECK_CHAR_NAME: {
if (data->size != sizeof(sP_CL2LS_REQ_CHECK_CHAR_NAME))
return;
// naughty words allowed!!!!!!!! (also for some reason, the client will always show 'Player 0' if you manually type a name. It will show up for other connected players though)
sP_CL2LS_REQ_CHECK_CHAR_NAME* nameCheck = (sP_CL2LS_REQ_CHECK_CHAR_NAME*)data->buf;
//check if name is occupied
if (Database::isNameFree(nameCheck))
{
// naughty words allowed!!!!!!!! (also for some reason, the client will always show 'Player + ID' if you manually type a name. It will show up for other connected players though)
sP_CL2LS_REQ_CHECK_CHAR_NAME* nameCheck = (sP_CL2LS_REQ_CHECK_CHAR_NAME*)data->buf;
bool success = true;
int errorcode = 0;
// check regex
if (!CNLoginServer::isCharacterNameGood(U16toU8(nameCheck->szFirstName), U16toU8(nameCheck->szLastName))) {
success = false;
errorcode = 4;
} else if (!Database::isNameFree(nameCheck)){ // check if name isn't already occupied
success = false;
errorcode = 1;
}
loginSessions[sock].lastHeartbeat = getTime();
if (success) {
INITSTRUCT(sP_LS2CL_REP_CHECK_CHAR_NAME_SUCC, resp);
DEBUGLOG(
std::cout << "P_CL2LS_REQ_CHECK_CHAR_NAME:" << std::endl;
std::cout << "\tFirstName: " << U16toU8(nameCheck->szFirstName) << " LastName: " << U16toU8(nameCheck->szLastName) << std::endl;
std::cout << "P_CL2LS_REQ_CHECK_CHAR_NAME:" << std::endl;
std::cout << "\tFirstName: " << U16toU8(nameCheck->szFirstName) << " LastName: " << U16toU8(nameCheck->szLastName) << std::endl;
)
memcpy(resp.szFirstName, nameCheck->szFirstName, sizeof(char16_t) * 9);
memcpy(resp.szLastName, nameCheck->szLastName, sizeof(char16_t) * 17);
// fr*ck allowed!!!
sock->sendPacket((void*)&resp, P_LS2CL_REP_CHECK_CHAR_NAME_SUCC, sizeof(sP_LS2CL_REP_CHECK_CHAR_NAME_SUCC));
}
else {
} else {
INITSTRUCT(sP_LS2CL_REP_CHECK_CHAR_NAME_FAIL, resp);
resp.iErrorCode = 1;
resp.iErrorCode = errorcode;
sock->sendPacket((void*)&resp, P_LS2CL_REP_CHECK_CHAR_NAME_FAIL, sizeof(sP_LS2CL_REP_CHECK_CHAR_NAME_FAIL));
}
break;
}
case P_CL2LS_REQ_SAVE_CHAR_NAME: {
if (data->size != sizeof(sP_CL2LS_REQ_SAVE_CHAR_NAME))
return;
sP_CL2LS_REQ_SAVE_CHAR_NAME* save = (sP_CL2LS_REQ_SAVE_CHAR_NAME*)data->buf;
INITSTRUCT(sP_LS2CL_REP_SAVE_CHAR_NAME_SUCC, resp);
DEBUGLOG(
std::cout << "P_CL2LS_REQ_SAVE_CHAR_NAME:" << std::endl;
std::cout << "\tSlot: " << (int)save->iSlotNum << std::endl;
@@ -203,6 +201,8 @@ void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
memcpy(resp.szFirstName, save->szFirstName, sizeof(char16_t) * 9);
memcpy(resp.szLastName, save->szLastName, sizeof(char16_t) * 17);
loginSessions[sock].lastHeartbeat = getTime();
sock->sendPacket((void*)&resp, P_LS2CL_REP_SAVE_CHAR_NAME_SUCC, sizeof(sP_LS2CL_REP_SAVE_CHAR_NAME_SUCC));
Database::updateSelected(loginSessions[sock].userID, save->iSlotNum);
@@ -211,7 +211,7 @@ void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
case P_CL2LS_REQ_CHAR_CREATE: {
if (data->size != sizeof(sP_CL2LS_REQ_CHAR_CREATE))
return;
sP_CL2LS_REQ_CHAR_CREATE* character = (sP_CL2LS_REQ_CHAR_CREATE*)data->buf;
Database::finishCharacter(character);
@@ -232,12 +232,9 @@ void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
std::cout << "\tiEquipUBID: " << (int)character->sOn_Item.iEquipUBID << std::endl;
std::cout << "\tiEquipLBID: " << (int)character->sOn_Item.iEquipLBID << std::endl;
std::cout << "\tiEquipFootID: " << (int)character->sOn_Item.iEquipFootID << std::endl;
)
Player player =
Database::DbToPlayer(
Database::getDbPlayerById(character->PCStyle.iPC_UID)
);
)
Player player = Database::getPlayer(character->PCStyle.iPC_UID);
int64_t UID = player.iID;
INITSTRUCT(sP_LS2CL_REP_CHAR_CREATE_SUCC, resp);
@@ -246,10 +243,12 @@ void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
resp.iLevel = player.level;
resp.sOn_Item = character->sOn_Item;
//save player in session
// save player in session
loginSessions[sock].characters[UID] = Player(player);
loginSessions[sock].characters[UID].FEKey = sock->getFEKey();
loginSessions[sock].lastHeartbeat = getTime();
sock->sendPacket((void*)&resp, P_LS2CL_REP_CHAR_CREATE_SUCC, sizeof(sP_LS2CL_REP_CHAR_CREATE_SUCC));
Database::updateSelected(loginSessions[sock].userID, player.slot);
break;
@@ -259,18 +258,18 @@ void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
return;
sP_CL2LS_REQ_CHAR_DELETE* del = (sP_CL2LS_REQ_CHAR_DELETE*)data->buf;
int operationResult = Database::deleteCharacter(del->iPC_UID);
int operationResult = Database::deleteCharacter(del->iPC_UID, loginSessions[sock].userID);
INITSTRUCT(sP_LS2CL_REP_CHAR_DELETE_SUCC, resp);
resp.iSlotNum = operationResult;
sock->sendPacket((void*)&resp, P_LS2CL_REP_CHAR_DELETE_SUCC, sizeof(sP_LS2CL_REP_CHAR_DELETE_SUCC));
loginSessions[sock].lastHeartbeat = getTime();
break;
}
case P_CL2LS_REQ_CHAR_SELECT: {
if (data->size != sizeof(sP_CL2LS_REQ_CHAR_SELECT))
return;
// character selected
sP_CL2LS_REQ_CHAR_SELECT* chararacter = (sP_CL2LS_REQ_CHAR_SELECT*)data->buf;
INITSTRUCT(sP_LS2CL_REP_CHAR_SELECT_SUCC, resp);
@@ -280,6 +279,8 @@ void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
std::cout << "\tPC_UID: " << chararacter->iPC_UID << std::endl;
)
loginSessions[sock].lastHeartbeat = getTime();
loginSessions[sock].selectedChar = chararacter->iPC_UID;
Database::updateSelected(loginSessions[sock].userID, loginSessions[sock].characters[chararacter->iPC_UID].slot);
sock->sendPacket((void*)&resp, P_LS2CL_REP_CHAR_SELECT_SUCC, sizeof(sP_LS2CL_REP_CHAR_SELECT_SUCC));
@@ -288,7 +289,7 @@ void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
case P_CL2LS_REQ_SHARD_SELECT: {
if (data->size != sizeof(sP_CL2LS_REQ_SHARD_SELECT))
return;
// tell client to connect to the shard server
sP_CL2LS_REQ_SHARD_SELECT* shard = (sP_CL2LS_REQ_SHARD_SELECT*)data->buf;
INITSTRUCT(sP_LS2CL_REP_SHARD_SELECT_SUCC, resp);
@@ -317,23 +318,30 @@ void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
return;
sP_CL2LS_REQ_SAVE_CHAR_TUTOR* save = (sP_CL2LS_REQ_SAVE_CHAR_TUTOR*)data->buf;
Database::finishTutorial(save->iPC_UID);
loginSessions[sock].characters[save->iPC_UID].PCStyle2.iTutorialFlag = 1;
loginSessions[sock].characters[save->iPC_UID].Equip[0].iID = 328;
loginSessions[sock].characters[save->iPC_UID].Equip[0].iType = 0;
loginSessions[sock].characters[save->iPC_UID].Equip[0].iOpt = 1;
// update character in session
auto key = loginSessions[sock].characters[save->iPC_UID].FEKey;
loginSessions[sock].characters[save->iPC_UID] = Player(Database::getPlayer(save->iPC_UID));
loginSessions[sock].characters[save->iPC_UID].FEKey = key;
loginSessions[sock].lastHeartbeat = getTime();
// no response here
break;
}
case P_CL2LS_REQ_CHANGE_CHAR_NAME: {
if (data->size != sizeof(sP_CL2LS_REQ_CHANGE_CHAR_NAME))
return;
sP_CL2LS_REQ_CHANGE_CHAR_NAME* save = (sP_CL2LS_REQ_CHANGE_CHAR_NAME*)data->buf;
Database::changeName(save);
INITSTRUCT(sP_LS2CL_REP_CHANGE_CHAR_NAME_SUCC, resp);
resp.iPC_UID = save->iPCUID;
memcpy(resp.szFirstName, save->szFirstName, sizeof(char16_t)*9);
memcpy(resp.szLastName, save->szLastName, sizeof(char16_t) * 17);
resp.iSlotNum = save->iSlotNum;
loginSessions[sock].lastHeartbeat = getTime();
sock->sendPacket((void*)&resp, P_LS2CL_REP_CHANGE_CHAR_NAME_SUCC, sizeof(sP_LS2CL_REP_CHANGE_CHAR_NAME_SUCC));
break;
}
@@ -343,24 +351,26 @@ void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
sP_CL2LS_REQ_PC_EXIT_DUPLICATE* exit = (sP_CL2LS_REQ_PC_EXIT_DUPLICATE*)data->buf;
auto account = Database::findAccount(U16toU8(exit->szID));
if (account == nullptr)
break;
int accountId = account->AccountID;
if (!exitDuplicate(accountId))
PlayerManager::exitDuplicate(accountId);
// sanity check
if (account == nullptr) {
std::cout << "[WARN] P_CL2LS_REQ_PC_EXIT_DUPLICATE submitted unknown username: " << exit->szID << std::endl;
break;
}
exitDuplicate(account->AccountID);
break;
}
default:
if (settings::VERBOSITY)
std::cerr << "OpenFusion: LOGIN UNIMPLM ERR. PacketType: " << Defines::p2str(CL2LS, data->type) << " (" << data->type << ")" << std::endl;
break;
/* Unimplemented CL2LS packets:
P_CL2LS_CHECK_NAME_LIST - unused by the client
P_CL2LS_REQ_SERVER_SELECT
P_CL2LS_REQ_SHARD_LIST_INFO - dev commands, useless as we only run 1 server
*/
/*
* Unimplemented CL2LS packets:
* P_CL2LS_CHECK_NAME_LIST - unused by the client
* P_CL2LS_REQ_SERVER_SELECT
* P_CL2LS_REQ_SHARD_LIST_INFO - dev commands, useless as we only run 1 server
*/
}
}
@@ -372,23 +382,39 @@ void CNLoginServer::killConnection(CNSocket* cns) {
loginSessions.erase(cns);
}
void CNLoginServer::onStep() {
time_t currTime = getTime();
static time_t lastCheck = 0;
if (currTime - lastCheck < 16000)
return;
lastCheck = currTime;
for (auto& pair : loginSessions) {
if (pair.second.lastHeartbeat != 0 && currTime - pair.second.lastHeartbeat > 32000) {
pair.first->kill();
continue;
}
INITSTRUCT(sP_LS2CL_REQ_LIVE_CHECK, pkt);
pair.first->sendPacket((void*)&pkt, P_LS2CL_REQ_LIVE_CHECK, sizeof(sP_LS2CL_REQ_LIVE_CHECK));
}
}
#pragma region helperMethods
bool CNLoginServer::isAccountInUse(int accountId) {
std::map<CNSocket*, CNLoginData>::iterator it;
for (it = CNLoginServer::loginSessions.begin(); it != CNLoginServer::loginSessions.end(); it++)
{
for (it = CNLoginServer::loginSessions.begin(); it != CNLoginServer::loginSessions.end(); it++) {
if (it->second.userID == accountId)
return true;
}
return false;
}
bool CNLoginServer::exitDuplicate(int accountId)
{
bool CNLoginServer::exitDuplicate(int accountId) {
std::map<CNSocket*, CNLoginData>::iterator it;
for (it = CNLoginServer::loginSessions.begin(); it != CNLoginServer::loginSessions.end(); it++)
{
if (it->second.userID == accountId)
{
for (it = CNLoginServer::loginSessions.begin(); it != CNLoginServer::loginSessions.end(); it++) {
if (it->second.userID == accountId) {
CNSocket* sock = it->first;
INITSTRUCT(sP_LS2CL_REP_PC_EXIT_DUPLICATE, resp);
resp.iErrorCode = 0;
@@ -399,14 +425,21 @@ bool CNLoginServer::exitDuplicate(int accountId)
}
return false;
}
bool CNLoginServer::isLoginDataGood(std::string login, std::string password)
{
bool CNLoginServer::isLoginDataGood(std::string login, std::string password) {
std::regex loginRegex("[a-zA-Z0-9_-]{4,32}");
std::regex passwordRegex("[a-zA-Z0-9!@#$%^&*()_+]{8,32}");
return (std::regex_match(login, loginRegex) && std::regex_match(password, passwordRegex));
}
bool CNLoginServer::isPasswordCorrect(std::string actualPassword, std::string tryPassword)
{
bool CNLoginServer::isPasswordCorrect(std::string actualPassword, std::string tryPassword) {
return BCrypt::validatePassword(tryPassword, actualPassword);
}
bool CNLoginServer::isCharacterNameGood(std::string Firstname, std::string Lastname) {
std::regex firstnamecheck("[a-zA-Z0-9]+(?: [a-zA-Z0-9]+)*$");
std::regex lastnamecheck("[a-zA-Z0-9]+(?: [a-zA-Z0-9]+)*$");
return (std::regex_match(Firstname, firstnamecheck) && std::regex_match(Lastname, lastnamecheck));
}
#pragma endregion helperMethods

View File

@@ -10,18 +10,19 @@ struct CNLoginData {
std::map<int64_t, Player> characters;
int64_t selectedChar;
int userID; int slot;
time_t lastHeartbeat;
};
enum class LOGINERRORID {
database_error = 0,
id_doesnt_exist = 1,
id_and_password_do_not_match = 2,
id_already_in_use = 3,
login_error = 4,
client_version_outdated = 6,
you_are_not_an_authorized_beta_tester = 7,
authentication_connection_error = 8,
updated_euala_required = 9
enum class LoginError {
DATABASE_ERROR = 0,
ID_DOESNT_EXIST = 1,
ID_AND_PASSWORD_DO_NOT_MATCH = 2,
ID_ALREADY_IN_USE = 3,
LOGIN_ERROR = 4,
CLIENT_VERSION_OUTDATED = 6,
YOU_ARE_NOT_AN_AUTHORIZED_BETA_TESTER = 7,
AUTHENTICATION_CONNECTION_ERROR = 8,
UPDATED_EUALA_REQUIRED = 9
};
// WARNING: THERE CAN ONLY BE ONE OF THESE SERVERS AT A TIME!!!!!! TODO: change loginSessions & packet handlers to be non-static
@@ -29,15 +30,17 @@ class CNLoginServer : public CNServer {
private:
static void handlePacket(CNSocket* sock, CNPacketData* data);
static std::map<CNSocket*, CNLoginData> loginSessions;
static bool isLoginDataGood(std::string login, std::string password);
static bool isPasswordCorrect(std::string actualPassword, std::string tryPassword);
static bool isAccountInUse(int accountId);
//returns true if success
static bool isCharacterNameGood(std::string Firstname, std::string Lastname);
// returns true if success
static bool exitDuplicate(int accountId);
public:
CNLoginServer(uint16_t p);
void newConnection(CNSocket* cns);
void killConnection(CNSocket* cns);
void killConnection(CNSocket* cns);
void onStep();
};

View File

@@ -9,8 +9,7 @@ int CNSocketEncryption::Encrypt_byte_change_A(int ERSize, uint8_t* data, int siz
int num2 = 0;
int num3 = 0;
while (num + ERSize <= size)
{
while (num + ERSize <= size) {
int num4 = num + num3;
int num5 = num + (ERSize - 1 - num3);
@@ -19,8 +18,7 @@ int CNSocketEncryption::Encrypt_byte_change_A(int ERSize, uint8_t* data, int siz
data[num5] = b;
num += ERSize;
num3++;
if (num3 > ERSize / 2)
{
if (num3 > ERSize / 2) {
num3 = 0;
}
}
@@ -83,7 +81,7 @@ bool CNSocket::sendData(uint8_t* data, int size) {
}
sentBytes += sent;
}
return true; // it worked!
}
@@ -122,7 +120,7 @@ void CNSocket::kill() {
void CNSocket::sendPacket(void* buf, uint32_t type, size_t size) {
if (!alive)
return;
size_t bodysize = size + sizeof(uint32_t);
uint8_t* fullpkt = (uint8_t*)xmalloc(bodysize+4);
uint8_t* body = fullpkt+4;
@@ -150,7 +148,7 @@ void CNSocket::sendPacket(void* buf, uint32_t type, size_t size) {
}
// send packet data!
if (alive && !sendData(fullpkt, bodysize+4))
if (alive && !sendData(fullpkt, bodysize+4))
kill();
free(fullpkt);
@@ -167,7 +165,7 @@ void CNSocket::step() {
// we aren't reading a packet yet, try to start looking for one
int recved = recv(sock, (buffer_t*)readBuffer, sizeof(int32_t), 0);
if (!SOCKETERROR(recved)) {
// we got out packet size!!!!
// we got our packet size!!!!
readSize = *((int32_t*)readBuffer);
// sanity check
if (readSize > CN_PACKET_BUFFER_SIZE) {
@@ -183,7 +181,7 @@ void CNSocket::step() {
return;
}
}
if (readSize > 0 && readBufferIndex < readSize) {
// read until the end of the packet! (or at least try too)
int recved = recv(sock, (buffer_t*)(readBuffer + readBufferIndex), readSize - readBufferIndex, 0);
@@ -196,7 +194,7 @@ void CNSocket::step() {
}
}
if (activelyReading && readBufferIndex - readSize <= 0) {
if (activelyReading && readBufferIndex - readSize <= 0) {
// decrypt readBuffer and copy to CNPacketData
CNSocketEncryption::decryptData((uint8_t*)&readBuffer, (uint8_t*)(&EKey), readSize);
@@ -216,38 +214,38 @@ void CNSocket::step() {
// ========================================================[[ CNServer ]]========================================================
void CNServer::init() {
// create socket file descriptor
// create socket file descriptor
sock = socket(AF_INET, SOCK_STREAM, 0);
if (SOCKETINVALID(sock)) {
std::cerr << "[FATAL] OpenFusion: socket failed" << std::endl;
exit(EXIT_FAILURE);
if (SOCKETINVALID(sock)) {
std::cerr << "[FATAL] OpenFusion: socket failed" << std::endl;
exit(EXIT_FAILURE);
}
// attach socket to the port
int opt = 1;
#ifdef _WIN32
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt)) != 0) {
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt)) != 0) {
#else
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) != 0) {
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) != 0) {
#endif
std::cerr << "[FATAL] OpenFusion: setsockopt failed" << std::endl;
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(port);
std::cerr << "[FATAL] OpenFusion: setsockopt failed" << std::endl;
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(port);
addressSize = sizeof(address);
// Bind to the port
if (SOCKETERROR(bind(sock, (struct sockaddr *)&address, addressSize))) {
std::cerr << "[FATAL] OpenFusion: bind failed" << std::endl;
exit(EXIT_FAILURE);
if (SOCKETERROR(bind(sock, (struct sockaddr *)&address, addressSize))) {
std::cerr << "[FATAL] OpenFusion: bind failed" << std::endl;
exit(EXIT_FAILURE);
}
if (SOCKETERROR(listen(sock, SOMAXCONN))) {
std::cerr << "[FATAL] OpenFusion: listen failed" << std::endl;
exit(EXIT_FAILURE);
std::cerr << "[FATAL] OpenFusion: listen failed" << std::endl;
exit(EXIT_FAILURE);
}
// set server listener to non-blocking
@@ -257,8 +255,8 @@ void CNServer::init() {
#else
if (fcntl(sock, F_SETFL, (fcntl(sock, F_GETFL, 0) | O_NONBLOCK)) != 0) {
#endif
std::cerr << "[FATAL] OpenFusion: fcntl failed" << std::endl;
exit(EXIT_FAILURE);
std::cerr << "[FATAL] OpenFusion: fcntl failed" << std::endl;
exit(EXIT_FAILURE);
}
}
@@ -281,7 +279,7 @@ void CNServer::start() {
#else
if (fcntl(newConnectionSocket, F_SETFL, (fcntl(sock, F_GETFL, 0) | O_NONBLOCK)) != 0) {
#endif
std::cerr << "[WARN] OpenFusion: fcntl failed on new connection" << std::endl;
std::cerr << "[WARN] OpenFusion: fcntl failed on new connection" << std::endl;
#ifdef _WIN32
shutdown(newConnectionSocket, SD_BOTH);
closesocket(newConnectionSocket);
@@ -295,7 +293,7 @@ void CNServer::start() {
std::cout << "New connection! " << inet_ntoa(address.sin_addr) << std::endl;
// add connection to list!
CNSocket* tmp = new CNSocket(newConnectionSocket, pHandler);
CNSocket* tmp = new CNSocket(newConnectionSocket, pHandler);
connections.push_back(tmp);
newConnection(tmp);
}

View File

@@ -5,8 +5,11 @@
#include <iostream>
#include <stdio.h>
#include <stdint.h>
#ifdef _WIN32
#ifdef _WIN32
// windows
#ifndef NOMINMAX
#define NOMINMAX
#endif
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <winsock2.h>
#include <windows.h>
@@ -46,18 +49,18 @@
#if defined(__MINGW32__) && !defined(_GLIBCXX_HAS_GTHREADS)
#include "mingw/mingw.mutex.h"
#else
#else
#include <mutex>
#endif
/*
Packets format (sent from the client):
[4 bytes] - size of packet including the 4 byte packet type
[size bytes] - Encrypted packet (byte swapped && xor'd with 8 byte key; see CNSocketEncryption)
[4 bytes] - packet type (which is a combination of the first 4 bytes of the packet and a checksum in some versions)
[structure] - one member contains length of trailing data (expressed in packet-dependant structures)
[trailing data] - optional variable-length data that only some packets make use of
*/
* Packets format (sent from the client):
* [4 bytes] - size of packet including the 4 byte packet type
* [size bytes] - Encrypted packet (byte swapped && xor'd with 8 byte key; see CNSocketEncryption)
* [4 bytes] - packet type (which is a combination of the first 4 bytes of the packet and a checksum in some versions)
* [structure] - one member contains length of trailing data (expressed in packet-dependant structures)
* [trailing data] - optional variable-length data that only some packets make use of
*/
// error checking calloc wrapper
inline void* xmalloc(size_t sz) {
@@ -75,14 +78,14 @@ inline void* xmalloc(size_t sz) {
// for outbound packets
inline bool validOutVarPacket(size_t base, int32_t npayloads, size_t plsize) {
// check for multiplication overflow
if (npayloads > 0 && CN_PACKET_BUFFER_SIZE / (size_t)npayloads < plsize)
if (npayloads > 0 && (CN_PACKET_BUFFER_SIZE - 8) / (size_t)npayloads < plsize)
return false;
// it's safe to multiply
size_t trailing = npayloads * plsize;
// does it fit in a packet?
if (base + trailing > CN_PACKET_BUFFER_SIZE)
if (base + trailing > CN_PACKET_BUFFER_SIZE - 8)
return false;
// everything is a-ok!
@@ -92,7 +95,7 @@ inline bool validOutVarPacket(size_t base, int32_t npayloads, size_t plsize) {
// for inbound packets
inline bool validInVarPacket(size_t base, int32_t npayloads, size_t plsize, size_t datasize) {
// check for multiplication overflow
if (npayloads > 0 && CN_PACKET_BUFFER_SIZE / (size_t)npayloads < plsize)
if (npayloads > 0 && (CN_PACKET_BUFFER_SIZE - 8) / (size_t)npayloads < plsize)
return false;
// it's safe to multiply
@@ -132,6 +135,8 @@ enum ACTIVEKEY {
SOCKETKEY_FE
};
struct Player;
class CNSocket;
typedef void (*PacketHandler)(CNSocket* sock, CNPacketData* data);
@@ -153,6 +158,7 @@ private:
public:
SOCKET sock;
PacketHandler pHandler;
Player *plr = nullptr;
CNSocket(SOCKET s, PacketHandler ph);
@@ -169,15 +175,15 @@ public:
};
class CNServer;
typedef void (*TimerHandler)(CNServer* serv, uint64_t time);
typedef void (*TimerHandler)(CNServer* serv, time_t time);
// timer struct
struct TimerEvent {
TimerHandler handlr;
uint64_t delta; // time to be added to the current time on reset
uint64_t scheduledEvent; // time to call handlr()
time_t delta; // time to be added to the current time on reset
time_t scheduledEvent; // time to call handlr()
TimerEvent(TimerHandler h, uint64_t d): handlr(h), delta(d) {
TimerEvent(TimerHandler h, time_t d): handlr(h), delta(d) {
scheduledEvent = 0;
}
};
@@ -207,5 +213,5 @@ public:
static void printPacket(CNPacketData *data, int type);
virtual void newConnection(CNSocket* cns);
virtual void killConnection(CNSocket* cns);
virtual void onStep(); // called every 2 seconds
virtual void onStep();
};

View File

@@ -2,8 +2,11 @@
#include "CNStructs.hpp"
#include "CNShardServer.hpp"
#include "PlayerManager.hpp"
#include "MobManager.hpp"
#include "CNShared.hpp"
#include "settings.hpp"
#include "Database.hpp"
#include "TableData.hpp" // for flush()
#include <iostream>
#include <sstream>
@@ -15,7 +18,8 @@ std::list<TimerEvent> CNShardServer::Timers;
CNShardServer::CNShardServer(uint16_t p) {
port = p;
pHandler = &CNShardServer::handlePacket;
REGISTER_SHARD_TIMER(keepAliveTimer, 2000);
REGISTER_SHARD_TIMER(keepAliveTimer, 4000);
REGISTER_SHARD_TIMER(periodicSaveTimer, settings::DBSAVEINTERVAL*1000);
init();
}
@@ -26,41 +30,76 @@ void CNShardServer::handlePacket(CNSocket* sock, CNPacketData* data) {
ShardPackets[data->type](sock, data);
else if (settings::VERBOSITY > 0)
std::cerr << "OpenFusion: SHARD UNIMPLM ERR. PacketType: " << Defines::p2str(CL2FE, data->type) << " (" << data->type << ")" << std::endl;
PlayerManager::players[sock].lastHeartbeat = getTime();
}
void CNShardServer::keepAliveTimer(CNServer* serv, uint64_t currTime) {
auto cachedPlayers = PlayerManager::players;
for (auto pair : cachedPlayers) {
if (pair.second.lastHeartbeat != 0 && currTime - pair.second.lastHeartbeat > 60000) { // if the client hadn't responded in 60 seconds, its a dead connection so throw it out
void CNShardServer::keepAliveTimer(CNServer* serv, time_t currTime) {
for (auto& pair : PlayerManager::players) {
if (pair.second.lastHeartbeat != 0 && currTime - pair.second.lastHeartbeat > settings::TIMEOUT) {
// if the client hasn't responded in 60 seconds, its a dead connection so throw it out
pair.first->kill();
continue;
} else if (pair.second.lastHeartbeat != 0 && currTime - pair.second.lastHeartbeat > settings::TIMEOUT/2) {
// if the player hasn't responded in 30 seconds, send a live check
INITSTRUCT(sP_FE2CL_REQ_LIVE_CHECK, data);
pair.first->sendPacket((void*)&data, P_FE2CL_REQ_LIVE_CHECK, sizeof(sP_FE2CL_REQ_LIVE_CHECK));
}
// passed the heartbeat, send another
INITSTRUCT(sP_FE2CL_REQ_LIVE_CHECK, data);
pair.first->sendPacket((void*)&data, P_FE2CL_REQ_LIVE_CHECK, sizeof(sP_FE2CL_REQ_LIVE_CHECK));
}
}
void CNShardServer::periodicSaveTimer(CNServer* serv, time_t currTime) {
if (PlayerManager::players.empty())
return;
std::cout << "[INFO] Saving " << PlayerManager::players.size() << " players to DB..." << std::endl;
for (auto& pair : PlayerManager::players) {
Database::updatePlayer(pair.second.plr);
}
TableData::flush();
std::cout << "[INFO] Done." << std::endl;
}
void CNShardServer::newConnection(CNSocket* cns) {
cns->setActiveKey(SOCKETKEY_E); // by default they accept keys encrypted with the default key
}
void CNShardServer::killConnection(CNSocket* cns) {
// must be static to be called from PlayerManager::exitDuplicate()
void CNShardServer::_killConnection(CNSocket* cns) {
// check if the player ever sent a REQ_PC_ENTER
if (PlayerManager::players.find(cns) == PlayerManager::players.end())
return;
// remove from CNSharedData
int64_t key = PlayerManager::getPlayer(cns)->SerialKey;
PlayerManager::removePlayer(cns);
Player* plr = PlayerManager::getPlayer(cns);
if (plr == nullptr) { // this shouldn't happen if everything works correctly...
PlayerManager::removePlayer(cns);
// also, hopefully the player's progress was already saved since the last db save interval, but rip those 2 mins of progress lol
return;
}
int64_t key = plr->SerialKey;
PlayerManager::removePlayer(cns); // removes the player from the list and saves it to DB
// remove from CNSharedData
CNSharedData::erasePlayer(key);
}
void CNShardServer::killConnection(CNSocket *cns) {
_killConnection(cns);
}
// flush the DB when terminating the server
void CNShardServer::kill() {
periodicSaveTimer(nullptr, 0);
CNServer::kill();
}
void CNShardServer::onStep() {
uint64_t currTime = getTime();
time_t currTime = getTime();
for (TimerEvent& event : Timers) {
if (event.scheduledEvent == 0) {

View File

@@ -12,7 +12,8 @@ class CNShardServer : public CNServer {
private:
static void handlePacket(CNSocket* sock, CNPacketData* data);
static void keepAliveTimer(CNServer*, uint64_t);
static void keepAliveTimer(CNServer*, time_t);
static void periodicSaveTimer(CNServer* serv, time_t currTime);
public:
static std::map<uint32_t, PacketHandler> ShardPackets;
@@ -20,7 +21,10 @@ public:
CNShardServer(uint16_t p);
static void _killConnection(CNSocket *cns);
void newConnection(CNSocket* cns);
void killConnection(CNSocket* cns);
void kill();
void onStep();
};

View File

@@ -2,7 +2,7 @@
#if defined(__MINGW32__) && !defined(_GLIBCXX_HAS_GTHREADS)
#include "mingw/mingw.mutex.h"
#else
#else
#include <mutex>
#endif
std::map<int64_t, Player> CNSharedData::players;

View File

@@ -1,7 +1,7 @@
/*
CNShared.hpp
There's some data shared between the Login Server and the Shard Server. Of course all of this needs to be thread-safe. No mucking about on this one!
*/
* CNShared.hpp
* There's some data shared between the Login Server and the Shard Server. Of course all of this needs to be thread-safe. No mucking about on this one!
*/
#pragma once

View File

@@ -1,36 +0,0 @@
#include "CNStructs.hpp"
#if defined _MSC_VER
#include <chrono>
#endif
std::string U16toU8(char16_t* src) {
try {
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>,char16_t> convert;
return convert.to_bytes(src);
} catch(std::exception e) {
return "";
}
}
// returns number of char16_t that was written at des
size_t U8toU16(std::string src, char16_t* des) {
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>,char16_t> convert;
std::u16string tmp = convert.from_bytes(src);
// copy utf16 string to buffer
memcpy(des, tmp.c_str(), sizeof(char16_t) * tmp.length());
des[tmp.length()] = '\0';
return tmp.length();
}
uint64_t getTime() {
#ifndef _MSC_VER
struct timeval tp;
gettimeofday(&tp, NULL);
return tp.tv_sec * 1000 + tp.tv_usec / 1000;
#else
std::chrono::milliseconds value = std::chrono::duration_cast<std::chrono::milliseconds>((std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now())).time_since_epoch());
return (uint64_t)(value.count());
#endif
}

View File

@@ -1,6 +1,6 @@
/*
CNStructs.hpp - defines some basic structs & useful methods for packets used by FusionFall based on the version defined
*/
* CNStructs.hpp - defines some basic structs & useful methods for packets used by FusionFall based on the version defined
*/
#pragma once
@@ -24,15 +24,21 @@
#include <locale>
#include <codecvt>
// yes this is ugly, but this is needed to zero out the memory so we don't have random stackdata in our structs.
// yes this is ugly, but this is needed to zero out the memory so we don't have random stackdata in our structs.
#define INITSTRUCT(T, x) T x; \
memset(&x, 0, sizeof(T));
// macros to extract fields from instanceIDs
#define MAPNUM(x) ((x) & 0xffffffff)
#define PLAYERID(x) ((x) >> 32)
// TODO: rewrite U16toU8 & U8toU16 to not use codecvt
std::string U16toU8(char16_t* src);
size_t U8toU16(std::string src, char16_t* des); // returns number of char16_t that was written at des
uint64_t getTime();
size_t U8toU16(std::string src, char16_t* des, size_t max); // returns number of char16_t that was written at des
time_t getTime();
time_t getTimestamp();
void terminate(int);
// The PROTOCOL_VERSION definition is defined by the build system.
#if !defined(PROTOCOL_VERSION)

View File

@@ -2,36 +2,499 @@
#include "CNStructs.hpp"
#include "ChatManager.hpp"
#include "PlayerManager.hpp"
#include "TransportManager.hpp"
#include "TableData.hpp"
#include "NPCManager.hpp"
#include "MobManager.hpp"
#include "MissionManager.hpp"
#include <sstream>
#include <iterator>
std::map<std::string, ChatCommand> ChatManager::commands;
std::vector<std::string> parseArgs(std::string full) {
std::stringstream ss(full);
std::istream_iterator<std::string> begin(ss);
std::istream_iterator<std::string> end;
return std::vector<std::string>(begin, end);
}
bool runCmd(std::string full, CNSocket* sock) {
std::vector<std::string> args = parseArgs(full);
std::string cmd = args[0].substr(1, args[0].size() - 1);
// check if the command exists
if (ChatManager::commands.find(cmd) != ChatManager::commands.end()) {
Player* plr = PlayerManager::getPlayer(sock);
ChatCommand command = ChatManager::commands[cmd];
// sanity check + does the player have the required account level to use the command?
if (plr != nullptr && plr->accountLevel <= command.requiredAccLevel) {
command.handlr(full, args, sock);
return true;
} else {
ChatManager::sendServerMessage(sock, "You don't have access to that command!");
return false;
}
}
ChatManager::sendServerMessage(sock, "Unknown command!");
return false;
}
void helpCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
ChatManager::sendServerMessage(sock, "Commands available to you:");
Player *plr = PlayerManager::getPlayer(sock);
for (auto& cmd : ChatManager::commands) {
if (cmd.second.requiredAccLevel >= plr->accountId)
ChatManager::sendServerMessage(sock, "/" + cmd.first + (cmd.second.help.length() > 0 ? " - " + cmd.second.help : ""));
}
}
void accessCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
ChatManager::sendServerMessage(sock, "Your access level is " + std::to_string(PlayerManager::getPlayer(sock)->accountLevel));
}
void populationCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
ChatManager::sendServerMessage(sock, std::to_string(PlayerManager::players.size()) + " players online");
}
void levelCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
if (args.size() < 2) {
ChatManager::sendServerMessage(sock, "/level: no level specified");
return;
}
Player *plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return;
char *tmp;
int level = std::strtol(args[1].c_str(), &tmp, 10);
if (*tmp)
return;
if ((level < 1 || level > 36) && plr->accountLevel > 30)
return;
if (!(level < 1 || level > 36))
plr->level = level;
INITSTRUCT(sP_FE2CL_REP_PC_CHANGE_LEVEL, resp);
resp.iPC_ID = plr->iID;
resp.iPC_Level = level;
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_CHANGE_LEVEL, sizeof(sP_FE2CL_REP_PC_CHANGE_LEVEL));
PlayerManager::sendToViewable(sock, (void*)&resp, P_FE2CL_REP_PC_CHANGE_LEVEL, sizeof(sP_FE2CL_REP_PC_CHANGE_LEVEL));
}
void mssCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
if (args.size() < 2) {
ChatManager::sendServerMessage(sock, "[MSS] Too few arguments");
ChatManager::sendServerMessage(sock, "[MSS] Usage: /mss <route> <add/remove/goto/clear/test/export> <<height>>");
return;
}
// Validate route number
char* routeNumC;
int routeNum = std::strtol(args[1].c_str(), &routeNumC, 10);
if (*routeNumC) {
// not an integer
ChatManager::sendServerMessage(sock, "[MSS] Invalid route number '" + args[1] + "'");
return;
}
if (args.size() < 3) {
ChatManager::sendServerMessage(sock, "[MSS] Too few arguments");
ChatManager::sendServerMessage(sock, "[MSS] Usage: /mss <route> <add/remove/goto/clear/test> <<height>>");
return;
}
// get the route (if it doesn't exist yet, this will also make it)
std::vector<WarpLocation>* route = &TableData::RunningSkywayRoutes[routeNum];
// mss <route> add <height>
if (args[2] == "add") {
// make sure height token exists
if (args.size() < 4) {
ChatManager::sendServerMessage(sock, "[MSS] Point height must be specified");
ChatManager::sendServerMessage(sock, "[MSS] Usage: /mss <route> add <height>");
return;
}
// validate height token
char* heightC;
int height = std::strtol(args[3].c_str(), &heightC, 10);
if (*heightC) {
ChatManager::sendServerMessage(sock, "[MSS] Invalid height " + args[3]);
return;
}
Player* plr = PlayerManager::getPlayer(sock);
route->push_back({ plr->x, plr->y, height }); // add point
ChatManager::sendServerMessage(sock, "[MSS] Added point (" + std::to_string(plr->x) + ", " + std::to_string(plr->y) + ", " + std::to_string(height) + ") to route " + std::to_string(routeNum));
return;
}
// mss <route> remove
if (args[2] == "remove") {
if (route->empty()) {
ChatManager::sendServerMessage(sock, "[MSS] Route " + std::to_string(routeNum) + " is empty");
return;
}
WarpLocation pulled = route->back();
route->pop_back(); // remove point at top of stack
ChatManager::sendServerMessage(sock, "[MSS] Removed point (" + std::to_string(pulled.x) + ", " + std::to_string(pulled.y) + ", " + std::to_string(pulled.z) + ") from route " + std::to_string(routeNum));
return;
}
// mss <route> goto
if (args[2] == "goto") {
if (route->empty()) {
ChatManager::sendServerMessage(sock, "[MSS] Route " + std::to_string(routeNum) + " is empty");
return;
}
WarpLocation pulled = route->back();
PlayerManager::sendPlayerTo(sock, pulled.x, pulled.y, pulled.z);
return;
}
// mss <route> clear
if (args[2] == "clear") {
route->clear();
ChatManager::sendServerMessage(sock, "[MSS] Cleared route " + std::to_string(routeNum));
return;
}
// mss <route> test
if (args[2] == "test") {
if (route->empty()) {
ChatManager::sendServerMessage(sock, "[MSS] Route " + std::to_string(routeNum) + " is empty");
return;
}
WarpLocation pulled = route->front();
PlayerManager::sendPlayerTo(sock, pulled.x, pulled.y, pulled.z);
TransportManager::testMssRoute(sock, route);
return;
}
// for compatibility: mss <route> export
if (args[2] == "export") {
ChatManager::sendServerMessage(sock, "Wrote gruntwork to " + settings::GRUNTWORKJSON);
TableData::flush();
return;
}
// mss ????
ChatManager::sendServerMessage(sock, "[MSS] Unknown command '" + args[2] + "'");
}
void summonWCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
if (args.size() < 2) {
ChatManager::sendServerMessage(sock, "/summonW: no mob type specified");
return;
}
Player* plr = PlayerManager::getPlayer(sock);
char *rest;
int type = std::strtol(args[1].c_str(), &rest, 10);
if (*rest) {
ChatManager::sendServerMessage(sock, "Invalid NPC number: " + args[1]);
return;
}
// permission & sanity check
if (plr == nullptr || type >= 3314)
return;
int team = NPCManager::NPCData[type]["m_iTeam"];
assert(NPCManager::nextId < INT32_MAX);
#define EXTRA_HEIGHT 200
BaseNPC *npc = nullptr;
int id = NPCManager::nextId++;
if (team == 2) {
npc = new Mob(plr->x, plr->y, plr->z + EXTRA_HEIGHT, plr->instanceID, type, NPCManager::NPCData[type], id);
MobManager::Mobs[npc->appearanceData.iNPC_ID] = (Mob*)npc;
// re-enable respawning
((Mob*)npc)->summoned = false;
} else {
npc = new BaseNPC(plr->x, plr->y, plr->z + EXTRA_HEIGHT, 0, plr->instanceID, type, id);
}
npc->appearanceData.iAngle = (plr->angle + 180) % 360;
NPCManager::NPCs[npc->appearanceData.iNPC_ID] = npc;
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, plr->x, plr->y, plr->z);
// if we're in a lair, we need to spawn the NPC in both the private instance and the template
if (PLAYERID(plr->instanceID) != 0) {
id = NPCManager::nextId++;
if (team == 2) {
npc = new Mob(plr->x, plr->y, plr->z + EXTRA_HEIGHT, MAPNUM(plr->instanceID), type, NPCManager::NPCData[type], id);
MobManager::Mobs[npc->appearanceData.iNPC_ID] = (Mob*)npc;
((Mob*)npc)->summoned = false;
} else {
npc = new BaseNPC(plr->x, plr->y, plr->z + EXTRA_HEIGHT, 0, MAPNUM(plr->instanceID), type, id);
}
npc->appearanceData.iAngle = (plr->angle + 180) % 360;
NPCManager::NPCs[npc->appearanceData.iNPC_ID] = npc;
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, plr->x, plr->y, plr->z);
}
ChatManager::sendServerMessage(sock, "/summonW: placed mob with type: " + std::to_string(type) +
", id: " + std::to_string(npc->appearanceData.iNPC_ID));
TableData::RunningMobs[npc->appearanceData.iNPC_ID] = npc; // only record the one in the template
}
void unsummonWCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
PlayerView& plrv = PlayerManager::players[sock];
Player* plr = plrv.plr;
BaseNPC* npc = NPCManager::getNearestNPC(plrv.currentChunks, plr->x, plr->y, plr->z);
if (npc == nullptr) {
ChatManager::sendServerMessage(sock, "/unsummonW: No NPCs found nearby");
return;
}
if (TableData::RunningMobs.find(npc->appearanceData.iNPC_ID) == TableData::RunningMobs.end()) {
ChatManager::sendServerMessage(sock, "/unsummonW: Closest NPC is not a gruntwork mob.");
return;
}
ChatManager::sendServerMessage(sock, "/unsummonW: removed mob with type: " + std::to_string(npc->appearanceData.iNPCType) +
", id: " + std::to_string(npc->appearanceData.iNPC_ID));
TableData::RunningMobs.erase(npc->appearanceData.iNPC_ID);
NPCManager::destroyNPC(npc->appearanceData.iNPC_ID);
}
void toggleAiCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
MobManager::simulateMobs = !MobManager::simulateMobs;
if (MobManager::simulateMobs)
return;
// return all mobs to their spawn points
for (auto& pair : MobManager::Mobs) {
pair.second->state = MobState::RETREAT;
pair.second->target = nullptr;
pair.second->nextMovement = getTime();
// mobs with static paths can chill where they are
if (pair.second->staticPath) {
pair.second->roamX = pair.second->appearanceData.iX;
pair.second->roamY = pair.second->appearanceData.iY;
pair.second->roamZ = pair.second->appearanceData.iZ;
} else {
pair.second->roamX = pair.second->spawnX;
pair.second->roamY = pair.second->spawnY;
pair.second->roamZ = pair.second->spawnZ;
}
}
}
void npcRotateCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
PlayerView& plrv = PlayerManager::players[sock];
Player* plr = plrv.plr;
BaseNPC* npc = NPCManager::getNearestNPC(plrv.currentChunks, plr->x, plr->y, plr->z);
if (npc == nullptr) {
ChatManager::sendServerMessage(sock, "[NPCR] No NPCs found nearby");
return;
}
int angle = (plr->angle + 180) % 360;
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->appearanceData.iX, npc->appearanceData.iY, npc->appearanceData.iZ, angle);
// if it's a gruntwork NPC, rotate in-place
if (TableData::RunningMobs.find(npc->appearanceData.iNPC_ID) != TableData::RunningMobs.end()) {
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->appearanceData.iX, npc->appearanceData.iY, npc->appearanceData.iZ, angle);
ChatManager::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) + " for gruntwork NPC "
+ std::to_string(npc->appearanceData.iNPC_ID));
} else {
TableData::RunningNPCRotations[npc->appearanceData.iNPC_ID] = angle;
ChatManager::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) + " for NPC "
+ std::to_string(npc->appearanceData.iNPC_ID));
}
// update rotation clientside
INITSTRUCT(sP_FE2CL_NPC_ENTER, pkt);
pkt.NPCAppearanceData = npc->appearanceData;
sock->sendPacket((void*)&pkt, P_FE2CL_NPC_ENTER, sizeof(sP_FE2CL_NPC_ENTER));
}
void refreshCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
Player* plr = PlayerManager::getPlayer(sock);
PlayerManager::sendPlayerTo(sock, plr->x, plr->y, plr->z);
}
void instanceCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
Player* plr = PlayerManager::getPlayer(sock);
// no additional arguments: report current instance ID
if (args.size() < 2) {
ChatManager::sendServerMessage(sock, "[INST] Current instance ID: " + std::to_string(plr->instanceID));
ChatManager::sendServerMessage(sock, "[INST] (Map " + std::to_string(MAPNUM(plr->instanceID)) + ", instance " + std::to_string(PLAYERID(plr->instanceID)) + ")");
return;
}
// move player to specified instance
// validate instance ID
char* instanceS;
int instance = std::strtol(args[1].c_str(), &instanceS, 10);
if (*instanceS) {
ChatManager::sendServerMessage(sock, "[INST] Invalid instance ID: " + args[1]);
return;
}
PlayerManager::sendPlayerTo(sock, plr->x, plr->y, plr->z, instance);
ChatManager::sendServerMessage(sock, "[INST] Switched to instance with ID " + std::to_string(instance));
}
void npcInstanceCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
PlayerView& plrv = PlayerManager::players[sock];
Player* plr = plrv.plr;
if (args.size() < 2) {
ChatManager::sendServerMessage(sock, "[NPCI] Instance ID must be specified");
ChatManager::sendServerMessage(sock, "[NPCI] Usage: /npci <instance ID>");
return;
}
BaseNPC* npc = NPCManager::getNearestNPC(plrv.currentChunks, plr->x, plr->y, plr->z);
if (npc == nullptr) {
ChatManager::sendServerMessage(sock, "[NPCI] No NPCs found nearby");
return;
}
// validate instance ID
char* instanceS;
int instance = std::strtol(args[1].c_str(), &instanceS, 10);
if (*instanceS) {
ChatManager::sendServerMessage(sock, "[NPCI] Invalid instance ID: " + args[1]);
return;
}
ChatManager::sendServerMessage(sock, "[NPCI] Moving NPC with ID " + std::to_string(npc->appearanceData.iNPC_ID) + " to instance " + std::to_string(instance));
TableData::RunningNPCMapNumbers[npc->appearanceData.iNPC_ID] = instance;
NPCManager::updateNPCInstance(npc->appearanceData.iNPC_ID, instance);
}
void minfoCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
Player* plr = PlayerManager::getPlayer(sock);
ChatManager::sendServerMessage(sock, "[MINFO] Current mission ID: " + std::to_string(plr->CurrentMissionID));
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
if (plr->tasks[i] != 0) {
TaskData& task = *MissionManager::Tasks[plr->tasks[i]];
if ((int)(task["m_iHMissionID"]) == plr->CurrentMissionID) {
ChatManager::sendServerMessage(sock, "[MINFO] Current task ID: " + std::to_string(plr->tasks[i]));
ChatManager::sendServerMessage(sock, "[MINFO] Current task type: " + std::to_string((int)(task["m_iHTaskType"])));
ChatManager::sendServerMessage(sock, "[MINFO] Current waypoint NPC ID: " + std::to_string((int)(task["m_iSTGrantWayPoint"])));
for (int j = 0; j < 3; j++)
if ((int)(task["m_iCSUEnemyID"][j]) != 0)
ChatManager::sendServerMessage(sock, "[MINFO] Current task mob #" + std::to_string(j+1) +": " + std::to_string((int)(task["m_iCSUEnemyID"][j])));
return;
}
}
}
}
void tasksCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
Player* plr = PlayerManager::getPlayer(sock);
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
if (plr->tasks[i] != 0) {
TaskData& task = *MissionManager::Tasks[plr->tasks[i]];
ChatManager::sendServerMessage(sock, "[TASK-" + std::to_string(i) + "] mission ID: " + std::to_string((int)(task["m_iHMissionID"])));
ChatManager::sendServerMessage(sock, "[TASK-" + std::to_string(i) + "] task ID: " + std::to_string(plr->tasks[i]));
}
}
}
void flushCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
TableData::flush();
ChatManager::sendServerMessage(sock, "Wrote gruntwork to " + settings::GRUNTWORKJSON);
}
void ChatManager::init() {
REGISTER_SHARD_PACKET(P_CL2FE_REQ_SEND_FREECHAT_MESSAGE, chatHandler);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_AVATAR_EMOTES_CHAT, emoteHandler);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_SEND_MENUCHAT_MESSAGE, menuChatHandler);
registerCommand("help", 100, helpCommand, "list all unlocked server-side commands");
registerCommand("access", 100, accessCommand, "print your access level");
registerCommand("instance", 30, instanceCommand, "print or change your current instance");
registerCommand("mss", 30, mssCommand, "edit Monkey Skyway routes");
registerCommand("npcr", 30, npcRotateCommand, "rotate NPCs");
registerCommand("npci", 30, npcInstanceCommand, "move NPCs across instances");
registerCommand("summonW", 30, summonWCommand, "permanently summon NPCs");
registerCommand("unsummonW", 30, unsummonWCommand, "delete permanently summoned NPCs");
registerCommand("toggleai", 30, toggleAiCommand, "enable/disable mob AI");
registerCommand("flush", 30, flushCommand, "save gruntwork to file");
registerCommand("level", 50, levelCommand, "change your character's level");
registerCommand("population", 100, populationCommand, "check how many players are online");
registerCommand("refresh", 100, refreshCommand, "teleport yourself to your current location");
registerCommand("minfo", 30, minfoCommand, "show details of the current mission and task.");
registerCommand("tasks", 30, tasksCommand, "list all active missions and their respective task ids.");
}
void ChatManager::registerCommand(std::string cmd, int requiredLevel, CommandHandler handlr, std::string help) {
commands[cmd] = ChatCommand(requiredLevel, handlr, help);
}
void ChatManager::chatHandler(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_SEND_FREECHAT_MESSAGE))
return; // malformed packet
sP_CL2FE_REQ_SEND_FREECHAT_MESSAGE* chat = (sP_CL2FE_REQ_SEND_FREECHAT_MESSAGE*)data->buf;
PlayerView plr = PlayerManager::players[sock];
Player* plr = PlayerManager::getPlayer(sock);
std::string fullChat = U16toU8(chat->szFreeChat);
if (fullChat.length() > 1 && fullChat[0] == CMD_PREFIX) { // PREFIX
runCmd(fullChat, sock);
return;
}
// send to client
INITSTRUCT(sP_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC, resp);
memcpy(resp.szFreeChat, chat->szFreeChat, sizeof(chat->szFreeChat));
resp.iPC_ID = plr.plr->iID;
resp.iPC_ID = plr->iID;
resp.iEmoteCode = chat->iEmoteCode;
sock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC));
// send to visible players
for (CNSocket* otherSock : plr.viewable) {
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC));
}
PlayerManager::sendToViewable(sock, (void*)&resp, P_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC));
}
void ChatManager::menuChatHandler(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_SEND_MENUCHAT_MESSAGE))
return; // malformed packet
sP_CL2FE_REQ_SEND_MENUCHAT_MESSAGE* chat = (sP_CL2FE_REQ_SEND_MENUCHAT_MESSAGE*)data->buf;
PlayerView plr = PlayerManager::players[sock];
// send to client
INITSTRUCT(sP_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC, resp);
@@ -41,10 +504,9 @@ void ChatManager::menuChatHandler(CNSocket* sock, CNPacketData* data) {
sock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC));
// send to visible players
for (CNSocket* otherSock : plr.viewable) {
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC));
}
PlayerManager::sendToViewable(sock, (void*)&resp, P_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC));
}
void ChatManager::emoteHandler(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_PC_AVATAR_EMOTES_CHAT))
return; // ignore the malformed packet
@@ -52,16 +514,25 @@ void ChatManager::emoteHandler(CNSocket* sock, CNPacketData* data) {
// you can dance with friends!!!!!!!!
sP_CL2FE_REQ_PC_AVATAR_EMOTES_CHAT* emote = (sP_CL2FE_REQ_PC_AVATAR_EMOTES_CHAT*)data->buf;
PlayerView plr = PlayerManager::players[sock];
Player* plr = PlayerManager::getPlayer(sock);
// send to client
INITSTRUCT(sP_FE2CL_REP_PC_AVATAR_EMOTES_CHAT, resp);
resp.iEmoteCode = emote->iEmoteCode;
resp.iID_From = plr.plr->iID;
resp.iID_From = plr->iID;
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_AVATAR_EMOTES_CHAT, sizeof(sP_FE2CL_REP_PC_AVATAR_EMOTES_CHAT));
// send to visible players (players within render distance)
for (CNSocket* otherSock : plr.viewable) {
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_PC_AVATAR_EMOTES_CHAT, sizeof(sP_FE2CL_REP_PC_AVATAR_EMOTES_CHAT));
}
PlayerManager::sendToViewable(sock, (void*)&resp, P_FE2CL_REP_PC_AVATAR_EMOTES_CHAT, sizeof(sP_FE2CL_REP_PC_AVATAR_EMOTES_CHAT));
}
void ChatManager::sendServerMessage(CNSocket* sock, std::string msg) {
INITSTRUCT(sP_FE2CL_PC_MOTD_LOGIN, motd);
motd.iType = 1;
// convert string to u16 and write it to the buffer
U8toU16(msg, (char16_t*)motd.szSystemMsg, sizeof(motd.szSystemMsg));
// send the packet :)
sock->sendPacket((void*)&motd, P_FE2CL_PC_MOTD_LOGIN, sizeof(sP_FE2CL_PC_MOTD_LOGIN));
}

View File

@@ -2,10 +2,28 @@
#include "CNShardServer.hpp"
#define CMD_PREFIX '/'
typedef void (*CommandHandler)(std::string fullString, std::vector<std::string>& args, CNSocket* sock);
struct ChatCommand {
int requiredAccLevel;
std::string help;
CommandHandler handlr;
ChatCommand(int r, CommandHandler h): requiredAccLevel(r), handlr(h) {}
ChatCommand(int r, CommandHandler h, std::string str): requiredAccLevel(r), help(str), handlr(h) {}
ChatCommand(): ChatCommand(0, nullptr) {}
};
namespace ChatManager {
extern std::map<std::string, ChatCommand> commands;
void init();
void registerCommand(std::string cmd, int requiredLevel, CommandHandler handlr, std::string help = "");
void chatHandler(CNSocket* sock, CNPacketData* data);
void emoteHandler(CNSocket* sock, CNPacketData* data);
void menuChatHandler(CNSocket* sock, CNPacketData* data);
void sendServerMessage(CNSocket* sock, std::string msg); // uses MOTD
}

253
src/ChunkManager.cpp Normal file
View File

@@ -0,0 +1,253 @@
#include "ChunkManager.hpp"
#include "PlayerManager.hpp"
#include "NPCManager.hpp"
#include "settings.hpp"
#include "MobManager.hpp"
std::map<std::tuple<int, int, uint64_t>, Chunk*> ChunkManager::chunks;
void ChunkManager::init() {} // stubbed
void ChunkManager::addNPC(int posX, int posY, uint64_t instanceID, int32_t id) {
std::tuple<int, int, uint64_t> pos = grabChunk(posX, posY, instanceID);
// make chunk if it doesn't exist!
if (chunks.find(pos) == chunks.end()) {
chunks[pos] = new Chunk();
chunks[pos]->players = std::set<CNSocket*>();
chunks[pos]->NPCs = std::set<int32_t>();
}
Chunk* chunk = chunks[pos];
chunk->NPCs.insert(id);
}
void ChunkManager::addPlayer(int posX, int posY, uint64_t instanceID, CNSocket* sock) {
std::tuple<int, int, uint64_t> pos = grabChunk(posX, posY, instanceID);
// make chunk if it doesn't exist!
if (chunks.find(pos) == chunks.end()) {
chunks[pos] = new Chunk();
chunks[pos]->players = std::set<CNSocket*>();
chunks[pos]->NPCs = std::set<int32_t>();
}
Chunk* chunk = chunks[pos];
chunk->players.insert(sock);
}
bool ChunkManager::removePlayer(std::tuple<int, int, uint64_t> chunkPos, CNSocket* sock) {
if (!checkChunk(chunkPos))
return false; // do nothing if chunk doesn't even exist
Chunk* chunk = chunks[chunkPos];
chunk->players.erase(sock); // gone
// if players and NPCs are empty, free chunk and remove it from surrounding views
if (chunk->NPCs.size() == 0 && chunk->players.size() == 0) {
destroyChunk(chunkPos);
// the chunk we left was destroyed
return true;
}
// the chunk we left was not destroyed
return false;
}
bool ChunkManager::removeNPC(std::tuple<int, int, uint64_t> chunkPos, int32_t id) {
if (!checkChunk(chunkPos))
return false; // do nothing if chunk doesn't even exist
Chunk* chunk = chunks[chunkPos];
chunk->NPCs.erase(id); // gone
// if players and NPCs are empty, free chunk and remove it from surrounding views
if (chunk->NPCs.size() == 0 && chunk->players.size() == 0) {
destroyChunk(chunkPos);
// the chunk we left was destroyed
return true;
}
// the chunk we left was not destroyed
return false;
}
void ChunkManager::destroyChunk(std::tuple<int, int, uint64_t> chunkPos) {
if (!checkChunk(chunkPos))
return; // chunk doesn't exist, we don't need to do anything
Chunk* chunk = chunks[chunkPos];
// unspawn all of the mobs/npcs
std::set npcIDs(chunk->NPCs);
for (uint32_t id : npcIDs) {
NPCManager::destroyNPC(id);
}
// we also need to remove it from all NPCs/Players views
for (Chunk* otherChunk : grabChunks(chunkPos)) {
if (otherChunk == chunk)
continue;
// remove from NPCs
for (uint32_t id : otherChunk->NPCs) {
if (std::find(NPCManager::NPCs[id]->currentChunks.begin(), NPCManager::NPCs[id]->currentChunks.end(), chunk) != NPCManager::NPCs[id]->currentChunks.end()) {
NPCManager::NPCs[id]->currentChunks.erase(std::remove(NPCManager::NPCs[id]->currentChunks.begin(), NPCManager::NPCs[id]->currentChunks.end(), chunk), NPCManager::NPCs[id]->currentChunks.end());
}
}
// remove from players
for (CNSocket* sock : otherChunk->players) {
PlayerView* plyr = &PlayerManager::players[sock];
if (std::find(plyr->currentChunks.begin(), plyr->currentChunks.end(), chunk) != plyr->currentChunks.end()) {
plyr->currentChunks.erase(std::remove(plyr->currentChunks.begin(), plyr->currentChunks.end(), chunk), plyr->currentChunks.end());
}
}
}
assert(chunk->players.size() == 0);
// remove from the map
chunks.erase(chunkPos);
delete chunk;
}
bool ChunkManager::checkChunk(std::tuple<int, int, uint64_t> chunk) {
return chunks.find(chunk) != chunks.end();
}
std::tuple<int, int, uint64_t> ChunkManager::grabChunk(int posX, int posY, uint64_t instanceID) {
return std::make_tuple(posX / (settings::VIEWDISTANCE / 3), posY / (settings::VIEWDISTANCE / 3), instanceID);
}
std::vector<Chunk*> ChunkManager::grabChunks(std::tuple<int, int, uint64_t> chunk) {
std::vector<Chunk*> chnks;
chnks.reserve(9);
int x, y;
uint64_t inst;
std::tie(x, y, inst) = chunk;
// grabs surrounding chunks if they exist
for (int i = -1; i < 2; i++) {
for (int z = -1; z < 2; z++) {
std::tuple<int, int, uint64_t> pos = std::make_tuple(x+i, y+z, inst);
// if chunk exists, add it to the vector
if (checkChunk(pos))
chnks.push_back(chunks[pos]);
}
}
return chnks;
}
// returns the chunks that aren't shared (only from from)
std::vector<Chunk*> ChunkManager::getDeltaChunks(std::vector<Chunk*> from, std::vector<Chunk*> to) {
std::vector<Chunk*> delta;
for (Chunk* i : from) {
bool found = false;
// search for it in the other array
for (Chunk* z : to) {
if (i == z) {
found = true;
break;
}
}
// add it to the vector if we didn't find it!
if (!found)
delta.push_back(i);
}
return delta;
}
/*
* inefficient algorithm to get all chunks from a specific instance
*/
std::vector<std::tuple<int, int, uint64_t>> ChunkManager::getChunksInMap(uint64_t mapNum) {
std::vector<std::tuple<int, int, uint64_t>> chnks;
for (auto it = ChunkManager::chunks.begin(); it != ChunkManager::chunks.end(); it++) {
if (std::get<2>(it->first) == mapNum) {
chnks.push_back(it->first);
}
}
return chnks;
}
bool ChunkManager::inPopulatedChunks(int posX, int posY, uint64_t instanceID) {
auto chunk = ChunkManager::grabChunk(posX, posY, instanceID);
auto nearbyChunks = ChunkManager::grabChunks(chunk);
for (Chunk *c: nearbyChunks) {
if (!c->players.empty())
return true;
}
return false;
}
void ChunkManager::createInstance(uint64_t instanceID) {
std::vector<std::tuple<int, int, uint64_t>> templateChunks = ChunkManager::getChunksInMap(MAPNUM(instanceID)); // base instance chunks
if (ChunkManager::getChunksInMap(instanceID).size() == 0) { // only instantiate if the instance doesn't exist already
std::cout << "Creating instance " << instanceID << std::endl;
for (std::tuple<int, int, uint64_t> &coords : templateChunks) {
for (int npcID : chunks[coords]->NPCs) {
// make a copy of each NPC in the template chunks and put them in the new instance
int newID = NPCManager::nextId++;
BaseNPC* baseNPC = NPCManager::NPCs[npcID];
if (baseNPC->npcClass == NPC_MOB) {
Mob* newMob = new Mob(baseNPC->appearanceData.iX, baseNPC->appearanceData.iY, baseNPC->appearanceData.iZ, baseNPC->appearanceData.iAngle,
instanceID, baseNPC->appearanceData.iNPCType, baseNPC->appearanceData.iHP, NPCManager::NPCData[baseNPC->appearanceData.iNPCType], newID);
NPCManager::NPCs[newID] = newMob;
MobManager::Mobs[newID] = newMob;
} else {
BaseNPC* newNPC = new BaseNPC(baseNPC->appearanceData.iX, baseNPC->appearanceData.iY, baseNPC->appearanceData.iZ, baseNPC->appearanceData.iAngle,
instanceID, baseNPC->appearanceData.iNPCType, newID);
NPCManager::NPCs[newID] = newNPC;
}
NPCManager::updateNPCInstance(newID, instanceID); // make sure the npc state gets updated
}
}
} else {
std::cout << "Instance " << instanceID << " already exists" << std::endl;
}
}
void ChunkManager::destroyInstance(uint64_t instanceID) {
std::cout << "Deleting instance " << instanceID << std::endl;
std::vector<std::tuple<int, int, uint64_t>> instanceChunks = ChunkManager::getChunksInMap(instanceID);
for (std::tuple<int, int, uint64_t>& coords : instanceChunks) {
destroyChunk(coords);
}
}
void ChunkManager::destroyInstanceIfEmpty(uint64_t instanceID) {
if (PLAYERID(instanceID) == 0)
return; // don't clean up overworld/IZ chunks
std::vector<std::tuple<int, int, uint64_t>> sourceChunkCoords = getChunksInMap(instanceID);
for (std::tuple<int, int, uint64_t>& coords : sourceChunkCoords) {
Chunk* chunk = chunks[coords];
if (chunk->players.size() > 0)
return; // there are still players inside
}
destroyInstance(instanceID);
}

44
src/ChunkManager.hpp Normal file
View File

@@ -0,0 +1,44 @@
#pragma once
#include "CNProtocol.hpp"
#include <utility>
#include <vector>
#include <set>
#include <map>
#include <tuple>
class Chunk {
public:
std::set<CNSocket*> players;
std::set<int32_t> NPCs;
};
enum {
INSTANCE_OVERWORLD, // default instance every player starts in
INSTANCE_IZ, // these aren't actually used
INSTANCE_UNIQUE // these aren't actually used
};
namespace ChunkManager {
void init();
void cleanup();
extern std::map<std::tuple<int, int, uint64_t>, Chunk*> chunks;
void addNPC(int posX, int posY, uint64_t instanceID, int32_t id);
void addPlayer(int posX, int posY, uint64_t instanceID, CNSocket* sock);
bool removePlayer(std::tuple<int, int, uint64_t> chunkPos, CNSocket* sock);
bool removeNPC(std::tuple<int, int, uint64_t> chunkPos, int32_t id);
bool checkChunk(std::tuple<int, int, uint64_t> chunk);
void destroyChunk(std::tuple<int, int, uint64_t> chunkPos);
std::tuple<int, int, uint64_t> grabChunk(int posX, int posY, uint64_t instanceID);
std::vector<Chunk*> grabChunks(std::tuple<int, int, uint64_t> chunkPos);
std::vector<Chunk*> getDeltaChunks(std::vector<Chunk*> from, std::vector<Chunk*> to);
std::vector<std::tuple<int, int, uint64_t>> getChunksInMap(uint64_t mapNum);
bool inPopulatedChunks(int posX, int posY, uint64_t instanceID);
void createInstance(uint64_t);
void destroyInstance(uint64_t);
void destroyInstanceIfEmpty(uint64_t);
}

View File

@@ -1,131 +0,0 @@
#include "CombatManager.hpp"
#include "PlayerManager.hpp"
#include "NPCManager.hpp"
#include "ItemManager.hpp"
#include <assert.h>
void CombatManager::init() {
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ATTACK_NPCs, pcAttackNpcs);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_COMBAT_BEGIN, combatBegin);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_COMBAT_END, combatEnd);
REGISTER_SHARD_PACKET(P_CL2FE_DOT_DAMAGE_ONOFF, dotDamageOnOff);
}
void CombatManager::pcAttackNpcs(CNSocket *sock, CNPacketData *data) {
sP_CL2FE_REQ_PC_ATTACK_NPCs* pkt = (sP_CL2FE_REQ_PC_ATTACK_NPCs*)data->buf;
Player *plr = PlayerManager::getPlayer(sock);
// sanity check
if (!validInVarPacket(sizeof(sP_CL2FE_REQ_PC_ATTACK_NPCs), pkt->iNPCCnt, sizeof(int32_t), data->size)) {
std::cout << "[WARN] bad sP_CL2FE_REQ_PC_ATTACK_NPCs packet size\n";
return;
}
int32_t *pktdata = (int32_t*)((uint8_t*)data->buf + sizeof(sP_CL2FE_REQ_PC_ATTACK_NPCs));
/*
* Due to the possibility of multiplication overflow (and regular buffer overflow),
* both incoming and outgoing variable-length packets must be validated, at least if
* the number of trailing structs isn't well known (ie. it's from the client).
*/
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_ATTACK_NPCs_SUCC), pkt->iNPCCnt, sizeof(sAttackResult))) {
std::cout << "[WARN] bad sP_FE2CL_PC_ATTACK_NPCs_SUCC packet size\n";
return;
}
// initialize response struct
size_t resplen = sizeof(sP_FE2CL_PC_ATTACK_NPCs_SUCC) + pkt->iNPCCnt * sizeof(sAttackResult);
uint8_t respbuf[4096];
memset(respbuf, 0, resplen);
sP_FE2CL_PC_ATTACK_NPCs_SUCC *resp = (sP_FE2CL_PC_ATTACK_NPCs_SUCC*)respbuf;
sAttackResult *respdata = (sAttackResult*)(respbuf+sizeof(sP_FE2CL_PC_ATTACK_NPCs_SUCC));
resp->iNPCCnt = pkt->iNPCCnt;
for (int i = 0; i < pkt->iNPCCnt; i++) {
if (NPCManager::NPCs.find(pktdata[i]) == NPCManager::NPCs.end()) {
// not sure how to best handle this
std::cout << "[WARN] pcAttackNpcs: mob ID not found" << std::endl;
return;
}
BaseNPC& mob = NPCManager::NPCs[pktdata[i]];
mob.appearanceData.iHP -= 100;
if (mob.appearanceData.iHP <= 0)
giveReward(sock);
// TODO: despawn mobs when they die
respdata[i].iID = mob.appearanceData.iNPC_ID;
respdata[i].iDamage = 100;
respdata[i].iHP = mob.appearanceData.iHP;
respdata[i].iHitFlag = 2;
}
sock->sendPacket((void*)respbuf, P_FE2CL_PC_ATTACK_NPCs_SUCC, resplen);
// a bit of a hack: these are the same size, so we can reuse the output packet
assert(sizeof(sP_FE2CL_PC_ATTACK_NPCs_SUCC) == sizeof(sP_FE2CL_PC_ATTACK_NPCs));
sP_FE2CL_PC_ATTACK_NPCs *resp1 = (sP_FE2CL_PC_ATTACK_NPCs*)respbuf;
resp1->iPC_ID = plr->iID;
// send to other players
for (CNSocket *s : PlayerManager::players[sock].viewable) {
if (s == sock)
continue;
s->sendPacket((void*)respbuf, P_FE2CL_PC_ATTACK_NPCs, resplen);
}
}
void CombatManager::combatBegin(CNSocket *sock, CNPacketData *data) {} // stub
void CombatManager::combatEnd(CNSocket *sock, CNPacketData *data) {} // stub
void CombatManager::dotDamageOnOff(CNSocket *sock, CNPacketData *data) {} // stub
void CombatManager::giveReward(CNSocket *sock) {
Player *plr = PlayerManager::getPlayer(sock);
const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward);
assert(resplen < CN_PACKET_BUFFER_SIZE);
// we know it's only one trailing struct, so we can skip full validation
uint8_t respbuf[resplen]; // not a variable length array, don't worry
sP_FE2CL_REP_REWARD_ITEM *reward = (sP_FE2CL_REP_REWARD_ITEM *)respbuf;
sItemReward *item = (sItemReward *)(respbuf + sizeof(sP_FE2CL_REP_REWARD_ITEM));
// don't forget to zero the buffer!
memset(respbuf, 0, resplen);
// update player
plr->money += 50;
plr->fusionmatter += 70;
// simple rewards
reward->m_iCandy = plr->money;
reward->m_iFusionMatter = plr->fusionmatter;
reward->iFatigue = 100; // prevents warning message
reward->iFatigue_Level = 1;
reward->iItemCnt = 1; // remember to update resplen if you change this
int slot = ItemManager::findFreeSlot(plr);
if (slot == -1) {
// no room for an item, but you still get FM and taros
sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, sizeof(sP_FE2CL_REP_REWARD_ITEM));
} else {
// item reward
item->sItem.iType = 9;
item->sItem.iID = 1;
item->iSlotNum = slot;
item->eIL = 1; // Inventory Location. 1 means player inventory.
// update player
plr->Inven[slot] = item->sItem;
sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen);
}
}

View File

@@ -1,16 +0,0 @@
#pragma once
#include "CNProtocol.hpp"
#include "CNShared.hpp"
#include "CNShardServer.hpp"
namespace CombatManager {
void init();
void pcAttackNpcs(CNSocket *sock, CNPacketData *data);
void combatBegin(CNSocket *sock, CNPacketData *data);
void combatEnd(CNSocket *sock, CNPacketData *data);
void dotDamageOnOff(CNSocket *sock, CNPacketData *data);
void giveReward(CNSocket *sock);
}

View File

@@ -1,4 +1,5 @@
#include "Database.hpp"
#include "Database.hpp"
#include "contrib/bcrypt/BCrypt.hpp"
#include "CNProtocol.hpp"
#include <string>
@@ -8,51 +9,90 @@
#include "Player.hpp"
#include "CNStructs.hpp"
#include "contrib/sqlite/sqlite_orm.h"
#include "MissionManager.hpp"
#if defined(__MINGW32__) && !defined(_GLIBCXX_HAS_GTHREADS)
#include "mingw/mingw.mutex.h"
#else
#include <mutex>
#endif
using namespace sqlite_orm;
std::mutex dbCrit;
# pragma region DatabaseScheme
auto db = make_storage("database.db",
make_table("Accounts",
make_column("AccountID", &Database::Account::AccountID, autoincrement(), primary_key()),
make_column("Login", &Database::Account::Login),
make_column("Password", &Database::Account::Password),
make_column("Selected", &Database::Account::Selected)
make_column("Selected", &Database::Account::Selected),
make_column("Created", &Database::Account::Created),
make_column("LastLogin", &Database::Account::LastLogin)
),
make_table("Players",
make_column("PlayerID", &Database::DbPlayer::PlayerID, autoincrement(), primary_key()),
make_column("AccountID", &Database::DbPlayer::AccountID),
make_column("Slot", &Database::DbPlayer::slot),
make_column("Firstname", &Database::DbPlayer::FirstName),
make_column("LastName", &Database::DbPlayer::LastName),
make_column("Firstname", &Database::DbPlayer::FirstName, collate_nocase()),
make_column("LastName", &Database::DbPlayer::LastName, collate_nocase()),
make_column("Created", &Database::DbPlayer::Created),
make_column("LastLogin", &Database::DbPlayer::LastLogin),
make_column("Level", &Database::DbPlayer::Level),
make_column("Nano1", &Database::DbPlayer::Nano1),
make_column("Nano2", &Database::DbPlayer::Nano2),
make_column("Nano3", &Database::DbPlayer::Nano3),
make_column("AppearanceFlag", &Database::DbPlayer::AppearanceFlag),
make_column("TutorialFlag", &Database::DbPlayer::TutorialFlag),
make_column("PayZoneFlag", &Database::DbPlayer::PayZoneFlag),
make_column("XCoordinates", &Database::DbPlayer::x_coordinates),
make_column("YCoordinates", &Database::DbPlayer::y_coordinates),
make_column("ZCoordinates", &Database::DbPlayer::z_coordinates),
make_column("ZCoordinates", &Database::DbPlayer::z_coordinates),
make_column("Angle", &Database::DbPlayer::angle),
make_column("Body", &Database::DbPlayer::Body),
make_column("Class", &Database::DbPlayer::Class),
make_column("EquipFoot", &Database::DbPlayer::EquipFoot),
make_column("EquipLB", &Database::DbPlayer::EquipLB),
make_column("EquipUB", &Database::DbPlayer::EquipUB),
make_column("EquipWeapon1", &Database::DbPlayer::EquipWeapon1),
make_column("EyeColor", &Database::DbPlayer::EyeColor),
make_column("FaceStyle", &Database::DbPlayer::FaceStyle),
make_column("Gender", &Database::DbPlayer::Gender),
make_column("HP", &Database::DbPlayer::HP),
make_column("HairColor", &Database::DbPlayer::HairColor),
make_column("HairStyle", &Database::DbPlayer::HairStyle),
make_column("Height", &Database::DbPlayer::Height),
make_column("NameCheck", &Database::DbPlayer::NameCheck),
make_column("SkinColor", &Database::DbPlayer::SkinColor),
make_column("isGM", &Database::DbPlayer::isGM),
make_column("Height", &Database::DbPlayer::Height),
make_column("NameCheck", &Database::DbPlayer::NameCheck),
make_column("SkinColor", &Database::DbPlayer::SkinColor),
make_column("AccountLevel", &Database::DbPlayer::AccountLevel),
make_column("FusionMatter", &Database::DbPlayer::FusionMatter),
make_column("Taros", &Database::DbPlayer::Taros)
make_column("Taros", &Database::DbPlayer::Taros),
make_column("Quests", &Database::DbPlayer::QuestFlag),
make_column("BatteryW", &Database::DbPlayer::BatteryW),
make_column("BatteryN", &Database::DbPlayer::BatteryN),
make_column("Mentor", &Database::DbPlayer::Mentor),
make_column("WarpLocationFlag", &Database::DbPlayer::WarpLocationFlag),
make_column("SkywayLocationFlag1", &Database::DbPlayer::SkywayLocationFlag1),
make_column("SkywayLocationFlag2", &Database::DbPlayer::SkywayLocationFlag2),
make_column("CurrentMissionID", &Database::DbPlayer::CurrentMissionID)
),
make_table("Inventory",
make_column("AccountID", &Database::Inventory::AccountID, primary_key())
make_column("PlayerId", &Database::Inventory::playerId),
make_column("Slot", &Database::Inventory::slot),
make_column("Id", &Database::Inventory::id),
make_column("Type", &Database::Inventory::Type),
make_column("Opt", &Database::Inventory::Opt),
make_column("TimeLimit", &Database::Inventory::TimeLimit)
),
make_table("Nanos",
make_column("PlayerId", &Database::Nano::playerId),
make_column("Id", &Database::Nano::iID),
make_column("Skill", &Database::Nano::iSkillID),
make_column("Stamina", &Database::Nano::iStamina)
),
make_table("RunningQuests",
make_column("PlayerId", &Database::DbQuest::PlayerId),
make_column("TaskId", &Database::DbQuest::TaskId),
make_column("RemainingNPCCount1", &Database::DbQuest::RemainingNPCCount1),
make_column("RemainingNPCCount2", &Database::DbQuest::RemainingNPCCount2),
make_column("RemainingNPCCount3", &Database::DbQuest::RemainingNPCCount3)
)
);
@@ -60,113 +100,142 @@ auto db = make_storage("database.db",
#pragma region LoginServer
void Database::open()
{
//this parameter means it will try to preserve data during migration
void Database::open() {
// this parameter means it will try to preserve data during migration
bool preserve = true;
db.sync_schema(preserve);
DEBUGLOG(
std::cout << "[DB] Database in operation" << std::endl;
)
std::cout << "[INFO] Database in operation ";
int accounts = getAccountsCount();
int players = getPlayersCount();
std::string message = "";
if (accounts > 0) {
message += ": Found " + std::to_string(accounts) + " Account";
if (accounts > 1)
message += "s";
}
if (players > 0) {
message += " and " + std::to_string(players) + " Player Character";
if (players > 1)
message += "s";
}
std::cout << message << std::endl;
}
int Database::addAccount(std::string login, std::string password)
{
int Database::getAccountsCount() {
return db.count<Account>();
}
int Database::getPlayersCount() {
return db.count<DbPlayer>();
}
int Database::addAccount(std::string login, std::string password) {
std::lock_guard<std::mutex> lock(dbCrit);
password = BCrypt::generateHash(password);
Account x = {};
x.Login = login;
x.Password = password;
x.Selected = 1;
return db.insert(x);
Account account = {};
account.Login = login;
account.Password = password;
account.Selected = 1;
account.Created = getTimestamp();
return db.insert(account);
}
void Database::updateSelected(int accountId, int slot)
{
void Database::updateSelected(int accountId, int slot) {
std::lock_guard<std::mutex> lock(dbCrit);
Account acc = db.get<Account>(accountId);
acc.Selected = slot;
// timestamp
acc.LastLogin = getTimestamp();
db.update(acc);
}
std::unique_ptr<Database::Account> Database::findAccount(std::string login)
{
//this is awful, I've tried everything to improve it
std::unique_ptr<Database::Account> Database::findAccount(std::string login) {
std::lock_guard<std::mutex> lock(dbCrit);
// this is awful, I've tried everything to improve it
auto find = db.get_all<Account>(
where(c(&Account::Login) == login), limit(1));
if (find.empty())
return nullptr;
return
std::unique_ptr<Account>(new Account(find.front()));
std::unique_ptr<Account>(new Account(find.front()));
}
bool Database::isNameFree(sP_CL2LS_REQ_CHECK_CHAR_NAME* nameCheck)
{
//TODO: add colate nocase
bool Database::isNameFree(sP_CL2LS_REQ_CHECK_CHAR_NAME* nameCheck) {
std::lock_guard<std::mutex> lock(dbCrit);
std::string First = U16toU8(nameCheck->szFirstName);
std::string Last = U16toU8(nameCheck->szLastName);
return
(db.get_all<DbPlayer>
(where((c(&DbPlayer::FirstName) == First)
and (c(&DbPlayer::LastName) == Last)))
.empty());
.empty());
}
int Database::createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID)
{
int Database::createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID) {
std::lock_guard<std::mutex> lock(dbCrit);
// fail if the player already has 4 or more characters
if (db.count<DbPlayer>(where(c(&DbPlayer::AccountID) == AccountID)) >= 4)
return -1;
DbPlayer create = {};
//save packet data
// set timestamp
create.Created = getTimestamp();
// save packet data
create.FirstName = U16toU8(save->szFirstName);
create.LastName = U16toU8(save->szLastName);
create.slot = save->iSlotNum;
create.AccountID = AccountID;
//set flags
// set flags
create.AppearanceFlag = 0;
create.TutorialFlag = 0;
create.PayZoneFlag = 0;
//set namecheck based on setting
// set namecheck based on setting
if (settings::APPROVEALLNAMES || save->iFNCode)
create.NameCheck = 1;
else
create.NameCheck = 0;
//create default body character
create.Body= 0;
create.Class= 0;
create.EquipFoot= 0;
create.EquipLB= 0;
create.EquipUB= 0;
create.EquipWeapon1= 0;
create.EquipWeapon2= 0;
create.EyeColor= 1;
create.FaceStyle= 1;
create.Gender= 1;
create.HP= 1000;
create.HairColor= 1;
// create default body character
create.Body = 0;
create.Class = 0;
create.EyeColor = 1;
create.FaceStyle = 1;
create.Gender = 1;
create.Level = 1;
create.HP = PC_MAXHEALTH(create.Level);
create.HairColor = 1;
create.HairStyle = 1;
create.Height= 0;
create.Level= 1;
create.SkinColor= 1;
create.isGM = false;
//commented and disabled for now
//if (U16toU8(save->szFirstName) == settings::GMPASS) {
// create.isGM = true;
//}
create.FusionMatter= 0;
create.Taros= 0;
create.Height = 0;
create.SkinColor = 1;
create.AccountLevel = settings::ACCLEVEL;
create.x_coordinates = settings::SPAWN_X;
create.y_coordinates= settings::SPAWN_Y;
create.z_coordinates= settings::SPAWN_Z;
create.y_coordinates = settings::SPAWN_Y;
create.z_coordinates = settings::SPAWN_Z;
create.angle = settings::SPAWN_ANGLE;
// set mentor to computress
create.Mentor = 5;
// initialize the quest blob to 128 0-bytes
create.QuestFlag = std::vector<char>(128, 0);
return db.insert(create);
}
void Database::finishCharacter(sP_CL2LS_REQ_CHAR_CREATE* character)
{
void Database::finishCharacter(sP_CL2LS_REQ_CHAR_CREATE* character) {
std::lock_guard<std::mutex> lock(dbCrit);
DbPlayer finish = getDbPlayerById(character->PCStyle.iPC_UID);
finish.AppearanceFlag = 1;
finish.Body = character->PCStyle.iBody;
finish.Class = character->PCStyle.iClass;
finish.EquipFoot = character->sOn_Item.iEquipFootID;
finish.EquipLB = character->sOn_Item.iEquipLBID;
finish.EquipUB = character->sOn_Item.iEquipUBID;
finish.EyeColor = character->PCStyle.iEyeColor;
finish.FaceStyle = character->PCStyle.iFaceStyle;
finish.Gender = character->PCStyle.iGender;
@@ -176,35 +245,83 @@ void Database::finishCharacter(sP_CL2LS_REQ_CHAR_CREATE* character)
finish.Level = 1;
finish.SkinColor = character->PCStyle.iSkinColor;
db.update(finish);
// clothes
Inventory Foot, LB, UB;
Foot.playerId = character->PCStyle.iPC_UID;
Foot.id = character->sOn_Item.iEquipFootID;
Foot.Type = 3;
Foot.slot = 3;
Foot.Opt = 1;
Foot.TimeLimit = 0;
db.insert(Foot);
LB.playerId = character->PCStyle.iPC_UID;
LB.id = character->sOn_Item.iEquipLBID;
LB.Type = 2;
LB.slot = 2;
LB.Opt = 1;
LB.TimeLimit = 0;
db.insert(LB);
UB.playerId = character->PCStyle.iPC_UID;
UB.id = character->sOn_Item.iEquipUBID;
UB.Type = 1;
UB.slot = 1;
UB.Opt = 1;
UB.TimeLimit = 0;
db.insert(UB);
}
void Database::finishTutorial(int PlayerID)
{
DbPlayer finish = getDbPlayerById(PlayerID);
finish.TutorialFlag = 1;
//equip lightning gun
finish.EquipWeapon1 = 328;
db.update(finish);
void Database::finishTutorial(int PlayerID) {
std::lock_guard<std::mutex> lock(dbCrit);
Player finish = getPlayer(PlayerID);
// set flag
finish.PCStyle2.iTutorialFlag= 1;
// add Gun
Inventory LightningGun = {};
LightningGun.playerId = PlayerID;
LightningGun.id = 328;
LightningGun.slot = 0;
LightningGun.Type = 0;
LightningGun.Opt = 1;
db.insert(LightningGun);
// add Nano
Nano Buttercup = {};
Buttercup.playerId = PlayerID;
Buttercup.iID = 1;
Buttercup.iSkillID = 1;
Buttercup.iStamina = 150;
finish.equippedNanos[0] = 1;
db.insert(Buttercup);
// save missions
MissionManager::saveMission(&finish, 0);
MissionManager::saveMission(&finish, 1);
db.update(playerToDb(&finish));
}
int Database::deleteCharacter(int characterID)
{
int Database::deleteCharacter(int characterID, int userID) {
std::lock_guard<std::mutex> lock(dbCrit);
auto find =
db.get_all<DbPlayer>(where(c(&DbPlayer::PlayerID) == characterID));
db.get_all<DbPlayer>(where(c(&DbPlayer::PlayerID) == characterID and c(&DbPlayer::AccountID)==userID));
int slot = find.front().slot;
db.remove<DbPlayer>(find.front().PlayerID);
db.remove_all<Inventory>(where(c(&Inventory::playerId) == characterID));
db.remove_all<Nano>(where(c(&Nano::playerId) == characterID));
return slot;
}
std::vector <Player> Database::getCharacters(int UserID)
{
std::vector <Player> Database::getCharacters(int UserID) {
std::lock_guard<std::mutex> lock(dbCrit);
std::vector<DbPlayer>characters =
db.get_all<DbPlayer>(where
(c(&DbPlayer::AccountID) == UserID));
//parsing DbPlayer to Player
// parsing DbPlayer to Player
std::vector<Player> result = std::vector<Player>();
for (auto &character : characters) {
Player toadd = DbToPlayer(character);
Player toadd = DbToPlayer(character);
result.push_back(
toadd
);
@@ -212,13 +329,18 @@ std::vector <Player> Database::getCharacters(int UserID)
return result;
}
void Database::evaluateCustomName(int characterID, CUSTOMNAME decision) {
// XXX: This is never called?
void Database::evaluateCustomName(int characterID, CustomName decision) {
std::lock_guard<std::mutex> lock(dbCrit);
DbPlayer player = getDbPlayerById(characterID);
player.NameCheck = (int)decision;
db.update(player);
}
void Database::changeName(sP_CL2LS_REQ_CHANGE_CHAR_NAME* save) {
std::lock_guard<std::mutex> lock(dbCrit);
DbPlayer Player = getDbPlayerById(save->iPCUID);
Player.FirstName = U16toU8(save->szFirstName);
Player.LastName = U16toU8(save->szLastName);
@@ -229,59 +351,85 @@ void Database::changeName(sP_CL2LS_REQ_CHANGE_CHAR_NAME* save) {
db.update(Player);
}
Database::DbPlayer Database::playerToDb(Player player)
{
DbPlayer result = {}; // fixes some weird memory errors, this zeros out the members (not the padding inbetween though)
Database::DbPlayer Database::playerToDb(Player *player) {
// TODO: move stuff that is never updated to separate table so it doesn't try to update it every time
DbPlayer result = {};
result.PlayerID = player.iID;
result.AccountID = player.accountId;
result.AppearanceFlag = player.PCStyle2.iAppearanceFlag;
result.Body = player.PCStyle.iBody;
result.Class = player.PCStyle.iClass;
//equipment
result.EyeColor = player.PCStyle.iEyeColor;
result.FaceStyle = player.PCStyle.iFaceStyle;
result.FirstName = U16toU8( player.PCStyle.szFirstName);
//fm
result.Gender = player.PCStyle.iGender;
result.HairColor = player.PCStyle.iHairColor;
result.HairStyle = player.PCStyle.iHairStyle;
result.Height = player.PCStyle.iHeight;
result.HP = player.HP;
result.isGM = player.IsGM;
result.LastName = U16toU8(player.PCStyle.szLastName);
result.Level = player.level;
result.NameCheck = player.PCStyle.iNameCheck;
result.PayZoneFlag = player.PCStyle2.iPayzoneFlag;
result.PlayerID = player.PCStyle.iPC_UID;
result.SkinColor = player.PCStyle.iSkinColor;
result.slot = player.slot;
//taros
result.TutorialFlag = player.PCStyle2.iTutorialFlag;
result.x_coordinates = player.x;
result.y_coordinates = player.y;
result.z_coordinates = player.z;
result.PlayerID = player->iID;
result.AccountID = player->accountId;
result.AppearanceFlag = player->PCStyle2.iAppearanceFlag;
result.Body = player->PCStyle.iBody;
result.Class = player->PCStyle.iClass;
result.EyeColor = player->PCStyle.iEyeColor;
result.FaceStyle = player->PCStyle.iFaceStyle;
result.FirstName = U16toU8( player->PCStyle.szFirstName);
result.FusionMatter = player->fusionmatter;
result.Gender = player->PCStyle.iGender;
result.HairColor = player->PCStyle.iHairColor;
result.HairStyle = player->PCStyle.iHairStyle;
result.Height = player->PCStyle.iHeight;
result.HP = player->HP;
result.AccountLevel = player->accountLevel;
result.LastName = U16toU8(player->PCStyle.szLastName);
result.Level = player->level;
result.NameCheck = player->PCStyle.iNameCheck;
result.PayZoneFlag = player->PCStyle2.iPayzoneFlag;
result.PlayerID = player->PCStyle.iPC_UID;
result.SkinColor = player->PCStyle.iSkinColor;
result.slot = player->slot;
result.Taros = player->money;
result.TutorialFlag = player->PCStyle2.iTutorialFlag;
if (player->instanceID == 0) { // only save coords if player isn't instanced
result.x_coordinates = player->x;
result.y_coordinates = player->y;
result.z_coordinates = player->z;
result.angle = player->angle;
} else {
result.x_coordinates = player->lastX;
result.y_coordinates = player->lastY;
result.z_coordinates = player->lastZ;
result.angle = player->lastAngle;
}
result.Nano1 = player->equippedNanos[0];
result.Nano2 = player->equippedNanos[1];
result.Nano3 = player->equippedNanos[2];
result.BatteryN = player->batteryN;
result.BatteryW = player->batteryW;
result.Mentor = player->mentor;
result.WarpLocationFlag = player->iWarpLocationFlag;
result.SkywayLocationFlag1 = player->aSkywayLocationFlag[0];
result.SkywayLocationFlag2 = player->aSkywayLocationFlag[1];
result.CurrentMissionID = player->CurrentMissionID;
// timestamp
result.LastLogin = getTimestamp();
result.Created = player->creationTime;
// save completed quests
result.QuestFlag = std::vector<char>((char*)player->aQuestFlag, (char*)player->aQuestFlag + 128);
return result;
}
Player Database::DbToPlayer(DbPlayer player) {
Player result = {}; // fixes some weird memory errors, this zeros out the members (not the padding inbetween though)
result.iID = player.PlayerID;
result.accountId = player.AccountID;
result.creationTime = player.Created;
result.PCStyle2.iAppearanceFlag = player.AppearanceFlag;
result.PCStyle.iBody = player.Body;
result.PCStyle.iClass = player.Class;
result.PCStyle.iEyeColor = player.EyeColor;
result.PCStyle.iFaceStyle = player.FaceStyle;
U8toU16(player.FirstName, result.PCStyle.szFirstName);
U8toU16(player.FirstName, result.PCStyle.szFirstName, sizeof(result.PCStyle.szFirstName));
result.PCStyle.iGender = player.Gender;
result.PCStyle.iHairColor = player.HairColor;
result.PCStyle.iHairStyle = player.HairStyle;
result.PCStyle.iHeight = player.Height;
result.HP = player.HP;
result.IsGM = player.isGM;
U8toU16(player.LastName, result.PCStyle.szLastName);
result.accountLevel = player.AccountLevel;
U8toU16(player.LastName, result.PCStyle.szLastName, sizeof(result.PCStyle.szLastName));
result.level = player.Level;
result.PCStyle.iNameCheck = player.NameCheck;
result.PCStyle2.iPayzoneFlag = player.PayZoneFlag;
@@ -293,65 +441,254 @@ Player Database::DbToPlayer(DbPlayer player) {
result.x = player.x_coordinates;
result.y = player.y_coordinates;
result.z = player.z_coordinates;
result.angle = player.angle;
result.money = player.Taros;
result.fusionmatter = player.FusionMatter;
result.batteryN = player.BatteryN;
result.batteryW = player.BatteryW;
result.mentor = player.Mentor;
result.CurrentMissionID = player.CurrentMissionID;
//TODO:: implement all of below
result.SerialKey = 0;
result.money = 0;
result.fusionmatter = 0;
result.activeNano = 0;
result.iPCState = 0;
result.equippedNanos[0] = 1;
result.equippedNanos[1] = 0;
result.equippedNanos[2] = 0;
result.isTrading = false;
result.isTradeConfirm = false;
result.equippedNanos[0] = player.Nano1;
result.equippedNanos[1] = player.Nano2;
result.equippedNanos[2] = player.Nano3;
result.Nanos[1].iID = 1;
result.Nanos[1].iSkillID = 1;
result.Nanos[1].iStamina = 150;
result.inCombat = false;
for (int i = 0; i < 37; i++) {
result.Nanos[i].iID = 0;
result.Nanos[i].iSkillID = 0;
result.Nanos[i].iStamina = 0;
}
result.Equip[0].iID = player.EquipWeapon1;
result.Equip[0].iType = 0;
(player.EquipWeapon1) ? result.Equip[0].iOpt = 1 : result.Equip[0].iOpt = 0;
result.iWarpLocationFlag = player.WarpLocationFlag;
result.aSkywayLocationFlag[0] = player.SkywayLocationFlag1;
result.aSkywayLocationFlag[1] = player.SkywayLocationFlag2;
result.Equip[1].iID = player.EquipUB;
result.Equip[1].iType = 1;
(player.EquipUB) ? result.Equip[1].iOpt = 1 : result.Equip[1].iOpt = 0;
Database::getInventory(&result);
Database::removeExpiredVehicles(&result);
Database::getNanos(&result);
Database::getQuests(&result);
result.Equip[2].iID = player.EquipLB;
result.Equip[2].iType = 2;
(player.EquipLB) ? result.Equip[2].iOpt = 1 : result.Equip[2].iOpt = 0;
// load completed quests
memcpy(&result.aQuestFlag, player.QuestFlag.data(), std::min(sizeof(result.aQuestFlag), player.QuestFlag.size()));
result.Equip[3].iID = player.EquipFoot;
result.Equip[3].iType = 3;
(player.EquipFoot) ? result.Equip[3].iOpt = 1 : result.Equip[3].iOpt = 0;
for (int i = 4; i < AEQUIP_COUNT; i++) {
// empty equips
result.Equip[i].iID = 0;
result.Equip[i].iType = i;
result.Equip[i].iOpt = 0;
}
for (int i = 0; i < AINVEN_COUNT; i++) {
// setup inventories
result.Inven[i].iID = 0;
result.Inven[i].iType = 0;
result.Inven[i].iOpt = 0;
}
return result;
}
Database::DbPlayer Database::getDbPlayerById(int id) {
return db.get_all<DbPlayer>(where(c(&DbPlayer::PlayerID) == id))
.front();
return db.get_all<DbPlayer>(where(c(&DbPlayer::PlayerID) == id)).front();
}
Player Database::getPlayer(int id) {
return DbToPlayer(
getDbPlayerById(id)
);
}
#pragma endregion LoginServer
#pragma region ShardServer
void Database::updatePlayer(Player *player) {
std::lock_guard<std::mutex> lock(dbCrit);
DbPlayer toUpdate = playerToDb(player);
db.update(toUpdate);
updateInventory(player);
updateNanos(player);
updateQuests(player);
}
void Database::updateInventory(Player *player){
// start transaction
db.begin_transaction();
// remove all
db.remove_all<Inventory>(
where(c(&Inventory::playerId) == player->iID)
);
// insert equip
for (int i = 0; i < AEQUIP_COUNT; i++) {
if (player->Equip[i].iID != 0) {
sItemBase* next = &player->Equip[i];
Inventory toAdd = {};
toAdd.playerId = player->iID;
toAdd.slot = i;
toAdd.id = next->iID;
toAdd.Opt = next->iOpt;
toAdd.Type = next->iType;
toAdd.TimeLimit = next->iTimeLimit;
db.insert(toAdd);
}
}
// insert inventory
for (int i = 0; i < AINVEN_COUNT; i++) {
if (player->Inven[i].iID != 0) {
sItemBase* next = &player->Inven[i];
Inventory toAdd = {};
toAdd.playerId = player->iID;
toAdd.slot = i + AEQUIP_COUNT;
toAdd.id = next->iID;
toAdd.Opt = next->iOpt;
toAdd.Type = next->iType;
toAdd.TimeLimit = next->iTimeLimit;
db.insert(toAdd);
}
}
// insert bank
for (int i = 0; i < ABANK_COUNT; i++) {
if (player->Bank[i].iID != 0) {
sItemBase* next = &player->Bank[i];
Inventory toAdd = {};
toAdd.playerId = player->iID;
toAdd.slot = i + AEQUIP_COUNT + AINVEN_COUNT;
toAdd.id = next->iID;
toAdd.Opt = next->iOpt;
toAdd.Type = next->iType;
toAdd.TimeLimit = next->iTimeLimit;
db.insert(toAdd);
}
}
// insert quest items
for (int i = 0; i < AQINVEN_COUNT; i++) {
if (player->QInven[i].iID != 0) {
sItemBase* next = &player->QInven[i];
Inventory toAdd = {};
toAdd.playerId = player->iID;
toAdd.slot = i + AEQUIP_COUNT + AINVEN_COUNT + ABANK_COUNT;
toAdd.id = next->iID;
toAdd.Opt = next->iOpt;
toAdd.Type = next->iType;
toAdd.TimeLimit = next->iTimeLimit;
db.insert(toAdd);
}
}
db.commit();
}
void Database::updateNanos(Player *player) {
// start transaction
db.begin_transaction();
// remove all
db.remove_all<Nano>(
where(c(&Nano::playerId) == player->iID)
);
// insert
for (int i=1; i < SIZEOF_NANO_BANK_SLOT; i++) {
if ((player->Nanos[i]).iID == 0)
continue;
Nano toAdd = {};
sNano* next = &player->Nanos[i];
toAdd.playerId = player->iID;
toAdd.iID = next->iID;
toAdd.iSkillID = next->iSkillID;
toAdd.iStamina = next->iStamina;
db.insert(toAdd);
}
db.commit();
}
void Database::updateQuests(Player* player) {
// start transaction
db.begin_transaction();
// remove all
db.remove_all<DbQuest>(
where(c(&DbQuest::PlayerId) == player->iID)
);
// insert
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
if (player->tasks[i] == 0)
continue;
DbQuest toAdd = {};
toAdd.PlayerId = player->iID;
toAdd.TaskId = player->tasks[i];
toAdd.RemainingNPCCount1 = player->RemainingNPCCount[i][0];
toAdd.RemainingNPCCount2 = player->RemainingNPCCount[i][1];
toAdd.RemainingNPCCount3 = player->RemainingNPCCount[i][2];
db.insert(toAdd);
}
db.commit();
}
void Database::getInventory(Player* player) {
// get items from DB
auto items = db.get_all<Inventory>(
where(c(&Inventory::playerId) == player->iID)
);
// set items
for (const Inventory &current : items) {
sItemBase toSet = {};
toSet.iID = current.id;
toSet.iType = current.Type;
toSet.iOpt = current.Opt;
toSet.iTimeLimit = current.TimeLimit;
// assign to proper arrays
if (current.slot < AEQUIP_COUNT)
player->Equip[current.slot] = toSet;
else if (current.slot < (AEQUIP_COUNT + AINVEN_COUNT))
player->Inven[current.slot - AEQUIP_COUNT] = toSet;
else if (current.slot < (AEQUIP_COUNT + AINVEN_COUNT + ABANK_COUNT))
player->Bank[current.slot - AEQUIP_COUNT - AINVEN_COUNT] = toSet;
else
player->QInven[current.slot - AEQUIP_COUNT - AINVEN_COUNT - ABANK_COUNT] = toSet;
}
}
void Database::removeExpiredVehicles(Player* player) {
int32_t currentTime = getTimestamp();
// remove from bank immediately
for (int i = 0; i < ABANK_COUNT; i++) {
if (player->Bank[i].iType == 10 && player->Bank[i].iTimeLimit < currentTime)
player->Bank[i] = {};
}
// for the rest, we want to leave only 1 expired vehicle on player to delete it with the client packet
std::vector<sItemBase*> toRemove;
// equiped vehicle
if (player->Equip[8].iOpt > 0 && player->Equip[8].iTimeLimit < currentTime) {
toRemove.push_back(&player->Equip[8]);
player->toRemoveVehicle.eIL = 0;
player->toRemoveVehicle.iSlotNum = 8;
}
// inventory
for (int i = 0; i < AINVEN_COUNT; i++) {
if (player->Inven[i].iType == 10 && player->Inven[i].iTimeLimit < currentTime) {
toRemove.push_back(&player->Inven[i]);
player->toRemoveVehicle.eIL = 1;
player->toRemoveVehicle.iSlotNum = i;
}
}
// delete all but one vehicles, leave last one for ceremonial deletion
for (int i = 0; i < (int)toRemove.size()-1; i++) {
memset(toRemove[i], 0, sizeof(sItemBase));
}
}
void Database::getNanos(Player* player) {
// get from DB
auto nanos = db.get_all<Nano>(
where(c(&Nano::playerId) == player->iID)
);
// set
for (const Nano& current : nanos) {
sNano *toSet = &player->Nanos[current.iID];
toSet->iID = current.iID;
toSet->iSkillID = current.iSkillID;
toSet->iStamina = current.iStamina;
}
}
void Database::getQuests(Player* player) {
// get from DB
auto quests = db.get_all<DbQuest>(
where(c(&DbQuest::PlayerId) == player->iID)
);
// set
int i = 0;
for (const DbQuest& current : quests) {
player->tasks[i] = current.TaskId;
player->RemainingNPCCount[i][0] = current.RemainingNPCCount1;
player->RemainingNPCCount[i][1] = current.RemainingNPCCount2;
player->RemainingNPCCount[i][2] = current.RemainingNPCCount3;
i++;
}
}
#pragma endregion ShardServer

View File

@@ -5,34 +5,45 @@
#include <vector>
namespace Database {
#pragma region DatabaseStructs
#pragma region DatabaseStructs
struct Account
{
struct Account {
int AccountID;
std::string Login;
std::string Password;
int Selected;
uint64_t Created;
uint64_t LastLogin;
};
struct Inventory
{
int AccountID;
struct Inventory {
int playerId;
int slot;
int16_t Type;
int16_t id;
int32_t Opt;
int32_t TimeLimit;
};
struct DbPlayer
{
struct Nano {
int playerId;
int16_t iID;
int16_t iSkillID;
int16_t iStamina;
};
struct DbPlayer {
int PlayerID;
int AccountID;
short int slot;
std::string FirstName;
std::string LastName;
uint64_t Created;
uint64_t LastLogin;
short int Level;
int Nano1;
int Nano2;
int Nano3;
short int AppearanceFlag;
short int Body;
short int Class;
short int EquipFoot;
short int EquipLB;
short int EquipUB;
short int EquipWeapon1;
short int EquipWeapon2;
short int EyeColor;
short int FaceStyle;
short int Gender;
@@ -40,52 +51,83 @@ namespace Database {
short int HairColor;
short int HairStyle;
short int Height;
short int Level;
short int NameCheck;
short int PayZoneFlag;
short int SkinColor;
bool TutorialFlag;
bool isGM;
int AccountLevel;
int FusionMatter;
int Taros;
int x_coordinates;
int y_coordinates;
int z_coordinates;
int angle;
short int PCState;
int BatteryW;
int BatteryN;
int16_t Mentor;
std::vector<char> QuestFlag;
int32_t CurrentMissionID;
int32_t WarpLocationFlag;
int64_t SkywayLocationFlag1;
int64_t SkywayLocationFlag2;
};
struct DbQuest {
int PlayerId;
int32_t TaskId;
int RemainingNPCCount1;
int RemainingNPCCount2;
int RemainingNPCCount3;
};
#pragma endregion DatabaseStructs
//handles migrations
// handles migrations
void open();
//returns ID
int getAccountsCount();
int getPlayersCount();
// returns ID
int addAccount(std::string login, std::string password);
void updateSelected(int accountId, int playerId);
std::unique_ptr<Account> findAccount(std::string login);
bool isNameFree(sP_CL2LS_REQ_CHECK_CHAR_NAME* nameCheck);
//called after chosing name, returns ID
// called after chosing name, returns ID
int createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID);
//called after finishing creation
// called after finishing creation
void finishCharacter(sP_CL2LS_REQ_CHAR_CREATE* character);
//called after tutorial
// called after tutorial
void finishTutorial(int PlayerID);
//returns slot number
int deleteCharacter(int characterID);
// returns slot number
int deleteCharacter(int characterID, int userID);
std::vector <Player> getCharacters(int userID);
//accepting/declining custom name
enum class CUSTOMNAME {
approve = 1,
disapprove = 2
// accepting/declining custom name
enum class CustomName {
APPROVE = 1,
DISAPPROVE = 2
};
void evaluateCustomName(int characterID, CUSTOMNAME decision);
void evaluateCustomName(int characterID, CustomName decision);
void changeName(sP_CL2LS_REQ_CHANGE_CHAR_NAME* save);
//parsing DbPlayer
DbPlayer playerToDb(Player player);
// parsing DbPlayer
DbPlayer playerToDb(Player *player);
Player DbToPlayer(DbPlayer player);
//getting players
// getting players
DbPlayer getDbPlayerById(int id);
Player getPlayer(int id);
void updatePlayer(Player *player);
void updateInventory(Player *player);
void updateNanos(Player *player);
void updateQuests(Player* player);
void getInventory(Player* player);
void removeExpiredVehicles(Player* player);
void getNanos(Player* player);
void getQuests(Player* player);
// parsing blobs
void appendBlob(std::vector<char>*blob, int64_t input);
int64_t blobToInt64(std::vector<char>::iterator it);
}

View File

@@ -8,6 +8,7 @@
* implementing just yet anyway.
*/
// floats
const float VALUE_BATTERY_EMPTY_PENALTY = 0.5f;
const float CN_EP_RANK_1 = 0.8f;
const float CN_EP_RANK_2 = 0.7f;
@@ -15,6 +16,86 @@ const float CN_EP_RANK_3 = 0.5f;
const float CN_EP_RANK_4 = 0.3f;
const float CN_EP_RANK_5 = 0.29f;
// NPC classes
enum NPCClass {
NPC_BASE = 0,
NPC_MOB = 1,
NPC_BUS = 2,
NPC_EGG = 3
};
// nano powers
enum {
EST_NONE = 0,
EST_DAMAGE = 1,
EST_HEAL_HP = 2,
EST_KNOCKDOWN = 3,
EST_SLEEP = 4,
EST_SNARE = 5,
EST_HEAL_STAMINA = 6,
EST_STAMINA_SELF = 7,
EST_STUN = 8,
EST_WEAPONSLOW = 9,
EST_JUMP = 10,
EST_RUN = 11,
EST_STEALTH = 12,
EST_SWIM = 13,
EST_MINIMAPENEMY = 14,
EST_MINIMAPTRESURE = 15,
EST_PHOENIX = 16,
EST_PROTECTBATTERY = 17,
EST_PROTECTINFECTION = 18,
EST_REWARDBLOB = 19,
EST_REWARDCASH = 20,
EST_BATTERYDRAIN = 21,
EST_CORRUPTIONATTACK = 22,
EST_INFECTIONDAMAGE = 23,
EST_KNOCKBACK = 24,
EST_FREEDOM = 25,
EST_PHOENIX_GROUP = 26,
EST_RECALL = 27,
EST_RECALL_GROUP = 28,
EST_RETROROCKET_SELF = 29,
EST_BLOODSUCKING = 30,
EST_BOUNDINGBALL = 31,
EST_INVULNERABLE = 32,
EST_NANOSTIMPAK = 33,
EST_RETURNHOMEHEAL = 34,
EST_BUFFHEAL = 35,
EST_EXTRABANK = 36,
EST__END = 37,
EST_CORRUPTIONATTACKWIN = 38,
EST_CORRUPTIONATTACKLOSE = 39,
ECSB_NONE = 0,
ECSB_UP_MOVE_SPEED = 1,
ECSB_UP_SWIM_SPEED = 2,
ECSB_UP_JUMP_HEIGHT = 3,
ECSB_UP_STEALTH = 4,
ECSB_PHOENIX = 5,
ECSB_PROTECT_BATTERY = 6,
ECSB_PROTECT_INFECTION = 7,
ECSB_DN_MOVE_SPEED = 8,
ECSB_DN_ATTACK_SPEED = 9,
ECSB_STUN = 10,
ECSB_MEZ = 11,
ECSB_KNOCKDOWN = 12,
ECSB_MINIMAP_ENEMY = 13,
ECSB_MINIMAP_TRESURE = 14,
ECSB_REWARD_BLOB = 15,
ECSB_REWARD_CASH = 16,
ECSB_INFECTION = 17,
ECSB_FREEDOM = 18,
ECSB_BOUNDINGBALL = 19,
ECSB_INVULNERABLE = 20,
ECSB_STIMPAKSLOT1 = 21,
ECSB_STIMPAKSLOT2 = 22,
ECSB_STIMPAKSLOT3 = 23,
ECSB_HEAL = 24,
ECSB_EXTRABANK = 25,
ECSTB__END = 26,
};
enum {
SUCC = 1,
FAIL = 0,

376
src/GroupManager.cpp Normal file
View File

@@ -0,0 +1,376 @@
#include "CNShardServer.hpp"
#include "CNStructs.hpp"
#include "ChatManager.hpp"
#include "PlayerManager.hpp"
#include "GroupManager.hpp"
#include "NanoManager.hpp"
#include <iostream>
#include <chrono>
#include <algorithm>
#include <thread>
void GroupManager::init() {
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GROUP_INVITE, requestGroup);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GROUP_INVITE_REFUSE, refuseGroup);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GROUP_JOIN, joinGroup);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GROUP_LEAVE, leaveGroup);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE, chatGroup);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE, menuChatGroup);
}
void GroupManager::requestGroup(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_PC_GROUP_INVITE))
return; // malformed packet
sP_CL2FE_REQ_PC_GROUP_INVITE* recv = (sP_CL2FE_REQ_PC_GROUP_INVITE*)data->buf;
Player* plr = PlayerManager::getPlayer(sock);
Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_To);
if (plr == nullptr || otherPlr == nullptr)
return;
otherPlr = PlayerManager::getPlayerFromID(otherPlr->iIDGroup);
if (otherPlr == nullptr)
return;
// fail if the group is full or the other player is already in a group
if (plr->groupCnt >= 4 || otherPlr->groupCnt > 1) {
INITSTRUCT(sP_FE2CL_PC_GROUP_INVITE_FAIL, resp);
sock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_INVITE_FAIL, sizeof(sP_FE2CL_PC_GROUP_INVITE_FAIL));
return;
}
CNSocket* otherSock = PlayerManager::getSockFromID(recv->iID_To);
if (otherSock == nullptr)
return;
INITSTRUCT(sP_FE2CL_PC_GROUP_INVITE, resp);
resp.iHostID = plr->iIDGroup;
otherSock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_INVITE, sizeof(sP_FE2CL_PC_GROUP_INVITE));
}
void GroupManager::refuseGroup(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_PC_GROUP_INVITE_REFUSE))
return; // malformed packet
sP_CL2FE_REQ_PC_GROUP_INVITE_REFUSE* recv = (sP_CL2FE_REQ_PC_GROUP_INVITE_REFUSE*)data->buf;
CNSocket* otherSock = PlayerManager::getSockFromID(recv->iID_From);
if (otherSock == nullptr)
return;
Player* plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return;
INITSTRUCT(sP_FE2CL_PC_GROUP_INVITE_REFUSE, resp);
resp.iID_To = plr->iID;
otherSock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_INVITE_REFUSE, sizeof(sP_FE2CL_PC_GROUP_INVITE_REFUSE));
}
void GroupManager::joinGroup(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_PC_GROUP_JOIN))
return; // malformed packet
sP_CL2FE_REQ_PC_GROUP_JOIN* recv = (sP_CL2FE_REQ_PC_GROUP_JOIN*)data->buf;
Player* plr = PlayerManager::getPlayer(sock);
Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_From);
if (plr == nullptr || otherPlr == nullptr)
return;
otherPlr = PlayerManager::getPlayerFromID(otherPlr->iIDGroup);
if (otherPlr == nullptr)
return;
// fail if the group is full or the other player is already in a group
if (plr->groupCnt > 1 || plr->iIDGroup != plr->iID || otherPlr->groupCnt >= 4) {
INITSTRUCT(sP_FE2CL_PC_GROUP_JOIN_FAIL, resp);
sock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_JOIN_FAIL, sizeof(sP_FE2CL_PC_GROUP_JOIN_FAIL));
return;
}
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_JOIN), otherPlr->groupCnt + 1, sizeof(sPCGroupMemberInfo))) {
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_JOIN packet size\n";
return;
}
int bitFlagBefore = getGroupFlags(otherPlr);
plr->iIDGroup = otherPlr->iID;
otherPlr->groupCnt += 1;
otherPlr->groupIDs[otherPlr->groupCnt-1] = plr->iID;
size_t resplen = sizeof(sP_FE2CL_PC_GROUP_JOIN) + otherPlr->groupCnt * sizeof(sPCGroupMemberInfo);
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
memset(respbuf, 0, resplen);
sP_FE2CL_PC_GROUP_JOIN *resp = (sP_FE2CL_PC_GROUP_JOIN*)respbuf;
sPCGroupMemberInfo *respdata = (sPCGroupMemberInfo*)(respbuf+sizeof(sP_FE2CL_PC_GROUP_JOIN));
resp->iID_NewMember = plr->iID;
resp->iMemberPCCnt = otherPlr->groupCnt;
int bitFlag = getGroupFlags(otherPlr);
for (int i = 0; i < otherPlr->groupCnt; i++) {
Player* varPlr = PlayerManager::getPlayerFromID(otherPlr->groupIDs[i]);
CNSocket* sockTo = PlayerManager::getSockFromID(otherPlr->groupIDs[i]);
if (varPlr == nullptr || sockTo == nullptr)
continue;
respdata[i].iPC_ID = varPlr->iID;
respdata[i].iPCUID = varPlr->PCStyle.iPC_UID;
respdata[i].iNameCheck = varPlr->PCStyle.iNameCheck;
memcpy(respdata[i].szFirstName, varPlr->PCStyle.szFirstName, sizeof(varPlr->PCStyle.szFirstName));
memcpy(respdata[i].szLastName, varPlr->PCStyle.szLastName, sizeof(varPlr->PCStyle.szLastName));
respdata[i].iSpecialState = varPlr->iSpecialState;
respdata[i].iLv = varPlr->level;
respdata[i].iHP = varPlr->HP;
respdata[i].iMaxHP = PC_MAXHEALTH(varPlr->level);
//respdata[i].iMapType = 0;
//respdata[i].iMapNum = 0;
respdata[i].iX = varPlr->x;
respdata[i].iY = varPlr->y;
respdata[i].iZ = varPlr->z;
// client doesnt read nano data here
NanoManager::nanoChangeBuff(sockTo, varPlr, bitFlagBefore | varPlr->iConditionBitFlag, bitFlag | varPlr->iConditionBitFlag);
}
sendToGroup(otherPlr, (void*)&respbuf, P_FE2CL_PC_GROUP_JOIN, resplen);
}
void GroupManager::leaveGroup(CNSocket* sock, CNPacketData* data) {
Player* plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return;
groupKickPlayer(plr);
}
void GroupManager::chatGroup(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE))
return; // malformed packet
sP_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE* chat = (sP_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE*)data->buf;
Player* plr = PlayerManager::getPlayer(sock);
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
if (plr == nullptr || otherPlr == nullptr)
return;
// send to client
INITSTRUCT(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, resp);
memcpy(resp.szFreeChat, chat->szFreeChat, sizeof(chat->szFreeChat));
resp.iSendPCID = plr->iID;
resp.iEmoteCode = chat->iEmoteCode;
sendToGroup(otherPlr, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC));
}
void GroupManager::menuChatGroup(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE))
return; // malformed packet
sP_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE* chat = (sP_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE*)data->buf;
Player* plr = PlayerManager::getPlayer(sock);
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
if (plr == nullptr || otherPlr == nullptr)
return;
// send to client
INITSTRUCT(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, resp);
memcpy(resp.szFreeChat, chat->szFreeChat, sizeof(chat->szFreeChat));
resp.iSendPCID = plr->iID;
resp.iEmoteCode = chat->iEmoteCode;
sendToGroup(otherPlr, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC));
}
void GroupManager::sendToGroup(Player* plr, void* buf, uint32_t type, size_t size) {
for (int i = 0; i < plr->groupCnt; i++) {
CNSocket* sock = PlayerManager::getSockFromID(plr->groupIDs[i]);
if (sock == nullptr)
continue;
if (type == P_FE2CL_PC_GROUP_LEAVE_SUCC) {
Player* leavingPlr = PlayerManager::getPlayer(sock);
leavingPlr->iIDGroup = leavingPlr->iID;
}
sock->sendPacket(buf, type, size);
}
}
void GroupManager::groupTickInfo(Player* plr) {
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO), plr->groupCnt, sizeof(sPCGroupMemberInfo))) {
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_JOIN packet size\n";
return;
}
size_t resplen = sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO) + plr->groupCnt * sizeof(sPCGroupMemberInfo);
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
memset(respbuf, 0, resplen);
sP_FE2CL_PC_GROUP_MEMBER_INFO *resp = (sP_FE2CL_PC_GROUP_MEMBER_INFO*)respbuf;
sPCGroupMemberInfo *respdata = (sPCGroupMemberInfo*)(respbuf+sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO));
resp->iID = plr->iID;
resp->iMemberPCCnt = plr->groupCnt;
for (int i = 0; i < plr->groupCnt; i++) {
Player* varPlr = PlayerManager::getPlayerFromID(plr->groupIDs[i]);
if (varPlr == nullptr)
continue;
respdata[i].iPC_ID = varPlr->iID;
respdata[i].iPCUID = varPlr->PCStyle.iPC_UID;
respdata[i].iNameCheck = varPlr->PCStyle.iNameCheck;
memcpy(respdata[i].szFirstName, varPlr->PCStyle.szFirstName, sizeof(varPlr->PCStyle.szFirstName));
memcpy(respdata[i].szLastName, varPlr->PCStyle.szLastName, sizeof(varPlr->PCStyle.szLastName));
respdata[i].iSpecialState = varPlr->iSpecialState;
respdata[i].iLv = varPlr->level;
respdata[i].iHP = varPlr->HP;
respdata[i].iMaxHP = PC_MAXHEALTH(varPlr->level);
//respdata[i].iMapType = 0;
//respdata[i].iMapNum = 0;
respdata[i].iX = varPlr->x;
respdata[i].iY = varPlr->y;
respdata[i].iZ = varPlr->z;
if (varPlr->activeNano > 0) {
respdata[i].bNano = 1;
respdata[i].Nano = varPlr->Nanos[varPlr->activeNano];
}
}
sendToGroup(plr, (void*)&respbuf, P_FE2CL_PC_GROUP_MEMBER_INFO, resplen);
}
void GroupManager::groupKickPlayer(Player* plr) {
// if you are the group leader, destroy your own group and kick everybody
if (plr->iID == plr->iIDGroup) {
groupUnbuff(plr);
INITSTRUCT(sP_FE2CL_PC_GROUP_LEAVE_SUCC, resp1);
sendToGroup(plr, (void*)&resp1, P_FE2CL_PC_GROUP_LEAVE_SUCC, sizeof(sP_FE2CL_PC_GROUP_LEAVE_SUCC));
plr->groupCnt = 1;
return;
}
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
if (otherPlr == nullptr)
return;
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_LEAVE), otherPlr->groupCnt - 1, sizeof(sPCGroupMemberInfo))) {
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_LEAVE packet size\n";
return;
}
size_t resplen = sizeof(sP_FE2CL_PC_GROUP_LEAVE) + (otherPlr->groupCnt - 1) * sizeof(sPCGroupMemberInfo);
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
memset(respbuf, 0, resplen);
sP_FE2CL_PC_GROUP_LEAVE *resp = (sP_FE2CL_PC_GROUP_LEAVE*)respbuf;
sPCGroupMemberInfo *respdata = (sPCGroupMemberInfo*)(respbuf+sizeof(sP_FE2CL_PC_GROUP_LEAVE));
resp->iID_LeaveMember = plr->iID;
resp->iMemberPCCnt = otherPlr->groupCnt - 1;
int bitFlagBefore = getGroupFlags(otherPlr);
int bitFlag = bitFlagBefore & ~plr->iGroupConditionBitFlag;
int moveDown = 0;
for (int i = 0; i < otherPlr->groupCnt; i++) {
Player* varPlr = PlayerManager::getPlayerFromID(otherPlr->groupIDs[i]);
CNSocket* sockTo = PlayerManager::getSockFromID(otherPlr->groupIDs[i]);
if (varPlr == nullptr || sockTo == nullptr)
continue;
if (moveDown == 1)
otherPlr->groupIDs[i-1] = otherPlr->groupIDs[i];
respdata[i-moveDown].iPC_ID = varPlr->iID;
respdata[i-moveDown].iPCUID = varPlr->PCStyle.iPC_UID;
respdata[i-moveDown].iNameCheck = varPlr->PCStyle.iNameCheck;
memcpy(respdata[i-moveDown].szFirstName, varPlr->PCStyle.szFirstName, sizeof(varPlr->PCStyle.szFirstName));
memcpy(respdata[i-moveDown].szLastName, varPlr->PCStyle.szLastName, sizeof(varPlr->PCStyle.szLastName));
respdata[i-moveDown].iSpecialState = varPlr->iSpecialState;
respdata[i-moveDown].iLv = varPlr->level;
respdata[i-moveDown].iHP = varPlr->HP;
respdata[i-moveDown].iMaxHP = PC_MAXHEALTH(varPlr->level);
// respdata[i-moveDown]].iMapType = 0;
// respdata[i-moveDown]].iMapNum = 0;
respdata[i-moveDown].iX = varPlr->x;
respdata[i-moveDown].iY = varPlr->y;
respdata[i-moveDown].iZ = varPlr->z;
// client doesnt read nano data here
if (varPlr == plr) {
moveDown = 1;
otherPlr->groupIDs[i] = 0;
NanoManager::nanoChangeBuff(sockTo, varPlr, bitFlagBefore | varPlr->iConditionBitFlag, varPlr->iConditionBitFlag);
} else
NanoManager::nanoChangeBuff(sockTo, varPlr, bitFlagBefore | varPlr->iConditionBitFlag, bitFlag | varPlr->iConditionBitFlag);
}
plr->iIDGroup = plr->iID;
otherPlr->groupCnt -= 1;
sendToGroup(otherPlr, (void*)&respbuf, P_FE2CL_PC_GROUP_LEAVE, resplen);
CNSocket* sock = PlayerManager::getSockFromID(plr->iID);
if (sock == nullptr)
return;
INITSTRUCT(sP_FE2CL_PC_GROUP_LEAVE_SUCC, resp1);
sock->sendPacket((void*)&resp1, P_FE2CL_PC_GROUP_LEAVE_SUCC, sizeof(sP_FE2CL_PC_GROUP_LEAVE_SUCC));
}
void GroupManager::groupUnbuff(Player* plr) {
int bitFlag = getGroupFlags(plr);
for (int i = 0; i < plr->groupCnt; i++) {
CNSocket* sock = PlayerManager::getSockFromID(plr->groupIDs[i]);
if (sock == nullptr)
continue;
Player* otherPlr = PlayerManager::getPlayer(sock);
NanoManager::nanoChangeBuff(sock, otherPlr, bitFlag | otherPlr->iConditionBitFlag, otherPlr->iConditionBitFlag);
}
}
int GroupManager::getGroupFlags(Player* plr) {
int bitFlag = 0;
for (int i = 0; i < plr->groupCnt; i++) {
Player* otherPlr = PlayerManager::getPlayerFromID(plr->groupIDs[i]);
if (otherPlr == nullptr)
continue;
bitFlag |= otherPlr->iGroupConditionBitFlag;
}
return bitFlag;
}

25
src/GroupManager.hpp Normal file
View File

@@ -0,0 +1,25 @@
#pragma once
#include "Player.hpp"
#include "CNProtocol.hpp"
#include "CNStructs.hpp"
#include "CNShardServer.hpp"
#include <map>
#include <list>
namespace GroupManager {
void init();
void requestGroup(CNSocket* sock, CNPacketData* data);
void refuseGroup(CNSocket* sock, CNPacketData* data);
void joinGroup(CNSocket* sock, CNPacketData* data);
void leaveGroup(CNSocket* sock, CNPacketData* data);
void chatGroup(CNSocket* sock, CNPacketData* data);
void menuChatGroup(CNSocket* sock, CNPacketData* data);
void sendToGroup(Player* plr, void* buf, uint32_t type, size_t size);
void groupTickInfo(Player* plr);
void groupKickPlayer(Player* plr);
void groupUnbuff(Player* plr);
int getGroupFlags(Player* plr);
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,14 +3,47 @@
#include "CNShardServer.hpp"
#include "Player.hpp"
namespace ItemManager {
void init();
struct Item {
bool tradeable, sellable;
int buyPrice, sellPrice, stackSize, level, rarity, pointDamage, groupDamage, defense, gender; // TODO: implement more as needed
};
struct VendorListing {
int sort, type, iID;
};
struct CrocPotEntry {
int multStats, multLooks;
float base, rd0, rd1, rd2, rd3;
};
struct Crate {
int rarityRatioId;
std::vector<int> itemSets;
};
void itemMoveHandler(CNSocket* sock, CNPacketData* data);
void itemDeleteHandler(CNSocket* sock, CNPacketData* data);
namespace ItemManager {
enum class SlotType {
EQUIP = 0,
INVENTORY = 1,
BANK = 3
};
// hopefully this is fine since it's never modified after load
extern std::map<std::pair<int32_t, int32_t>, Item> ItemData; // <id, type> -> data
extern std::map<int32_t, std::vector<VendorListing>> VendorTables;
extern std::map<int32_t, CrocPotEntry> CrocPotTable; // level gap -> entry
extern std::map<int32_t, std::vector<int>> RarityRatios;
extern std::map<int32_t, Crate> Crates;
// pair <Itemset, Rarity> -> vector of pointers (map iterators) to records in ItemData (it looks a lot scarier than it is)
extern std::map<std::pair<int32_t, int32_t>,
std::vector<std::map<std::pair<int32_t, int32_t>, Item>::iterator>> CrateItems;
void init();
void itemMoveHandler(CNSocket* sock, CNPacketData* data);
void itemDeleteHandler(CNSocket* sock, CNPacketData* data);
void itemGMGiveHandler(CNSocket* sock, CNPacketData* data);
void itemUseHandler(CNSocket* sock, CNPacketData* data);
// Bank
void itemBankOpenHandler(CNSocket* sock, CNPacketData* data);
void itemTradeOfferHandler(CNSocket* sock, CNPacketData* data);
//void itemTradeOfferCancel(CNSocket* sock, CNPacketData* data);
void itemTradeOfferAcceptHandler(CNSocket* sock, CNPacketData* data);
void itemTradeOfferRefusalHandler(CNSocket* sock, CNPacketData* data);
void itemTradeConfirmHandler(CNSocket* sock, CNPacketData* data);
@@ -21,5 +54,14 @@ namespace ItemManager {
void itemTradeChatHandler(CNSocket* sock, CNPacketData* data);
void chestOpenHandler(CNSocket* sock, CNPacketData* data);
// crate opening logic with all helper functions
int getItemSetId(Crate& crate, int crateId);
int getRarity(Crate& crate, int itemSetId);
int getCrateItem(sItemBase& reward, int itemSetId, int rarity, int playerGender);
int findFreeSlot(Player *plr);
Item* getItemData(int32_t id, int32_t type);
void checkItemExpire(CNSocket* sock, Player* player);
void setItemStats(Player* plr);
void updateEquips(CNSocket* sock, Player* plr);
}

View File

@@ -2,26 +2,98 @@
#include "CNStructs.hpp"
#include "MissionManager.hpp"
#include "PlayerManager.hpp"
#include "NanoManager.hpp"
#include "ItemManager.hpp"
#include "string.h"
std::map<int32_t, Reward*> MissionManager::Rewards;
std::map<int32_t, TaskData*> MissionManager::Tasks;
nlohmann::json MissionManager::AvatarGrowth[37];
void MissionManager::init() {
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TASK_START, acceptMission);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TASK_END, completeMission);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TASK_START, taskStart);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TASK_END, taskEnd);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_SET_CURRENT_MISSION_ID, setMission);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TASK_STOP, quitMission);
}
void MissionManager::acceptMission(CNSocket* sock, CNPacketData* data) {
bool startTask(Player* plr, int TaskID, bool NanoMission) {
if (MissionManager::Tasks.find(TaskID) == MissionManager::Tasks.end()) {
std::cout << "[WARN] Player submitted unknown task!?" << std::endl;
return false;
}
// client freaks out if nano mission isn't sent first after reloging, so it's easiest to set it here
if (NanoMission && plr->tasks[0] != 0) {
// lets move task0 to different spot
int moveToSlot = 1;
for (; moveToSlot < ACTIVE_MISSION_COUNT; moveToSlot++)
if (plr->tasks[moveToSlot] == 0)
break;
plr->tasks[moveToSlot] = plr->tasks[0];
plr->tasks[0] = 0;
for (int i = 0; i < 3; i++) {
plr->RemainingNPCCount[moveToSlot][i] = plr->RemainingNPCCount[0][i];
plr->RemainingNPCCount[0][i] = 0;
}
}
TaskData& task = *MissionManager::Tasks[TaskID];
int i;
for (i = 0; i < ACTIVE_MISSION_COUNT; i++) {
if (plr->tasks[i] == 0) {
plr->tasks[i] = TaskID;
for (int j = 0; j < 3; j++) {
plr->RemainingNPCCount[i][j] = (int)task["m_iCSUNumToKill"][j];
}
break;
}
}
if (i == ACTIVE_MISSION_COUNT - 1 && plr->tasks[i] != TaskID) {
std::cout << "[WARN] Player has more than 6 active missions!?" << std::endl;
}
return true;
}
void MissionManager::taskStart(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_PC_TASK_START))
return; // malformed packet
sP_CL2FE_REQ_PC_TASK_START* missionData = (sP_CL2FE_REQ_PC_TASK_START*)data->buf;
INITSTRUCT(sP_FE2CL_REP_PC_TASK_START_SUCC, response);
Player *plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return;
if (!startTask(plr, missionData->iTaskNum, false)) {
// TODO: TASK_FAIL?
response.iTaskNum = missionData->iTaskNum;
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_START_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_START_SUCC));
return;
}
response.iTaskNum = missionData->iTaskNum;
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_START_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_START_SUCC));
// HACK: auto-succeed Eduardo escort task
// TODO: maybe check for iTaskType == 6 and skip all escort missions?
if (missionData->iTaskNum == 576) {
std::cout << "Sending Eduardo success packet" << std::endl;
INITSTRUCT(sP_FE2CL_REP_PC_TASK_END_SUCC, response);
endTask(sock, 576);
response.iTaskNum = 576;
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_END_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_END_SUCC));
}
}
void MissionManager::completeMission(CNSocket* sock, CNPacketData* data) {
void MissionManager::taskEnd(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_PC_TASK_END))
return; // malformed packet
@@ -29,17 +101,94 @@ void MissionManager::completeMission(CNSocket* sock, CNPacketData* data) {
INITSTRUCT(sP_FE2CL_REP_PC_TASK_END_SUCC, response);
response.iTaskNum = missionData->iTaskNum;
if (!endTask(sock, missionData->iTaskNum)) {
return;
}
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_END_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_END_SUCC));
}
bool MissionManager::endTask(CNSocket *sock, int32_t taskNum) {
Player *plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return false;
if (Tasks.find(taskNum) == Tasks.end())
return false;
// ugly pointer/reference juggling for the sake of operator overloading...
TaskData& task = *Tasks[taskNum];
// mission rewards
if (Rewards.find(taskNum) != Rewards.end()) {
if (giveMissionReward(sock, taskNum) == -1)
return false; // we don't want to send anything
}
// don't take away quest items if we haven't finished the quest
/*
* Give (or take away) quest items
*
* Some mission tasks give the player a quest item upon completion.
* This is distinct from quest item mob drops.
* They can be identified by a counter in the task indicator (ie. 1/1 Gravity Decelerator).
* The server is responsible for dropping the correct item.
* Yes, this is pretty stupid.
*
* iSUInstancename is the number of items to give. It is usually negative at the end of
* a mission, to clean up its quest items.
*/
for (int i = 0; i < 3; i++)
if (task["m_iSUItem"][i] != 0)
dropQuestItem(sock, taskNum, task["m_iSUInstancename"][i], task["m_iSUItem"][i], 0);
// update player
int i;
for (i = 0; i < ACTIVE_MISSION_COUNT; i++) {
if (plr->tasks[i] == taskNum) {
plr->tasks[i] = 0;
for (int j = 0; j < 3; j++) {
plr->RemainingNPCCount[i][j] = 0;
}
}
}
if (i == ACTIVE_MISSION_COUNT - 1 && plr->tasks[i] != 0) {
std::cout << "[WARN] Player completed non-active mission!?" << std::endl;
}
// if it's the last task
if (task["m_iSUOutgoingTask"] == 0) {
// save completed mission on player
saveMission(plr, (int)(task["m_iHMissionID"])-1);
// if it's a nano mission, reward the nano.
if (task["m_iSTNanoID"] != 0)
NanoManager::addNano(sock, task["m_iSTNanoID"], 0, true);
// remove current mission
plr->CurrentMissionID = 0;
}
return true;
}
void MissionManager::setMission(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_PC_SET_CURRENT_MISSION_ID))
return; // malformed packet
Player* plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return;
sP_CL2FE_REQ_PC_SET_CURRENT_MISSION_ID* missionData = (sP_CL2FE_REQ_PC_SET_CURRENT_MISSION_ID*)data->buf;
INITSTRUCT(sP_FE2CL_REP_PC_SET_CURRENT_MISSION_ID, response);
response.iCurrentMissionID = missionData->iCurrentMissionID;
plr->CurrentMissionID = missionData->iCurrentMissionID;
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_SET_CURRENT_MISSION_ID, sizeof(sP_FE2CL_REP_PC_SET_CURRENT_MISSION_ID));
}
@@ -48,8 +197,333 @@ void MissionManager::quitMission(CNSocket* sock, CNPacketData* data) {
return; // malformed packet
sP_CL2FE_REQ_PC_TASK_STOP* missionData = (sP_CL2FE_REQ_PC_TASK_STOP*)data->buf;
INITSTRUCT(sP_FE2CL_REP_PC_TASK_STOP_SUCC, response);
quitTask(sock, missionData->iTaskNum, true);
}
response.iTaskNum = missionData->iTaskNum;
void MissionManager::quitTask(CNSocket* sock, int32_t taskNum, bool manual) {
Player* plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return;
// update player
int i;
for (i = 0; i < ACTIVE_MISSION_COUNT; i++) {
if (plr->tasks[i] == taskNum) {
plr->tasks[i] = 0;
for (int j = 0; j < 3; j++) {
plr->RemainingNPCCount[i][j] = 0;
}
}
}
if (i == ACTIVE_MISSION_COUNT - 1 && plr->tasks[i] != 0) {
std::cout << "[WARN] Player quit non-active mission!?" << std::endl;
}
// remove current mission
plr->CurrentMissionID = 0;
TaskData& task = *Tasks[taskNum];
// clean up quest items
for (i = 0; i < 3; i++) {
if (task["m_iSUItem"][i] == 0 && task["m_iCSUItemID"][i] == 0)
continue;
/*
* It's ok to do this only server-side, because the server decides which
* slot later items will be placed in.
*/
for (int j = 0; j < AQINVEN_COUNT; j++)
if (plr->QInven[j].iID == task["m_iSUItem"][i] || plr->QInven[j].iID == task["m_iCSUItemID"][i])
memset(&plr->QInven[j], 0, sizeof(sItemBase));
}
if (!manual) {
INITSTRUCT(sP_FE2CL_REP_PC_TASK_END_FAIL, failResp);
failResp.iErrorCode = 1;
failResp.iTaskNum = taskNum;
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_TASK_END_FAIL, sizeof(sP_FE2CL_REP_PC_TASK_END_FAIL));
}
INITSTRUCT(sP_FE2CL_REP_PC_TASK_STOP_SUCC, response);
response.iTaskNum = taskNum;
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_STOP_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_STOP_SUCC));
}
}
int MissionManager::findQSlot(Player *plr, int id) {
int i;
// two passes. we mustn't fail to find an existing stack.
for (i = 0; i < AQINVEN_COUNT; i++)
if (plr->QInven[i].iID == id)
return i;
// no stack. start a new one.
for (i = 0; i < AQINVEN_COUNT; i++)
if (plr->QInven[i].iOpt == 0)
return i;
// not found
return -1;
}
void MissionManager::dropQuestItem(CNSocket *sock, int task, int count, int id, int mobid) {
const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward);
assert(resplen < CN_PACKET_BUFFER_SIZE);
// we know it's only one trailing struct, so we can skip full validation
Player *plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return;
uint8_t respbuf[resplen]; // not a variable length array, don't worry
sP_FE2CL_REP_REWARD_ITEM *reward = (sP_FE2CL_REP_REWARD_ITEM *)respbuf;
sItemReward *item = (sItemReward *)(respbuf + sizeof(sP_FE2CL_REP_REWARD_ITEM));
// don't forget to zero the buffer!
memset(respbuf, 0, resplen);
// find free quest item slot
int slot = findQSlot(plr, id);
if (slot == -1) {
// this should never happen
std::cout << "[WARN] Player has no room for quest item!?" << std::endl;
return;
}
if (id != 0)
std::cout << "new qitem in slot " << slot << std::endl;
// update player
if (id != 0) {
plr->QInven[slot].iType = 8;
plr->QInven[slot].iID = id;
plr->QInven[slot].iOpt += count; // stacking
}
// fully destory deleted items, for good measure
if (plr->QInven[slot].iOpt <= 0)
memset(&plr->QInven[slot], 0, sizeof(sItemBase));
// preserve stats
reward->m_iCandy = plr->money;
reward->m_iFusionMatter = plr->fusionmatter;
reward->iFatigue = 100; // prevents warning message
reward->iFatigue_Level = 1;
reward->m_iBatteryN = plr->batteryN;
reward->m_iBatteryW = plr->batteryW;
reward->iItemCnt = 1; // remember to update resplen if you change this
reward->iTaskID = task;
reward->iNPC_TypeID = mobid;
item->sItem = plr->QInven[slot];
item->iSlotNum = slot;
item->eIL = 2;
sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen);
}
int MissionManager::giveMissionReward(CNSocket *sock, int task) {
Reward *reward = Rewards[task];
Player *plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return -1;
int nrewards = 0;
for (int i = 0; i < 4; i++) {
if (reward->itemIds[i] != 0)
nrewards++;
}
int slots[4];
for (int i = 0; i < nrewards; i++) {
slots[i] = ItemManager::findFreeSlot(plr);
if (slots[i] == -1) {
std::cout << "Not enough room to complete task" << std::endl;
INITSTRUCT(sP_FE2CL_REP_PC_TASK_END_FAIL, fail);
fail.iTaskNum = task;
fail.iErrorCode = 13; // inventory full
sock->sendPacket((void*)&fail, P_FE2CL_REP_PC_TASK_END_FAIL, sizeof(sP_FE2CL_REP_PC_TASK_END_FAIL));
return -1;
}
}
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + nrewards * sizeof(sItemReward);
assert(resplen < CN_PACKET_BUFFER_SIZE);
sP_FE2CL_REP_REWARD_ITEM *resp = (sP_FE2CL_REP_REWARD_ITEM *)respbuf;
sItemReward *item = (sItemReward *)(respbuf + sizeof(sP_FE2CL_REP_REWARD_ITEM));
// don't forget to zero the buffer!
memset(respbuf, 0, resplen);
// update player
plr->money += reward->money;
updateFusionMatter(sock, reward->fusionmatter);
// simple rewards
resp->m_iCandy = plr->money;
resp->m_iFusionMatter = plr->fusionmatter;
resp->iFatigue = 100; // prevents warning message
resp->iFatigue_Level = 1;
resp->iItemCnt = nrewards;
resp->m_iBatteryN = plr->batteryN;
resp->m_iBatteryW = plr->batteryW;
for (int i = 0; i < nrewards; i++) {
item[i].sItem.iType = reward->itemTypes[i];
item[i].sItem.iID = reward->itemIds[i];
item[i].iSlotNum = slots[i];
item[i].eIL = 1;
// update player
plr->Inven[slots[i]] = item->sItem;
}
sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen);
return 0;
}
void MissionManager::updateFusionMatter(CNSocket* sock, int fusion) {
Player *plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return;
plr->fusionmatter += fusion;
// there's a much lower FM cap in the Future
if (plr->fusionmatter > AvatarGrowth[plr->level]["m_iFMLimit"])
plr->fusionmatter = AvatarGrowth[plr->level]["m_iFMLimit"];
else if (plr->fusionmatter < 0) // if somehow lowered too far
plr->fusionmatter = 0;
// check if it is enough for the nano mission
if (plr->fusionmatter <= AvatarGrowth[plr->level]["m_iReqBlob_NanoCreate"])
return;
// don't give the Blossom nano mission until the player's in the Past
if (plr->level == 4 && plr->PCStyle2.iPayzoneFlag == 0)
return;
// check if the nano task is already started
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
TaskData& task = *Tasks[plr->tasks[i]];
if (task["m_iSTNanoID"] != 0)
return; // nano mission was already started!
}
// start the nano mission
startTask(plr, AvatarGrowth[plr->level]["m_iNanoQuestTaskID"], true);
INITSTRUCT(sP_FE2CL_REP_PC_TASK_START_SUCC, response);
response.iTaskNum = AvatarGrowth[plr->level]["m_iNanoQuestTaskID"];
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_START_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_START_SUCC));
}
void MissionManager::mobKilled(CNSocket *sock, int mobid) {
Player *plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return;
bool missionmob = false;
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
if (plr->tasks[i] == 0)
continue;
// tasks[] should always have valid IDs
TaskData& task = *Tasks[plr->tasks[i]];
for (int j = 0; j < 3; j++) {
if (task["m_iCSUEnemyID"][j] != mobid)
continue;
// acknowledge killing of mission mob...
if (task["m_iCSUNumToKill"][j] != 0) {
missionmob = true;
// sanity check
if (plr->RemainingNPCCount[i][j] == 0) {
std::cout << "[WARN] RemainingNPCCount tries to go below 0?!" << std::endl;
} else {
plr->RemainingNPCCount[i][j]--;
}
}
// drop quest item
if (task["m_iCSUItemNumNeeded"][j] != 0 && !isQuestItemFull(sock, task["m_iCSUItemID"][j], task["m_iCSUItemNumNeeded"][j]) ) {
bool drop = rand() % 100 < task["m_iSTItemDropRate"][j];
if (drop) {
// XXX: are CSUItemID and CSTItemID the same?
dropQuestItem(sock, plr->tasks[i], 1, task["m_iCSUItemID"][j], mobid);
} else {
// fail to drop (itemID == 0)
dropQuestItem(sock, plr->tasks[i], 1, 0, mobid);
}
}
}
}
// ...but only once
// XXX: is it actually necessary to do it this way?
if (missionmob) {
INITSTRUCT(sP_FE2CL_REP_PC_KILL_QUEST_NPCs_SUCC, kill);
kill.iNPCID = mobid;
sock->sendPacket((void*)&kill, P_FE2CL_REP_PC_KILL_QUEST_NPCs_SUCC, sizeof(sP_FE2CL_REP_PC_KILL_QUEST_NPCs_SUCC));
}
}
void MissionManager::saveMission(Player* player, int missionId) {
// sanity check missionID so we don't get exceptions
if (missionId < 0 || missionId>1023) {
std::cout << "[WARN] Client submitted invalid missionId: " <<missionId<< std::endl;
return;
}
// Missions are stored in int64_t array
int row = missionId / 64;
int column = missionId % 64;
player->aQuestFlag[row] |= (1ULL << column);
}
bool MissionManager::isQuestItemFull(CNSocket* sock, int itemId, int itemCount) {
Player* plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return true;
int slot = findQSlot(plr, itemId);
if (slot == -1) {
// this should never happen
std::cout << "[WARN] Player has no room for quest item!?" << std::endl;
return true;
}
return (itemCount == plr->QInven[slot].iOpt);
}
void MissionManager::failInstancedMissions(CNSocket* sock) {
// loop through all tasks; if the required instance is being left, "fail" the task
Player* plr = PlayerManager::getPlayer(sock);
for (int i = 0; i < 6; i++) {
int taskNum = plr->tasks[i];
if (MissionManager::Tasks.find(taskNum) == MissionManager::Tasks.end())
continue; // sanity check
TaskData* task = MissionManager::Tasks[taskNum];
if (task->task["m_iRequireInstanceID"] != 0) { // mission is instanced
int failTaskID = task->task["m_iFOutgoingTask"];
if (failTaskID != 0) {
MissionManager::quitTask(sock, taskNum, false);
plr->tasks[i] = failTaskID;
}
}
}
}

View File

@@ -1,12 +1,63 @@
#pragma once
#include "CNShardServer.hpp"
#include "Player.hpp"
#include "contrib/JSON.hpp"
struct Reward {
int32_t id;
int32_t itemTypes[4];
int32_t itemIds[4];
int32_t money;
int32_t fusionmatter;
Reward(int32_t id, nlohmann::json types, nlohmann::json ids, int32_t m, int32_t fm) :
id(id), money(m), fusionmatter(fm) {
for (int i = 0; i < 4; i++) {
itemTypes[i] = types[i];
itemIds[i] = ids[i];
}
};
};
struct TaskData {
/*
* TODO: We'll probably want to keep only the data the server actually needs,
* but for now RE/development is much easier if we have everything at
* our fingertips.
*/
nlohmann::json task;
TaskData(nlohmann::json t) : task(t) {}
// convenience
auto operator[](std::string s) { return task[s]; }
};
namespace MissionManager {
extern std::map<int32_t, Reward*> Rewards;
extern std::map<int32_t, TaskData*> Tasks;
extern nlohmann::json AvatarGrowth[37];
void init();
void acceptMission(CNSocket* sock, CNPacketData* data);
void completeMission(CNSocket* sock, CNPacketData* data);
void taskStart(CNSocket* sock, CNPacketData* data);
void taskEnd(CNSocket* sock, CNPacketData* data);
void setMission(CNSocket* sock, CNPacketData* data);
void quitMission(CNSocket* sock, CNPacketData* data);
}
int findQSlot(Player *plr, int id);
void dropQuestItem(CNSocket *sock, int task, int count, int id, int mobid);
// checks if player doesn't have n/n quest items
bool isQuestItemFull(CNSocket* sock, int itemId, int itemCount);
int giveMissionReward(CNSocket *sock, int task);
void updateFusionMatter(CNSocket* sock, int fusion);
void mobKilled(CNSocket *sock, int mobid);
bool endTask(CNSocket *sock, int32_t taskNum);
void saveMission(Player* player, int missionId);
void quitTask(CNSocket* sock, int32_t taskNum, bool manual);
void failInstancedMissions(CNSocket* sock);
}

1085
src/MobManager.cpp Normal file

File diff suppressed because it is too large Load Diff

138
src/MobManager.hpp Normal file
View File

@@ -0,0 +1,138 @@
#pragma once
#include "CNProtocol.hpp"
#include "CNShared.hpp"
#include "CNShardServer.hpp"
#include "NPC.hpp"
#include "contrib/JSON.hpp"
#include <map>
#include <queue>
enum class MobState {
INACTIVE,
ROAMING,
COMBAT,
RETREAT,
DEAD
};
struct Mob : public BaseNPC {
// general
MobState state;
int maxHealth;
int spawnX;
int spawnY;
int spawnZ;
int level;
// dead
time_t killedTime = 0;
time_t regenTime;
bool summoned = false;
bool despawned = false; // for the sake of death animations
// roaming
int idleRange;
time_t nextMovement = 0;
bool staticPath = false;
// combat
CNSocket *target = nullptr;
time_t nextAttack = 0;
int roamX, roamY, roamZ;
// drop
int dropType;
// temporary; until we're sure what's what
nlohmann::json data;
Mob(int x, int y, int z, int angle, uint64_t iID, int type, int hp, nlohmann::json d, int32_t id)
: BaseNPC(x, y, z, angle, iID, type, id), maxHealth(hp) {
state = MobState::ROAMING;
data = d;
regenTime = data["m_iRegenTime"];
idleRange = (int)data["m_iIdleRange"] * 2; // TODO: tuning?
dropType = data["m_iDropType"];
level = data["m_iNpcLevel"];
roamX = spawnX = appearanceData.iX;
roamY = spawnY = appearanceData.iY;
roamZ = spawnZ = appearanceData.iZ;
appearanceData.iConditionBitFlag = 0;
// NOTE: there appear to be discrepancies in the dump
appearanceData.iHP = maxHealth;
npcClass = NPC_MOB;
}
// constructor for /summon
Mob(int x, int y, int z, uint64_t iID, int type, nlohmann::json d, int32_t id)
: Mob(x, y, z, 0, iID, type, 0, d, id) {
summoned = true; // will be despawned and deallocated when killed
appearanceData.iHP = maxHealth = d["m_iHP"];
}
~Mob() {}
auto operator[](std::string s) {
return data[s];
}
};
struct MobDropChance {
int dropChance;
std::vector<int> cratesRatio;
};
struct MobDrop {
std::vector<int> crateIDs;
int dropChanceType;
int taros;
int fm;
int boosts;
};
namespace MobManager {
extern std::map<int32_t, Mob*> Mobs;
extern std::queue<int32_t> RemovalQueue;
extern std::map<int32_t, MobDropChance> MobDropChances;
extern std::map<int32_t, MobDrop> MobDrops;
extern bool simulateMobs;
void init();
void step(CNServer*, time_t);
void playerTick(CNServer*, time_t);
void deadStep(Mob*, time_t);
void combatStep(Mob*, time_t);
void retreatStep(Mob*, time_t);
void roamingStep(Mob*, time_t);
void pcAttackNpcs(CNSocket *sock, CNPacketData *data);
void combatBegin(CNSocket *sock, CNPacketData *data);
void combatEnd(CNSocket *sock, CNPacketData *data);
void dotDamageOnOff(CNSocket *sock, CNPacketData *data);
void dealGooDamage(CNSocket *sock, int amount);
void npcAttackPc(Mob *mob, time_t currTime);
int hitMob(CNSocket *sock, Mob *mob, int damage);
void killMob(CNSocket *sock, Mob *mob);
void giveReward(CNSocket *sock, Mob *mob);
void getReward(sItemBase *reward, MobDrop *drop, MobDropChance *chance);
void giveEventReward(CNSocket* sock, Player* player);
std::pair<int,int> lerp(int, int, int, int, int);
std::pair<int,int> getDamage(int, int, bool, bool, int, int, int);
void pcAttackChars(CNSocket *sock, CNPacketData *data);
void resendMobHP(Mob *mob);
void incNextMovement(Mob *mob, time_t currTime=0);
bool aggroCheck(Mob *mob, time_t currTime);
}

View File

@@ -1,37 +1,33 @@
#pragma once
#include "CNStructs.hpp"
#include "ChunkManager.hpp"
class BaseNPC {
public:
sNPCAppearanceData appearanceData;
NPCClass npcClass;
uint64_t instanceID;
std::tuple<int, int, uint64_t> chunkPos;
std::vector<Chunk*> currentChunks;
BaseNPC() {};
BaseNPC(int x, int y, int z, int type) {
BaseNPC(int x, int y, int z, int angle, uint64_t iID, int type, int id) {
appearanceData.iX = x;
appearanceData.iY = y;
appearanceData.iZ = z;
appearanceData.iNPCType = type;
appearanceData.iHP = 400;
appearanceData.iAngle = 0;
appearanceData.iAngle = angle;
appearanceData.iConditionBitFlag = 0;
appearanceData.iBarkerType = 0;
appearanceData.iNPC_ID = id;
// hopefully no collisions happen :eyes:
appearanceData.iNPC_ID = (int32_t)rand();
instanceID = iID;
chunkPos = std::make_tuple(0, 0, instanceID);
};
BaseNPC(int x, int y, int z, int type, int hp, int cond, int angle, int barker) {
appearanceData.iX = x;
appearanceData.iY = y;
appearanceData.iZ = z;
appearanceData.iNPCType = type;
appearanceData.iHP = hp;
appearanceData.iAngle = angle;
appearanceData.iConditionBitFlag = cond;
appearanceData.iBarkerType = barker;
// hopefully no collisions happen :eyes:
appearanceData.iNPC_ID = (int32_t)rand();
BaseNPC(int x, int y, int z, int angle, uint64_t iID, int type, int id, NPCClass classType) : BaseNPC(x, y, z, angle, iID, type, id) {
npcClass = classType;
}
};

View File

@@ -1,171 +1,568 @@
#include "NPCManager.hpp"
#include "ItemManager.hpp"
#include "settings.hpp"
#include "MobManager.hpp"
#include "MissionManager.hpp"
#include "ChunkManager.hpp"
#include <cmath>
#include <algorithm>
#include <list>
#include <fstream>
#include <vector>
#include <assert.h>
#include <limits.h>
#include "contrib/JSON.hpp"
std::map<int32_t, BaseNPC> NPCManager::NPCs;
std::map<int32_t, BaseNPC*> NPCManager::NPCs;
std::map<int32_t, WarpLocation> NPCManager::Warps;
std::vector<WarpLocation> NPCManager::RespawnPoints;
nlohmann::json NPCManager::NPCData;
/*
* Initialized at the end of TableData::init().
* This allows us to summon and kill mobs in arbitrary order without
* NPC ID collisions.
*/
int32_t NPCManager::nextId;
void NPCManager::init() {
// load NPCs from NPCs.json into our NPC manager
// Temporary fix, IDs will be pulled from json later
int i = 0;
try {
std::ifstream inFile(settings::NPCJSON);
nlohmann::json npcData;
// read file into json
inFile >> npcData;
for (nlohmann::json::iterator npc = npcData.begin(); npc != npcData.end(); npc++) {
BaseNPC tmp(npc.value()["x"], npc.value()["y"], npc.value()["z"], npc.value()["id"]);
// Temporary fix, IDs will be pulled from json later
tmp.appearanceData.iNPC_ID = i;
i++;
NPCs[tmp.appearanceData.iNPC_ID] = tmp;
if (npc.value()["id"] == 641 || npc.value()["id"] == 642)
RespawnPoints.push_back({ npc.value()["x"], npc.value()["y"], ((int)npc.value()["z"]) + RESURRECT_HEIGHT });
}
}
catch (const std::exception& err) {
std::cerr << "[WARN] Malformed NPCs.json file! Reason:" << err.what() << std::endl;
}
// load temporary mob dump
try {
std::ifstream inFile(settings::MOBJSON); // not in settings, since it's temp
nlohmann::json npcData;
// read file into json
inFile >> npcData;
for (nlohmann::json::iterator npc = npcData.begin(); npc != npcData.end(); npc++) {
BaseNPC tmp(npc.value()["iX"], npc.value()["iY"], npc.value()["iZ"], npc.value()["iNPCType"],
npc.value()["iHP"], npc.value()["iConditionBitFlag"], npc.value()["iAngle"], npc.value()["iBarkerType"]);
// Temporary fix, IDs will be pulled from json later
tmp.appearanceData.iNPC_ID = i;
i++;
NPCs[tmp.appearanceData.iNPC_ID] = tmp;
}
std::cout << "[INFO] populated " << NPCs.size() << " NPCs" << std::endl;
}
catch (const std::exception& err) {
std::cerr << "[WARN] Malformed mobs.json file! Reason:" << err.what() << std::endl;
}
try {
std::ifstream infile(settings::WARPJSON);
nlohmann::json warpData;
// read file into json
infile >> warpData;
for (nlohmann::json::iterator warp = warpData.begin(); warp != warpData.end(); warp++) {
WarpLocation warpLoc = { warp.value()["m_iToX"], warp.value()["m_iToY"], warp.value()["m_iToZ"] };
int warpID = atoi(warp.key().c_str());
Warps[warpID] = warpLoc;
}
std::cout << "[INFO] populated " << Warps.size() << " Warps" << std::endl;
}
catch (const std::exception& err) {
std::cerr << "[WARN] Malformed warps.json file! Reason:" << err.what() << std::endl;
}
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_WARP_USE_NPC, npcWarpHandler);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TIME_TO_GO_WARP, npcWarpTimeMachine);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_NPC_SUMMON, npcSummonHandler);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_NPC_UNSUMMON, npcUnsummonHandler);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_BARKER, npcBarkHandler);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_START, npcVendorStart);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_TABLE_UPDATE, npcVendorTable);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_ITEM_BUY, npcVendorBuy);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_ITEM_SELL, npcVendorSell);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_ITEM_RESTORE_BUY, npcVendorBuyback);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_BATTERY_BUY, npcVendorBuyBattery);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ITEM_COMBINATION, npcCombineItems);
}
void NPCManager::updatePlayerNPCS(CNSocket* sock, PlayerView& view) {
std::list<int32_t> yesView;
std::list<int32_t> noView;
void NPCManager::removeNPC(std::vector<Chunk*> viewableChunks, int32_t id) {
BaseNPC* npc = NPCs[id];
for (auto& pair : NPCs) {
int diffX = abs(view.plr->x - pair.second.appearanceData.iX);
int diffY = abs(view.plr->y - pair.second.appearanceData.iY);
switch (npc->npcClass) {
case NPC_BUS:
INITSTRUCT(sP_FE2CL_TRANSPORTATION_EXIT, exitBusData);
exitBusData.eTT = 3;
exitBusData.iT_ID = id;
if (diffX < settings::NPCDISTANCE && diffY < settings::NPCDISTANCE) {
yesView.push_back(pair.first);
for (Chunk* chunk : viewableChunks) {
for (CNSocket* sock : chunk->players) {
// send to socket
sock->sendPacket((void*)&exitBusData, P_FE2CL_TRANSPORTATION_EXIT, sizeof(sP_FE2CL_TRANSPORTATION_EXIT));
}
}
else {
noView.push_back(pair.first);
break;
default:
// create struct
INITSTRUCT(sP_FE2CL_NPC_EXIT, exitData);
exitData.iNPC_ID = id;
// remove it from the clients
for (Chunk* chunk : viewableChunks) {
for (CNSocket* sock : chunk->players) {
// send to socket
sock->sendPacket((void*)&exitData, P_FE2CL_NPC_EXIT, sizeof(sP_FE2CL_NPC_EXIT));
}
}
break;
}
}
void NPCManager::addNPC(std::vector<Chunk*> viewableChunks, int32_t id) {
BaseNPC* npc = NPCs[id];
switch (npc->npcClass) {
case NPC_BUS:
INITSTRUCT(sP_FE2CL_TRANSPORTATION_ENTER, enterBusData);
enterBusData.AppearanceData = { 3, npc->appearanceData.iNPC_ID, npc->appearanceData.iNPCType, npc->appearanceData.iX, npc->appearanceData.iY, npc->appearanceData.iZ };
for (Chunk* chunk : viewableChunks) {
for (CNSocket* sock : chunk->players) {
// send to socket
sock->sendPacket((void*)&enterBusData, P_FE2CL_TRANSPORTATION_ENTER, sizeof(sP_FE2CL_TRANSPORTATION_ENTER));
}
}
break;
default:
// create struct
INITSTRUCT(sP_FE2CL_NPC_ENTER, enterData);
enterData.NPCAppearanceData = npc->appearanceData;
for (Chunk* chunk : viewableChunks) {
for (CNSocket* sock : chunk->players) {
// send to socket
sock->sendPacket((void*)&enterData, P_FE2CL_NPC_ENTER, sizeof(sP_FE2CL_NPC_ENTER));
}
}
break;
}
}
void NPCManager::destroyNPC(int32_t id) {
// sanity check
if (NPCs.find(id) == NPCs.end()) {
std::cout << "npc not found : " << id << std::endl;
return;
}
INITSTRUCT(sP_FE2CL_NPC_EXIT, exitData);
std::list<int32_t>::iterator i = view.viewableNPCs.begin();
while (i != view.viewableNPCs.end()) {
int32_t id = *i;
BaseNPC* entity = NPCs[id];
if (std::find(noView.begin(), noView.end(), id) != noView.end()) {
// it shouldn't be visible, send NPC_EXIT
exitData.iNPC_ID = id;
sock->sendPacket((void*)&exitData, P_FE2CL_NPC_EXIT, sizeof(sP_FE2CL_NPC_EXIT));
// remove from view
view.viewableNPCs.erase(i++);
}
else {
i++;
}
// sanity check
if (ChunkManager::chunks.find(entity->chunkPos) == ChunkManager::chunks.end()) {
std::cout << "chunk not found!" << std::endl;
return;
}
INITSTRUCT(sP_FE2CL_NPC_ENTER, enterData);
for (int32_t id : yesView) {
if (std::find(view.viewableNPCs.begin(), view.viewableNPCs.end(), id) == view.viewableNPCs.end()) {
// needs to be added to viewableNPCs! send NPC_ENTER
// remove NPC from the chunk
Chunk* chunk = ChunkManager::chunks[entity->chunkPos];
chunk->NPCs.erase(id);
enterData.NPCAppearanceData = NPCs[id].appearanceData;
sock->sendPacket((void*)&enterData, P_FE2CL_NPC_ENTER, sizeof(sP_FE2CL_NPC_ENTER));
// remove from viewable chunks
removeNPC(entity->currentChunks, id);
// add to viewable
view.viewableNPCs.push_back(id);
}
// remove from mob manager
if (MobManager::Mobs.find(id) != MobManager::Mobs.end())
MobManager::Mobs.erase(id);
// finally, remove it from the map and free it
NPCs.erase(id);
delete entity;
std::cout << "npc removed!" << std::endl;
}
void NPCManager::updateNPCPosition(int32_t id, int X, int Y, int Z, int angle) {
NPCs[id]->appearanceData.iAngle = angle;
updateNPCPosition(id, X, Y, Z);
}
void NPCManager::updateNPCPosition(int32_t id, int X, int Y, int Z) {
BaseNPC* npc = NPCs[id];
npc->appearanceData.iX = X;
npc->appearanceData.iY = Y;
npc->appearanceData.iZ = Z;
std::tuple<int, int, uint64_t> newPos = ChunkManager::grabChunk(X, Y, npc->instanceID);
// nothing to be done (but we should also update currentChunks to add/remove stale chunks)
if (newPos == npc->chunkPos) {
npc->currentChunks = ChunkManager::grabChunks(newPos);
return;
}
PlayerManager::players[sock].viewableNPCs = view.viewableNPCs;
std::vector<Chunk*> allChunks = ChunkManager::grabChunks(newPos);
// send npc exit to stale chunks
removeNPC(ChunkManager::getDeltaChunks(npc->currentChunks, allChunks), id);
// send npc enter to new chunks
addNPC(ChunkManager::getDeltaChunks(allChunks, npc->currentChunks), id);
Chunk *chunk = nullptr;
if (ChunkManager::checkChunk(npc->chunkPos))
chunk = ChunkManager::chunks[npc->chunkPos];
if (ChunkManager::removeNPC(npc->chunkPos, id)) {
// if the old chunk was deallocated, remove it
allChunks.erase(std::remove(allChunks.begin(), allChunks.end(), chunk), allChunks.end());
}
ChunkManager::addNPC(X, Y, npc->instanceID, id);
npc->chunkPos = newPos;
npc->currentChunks = allChunks;
}
void NPCManager::updateNPCInstance(int32_t npcID, uint64_t instanceID) {
BaseNPC* npc = NPCs[npcID];
npc->instanceID = instanceID;
updateNPCPosition(npcID, npc->appearanceData.iX, npc->appearanceData.iY, npc->appearanceData.iZ);
}
void NPCManager::sendToViewable(BaseNPC *npc, void *buf, uint32_t type, size_t size) {
for (Chunk *chunk : npc->currentChunks) {
for (CNSocket *s : chunk->players) {
s->sendPacket(buf, type, size);
}
}
}
void NPCManager::npcVendorBuy(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_PC_VENDOR_ITEM_BUY))
return; // malformed packet
sP_CL2FE_REQ_PC_VENDOR_ITEM_BUY* req = (sP_CL2FE_REQ_PC_VENDOR_ITEM_BUY*)data->buf;
Player *plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return;
Item* item = ItemManager::getItemData(req->Item.iID, req->Item.iType);
if (item == nullptr) {
std::cout << "[WARN] Item id " << req->Item.iID << " with type " << req->Item.iType << " not found (buy)" << std::endl;
// NOTE: VENDOR_ITEM_BUY_FAIL is not actually handled client-side.
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL, failResp);
failResp.iErrorCode = 0;
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL));
return;
}
int itemCost = item->buyPrice * (item->stackSize > 1 ? req->Item.iOpt : 1);
int slot = ItemManager::findFreeSlot(plr);
if (itemCost > plr->money || slot == -1) {
// NOTE: VENDOR_ITEM_BUY_FAIL is not actually handled client-side.
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL, failResp);
failResp.iErrorCode = 0;
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_FAIL));
return;
}
// if vehicle
if (req->Item.iType == 10)
// set time limit: current time + 7days
req->Item.iTimeLimit = getTimestamp() + 604800;
if (slot != req->iInvenSlotNum) {
// possible item stacking?
std::cout << "[WARN] Client and server disagree on bought item slot (" << req->iInvenSlotNum << " vs " << slot << ")" << std::endl;
}
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_SUCC, resp);
plr->money = plr->money - itemCost;
plr->Inven[slot] = req->Item;
resp.iCandy = plr->money;
resp.iInvenSlotNum = slot;
resp.Item = req->Item;
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_VENDOR_ITEM_BUY_SUCC, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_SUCC));
}
void NPCManager::npcVendorSell(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_PC_VENDOR_ITEM_SELL))
return; // malformed packet
sP_CL2FE_REQ_PC_VENDOR_ITEM_SELL* req = (sP_CL2FE_REQ_PC_VENDOR_ITEM_SELL*)data->buf;
Player* plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return;
if (req->iInvenSlotNum < 0 || req->iInvenSlotNum >= AINVEN_COUNT || req->iItemCnt < 0) {
std::cout << "[WARN] Client failed to sell item in slot " << req->iInvenSlotNum << std::endl;
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL, failResp);
failResp.iErrorCode = 0;
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL));
return;
}
sItemBase* item = &plr->Inven[req->iInvenSlotNum];
Item* itemData = ItemManager::getItemData(item->iID, item->iType);
if (itemData == nullptr || !itemData->sellable) { // sanity + sellable check
std::cout << "[WARN] Item id " << item->iID << " with type " << item->iType << " not found (sell)" << std::endl;
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL, failResp);
failResp.iErrorCode = 0;
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_FAIL));
return;
}
sItemBase original;
memcpy(&original, item, sizeof(sItemBase));
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_SUCC, resp);
int sellValue = itemData->sellPrice * req->iItemCnt;
// increment taros
plr->money = plr->money + sellValue;
// modify item
if (plr->Inven[req->iInvenSlotNum].iOpt - req->iItemCnt > 0) { // selling part of a stack
item->iOpt -= req->iItemCnt;
original.iOpt = req->iItemCnt;
} else { // selling entire slot
item->iID = 0;
item->iOpt = 0;
item->iType = 0;
item->iTimeLimit = 0;
}
// response parameters
resp.iInvenSlotNum = req->iInvenSlotNum;
resp.iCandy = plr->money;
resp.Item = original; // the item that gets sent to buyback
resp.ItemStay = *item; // the void item that gets put in the slot
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_VENDOR_ITEM_SELL_SUCC, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_SUCC));
}
void NPCManager::npcVendorBuyback(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_PC_VENDOR_ITEM_RESTORE_BUY))
return; // malformed packet
sP_CL2FE_REQ_PC_VENDOR_ITEM_RESTORE_BUY* req = (sP_CL2FE_REQ_PC_VENDOR_ITEM_RESTORE_BUY*)data->buf;
Player* plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return;
Item* item = ItemManager::getItemData(req->Item.iID, req->Item.iType);
if (item == nullptr) {
std::cout << "[WARN] Item id " << req->Item.iID << " with type " << req->Item.iType << " not found (rebuy)" << std::endl;
// NOTE: VENDOR_ITEM_BUY_FAIL is not actually handled client-side.
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL, failResp);
failResp.iErrorCode = 0;
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL));
return;
}
// sell price is used on rebuy. ternary identifies stacked items
int itemCost = item->sellPrice * (item->stackSize > 1 ? req->Item.iOpt : 1);
int slot = ItemManager::findFreeSlot(plr);
if (itemCost > plr->money || slot == -1) {
// NOTE: VENDOR_ITEM_BUY_FAIL is not actually handled client-side.
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL, failResp);
failResp.iErrorCode = 0;
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_FAIL));
return;
}
if (slot != req->iInvenSlotNum) {
// possible item stacking?
std::cout << "[WARN] Client and server disagree on bought item slot (" << req->iInvenSlotNum << " vs " << slot << ")" << std::endl;
}
plr->money = plr->money - itemCost;
plr->Inven[slot] = req->Item;
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_SUCC, resp);
// response parameters
resp.iCandy = plr->money;
resp.iInvenSlotNum = slot;
resp.Item = req->Item;
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_SUCC, sizeof(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_SUCC));
}
void NPCManager::npcVendorTable(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_PC_VENDOR_TABLE_UPDATE))
return; // malformed packet
sP_CL2FE_REQ_PC_VENDOR_TABLE_UPDATE* req = (sP_CL2FE_REQ_PC_VENDOR_TABLE_UPDATE*)data->buf;
if (req->iVendorID != req->iNPC_ID || ItemManager::VendorTables.find(req->iNPC_ID) == ItemManager::VendorTables.end())
return;
std::vector<VendorListing> listings = ItemManager::VendorTables[req->iNPC_ID]; // maybe use iVendorID instead...?
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_TABLE_UPDATE_SUCC, resp);
for (int i = 0; i < (int)listings.size() && i < 20; i++) { // 20 is the max
sItemBase base;
base.iID = listings[i].iID;
base.iOpt = 0;
base.iTimeLimit = 0;
base.iType = listings[i].type;
sItemVendor vItem;
vItem.item = base;
vItem.iSortNum = listings[i].sort;
vItem.iVendorID = req->iVendorID;
//vItem.fBuyCost = listings[i].price; // this value is not actually the one that is used
resp.item[i] = vItem;
}
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_VENDOR_TABLE_UPDATE_SUCC, sizeof(sP_FE2CL_REP_PC_VENDOR_TABLE_UPDATE_SUCC));
}
void NPCManager::npcVendorStart(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_PC_VENDOR_START))
return; // malformed packet
sP_CL2FE_REQ_PC_VENDOR_START* req = (sP_CL2FE_REQ_PC_VENDOR_START*)data->buf;
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_START_SUCC, resp);
resp.iNPC_ID = req->iNPC_ID;
resp.iVendorID = req->iVendorID;
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_VENDOR_START_SUCC, sizeof(sP_FE2CL_REP_PC_VENDOR_START_SUCC));
}
void NPCManager::npcVendorBuyBattery(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_PC_VENDOR_BATTERY_BUY))
return; // malformed packet
sP_CL2FE_REQ_PC_VENDOR_BATTERY_BUY* req = (sP_CL2FE_REQ_PC_VENDOR_BATTERY_BUY*)data->buf;
Player* plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return;
int cost = req->Item.iOpt * 10;
if ((req->Item.iID == 3 ? (plr->batteryW >= 9999) : (plr->batteryN >= 9999)) || plr->money < cost) { // sanity check
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_BATTERY_BUY_FAIL, failResp);
failResp.iErrorCode = 0;
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_BATTERY_BUY_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_BATTERY_BUY_FAIL));
}
plr->money -= cost;
plr->batteryW += req->Item.iID == 3 ? req->Item.iOpt * 10 : 0;
plr->batteryN += req->Item.iID == 4 ? req->Item.iOpt * 10 : 0;
// caps
if (plr->batteryW > 9999)
plr->batteryW = 9999;
if (plr->batteryN > 9999)
plr->batteryN = 9999;
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_BATTERY_BUY_SUCC, resp);
resp.iCandy = plr->money;
resp.iBatteryW = plr->batteryW;
resp.iBatteryN = plr->batteryN;
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_VENDOR_BATTERY_BUY_SUCC, sizeof(sP_FE2CL_REP_PC_VENDOR_BATTERY_BUY_SUCC));
}
void NPCManager::npcCombineItems(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_PC_ITEM_COMBINATION))
return; // malformed packet
sP_CL2FE_REQ_PC_ITEM_COMBINATION* req = (sP_CL2FE_REQ_PC_ITEM_COMBINATION*)data->buf;
Player* plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return;
if (req->iCostumeItemSlot < 0 || req->iCostumeItemSlot >= AINVEN_COUNT || req->iStatItemSlot < 0 || req->iStatItemSlot >= AINVEN_COUNT) { // sanity check 1
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, failResp);
failResp.iCostumeItemSlot = req->iCostumeItemSlot;
failResp.iStatItemSlot = req->iStatItemSlot;
failResp.iErrorCode = 0;
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, sizeof(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL));
std::cout << "[WARN] Inventory slot(s) out of range (" << req->iStatItemSlot << " and " << req->iCostumeItemSlot << ")" << std::endl;
return;
}
sItemBase* itemStats = &plr->Inven[req->iStatItemSlot];
sItemBase* itemLooks = &plr->Inven[req->iCostumeItemSlot];
Item* itemStatsDat = ItemManager::getItemData(itemStats->iID, itemStats->iType);
Item* itemLooksDat = ItemManager::getItemData(itemLooks->iID, itemLooks->iType);
if (itemStatsDat == nullptr || itemLooksDat == nullptr
|| ItemManager::CrocPotTable.find(abs(itemStatsDat->level - itemLooksDat->level)) == ItemManager::CrocPotTable.end()) { // sanity check 2
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, failResp);
failResp.iCostumeItemSlot = req->iCostumeItemSlot;
failResp.iStatItemSlot = req->iStatItemSlot;
failResp.iErrorCode = 0;
std::cout << "[WARN] Either item ids or croc pot value set not found" << std::endl;
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_ITEM_COMBINATION_FAIL, sizeof(sP_FE2CL_REP_PC_ITEM_COMBINATION_FAIL));
return;
}
CrocPotEntry* recipe = &ItemManager::CrocPotTable[abs(itemStatsDat->level - itemLooksDat->level)];
int cost = itemStatsDat->buyPrice * recipe->multStats + itemLooksDat->buyPrice * recipe->multLooks;
float successChance = recipe->base / 100.0f; // base success chance
// rarity gap multiplier
switch(abs(itemStatsDat->rarity - itemLooksDat->rarity)) {
case 0:
successChance *= recipe->rd0;
break;
case 1:
successChance *= recipe->rd1;
break;
case 2:
successChance *= recipe->rd2;
break;
case 3:
successChance *= recipe->rd3;
break;
default:
break;
}
float rolled = (rand() * 1.0f / RAND_MAX) * 100.0f; // success chance out of 100
//std::cout << rolled << " vs " << successChance << std::endl;
plr->money -= cost;
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_COMBINATION_SUCC, resp);
if (rolled < successChance) {
// success
resp.iSuccessFlag = 1;
// modify the looks item with the new stats and set the appearance through iOpt
itemLooks->iOpt = (int32_t)((itemLooks->iOpt) >> 16 > 0 ? (itemLooks->iOpt >> 16) : itemLooks->iID) << 16;
itemLooks->iID = itemStats->iID;
// delete stats item
itemStats->iID = 0;
itemStats->iOpt = 0;
itemStats->iTimeLimit = 0;
itemStats->iType = 0;
} else {
// failure; don't do anything?
resp.iSuccessFlag = 0;
}
resp.iCandy = plr->money;
resp.iNewItemSlot = req->iCostumeItemSlot;
resp.iStatItemSlot = req->iStatItemSlot;
resp.sNewItem = *itemLooks;
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_ITEM_COMBINATION_SUCC, sizeof(sP_FE2CL_REP_PC_ITEM_COMBINATION_SUCC));
}
void NPCManager::npcBarkHandler(CNSocket* sock, CNPacketData* data) {} // stubbed for now
void NPCManager::npcUnsummonHandler(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_NPC_UNSUMMON))
return; // malformed packet
Player* plr = PlayerManager::getPlayer(sock);
if (plr == nullptr || plr->accountLevel > 30)
return;
sP_CL2FE_REQ_NPC_UNSUMMON* req = (sP_CL2FE_REQ_NPC_UNSUMMON*)data->buf;
NPCManager::destroyNPC(req->iNPC_ID);
}
void NPCManager::npcSummonHandler(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_NPC_SUMMON))
return; // malformed packet
sP_CL2FE_REQ_NPC_SUMMON* req = (sP_CL2FE_REQ_NPC_SUMMON*)data->buf;
INITSTRUCT(sP_FE2CL_NPC_ENTER, resp);
Player* plr = PlayerManager::getPlayer(sock);
// permission & sanity check
if (!plr->IsGM || req->iNPCType >= 3314)
if (plr == nullptr || plr->accountLevel > 30 || req->iNPCType >= 3314 || req->iNPCCnt > 100)
return;
resp.NPCAppearanceData.iNPC_ID = rand(); // cpunch-style
resp.NPCAppearanceData.iNPCType = req->iNPCType;
resp.NPCAppearanceData.iHP = 1000; // TODO: placeholder
resp.NPCAppearanceData.iX = plr->x;
resp.NPCAppearanceData.iY = plr->y;
resp.NPCAppearanceData.iZ = plr->z;
int team = NPCData[req->iNPCType]["m_iTeam"];
sock->sendPacket((void*)&resp, P_FE2CL_NPC_ENTER, sizeof(sP_FE2CL_NPC_ENTER));
for (int i = 0; i < req->iNPCCnt; i++) {
assert(nextId < INT32_MAX);
int id = nextId++;
if (team == 2) {
NPCs[id] = new Mob(plr->x, plr->y, plr->z, plr->instanceID, req->iNPCType, NPCData[req->iNPCType], id);
MobManager::Mobs[id] = (Mob*)NPCs[id];
} else
NPCs[id] = new BaseNPC(plr->x, plr->y, plr->z, 0, plr->instanceID, req->iNPCType, id);
updateNPCPosition(id, plr->x, plr->y, plr->z);
}
}
void NPCManager::npcWarpHandler(CNSocket* sock, CNPacketData* data) {
@@ -173,21 +570,77 @@ void NPCManager::npcWarpHandler(CNSocket* sock, CNPacketData* data) {
return; // malformed packet
sP_CL2FE_REQ_PC_WARP_USE_NPC* warpNpc = (sP_CL2FE_REQ_PC_WARP_USE_NPC*)data->buf;
PlayerView& plrv = PlayerManager::players[sock];
handleWarp(sock, warpNpc->iWarpID);
}
void NPCManager::npcWarpTimeMachine(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_PC_TIME_TO_GO_WARP))
return; // malformed packet
// this is just a warp request
handleWarp(sock, 28);
}
void NPCManager::handleWarp(CNSocket* sock, int32_t warpId) {
PlayerView& plrv = PlayerManager::players[sock];
// sanity check
if (Warps.find(warpNpc->iWarpID) == Warps.end())
if (Warps.find(warpId) == Warps.end())
return;
// send to client
INITSTRUCT(sP_FE2CL_REP_PC_WARP_USE_NPC_SUCC, resp);
resp.iX = Warps[warpNpc->iWarpID].x;
resp.iY = Warps[warpNpc->iWarpID].y;
resp.iZ = Warps[warpNpc->iWarpID].z;
MissionManager::failInstancedMissions(sock); // fail any missions that require the player's current instance
// force player & NPC reload
plrv.viewable.clear();
plrv.viewableNPCs.clear();
uint64_t fromInstance = plrv.plr->instanceID; // saved for post-warp
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_WARP_USE_NPC_SUCC, sizeof(sP_FE2CL_REP_PC_WARP_USE_NPC_SUCC));
}
if (plrv.plr->instanceID == 0) {
// save last uninstanced coords
plrv.plr->lastX = plrv.plr->x;
plrv.plr->lastY = plrv.plr->y;
plrv.plr->lastZ = plrv.plr->z;
plrv.plr->lastAngle = plrv.plr->angle;
}
// std::cerr << "Warped to Map Num:" << Warps[warpId].instanceID << " NPC ID " << Warps[warpId].npcID << std::endl;
if (Warps[warpId].isInstance) {
uint64_t instanceID = Warps[warpId].instanceID;
if (Warps[warpId].limitTaskID != 0) { // if warp requires you to be on a mission, it's gotta be a unique instance
instanceID += ((uint64_t)plrv.plr->iIDGroup << 32); // upper 32 bits are leader ID
ChunkManager::createInstance(instanceID);
}
PlayerManager::sendPlayerTo(sock, Warps[warpId].x, Warps[warpId].y, Warps[warpId].z, instanceID);
} else {
INITSTRUCT(sP_FE2CL_REP_PC_WARP_USE_NPC_SUCC, resp); // Can only be used for exiting instances because it sets the instance flag to false
resp.iX = Warps[warpId].x;
resp.iY = Warps[warpId].y;
resp.iZ = Warps[warpId].z;
resp.iCandy = plrv.plr->money;
resp.eIL = 4; // do not take away any items
PlayerManager::removePlayerFromChunks(plrv.currentChunks, sock);
plrv.currentChunks.clear();
plrv.plr->instanceID = INSTANCE_OVERWORLD;
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_WARP_USE_NPC_SUCC, sizeof(sP_FE2CL_REP_PC_WARP_USE_NPC_SUCC));
}
// post-warp: check if the source instance has no more players in it and delete it if so
ChunkManager::destroyInstanceIfEmpty(fromInstance);
}
/*
* Helper function to get NPC closest to coordinates in specified chunks
*/
BaseNPC* NPCManager::getNearestNPC(std::vector<Chunk*> chunks, int X, int Y, int Z) {
BaseNPC* npc = nullptr;
int lastDist = INT_MAX;
for (auto c = chunks.begin(); c != chunks.end(); c++) { // haha get it
Chunk* chunk = *c;
for (auto _npc = chunk->NPCs.begin(); _npc != chunk->NPCs.end(); _npc++) {
BaseNPC* npcTemp = NPCs[*_npc];
int distXY = std::hypot(X - npcTemp->appearanceData.iX, Y - npcTemp->appearanceData.iY);
int dist = std::hypot(distXY, Z - npcTemp->appearanceData.iZ);
if (dist < lastDist) {
npc = npcTemp;
lastDist = dist;
}
}
}
return npc;
}

View File

@@ -4,6 +4,8 @@
#include "PlayerManager.hpp"
#include "NPC.hpp"
#include "contrib/JSON.hpp"
#include <map>
#include <vector>
@@ -11,18 +13,41 @@
// this should really be called vec3 or something...
struct WarpLocation {
int x, y, z;
int x, y, z, instanceID, isInstance, limitTaskID, npcID;
};
namespace NPCManager {
extern std::map<int32_t, BaseNPC> NPCs;
extern std::map<int32_t, BaseNPC*> NPCs;
extern std::map<int32_t, WarpLocation> Warps;
extern std::vector<WarpLocation> RespawnPoints;
extern nlohmann::json NPCData;
extern int32_t nextId;
void init();
void addNPC(std::vector<Chunk*> viewableChunks, int32_t id);
void removeNPC(std::vector<Chunk*> viewableChunks, int32_t id);
void destroyNPC(int32_t);
void updateNPCPosition(int32_t, int X, int Y, int Z, int angle);
void updateNPCPosition(int32_t, int X, int Y, int Z);
void updateNPCInstance(int32_t, uint64_t instanceID);
void sendToViewable(BaseNPC* npc, void* buf, uint32_t type, size_t size);
void npcBarkHandler(CNSocket* sock, CNPacketData* data);
void npcSummonHandler(CNSocket* sock, CNPacketData* data);
void npcUnsummonHandler(CNSocket* sock, CNPacketData* data);
void npcWarpHandler(CNSocket* sock, CNPacketData* data);
void npcWarpTimeMachine(CNSocket* sock, CNPacketData* data);
void updatePlayerNPCS(CNSocket* sock, PlayerView& plr);
void npcVendorStart(CNSocket* sock, CNPacketData* data);
void npcVendorTable(CNSocket* sock, CNPacketData* data);
void npcVendorBuy(CNSocket* sock, CNPacketData* data);
void npcVendorSell(CNSocket* sock, CNPacketData* data);
void npcVendorBuyback(CNSocket* sock, CNPacketData* data);
void npcVendorBuyBattery(CNSocket* sock, CNPacketData* data);
void npcCombineItems(CNSocket* sock, CNPacketData* data);
void handleWarp(CNSocket* sock, int32_t warpId);
BaseNPC* getNearestNPC(std::vector<Chunk*> chunks, int X, int Y, int Z);
}

View File

@@ -2,6 +2,50 @@
#include "CNStructs.hpp"
#include "NanoManager.hpp"
#include "PlayerManager.hpp"
#include "NPCManager.hpp"
#include "MobManager.hpp"
#include "MissionManager.hpp"
#include "GroupManager.hpp"
namespace NanoManager {
// active powers
std::set<int> StunPowers = {1, 13, 42, 59, 78, 103};
std::set<int> HealPowers = {7, 12, 38, 53, 92, 98};
std::set<int> GroupHealPowers = {2, 61, 82};
std::set<int> RecallPowers = {5, 25, 66, 69, 75, 87};
std::set<int> DrainPowers = {10, 34, 37, 56, 93, 97};
std::set<int> SnarePowers = {17, 18, 27, 41, 43, 47, 90, 96, 106};
std::set<int> DamagePowers = {19, 21, 33, 45, 46, 52, 101, 105, 108};
std::set<int> GroupRevivePowers = {20, 63, 91};
std::set<int> LeechPowers = {24, 51, 89};
std::set<int> SleepPowers = {28, 30, 32, 49, 70, 71, 81, 85, 94};
// passive powers
std::set<int> ScavangePowers = {3, 50, 99};
std::set<int> RunPowers = {4, 68, 86};
std::set<int> GroupRunPowers = {8, 62, 73};
std::set<int> BonusPowers = {6, 54, 104};
std::set<int> GuardPowers = {9, 57, 76};
std::set<int> RadarPowers = {11, 67, 95};
std::set<int> AntidotePowers = {14, 58, 102};
std::set<int> FreedomPowers = {31, 39, 107};
std::set<int> GroupFreedomPowers = {15, 55, 77};
std::set<int> JumpPowers = {16, 44, 88};
std::set<int> GroupJumpPowers = {35, 60, 100};
std::set<int> SelfRevivePowers = {22, 48, 83};
std::set<int> SneakPowers = {29, 72, 80};
std::set<int> GroupSneakPowers = {23, 65, 84};
std::set<int> TreasureFinderPowers = {26, 40, 74};
/*
* The active nano power table is down below activePower<>() and its
* worker functions so we don't have to have unsightly function declarations.
*/
}; // namespace
std::map<int32_t, NanoData> NanoManager::NanoTable;
void NanoManager::init() {
REGISTER_SHARD_PACKET(P_CL2FE_REQ_NANO_ACTIVE, nanoSummonHandler);
@@ -9,7 +53,10 @@ void NanoManager::init() {
REGISTER_SHARD_PACKET(P_CL2FE_REQ_NANO_UNEQUIP, nanoUnEquipHandler);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GIVE_NANO, nanoGMGiveHandler);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_NANO_TUNE, nanoSkillSetHandler);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GIVE_NANO_SKILL, nanoSkillSetGMHandler);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_NANO_SKILL_USE, nanoSkillUseHandler);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_WARP_USE_RECALL, nanoRecallHandler);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_CHARGE_NANO_STAMINA, nanoPotionHandler);
}
void NanoManager::nanoEquipHandler(CNSocket* sock, CNPacketData* data) {
@@ -20,8 +67,8 @@ void NanoManager::nanoEquipHandler(CNSocket* sock, CNPacketData* data) {
INITSTRUCT(sP_FE2CL_REP_NANO_EQUIP_SUCC, resp);
Player *plr = PlayerManager::getPlayer(sock);
// sanity check
if (nano->iNanoSlotNum > 2 || nano->iNanoSlotNum < 0)
// sanity checks
if (plr == nullptr || nano->iNanoSlotNum > 2 || nano->iNanoSlotNum < 0)
return;
resp.iNanoID = nano->iNanoID;
@@ -46,7 +93,7 @@ void NanoManager::nanoUnEquipHandler(CNSocket* sock, CNPacketData* data) {
Player *plr = PlayerManager::getPlayer(sock);
// sanity check
if (nano->iNanoSlotNum > 2 || nano->iNanoSlotNum < 0)
if (plr == nullptr || nano->iNanoSlotNum > 2 || nano->iNanoSlotNum < 0)
return;
resp.iNanoSlotNum = nano->iNanoSlotNum;
@@ -69,6 +116,9 @@ void NanoManager::nanoGMGiveHandler(CNSocket* sock, CNPacketData* data) {
sP_CL2FE_REQ_PC_GIVE_NANO* nano = (sP_CL2FE_REQ_PC_GIVE_NANO*)data->buf;
Player *plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return;
// Add nano to player
addNano(sock, nano->iNanoID, 0);
@@ -84,6 +134,9 @@ void NanoManager::nanoSummonHandler(CNSocket* sock, CNPacketData* data) {
sP_CL2FE_REQ_NANO_ACTIVE* pkt = (sP_CL2FE_REQ_NANO_ACTIVE*)data->buf;
Player *plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return;
summonNano(sock, pkt->iNanoSlotNum);
// Send to client
@@ -93,27 +146,40 @@ void NanoManager::nanoSummonHandler(CNSocket* sock, CNPacketData* data) {
}
void NanoManager::nanoSkillUseHandler(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_NANO_SKILL_USE))
return; // malformed packet
sP_CL2FE_REQ_NANO_SKILL_USE* skill = (sP_CL2FE_REQ_NANO_SKILL_USE*)data->buf;
Player *plr = PlayerManager::getPlayer(sock);
// Send to client
INITSTRUCT(sP_FE2CL_NANO_SKILL_USE_SUCC, resp);
resp.iArg1 = skill->iArg1;
resp.iArg2 = skill->iArg2;
resp.iArg3 = skill->iArg3;
resp.iBulletID = skill->iBulletID;
resp.iTargetCnt = skill->iTargetCnt;
resp.iPC_ID = plr->iID;
resp.iNanoStamina = 150; // Hardcoded for now
if (plr == nullptr)
return;
sock->sendPacket((void*)&resp, P_FE2CL_NANO_SKILL_USE_SUCC, sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC));
int16_t nanoId = plr->activeNano;
int16_t skillId = plr->Nanos[nanoId].iSkillID;
DEBUGLOG(
std::cout << U16toU8(plr->PCStyle.szFirstName) << U16toU8(plr->PCStyle.szLastName) << " requested to summon nano skill " << std::endl;
)
for (auto& pwr : ActivePowers)
if (pwr.powers.count(skillId)) // std::set's contains method is C++20 only...
pwr.handle(sock, data, nanoId, skillId);
// Group Revive is handled separately (XXX: move into table?)
if (GroupRevivePowers.find(skillId) == GroupRevivePowers.end())
return;
Player *leader = PlayerManager::getPlayerFromID(plr->iIDGroup);
if (leader == nullptr)
return;
for (int i = 0; i < leader->groupCnt; i++) {
Player* varPlr = PlayerManager::getPlayerFromID(leader->groupIDs[i]);
if (varPlr == nullptr)
return;
if (varPlr->HP <= 0)
revivePlayer(varPlr);
}
}
void NanoManager::nanoSkillSetHandler(CNSocket* sock, CNPacketData* data) {
@@ -124,51 +190,158 @@ void NanoManager::nanoSkillSetHandler(CNSocket* sock, CNPacketData* data) {
setNanoSkill(sock, skill->iNanoID, skill->iTuneID);
}
void NanoManager::nanoSkillSetGMHandler(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_PC_GIVE_NANO_SKILL))
return; // malformed packet
sP_CL2FE_REQ_NANO_TUNE* skillGM = (sP_CL2FE_REQ_NANO_TUNE*)data->buf;
setNanoSkill(sock, skillGM->iNanoID, skillGM->iTuneID);
}
void NanoManager::nanoRecallHandler(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_WARP_USE_RECALL))
return;
INITSTRUCT(sP_FE2CL_REP_WARP_USE_RECALL_FAIL, resp);
sock->sendPacket((void*)&resp, P_FE2CL_REP_WARP_USE_RECALL_FAIL, sizeof(sP_FE2CL_REP_WARP_USE_RECALL_FAIL));
// stubbed for now
}
void NanoManager::nanoPotionHandler(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_CHARGE_NANO_STAMINA))
return;
Player* player = PlayerManager::getPlayer(sock);
// sanity checks
if (player == nullptr || player->activeNano == -1 || player->batteryN == 0)
return;
sNano nano = player->Nanos[player->activeNano];
int difference = 150 - nano.iStamina;
if (player->batteryN < difference)
difference = player->batteryN;
if (difference == 0)
return;
INITSTRUCT(sP_FE2CL_REP_CHARGE_NANO_STAMINA, response);
response.iNanoID = nano.iID;
response.iNanoStamina = nano.iStamina + difference;
response.iBatteryN = player->batteryN - difference;
sock->sendPacket((void*)&response, P_FE2CL_REP_CHARGE_NANO_STAMINA, sizeof(sP_FE2CL_REP_CHARGE_NANO_STAMINA));
// now update serverside
player->batteryN -= difference;
player->Nanos[nano.iID].iStamina += difference;
}
#pragma region Helper methods
void NanoManager::addNano(CNSocket* sock, int16_t nanoId, int16_t slot) {
void NanoManager::addNano(CNSocket* sock, int16_t nanoId, int16_t slot, bool spendfm) {
if (nanoId > 36)
return;
Player *plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return;
int level = nanoId < plr->level ? plr->level : nanoId;
/*
* Spend the necessary Fusion Matter.
* Note the use of the not-yet-incremented plr->level as opposed to level.
* Doing it the other way always leaves the FM at 0. Jade totally called it.
*/
plr->level = level;
if (spendfm)
MissionManager::updateFusionMatter(sock, -(int)MissionManager::AvatarGrowth[plr->level-1]["m_iReqBlob_NanoCreate"]);
// Send to client
INITSTRUCT(sP_FE2CL_REP_PC_NANO_CREATE_SUCC, resp);
resp.Nano.iID = nanoId;
resp.Nano.iStamina = 150;
resp.iQuestItemSlotNum = slot;
resp.iPC_Level = level;
resp.iPC_FusionMatter = plr->fusionmatter;
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_NANO_CREATE_SUCC, sizeof(sP_FE2CL_REP_PC_NANO_CREATE_SUCC));
if (plr->activeNano > 0 && plr->activeNano == nanoId)
summonNano(sock, -1); // just unsummon the nano to prevent infinite buffs
// Update player
plr->Nanos[nanoId] = resp.Nano;
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_NANO_CREATE_SUCC, sizeof(sP_FE2CL_REP_PC_NANO_CREATE_SUCC));
/*
* iPC_Level in NANO_CREATE_SUCC sets the player's level.
* Other players must be notified of the change as well. Both P_FE2CL_REP_PC_NANO_CREATE and
* P_FE2CL_REP_PC_CHANGE_LEVEL appear to play the same animation, but only the latter affects
* the other player's displayed level.
*/
INITSTRUCT(sP_FE2CL_REP_PC_CHANGE_LEVEL, resp2);
resp2.iPC_ID = plr->iID;
resp2.iPC_Level = level;
// Update other players' perception of the player's level
PlayerManager::sendToViewable(sock, (void*)&resp2, P_FE2CL_REP_PC_CHANGE_LEVEL, sizeof(sP_FE2CL_REP_PC_CHANGE_LEVEL));
}
void NanoManager::summonNano(CNSocket *sock, int slot) {
INITSTRUCT(sP_FE2CL_REP_NANO_ACTIVE_SUCC, resp);
resp.iActiveNanoSlotNum = slot;
sock->sendPacket((void*)&resp, P_FE2CL_REP_NANO_ACTIVE_SUCC, sizeof(sP_FE2CL_REP_NANO_ACTIVE_SUCC));
Player *plr = PlayerManager::getPlayer(sock);
std::cout << "summon nano\n";
if (slot > 2 || slot < 0)
return; //sanity check
int nanoId = plr->equippedNanos[slot];
if (nanoId > 36 || nanoId < 0)
if (plr == nullptr || slot > 2 || slot < -1)
return; // sanity check
int16_t nanoId = slot == -1 ? -1 : plr->equippedNanos[slot];
if (nanoId > 36 || nanoId < -1)
return; // sanity check
int16_t skillId = 0;
if (plr->activeNano > 0)
for (auto& pwr : PassivePowers)
if (pwr.powers.count(plr->Nanos[plr->activeNano].iSkillID)) { // std::set's contains method is C++20 only...
nanoUnbuff(sock, pwr.iCBFlag, pwr.eCharStatusTimeBuffID, pwr.iValue, pwr.groupPower);
plr->passiveNanoOut = false;
}
sNano nano = plr->Nanos[nanoId];
skillId = nano.iSkillID;
if (slot > -1) {
plr->activeNano = nanoId;
for (auto& pwr : PassivePowers)
if (pwr.powers.count(skillId)) { // std::set's contains method is C++20 only...
resp.eCSTB___Add = 1;
nanoBuff(sock, nanoId, skillId, pwr.eSkillType, pwr.iCBFlag, pwr.eCharStatusTimeBuffID, pwr.iValue, pwr.groupPower);
plr->passiveNanoOut = true;
}
} else
plr->activeNano = 0;
sock->sendPacket((void*)&resp, P_FE2CL_REP_NANO_ACTIVE_SUCC, sizeof(sP_FE2CL_REP_NANO_ACTIVE_SUCC));
// Send to other players
INITSTRUCT(sP_FE2CL_NANO_ACTIVE, pkt1);
pkt1.iPC_ID = plr->iID;
pkt1.Nano = nano;
for (CNSocket* s : PlayerManager::players[sock].viewable)
s->sendPacket((void*)&pkt1, P_FE2CL_NANO_ACTIVE, sizeof(sP_FE2CL_NANO_ACTIVE));
if (nanoId == -1)
memset(&pkt1.Nano, 0, sizeof(pkt1.Nano));
else
pkt1.Nano = plr->Nanos[nanoId];
PlayerManager::sendToViewable(sock, (void*)&pkt1, P_FE2CL_NANO_ACTIVE, sizeof(sP_FE2CL_NANO_ACTIVE));
// update player
plr->activeNano = nanoId;
@@ -179,6 +352,13 @@ void NanoManager::setNanoSkill(CNSocket* sock, int16_t nanoId, int16_t skillId)
return;
Player *plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return;
if (plr->activeNano > 0 && plr->activeNano == nanoId)
summonNano(sock, -1); // just unsummon the nano to prevent infinite buffs
sNano nano = plr->Nanos[nanoId];
nano.iSkillID = skillId;
@@ -188,6 +368,8 @@ void NanoManager::setNanoSkill(CNSocket* sock, int16_t nanoId, int16_t skillId)
INITSTRUCT(sP_FE2CL_REP_NANO_TUNE_SUCC, resp);
resp.iNanoID = nanoId;
resp.iSkillID = skillId;
resp.iPC_FusionMatter = plr->fusionmatter;
resp.aItem[9] = plr->Inven[0]; // temp fix for a bug TODO: Use this for nano power changing later
sock->sendPacket((void*)&resp, P_FE2CL_REP_NANO_TUNE_SUCC, sizeof(sP_FE2CL_REP_NANO_TUNE_SUCC));
@@ -201,6 +383,10 @@ void NanoManager::resetNanoSkill(CNSocket* sock, int16_t nanoId) {
return;
Player *plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return;
sNano nano = plr->Nanos[nanoId];
// 0 is reset
@@ -208,3 +394,534 @@ void NanoManager::resetNanoSkill(CNSocket* sock, int16_t nanoId) {
plr->Nanos[nanoId] = nano;
}
#pragma endregion
#pragma region Active Powers
namespace NanoManager {
bool doDebuff(CNSocket *sock, int32_t *pktdata, sSkillResult_Damage_N_Debuff *respdata, int i, int32_t iCBFlag, int32_t amount) {
if (MobManager::Mobs.find(pktdata[i]) == MobManager::Mobs.end()) {
// not sure how to best handle this
std::cout << "[WARN] nanoDebuffEnemy: mob ID not found" << std::endl;
return false;
}
Mob* mob = MobManager::Mobs[pktdata[i]];
int damage = MobManager::hitMob(sock, mob, amount);
respdata[i].eCT = 4;
respdata[i].iDamage = damage;
respdata[i].iID = mob->appearanceData.iNPC_ID;
respdata[i].iHP = mob->appearanceData.iHP;
respdata[i].iConditionBitFlag = mob->appearanceData.iConditionBitFlag |= iCBFlag;
std::cout << (int)mob->appearanceData.iNPC_ID << " was debuffed" << std::endl;
return true;
}
bool doBuff(CNSocket *sock, int32_t *pktdata, sSkillResult_Buff *respdata, int i, int32_t iCBFlag, int32_t amount) {
if (MobManager::Mobs.find(pktdata[i]) == MobManager::Mobs.end()) {
// not sure how to best handle this
std::cout << "[WARN] nanoBuffEnemy: mob ID not found" << std::endl;
return false;
}
Mob* mob = MobManager::Mobs[pktdata[i]];
respdata[i].eCT = 4;
respdata[i].iID = mob->appearanceData.iNPC_ID;
respdata[i].iConditionBitFlag = mob->appearanceData.iConditionBitFlag |= iCBFlag;
std::cout << (int)mob->appearanceData.iNPC_ID << " was debuffed" << std::endl;
return true;
}
bool doHeal(CNSocket *sock, int32_t *pktdata, sSkillResult_Heal_HP *respdata, int i, int32_t iCBFlag, int32_t amount) {
Player *plr = nullptr;
for (auto& pair : PlayerManager::players) {
if (pair.second.plr->iID == pktdata[i]) {
plr = pair.second.plr;
break;
}
}
// player not found
if (plr == nullptr)
return false;
int healedAmount = PC_MAXHEALTH(plr->level) * amount / 100;
plr->HP += healedAmount;
if (plr->HP > PC_MAXHEALTH(plr->level))
plr->HP = PC_MAXHEALTH(plr->level);
respdata[i].eCT = 1;
respdata[i].iID = plr->iID;
respdata[i].iHP = plr->HP;
respdata[i].iHealHP = healedAmount;
std::cout << (int)plr->iID << " was healed" << std::endl;
return true;
}
bool doGroupHeal(CNSocket *sock, int32_t *pktdata, sSkillResult_Heal_HP *respdata, int i, int32_t iCBFlag, int32_t amount) {
Player *plr = nullptr;
for (auto& pair : PlayerManager::players) {
if (pair.second.plr->iID == pktdata[0]) {
plr = pair.second.plr;
break;
}
}
// player not found
if (plr == nullptr)
return false;
Player *leader = PlayerManager::getPlayer(sock);
// player not found
if (leader == nullptr)
return false;
int healedAmount = PC_MAXHEALTH(plr->level) * amount / 100;
leader->HP += healedAmount;
if (leader->HP > PC_MAXHEALTH(leader->level))
leader->HP = PC_MAXHEALTH(leader->level);
respdata[i].eCT = 1;
respdata[i].iID = plr->iID;
respdata[i].iHP = plr->HP;
respdata[i].iHealHP = healedAmount;
std::cout << (int)plr->iID << " was healed" << std::endl;
return true;
}
bool doDamage(CNSocket *sock, int32_t *pktdata, sSkillResult_Damage *respdata, int i, int32_t iCBFlag, int32_t amount) {
if (MobManager::Mobs.find(pktdata[i]) == MobManager::Mobs.end()) {
// not sure how to best handle this
std::cout << "[WARN] nanoDebuffEnemy: mob ID not found" << std::endl;
return false;
}
Mob* mob = MobManager::Mobs[pktdata[i]];
Player *plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return false;
int damage = MobManager::hitMob(sock, mob, PC_MAXHEALTH(plr->level) * amount / 100);
respdata[i].eCT = 4;
respdata[i].iDamage = damage;
respdata[i].iID = mob->appearanceData.iNPC_ID;
respdata[i].iHP = mob->appearanceData.iHP;
std::cout << (int)mob->appearanceData.iNPC_ID << " was damaged" << std::endl;
return true;
}
/*
* NOTE: Leech is specially encoded.
*
* It manages to fit inside the activePower<>() mold with only a slight hack,
* but it really is it's own thing. There is a hard assumption that players
* will only every leech a single mob, and the sanity check that enforces that
* assumption is critical.
*/
bool doLeech(CNSocket *sock, int32_t *pktdata, sSkillResult_Heal_HP *healdata, int i, int32_t iCBFlag, int32_t amount) {
// this sanity check is VERY important
if (i != 0) {
std::cout << "[WARN] Player attempted to leech more than one mob!" << std::endl;
return false;
}
sSkillResult_Damage *damagedata = (sSkillResult_Damage*)(((uint8_t*)healdata) + sizeof(sSkillResult_Heal_HP));
Player *plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return false;
int healedAmount = PC_MAXHEALTH(plr->level) * amount / 100;
plr->HP += healedAmount;
if (plr->HP > PC_MAXHEALTH(plr->level))
plr->HP = PC_MAXHEALTH(plr->level);
healdata->eCT = 1;
healdata->iID = plr->iID;
healdata->iHP = plr->HP;
healdata->iHealHP = healedAmount;
if (MobManager::Mobs.find(pktdata[i]) == MobManager::Mobs.end()) {
// not sure how to best handle this
std::cout << "[WARN] doLeech: mob ID not found" << std::endl;
return false;
}
Mob* mob = MobManager::Mobs[pktdata[i]];
int damage = MobManager::hitMob(sock, mob, PC_MAXHEALTH(plr->level) * amount / 100);
damagedata->eCT = 4;
damagedata->iDamage = damage;
damagedata->iID = mob->appearanceData.iNPC_ID;
damagedata->iHP = mob->appearanceData.iHP;
std::cout << (int)mob->appearanceData.iNPC_ID << " was leeched" << std::endl;
return true;
}
// XXX: Special flags. This is still pretty dirty.
enum {
NONE,
LEECH,
GHEAL
};
template<class sPAYLOAD,
bool (*work)(CNSocket*,int32_t*,sPAYLOAD*,int,int32_t,int32_t),
int specialCase=NONE>
void activePower(CNSocket *sock, CNPacketData *data,
int16_t nanoId, int16_t skillId, int16_t eSkillType,
int32_t iCBFlag, int32_t amount) {
sP_CL2FE_REQ_NANO_SKILL_USE* pkt = (sP_CL2FE_REQ_NANO_SKILL_USE*)data->buf;
// validate request check
if (!validInVarPacket(sizeof(sP_CL2FE_REQ_NANO_SKILL_USE), pkt->iTargetCnt, sizeof(int32_t), data->size)) {
std::cout << "[WARN] bad sP_CL2FE_REQ_NANO_SKILL_USE packet size" << std::endl;
return;
}
int32_t *pktdata = (int32_t*)((uint8_t*)data->buf + sizeof(sP_CL2FE_REQ_NANO_SKILL_USE));
size_t resplen;
Player *plr = PlayerManager::getPlayer(sock);
Player *otherPlr = plr;
if (plr == nullptr)
return;
// special case since leech is atypically encoded
if constexpr (specialCase == LEECH)
resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC) + sizeof(sSkillResult_Heal_HP) + sizeof(sSkillResult_Damage);
else if constexpr (specialCase == GHEAL) {
otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
if (otherPlr == nullptr)
return;
pkt->iTargetCnt = otherPlr->groupCnt;
resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC) + pkt->iTargetCnt * sizeof(sPAYLOAD);
} else
resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC) + pkt->iTargetCnt * sizeof(sPAYLOAD);
// validate response packet
if (!validOutVarPacket(sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC), pkt->iTargetCnt, sizeof(sPAYLOAD))) {
std::cout << "[WARN] bad sP_FE2CL_NANO_SKILL_USE packet size" << std::endl;
return;
}
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
memset(respbuf, 0, resplen);
sP_FE2CL_NANO_SKILL_USE_SUCC *resp = (sP_FE2CL_NANO_SKILL_USE_SUCC*)respbuf;
sPAYLOAD *respdata = (sPAYLOAD*)(respbuf+sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC));
plr->Nanos[plr->activeNano].iStamina -= 40;
if (plr->Nanos[plr->activeNano].iStamina < 0)
plr->Nanos[plr->activeNano].iStamina = 0;
resp->iPC_ID = plr->iID;
resp->iSkillID = skillId;
resp->iNanoID = nanoId;
resp->iNanoStamina = plr->Nanos[plr->activeNano].iStamina;
resp->eST = eSkillType;
resp->iTargetCnt = pkt->iTargetCnt;
CNSocket *workSock = sock;
for (int i = 0; i < pkt->iTargetCnt; i++) {
if constexpr (specialCase == GHEAL)
workSock = PlayerManager::getSockFromID(otherPlr->groupIDs[i]);
if (!work(workSock, pktdata, respdata, i, iCBFlag, amount))
return;
}
sock->sendPacket((void*)&respbuf, P_FE2CL_NANO_SKILL_USE_SUCC, resplen);
PlayerManager::sendToViewable(sock, (void*)&respbuf, P_FE2CL_NANO_SKILL_USE, resplen);
}
// active nano power dispatch table
std::vector<ActivePower> ActivePowers = {
ActivePower(StunPowers, activePower<sSkillResult_Damage_N_Debuff, doDebuff>, EST_STUN, CSB_BIT_STUN, 0),
ActivePower(HealPowers, activePower<sSkillResult_Heal_HP, doHeal>, EST_HEAL_HP, CSB_BIT_NONE, 25),
ActivePower(GroupHealPowers, activePower<sSkillResult_Heal_HP, doGroupHeal, GHEAL>,EST_HEAL_HP, CSB_BIT_NONE, 25),
// TODO: Recall
ActivePower(DrainPowers, activePower<sSkillResult_Buff, doBuff>, EST_BOUNDINGBALL, CSB_BIT_BOUNDINGBALL, 0),
ActivePower(SnarePowers, activePower<sSkillResult_Damage_N_Debuff, doDebuff>, EST_SNARE, CSB_BIT_DN_MOVE_SPEED, 0),
ActivePower(DamagePowers, activePower<sSkillResult_Damage, doDamage>, EST_DAMAGE, CSB_BIT_NONE, 12),
ActivePower(LeechPowers, activePower<sSkillResult_Heal_HP, doLeech, LEECH>, EST_BLOODSUCKING, CSB_BIT_NONE, 18),
ActivePower(SleepPowers, activePower<sSkillResult_Damage_N_Debuff, doDebuff>, EST_SLEEP, CSB_BIT_MEZ, 0),
};
}; // namespace
#pragma endregion
#pragma region Passive Powers
void NanoManager::nanoBuff(CNSocket* sock, int16_t nanoId, int skillId, int16_t eSkillType, int32_t iCBFlag, int16_t eCharStatusTimeBuffID, int16_t iValue, bool groupPower) {
Player *plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return;
int pktCnt = 1;
Player *leader = plr;
plr->iConditionBitFlag |= iCBFlag;
if (groupPower) {
plr->iGroupConditionBitFlag |= iCBFlag;
if (plr->iID != plr->iIDGroup)
leader = PlayerManager::getPlayerFromID(plr->iIDGroup);
if (leader == nullptr)
return;
pktCnt = leader->groupCnt;
}
if (leader == nullptr)
return;
if (!validOutVarPacket(sizeof(sP_FE2CL_NANO_SKILL_USE), pktCnt, sizeof(sSkillResult_Buff))) {
std::cout << "[WARN] bad sP_FE2CL_NANO_SKILL_USE packet size\n";
return;
}
size_t resplen = sizeof(sP_FE2CL_NANO_SKILL_USE) + pktCnt * sizeof(sSkillResult_Buff);
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
memset(respbuf, 0, resplen);
sP_FE2CL_NANO_SKILL_USE *resp = (sP_FE2CL_NANO_SKILL_USE*)respbuf;
sSkillResult_Buff *respdata = (sSkillResult_Buff*)(respbuf+sizeof(sP_FE2CL_NANO_SKILL_USE));
resp->iPC_ID = plr->iID;
resp->iSkillID = skillId;
resp->iNanoID = nanoId;
resp->iNanoStamina = plr->Nanos[plr->activeNano].iStamina;
resp->eST = eSkillType;
resp->iTargetCnt = pktCnt;
int bitFlag = GroupManager::getGroupFlags(leader);
for (int i = 0; i < pktCnt; i++) {
Player* varPlr;
CNSocket* sockTo;
if (plr->iID == leader->groupIDs[i]) {
varPlr = plr;
sockTo = sock;
} else {
varPlr = PlayerManager::getPlayerFromID(leader->groupIDs[i]);
sockTo = PlayerManager::getSockFromID(leader->groupIDs[i]);
}
if (varPlr == nullptr || sockTo == nullptr)
continue;
respdata[i].eCT = 1;
respdata[i].iID = varPlr->iID;
respdata[i].iConditionBitFlag = iCBFlag;
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt1);
pkt1.eCSTB = eCharStatusTimeBuffID; // eCharStatusTimeBuffID
pkt1.eTBU = 1; // eTimeBuffUpdate
pkt1.eTBT = 1; // eTimeBuffType 1 means nano
pkt1.iConditionBitFlag = bitFlag | varPlr->iConditionBitFlag;
if (iValue > 0)
pkt1.TimeBuff.iValue = iValue;
sockTo->sendPacket((void*)&pkt1, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
}
sock->sendPacket((void*)&respbuf, P_FE2CL_NANO_SKILL_USE_SUCC, resplen);
PlayerManager::sendToViewable(sock, (void*)&respbuf, P_FE2CL_NANO_SKILL_USE, resplen);
}
void NanoManager::nanoUnbuff(CNSocket* sock, int32_t iCBFlag, int16_t eCharStatusTimeBuffID, int16_t iValue, bool groupPower) {
Player *plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return;
int pktCnt = 1;
Player *leader = plr;
plr->iConditionBitFlag &= ~iCBFlag;
if (groupPower) {
plr->iGroupConditionBitFlag &= ~iCBFlag;
if (plr->iID != plr->iIDGroup)
leader = PlayerManager::getPlayerFromID(plr->iIDGroup);
if (leader == nullptr)
return;
pktCnt = leader->groupCnt;
}
int bitFlag = GroupManager::getGroupFlags(leader);
for (int i = 0; i < pktCnt; i++) {
Player* varPlr;
CNSocket* sockTo;
if (plr->iID == leader->groupIDs[i]) {
varPlr = plr;
sockTo = sock;
} else {
varPlr = PlayerManager::getPlayerFromID(leader->groupIDs[i]);
sockTo = PlayerManager::getSockFromID(leader->groupIDs[i]);
}
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, resp1);
resp1.eCSTB = eCharStatusTimeBuffID; // eCharStatusTimeBuffID
resp1.eTBU = 2; // eTimeBuffUpdate
resp1.eTBT = 1; // eTimeBuffType 1 means nano
resp1.iConditionBitFlag = bitFlag | varPlr->iConditionBitFlag;
if (iValue > 0)
resp1.TimeBuff.iValue = iValue;
sockTo->sendPacket((void*)&resp1, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
}
}
// 0=A 1=B 2=C -1=Not found
int NanoManager::nanoStyle(int nanoId) {
if (nanoId < 1 || nanoId >= (int)NanoTable.size())
return -1;
return NanoTable[nanoId].style;
}
namespace NanoManager {
std::vector<PassivePower> PassivePowers = {
PassivePower(ScavangePowers, EST_REWARDBLOB, CSB_BIT_REWARD_BLOB, ECSB_REWARD_BLOB, 0, false),
PassivePower(RunPowers, EST_RUN, CSB_BIT_UP_MOVE_SPEED, ECSB_UP_MOVE_SPEED, 200, false),
PassivePower(GroupRunPowers, EST_RUN, CSB_BIT_UP_MOVE_SPEED, ECSB_UP_MOVE_SPEED, 200, true),
PassivePower(BonusPowers, EST_REWARDCASH, CSB_BIT_REWARD_CASH, ECSB_REWARD_CASH, 0, false),
PassivePower(GuardPowers, EST_PROTECTBATTERY, CSB_BIT_PROTECT_BATTERY, ECSB_PROTECT_BATTERY, 0, false),
PassivePower(RadarPowers, EST_MINIMAPENEMY, CSB_BIT_MINIMAP_ENEMY, ECSB_MINIMAP_ENEMY, 0, false),
PassivePower(AntidotePowers, EST_PROTECTINFECTION, CSB_BIT_PROTECT_INFECTION, ECSB_PROTECT_INFECTION, 0, false),
PassivePower(FreedomPowers, EST_FREEDOM, CSB_BIT_FREEDOM, ECSB_FREEDOM, 0, false),
PassivePower(GroupFreedomPowers, EST_FREEDOM, CSB_BIT_FREEDOM, ECSB_FREEDOM, 0, true),
PassivePower(JumpPowers, EST_JUMP, CSB_BIT_UP_JUMP_HEIGHT, ECSB_UP_JUMP_HEIGHT, 400, false),
PassivePower(GroupJumpPowers, EST_JUMP, CSB_BIT_UP_JUMP_HEIGHT, ECSB_UP_JUMP_HEIGHT, 400, true),
PassivePower(SelfRevivePowers, EST_PHOENIX, CSB_BIT_PHOENIX, ECSB_PHOENIX, 0, false),
PassivePower(SneakPowers, EST_STEALTH, CSB_BIT_UP_STEALTH, ECSB_UP_STEALTH, 0, false),
PassivePower(GroupSneakPowers, EST_STEALTH, CSB_BIT_UP_STEALTH, ECSB_UP_STEALTH, 0, true),
PassivePower(TreasureFinderPowers, EST_MINIMAPTRESURE, CSB_BIT_MINIMAP_TRESURE, ECSB_MINIMAP_TRESURE, 0, false),
};
}; // namespace
void NanoManager::revivePlayer(Player* plr) {
CNSocket* sock = PlayerManager::getSockFromID(plr->iID);
INITSTRUCT(sP_FE2CL_REP_PC_REGEN_SUCC, response);
INITSTRUCT(sP_FE2CL_PC_REGEN, resp2);
plr->HP = PC_MAXHEALTH(plr->level);
// Nanos
int activeSlot = -1;
for (int i = 0; i < 3; i++) {
int nanoID = plr->equippedNanos[i];
if (plr->activeNano == nanoID) {
activeSlot = i;
}
response.PCRegenData.Nanos[i] = plr->Nanos[nanoID];
}
// Response parameters
response.PCRegenData.iActiveNanoSlotNum = activeSlot;
response.PCRegenData.iX = plr->x;
response.PCRegenData.iY = plr->y;
response.PCRegenData.iZ = plr->z;
response.PCRegenData.iHP = plr->HP;
response.iFusionMatter = plr->fusionmatter;
response.bMoveLocation = 0;
response.PCRegenData.iMapNum = 0;
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_REGEN_SUCC, sizeof(sP_FE2CL_REP_PC_REGEN_SUCC));
// Update other players
resp2.PCRegenDataForOtherPC.iPC_ID = plr->iID;
resp2.PCRegenDataForOtherPC.iX = plr->x;
resp2.PCRegenDataForOtherPC.iY = plr->y;
resp2.PCRegenDataForOtherPC.iZ = plr->z;
resp2.PCRegenDataForOtherPC.iHP = plr->HP;
resp2.PCRegenDataForOtherPC.iAngle = plr->angle;
resp2.PCRegenDataForOtherPC.Nano = plr->Nanos[plr->activeNano];
PlayerManager::sendToViewable(sock, (void*)&resp2, P_FE2CL_PC_REGEN, sizeof(sP_FE2CL_PC_REGEN));
}
void NanoManager::nanoChangeBuff(CNSocket* sock, Player* plr, int32_t cbFrom, int32_t cbTo) {
bool sentPacket = false;
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, resp);
resp.eTBU = 3;
resp.eTBT = 1;
resp.iConditionBitFlag = cbTo;
if (!(cbFrom & CSB_BIT_UP_MOVE_SPEED) && (cbTo & CSB_BIT_UP_MOVE_SPEED)) {
resp.eCSTB = ECSB_UP_MOVE_SPEED;
resp.eTBU = 1;
resp.TimeBuff.iValue = 200;
sock->sendPacket((void*)&resp, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
sentPacket = true;
} else if ((cbFrom & CSB_BIT_UP_MOVE_SPEED) && !(cbTo & CSB_BIT_UP_MOVE_SPEED)) {
resp.eCSTB = ECSB_UP_MOVE_SPEED;
resp.eTBU = 2;
resp.TimeBuff.iValue = 200;
sock->sendPacket((void*)&resp, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
sentPacket = true;
}
if (!(cbFrom & CSB_BIT_UP_JUMP_HEIGHT) && (cbTo & CSB_BIT_UP_JUMP_HEIGHT)) {
resp.eCSTB = ECSB_UP_JUMP_HEIGHT;
resp.eTBU = 1;
resp.TimeBuff.iValue = 400;
sock->sendPacket((void*)&resp, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
sentPacket = true;
} else if ((cbFrom & CSB_BIT_UP_JUMP_HEIGHT) && !(cbTo & CSB_BIT_UP_JUMP_HEIGHT)) {
resp.eCSTB = ECSB_UP_JUMP_HEIGHT;
resp.eTBU = 2;
resp.TimeBuff.iValue = 400;
sock->sendPacket((void*)&resp, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
sentPacket = true;
}
if (!sentPacket)
sock->sendPacket((void*)&resp, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
}
#pragma endregion

View File

@@ -1,19 +1,70 @@
#pragma once
#include <set>
#include <vector>
#include "CNShardServer.hpp"
typedef void (*ActivePowerHandler)(CNSocket*, CNPacketData*, int16_t, int16_t, int16_t, int32_t, int32_t);
struct ActivePower {
std::set<int> powers;
ActivePowerHandler handler;
int16_t eSkillType;
int32_t flag;
int32_t amount;
ActivePower(std::set<int> p, ActivePowerHandler h, int16_t t, int32_t f, int32_t a) : powers(p), handler(h), eSkillType(t), flag(f), amount(a) {}
void handle(CNSocket *sock, CNPacketData *data, int16_t nanoId, int16_t skillId) {
if (handler == nullptr)
return;
handler(sock, data, nanoId, skillId, eSkillType, flag, amount);
}
};
struct PassivePower {
std::set<int> powers;
int16_t eSkillType;
int32_t iCBFlag;
int16_t eCharStatusTimeBuffID;
int16_t iValue;
bool groupPower;
PassivePower(std::set<int> p, int16_t t, int32_t f, int16_t b, int16_t a, bool g) : powers(p), eSkillType(t), iCBFlag(f), eCharStatusTimeBuffID(b), iValue(a), groupPower(g) {}
};
struct NanoData {
int style;
};
namespace NanoManager {
extern std::vector<ActivePower> ActivePowers;
extern std::vector<PassivePower> PassivePowers;
extern std::map<int32_t, NanoData> NanoTable;
void init();
void nanoSummonHandler(CNSocket* sock, CNPacketData* data);
void nanoEquipHandler(CNSocket* sock, CNPacketData* data);
void nanoUnEquipHandler(CNSocket* sock, CNPacketData* data);
void nanoGMGiveHandler(CNSocket* sock, CNPacketData* data);
void nanoSkillUseHandler(CNSocket* sock, CNPacketData* data);
void nanoSkillSetHandler(CNSocket* sock, CNPacketData* data);
void nanoSkillSetGMHandler(CNSocket* sock, CNPacketData* data);
void nanoRecallHandler(CNSocket* sock, CNPacketData* data);
void nanoPotionHandler(CNSocket* sock, CNPacketData* data);
// Helper methods
void addNano(CNSocket* sock, int16_t nanoId, int16_t slot);
void addNano(CNSocket* sock, int16_t nanoId, int16_t slot, bool spendfm=false);
void summonNano(CNSocket* sock, int slot);
void setNanoSkill(CNSocket* sock, int16_t nanoId, int16_t skillId);
void resetNanoSkill(CNSocket* sock, int16_t nanoId);
void nanoBuff(CNSocket* sock, int16_t nanoId, int skillId, int16_t eSkillType, int32_t iCBFlag, int16_t eCharStatusTimeBuffID, int16_t iValue = 0, bool groupPower = false);
void nanoUnbuff(CNSocket* sock, int32_t iCBFlag, int16_t eCharStatusTimeBuffID, int16_t iValue = 0, bool groupPower = false);
int nanoStyle(int nanoId);
void revivePlayer(Player* plr);
void nanoChangeBuff(CNSocket* sock, Player* plr, int32_t cbFrom, int32_t cbTo);
}

View File

@@ -1 +0,0 @@
#include "Player.hpp"

View File

@@ -6,30 +6,65 @@
#include "CNProtocol.hpp"
#include "CNStructs.hpp"
#define ACTIVE_MISSION_COUNT 6
#define PC_MAXHEALTH(level) (925 + 75 * (level))
struct Player {
int accountId;
int accountLevel; // permission level (see CN_ACCOUNT_LEVEL enums)
int64_t SerialKey;
int32_t iID;
uint64_t FEKey;
time_t creationTime;
int level;
int HP;
int slot; // player slot, not nano slot
int16_t mentor;
int32_t money;
int32_t fusionmatter;
int32_t batteryW;
int32_t batteryN;
sPCStyle PCStyle;
sPCStyle2 PCStyle2;
sNano Nanos[37]; // acquired nanos
int equippedNanos[3];
int activeNano; // active nano (index into Nanos)
int8_t iPCState;
int32_t iWarpLocationFlag;
int64_t aSkywayLocationFlag[2];
int32_t iConditionBitFlag;
int8_t iSpecialState;
int x, y, z, angle;
int lastX, lastY, lastZ, lastAngle;
uint64_t instanceID;
sItemBase Equip[AEQUIP_COUNT];
sItemBase Inven[AINVEN_COUNT];
sItemBase Bank[ABANK_COUNT];
sItemTrade Trade[12];
int32_t moneyInTrade;
bool isTrading;
bool isTradeConfirm;
bool IsGM;
bool inCombat;
bool passiveNanoOut;
int pointDamage;
int groupDamage;
int defense;
int64_t aQuestFlag[16];
int tasks[ACTIVE_MISSION_COUNT];
int RemainingNPCCount[ACTIVE_MISSION_COUNT][3];
sItemBase QInven[AQINVEN_COUNT];
int32_t CurrentMissionID;
sTimeLimitItemDeleteInfo2CL toRemoveVehicle;
int32_t iIDGroup;
int groupCnt;
int32_t groupIDs[4];
int32_t iGroupConditionBitFlag;
};

View File

@@ -3,9 +3,17 @@
#include "NPCManager.hpp"
#include "CNShardServer.hpp"
#include "CNShared.hpp"
#include "MissionManager.hpp"
#include "ItemManager.hpp"
#include "NanoManager.hpp"
#include "GroupManager.hpp"
#include "ChatManager.hpp"
#include "Database.hpp"
#include "settings.hpp"
#include <assert.h>
#include <algorithm>
#include <vector>
#include <cmath>
@@ -23,6 +31,7 @@ void PlayerManager::init() {
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_LAUNCHER, PlayerManager::launchPlayer);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ZIPLINE, PlayerManager::ziplinePlayer);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_MOVEPLATFORM, PlayerManager::movePlatformPlayer);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_MOVETRANSPORTATION, PlayerManager::moveSliderPlayer);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_SLOPE, PlayerManager::moveSlopePlayer);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GOTO, PlayerManager::gotoPlayer);
REGISTER_SHARD_PACKET(P_CL2FE_GM_REQ_PC_SET_VALUE, PlayerManager::setSpecialPlayer);
@@ -30,6 +39,7 @@ void PlayerManager::init() {
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_REGEN, PlayerManager::revivePlayer);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_EXIT, PlayerManager::exitGame);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_SPECIAL_STATE_SWITCH, PlayerManager::setSpecialSwitchPlayer);
REGISTER_SHARD_PACKET(P_CL2FE_GM_REQ_PC_SPECIAL_STATE_SWITCH, PlayerManager::setGMSpecialSwitchPlayer);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VEHICLE_ON, PlayerManager::enterPlayerVehicle);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VEHICLE_OFF, PlayerManager::exitPlayerVehicle);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_CHANGE_MENTOR, PlayerManager::changePlayerGuide);
@@ -41,85 +51,105 @@ void PlayerManager::addPlayer(CNSocket* key, Player plr) {
memcpy(p, &plr, sizeof(Player));
players[key] = PlayerView();
players[key].viewable = std::list<CNSocket*>();
players[key].chunkPos = std::make_tuple(0, 0, 0);
players[key].currentChunks = std::vector<Chunk*>();
players[key].plr = p;
players[key].lastHeartbeat = 0;
key->plr = p;
std::cout << U16toU8(plr.PCStyle.szFirstName) << " " << U16toU8(plr.PCStyle.szLastName) << " has joined!" << std::endl;
std::cout << players.size() << " players" << std::endl;
}
void PlayerManager::removePlayer(CNSocket* key) {
PlayerView cachedView = players[key];
PlayerView& view = players[key];
uint64_t fromInstance = view.plr->instanceID;
// if players have them in their viewable lists, remove it
for (CNSocket* otherSock : players[key].viewable) {
players[otherSock].viewable.remove(key); // gone
GroupManager::groupKickPlayer(view.plr);
// now send PC_EXIT packet
sP_FE2CL_PC_EXIT exitPacket;
exitPacket.iID = players[key].plr->iID;
// save player to DB
Database::updatePlayer(view.plr);
otherSock->sendPacket((void*)&exitPacket, P_FE2CL_PC_EXIT, sizeof(sP_FE2CL_PC_EXIT));
}
// remove players from all chunks
removePlayerFromChunks(view.currentChunks, key);
std::cout << U16toU8(cachedView.plr->PCStyle.szFirstName) << " " << U16toU8(cachedView.plr->PCStyle.szLastName) << " has left!" << std::endl;
std::cout << players.size() << " players" << std::endl;
// remove from chunk
if (ChunkManager::chunks.find(view.chunkPos) != ChunkManager::chunks.end())
ChunkManager::chunks[view.chunkPos]->players.erase(key);
delete cachedView.plr;
std::cout << U16toU8(view.plr->PCStyle.szFirstName) << " " << U16toU8(view.plr->PCStyle.szLastName) << " (PlayerId = " << view.plr->iID << ") has left!" << std::endl;
key->plr = nullptr;
delete view.plr;
players.erase(key);
// if the player was in a lair, clean it up
ChunkManager::destroyInstanceIfEmpty(fromInstance);
std::cout << players.size() << " players" << std::endl;
}
void PlayerManager::updatePlayerPosition(CNSocket* sock, int X, int Y, int Z) {
players[sock].plr->x = X;
players[sock].plr->y = Y;
players[sock].plr->z = Z;
bool PlayerManager::removePlayerFromChunks(std::vector<Chunk*> chunks, CNSocket* sock) {
INITSTRUCT(sP_FE2CL_PC_EXIT, exitPlayer);
std::vector<CNSocket*> noView;
std::vector<CNSocket*> yesView;
// for chunks that need the player to be removed from
for (Chunk* chunk : chunks) {
// TODO: oh god this is sooooo perfomance heavy the more players you have
for (auto pair : players) {
if (pair.first == sock)
continue; // ignore our own connection
int diffX = abs(pair.second.plr->x - X); // the map is like a grid, X and Y are your position on the map, Z is the height. very different from other games...
int diffY = abs(pair.second.plr->y - Y);
if (diffX < settings::PLAYERDISTANCE && diffY < settings::PLAYERDISTANCE) {
yesView.push_back(pair.first);
// remove NPCs
for (int32_t id : chunk->NPCs) {
BaseNPC* npc = NPCManager::NPCs[id];
switch (npc->npcClass) {
case NPC_BUS:
INITSTRUCT(sP_FE2CL_TRANSPORTATION_EXIT, exitBusData);
exitBusData.eTT = 3;
exitBusData.iT_ID = id;
sock->sendPacket((void*)&exitBusData, P_FE2CL_TRANSPORTATION_EXIT, sizeof(sP_FE2CL_TRANSPORTATION_EXIT));
break;
default:
INITSTRUCT(sP_FE2CL_NPC_EXIT, exitData);
exitData.iNPC_ID = id;
sock->sendPacket((void*)&exitData, P_FE2CL_NPC_EXIT, sizeof(sP_FE2CL_NPC_EXIT));
break;
}
}
else {
noView.push_back(pair.first);
// remove players from eachother
for (CNSocket* otherSock : chunk->players) {
exitPlayer.iID = players[sock].plr->iID;
otherSock->sendPacket((void*)&exitPlayer, P_FE2CL_PC_EXIT, sizeof(sP_FE2CL_PC_EXIT));
exitPlayer.iID = players[otherSock].plr->iID;
sock->sendPacket((void*)&exitPlayer, P_FE2CL_PC_EXIT, sizeof(sP_FE2CL_PC_EXIT));
}
}
INITSTRUCT(sP_FE2CL_PC_EXIT, exitPacket);
std::list<CNSocket*>::iterator i = players[sock].viewable.begin();
while (i != players[sock].viewable.end()) {
CNSocket* otherSock = *i;
if (std::find(noView.begin(), noView.end(), otherSock) != noView.end()) {
// sock shouldn't be visible, send PC_EXIT packet
exitPacket.iID = players[sock].plr->iID;
otherSock->sendPacket((void*)&exitPacket, P_FE2CL_PC_EXIT, sizeof(sP_FE2CL_PC_EXIT));
exitPacket.iID = players[otherSock].plr->iID;
sock->sendPacket((void*)&exitPacket, P_FE2CL_PC_EXIT, sizeof(sP_FE2CL_PC_EXIT));
// remove them from the viewable list
players[sock].viewable.erase(i++);
players[otherSock].viewable.remove(sock);
continue;
}
++i;
}
// remove us from that old stinky chunk
return ChunkManager::removePlayer(players[sock].chunkPos, sock);
}
void PlayerManager::addPlayerToChunks(std::vector<Chunk*> chunks, CNSocket* sock) {
INITSTRUCT(sP_FE2CL_PC_NEW, newPlayer);
for (CNSocket* otherSock : yesView) {
if (std::find(players[sock].viewable.begin(), players[sock].viewable.end(), otherSock) == players[sock].viewable.end()) {
// this needs to be added to the viewable players, send PC_ENTER
for (Chunk* chunk : chunks) {
// add npcs
for (int32_t id : chunk->NPCs) {
BaseNPC* npc = NPCManager::NPCs[id];
switch (npc->npcClass) {
case NPC_BUS:
INITSTRUCT(sP_FE2CL_TRANSPORTATION_ENTER, enterBusData);
enterBusData.AppearanceData = { 3, npc->appearanceData.iNPC_ID, npc->appearanceData.iNPCType, npc->appearanceData.iX, npc->appearanceData.iY, npc->appearanceData.iZ };
sock->sendPacket((void*)&enterBusData, P_FE2CL_TRANSPORTATION_ENTER, sizeof(sP_FE2CL_TRANSPORTATION_ENTER));
break;
default:
INITSTRUCT(sP_FE2CL_NPC_ENTER, enterData);
enterData.NPCAppearanceData = NPCManager::NPCs[id]->appearanceData;
sock->sendPacket((void*)&enterData, P_FE2CL_NPC_ENTER, sizeof(sP_FE2CL_NPC_ENTER));
break;
}
}
// add players
for (CNSocket* otherSock : chunk->players) {
Player *otherPlr = players[otherSock].plr;
Player *plr = players[sock].plr;
@@ -133,6 +163,7 @@ void PlayerManager::updatePlayerPosition(CNSocket* sock, int X, int Y, int Z) {
newPlayer.PCAppearanceData.PCStyle = plr->PCStyle;
newPlayer.PCAppearanceData.Nano = plr->Nanos[plr->activeNano];
newPlayer.PCAppearanceData.iPCState = plr->iPCState;
newPlayer.PCAppearanceData.iSpecialState = plr->iSpecialState;
memcpy(newPlayer.PCAppearanceData.ItemEquip, plr->Equip, sizeof(sItemBase) * AEQUIP_COUNT);
otherSock->sendPacket((void*)&newPlayer, P_FE2CL_PC_NEW, sizeof(sP_FE2CL_PC_NEW));
@@ -147,30 +178,98 @@ void PlayerManager::updatePlayerPosition(CNSocket* sock, int X, int Y, int Z) {
newPlayer.PCAppearanceData.PCStyle = otherPlr->PCStyle;
newPlayer.PCAppearanceData.Nano = otherPlr->Nanos[otherPlr->activeNano];
newPlayer.PCAppearanceData.iPCState = otherPlr->iPCState;
newPlayer.PCAppearanceData.iSpecialState = otherPlr->iSpecialState;
memcpy(newPlayer.PCAppearanceData.ItemEquip, otherPlr->Equip, sizeof(sItemBase) * AEQUIP_COUNT);
sock->sendPacket((void*)&newPlayer, P_FE2CL_PC_NEW, sizeof(sP_FE2CL_PC_NEW));
players[sock].viewable.push_back(otherSock);
players[otherSock].viewable.push_back(sock);
}
}
NPCManager::updatePlayerNPCS(sock, players[sock]);
}
std::list<CNSocket*> PlayerManager::getNearbyPlayers(int x, int y, int dist) {
std::list<CNSocket*> plrs;
void PlayerManager::updatePlayerPosition(CNSocket* sock, int X, int Y, int Z, int angle) {
players[sock].plr->angle = angle;
updatePlayerPosition(sock, X, Y, Z);
}
for (auto pair : players) {
int diffX = abs(pair.second.plr->x - x);
int diffY = abs(pair.second.plr->x - x);
void PlayerManager::updatePlayerPosition(CNSocket* sock, int X, int Y, int Z) {
PlayerView& view = players[sock];
view.plr->x = X;
view.plr->y = Y;
view.plr->z = Z;
updatePlayerChunk(sock, X, Y, view.plr->instanceID);
}
if (diffX < dist && diffY < dist)
plrs.push_back(pair.first);
void PlayerManager::updatePlayerChunk(CNSocket* sock, int X, int Y, uint64_t instanceID) {
PlayerView& view = players[sock];
std::tuple<int, int, uint64_t> newPos = ChunkManager::grabChunk(X, Y, view.plr->instanceID);
// nothing to be done
if (newPos == view.chunkPos)
return;
// add player to chunk
std::vector<Chunk*> allChunks = ChunkManager::grabChunks(newPos);
Chunk *chunk = nullptr;
if (ChunkManager::checkChunk(view.chunkPos))
chunk = ChunkManager::chunks[view.chunkPos];
// first, remove all the old npcs & players from the old chunks
if (removePlayerFromChunks(ChunkManager::getDeltaChunks(view.currentChunks, allChunks), sock)) {
allChunks.erase(std::remove(allChunks.begin(), allChunks.end(), chunk), allChunks.end());
}
return plrs;
// now, add all the new npcs & players!
addPlayerToChunks(ChunkManager::getDeltaChunks(allChunks, view.currentChunks), sock);
ChunkManager::addPlayer(X, Y, view.plr->instanceID, sock); // takes care of adding the player to the chunk if it exists or not
view.chunkPos = newPos;
view.currentChunks = allChunks;
}
void PlayerManager::sendPlayerTo(CNSocket* sock, int X, int Y, int Z, uint64_t I) {
PlayerView& plrv = PlayerManager::players[sock];
Player* plr = plrv.plr;
uint64_t fromInstance = plr->instanceID;
plr->instanceID = I;
if (I != INSTANCE_OVERWORLD) {
INITSTRUCT(sP_FE2CL_INSTANCE_MAP_INFO, pkt);
pkt.iEP_ID = PLAYERID(I) == 0; // iEP_ID has to be positive for the map to be enabled
pkt.iInstanceMapNum = (int32_t)MAPNUM(I); // lower 32 bits are mapnum
sock->sendPacket((void*)&pkt, P_FE2CL_INSTANCE_MAP_INFO, sizeof(sP_FE2CL_INSTANCE_MAP_INFO));
sendPlayerTo(sock, X, Y, Z);
} else {
// annoying but necessary to set the flag back
MissionManager::failInstancedMissions(sock); // fail any instanced missions
INITSTRUCT(sP_FE2CL_REP_PC_WARP_USE_NPC_SUCC, resp);
resp.iX = X;
resp.iY = Y;
resp.iZ = Z;
resp.iCandy = plrv.plr->money;
resp.eIL = 4; // do not take away any items
PlayerManager::removePlayerFromChunks(plrv.currentChunks, sock);
plrv.currentChunks.clear();
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_WARP_USE_NPC_SUCC, sizeof(sP_FE2CL_REP_PC_WARP_USE_NPC_SUCC));
}
ChunkManager::destroyInstanceIfEmpty(fromInstance);
}
void PlayerManager::sendPlayerTo(CNSocket* sock, int X, int Y, int Z) {
PlayerManager::updatePlayerPosition(sock, X, Y, Z);
INITSTRUCT(sP_FE2CL_REP_PC_GOTO_SUCC, pkt);
pkt.iX = X;
pkt.iY = Y;
pkt.iZ = Z;
// force player & NPC reload
PlayerView& plrv = players[sock];
PlayerManager::removePlayerFromChunks(plrv.currentChunks, sock);
plrv.currentChunks.clear();
plrv.chunkPos = std::make_tuple(0, 0, plrv.plr->instanceID);
sock->sendPacket((void*)&pkt, P_FE2CL_REP_PC_GOTO_SUCC, sizeof(sP_FE2CL_REP_PC_GOTO_SUCC));
}
void PlayerManager::enterPlayer(CNSocket* sock, CNPacketData* data) {
@@ -179,11 +278,13 @@ void PlayerManager::enterPlayer(CNSocket* sock, CNPacketData* data) {
sP_CL2FE_REQ_PC_ENTER* enter = (sP_CL2FE_REQ_PC_ENTER*)data->buf;
INITSTRUCT(sP_FE2CL_REP_PC_ENTER_SUCC, response);
INITSTRUCT(sP_FE2CL_PC_MOTD_LOGIN, motd);
// TODO: check if serialkey exists, if it doesn't send sP_FE2CL_REP_PC_ENTER_FAIL
Player plr = CNSharedData::getPlayer(enter->iEnterSerialKey);
plr.groupCnt = 1;
plr.iIDGroup = plr.groupIDs[0] = plr.iID;
DEBUGLOG(
std::cout << "P_CL2FE_REQ_PC_ENTER:" << std::endl;
std::cout << "\tID: " << U16toU8(enter->szID) << std::endl;
@@ -192,54 +293,86 @@ void PlayerManager::enterPlayer(CNSocket* sock, CNPacketData* data) {
std::cout << "\tPC_UID: " << plr.PCStyle.iPC_UID << std::endl;
)
response.iID = rand();
// check if account is already in use
if (isAccountInUse(plr.accountId)) {
// kick the other player
exitDuplicate(plr.accountId);
}
response.iID = plr.iID;
response.uiSvrTime = getTime();
response.PCLoadData2CL.iUserLevel = 1;
response.PCLoadData2CL.iHP = 3625; //TODO: Check player levelupdata and get this right
response.PCLoadData2CL.iUserLevel = plr.accountLevel;
response.PCLoadData2CL.iHP = plr.HP;
response.PCLoadData2CL.iLevel = plr.level;
response.PCLoadData2CL.iCandy = plr.money;
response.PCLoadData2CL.iMentor = 1;
response.PCLoadData2CL.iMentorCount = 4;
response.PCLoadData2CL.iMapNum = 0;
response.PCLoadData2CL.iFusionMatter = plr.fusionmatter;
response.PCLoadData2CL.iMentor = plr.mentor;
response.PCLoadData2CL.iMentorCount = 1; // how many guides the player has had
response.PCLoadData2CL.iX = plr.x;
response.PCLoadData2CL.iY = plr.y;
response.PCLoadData2CL.iZ = plr.z;
response.PCLoadData2CL.iAngle = 130;
response.PCLoadData2CL.iAngle = plr.angle;
response.PCLoadData2CL.iBatteryN = plr.batteryN;
response.PCLoadData2CL.iBatteryW = plr.batteryW;
response.PCLoadData2CL.iBuddyWarpTime = 60; // sets 60s warp cooldown on login
response.PCLoadData2CL.iWarpLocationFlag = plr.iWarpLocationFlag;
response.PCLoadData2CL.aWyvernLocationFlag[0] = plr.aSkywayLocationFlag[0];
response.PCLoadData2CL.aWyvernLocationFlag[1] = plr.aSkywayLocationFlag[1];
response.PCLoadData2CL.iActiveNanoSlotNum = -1;
response.PCLoadData2CL.iFatigue = 50;
response.PCLoadData2CL.PCStyle = plr.PCStyle;
response.PCLoadData2CL.PCStyle2 = plr.PCStyle2;
// client doesnt read this, it gets it from charinfo
// response.PCLoadData2CL.PCStyle2 = plr.PCStyle2;
// inventory
for (int i = 0; i < AEQUIP_COUNT; i++)
response.PCLoadData2CL.aEquip[i] = plr.Equip[i];
for (int i = 0; i < AINVEN_COUNT; i++)
response.PCLoadData2CL.aInven[i] = plr.Inven[i];
// quest inventory
for (int i = 0; i < AQINVEN_COUNT; i++)
response.PCLoadData2CL.aQInven[i] = plr.QInven[i];
// nanos
for (int i = 1; i < SIZEOF_NANO_BANK_SLOT; i++) {
response.PCLoadData2CL.aNanoBank[i] = plr.Nanos[i];
}
for (int i = 0; i < 3; i++) {
response.PCLoadData2CL.aNanoSlots[i] = plr.equippedNanos[i];
}
// missions in progress
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
if (plr.tasks[i] == 0)
break;
response.PCLoadData2CL.aRunningQuest[i].m_aCurrTaskID = plr.tasks[i];
TaskData &task = *MissionManager::Tasks[plr.tasks[i]];
for (int j = 0; j < 3; j++) {
response.PCLoadData2CL.aRunningQuest[i].m_aKillNPCID[j] = (int)task["m_iCSUEnemyID"][j];
response.PCLoadData2CL.aRunningQuest[i].m_aKillNPCCount[j] = plr.RemainingNPCCount[i][j];
/*
* client doesn't care about NeededItem ID and Count,
* it gets Count from Quest Inventory
*
* KillNPCCount sets RemainEnemyNum in the client
* Yes, this is extraordinary stupid.
*/
}
}
response.PCLoadData2CL.iCurrentMissionID = plr.CurrentMissionID;
// don't ask..
for (int i = 1; i < 37; i++) {
response.PCLoadData2CL.aNanoBank[i].iID = i;
response.PCLoadData2CL.aNanoBank[i].iSkillID = 1;
response.PCLoadData2CL.aNanoBank[i].iStamina = 150;
// completed missions
// the packet requires 32 items, but the client only checks the first 16 (shrug)
for (int i = 0; i < 16; i++) {
response.PCLoadData2CL.aQuestFlag[i] = plr.aQuestFlag[i];
}
// temporarily not add nanos for nano add test through commands
//response.PCLoadData2CL.aNanoSlots[0] = 1;
//response.PCLoadData2CL.aNanoSlots[1] = 2;
//response.PCLoadData2CL.aNanoSlots[2] = 3;
response.PCLoadData2CL.aQuestFlag[0] = -1;
// shut computress up
// shut Computress up
response.PCLoadData2CL.iFirstUseFlag1 = UINT64_MAX;
response.PCLoadData2CL.iFirstUseFlag2 = UINT64_MAX;
plr.iID = response.iID;
plr.SerialKey = enter->iEnterSerialKey;
plr.HP = response.PCLoadData2CL.iHP;
motd.iType = 1;
U8toU16(settings::MOTDSTRING, (char16_t*)motd.szSystemMsg);
plr.instanceID = INSTANCE_OVERWORLD; // the player should never be in an instance on enter
sock->setEKey(CNSocketEncryption::createNewKey(response.uiSvrTime, response.iID + 1, response.PCLoadData2CL.iFusionMatter + 1));
sock->setFEKey(plr.FEKey);
@@ -248,9 +381,27 @@ void PlayerManager::enterPlayer(CNSocket* sock, CNPacketData* data) {
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_ENTER_SUCC, sizeof(sP_FE2CL_REP_PC_ENTER_SUCC));
// transmit MOTD after entering the game, so the client hopefully changes modes on time
sock->sendPacket((void*)&motd, P_FE2CL_PC_MOTD_LOGIN, sizeof(sP_FE2CL_PC_MOTD_LOGIN));
ChatManager::sendServerMessage(sock, settings::MOTDSTRING);
addPlayer(sock, plr);
// check if there is an expiring vehicle
ItemManager::checkItemExpire(sock, getPlayer(sock));
// set player equip stats
ItemManager::setItemStats(getPlayer(sock));
MissionManager::failInstancedMissions(sock);
}
void PlayerManager::sendToViewable(CNSocket* sock, void* buf, uint32_t type, size_t size) {
for (Chunk* chunk : players[sock].currentChunks) {
for (CNSocket* otherSock : chunk->players) {
if (otherSock == sock)
continue;
otherSock->sendPacket(buf, type, size);
}
}
}
void PlayerManager::loadPlayer(CNSocket* sock, CNPacketData* data) {
@@ -269,7 +420,7 @@ void PlayerManager::loadPlayer(CNSocket* sock, CNPacketData* data) {
response.iPC_ID = complete->iPC_ID;
// reload players & NPCs
updatePlayerPosition(sock, plr->x, plr->y, plr->z);
updatePlayerPosition(sock, plr->x, plr->y, plr->z, plr->angle);
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_LOADING_COMPLETE_SUCC, sizeof(sP_FE2CL_REP_PC_LOADING_COMPLETE_SUCC));
}
@@ -279,7 +430,7 @@ void PlayerManager::movePlayer(CNSocket* sock, CNPacketData* data) {
return; // ignore the malformed packet
sP_CL2FE_REQ_PC_MOVE* moveData = (sP_CL2FE_REQ_PC_MOVE*)data->buf;
updatePlayerPosition(sock, moveData->iX, moveData->iY, moveData->iZ);
updatePlayerPosition(sock, moveData->iX, moveData->iY, moveData->iZ, moveData->iAngle);
players[sock].plr->angle = moveData->iAngle;
uint64_t tm = getTime();
@@ -301,9 +452,7 @@ void PlayerManager::movePlayer(CNSocket* sock, CNPacketData* data) {
moveResponse.iCliTime = moveData->iCliTime; // maybe don't send this??? seems unneeded...
moveResponse.iSvrTime = tm;
for (CNSocket* otherSock : players[sock].viewable) {
otherSock->sendPacket((void*)&moveResponse, P_FE2CL_PC_MOVE, sizeof(sP_FE2CL_PC_MOVE));
}
sendToViewable(sock, (void*)&moveResponse, P_FE2CL_PC_MOVE, sizeof(sP_FE2CL_PC_MOVE));
}
void PlayerManager::stopPlayer(CNSocket* sock, CNPacketData* data) {
@@ -326,9 +475,7 @@ void PlayerManager::stopPlayer(CNSocket* sock, CNPacketData* data) {
stopResponse.iCliTime = stopData->iCliTime; // maybe don't send this??? seems unneeded...
stopResponse.iSvrTime = tm;
for (CNSocket* otherSock : players[sock].viewable) {
otherSock->sendPacket((void*)&stopResponse, P_FE2CL_PC_STOP, sizeof(sP_FE2CL_PC_STOP));
}
sendToViewable(sock, (void*)&stopResponse, P_FE2CL_PC_STOP, sizeof(sP_FE2CL_PC_STOP));
}
void PlayerManager::jumpPlayer(CNSocket* sock, CNPacketData* data) {
@@ -336,7 +483,7 @@ void PlayerManager::jumpPlayer(CNSocket* sock, CNPacketData* data) {
return; // ignore the malformed packet
sP_CL2FE_REQ_PC_JUMP* jumpData = (sP_CL2FE_REQ_PC_JUMP*)data->buf;
updatePlayerPosition(sock, jumpData->iX, jumpData->iY, jumpData->iZ);
updatePlayerPosition(sock, jumpData->iX, jumpData->iY, jumpData->iZ, jumpData->iAngle);
uint64_t tm = getTime();
@@ -357,9 +504,7 @@ void PlayerManager::jumpPlayer(CNSocket* sock, CNPacketData* data) {
jumpResponse.iCliTime = jumpData->iCliTime; // maybe don't send this??? seems unneeded...
jumpResponse.iSvrTime = tm;
for (CNSocket* otherSock : players[sock].viewable) {
otherSock->sendPacket((void*)&jumpResponse, P_FE2CL_PC_JUMP, sizeof(sP_FE2CL_PC_JUMP));
}
sendToViewable(sock, (void*)&jumpResponse, P_FE2CL_PC_JUMP, sizeof(sP_FE2CL_PC_JUMP));
}
void PlayerManager::jumppadPlayer(CNSocket* sock, CNPacketData* data) {
@@ -367,7 +512,7 @@ void PlayerManager::jumppadPlayer(CNSocket* sock, CNPacketData* data) {
return; // ignore the malformed packet
sP_CL2FE_REQ_PC_JUMPPAD* jumppadData = (sP_CL2FE_REQ_PC_JUMPPAD*)data->buf;
updatePlayerPosition(sock, jumppadData->iX, jumppadData->iY, jumppadData->iZ);
updatePlayerPosition(sock, jumppadData->iX, jumppadData->iY, jumppadData->iZ, jumppadData->iAngle);
uint64_t tm = getTime();
@@ -386,9 +531,7 @@ void PlayerManager::jumppadPlayer(CNSocket* sock, CNPacketData* data) {
jumppadResponse.iCliTime = jumppadData->iCliTime;
jumppadResponse.iSvrTime = tm;
for (CNSocket* otherSock : players[sock].viewable) {
otherSock->sendPacket((void*)&jumppadResponse, P_FE2CL_PC_JUMPPAD, sizeof(sP_FE2CL_PC_JUMPPAD));
}
sendToViewable(sock, (void*)&jumppadResponse, P_FE2CL_PC_JUMPPAD, sizeof(sP_FE2CL_PC_JUMPPAD));
}
void PlayerManager::launchPlayer(CNSocket* sock, CNPacketData* data) {
@@ -396,7 +539,7 @@ void PlayerManager::launchPlayer(CNSocket* sock, CNPacketData* data) {
return; // ignore the malformed packet
sP_CL2FE_REQ_PC_LAUNCHER* launchData = (sP_CL2FE_REQ_PC_LAUNCHER*)data->buf;
updatePlayerPosition(sock, launchData->iX, launchData->iY, launchData->iZ);
updatePlayerPosition(sock, launchData->iX, launchData->iY, launchData->iZ, launchData->iAngle);
uint64_t tm = getTime();
@@ -416,9 +559,7 @@ void PlayerManager::launchPlayer(CNSocket* sock, CNPacketData* data) {
launchResponse.iCliTime = launchData->iCliTime;
launchResponse.iSvrTime = tm;
for (CNSocket* otherSock : players[sock].viewable) {
otherSock->sendPacket((void*)&launchResponse, P_FE2CL_PC_LAUNCHER, sizeof(sP_FE2CL_PC_LAUNCHER));
}
sendToViewable(sock, (void*)&launchResponse, P_FE2CL_PC_LAUNCHER, sizeof(sP_FE2CL_PC_LAUNCHER));
}
void PlayerManager::ziplinePlayer(CNSocket* sock, CNPacketData* data) {
@@ -426,7 +567,7 @@ void PlayerManager::ziplinePlayer(CNSocket* sock, CNPacketData* data) {
return; // ignore the malformed packet
sP_CL2FE_REQ_PC_ZIPLINE* ziplineData = (sP_CL2FE_REQ_PC_ZIPLINE*)data->buf;
updatePlayerPosition(sock, ziplineData->iX, ziplineData->iY, ziplineData->iZ);
updatePlayerPosition(sock, ziplineData->iX, ziplineData->iY, ziplineData->iZ, ziplineData->iAngle);
uint64_t tm = getTime();
@@ -443,7 +584,7 @@ void PlayerManager::ziplinePlayer(CNSocket* sock, CNPacketData* data) {
ziplineResponse.fVZ = ziplineData->fVZ;
ziplineResponse.fMovDistance = ziplineData->fMovDistance;
ziplineResponse.fMaxDistance = ziplineData->fMaxDistance;
ziplineResponse.fDummy = ziplineData->fDummy; //wtf is this for?
ziplineResponse.fDummy = ziplineData->fDummy; // wtf is this for?
ziplineResponse.iStX = ziplineData->iStX;
ziplineResponse.iStY = ziplineData->iStY;
ziplineResponse.iStZ = ziplineData->iStZ;
@@ -453,9 +594,7 @@ void PlayerManager::ziplinePlayer(CNSocket* sock, CNPacketData* data) {
ziplineResponse.iRollMax = ziplineData->iRollMax;
ziplineResponse.iRoll = ziplineData->iRoll;
for (CNSocket* otherSock : players[sock].viewable) {
otherSock->sendPacket((void*)&ziplineResponse, P_FE2CL_PC_ZIPLINE, sizeof(sP_FE2CL_PC_ZIPLINE));
}
sendToViewable(sock, (void*)&ziplineResponse, P_FE2CL_PC_ZIPLINE, sizeof(sP_FE2CL_PC_ZIPLINE));
}
void PlayerManager::movePlatformPlayer(CNSocket* sock, CNPacketData* data) {
@@ -463,7 +602,7 @@ void PlayerManager::movePlatformPlayer(CNSocket* sock, CNPacketData* data) {
return; // ignore the malformed packet
sP_CL2FE_REQ_PC_MOVEPLATFORM* platformData = (sP_CL2FE_REQ_PC_MOVEPLATFORM*)data->buf;
updatePlayerPosition(sock, platformData->iX, platformData->iY, platformData->iZ);
updatePlayerPosition(sock, platformData->iX, platformData->iY, platformData->iZ, platformData->iAngle);
uint64_t tm = getTime();
@@ -487,9 +626,38 @@ void PlayerManager::movePlatformPlayer(CNSocket* sock, CNPacketData* data) {
platResponse.cKeyValue = platformData->cKeyValue;
platResponse.iPlatformID = platformData->iPlatformID;
for (CNSocket* otherSock : players[sock].viewable) {
otherSock->sendPacket((void*)&platResponse, P_FE2CL_PC_MOVEPLATFORM, sizeof(sP_FE2CL_PC_MOVEPLATFORM));
}
sendToViewable(sock, (void*)&platResponse, P_FE2CL_PC_MOVEPLATFORM, sizeof(sP_FE2CL_PC_MOVEPLATFORM));
}
void PlayerManager::moveSliderPlayer(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_PC_MOVETRANSPORTATION))
return; // ignore the malformed packet
sP_CL2FE_REQ_PC_MOVETRANSPORTATION* sliderData = (sP_CL2FE_REQ_PC_MOVETRANSPORTATION*)data->buf;
updatePlayerPosition(sock, sliderData->iX, sliderData->iY, sliderData->iZ, sliderData->iAngle);
uint64_t tm = getTime();
INITSTRUCT(sP_FE2CL_PC_MOVETRANSPORTATION, sliderResponse);
sliderResponse.iPC_ID = players[sock].plr->iID;
sliderResponse.iCliTime = sliderData->iCliTime;
sliderResponse.iSvrTime = tm;
sliderResponse.iX = sliderData->iX;
sliderResponse.iY = sliderData->iY;
sliderResponse.iZ = sliderData->iZ;
sliderResponse.iAngle = sliderData->iAngle;
sliderResponse.fVX = sliderData->fVX;
sliderResponse.fVY = sliderData->fVY;
sliderResponse.fVZ = sliderData->fVZ;
sliderResponse.iLcX = sliderData->iLcX;
sliderResponse.iLcY = sliderData->iLcY;
sliderResponse.iLcZ = sliderData->iLcZ;
sliderResponse.iSpeed = sliderData->iSpeed;
sliderResponse.cKeyValue = sliderData->cKeyValue;
sliderResponse.iT_ID = sliderData->iT_ID;
sendToViewable(sock, (void*)&sliderResponse, P_FE2CL_PC_MOVETRANSPORTATION, sizeof(sP_FE2CL_PC_MOVETRANSPORTATION));
}
void PlayerManager::moveSlopePlayer(CNSocket* sock, CNPacketData* data) {
@@ -497,7 +665,7 @@ void PlayerManager::moveSlopePlayer(CNSocket* sock, CNPacketData* data) {
return; // ignore the malformed packet
sP_CL2FE_REQ_PC_SLOPE* slopeData = (sP_CL2FE_REQ_PC_SLOPE*)data->buf;
updatePlayerPosition(sock, slopeData->iX, slopeData->iY, slopeData->iZ);
updatePlayerPosition(sock, slopeData->iX, slopeData->iY, slopeData->iZ,slopeData->iAngle);
uint64_t tm = getTime();
@@ -517,9 +685,7 @@ void PlayerManager::moveSlopePlayer(CNSocket* sock, CNPacketData* data) {
slopeResponse.cKeyValue = slopeData->cKeyValue;
slopeResponse.iSlopeID = slopeData->iSlopeID;
for (CNSocket* otherSock : players[sock].viewable) {
otherSock->sendPacket((void*)&slopeResponse, P_FE2CL_PC_SLOPE, sizeof(sP_FE2CL_PC_SLOPE));
}
sendToViewable(sock, (void*)&slopeResponse, P_FE2CL_PC_SLOPE, sizeof(sP_FE2CL_PC_SLOPE));
}
void PlayerManager::gotoPlayer(CNSocket* sock, CNPacketData* data) {
@@ -534,13 +700,10 @@ void PlayerManager::gotoPlayer(CNSocket* sock, CNPacketData* data) {
std::cout << "\tX: " << gotoData->iToX << std::endl;
std::cout << "\tY: " << gotoData->iToY << std::endl;
std::cout << "\tZ: " << gotoData->iToZ << std::endl;
)
)
response.iX = gotoData->iToX;
response.iY = gotoData->iToY;
response.iZ = gotoData->iToZ;
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_GOTO_SUCC, sizeof(sP_FE2CL_REP_PC_GOTO_SUCC));
MissionManager::failInstancedMissions(sock); // this ensures warping by command still fails instanced missions
sendPlayerTo(sock, gotoData->iToX, gotoData->iToY, gotoData->iToZ, 0);
}
void PlayerManager::setSpecialPlayer(CNSocket* sock, CNPacketData* data) {
@@ -548,6 +711,11 @@ void PlayerManager::setSpecialPlayer(CNSocket* sock, CNPacketData* data) {
return; // ignore the malformed packet
sP_CL2FE_GM_REQ_PC_SET_VALUE* setData = (sP_CL2FE_GM_REQ_PC_SET_VALUE*)data->buf;
Player *plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return;
INITSTRUCT(sP_FE2CL_GM_REP_PC_SET_VALUE, response);
DEBUGLOG(
@@ -557,6 +725,27 @@ void PlayerManager::setSpecialPlayer(CNSocket* sock, CNPacketData* data) {
std::cout << "\tSetValue: " << setData->iSetValue << std::endl;
)
// Handle serverside value-changes
switch (setData->iSetValueType) {
case 1:
plr->HP = setData->iSetValue;
break;
case 2:
plr->batteryW = setData->iSetValue;
break;
case 3:
plr->batteryN = setData->iSetValue;
break;
case 4:
plr->fusionmatter = setData->iSetValue;
break;
case 5:
plr->money = setData->iSetValue;
break;
default:
break;
}
response.iPC_ID = setData->iPC_ID;
response.iSetValue = setData->iSetValue;
response.iSetValueType = setData->iSetValueType;
@@ -586,70 +775,127 @@ void PlayerManager::revivePlayer(CNSocket* sock, CNPacketData* data) {
return;
Player *plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return;
WarpLocation target = PlayerManager::getRespawnPoint(plr);
// players respawn at same spot they died at for now...
sP_CL2FE_REQ_PC_REGEN* reviveData = (sP_CL2FE_REQ_PC_REGEN*)data->buf;
INITSTRUCT(sP_FE2CL_REP_PC_REGEN_SUCC, response);
response.bMoveLocation = reviveData->eIL;
response.PCRegenData.iMapNum = reviveData->iIndex;
response.PCRegenData.iHP = 1000 * plr->level;
response.PCRegenData.iX = target.x;
response.PCRegenData.iY = target.y;
response.PCRegenData.iZ = target.z;
INITSTRUCT(sP_FE2CL_PC_REGEN, resp2);
int activeSlot = -1;
if (reviveData->iRegenType == 3 && plr->iConditionBitFlag & CSB_BIT_PHOENIX) {
// nano revive
plr->Nanos[plr->activeNano].iStamina = 0;
NanoManager::nanoUnbuff(sock, CSB_BIT_PHOENIX, ECSB_PHOENIX, 0, false);
plr->HP = PC_MAXHEALTH(plr->level);
} else {
plr->x = target.x;
plr->y = target.y;
plr->z = target.z;
if (reviveData->iRegenType != 5)
plr->HP = PC_MAXHEALTH(plr->level);
for (int i = 0; i < 3; i++) {
int nanoID = plr->equippedNanos[i];
// halve nano health if respawning
if (reviveData->iRegenType != 5)
plr->Nanos[nanoID].iStamina = 75; // max is 150, so 75 is half
response.PCRegenData.Nanos[i] = plr->Nanos[nanoID];
if (plr->activeNano == nanoID)
activeSlot = i;
}
}
// Response parameters
response.PCRegenData.iActiveNanoSlotNum = activeSlot;
response.PCRegenData.iX = plr->x;
response.PCRegenData.iY = plr->y;
response.PCRegenData.iZ = plr->z;
response.PCRegenData.iHP = plr->HP;
response.iFusionMatter = plr->fusionmatter;
response.bMoveLocation = 0;
response.PCRegenData.iMapNum = 0;
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_REGEN_SUCC, sizeof(sP_FE2CL_REP_PC_REGEN_SUCC));
// Update other players
resp2.PCRegenDataForOtherPC.iPC_ID = plr->iID;
resp2.PCRegenDataForOtherPC.iX = plr->x;
resp2.PCRegenDataForOtherPC.iY = plr->y;
resp2.PCRegenDataForOtherPC.iZ = plr->z;
resp2.PCRegenDataForOtherPC.iHP = plr->HP;
resp2.PCRegenDataForOtherPC.iAngle = plr->angle;
resp2.PCRegenDataForOtherPC.iConditionBitFlag = plr->iConditionBitFlag;
resp2.PCRegenDataForOtherPC.iPCState = plr->iPCState;
resp2.PCRegenDataForOtherPC.iSpecialState = plr->iSpecialState;
resp2.PCRegenDataForOtherPC.Nano = plr->Nanos[plr->activeNano];
sendToViewable(sock, (void*)&resp2, P_FE2CL_PC_REGEN, sizeof(sP_FE2CL_PC_REGEN));
}
void PlayerManager::enterPlayerVehicle(CNSocket* sock, CNPacketData* data) {
PlayerView& plr = PlayerManager::players[sock];
if (plr.plr->Equip[8].iID > 0) {
if (plr.plr->Equip[8].iID > 0 && plr.plr->Equip[8].iTimeLimit>getTimestamp()) {
INITSTRUCT(sP_FE2CL_PC_VEHICLE_ON_SUCC, response);
sock->sendPacket((void*)&response, P_FE2CL_PC_VEHICLE_ON_SUCC, sizeof(sP_FE2CL_PC_VEHICLE_ON_SUCC));
//send to other players
// send to other players
plr.plr->iPCState = 8;
INITSTRUCT(sP_FE2CL_PC_STATE_CHANGE, response2);
response2.iPC_ID = plr.plr->iID;
response2.iState = 8;
for (CNSocket* otherSock : plr.viewable) {
otherSock->sendPacket((void*)&response2, P_FE2CL_PC_STATE_CHANGE, sizeof(sP_FE2CL_PC_STATE_CHANGE));
for (Chunk* chunk : players[sock].currentChunks) {
for (CNSocket* otherSock : chunk->players) {
otherSock->sendPacket((void*)&response2, P_FE2CL_PC_STATE_CHANGE, sizeof(sP_FE2CL_PC_STATE_CHANGE));
}
}
} else {
INITSTRUCT(sP_FE2CL_PC_VEHICLE_ON_FAIL, response);
sock->sendPacket((void*)&response, P_FE2CL_PC_VEHICLE_ON_FAIL, sizeof(sP_FE2CL_PC_VEHICLE_ON_FAIL));
// check if vehicle didn't expire
if (plr.plr->Equip[8].iTimeLimit < getTimestamp()) {
plr.plr->toRemoveVehicle.eIL = 0;
plr.plr->toRemoveVehicle.iSlotNum = 8;
ItemManager::checkItemExpire(sock, plr.plr);
}
}
}
void PlayerManager::exitPlayerVehicle(CNSocket* sock, CNPacketData* data) {
INITSTRUCT(sP_FE2CL_PC_VEHICLE_OFF_SUCC, response);
sock->sendPacket((void*)&response, P_FE2CL_PC_VEHICLE_OFF_SUCC, sizeof(sP_FE2CL_PC_VEHICLE_OFF_SUCC));
PlayerView plr = PlayerManager::players[sock];
//send to other players
// send to other players
plr.plr->iPCState = 0;
INITSTRUCT(sP_FE2CL_PC_STATE_CHANGE, response2);
response2.iPC_ID = plr.plr->iID;
response2.iState = 0;
for (CNSocket* otherSock : plr.viewable) {
otherSock->sendPacket((void*)&response2, P_FE2CL_PC_STATE_CHANGE, sizeof(sP_FE2CL_PC_STATE_CHANGE));
}
sendToViewable(sock, (void*)&response2, P_FE2CL_PC_STATE_CHANGE, sizeof(sP_FE2CL_PC_STATE_CHANGE));
}
void PlayerManager::setSpecialSwitchPlayer(CNSocket* sock, CNPacketData* data) {
sP_CL2FE_REQ_PC_SPECIAL_STATE_SWITCH* specialData = (sP_CL2FE_REQ_PC_SPECIAL_STATE_SWITCH*)data->buf;
INITSTRUCT(sP_FE2CL_REP_PC_SPECIAL_STATE_SWITCH_SUCC, response);
setSpecialState(sock, data);
}
response.iPC_ID = specialData->iPC_ID;
response.iReqSpecialStateFlag = specialData->iSpecialStateFlag;
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_SPECIAL_STATE_SWITCH_SUCC, sizeof(sP_FE2CL_REP_PC_SPECIAL_STATE_SWITCH_SUCC));
void PlayerManager::setGMSpecialSwitchPlayer(CNSocket* sock, CNPacketData* data) {
if (getPlayer(sock)->accountLevel > 30)
return;
setSpecialState(sock, data);
}
void PlayerManager::changePlayerGuide(CNSocket *sock, CNPacketData *data) {
@@ -665,11 +911,29 @@ void PlayerManager::changePlayerGuide(CNSocket *sock, CNPacketData *data) {
resp.iFusionMatter = plr->fusionmatter; // no cost
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_CHANGE_MENTOR_SUCC, sizeof(sP_FE2CL_REP_PC_CHANGE_MENTOR_SUCC));
// if it's changed from computress
if (plr->mentor == 5) {
// we're warping to the past
plr->PCStyle2.iPayzoneFlag = 1;
// remove all active missions
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
if (plr->tasks[i] != 0)
MissionManager::quitTask(sock, plr->tasks[i], true);
}
// start Blossom nano mission if applicable
MissionManager::updateFusionMatter(sock, 0);
}
// save it on player
plr->mentor = pkt->iMentor;
}
#pragma region Helper methods
Player *PlayerManager::getPlayer(CNSocket* key) {
return players[key].plr;
if (players.find(key) != players.end())
return players[key].plr;
return nullptr;
}
WarpLocation PlayerManager::getRespawnPoint(Player *plr) {
@@ -678,7 +942,7 @@ WarpLocation PlayerManager::getRespawnPoint(Player *plr) {
for (auto targ : NPCManager::RespawnPoints) {
curDist = sqrt(pow(plr->x - targ.x, 2) + pow(plr->y - targ.y, 2));
if (curDist < bestDist) {
if (curDist < bestDist && targ.instanceID == MAPNUM(plr->instanceID)) { // only mapNum needs to match
best = targ;
bestDist = curDist;
}
@@ -686,10 +950,10 @@ WarpLocation PlayerManager::getRespawnPoint(Player *plr) {
return best;
}
bool PlayerManager::isAccountInUse(int accountId) {
std::map<CNSocket*, PlayerView>::iterator it;
for (it = PlayerManager::players.begin(); it != PlayerManager::players.end(); it++)
{
for (it = PlayerManager::players.begin(); it != PlayerManager::players.end(); it++) {
if (it->second.plr->accountId == accountId)
return true;
}
@@ -698,16 +962,62 @@ bool PlayerManager::isAccountInUse(int accountId) {
void PlayerManager::exitDuplicate(int accountId) {
std::map<CNSocket*, PlayerView>::iterator it;
for (it = PlayerManager::players.begin(); it != PlayerManager::players.end(); it++)
{
if (it->second.plr->accountId == accountId)
{
// disconnect any duplicate players
for (it = players.begin(); it != players.end(); it++) {
if (it->second.plr->accountId == accountId) {
CNSocket* sock = it->first;
INITSTRUCT(sP_FE2CL_REP_PC_EXIT_DUPLICATE, resp);
resp.iErrorCode = 0;
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_EXIT_DUPLICATE, sizeof(sP_FE2CL_REP_PC_EXIT_DUPLICATE));
sock->kill();
CNShardServer::_killConnection(sock);
}
}
}
void PlayerManager::setSpecialState(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_GM_REQ_PC_SPECIAL_STATE_SWITCH))
return; // ignore the malformed packet
Player *plr = getPlayer(sock);
if (plr == nullptr)
return;
sP_CL2FE_GM_REQ_PC_SPECIAL_STATE_SWITCH* setData = (sP_CL2FE_GM_REQ_PC_SPECIAL_STATE_SWITCH*)data->buf;
// HACK: work around the invisible weapon bug
if (setData->iSpecialStateFlag == CN_SPECIAL_STATE_FLAG__FULL_UI)
ItemManager::updateEquips(sock, plr);
INITSTRUCT(sP_FE2CL_PC_SPECIAL_STATE_CHANGE, response);
plr->iSpecialState ^= setData->iSpecialStateFlag;
response.iPC_ID = setData->iPC_ID;
response.iReqSpecialStateFlag = setData->iSpecialStateFlag;
response.iSpecialState = plr->iSpecialState;
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_SPECIAL_STATE_SWITCH_SUCC, sizeof(sP_FE2CL_REP_PC_SPECIAL_STATE_SWITCH_SUCC));
sendToViewable(sock, (void*)&response, P_FE2CL_PC_SPECIAL_STATE_CHANGE, sizeof(sP_FE2CL_PC_SPECIAL_STATE_CHANGE));
}
Player *PlayerManager::getPlayerFromID(int32_t iID) {
for (auto& pair : PlayerManager::players)
if (pair.second.plr->iID == iID)
return pair.second.plr;
return nullptr;
}
CNSocket *PlayerManager::getSockFromID(int32_t iID) {
for (auto& pair : PlayerManager::players)
if (pair.second.plr->iID == iID)
return pair.first;
return nullptr;
}
#pragma endregion

View File

@@ -4,17 +4,19 @@
#include "CNProtocol.hpp"
#include "CNStructs.hpp"
#include "CNShardServer.hpp"
#include "ChunkManager.hpp"
#include <utility>
#include <map>
#include <list>
struct WarpLocation;
struct PlayerView {
std::list<CNSocket*> viewable;
std::list<int32_t> viewableNPCs;
std::tuple<int, int, uint64_t> chunkPos;
std::vector<Chunk*> currentChunks;
Player *plr;
uint64_t lastHeartbeat;
time_t lastHeartbeat;
};
@@ -25,8 +27,17 @@ namespace PlayerManager {
void addPlayer(CNSocket* key, Player plr);
void removePlayer(CNSocket* key);
bool removePlayerFromChunks(std::vector<Chunk*> chunks, CNSocket* sock);
void addPlayerToChunks(std::vector<Chunk*> chunks, CNSocket* sock);
void updatePlayerPosition(CNSocket* sock, int X, int Y, int Z);
std::list<CNSocket*> getNearbyPlayers(int X, int Y, int dist);
void updatePlayerPosition(CNSocket* sock, int X, int Y, int Z, int angle);
void updatePlayerChunk(CNSocket* sock, int X, int Y, uint64_t instanceID);
void sendPlayerTo(CNSocket* sock, int X, int Y, int Z, uint64_t I);
void sendPlayerTo(CNSocket* sock, int X, int Y, int Z);
void sendToViewable(CNSocket* sock, void* buf, uint32_t type, size_t size);
void enterPlayer(CNSocket* sock, CNPacketData* data);
void loadPlayer(CNSocket* sock, CNPacketData* data);
@@ -37,6 +48,7 @@ namespace PlayerManager {
void launchPlayer(CNSocket* sock, CNPacketData* data);
void ziplinePlayer(CNSocket* sock, CNPacketData* data);
void movePlatformPlayer(CNSocket* sock, CNPacketData* data);
void moveSliderPlayer(CNSocket* sock, CNPacketData* data);
void moveSlopePlayer(CNSocket* sock, CNPacketData* data);
void gotoPlayer(CNSocket* sock, CNPacketData* data);
void setSpecialPlayer(CNSocket* sock, CNPacketData* data);
@@ -45,6 +57,7 @@ namespace PlayerManager {
void exitGame(CNSocket* sock, CNPacketData* data);
void setSpecialSwitchPlayer(CNSocket* sock, CNPacketData* data);
void setGMSpecialSwitchPlayer(CNSocket* sock, CNPacketData* data);
void changePlayerGuide(CNSocket *sock, CNPacketData *data);
void enterPlayerVehicle(CNSocket* sock, CNPacketData* data);
@@ -55,4 +68,7 @@ namespace PlayerManager {
bool isAccountInUse(int accountId);
void exitDuplicate(int accountId);
void setSpecialState(CNSocket* sock, CNPacketData* data);
Player *getPlayerFromID(int32_t iID);
CNSocket *getSockFromID(int32_t iID);
}

658
src/TableData.cpp Normal file
View File

@@ -0,0 +1,658 @@
#include "TableData.hpp"
#include "NPCManager.hpp"
#include "TransportManager.hpp"
#include "ItemManager.hpp"
#include "settings.hpp"
#include "MissionManager.hpp"
#include "MobManager.hpp"
#include "ChunkManager.hpp"
#include "NanoManager.hpp"
#include "contrib/JSON.hpp"
#include <fstream>
std::map<int32_t, std::vector<WarpLocation>> TableData::RunningSkywayRoutes;
std::map<int32_t, int> TableData::RunningNPCRotations;
std::map<int32_t, int> TableData::RunningNPCMapNumbers;
std::map<int32_t, BaseNPC*> TableData::RunningMobs;
class TableException : public std::exception {
public:
std::string msg;
TableException(std::string m) : std::exception() { msg = m; }
const char *what() const throw() { return msg.c_str(); }
};
void TableData::init() {
int32_t nextId = 0;
// load NPCs from NPC.json
try {
std::ifstream inFile(settings::NPCJSON);
nlohmann::json npcData;
// read file into json
inFile >> npcData;
for (nlohmann::json::iterator _npc = npcData.begin(); _npc != npcData.end(); _npc++) {
auto npc = _npc.value();
int instanceID = npc.find("mapNum") == npc.end() ? INSTANCE_OVERWORLD : (int)npc["mapNum"];
BaseNPC *tmp = new BaseNPC(npc["x"], npc["y"], npc["z"], npc["angle"], instanceID, npc["id"], nextId);
NPCManager::NPCs[nextId] = tmp;
NPCManager::updateNPCPosition(nextId, npc["x"], npc["y"], npc["z"]);
nextId++;
if (npc["id"] == 641 || npc["id"] == 642)
NPCManager::RespawnPoints.push_back({ npc["x"], npc["y"], ((int)npc["z"]) + RESURRECT_HEIGHT, instanceID });
}
}
catch (const std::exception& err) {
std::cerr << "[FATAL] Malformed NPCs.json file! Reason:" << err.what() << std::endl;
terminate(0);
}
// load everything else from xdttable
std::cout << "[INFO] Parsing xdt.json..." << std::endl;
std::ifstream infile(settings::XDTJSON);
nlohmann::json xdtData;
// read file into json
infile >> xdtData;
// data we'll need for summoned mobs
NPCManager::NPCData = xdtData["m_pNpcTable"]["m_pNpcData"];
try {
// load warps
nlohmann::json warpData = xdtData["m_pInstanceTable"]["m_pWarpData"];
for (nlohmann::json::iterator _warp = warpData.begin(); _warp != warpData.end(); _warp++) {
auto warp = _warp.value();
WarpLocation warpLoc = { warp["m_iToX"], warp["m_iToY"], warp["m_iToZ"], warp["m_iToMapNum"], warp["m_iIsInstance"], warp["m_iLimit_TaskID"], warp["m_iNpcNumber"] };
int warpID = warp["m_iWarpNumber"];
NPCManager::Warps[warpID] = warpLoc;
}
std::cout << "[INFO] Populated " << NPCManager::Warps.size() << " Warps" << std::endl;
// load transport routes and locations
nlohmann::json transRouteData = xdtData["m_pTransportationTable"]["m_pTransportationData"];
nlohmann::json transLocData = xdtData["m_pTransportationTable"]["m_pTransportationWarpLocation"];
for (nlohmann::json::iterator _tLoc = transLocData.begin(); _tLoc != transLocData.end(); _tLoc++) {
auto tLoc = _tLoc.value();
TransportLocation transLoc = { tLoc["m_iNPCID"], tLoc["m_iXpos"], tLoc["m_iYpos"], tLoc["m_iZpos"] };
TransportManager::Locations[tLoc["m_iLocationID"]] = transLoc;
}
std::cout << "[INFO] Loaded " << TransportManager::Locations.size() << " S.C.A.M.P.E.R. locations" << std::endl;
for (nlohmann::json::iterator _tRoute = transRouteData.begin(); _tRoute != transRouteData.end(); _tRoute++) {
auto tRoute = _tRoute.value();
TransportRoute transRoute = { tRoute["m_iMoveType"], tRoute["m_iStartLocation"], tRoute["m_iEndLocation"],
tRoute["m_iCost"] , tRoute["m_iSpeed"], tRoute["m_iRouteNum"] };
TransportManager::Routes[tRoute["m_iVehicleID"]] = transRoute;
}
std::cout << "[INFO] Loaded " << TransportManager::Routes.size() << " transportation routes" << std::endl;
// load mission-related data
nlohmann::json tasks = xdtData["m_pMissionTable"]["m_pMissionData"];
for (auto _task = tasks.begin(); _task != tasks.end(); _task++) {
auto task = _task.value();
// rewards
if (task["m_iSUReward"] != 0) {
auto _rew = xdtData["m_pMissionTable"]["m_pRewardData"][(int)task["m_iSUReward"]];
Reward *rew = new Reward(_rew["m_iMissionRewardID"], _rew["m_iMissionRewarItemType"],
_rew["m_iMissionRewardItemID"], _rew["m_iCash"], _rew["m_iFusionMatter"]);
MissionManager::Rewards[task["m_iHTaskID"]] = rew;
}
// everything else lol. see TaskData comment.
MissionManager::Tasks[task["m_iHTaskID"]] = new TaskData(task);
}
std::cout << "[INFO] Loaded mission-related data" << std::endl;
// load all item data. i'm sorry. it has to be done
const char* setNames[12] = { "m_pBackItemTable", "m_pFaceItemTable", "m_pGlassItemTable", "m_pHatItemTable",
"m_pHeadItemTable", "m_pPantsItemTable", "m_pShirtsItemTable", "m_pShoesItemTable", "m_pWeaponItemTable",
"m_pVehicleItemTable", "m_pGeneralItemTable", "m_pChestItemTable" };
nlohmann::json itemSet;
for (int i = 0; i < 12; i++) {
itemSet = xdtData[setNames[i]]["m_pItemData"];
for (nlohmann::json::iterator _item = itemSet.begin(); _item != itemSet.end(); _item++) {
auto item = _item.value();
int typeOverride = getItemType(i); // used for special cases where iEquipLoc doesn't indicate item type
ItemManager::ItemData[std::pair<int32_t, int32_t>(item["m_iItemNumber"], typeOverride != -1 ? typeOverride : (int)item["m_iEquipLoc"])]
= { item["m_iTradeAble"] == 1, item["m_iSellAble"] == 1, item["m_iItemPrice"], item["m_iItemSellPrice"], item["m_iStackNumber"], i > 9 ? 0 : (int)item["m_iMinReqLev"], i > 9 ? 1 : (int)item["m_iRarity"], i > 9 ? 0 : (int)item["m_iPointRat"], i > 9 ? 0 : (int)item["m_iGroupRat"], i > 9 ? 0 : (int)item["m_iDefenseRat"], i > 9 ? 0 : (int)item["m_iReqSex"] };
}
}
std::cout << "[INFO] Loaded " << ItemManager::ItemData.size() << " items" << std::endl;
// load player limits from m_pAvatarTable.m_pAvatarGrowData
nlohmann::json growth = xdtData["m_pAvatarTable"]["m_pAvatarGrowData"];
for (int i = 0; i < 37; i++) {
MissionManager::AvatarGrowth[i] = growth[i];
}
// load vendor listings
nlohmann::json listings = xdtData["m_pVendorTable"]["m_pItemData"];
for (nlohmann::json::iterator _lst = listings.begin(); _lst != listings.end(); _lst++) {
auto lst = _lst.value();
VendorListing vListing = { lst["m_iSortNumber"], lst["m_iItemType"], lst["m_iitemID"] };
ItemManager::VendorTables[lst["m_iNpcNumber"]].push_back(vListing);
}
std::cout << "[INFO] Loaded " << ItemManager::VendorTables.size() << " vendor tables" << std::endl;
// load crocpot entries
nlohmann::json crocs = xdtData["m_pCombiningTable"]["m_pCombiningData"];
for (nlohmann::json::iterator croc = crocs.begin(); croc != crocs.end(); croc++) {
CrocPotEntry crocEntry = { croc.value()["m_iStatConstant"], croc.value()["m_iLookConstant"], croc.value()["m_fLevelGapStandard"],
croc.value()["m_fSameGrade"], croc.value()["m_fOneGrade"], croc.value()["m_fTwoGrade"], croc.value()["m_fThreeGrade"] };
ItemManager::CrocPotTable[croc.value()["m_iLevelGap"]] = crocEntry;
}
std::cout << "[INFO] Loaded " << ItemManager::CrocPotTable.size() << " croc pot value sets" << std::endl;
// load nano info
nlohmann::json nanoInfo = xdtData["m_pNanoTable"]["m_pNanoData"];
for (nlohmann::json::iterator _nano = nanoInfo.begin(); _nano != nanoInfo.end(); _nano++) {
auto nano = _nano.value();
NanoData nanoData;
nanoData.style = nano["m_iStyle"];
NanoManager::NanoTable[nano["m_iNanoNumber"]] = nanoData;
}
std::cout << "[INFO] Loaded " << NanoManager::NanoTable.size() << " nanos" << std::endl;
}
catch (const std::exception& err) {
std::cerr << "[FATAL] Malformed xdt.json file! Reason:" << err.what() << std::endl;
terminate(0);
}
// load temporary mob dump
try {
std::ifstream inFile(settings::MOBJSON);
nlohmann::json npcData;
// read file into json
inFile >> npcData;
for (nlohmann::json::iterator _npc = npcData.begin(); _npc != npcData.end(); _npc++) {
auto npc = _npc.value();
auto td = NPCManager::NPCData[(int)npc["iNPCType"]];
uint64_t instanceID = npc.find("iMapNum") == npc.end() ? INSTANCE_OVERWORLD : (int)npc["iMapNum"];
Mob *tmp = new Mob(npc["iX"], npc["iY"], npc["iZ"], npc["iAngle"], instanceID, npc["iNPCType"], npc["iHP"], td, nextId);
NPCManager::NPCs[nextId] = tmp;
MobManager::Mobs[nextId] = (Mob*)NPCManager::NPCs[nextId];
NPCManager::updateNPCPosition(nextId, npc["iX"], npc["iY"], npc["iZ"]);
nextId++;
}
std::cout << "[INFO] Populated " << NPCManager::NPCs.size() << " NPCs" << std::endl;
}
catch (const std::exception& err) {
std::cerr << "[FATAL] Malformed mobs.json file! Reason:" << err.what() << std::endl;
terminate(0);
}
loadDrops();
loadPaths(&nextId); // load paths
loadGruntwork(&nextId);
NPCManager::nextId = nextId;
}
/*
* Some item categories either don't possess iEquipLoc or use a different value for item type.
*/
int TableData::getItemType(int itemSet) {
int overriden;
switch (itemSet) {
case 11: // Chest items don't have iEquipLoc and are type 9.
overriden = 9;
break;
case 10: // General items don't have iEquipLoc and are type 7.
overriden = 7;
break;
case 9: // Vehicles have iEquipLoc 8, but type 10.
overriden = 10;
break;
default:
overriden = -1;
}
return overriden;
}
/*
* Load paths from paths JSON.
*/
void TableData::loadPaths(int* nextId) {
try {
std::ifstream inFile(settings::PATHJSON);
nlohmann::json pathData;
// read file into json
inFile >> pathData;
// skyway paths
nlohmann::json pathDataSkyway = pathData["skyway"];
for (nlohmann::json::iterator skywayPath = pathDataSkyway.begin(); skywayPath != pathDataSkyway.end(); skywayPath++) {
constructPathSkyway(skywayPath);
}
std::cout << "[INFO] Loaded " << TransportManager::SkywayPaths.size() << " skyway paths" << std::endl;
// slider circuit
int sliders = 0;
nlohmann::json pathDataSlider = pathData["slider"];
for (nlohmann::json::iterator _sliderPoint = pathDataSlider.begin(); _sliderPoint != pathDataSlider.end(); _sliderPoint++) {
auto sliderPoint = _sliderPoint.value();
if (sliderPoint["stop"] && sliders % 2 == 0) { // check if this point in the circuit is a stop
// spawn a slider
BaseNPC* slider = new BaseNPC(sliderPoint["iX"], sliderPoint["iY"], sliderPoint["iZ"], 0, INSTANCE_OVERWORLD, 1, (*nextId)++, NPC_BUS);
NPCManager::NPCs[slider->appearanceData.iNPC_ID] = slider;
NPCManager::updateNPCPosition(slider->appearanceData.iNPC_ID, slider->appearanceData.iX, slider->appearanceData.iY, slider->appearanceData.iZ);
// set slider path to a rotation of the circuit
constructPathSlider(pathDataSlider, 0, slider->appearanceData.iNPC_ID);
sliders++;
}
}
// npc paths
nlohmann::json pathDataNPC = pathData["npc"];
for (nlohmann::json::iterator npcPath = pathDataNPC.begin(); npcPath != pathDataNPC.end(); npcPath++) {
constructPathNPC(npcPath);
}
// mob paths
pathDataNPC = pathData["mob"];
for (nlohmann::json::iterator npcPath = pathDataNPC.begin(); npcPath != pathDataNPC.end(); npcPath++) {
for (auto& pair : MobManager::Mobs) {
if (pair.second->appearanceData.iNPCType == npcPath.value()["iNPCType"]) {
std::cout << "[INFO] Using static path for mob " << pair.second->appearanceData.iNPCType << " with ID " << pair.first << std::endl;
auto firstPoint = npcPath.value()["points"][0];
if (firstPoint["iX"] != pair.second->spawnX || firstPoint["iY"] != pair.second->spawnY) {
std::cout << "[FATAL] The first point of the route for mob " << pair.first <<
" (type " << pair.second->appearanceData.iNPCType << ") does not correspond with its spawn point." << std::endl;
terminate(0);
}
constructPathNPC(npcPath, pair.first);
pair.second->staticPath = true;
break; // only one NPC per path
}
}
}
std::cout << "[INFO] Loaded " << TransportManager::NPCQueues.size() << " NPC paths" << std::endl;
}
catch (const std::exception& err) {
std::cerr << "[FATAL] Malformed paths.json file! Reason:" << err.what() << std::endl;
terminate(0);
}
}
/*
* Load drops data from JSON.
* This has to be called after reading xdt because it reffers to ItemData!!!
*/
void TableData::loadDrops() {
try {
std::ifstream inFile(settings::DROPSJSON);
nlohmann::json dropData;
// read file into json
inFile >> dropData;
// MobDropChances
nlohmann::json mobDropChances = dropData["MobDropChances"];
for (nlohmann::json::iterator _dropChance = mobDropChances.begin(); _dropChance != mobDropChances.end(); _dropChance++) {
auto dropChance = _dropChance.value();
MobDropChance toAdd = {};
toAdd.dropChance = (int)dropChance["DropChance"];
for (nlohmann::json::iterator _cratesRatio = dropChance["CratesRatio"].begin(); _cratesRatio != dropChance["CratesRatio"].end(); _cratesRatio++) {
toAdd.cratesRatio.push_back((int)_cratesRatio.value());
}
MobManager::MobDropChances[(int)dropChance["Type"]] = toAdd;
}
// MobDrops
nlohmann::json mobDrops = dropData["MobDrops"];
for (nlohmann::json::iterator _drop = mobDrops.begin(); _drop != mobDrops.end(); _drop++) {
auto drop = _drop.value();
MobDrop toAdd = {};
for (nlohmann::json::iterator _crates = drop["CrateIDs"].begin(); _crates != drop["CrateIDs"].end(); _crates++) {
toAdd.crateIDs.push_back((int)_crates.value());
}
toAdd.dropChanceType = (int)drop["DropChance"];
// Check if DropChance exists
if (MobManager::MobDropChances.find(toAdd.dropChanceType) == MobManager::MobDropChances.end()) {
throw TableException(" MobDropChance not found: " + std::to_string((toAdd.dropChanceType)));
}
// Check if number of crates is correct
if (!(MobManager::MobDropChances[(int)drop["DropChance"]].cratesRatio.size() == toAdd.crateIDs.size())) {
throw TableException(" DropType " + std::to_string((int)drop["DropType"]) + " contains invalid number of crates");
}
toAdd.taros = (int)drop["Taros"];
toAdd.fm = (int)drop["FM"];
toAdd.boosts = (int)drop["Boosts"];
MobManager::MobDrops[(int)drop["DropType"]] = toAdd;
}
std::cout << "[INFO] Loaded " << MobManager::MobDrops.size() << " Mob Drop Types"<< std::endl;
// Rarity Ratios
nlohmann::json rarities = dropData["RarityRatios"];
for (nlohmann::json::iterator _rarity = rarities.begin(); _rarity != rarities.end(); _rarity++) {
auto rarity = _rarity.value();
std::vector<int> toAdd;
for (nlohmann::json::iterator _ratio = rarity["Ratio"].begin(); _ratio != rarity["Ratio"].end(); _ratio++){
toAdd.push_back((int)_ratio.value());
}
ItemManager::RarityRatios[(int)rarity["Type"]] = toAdd;
}
// Crates
nlohmann::json crates = dropData["Crates"];
for (nlohmann::json::iterator _crate = crates.begin(); _crate != crates.end(); _crate++) {
auto crate = _crate.value();
Crate toAdd;
toAdd.rarityRatioId = (int)crate["RarityRatio"];
for (nlohmann::json::iterator _itemSet = crate["ItemSets"].begin(); _itemSet != crate["ItemSets"].end(); _itemSet++) {
toAdd.itemSets.push_back((int)_itemSet.value());
}
ItemManager::Crates[(int)crate["Id"]] = toAdd;
}
// Crate Items
nlohmann::json items = dropData["Items"];
int itemCount = 0;
for (nlohmann::json::iterator _item = items.begin(); _item != items.end(); _item++) {
auto item = _item.value();
std::pair<int32_t, int32_t> itemSetkey = std::make_pair((int)item["ItemSet"], (int)item["Rarity"]);
std::pair<int32_t, int32_t> itemDataKey = std::make_pair((int)item["Id"], (int)item["Type"]);
if (ItemManager::ItemData.find(itemDataKey) == ItemManager::ItemData.end()) {
char buff[255];
sprintf(buff, "Unknown item with Id %d and Type %d", (int)item["Id"], (int)item["Type"]);
throw TableException(std::string(buff));
}
std::map<std::pair<int32_t, int32_t>, Item>::iterator toAdd = ItemManager::ItemData.find(itemDataKey);
// if item collection doesn't exist, start a new one
if (ItemManager::CrateItems.find(itemSetkey) == ItemManager::CrateItems.end()) {
std::vector<std::map<std::pair<int32_t, int32_t>, Item>::iterator> vector;
vector.push_back(toAdd);
ItemManager::CrateItems[itemSetkey] = vector;
} else // else add a new element to existing collection
ItemManager::CrateItems[itemSetkey].push_back(toAdd);
itemCount++;
}
std::cout << "[INFO] Loaded " << ItemManager::Crates.size() << " Crates containing "
<< itemCount << " items" << std::endl;
}
catch (const std::exception& err) {
std::cerr << "[FATAL] Malformed drops.json file! Reason:" << err.what() << std::endl;
terminate(0);
}
}
/*
* Create a full and properly-paced path by interpolating between keyframes.
*/
void TableData::constructPathSkyway(nlohmann::json::iterator _pathData) {
auto pathData = _pathData.value();
// Interpolate
nlohmann::json pathPoints = pathData["points"];
std::queue<WarpLocation> points;
nlohmann::json::iterator _point = pathPoints.begin();
auto point = _point.value();
WarpLocation last = { point["iX"] , point["iY"] , point["iZ"] }; // start pos
// use some for loop trickery; start position should not be a point
for (_point++; _point != pathPoints.end(); _point++) {
point = _point.value();
WarpLocation coords = { point["iX"] , point["iY"] , point["iZ"] };
TransportManager::lerp(&points, last, coords, pathData["iMonkeySpeed"]);
points.push(coords); // add keyframe to the queue
last = coords; // update start pos
}
TransportManager::SkywayPaths[pathData["iRouteID"]] = points;
}
void TableData::constructPathSlider(nlohmann::json points, int rotations, int sliderID) {
std::queue<WarpLocation> route;
std::rotate(points.begin(), points.begin() + rotations, points.end()); // rotate points
nlohmann::json::iterator _point = points.begin(); // iterator
auto point = _point.value();
WarpLocation from = { point["iX"] , point["iY"] , point["iZ"] }; // point A coords
int stopTime = point["stop"] ? SLIDER_STOP_TICKS : 0; // arbitrary stop length
for (_point++; _point != points.end(); _point++) { // loop through all point Bs
point = _point.value();
for (int i = 0; i < stopTime + 1; i++) // repeat point if it's a stop
route.push(from); // add point A to the queue
WarpLocation to = { point["iX"] , point["iY"] , point["iZ"] }; // point B coords
float curve = 1;
if (stopTime > 0) { // point A is a stop
curve = 2.0f;
} else if (point["stop"]) { // point B is a stop
curve = 0.35f;
}
TransportManager::lerp(&route, from, to, SLIDER_SPEED, curve); // lerp from A to B (arbitrary speed)
from = to; // update point A
stopTime = point["stop"] ? SLIDER_STOP_TICKS : 0;
}
std::rotate(points.rbegin(), points.rbegin() + rotations, points.rend()); // undo rotation
TransportManager::NPCQueues[sliderID] = route;
}
void TableData::constructPathNPC(nlohmann::json::iterator _pathData, int32_t id) {
auto pathData = _pathData.value();
// Interpolate
nlohmann::json pathPoints = pathData["points"];
std::queue<WarpLocation> points;
nlohmann::json::iterator _point = pathPoints.begin();
auto point = _point.value();
WarpLocation from = { point["iX"] , point["iY"] , point["iZ"] }; // point A coords
int stopTime = point["stop"];
for (_point++; _point != pathPoints.end(); _point++) { // loop through all point Bs
point = _point.value();
for(int i = 0; i < stopTime + 1; i++) // repeat point if it's a stop
points.push(from); // add point A to the queue
WarpLocation to = { point["iX"] , point["iY"] , point["iZ"] }; // point B coords
TransportManager::lerp(&points, from, to, pathData["iBaseSpeed"]); // lerp from A to B
from = to; // update point A
stopTime = point["stop"];
}
if (id == 0)
id = pathData["iNPCID"];
TransportManager::NPCQueues[id] = points;
}
// load gruntwork output; if it exists
void TableData::loadGruntwork(int32_t *nextId) {
try {
std::ifstream inFile(settings::GRUNTWORKJSON);
nlohmann::json gruntwork;
// skip if there's no gruntwork to load
if (inFile.fail())
return;
inFile >> gruntwork;
// skyway paths
auto skyway = gruntwork["skyway"];
for (auto _route = skyway.begin(); _route != skyway.end(); _route++) {
auto route = _route.value();
std::vector<WarpLocation> points;
for (auto _point = route["points"].begin(); _point != route["points"].end(); _point++) {
auto point = _point.value();
points.push_back(WarpLocation{point["x"], point["y"], point["z"]});
}
RunningSkywayRoutes[(int)route["iRouteID"]] = points;
}
// npc rotations
auto npcRot = gruntwork["rotations"];
for (auto _rot = npcRot.begin(); _rot != npcRot.end(); _rot++) {
int32_t npcID = _rot.value()["iNPCID"];
int angle = _rot.value()["iAngle"];
if (NPCManager::NPCs.find(npcID) == NPCManager::NPCs.end())
continue; // NPC not found
BaseNPC* npc = NPCManager::NPCs[npcID];
npc->appearanceData.iAngle = angle;
RunningNPCRotations[npcID] = angle;
}
// npc map numbers
auto npcMap = gruntwork["instances"];
for (auto _map = npcMap.begin(); _map != npcMap.end(); _map++) {
int32_t npcID = _map.value()["iNPCID"];
uint64_t instanceID = _map.value()["iMapNum"];
if (NPCManager::NPCs.find(npcID) == NPCManager::NPCs.end())
continue; // NPC not found
BaseNPC* npc = NPCManager::NPCs[npcID];
NPCManager::updateNPCInstance(npc->appearanceData.iNPC_ID, instanceID);
RunningNPCMapNumbers[npcID] = instanceID;
}
// mobs
auto mobs = gruntwork["mobs"];
for (auto _mob = mobs.begin(); _mob != mobs.end(); _mob++) {
auto mob = _mob.value();
BaseNPC *npc;
int id = (*nextId)++;
uint64_t instanceID = mob.find("iMapNum") == mob.end() ? INSTANCE_OVERWORLD : (int)mob["iMapNum"];
if (NPCManager::NPCData[(int)mob["iNPCType"]]["m_iTeam"] == 2) {
npc = new Mob(mob["iX"], mob["iY"], mob["iZ"], instanceID, mob["iNPCType"],
NPCManager::NPCData[(int)mob["iNPCType"]], id);
// re-enable respawning
((Mob*)npc)->summoned = false;
MobManager::Mobs[npc->appearanceData.iNPC_ID] = (Mob*)npc;
} else {
npc = new BaseNPC(mob["iX"], mob["iY"], mob["iZ"], mob["iAngle"], instanceID, mob["iNPCType"], id);
}
NPCManager::NPCs[npc->appearanceData.iNPC_ID] = npc;
TableData::RunningMobs[npc->appearanceData.iNPC_ID] = npc;
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, mob["iX"], mob["iY"], mob["iZ"]);
}
std::cout << "[INFO] Loaded gruntwork.json" << std::endl;
}
catch (const std::exception& err) {
std::cerr << "[FATAL] Malformed gruntwork.json file! Reason:" << err.what() << std::endl;
terminate(0);
}
}
// write gruntwork output to file
void TableData::flush() {
std::ofstream file(settings::GRUNTWORKJSON);
nlohmann::json gruntwork;
for (auto& pair : RunningSkywayRoutes) {
nlohmann::json route;
route["iRouteID"] = (int)pair.first;
route["iMonkeySpeed"] = 1500;
std::cout << "serializing mss route " << (int)pair.first << std::endl;
for (WarpLocation& point : pair.second) {
nlohmann::json tmp;
tmp["x"] = point.x;
tmp["y"] = point.y;
tmp["z"] = point.z;
route["points"].push_back(tmp);
}
gruntwork["skyway"].push_back(route);
}
for (auto& pair : RunningNPCRotations) {
nlohmann::json rotation;
rotation["iNPCID"] = (int)pair.first;
rotation["iAngle"] = pair.second;
gruntwork["rotations"].push_back(rotation);
}
for (auto& pair : RunningNPCMapNumbers) {
nlohmann::json mapNumber;
mapNumber["iNPCID"] = (int)pair.first;
mapNumber["iMapNum"] = pair.second;
gruntwork["instances"].push_back(mapNumber);
}
for (auto& pair : RunningMobs) {
nlohmann::json mob;
BaseNPC *npc = pair.second;
if (NPCManager::NPCs.find(pair.first) == NPCManager::NPCs.end())
continue;
int x, y, z, hp;
if (npc->npcClass == NPC_MOB) {
Mob *m = (Mob*)npc;
x = m->spawnX;
y = m->spawnY;
z = m->spawnZ;
hp = m->maxHealth;
} else {
x = npc->appearanceData.iX;
y = npc->appearanceData.iY;
z = npc->appearanceData.iZ;
hp = npc->appearanceData.iHP;
}
// NOTE: this format deviates slightly from the one in mobs.json
mob["iNPCType"] = (int)npc->appearanceData.iNPCType;
mob["iHP"] = hp;
mob["iX"] = x;
mob["iY"] = y;
mob["iZ"] = z;
mob["iMapNum"] = MAPNUM(npc->instanceID);
// this is a bit imperfect, since this is a live angle, not a spawn angle so it'll change often, but eh
mob["iAngle"] = npc->appearanceData.iAngle;
// it's called mobs, but really it's everything
gruntwork["mobs"].push_back(mob);
}
file << gruntwork << std::endl;
}

24
src/TableData.hpp Normal file
View File

@@ -0,0 +1,24 @@
#pragma once
#include <map>
#include "contrib/JSON.hpp"
#include "NPCManager.hpp"
namespace TableData {
extern std::map<int32_t, std::vector<WarpLocation>> RunningSkywayRoutes;
extern std::map<int32_t, int> RunningNPCRotations;
extern std::map<int32_t, int> RunningNPCMapNumbers;
extern std::map<int32_t, BaseNPC*> RunningMobs;
void init();
void cleanup();
void loadGruntwork(int32_t*);
void flush();
int getItemType(int);
void loadPaths(int*);
void loadDrops();
void constructPathSkyway(nlohmann::json::iterator);
void constructPathSlider(nlohmann::json, int, int);
void constructPathNPC(nlohmann::json::iterator, int id=0);
}

View File

@@ -1,18 +1,367 @@
#include "CNShardServer.hpp"
#include "CNStructs.hpp"
#include "PlayerManager.hpp"
#include "NanoManager.hpp"
#include "TransportManager.hpp"
#include "TableData.hpp"
#include "MobManager.hpp"
#include <unordered_map>
#include <cmath>
std::map<int32_t, TransportRoute> TransportManager::Routes;
std::map<int32_t, TransportLocation> TransportManager::Locations;
std::map<int32_t, std::queue<WarpLocation>> TransportManager::SkywayPaths;
std::unordered_map<CNSocket*, std::queue<WarpLocation>> TransportManager::SkywayQueues;
std::unordered_map<int32_t, std::queue<WarpLocation>> TransportManager::NPCQueues;
void TransportManager::init() {
REGISTER_SHARD_TIMER(tickTransportationSystem, 1000);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_REGIST_TRANSPORTATION_LOCATION, transportRegisterLocationHandler);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_WARP_USE_TRANSPORTATION, transportWarpHandler);
}
void TransportManager::transportRegisterLocationHandler(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_REGIST_TRANSPORTATION_LOCATION))
return; // malformed packet
sP_CL2FE_REQ_REGIST_TRANSPORTATION_LOCATION* transport = (sP_CL2FE_REQ_REGIST_TRANSPORTATION_LOCATION*)data->buf;
Player* plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return;
bool newReg = false; // this is a new registration
//std::cout << "request to register transport, eTT " << transport->eTT << ", locID " << transport->iLocationID << ", npc " << transport->iNPC_ID << std::endl;
if (transport->eTT == 1) { // S.C.A.M.P.E.R.
if (transport->iLocationID < 1 || transport->iLocationID > 31) { // sanity check
std::cout << "[WARN] S.C.A.M.P.E.R. location ID " << transport->iLocationID << " is out of bounds" << std::endl;
INITSTRUCT(sP_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_FAIL, failResp);
failResp.eTT = transport->eTT;
failResp.iErrorCode = 0; // TODO: review what error code to use here
failResp.iLocationID = transport->iLocationID;
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_FAIL, sizeof(sP_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_FAIL));
return;
}
// update registration bitfield using bitmask
uint32_t newScamperFlag = plr->iWarpLocationFlag | (plr->accountLevel <= 40 ? INT32_MAX : (1UL << (transport->iLocationID - 1)));
if (newScamperFlag != plr->iWarpLocationFlag) {
plr->iWarpLocationFlag = newScamperFlag;
newReg = true;
}
} else if (transport->eTT == 2) { // Monkey Skyway System
if (transport->iLocationID < 1 || transport->iLocationID > 127) { // sanity check
std::cout << "[WARN] Skyway location ID " << transport->iLocationID << " is out of bounds" << std::endl;
INITSTRUCT(sP_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_FAIL, failResp);
failResp.eTT = transport->eTT;
failResp.iErrorCode = 0; // TODO: review what error code to use here
failResp.iLocationID = transport->iLocationID;
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_FAIL, sizeof(sP_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_FAIL));
return;
}
/*
* assuming the two bitfields are just stuck together to make a longer one, do a similar operation
*/
if (plr->accountLevel <= 40) {
plr->aSkywayLocationFlag[0] = INT64_MAX;
plr->aSkywayLocationFlag[1] = INT64_MAX;
newReg = true;
} else {
int index = transport->iLocationID > 64 ? 1 : 0;
uint64_t newMonkeyFlag = plr->aSkywayLocationFlag[index] | (1ULL << (index ? transport->iLocationID - 65 : transport->iLocationID - 1));
if (newMonkeyFlag != plr->aSkywayLocationFlag[index]) {
plr->aSkywayLocationFlag[index] = newMonkeyFlag;
newReg = true;
}
}
} else {
std::cout << "[WARN] Unknown mode of transport; eTT = " << transport->eTT << std::endl;
INITSTRUCT(sP_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_FAIL, failResp);
failResp.eTT = transport->eTT;
failResp.iErrorCode = 0; // TODO: review what error code to use here
failResp.iLocationID = transport->iLocationID;
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_FAIL, sizeof(sP_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_FAIL));
return;
}
if (!newReg)
return; // don't send new registration message
INITSTRUCT(sP_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_SUCC, resp);
// response parameters
resp.eTT = transport->eTT;
resp.iLocationID = transport->iLocationID;
resp.iWarpLocationFlag = plr->iWarpLocationFlag;
resp.aWyvernLocationFlag[0] = plr->aSkywayLocationFlag[0];
resp.aWyvernLocationFlag[1] = plr->aSkywayLocationFlag[1];
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_SUCC, sizeof(sP_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_SUCC));
}
void TransportManager::transportWarpHandler(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_PC_WARP_USE_TRANSPORTATION))
return; // malformed packet
sP_CL2FE_REQ_PC_WARP_USE_TRANSPORTATION* req = (sP_CL2FE_REQ_PC_WARP_USE_TRANSPORTATION*)data->buf;
Player* plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return;
/*
* req:
* eIL -- inventory type
* iNPC_ID -- the ID of the NPC who is warping you
* iTransporationID -- iVehicleID
* iSlotNum -- inventory slot number
*/
if (Routes.find(req->iTransporationID) == Routes.end() || Routes[req->iTransporationID].cost > plr->money) { // sanity check
INITSTRUCT(sP_FE2CL_REP_PC_WARP_USE_TRANSPORTATION_FAIL, failResp);
failResp.iErrorCode = 0; // TODO: error code
failResp.iTransportationID = req->iTransporationID;
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_WARP_USE_TRANSPORTATION_FAIL, sizeof(sP_FE2CL_REP_PC_WARP_USE_TRANSPORTATION_FAIL));
return;
}
TransportRoute route = Routes[req->iTransporationID];
plr->money -= route.cost;
TransportLocation target;
PlayerView& plrv = PlayerManager::players[sock];
switch (route.type) {
case 1: // S.C.A.M.P.E.R.
target = Locations[route.end];
plr->x = target.x;
plr->y = target.y;
plr->z = target.z;
/*
* Not strictly necessary since there isn't a valid SCAMPER that puts you in the
* same map tile you were already in, but we might as well force an NPC reload.
*/
PlayerManager::removePlayerFromChunks(plrv.currentChunks, sock);
plrv.currentChunks.clear();
plrv.chunkPos = std::make_tuple(0, 0, plrv.plr->instanceID);
break;
case 2: // Monkey Skyway
if (SkywayPaths.find(route.mssRouteNum) != SkywayPaths.end()) { // check if route exists
NanoManager::summonNano(sock, -1); // make sure that no nano is active during the ride
SkywayQueues[sock] = SkywayPaths[route.mssRouteNum]; // set socket point queue to route
break;
} else if (TableData::RunningSkywayRoutes.find(route.mssRouteNum) != TableData::RunningSkywayRoutes.end()) {
std::vector<WarpLocation>* _route = &TableData::RunningSkywayRoutes[route.mssRouteNum];
NanoManager::summonNano(sock, -1);
testMssRoute(sock, _route);
break;
}
// refund and send alert packet
plr->money += route.cost;
INITSTRUCT(sP_FE2CL_ANNOUNCE_MSG, alert);
alert.iAnnounceType = 0; // don't think this lets us make a confirm dialog
alert.iDuringTime = 3;
U8toU16("Skyway route " + std::to_string(route.mssRouteNum) + " isn't pathed yet. You will not be charged any taros.", (char16_t*)alert.szAnnounceMsg, sizeof(alert.szAnnounceMsg));
sock->sendPacket((void*)&alert, P_FE2CL_ANNOUNCE_MSG, sizeof(sP_FE2CL_ANNOUNCE_MSG));
std::cout << "[WARN] MSS route " << route.mssRouteNum << " not pathed" << std::endl;
break;
default:
std::cout << "[WARN] Unknown tranportation type " << route.type << std::endl;
break;
}
INITSTRUCT(sP_FE2CL_REP_PC_WARP_USE_TRANSPORTATION_SUCC, resp);
// response parameters
resp.eTT = route.type;
resp.iCandy = plr->money;
resp.iX = plr->x;
resp.iY = plr->y;
resp.iZ = plr->z;
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_WARP_USE_TRANSPORTATION_SUCC, sizeof(sP_FE2CL_REP_PC_WARP_USE_TRANSPORTATION_SUCC));
}
void TransportManager::testMssRoute(CNSocket *sock, std::vector<WarpLocation>* route) {
int speed = 1500; // TODO: make this adjustable
std::queue<WarpLocation> path;
WarpLocation last = route->front(); // start pos
for (int i = 1; i < route->size(); i++) {
WarpLocation coords = route->at(i);
TransportManager::lerp(&path, last, coords, speed);
path.push(coords); // add keyframe to the queue
last = coords; // update start pos
}
SkywayQueues[sock] = path;
}
void TransportManager::tickTransportationSystem(CNServer* serv, time_t currTime) {
stepNPCPathing();
stepSkywaySystem();
}
/*
* Go through every socket that has broomstick points queued up, and advance to the next point.
* If the player has disconnected or finished the route, clean up and remove them from the queue.
*/
void TransportManager::stepSkywaySystem() {
// using an unordered map so we can remove finished players in one iteration
std::unordered_map<CNSocket*, std::queue<WarpLocation>>::iterator it = SkywayQueues.begin();
while (it != SkywayQueues.end()) {
std::queue<WarpLocation>* queue = &it->second;
Player* plr = PlayerManager::getPlayer(it->first);
if (plr == nullptr) {
// pluck out dead socket + update iterator
it = SkywayQueues.erase(it);
continue;
}
if (queue->empty()) {
// send dismount packet
INITSTRUCT(sP_FE2CL_REP_PC_RIDING_SUCC, rideSucc);
INITSTRUCT(sP_FE2CL_PC_RIDING, rideBroadcast);
rideSucc.iPC_ID = plr->iID;
rideSucc.eRT = 0;
rideBroadcast.iPC_ID = plr->iID;
rideBroadcast.eRT = 0;
it->first->sendPacket((void*)&rideSucc, P_FE2CL_REP_PC_RIDING_SUCC, sizeof(sP_FE2CL_REP_PC_RIDING_SUCC));
// send packet to players in view
PlayerManager::sendToViewable(it->first, (void*)&rideBroadcast, P_FE2CL_PC_RIDING, sizeof(sP_FE2CL_PC_RIDING));
it = SkywayQueues.erase(it); // remove player from tracking map + update iterator
} else {
WarpLocation point = queue->front(); // get point
queue->pop(); // remove point from front of queue
INITSTRUCT(sP_FE2CL_PC_BROOMSTICK_MOVE, bmstk);
bmstk.iPC_ID = plr->iID;
bmstk.iToX = point.x;
bmstk.iToY = point.y;
bmstk.iToZ = point.z;
it->first->sendPacket((void*)&bmstk, P_FE2CL_PC_BROOMSTICK_MOVE, sizeof(sP_FE2CL_PC_BROOMSTICK_MOVE));
// set player location to point to update viewables
PlayerManager::updatePlayerChunk(it->first, point.x, point.y, plr->instanceID);
// send packet to players in view
PlayerManager::sendToViewable(it->first, (void*)&bmstk, P_FE2CL_PC_BROOMSTICK_MOVE, sizeof(sP_FE2CL_PC_BROOMSTICK_MOVE));
it++; // go to next entry in map
}
}
}
void TransportManager::stepNPCPathing() {
// all NPC pathing queues
std::unordered_map<int32_t, std::queue<WarpLocation>>::iterator it = NPCQueues.begin();
while (it != NPCQueues.end()) {
std::queue<WarpLocation>* queue = &it->second;
BaseNPC* npc = nullptr;
if (NPCManager::NPCs.find(it->first) != NPCManager::NPCs.end())
npc = NPCManager::NPCs[it->first];
if (npc == nullptr || queue->empty()) {
// pluck out dead path + update iterator
it = NPCQueues.erase(it);
continue;
}
// skip if not simulating mobs
if (npc->npcClass == NPC_MOB && !MobManager::simulateMobs) {
it++;
continue;
}
// do not roam if not roaming
if (npc->npcClass == NPC_MOB && ((Mob*)npc)->state != MobState::ROAMING) {
it++;
continue;
}
WarpLocation point = queue->front(); // get point
queue->pop(); // remove point from front of queue
// calculate displacement
int dXY = hypot(point.x - npc->appearanceData.iX, point.y - npc->appearanceData.iY); // XY plane distance
int distanceBetween = hypot(dXY, point.z - npc->appearanceData.iZ); // total distance
// update NPC location to update viewables
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, point.x, point.y, point.z);
switch (npc->npcClass) {
case NPC_BUS:
INITSTRUCT(sP_FE2CL_TRANSPORTATION_MOVE, busMove);
busMove.eTT = 3;
busMove.iT_ID = npc->appearanceData.iNPC_ID;
busMove.iMoveStyle = 0; // ???
busMove.iToX = point.x;
busMove.iToY = point.y;
busMove.iToZ = point.z;
busMove.iSpeed = distanceBetween; // set to distance to match how monkeys work
NPCManager::sendToViewable(npc, &busMove, P_FE2CL_TRANSPORTATION_MOVE, sizeof(sP_FE2CL_TRANSPORTATION_MOVE));
break;
case NPC_MOB:
MobManager::incNextMovement((Mob*)npc);
/* fallthrough */
default:
INITSTRUCT(sP_FE2CL_NPC_MOVE, move);
move.iNPC_ID = npc->appearanceData.iNPC_ID;
move.iMoveStyle = 0; // ???
move.iToX = point.x;
move.iToY = point.y;
move.iToZ = point.z;
move.iSpeed = distanceBetween;
NPCManager::sendToViewable(npc, &move, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE));
break;
}
/*
* Move processed point to the back to maintain cycle, unless this is a
* dynamically calculated mob route.
*/
if (!(npc->npcClass == NPC_MOB && !((Mob*)npc)->staticPath))
queue->push(point);
it++; // go to next entry in map
}
}
/*
* Linearly interpolate between two points and insert the results into a queue.
*/
void TransportManager::lerp(std::queue<WarpLocation>* queue, WarpLocation start, WarpLocation end, int gapSize, float curve) {
int dXY = hypot(end.x - start.x, end.y - start.y); // XY plane distance
int distanceBetween = hypot(dXY, end.z - start.z); // total distance
int lerps = distanceBetween / gapSize; // number of intermediate points to add
for (int i = 1; i <= lerps; i++) {
WarpLocation lerp;
// lerp math
//float frac = i / (lerps + 1);
float frac = powf(i, curve) / powf(lerps + 1, curve);
lerp.x = (start.x * (1.0f - frac)) + (end.x * frac);
lerp.y = (start.y * (1.0f - frac)) + (end.y * frac);
lerp.z = (start.z * (1.0f - frac)) + (end.z * frac);
queue->push(lerp); // add lerp'd point
}
}
void TransportManager::lerp(std::queue<WarpLocation>* queue, WarpLocation start, WarpLocation end, int gapSize) {
lerp(queue, start, end, gapSize, 1);
}

View File

@@ -1,9 +1,41 @@
#pragma once
#include "CNShardServer.hpp"
#include "NPCManager.hpp"
#include <unordered_map>
const int SLIDER_SPEED = 1200;
const int SLIDER_STOP_TICKS = 8;
struct WarpLocation;
struct TransportRoute {
int type, start, end, cost, mssSpeed, mssRouteNum;
};
struct TransportLocation {
int npcID, x, y, z;
};
namespace TransportManager {
extern std::map<int32_t, TransportRoute> Routes;
extern std::map<int32_t, TransportLocation> Locations;
extern std::map<int32_t, std::queue<WarpLocation>> SkywayPaths; // predefined skyway paths with points
extern std::unordered_map<CNSocket*, std::queue<WarpLocation>> SkywayQueues; // player sockets with queued broomstick points
extern std::unordered_map<int32_t, std::queue<WarpLocation>> NPCQueues; // NPC ids with queued pathing points
void init();
void transportRegisterLocationHandler(CNSocket* sock, CNPacketData* data);
void transportRegisterLocationHandler(CNSocket*, CNPacketData*);
void transportWarpHandler(CNSocket*, CNPacketData*);
void testMssRoute(CNSocket *sock, std::vector<WarpLocation>* route);
void tickTransportationSystem(CNServer*, time_t);
void stepNPCPathing();
void stepSkywaySystem();
void lerp(std::queue<WarpLocation>*, WarpLocation, WarpLocation, int, float);
void lerp(std::queue<WarpLocation>*, WarpLocation, WarpLocation, int);
}

View File

@@ -2,40 +2,58 @@
#include "CNShardServer.hpp"
#include "PlayerManager.hpp"
#include "ChatManager.hpp"
#include "CombatManager.hpp"
#include "MobManager.hpp"
#include "ItemManager.hpp"
#include "MissionManager.hpp"
#include "NanoManager.hpp"
#include "NPCManager.hpp"
#include "TransportManager.hpp"
#include "BuddyManager.hpp"
#include "Database.hpp"
#include "TableData.hpp"
#include "ChunkManager.hpp"
#include "GroupManager.hpp"
#include "settings.hpp"
#include "../version.h"
#if defined(__MINGW32__) && !defined(_GLIBCXX_HAS_GTHREADS)
#include "mingw/mingw.thread.h"
#else
#else
#include <thread>
#endif
#include <string>
#include <chrono>
#include <signal.h>
CNShardServer *shardServer;
std::thread *shardThread;
// HACK
#ifdef __has_feature
#if __has_feature(address_sanitizer)
#define __SANITIZE_ADDRESS__ 1
#endif
#endif
CNShardServer *shardServer = nullptr;
std::thread *shardThread = nullptr;
void startShard(CNShardServer* server) {
server->start();
}
#ifndef _WIN32
// terminate gracefully on SIGINT (for gprof)
void terminate(int arg) {
std::cout << "OpenFusion: terminating." << std::endl;
shardServer->kill();
shardThread->join();
if (shardServer != nullptr && shardThread != nullptr) {
shardServer->kill();
shardThread->join();
}
exit(0);
}
#ifndef _WIN32
void initsignals() {
struct sigaction act;
@@ -67,20 +85,35 @@ int main() {
#else
initsignals();
#endif
srand(getTime());
settings::init();
std::cout << "[INFO] OpenFusion v" GIT_VERSION << std::endl;
std::cout << "[INFO] Protocol version: " << PROTOCOL_VERSION << std::endl;
std::cout << "[INFO] Intializing Packet Managers..." << std::endl;
TableData::init();
PlayerManager::init();
ChatManager::init();
CombatManager::init();
MobManager::init();
ItemManager::init();
MissionManager::init();
NanoManager::init();
NPCManager::init();
TransportManager::init();
// BuddyManager::init(); // stubbed until we have database integration + lots of bug fixes
GroupManager::init();
Database::open();
switch (settings::EVENTMODE) {
case 0: break; // no event
case 1: std::cout << "[INFO] Event active. Hey, Hey It's Knishmas!" << std::endl; break;
case 2: std::cout << "[INFO] Event active. Wishing you a spook-tacular Halloween!" << std::endl; break;
case 3: std::cout << "[INFO] Event active. Have a very hoppy Easter!" << std::endl; break;
default:
std::cout << "[FATAL] Unknown event set in config file." << std::endl;
terminate(0);
/* not reached */
}
std::cout << "[INFO] Starting Server Threads..." << std::endl;
CNLoginServer loginServer(settings::LOGINPORT);
shardServer = new CNShardServer(settings::SHARDPORT);
@@ -97,3 +130,46 @@ int main() {
#endif
return 0;
}
// helper functions
std::string U16toU8(char16_t* src) {
try {
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>,char16_t> convert;
return convert.to_bytes(src);
} catch(const std::exception& e) {
return "";
}
}
// returns number of char16_t that was written at des
size_t U8toU16(std::string src, char16_t* des, size_t max) {
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>,char16_t> convert;
std::u16string tmp = convert.from_bytes(src);
// copy utf16 string to buffer
if (sizeof(char16_t) * tmp.length() > max) // make sure we don't write outside the buffer
memcpy(des, tmp.c_str(), sizeof(char16_t) * max);
else
memcpy(des, tmp.c_str(), sizeof(char16_t) * tmp.length());
des[tmp.length()] = '\0';
return tmp.length();
}
time_t getTime() {
using namespace std::chrono;
milliseconds value = duration_cast<milliseconds>((time_point_cast<milliseconds>(high_resolution_clock::now())).time_since_epoch());
return (time_t)value.count();
}
// returns system time in seconds
time_t getTimestamp() {
using namespace std::chrono;
seconds value = duration_cast<seconds>((time_point_cast<seconds>(system_clock::now())).time_since_epoch());
return (time_t)value.count();
}

View File

@@ -6,24 +6,32 @@
int settings::VERBOSITY = 1;
int settings::LOGINPORT = 8001;
bool settings::LOGINRANDCHARACTERS = false;
bool settings::APPROVEALLNAMES = true;
int settings::DBSAVEINTERVAL = 240;
int settings::SHARDPORT = 8002;
std::string settings::SHARDSERVERIP = "127.0.0.1";
int settings::PLAYERDISTANCE = 20000;
int settings::NPCDISTANCE = 16000;
time_t settings::TIMEOUT = 60000;
int settings::VIEWDISTANCE = 40000;
bool settings::SIMULATEMOBS = true;
// default spawn point is city hall
int settings::SPAWN_X = 179213;
int settings::SPAWN_Y = 268451;
int settings::SPAWN_Z = -4210;
std::string settings::GMPASS = "pass";
std::string settings::NPCJSON = "data/NPCs.json";
std::string settings::WARPJSON = "data/warps.json";
std::string settings::MOBJSON = "data/mobs.json";
// default spawn point is Sector V (future)
int settings::SPAWN_X = 632032;
int settings::SPAWN_Y = 187177;
int settings::SPAWN_Z = -5500;
int settings::SPAWN_ANGLE = 130;
std::string settings::NPCJSON = "tdata/NPCs.json";
std::string settings::XDTJSON = "tdata/xdt.json";
std::string settings::MOBJSON = "tdata/mobs.json";
std::string settings::PATHJSON = "tdata/paths.json";
std::string settings::DROPSJSON = "tdata/drops.json";
std::string settings::GRUNTWORKJSON = "tdata/gruntwork.json";
std::string settings::MOTDSTRING = "Welcome to OpenFusion!";
bool settings::GM = false;
int settings::ACCLEVEL = 1;
// event mode settings
int settings::EVENTMODE = 0;
int settings::EVENTCRATECHANCE = 10;
void settings::init() {
INIReader reader("config.ini");
@@ -31,7 +39,7 @@ void settings::init() {
if (reader.ParseError() != 0) {
if (reader.ParseError() == -1)
std::cerr << "[WARN] Settings: missing config.ini file!" << std::endl;
else
else
std::cerr << "[WARN] Settings: invalid config.ini syntax at line " << reader.ParseError() << std::endl;
return;
@@ -40,18 +48,24 @@ void settings::init() {
APPROVEALLNAMES = reader.GetBoolean("", "acceptallcustomnames", APPROVEALLNAMES);
VERBOSITY = reader.GetInteger("", "verbosity", VERBOSITY);
LOGINPORT = reader.GetInteger("login", "port", LOGINPORT);
LOGINRANDCHARACTERS = reader.GetBoolean("login", "randomcharacters", LOGINRANDCHARACTERS);
SHARDPORT = reader.GetInteger("shard", "port", SHARDPORT);
SHARDSERVERIP = reader.Get("shard", "ip", "127.0.0.1");
PLAYERDISTANCE = reader.GetInteger("shard", "playerdistance", PLAYERDISTANCE);
NPCDISTANCE = reader.GetInteger("shard", "npcdistance", NPCDISTANCE);
DBSAVEINTERVAL = reader.GetInteger("shard", "dbsaveinterval", DBSAVEINTERVAL);
TIMEOUT = reader.GetInteger("shard", "timeout", TIMEOUT);
VIEWDISTANCE = reader.GetInteger("shard", "viewdistance", VIEWDISTANCE);
SIMULATEMOBS = reader.GetBoolean("shard", "simulatemobs", SIMULATEMOBS);
SPAWN_X = reader.GetInteger("shard", "spawnx", SPAWN_X);
SPAWN_Y = reader.GetInteger("shard", "spawny", SPAWN_Y);
SPAWN_Z = reader.GetInteger("shard", "spawnz", SPAWN_Z);
GMPASS = reader.Get("login", "pass", GMPASS);
SPAWN_ANGLE = reader.GetInteger("shard", "spawnangle", SPAWN_ANGLE);
NPCJSON = reader.Get("shard", "npcdata", NPCJSON);
WARPJSON = reader.Get("shard", "warpdata", WARPJSON);
XDTJSON = reader.Get("shard", "xdtdata", XDTJSON);
MOBJSON = reader.Get("shard", "mobdata", MOBJSON);
DROPSJSON = reader.Get("shard", "dropdata", DROPSJSON);
PATHJSON = reader.Get("shard", "pathdata", PATHJSON);
GRUNTWORKJSON = reader.Get("shard", "gruntwork", GRUNTWORKJSON);
MOTDSTRING = reader.Get("shard", "motd", MOTDSTRING);
GM = reader.GetBoolean("shard", "gm", GM);
ACCLEVEL = reader.GetInteger("shard", "accountlevel", ACCLEVEL);
EVENTMODE = reader.GetInteger("shard", "eventmode", EVENTMODE);
EVENTCRATECHANCE = reader.GetInteger("shard", "eventcratechance", EVENTCRATECHANCE);
}

View File

@@ -3,21 +3,27 @@
namespace settings {
extern int VERBOSITY;
extern int LOGINPORT;
extern bool LOGINRANDCHARACTERS;
extern bool APPROVEALLNAMES;
extern int DBSAVEINTERVAL;
extern int SHARDPORT;
extern std::string SHARDSERVERIP;
extern int PLAYERDISTANCE;
extern int NPCDISTANCE;
extern time_t TIMEOUT;
extern int VIEWDISTANCE;
extern bool SIMULATEMOBS;
extern int SPAWN_X;
extern int SPAWN_Y;
extern int SPAWN_Z;
extern int SPAWN_ANGLE;
extern int ACCLEVEL;
extern std::string MOTDSTRING;
extern std::string NPCJSON;
extern std::string WARPJSON;
extern std::string XDTJSON;
extern std::string MOBJSON;
extern std::string GMPASS;
extern bool GM;
extern std::string PATHJSON;
extern std::string DROPSJSON;
extern std::string GRUNTWORKJSON;
extern int EVENTMODE;
extern int EVENTCRATECHANCE;
void init();
}

View File

@@ -2,6 +2,8 @@
#define AEQUIP_COUNT 9
#define AINVEN_COUNT 50
#define AQINVEN_COUNT 50
#define ABANK_COUNT 119
#pragma pack(push)

View File

@@ -2,6 +2,8 @@
#define AEQUIP_COUNT 12
#define AINVEN_COUNT 50
#define AQINVEN_COUNT 50
#define ABANK_COUNT 200
#pragma pack(push)

8
suppr.txt Normal file
View File

@@ -0,0 +1,8 @@
leak:TableData::init
leak:ChunkManager::addPlayer
leak:ChunkManager::addNPC
leak:NPCManager::updateNPCPosition
leak:NPCManager::npcSummonHandler
leak:summonWCommand
leak:TableData::loadGruntwork
leak:nlohmann::basic_json

1
tdata Submodule

Submodule tdata added at aa4338202e

1
version.h.in Normal file
View File

@@ -0,0 +1 @@
#define GIT_VERSION "@GIT_VERSION@"