mirror of
				https://github.com/OpenFusionProject/OpenFusion.git
				synced 2025-10-31 16:50:12 +00:00 
			
		
		
		
	Compare commits
	
		
			115 Commits
		
	
	
		
			patchmap
			...
			4f3aa24d9d
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 4f3aa24d9d | ||
|   | 6956782027 | ||
| bd0cc3c212 | |||
| a14354ca4b | |||
| 46296b76e9 | |||
| 1800334bb7 | |||
| c113ea4a6c | |||
| 208a301a48 | |||
| 278818dd37 | |||
|   | ea033a97dd | ||
| c636c538eb | |||
|   | d8b63a043e | ||
|   | f2447cd940 | ||
|   | 97dca62fd0 | ||
| d3bef95a7f | |||
|   | 0eff236512 | ||
|   | 0fcf41cbfe | ||
|   | efb3887e3e | ||
|   | 874bdefb13 | ||
|   | 94b08659d2 | ||
|   | d936d0b621 | ||
|   | 249e5b081f | ||
|   | 52c1ed4480 | ||
|   | 522b9ab7b8 | ||
|   | c0a9ec6b7c | ||
|   | d416763ae0 | ||
|   | ff74c8ede1 | ||
|   | e64bcf91a5 | ||
|   | b04c292272 | ||
|   | 36b6aeeb00 | ||
|   | 93ec6380c1 | ||
|   | c1aff8d3c3 | ||
|   | 1cf2be9968 | ||
|   | dd4063e416 | ||
|   | 464d18820b | ||
|   | 288a4a3da5 | ||
|   | 807e182407 | ||
|   | 0dd628717d | ||
|   | c70205a15b | ||
|   | 8062e52c55 | ||
|   | 3b5f6c0fe7 | ||
|   | e9cd5db8a2 | ||
|   | e7450b974c | ||
|   | 13e71de785 | ||
|   | e3c3da87b2 | ||
|   | 4bc53b2e7e | ||
|   | 03abb6f830 | ||
|   | bd1abcef72 | ||
|   | 49fa6d65c4 | ||
|   | 773e4b36e1 | ||
|   | 945599f93c | ||
|   | 78c15e2899 | ||
|   | eaebe3ba4c | ||
|   | 9312706524 | ||
|   | 80dd6b5479 | ||
|   | 751fd4fd9d | ||
|   | ec71fd8f46 | ||
|   | f0bb90b547 | ||
|   | 0dfcc928a9 | ||
|   | dc6386131a | ||
|   | 9977907842 | ||
|   | 723e455b1d | ||
|   | ab4b763f00 | ||
|   | 8b94bcd5ca | ||
|   | 1637b8e789 | ||
|   | da38bbec29 | ||
|   | a07f36e379 | ||
|   | afc48b7676 | ||
|   | 15b6cd2fb4 | ||
|   | 34669eb7b9 | ||
|   | ded7462677 | ||
|   | 704d6a2452 | ||
|   | 98634d5aa2 | ||
|   | 306a75f469 | ||
|   | e4e4a421f4 | ||
|   | 2f612ce0e1 | ||
|   | 760170af94 | ||
|   | 2f4c8cdd60 | ||
|   | c8f5aab929 | ||
|   | 9f74b7decb | ||
|   | eea4107665 | ||
|   | ad53ec82af | ||
|   | 22c93ac854 | ||
|   | 710300c04c | ||
|   | 04221f1c5f | ||
|   | 828f49cd62 | ||
|   | 4f49bcea87 | ||
|   | f6094fde58 | ||
|   | 595dcda1b7 | ||
|   | b6f15824f1 | ||
|   | 35e938b8c6 | ||
|   | 345c9cd3b2 | ||
|   | 68d53feea3 | ||
|   | 45742e90a2 | ||
|   | 69a478b777 | ||
|   | c32e5b2d5e | ||
|   | 3d572432b3 | ||
|   | 0c4cdaeabf | ||
|   | ed866fbee4 | ||
|   | 5ab0112298 | ||
| 4494ba5932 | |||
| 803073213e | |||
| 4153d5cd30 | |||
| 71f1f6edb9 | |||
|   | 32fad56d38 | ||
|   | af7b99195f | ||
|   | efc00e63b3 | ||
|   | 7ab01b098d | ||
|   | 32db574700 | ||
|   | c965024d1c | ||
|   | 650f947451 | ||
|   | b12aecad63 | ||
|   | 5bf0c8f3ea | ||
|   | 2ddc956c9b | ||
|   | 4f0ae027a5 | 
							
								
								
									
										1
									
								
								.dockerignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.dockerignore
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| version.h | ||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -16,3 +16,5 @@ build/ | ||||
| version.h | ||||
| infer-out | ||||
| gmon.out | ||||
| *.bak | ||||
|  | ||||
|   | ||||
							
								
								
									
										26
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| { | ||||
|     "version": "0.2.0", | ||||
|     "configurations": [ | ||||
|         { | ||||
|             "name": "Debug (Linux)", | ||||
|             "type": "cppdbg", | ||||
|             "request": "launch", | ||||
|             "program": "${workspaceFolder}/bin/fusion", | ||||
|             "cwd": "${workspaceFolder}" | ||||
|         }, | ||||
|         { | ||||
|             "name": "Debug (Windows)", | ||||
|             "type": "cppvsdbg", | ||||
|             "request": "launch", | ||||
|             "program": "${workspaceFolder}/bin/Debug/winfusion.exe", | ||||
|             "cwd": "${workspaceFolder}" | ||||
|         }, | ||||
|         { | ||||
|             "name": "Release (Windows)", | ||||
|             "type": "cppvsdbg", | ||||
|             "request": "launch", | ||||
|             "program": "${workspaceFolder}/bin/Release/winfusion.exe", | ||||
|             "cwd": "${workspaceFolder}" | ||||
|         } | ||||
|     ] | ||||
| } | ||||
| @@ -3,7 +3,8 @@ project(OpenFusion) | ||||
|  | ||||
| set(CMAKE_CXX_STANDARD 17) | ||||
|  | ||||
| execute_process(COMMAND git describe --tags OUTPUT_VARIABLE GIT_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE) | ||||
| execute_process(COMMAND git describe --tags OUTPUT_VARIABLE GIT_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE | ||||
| 				WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) | ||||
|  | ||||
| # OpenFusion supports multiple packet/struct versions | ||||
| # 104 is the default version to build which can be changed | ||||
| @@ -44,7 +45,7 @@ add_executable(openfusion ${SOURCES}) | ||||
| set_target_properties(openfusion PROPERTIES OUTPUT_NAME ${BIN_NAME}) | ||||
|  | ||||
| # find sqlite3 and use it | ||||
| find_package(sqlite3 REQUIRED) | ||||
| find_package(SQLite3 REQUIRED) | ||||
| target_include_directories(openfusion PRIVATE ${SQLite3_INCLUDE_DIRS}) | ||||
| target_link_libraries(openfusion PRIVATE ${SQLite3_LIBRARIES}) | ||||
|  | ||||
| @@ -56,5 +57,5 @@ set_property(TARGET openfusion PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_S | ||||
| # It's not something you should do, but it's there if you need it... | ||||
| 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 PRIVATE pthread) | ||||
| endif() | ||||
|   | ||||
| @@ -26,7 +26,7 @@ Both are pretty short reads and following them will get you up to speed with bra | ||||
|  | ||||
| I will now cover a few examples of the complications people have encountered contributing to this project, how to understand them, overcome them and henceforth avoid them. | ||||
|  | ||||
| ## Dirty pull requests | ||||
| ### Dirty pull requests | ||||
|  | ||||
| Many Pull Requests OpenFusion receives fail to present a clean set of commits to merge. | ||||
| These are generally either: | ||||
| @@ -44,7 +44,7 @@ If you read the above links, you'll note that this isn't exactly a perfect solut | ||||
| The obvious issue, then, is that the people submitting dirty PRs are the exact people who don't *know* how to rebase their fork to continue submitting their work cleanly. | ||||
| So they end up creating countless merge commits when pulling upstream on top of their own incompatible histories, and then submitting those merge commits in their PRs and the cycle continues. | ||||
|  | ||||
| ## The details | ||||
| ### The details | ||||
|  | ||||
| A git commit is uniquely identified by its SHA1 hash. | ||||
| Its hash is generated from its contents, as well as the hash(es) of its parent commit(s). | ||||
| @@ -52,7 +52,7 @@ Its hash is generated from its contents, as well as the hash(es) of its parent c | ||||
| That means that even if two commits are exactly the same in terms of content, they're not the same commit if their parent commits differ (ex. if they've been rebased). | ||||
| So if you keep issuing `git pull`s after upstream has merged a rebased version of your PR, you will never re-synchronize with it, and will instead construct an alternate history polluted by pointless merge commits. | ||||
|  | ||||
| ## The solution | ||||
| ### The solution | ||||
|  | ||||
| If you already have a messed-up fork and you have no changes on it that you're afraid to lose, the solution is simple: | ||||
|  | ||||
| @@ -67,7 +67,7 @@ If you do have some committed changes that haven't yet been merged upstream, you | ||||
| If you do end up messing something up, don't worry, it most likely isn't really lost and `git reflog` is your friend. | ||||
| (You can checkout an arbitrary commit, and make it into its own branch with `git checkout -b BRANCH` or set a pre-exisitng branch to it with `git reset --hard COMMIT`) | ||||
|  | ||||
| ## Avoiding the problem | ||||
| ### Avoiding the problem | ||||
|  | ||||
| When working on a changeset you want to submit back upstream, don't do it on the main branch. | ||||
| Create a work branch just for your changeset with `git checkout -b work`. | ||||
| @@ -81,3 +81,34 @@ That way you can always keep master in sync with upstream with `git pull --ff-on | ||||
| Creating new branches for the rebase isn't strictly necessary since you can always return a branch to its previous state with `git reflog`. | ||||
|  | ||||
| For moving uncommited changes around between branches, `git stash` is a real blessing. | ||||
|  | ||||
| ## Code guidelines | ||||
|  | ||||
| Alright, you're up to speed on Git and ready to go. Here are a few specific code guidelines to try and follow: | ||||
|  | ||||
| ### Match the styling | ||||
|  | ||||
| Pretty straightforward, make sure your code looks similar to the code around it. Match whitespacing, bracket styling, variable naming conventions, etc. | ||||
|  | ||||
| ### Prefer short-circuiting | ||||
|  | ||||
| To minimize branching complexity (as this makes the code hard to read), we prefer to keep the number of `if-else` statements as low as possible. One easy way to achieve this is by doing an early return after branching into an `if` block and then writing the code for the other path outside the block entirely. You can find examples of this in practically every source file. Note that in a few select situations, this might actually make your code less elegant, which is why this isn't a strict rule. Lean towards short-circuiting and use your better judgement. | ||||
|  | ||||
| ### Follow the include convention | ||||
|  | ||||
| This one matters a lot as it can cause cyclic dependencies and other code-breaking issues. | ||||
|  | ||||
| FOR HEADER FILES (.hpp): | ||||
| - everything you use IN THE HEADER must be EXPLICITLY INCLUDED with the exception of things that fall under Core.hpp | ||||
| - you may NOT include ANYTHING ELSE | ||||
|  | ||||
| FOR SOURCE FILES (.cpp): | ||||
| - you can #include whatever you want as long as the partner header is included first | ||||
| - anything that gets included by another include is fair game | ||||
| - redundant includes are ok because they'll be harmless AS LONG AS our header files stay lean. | ||||
|  | ||||
| The point of this is NOT to optimize the number of includes used all around or make things more efficient necessarily. it's to improve readability & coherence and make it easier to avoid cyclical issues. | ||||
|  | ||||
| ## When in doubt, ask | ||||
|  | ||||
| If you still have questions that were not answered here, feel free to ping a dev in the Discord server or on GitHub. | ||||
|   | ||||
							
								
								
									
										21
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| FROM debian:latest | ||||
|  | ||||
| WORKDIR /usr/src/app | ||||
|  | ||||
| RUN apt-get -y update && apt-get install -y \ | ||||
| git \ | ||||
| clang \ | ||||
| make \ | ||||
| libsqlite3-dev | ||||
|  | ||||
| COPY . ./ | ||||
|  | ||||
| RUN make -j8 | ||||
|  | ||||
| # tabledata should be copied from the host; | ||||
| # clone it there before building the container | ||||
| #RUN git submodule update --init --recursive | ||||
|  | ||||
| CMD ["./bin/fusion"] | ||||
|  | ||||
| LABEL Name=openfusion Version=0.0.1 | ||||
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
									
									
									
									
								
							| @@ -50,6 +50,7 @@ CXXSRC=\ | ||||
| 	src/db/email.cpp\ | ||||
| 	src/sandbox/seccomp.cpp\ | ||||
| 	src/sandbox/openbsd.cpp\ | ||||
| 	src/Buffs.cpp\ | ||||
| 	src/Chat.cpp\ | ||||
| 	src/CustomCommands.cpp\ | ||||
| 	src/Entities.cpp\ | ||||
| @@ -96,6 +97,7 @@ CXXHDR=\ | ||||
| 	vendor/JSON.hpp\ | ||||
| 	vendor/INIReader.hpp\ | ||||
| 	vendor/JSON.hpp\ | ||||
| 	src/Buffs.hpp\ | ||||
| 	src/Chat.hpp\ | ||||
| 	src/CustomCommands.hpp\ | ||||
| 	src/Entities.hpp\ | ||||
|   | ||||
							
								
								
									
										12
									
								
								config.ini
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								config.ini
									
									
									
									
									
								
							| @@ -1,8 +1,3 @@ | ||||
| # name of the client build the server is targetting. | ||||
| # used for determining which patches to apply. | ||||
| # default is beta-20111013 for Academy, beta-20100104 otherwise. | ||||
| #buildname=beta-20100104 | ||||
|  | ||||
| # verbosity level | ||||
| # 0 = mostly silence | ||||
| # 1 = debug prints and unknown packets | ||||
| @@ -51,6 +46,11 @@ motd=Welcome to OpenFusion! | ||||
| # location of the patch folder | ||||
| #patchdir=tdata/patch/ | ||||
|  | ||||
| # Space-separated list of patch folders in patchdir to load from. | ||||
| # If you uncomment this, note that Academy builds *must* contain 1013, | ||||
| # and pre-Academy builds must *not* contain it. | ||||
| #enabledpatches=1013 | ||||
|  | ||||
| # xdt json filename | ||||
| #xdtdata=xdt.json | ||||
| # NPC json filename | ||||
| @@ -61,8 +61,6 @@ motd=Welcome to OpenFusion! | ||||
| #pathdata=paths.json | ||||
| # drop json filename | ||||
| #dropdata=drops.json | ||||
| # patchmap json filename | ||||
| #patchmapdata=patchmap.json | ||||
| # gruntwork output filename (this is what you submit) | ||||
| #gruntwork=gruntwork.json | ||||
| # location of the database | ||||
|   | ||||
							
								
								
									
										12
									
								
								docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| version: '3.4' | ||||
|  | ||||
| services: | ||||
|   openfusion: | ||||
|     image: openfusion | ||||
|     build: | ||||
|       context: . | ||||
|       dockerfile: ./Dockerfile | ||||
|     ports: | ||||
|       - "23000:23000" | ||||
|       - "23001:23001" | ||||
|       - "8003:8003" | ||||
							
								
								
									
										1271
									
								
								src/Abilities.cpp
									
									
									
									
									
								
							
							
						
						
									
										1271
									
								
								src/Abilities.cpp
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,64 +1,112 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "core/Core.hpp" | ||||
| #include "Combat.hpp" | ||||
|  | ||||
| typedef void (*PowerHandler)(CNSocket*, std::vector<int>, int16_t, int16_t, int16_t, int16_t, int16_t, int32_t, int16_t); | ||||
| #include "Entities.hpp" | ||||
| #include "Player.hpp" | ||||
|  | ||||
| struct NanoPower { | ||||
|     int16_t skillType; | ||||
|     int32_t bitFlag; | ||||
|     int16_t timeBuffID; | ||||
|     PowerHandler handler; | ||||
| #include <map> | ||||
| #include <vector> | ||||
| #include <assert.h> | ||||
|  | ||||
|     NanoPower(int16_t s, int32_t b, int16_t t, PowerHandler h) : skillType(s), bitFlag(b), timeBuffID(t), handler(h) {} | ||||
| const int COMBAT_TICKS_PER_DRAIN_PROC = 2; | ||||
| constexpr size_t MAX_SKILLRESULT_SIZE = sizeof(sSkillResult_BatteryDrain); | ||||
|  | ||||
|     void handle(CNSocket *sock, std::vector<int> targetData, int16_t nanoID, int16_t skillID, int16_t duration, int16_t amount) { | ||||
|         if (handler == nullptr) | ||||
|             return; | ||||
|  | ||||
|         handler(sock, targetData, nanoID, skillID, duration, amount, skillType, bitFlag, timeBuffID); | ||||
|     } | ||||
| enum class SkillType { | ||||
|     DAMAGE = 1, | ||||
|     HEAL_HP = 2, | ||||
|     KNOCKDOWN = 3, // uses DamageNDebuff | ||||
|     SLEEP = 4, // uses DamageNDebuff | ||||
|     SNARE = 5, // uses DamageNDebuff | ||||
|     HEAL_STAMINA = 6, | ||||
|     STAMINA_SELF = 7, | ||||
|     STUN = 8, // uses DamageNDebuff | ||||
|     WEAPONSLOW = 9, | ||||
|     JUMP = 10, | ||||
|     RUN = 11, | ||||
|     STEALTH = 12, | ||||
|     SWIM = 13, | ||||
|     MINIMAPENEMY = 14, | ||||
|     MINIMAPTRESURE = 15, | ||||
|     PHOENIX = 16, | ||||
|     PROTECTBATTERY = 17, | ||||
|     PROTECTINFECTION = 18, | ||||
|     REWARDBLOB = 19, | ||||
|     REWARDCASH = 20, | ||||
|     BATTERYDRAIN = 21, | ||||
|     CORRUPTIONATTACK = 22, | ||||
|     INFECTIONDAMAGE = 23, | ||||
|     KNOCKBACK = 24, | ||||
|     FREEDOM = 25, | ||||
|     PHOENIX_GROUP = 26, | ||||
|     RECALL = 27, | ||||
|     RECALL_GROUP = 28, | ||||
|     RETROROCKET_SELF = 29, | ||||
|     BLOODSUCKING = 30, | ||||
|     BOUNDINGBALL = 31, | ||||
|     INVULNERABLE = 32, | ||||
|     NANOSTIMPAK = 33, | ||||
|     RETURNHOMEHEAL = 34, | ||||
|     BUFFHEAL = 35, | ||||
|     EXTRABANK = 36, | ||||
|     CORRUPTIONATTACKWIN = 38, | ||||
|     CORRUPTIONATTACKLOSE = 39, | ||||
| }; | ||||
|  | ||||
| typedef void (*MobPowerHandler)(Mob*, std::vector<int>, int16_t, int16_t, int16_t, int16_t, int32_t, int16_t); | ||||
| enum class SkillEffectTarget { | ||||
|     POINT = 1, | ||||
|     SELF = 2, | ||||
|     CONE = 3, | ||||
|     WEAPON = 4, | ||||
|     AREA_SELF = 5, | ||||
|     AREA_TARGET = 6 | ||||
| }; | ||||
|  | ||||
| struct MobPower { | ||||
|     int16_t skillType; | ||||
|     int32_t bitFlag; | ||||
|     int16_t timeBuffID; | ||||
|     MobPowerHandler handler; | ||||
| enum class SkillTargetType { | ||||
|     MOBS = 1, | ||||
|     PLAYERS = 2, | ||||
|     GROUP = 3 | ||||
| }; | ||||
|  | ||||
|     MobPower(int16_t s, int32_t b, int16_t t, MobPowerHandler h) : skillType(s), bitFlag(b), timeBuffID(t), handler(h) {} | ||||
| enum class SkillDrainType { | ||||
|     ACTIVE = 1, | ||||
|     PASSIVE = 2 | ||||
| }; | ||||
|  | ||||
|     void handle(Mob *mob, std::vector<int> targetData, int16_t skillID, int16_t duration, int16_t amount) { | ||||
|         if (handler == nullptr) | ||||
|             return; | ||||
|  | ||||
|         handler(mob, targetData, skillID, duration, amount, skillType, bitFlag, timeBuffID); | ||||
| struct SkillResult { | ||||
|     size_t size; | ||||
|     uint8_t payload[MAX_SKILLRESULT_SIZE]; | ||||
|     SkillResult(size_t len, void* dat) { | ||||
|         assert(len <= MAX_SKILLRESULT_SIZE); | ||||
|         size = len; | ||||
|         memcpy(payload, dat, len); | ||||
|     } | ||||
|     SkillResult() { | ||||
|         size = 0; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| struct SkillData { | ||||
|     int skillType; | ||||
|     int targetType; | ||||
|     int drainType; | ||||
|     SkillType skillType; // eST | ||||
|     SkillEffectTarget effectTarget; | ||||
|     int effectType; // always 1? | ||||
|     SkillTargetType targetType; | ||||
|     SkillDrainType drainType; | ||||
|     int effectArea; | ||||
|  | ||||
|     int batteryUse[4]; | ||||
|     int durationTime[4]; | ||||
|     int powerIntensity[4]; | ||||
|  | ||||
|     int valueTypes[3]; | ||||
|     int values[3][4]; | ||||
| }; | ||||
|  | ||||
| namespace Nanos { | ||||
|     extern std::vector<NanoPower> NanoPowers; | ||||
| namespace Abilities { | ||||
|     extern std::map<int32_t, SkillData> SkillTable; | ||||
|  | ||||
|     void nanoUnbuff(CNSocket* sock, std::vector<int> targetData, int32_t bitFlag, int16_t timeBuffID, int16_t amount, bool groupPower); | ||||
|     int applyBuff(CNSocket* sock, int skillID, int eTBU, int eTBT, int32_t groupFlags); | ||||
|     void useNanoSkill(CNSocket*, SkillData*, sNano&, std::vector<ICombatant*>); | ||||
|     void useNPCSkill(EntityRef, int skillID, std::vector<ICombatant*>); | ||||
|  | ||||
|     std::vector<int> findTargets(Player* plr, int skillID, CNPacketData* data = nullptr); | ||||
| } | ||||
|  | ||||
| namespace Combat { | ||||
|     extern std::vector<MobPower> MobPowers; | ||||
|     std::vector<ICombatant*> matchTargets(ICombatant*, SkillData*, int, int32_t*); | ||||
|     int getCSTBFromST(SkillType skillType); | ||||
| } | ||||
|   | ||||
| @@ -1,15 +1,10 @@ | ||||
| #include "servers/CNShardServer.hpp" | ||||
| #include "Buddies.hpp" | ||||
| #include "PlayerManager.hpp" | ||||
| #include "Buddies.hpp" | ||||
| #include "db/Database.hpp" | ||||
| #include "Items.hpp" | ||||
| #include "db/Database.hpp" | ||||
|  | ||||
| #include <iostream> | ||||
| #include <chrono> | ||||
| #include <algorithm> | ||||
| #include <thread> | ||||
| #include "db/Database.hpp" | ||||
| #include "servers/CNShardServer.hpp" | ||||
|  | ||||
| #include "Player.hpp" | ||||
| #include "PlayerManager.hpp" | ||||
|  | ||||
| using namespace Buddies; | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,5 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "Player.hpp" | ||||
| #include "core/Core.hpp" | ||||
| #include "core/Core.hpp" | ||||
|  | ||||
| namespace Buddies { | ||||
|   | ||||
							
								
								
									
										198
									
								
								src/Buffs.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										198
									
								
								src/Buffs.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,198 @@ | ||||
| #include "Buffs.hpp" | ||||
|  | ||||
| #include "PlayerManager.hpp" | ||||
| #include "NPCManager.hpp" | ||||
|  | ||||
| using namespace Buffs; | ||||
|  | ||||
| void Buff::tick(time_t currTime) { | ||||
|     auto it = stacks.begin(); | ||||
|     while(it != stacks.end()) { | ||||
|         BuffStack& stack = *it; | ||||
|         //if(onTick) onTick(self, this, currTime); | ||||
|  | ||||
|         if(stack.durationTicks == 0) { | ||||
|             BuffStack deadStack = stack; | ||||
|             it = stacks.erase(it); | ||||
|             if(onUpdate) onUpdate(self, this, ETBU_DEL, &deadStack); | ||||
|         } else { | ||||
|             if(stack.durationTicks > 0) stack.durationTicks--; | ||||
|             it++; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void Buff::combatTick(time_t currTime) { | ||||
|     if(onCombatTick) onCombatTick(self, this, currTime); | ||||
| } | ||||
|  | ||||
| void Buff::clear() { | ||||
|     while(!stacks.empty()) { | ||||
|         BuffStack stack = stacks.back(); | ||||
|         stacks.pop_back(); | ||||
|         if(onUpdate) onUpdate(self, this, ETBU_DEL, &stack); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void Buff::clear(BuffClass buffClass) { | ||||
|     auto it = stacks.begin(); | ||||
|     while(it != stacks.end()) { | ||||
|         BuffStack& stack = *it; | ||||
|         if(stack.buffStackClass == buffClass) { | ||||
|             BuffStack deadStack = stack; | ||||
|             it = stacks.erase(it); | ||||
|             if(onUpdate) onUpdate(self, this, ETBU_DEL, &deadStack); | ||||
|         } else it++; | ||||
|     } | ||||
| } | ||||
|  | ||||
| void Buff::addStack(BuffStack* stack) { | ||||
|     stacks.push_back(*stack); | ||||
|     if(onUpdate) onUpdate(self, this, ETBU_ADD, &stacks.back()); | ||||
| } | ||||
|  | ||||
| bool Buff::hasClass(BuffClass buffClass) { | ||||
|     for(BuffStack& stack : stacks) { | ||||
|         if(stack.buffStackClass == buffClass) | ||||
|             return true; | ||||
|     } | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| BuffClass Buff::maxClass() { | ||||
|     BuffClass buffClass = BuffClass::NONE; | ||||
|     for(BuffStack& stack : stacks) { | ||||
|         if(stack.buffStackClass > buffClass) | ||||
|             buffClass = stack.buffStackClass; | ||||
|     } | ||||
|     return buffClass; | ||||
| } | ||||
|  | ||||
| int Buff::getValue(BuffValueSelector selector) { | ||||
|     if(isStale()) return 0; | ||||
|  | ||||
|     int value = selector == BuffValueSelector::NET_TOTAL ? 0 : stacks.front().value; | ||||
|     for(BuffStack& stack : stacks) { | ||||
|         switch(selector) | ||||
|         { | ||||
|             case BuffValueSelector::NET_TOTAL: | ||||
|                 value += stack.value; | ||||
|                 break; | ||||
|             case BuffValueSelector::MIN_VALUE: | ||||
|                 if(stack.value < value) value = stack.value; | ||||
|                 break; | ||||
|             case BuffValueSelector::MAX_VALUE: | ||||
|                 if(stack.value > value) value = stack.value; | ||||
|                 break; | ||||
|             case BuffValueSelector::MIN_MAGNITUDE: | ||||
|                 if(abs(stack.value) < abs(value)) value = stack.value; | ||||
|                 break; | ||||
|             case BuffValueSelector::MAX_MAGNITUDE: | ||||
|             default: | ||||
|                 if(abs(stack.value) > abs(value)) value = stack.value; | ||||
|         } | ||||
|     } | ||||
|     return value; | ||||
| } | ||||
|  | ||||
| EntityRef Buff::getLastSource() { | ||||
|     if(stacks.empty()) | ||||
|         return self; | ||||
|     return stacks.back().source; | ||||
| } | ||||
|  | ||||
| bool Buff::isStale() { | ||||
|     return stacks.empty(); | ||||
| } | ||||
|  | ||||
| /* This will practically never do anything important, but it's here just in case */ | ||||
| void Buff::updateCallbacks(BuffCallback<int, BuffStack*> fOnUpdate, BuffCallback<time_t> fOnCombatTick) { | ||||
|     if(!onUpdate) onUpdate = fOnUpdate; | ||||
|     if(!onCombatTick) onCombatTick = fOnCombatTick; | ||||
| } | ||||
|  | ||||
| #pragma region Handlers | ||||
| void Buffs::timeBuffUpdate(EntityRef self, Buff* buff, int status, BuffStack* stack) { | ||||
|  | ||||
|     if(self.kind != EntityKind::PLAYER) | ||||
|         return; // not implemented | ||||
|      | ||||
|     Player* plr = (Player*)self.getEntity(); | ||||
|     if(plr == nullptr) | ||||
|         return; // sanity check | ||||
|  | ||||
|     if(status == ETBU_DEL && !buff->isStale()) | ||||
|         return; // no premature effect deletion | ||||
|  | ||||
|     int cbf = plr->getCompositeCondition(); | ||||
|     sTimeBuff payload{}; | ||||
|     if(status == ETBU_ADD) { | ||||
|         payload.iValue = buff->getValue(BuffValueSelector::MAX_MAGNITUDE); | ||||
|         // we need to explicitly add the ECSB for this buff, | ||||
|         // in case this is the first stack in and the entry | ||||
|         // in the buff map doesn't yet exist | ||||
|         if(buff->id > 0) cbf |= CSB_FROM_ECSB(buff->id); | ||||
|     } | ||||
|      | ||||
|     INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt); | ||||
|     pkt.eCSTB = buff->id; // eCharStatusTimeBuffID | ||||
|     pkt.eTBU = status; // eTimeBuffUpdate | ||||
|     pkt.eTBT = (int)stack->buffStackClass; | ||||
|     pkt.iConditionBitFlag = cbf; | ||||
|     pkt.TimeBuff = payload; | ||||
|     self.sock->sendPacket((void*)&pkt, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE)); | ||||
| } | ||||
|  | ||||
| void Buffs::timeBuffTick(EntityRef self, Buff* buff) { | ||||
|     if(self.kind != EntityKind::COMBAT_NPC && self.kind != EntityKind::MOB) | ||||
|         return; // not implemented | ||||
|     Entity* entity = self.getEntity(); | ||||
|     ICombatant* combatant = dynamic_cast<ICombatant*>(entity); | ||||
|  | ||||
|     INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK, pkt); | ||||
|     pkt.eCT = combatant->getCharType(); | ||||
|     pkt.iID = combatant->getID(); | ||||
|     pkt.iTB_ID = buff->id; | ||||
|     NPCManager::sendToViewable(entity, &pkt, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK)); | ||||
| } | ||||
|  | ||||
| void Buffs::timeBuffTimeout(EntityRef self) { | ||||
|     if(self.kind != EntityKind::PLAYER && self.kind != EntityKind::COMBAT_NPC && self.kind != EntityKind::MOB) | ||||
|         return; // not a combatant | ||||
|     Entity* entity = self.getEntity(); | ||||
|     ICombatant* combatant = dynamic_cast<ICombatant*>(entity); | ||||
|     INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt); // send a buff timeout to other players | ||||
|     int32_t eCharType = combatant->getCharType(); | ||||
|     pkt.eCT = eCharType == 4 ? 2 : eCharType; // convention not followed by client here | ||||
|     pkt.iID = combatant->getID(); | ||||
|     pkt.iConditionBitFlag = combatant->getCompositeCondition(); | ||||
|     NPCManager::sendToViewable(entity, &pkt, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT)); | ||||
| } | ||||
|  | ||||
| void Buffs::tickDrain(EntityRef self, Buff* buff, int mult) { | ||||
|     if(self.kind != EntityKind::COMBAT_NPC && self.kind != EntityKind::MOB) | ||||
|         return; // not implemented | ||||
|     Entity* entity = self.getEntity(); | ||||
|     ICombatant* combatant = dynamic_cast<ICombatant*>(entity); | ||||
|     int damage = combatant->getMaxHP() / 100 * mult; | ||||
|     int dealt = combatant->takeDamage(buff->getLastSource(), damage); | ||||
|  | ||||
|     size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_Damage); | ||||
|     assert(resplen < CN_PACKET_BUFFER_SIZE - 8); | ||||
|     uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; | ||||
|     memset(respbuf, 0, resplen); | ||||
|  | ||||
|     sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK *pkt = (sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK*)respbuf; | ||||
|     pkt->iID = self.id; | ||||
|     pkt->eCT = combatant->getCharType(); | ||||
|     pkt->iTB_ID = ECSB_BOUNDINGBALL; | ||||
|  | ||||
|     sSkillResult_Damage *drain = (sSkillResult_Damage*)(respbuf + sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK)); | ||||
|     drain->iDamage = dealt; | ||||
|     drain->iHP = combatant->getCurrentHP(); | ||||
|     drain->eCT = pkt->eCT; | ||||
|     drain->iID = pkt->iID; | ||||
|  | ||||
|     NPCManager::sendToViewable(self.getEntity(), (void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen); | ||||
| } | ||||
| #pragma endregion | ||||
							
								
								
									
										93
									
								
								src/Buffs.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								src/Buffs.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,93 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "core/Core.hpp" | ||||
|  | ||||
| #include "EntityRef.hpp" | ||||
|  | ||||
| #include <vector> | ||||
| #include <functional> | ||||
|  | ||||
| /* forward declaration(s) */ | ||||
| class Buff; | ||||
| template<class... Types> | ||||
| using BuffCallback = std::function<void(EntityRef, Buff*, Types...)>; | ||||
|  | ||||
| #define CSB_FROM_ECSB(x) (1 << (x - 1)) | ||||
|  | ||||
| enum class BuffClass { | ||||
|     NONE = ETBT_NONE, | ||||
| 	NANO = ETBT_NANO, | ||||
| 	GROUP_NANO = ETBT_GROUPNANO, | ||||
| 	EGG = ETBT_SHINY, | ||||
| 	ENVIRONMENT = ETBT_LANDEFFECT, | ||||
| 	ITEM = ETBT_ITEM, | ||||
| 	CASH_ITEM = ETBT_CASHITEM | ||||
| }; | ||||
|  | ||||
| enum class BuffValueSelector { | ||||
|     MAX_VALUE, | ||||
|     MIN_VALUE, | ||||
|     MAX_MAGNITUDE, | ||||
|     MIN_MAGNITUDE, | ||||
|     NET_TOTAL | ||||
| }; | ||||
|  | ||||
| struct BuffStack { | ||||
|     int durationTicks; | ||||
|     int value; | ||||
|     EntityRef source; | ||||
|     BuffClass buffStackClass; | ||||
| }; | ||||
|  | ||||
| class Buff { | ||||
| private: | ||||
|     EntityRef self; | ||||
|     std::vector<BuffStack> stacks; | ||||
|  | ||||
| public: | ||||
|     int id; | ||||
|     /* called just after a stack is added or removed */ | ||||
|     BuffCallback<int, BuffStack*> onUpdate; | ||||
|     /* called when the buff is combat-ticked */ | ||||
|     BuffCallback<time_t> onCombatTick; | ||||
|  | ||||
|     void tick(time_t); | ||||
|     void combatTick(time_t); | ||||
|     void clear(); | ||||
|     void clear(BuffClass buffClass); | ||||
|     void addStack(BuffStack* stack); | ||||
|  | ||||
|     /* | ||||
|     * Sometimes we need to determine if a buff | ||||
|     * is covered by a certain class, ex: nano | ||||
|     * vs. coco egg in the case of infection protection | ||||
|     */ | ||||
|     bool hasClass(BuffClass buffClass); | ||||
|     BuffClass maxClass(); | ||||
|  | ||||
|     int getValue(BuffValueSelector selector); | ||||
|     EntityRef getLastSource(); | ||||
|  | ||||
|     /*  | ||||
|      * In general, a Buff object won't exist | ||||
|      * unless it has stacks. However, when | ||||
|      * popping stacks during iteration (onExpire), | ||||
|      * stacks will be empty for a brief moment | ||||
|      * when the last stack is popped. | ||||
|      */ | ||||
|     bool isStale(); | ||||
|  | ||||
|     void updateCallbacks(BuffCallback<int, BuffStack*> fOnUpdate, BuffCallback<time_t> fonTick); | ||||
|  | ||||
|     Buff(int iid, EntityRef pSelf, BuffCallback<int, BuffStack*> fOnUpdate, BuffCallback<time_t> fOnCombatTick, BuffStack* firstStack) | ||||
|         : self(pSelf), id(iid), onUpdate(fOnUpdate), onCombatTick(fOnCombatTick) { | ||||
|         addStack(firstStack); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| namespace Buffs { | ||||
|     void timeBuffUpdate(EntityRef self, Buff* buff, int status, BuffStack* stack); | ||||
|     void timeBuffTick(EntityRef self, Buff* buff); | ||||
|     void timeBuffTimeout(EntityRef self); | ||||
|     void tickDrain(EntityRef self, Buff* buff, int mult); | ||||
| } | ||||
| @@ -1,10 +1,13 @@ | ||||
| #include "BuiltinCommands.hpp" | ||||
|  | ||||
| #include "servers/CNShardServer.hpp" | ||||
|  | ||||
| #include "Player.hpp" | ||||
| #include "PlayerManager.hpp" | ||||
| #include "Chat.hpp" | ||||
| #include "Items.hpp" | ||||
| #include "Missions.hpp" | ||||
| #include "Chat.hpp" | ||||
| #include "Nanos.hpp" | ||||
| #include "Rand.hpp" | ||||
|  | ||||
| // helper function, not a packet handler | ||||
| void BuiltinCommands::setSpecialState(CNSocket* sock, CNPacketData* data) { | ||||
| @@ -69,31 +72,41 @@ static void setValuePlayer(CNSocket* sock, CNPacketData* data) { | ||||
|  | ||||
|     // Handle serverside value-changes | ||||
|     switch (setData->iSetValueType) { | ||||
|     case 1: | ||||
|         plr->HP = setData->iSetValue; | ||||
|     case CN_GM_SET_VALUE_TYPE__HP: | ||||
|         response.iSetValue = plr->HP = setData->iSetValue; | ||||
|         break; | ||||
|     case 2: | ||||
|     case CN_GM_SET_VALUE_TYPE__WEAPON_BATTERY : | ||||
|         plr->batteryW = setData->iSetValue; | ||||
|  | ||||
|         // caps | ||||
|         if (plr->batteryW > 9999) | ||||
|             plr->batteryW = 9999; | ||||
|  | ||||
|         response.iSetValue = plr->batteryW; | ||||
|         break; | ||||
|     case 3: | ||||
|     case CN_GM_SET_VALUE_TYPE__NANO_BATTERY: | ||||
|         plr->batteryN = setData->iSetValue; | ||||
|  | ||||
|         // caps | ||||
|         if (plr->batteryN > 9999) | ||||
|             plr->batteryN = 9999; | ||||
|  | ||||
|         response.iSetValue = plr->batteryN; | ||||
|         break; | ||||
|     case 4: | ||||
|     case CN_GM_SET_VALUE_TYPE__FUSION_MATTER: | ||||
|         Missions::updateFusionMatter(sock, setData->iSetValue - plr->fusionmatter); | ||||
|         response.iSetValue = plr->fusionmatter; | ||||
|         break; | ||||
|     case 5: | ||||
|         plr->money = setData->iSetValue; | ||||
|     case CN_GM_SET_VALUE_TYPE__CANDY: | ||||
|         response.iSetValue = plr->money = setData->iSetValue; | ||||
|         break; | ||||
|     case CN_GM_SET_VALUE_TYPE__SPEED: | ||||
|     case CN_GM_SET_VALUE_TYPE__JUMP: | ||||
|         response.iSetValue = setData->iSetValue; | ||||
|         break; | ||||
|     } | ||||
|  | ||||
|     response.iPC_ID = setData->iPC_ID; | ||||
|     response.iSetValue = setData->iSetValue; | ||||
|     response.iSetValueType = setData->iSetValueType; | ||||
|  | ||||
|     sock->sendPacket(response, P_FE2CL_GM_REP_PC_SET_VALUE); | ||||
| @@ -247,17 +260,17 @@ static void teleportPlayer(CNSocket *sock, CNPacketData *data) { | ||||
|     uint64_t instance = plr->instanceID; | ||||
|     const int unstickRange = 400; | ||||
|  | ||||
|     switch (req->eTeleportType) { | ||||
|     case eCN_GM_TeleportMapType__MyLocation: | ||||
|     switch ((eCN_GM_TeleportType)req->eTeleportType) { | ||||
|     case eCN_GM_TeleportType::MyLocation: | ||||
|         PlayerManager::sendPlayerTo(targetSock, plr->x, plr->y, plr->z, instance); | ||||
|         break; | ||||
|     case eCN_GM_TeleportMapType__MapXYZ: | ||||
|     case eCN_GM_TeleportType::MapXYZ: | ||||
|         instance = req->iToMap; | ||||
|         // fallthrough | ||||
|     case eCN_GM_TeleportMapType__XYZ: | ||||
|     case eCN_GM_TeleportType::XYZ: | ||||
|         PlayerManager::sendPlayerTo(targetSock, req->iToX, req->iToY, req->iToZ, instance); | ||||
|         break; | ||||
|     case eCN_GM_TeleportMapType__SomeoneLocation: | ||||
|     case eCN_GM_TeleportType::SomeoneLocation: | ||||
|         // player to teleport to | ||||
|         goalSock = PlayerManager::getSockFromAny(req->eGoalPCSearchBy, req->iGoalPC_ID, req->iGoalPC_UID, | ||||
|             AUTOU16TOU8(req->szGoalPC_FirstName), AUTOU16TOU8(req->szGoalPC_LastName)); | ||||
| @@ -269,7 +282,7 @@ static void teleportPlayer(CNSocket *sock, CNPacketData *data) { | ||||
|  | ||||
|         PlayerManager::sendPlayerTo(targetSock, goalPlr->x, goalPlr->y, goalPlr->z, goalPlr->instanceID); | ||||
|         break; | ||||
|     case eCN_GM_TeleportMapType__Unstick: | ||||
|     case eCN_GM_TeleportType::Unstick: | ||||
|         targetPlr = PlayerManager::getPlayer(targetSock); | ||||
|  | ||||
|         PlayerManager::sendPlayerTo(targetSock, targetPlr->x - unstickRange/2 + Rand::rand(unstickRange), | ||||
|   | ||||
							
								
								
									
										22
									
								
								src/Chat.cpp
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								src/Chat.cpp
									
									
									
									
									
								
							| @@ -1,6 +1,9 @@ | ||||
| #include "Chat.hpp" | ||||
|  | ||||
| #include "servers/CNShardServer.hpp" | ||||
|  | ||||
| #include "Player.hpp" | ||||
| #include "PlayerManager.hpp" | ||||
| #include "Groups.hpp" | ||||
| #include "CustomCommands.hpp" | ||||
|  | ||||
| #include <assert.h> | ||||
| @@ -225,10 +228,6 @@ static void tradeChatHandler(CNSocket* sock, CNPacketData* data) { | ||||
| static void groupChatHandler(CNSocket* sock, CNPacketData* data) { | ||||
|     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 (otherPlr == nullptr) | ||||
|         return; | ||||
|  | ||||
|     std::string fullChat = sanitizeText(AUTOU16TOU8(chat->szFreeChat)); | ||||
|  | ||||
| @@ -251,16 +250,15 @@ static void groupChatHandler(CNSocket* sock, CNPacketData* data) { | ||||
|     resp.iSendPCID = plr->iID; | ||||
|     resp.iEmoteCode = chat->iEmoteCode; | ||||
|  | ||||
|     Groups::sendToGroup(otherPlr, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC)); | ||||
|     if (plr->group == nullptr) | ||||
|         sock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC)); | ||||
|     else | ||||
|         Groups::sendToGroup(plr->group, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC)); | ||||
| } | ||||
|  | ||||
| static void groupMenuChatHandler(CNSocket* sock, CNPacketData* data) { | ||||
|     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 (otherPlr == nullptr) | ||||
|         return; | ||||
|  | ||||
|     std::string fullChat = sanitizeText(AUTOU16TOU8(chat->szFreeChat)); | ||||
|     std::string logLine = "[GroupMenuChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat; | ||||
| @@ -275,7 +273,9 @@ static void groupMenuChatHandler(CNSocket* sock, CNPacketData* data) { | ||||
|     resp.iSendPCID = plr->iID; | ||||
|     resp.iEmoteCode = chat->iEmoteCode; | ||||
|  | ||||
|     Groups::sendToGroup(otherPlr, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC)); | ||||
|     if (plr->group == nullptr) | ||||
|         sock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC)); | ||||
|     Groups::sendToGroup(plr->group, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC)); | ||||
| } | ||||
|  | ||||
| // we only allow plain ascii, at least for now | ||||
|   | ||||
| @@ -2,7 +2,10 @@ | ||||
|  | ||||
| #define CMD_PREFIX '/' | ||||
|  | ||||
| #include "servers/CNShardServer.hpp" | ||||
| #include "core/Core.hpp" | ||||
|  | ||||
| #include <string> | ||||
| #include <vector> | ||||
|  | ||||
| namespace Chat { | ||||
|     extern std::vector<std::string> dump; | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| #include "Chunking.hpp" | ||||
| #include "PlayerManager.hpp" | ||||
|  | ||||
| #include "MobAI.hpp" | ||||
| #include "NPCManager.hpp" | ||||
| #include "settings.hpp" | ||||
| #include "Combat.hpp" | ||||
| #include "Eggs.hpp" | ||||
|  | ||||
| #include <assert.h> | ||||
|  | ||||
| using namespace Chunking; | ||||
|  | ||||
| @@ -26,7 +26,7 @@ static void newChunk(ChunkPos pos) { | ||||
|     // add the chunk to the cache of all players and NPCs in the surrounding chunks | ||||
|     std::set<Chunk*> surroundings = getViewableChunks(pos); | ||||
|     for (Chunk* c : surroundings) | ||||
|         for (const EntityRef& ref : c->entities) | ||||
|         for (const EntityRef ref : c->entities) | ||||
|             ref.getEntity()->viewableChunks.insert(chunk); | ||||
| } | ||||
|  | ||||
| @@ -41,24 +41,24 @@ static void deleteChunk(ChunkPos pos) { | ||||
|     // remove the chunk from the cache of all players and NPCs in the surrounding chunks | ||||
|     std::set<Chunk*> surroundings = getViewableChunks(pos); | ||||
|     for(Chunk* c : surroundings) | ||||
|         for (const EntityRef& ref : c->entities) | ||||
|         for (const EntityRef ref : c->entities) | ||||
|             ref.getEntity()->viewableChunks.erase(chunk); | ||||
|  | ||||
|     chunks.erase(pos); // remove from map | ||||
|     delete chunk; // free from memory | ||||
| } | ||||
|  | ||||
| void Chunking::trackEntity(ChunkPos chunkPos, const EntityRef& ref) { | ||||
| void Chunking::trackEntity(ChunkPos chunkPos, const EntityRef ref) { | ||||
|     if (!chunkExists(chunkPos)) | ||||
|         return; // shouldn't happen | ||||
|  | ||||
|     chunks[chunkPos]->entities.insert(ref); | ||||
|  | ||||
|     if (ref.type == EntityType::PLAYER) | ||||
|     if (ref.kind == EntityKind::PLAYER) | ||||
|         chunks[chunkPos]->nplayers++; | ||||
| } | ||||
|  | ||||
| void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef& ref) { | ||||
| void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef ref) { | ||||
|     if (!chunkExists(chunkPos)) | ||||
|         return; // do nothing if chunk doesn't even exist | ||||
|  | ||||
| @@ -66,7 +66,7 @@ void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef& ref) { | ||||
|  | ||||
|     chunk->entities.erase(ref); // gone | ||||
|  | ||||
|     if (ref.type == EntityType::PLAYER) | ||||
|     if (ref.kind == EntityKind::PLAYER) | ||||
|         chunks[chunkPos]->nplayers--; | ||||
|     assert(chunks[chunkPos]->nplayers >= 0); | ||||
|  | ||||
| @@ -75,13 +75,13 @@ void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef& ref) { | ||||
|         deleteChunk(chunkPos); | ||||
| } | ||||
|  | ||||
| void Chunking::addEntityToChunks(std::set<Chunk*> chnks, const EntityRef& ref) { | ||||
| void Chunking::addEntityToChunks(std::set<Chunk*> chnks, const EntityRef ref) { | ||||
|     Entity *ent = ref.getEntity(); | ||||
|     bool alive = ent->isAlive(); | ||||
|     bool alive = ent->isExtant(); | ||||
|  | ||||
|     // TODO: maybe optimize this, potentially using AROUND packets? | ||||
|     for (Chunk *chunk : chnks) { | ||||
|         for (const EntityRef& otherRef : chunk->entities) { | ||||
|         for (const EntityRef otherRef : chunk->entities) { | ||||
|             // skip oneself | ||||
|             if (ref == otherRef) | ||||
|                 continue; | ||||
| @@ -89,31 +89,31 @@ void Chunking::addEntityToChunks(std::set<Chunk*> chnks, const EntityRef& ref) { | ||||
|             Entity *other = otherRef.getEntity(); | ||||
|  | ||||
|             // notify all visible players of the existence of this Entity | ||||
|             if (alive && otherRef.type == EntityType::PLAYER) { | ||||
|             if (alive && otherRef.kind == EntityKind::PLAYER) { | ||||
|                 ent->enterIntoViewOf(otherRef.sock); | ||||
|             } | ||||
|  | ||||
|             // notify this *player* of the existence of all visible Entities | ||||
|             if (ref.type == EntityType::PLAYER && other->isAlive()) { | ||||
|             if (ref.kind == EntityKind::PLAYER && other->isExtant()) { | ||||
|                 other->enterIntoViewOf(ref.sock); | ||||
|             } | ||||
|  | ||||
|             // for mobs, increment playersInView | ||||
|             if (ref.type == EntityType::MOB && otherRef.type == EntityType::PLAYER) | ||||
|             if (ref.kind == EntityKind::MOB && otherRef.kind == EntityKind::PLAYER) | ||||
|                 ((Mob*)ent)->playersInView++; | ||||
|             if (otherRef.type == EntityType::MOB && ref.type == EntityType::PLAYER) | ||||
|             if (otherRef.kind == EntityKind::MOB && ref.kind == EntityKind::PLAYER) | ||||
|                 ((Mob*)other)->playersInView++; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void Chunking::removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef& ref) { | ||||
| void Chunking::removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef ref) { | ||||
|     Entity *ent = ref.getEntity(); | ||||
|     bool alive = ent->isAlive(); | ||||
|     bool alive = ent->isExtant(); | ||||
|  | ||||
|     // TODO: same as above | ||||
|     for (Chunk *chunk : chnks) { | ||||
|         for (const EntityRef& otherRef : chunk->entities) { | ||||
|         for (const EntityRef otherRef : chunk->entities) { | ||||
|             // skip oneself | ||||
|             if (ref == otherRef) | ||||
|                 continue; | ||||
| @@ -121,19 +121,19 @@ void Chunking::removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef& r | ||||
|             Entity *other = otherRef.getEntity(); | ||||
|  | ||||
|             // notify all visible players of the departure of this Entity | ||||
|             if (alive && otherRef.type == EntityType::PLAYER) { | ||||
|             if (alive && otherRef.kind == EntityKind::PLAYER) { | ||||
|                 ent->disappearFromViewOf(otherRef.sock); | ||||
|             } | ||||
|  | ||||
|             // notify this *player* of the departure of all visible Entities | ||||
|             if (ref.type == EntityType::PLAYER && other->isAlive()) { | ||||
|             if (ref.kind == EntityKind::PLAYER && other->isExtant()) { | ||||
|                 other->disappearFromViewOf(ref.sock); | ||||
|             } | ||||
|  | ||||
|             // for mobs, decrement playersInView | ||||
|             if (ref.type == EntityType::MOB && otherRef.type == EntityType::PLAYER) | ||||
|             if (ref.kind == EntityKind::MOB && otherRef.kind == EntityKind::PLAYER) | ||||
|                 ((Mob*)ent)->playersInView--; | ||||
|             if (otherRef.type == EntityType::MOB && ref.type == EntityType::PLAYER) | ||||
|             if (otherRef.kind == EntityKind::MOB && ref.kind == EntityKind::PLAYER) | ||||
|                 ((Mob*)other)->playersInView--; | ||||
|         } | ||||
|     } | ||||
| @@ -154,8 +154,8 @@ static void emptyChunk(ChunkPos chunkPos) { | ||||
|  | ||||
|     // unspawn all of the mobs/npcs | ||||
|     std::set refs(chunk->entities); | ||||
|     for (const EntityRef& ref : refs) { | ||||
|         if (ref.type == EntityType::PLAYER) | ||||
|     for (const EntityRef ref : refs) { | ||||
|         if (ref.kind == EntityKind::PLAYER) | ||||
|             assert(0); | ||||
|  | ||||
|         // every call of this will check if the chunk is empty and delete it if so | ||||
| @@ -163,7 +163,7 @@ static void emptyChunk(ChunkPos chunkPos) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| void Chunking::updateEntityChunk(const EntityRef& ref, ChunkPos from, ChunkPos to) { | ||||
| void Chunking::updateEntityChunk(const EntityRef ref, ChunkPos from, ChunkPos to) { | ||||
|     Entity* ent = ref.getEntity(); | ||||
|  | ||||
|     // move to other chunk's player set | ||||
| @@ -267,54 +267,53 @@ void Chunking::createInstance(uint64_t instanceID) { | ||||
|  | ||||
|     std::cout << "Creating instance " << instanceID << std::endl; | ||||
|     for (ChunkPos &coords : templateChunks) { | ||||
|         for (const EntityRef& ref : chunks[coords]->entities) { | ||||
|             if (ref.type == EntityType::PLAYER) | ||||
|         for (const EntityRef ref : chunks[coords]->entities) { | ||||
|             if (ref.kind == EntityKind::PLAYER) | ||||
|                 continue; | ||||
|  | ||||
|             int npcID = ref.id; | ||||
|             BaseNPC* baseNPC = (BaseNPC*)ref.getEntity(); | ||||
|  | ||||
|             // make a copy of each NPC in the template chunks and put them in the new instance | ||||
|             if (baseNPC->type == EntityType::MOB) { | ||||
|             if (baseNPC->kind == EntityKind::MOB) { | ||||
|                 if (((Mob*)baseNPC)->groupLeader != 0 && ((Mob*)baseNPC)->groupLeader != npcID) | ||||
|                     continue; // follower; don't copy individually | ||||
|  | ||||
|                 Mob* newMob = new Mob(baseNPC->x, baseNPC->y, baseNPC->z, baseNPC->appearanceData.iAngle, | ||||
|                     instanceID, baseNPC->appearanceData.iNPCType, NPCManager::NPCData[baseNPC->appearanceData.iNPCType], NPCManager::nextId--); | ||||
|                 NPCManager::NPCs[newMob->appearanceData.iNPC_ID] = newMob; | ||||
|                 Mob* newMob = new Mob(baseNPC->x, baseNPC->y, baseNPC->z, baseNPC->angle, | ||||
|                     instanceID, baseNPC->type, NPCManager::NPCData[baseNPC->type], NPCManager::nextId--); | ||||
|                 NPCManager::NPCs[newMob->id] = newMob; | ||||
|  | ||||
|                 // if in a group, copy over group members as well | ||||
|                 if (((Mob*)baseNPC)->groupLeader != 0) { | ||||
|                     newMob->groupLeader = newMob->appearanceData.iNPC_ID; // set leader ID for new leader | ||||
|                     newMob->groupLeader = newMob->id; // set leader ID for new leader | ||||
|                     Mob* mobData = (Mob*)baseNPC; | ||||
|                     for (int i = 0; i < 4; i++) { | ||||
|                         if (mobData->groupMember[i] != 0) { | ||||
|                             int followerID = NPCManager::nextId--; // id for follower | ||||
|                             BaseNPC* baseFollower = NPCManager::NPCs[mobData->groupMember[i]]; // follower from template | ||||
|                             // new follower instance | ||||
|                             Mob* newMobFollower = new Mob(baseFollower->x, baseFollower->y, baseFollower->z, baseFollower->appearanceData.iAngle, | ||||
|                                 instanceID, baseFollower->appearanceData.iNPCType, NPCManager::NPCData[baseFollower->appearanceData.iNPCType], followerID); | ||||
|                             Mob* newMobFollower = new Mob(baseFollower->x, baseFollower->y, baseFollower->z, baseFollower->angle, | ||||
|                                 instanceID, baseFollower->type, NPCManager::NPCData[baseFollower->type], followerID); | ||||
|                             // add follower to NPC maps | ||||
|                             NPCManager::NPCs[followerID] = newMobFollower; | ||||
|                             // set follower-specific properties | ||||
|                             newMobFollower->groupLeader = newMob->appearanceData.iNPC_ID; | ||||
|                             newMobFollower->groupLeader = newMob->id; | ||||
|                             newMobFollower->offsetX = ((Mob*)baseFollower)->offsetX; | ||||
|                             newMobFollower->offsetY = ((Mob*)baseFollower)->offsetY; | ||||
|                             // add follower copy to leader copy | ||||
|                             newMob->groupMember[i] = followerID; | ||||
|                             NPCManager::updateNPCPosition(followerID, baseFollower->x, baseFollower->y, baseFollower->z, | ||||
|                                 instanceID, baseFollower->appearanceData.iAngle); | ||||
|                                 instanceID, baseFollower->angle); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 NPCManager::updateNPCPosition(newMob->appearanceData.iNPC_ID, baseNPC->x, baseNPC->y, baseNPC->z, | ||||
|                     instanceID, baseNPC->appearanceData.iAngle); | ||||
|                 NPCManager::updateNPCPosition(newMob->id, baseNPC->x, baseNPC->y, baseNPC->z, | ||||
|                     instanceID, baseNPC->angle); | ||||
|             } else { | ||||
|                 BaseNPC* newNPC = new BaseNPC(baseNPC->x, baseNPC->y, baseNPC->z, baseNPC->appearanceData.iAngle, | ||||
|                     instanceID, baseNPC->appearanceData.iNPCType, NPCManager::nextId--); | ||||
|                 NPCManager::NPCs[newNPC->appearanceData.iNPC_ID] = newNPC; | ||||
|                 NPCManager::updateNPCPosition(newNPC->appearanceData.iNPC_ID, baseNPC->x, baseNPC->y, baseNPC->z, | ||||
|                     instanceID, baseNPC->appearanceData.iAngle); | ||||
|                 BaseNPC* newNPC = new BaseNPC(baseNPC->angle, instanceID, baseNPC->type, NPCManager::nextId--); | ||||
|                 NPCManager::NPCs[newNPC->id] = newNPC; | ||||
|                 NPCManager::updateNPCPosition(newNPC->id, baseNPC->x, baseNPC->y, baseNPC->z, | ||||
|                     instanceID, baseNPC->angle); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -1,14 +1,10 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "core/Core.hpp" | ||||
| #include "EntityRef.hpp" | ||||
|  | ||||
| #include <utility> | ||||
| #include <set> | ||||
| #include <map> | ||||
| #include <tuple> | ||||
| #include <algorithm> | ||||
|  | ||||
| struct EntityRef; | ||||
| #include <vector> | ||||
|  | ||||
| class Chunk { | ||||
| public: | ||||
| @@ -36,13 +32,13 @@ namespace Chunking { | ||||
|  | ||||
|     extern const ChunkPos INVALID_CHUNK; | ||||
|  | ||||
|     void updateEntityChunk(const EntityRef& ref, ChunkPos from, ChunkPos to); | ||||
|     void updateEntityChunk(const EntityRef ref, ChunkPos from, ChunkPos to); | ||||
|  | ||||
|     void trackEntity(ChunkPos chunkPos, const EntityRef& ref); | ||||
|     void untrackEntity(ChunkPos chunkPos, const EntityRef& ref); | ||||
|     void trackEntity(ChunkPos chunkPos, const EntityRef ref); | ||||
|     void untrackEntity(ChunkPos chunkPos, const EntityRef ref); | ||||
|  | ||||
|     void addEntityToChunks(std::set<Chunk*> chnks, const EntityRef& ref); | ||||
|     void removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef& ref); | ||||
|     void addEntityToChunks(std::set<Chunk*> chnks, const EntityRef ref); | ||||
|     void removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef ref); | ||||
|  | ||||
|     bool chunkExists(ChunkPos chunk); | ||||
|     ChunkPos chunkPosAt(int posX, int posY, uint64_t instanceID); | ||||
|   | ||||
							
								
								
									
										691
									
								
								src/Combat.cpp
									
									
									
									
									
								
							
							
						
						
									
										691
									
								
								src/Combat.cpp
									
									
									
									
									
								
							| @@ -1,22 +1,331 @@ | ||||
| #include "Combat.hpp" | ||||
| #include "PlayerManager.hpp" | ||||
| #include "Nanos.hpp" | ||||
| #include "NPCManager.hpp" | ||||
| #include "Items.hpp" | ||||
| #include "Missions.hpp" | ||||
| #include "Groups.hpp" | ||||
| #include "Transport.hpp" | ||||
| #include "Racing.hpp" | ||||
| #include "Abilities.hpp" | ||||
|  | ||||
| #include "servers/CNShardServer.hpp" | ||||
|  | ||||
| #include "Rand.hpp" | ||||
| #include "Player.hpp" | ||||
| #include "PlayerManager.hpp" | ||||
| #include "NPCManager.hpp" | ||||
| #include "Nanos.hpp" | ||||
| #include "Abilities.hpp" | ||||
| #include "Buffs.hpp" | ||||
|  | ||||
| #include <assert.h> | ||||
| #include <iostream> | ||||
| #include <functional> | ||||
|  | ||||
| using namespace Combat; | ||||
|  | ||||
| /// Player Id -> Bullet Id -> Bullet | ||||
| std::map<int32_t, std::map<int8_t, Bullet>> Combat::Bullets; | ||||
|  | ||||
| #pragma region Player | ||||
| bool Player::addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) { | ||||
|     if(!isAlive()) | ||||
|         return false; | ||||
|  | ||||
|     if(!hasBuff(buffId)) { | ||||
|         buffs[buffId] = new Buff(buffId, getRef(), onUpdate, onTick, stack); | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     buffs[buffId]->updateCallbacks(onUpdate, onTick); | ||||
|     buffs[buffId]->addStack(stack); | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| Buff* Player::getBuff(int buffId) { | ||||
|     if(hasBuff(buffId)) { | ||||
|         return buffs[buffId]; | ||||
|     } | ||||
|     return nullptr; | ||||
| } | ||||
|  | ||||
| void Player::removeBuff(int buffId) { | ||||
|     if(hasBuff(buffId)) { | ||||
|         buffs[buffId]->clear(); | ||||
|         delete buffs[buffId]; | ||||
|         buffs.erase(buffId); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void Player::removeBuff(int buffId, BuffClass buffClass) { | ||||
|     if(hasBuff(buffId)) { | ||||
|         buffs[buffId]->clear(buffClass); | ||||
|         // buff might not be stale since another buff class might remain | ||||
|         if(buffs[buffId]->isStale()) { | ||||
|             delete buffs[buffId]; | ||||
|             buffs.erase(buffId); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void Player::clearBuffs(bool force) { | ||||
|     auto it = buffs.begin(); | ||||
|     while(it != buffs.end()) { | ||||
|         Buff* buff = (*it).second; | ||||
|         if(!force) buff->clear(); | ||||
|         delete buff; | ||||
|         it = buffs.erase(it); | ||||
|     } | ||||
| } | ||||
|  | ||||
| bool Player::hasBuff(int buffId) { | ||||
|     auto buff = buffs.find(buffId); | ||||
|     return buff != buffs.end() && !buff->second->isStale(); | ||||
| } | ||||
|  | ||||
| int Player::getCompositeCondition() { | ||||
|     int conditionBitFlag = 0; | ||||
|     for(auto buff : buffs) { | ||||
|         if(!buff.second->isStale() && buff.second->id > 0) | ||||
|             conditionBitFlag |= CSB_FROM_ECSB(buff.first); | ||||
|     } | ||||
|     return conditionBitFlag; | ||||
| } | ||||
|  | ||||
| int Player::takeDamage(EntityRef src, int amt) { | ||||
|     int dmg = amt; | ||||
|     if(HP - dmg < 0) dmg = HP; | ||||
|     HP -= dmg; | ||||
|  | ||||
|     return dmg; | ||||
| } | ||||
|  | ||||
| int Player::heal(EntityRef src, int amt) { | ||||
|     int heal = amt; | ||||
|     if(HP + heal > getMaxHP()) heal = getMaxHP() - HP; | ||||
|     HP += heal; | ||||
|  | ||||
|     return heal; | ||||
| } | ||||
|  | ||||
| bool Player::isAlive() { | ||||
|     return HP > 0; | ||||
| } | ||||
|  | ||||
| int Player::getCurrentHP() { | ||||
|     return HP; | ||||
| } | ||||
|  | ||||
| int Player::getMaxHP() { | ||||
|     return PC_MAXHEALTH(level); | ||||
| } | ||||
|  | ||||
| int Player::getLevel() { | ||||
|     return level; | ||||
| } | ||||
|  | ||||
| std::vector<EntityRef> Player::getGroupMembers() { | ||||
|     std::vector<EntityRef> members; | ||||
|     if(group != nullptr) | ||||
|         members = group->members; | ||||
|     else | ||||
|         members.push_back(PlayerManager::getSockFromID(iID)); | ||||
|     return members; | ||||
| } | ||||
|  | ||||
| int32_t Player::getCharType() { | ||||
|     return 1; // eCharType (eCT_PC) | ||||
| } | ||||
|  | ||||
| int32_t Player::getID() { | ||||
|     return iID; | ||||
| } | ||||
|  | ||||
| EntityRef Player::getRef() { | ||||
|     return EntityRef(PlayerManager::getSockFromID(iID)); | ||||
| } | ||||
|  | ||||
| void Player::step(time_t currTime) { | ||||
|     CNSocket* sock = getRef().sock; | ||||
|  | ||||
|     // nanos | ||||
|     for (int i = 0; i < 3; i++) { | ||||
|         if (activeNano != 0 && equippedNanos[i] == activeNano) { // tick active nano | ||||
|             sNano& nano = Nanos[activeNano]; | ||||
|             int drainRate = 0; | ||||
|  | ||||
|             if (Abilities::SkillTable.find(nano.iSkillID) != Abilities::SkillTable.end()) { | ||||
|                 // nano has skill data | ||||
|                 SkillData* skill = &Abilities::SkillTable[nano.iSkillID]; | ||||
|                 int boost = Nanos::getNanoBoost(this); | ||||
|                 if (skill->drainType == SkillDrainType::PASSIVE) | ||||
|                     drainRate = skill->batteryUse[boost * 3]; | ||||
|             } | ||||
|  | ||||
|             nano.iStamina -= 1 + drainRate / 5; | ||||
|             if (nano.iStamina <= 0) | ||||
|                 Nanos::summonNano(sock, -1, true); // unsummon nano silently | ||||
|  | ||||
|         } else if (Nanos[equippedNanos[i]].iStamina < 150) { // tick resting nano | ||||
|             sNano& nano = Nanos[equippedNanos[i]]; | ||||
|             if (nano.iStamina < 150) | ||||
|                 nano.iStamina += 1; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // buffs | ||||
|     for(auto buffEntry : buffs) { | ||||
|         buffEntry.second->combatTick(currTime); | ||||
|         if(!isAlive()) | ||||
|             break; // unsafe to keep ticking if we're dead | ||||
|     } | ||||
| } | ||||
| #pragma endregion | ||||
|  | ||||
| #pragma region CombatNPC | ||||
| bool CombatNPC::addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) { | ||||
|     if(!isAlive()) | ||||
|         return false; | ||||
|  | ||||
|     if (this->state != AIState::COMBAT && this->state != AIState::ROAMING) | ||||
|         return false; | ||||
|  | ||||
|     if(!hasBuff(buffId)) { | ||||
|         buffs[buffId] = new Buff(buffId, getRef(), onUpdate, onTick, stack); | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     buffs[buffId]->updateCallbacks(onUpdate, onTick); | ||||
|     buffs[buffId]->addStack(stack); | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| Buff* CombatNPC::getBuff(int buffId) { | ||||
|     if(hasBuff(buffId)) { | ||||
|         return buffs[buffId]; | ||||
|     } | ||||
|     return nullptr; | ||||
| } | ||||
|  | ||||
| void CombatNPC::removeBuff(int buffId) { | ||||
|     if(hasBuff(buffId)) { | ||||
|         buffs[buffId]->clear(); | ||||
|         delete buffs[buffId]; | ||||
|         buffs.erase(buffId); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void CombatNPC::removeBuff(int buffId, BuffClass buffClass) { | ||||
|     if(hasBuff(buffId)) { | ||||
|         buffs[buffId]->clear(buffClass); | ||||
|         // buff might not be stale since another buff class might remain | ||||
|         if(buffs[buffId]->isStale()) { | ||||
|             delete buffs[buffId]; | ||||
|             buffs.erase(buffId); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void CombatNPC::clearBuffs(bool force) { | ||||
|     auto it = buffs.begin(); | ||||
|     while(it != buffs.end()) { | ||||
|         Buff* buff = (*it).second; | ||||
|         if(!force) buff->clear(); | ||||
|         delete buff; | ||||
|         it = buffs.erase(it); | ||||
|     } | ||||
| } | ||||
|  | ||||
| bool CombatNPC::hasBuff(int buffId) { | ||||
|     auto buff = buffs.find(buffId); | ||||
|     return buff != buffs.end() && !buff->second->isStale(); | ||||
| } | ||||
|  | ||||
| int CombatNPC::getCompositeCondition() { | ||||
|     int conditionBitFlag = 0; | ||||
|     for(auto buff : buffs) { | ||||
|         if(!buff.second->isStale() && buff.second->id > 0) | ||||
|             conditionBitFlag |= CSB_FROM_ECSB(buff.first); | ||||
|     } | ||||
|     return conditionBitFlag; | ||||
| } | ||||
|  | ||||
| int CombatNPC::takeDamage(EntityRef src, int amt) { | ||||
|     int dmg = amt; | ||||
|     if(hp - dmg < 0) dmg = hp; | ||||
|     hp -= dmg; | ||||
|  | ||||
|     if(hp <= 0) transition(AIState::DEAD, src); | ||||
|  | ||||
|     return dmg; | ||||
| } | ||||
|  | ||||
| int CombatNPC::heal(EntityRef src, int amt) { | ||||
|     int heal = amt; | ||||
|     if(hp + heal > getMaxHP()) heal = getMaxHP() - hp; | ||||
|     hp += heal; | ||||
|  | ||||
|     return heal; | ||||
| } | ||||
|  | ||||
| bool CombatNPC::isAlive() { | ||||
|     return hp > 0; | ||||
| } | ||||
|  | ||||
| int CombatNPC::getCurrentHP() { | ||||
|     return hp; | ||||
| } | ||||
|  | ||||
| int CombatNPC::getMaxHP() { | ||||
|     return maxHealth; | ||||
| } | ||||
|  | ||||
| int CombatNPC::getLevel() { | ||||
|     return level; | ||||
| } | ||||
|  | ||||
| std::vector<EntityRef> CombatNPC::getGroupMembers() { | ||||
|     std::vector<EntityRef> members; | ||||
|     if(group != nullptr) | ||||
|         members = group->members; | ||||
|     else | ||||
|         members.push_back(id); | ||||
|     return members; | ||||
| } | ||||
|  | ||||
| int32_t CombatNPC::getCharType() { | ||||
|     if(kind == EntityKind::MOB) | ||||
|         return 4; // eCharType (eCT_MOB) | ||||
|     return 2; // eCharType (eCT_NPC) | ||||
| } | ||||
|  | ||||
| int32_t CombatNPC::getID() { | ||||
|     return id; | ||||
| } | ||||
|  | ||||
| EntityRef CombatNPC::getRef() { | ||||
|     return EntityRef(id); | ||||
| } | ||||
|  | ||||
| void CombatNPC::step(time_t currTime) { | ||||
|      | ||||
|     if(stateHandlers.find(state) != stateHandlers.end()) | ||||
|         stateHandlers[state](this, currTime); | ||||
|     else { | ||||
|         std::cout << "[WARN] State " << (int)state << " has no handler; going inactive" << std::endl; | ||||
|         transition(AIState::INACTIVE, id); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void CombatNPC::transition(AIState newState, EntityRef src) { | ||||
|     state = newState; | ||||
|  | ||||
|     if (transitionHandlers.find(newState) != transitionHandlers.end()) | ||||
|         transitionHandlers[newState](this, src); | ||||
|     else { | ||||
|         std::cout << "[WARN] Transition to " << (int)state << " has no handler; going inactive" << std::endl; | ||||
|         transition(AIState::INACTIVE, id); | ||||
|     } | ||||
|  | ||||
|     // trigger special NPCEvents, if applicable | ||||
|     for (NPCEvent& event : NPCManager::NPCEvents) | ||||
|         if (event.triggerState == newState && event.npcType == type) | ||||
|             event.handler(this); | ||||
| } | ||||
| #pragma endregion | ||||
|  | ||||
| static std::pair<int,int> getDamage(int attackPower, int defensePower, bool shouldCrit, | ||||
|                                          bool batteryBoost, int attackerStyle, | ||||
|                                          int defenderStyle, int difficulty) { | ||||
| @@ -114,7 +423,7 @@ static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) { | ||||
|  | ||||
|  | ||||
|         BaseNPC* npc = NPCManager::NPCs[targets[i]]; | ||||
|         if (npc->type != EntityType::MOB) { | ||||
|         if (npc->kind != EntityKind::MOB) { | ||||
|             std::cout << "[WARN] pcAttackNpcs: NPC is not a mob" << std::endl; | ||||
|             return; | ||||
|         } | ||||
| @@ -137,11 +446,11 @@ static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) { | ||||
|         else | ||||
|             plr->batteryW = 0; | ||||
|  | ||||
|         damage.first = hitMob(sock, mob, damage.first); | ||||
|         damage.first = mob->takeDamage(sock, damage.first); | ||||
|  | ||||
|         respdata[i].iID = mob->appearanceData.iNPC_ID; | ||||
|         respdata[i].iID = mob->id; | ||||
|         respdata[i].iDamage = damage.first; | ||||
|         respdata[i].iHP = mob->appearanceData.iHP; | ||||
|         respdata[i].iHP = mob->hp; | ||||
|         respdata[i].iHitFlag = damage.second; // hitscan, not a rocket or a grenade | ||||
|     } | ||||
|  | ||||
| @@ -168,7 +477,7 @@ void Combat::npcAttackPc(Mob *mob, time_t currTime) { | ||||
|     if (!(plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE)) | ||||
|         plr->HP -= damage.first; | ||||
|  | ||||
|     pkt->iNPC_ID = mob->appearanceData.iNPC_ID; | ||||
|     pkt->iNPC_ID = mob->id; | ||||
|     pkt->iPCCnt = 1; | ||||
|  | ||||
|     atk->iID = plr->iID; | ||||
| @@ -180,52 +489,11 @@ void Combat::npcAttackPc(Mob *mob, time_t currTime) { | ||||
|     PlayerManager::sendToViewable(mob->target, respbuf, P_FE2CL_NPC_ATTACK_PCs); | ||||
|  | ||||
|     if (plr->HP <= 0) { | ||||
|         mob->target = nullptr; | ||||
|         mob->state = MobState::RETREAT; | ||||
|         if (!MobAI::aggroCheck(mob, currTime)) { | ||||
|             MobAI::clearDebuff(mob); | ||||
|             if (mob->groupLeader != 0) | ||||
|                 MobAI::groupRetreat(mob); | ||||
|         } | ||||
|         if (!MobAI::aggroCheck(mob, getTime())) | ||||
|             mob->transition(AIState::RETREAT, mob->target); | ||||
|     } | ||||
| } | ||||
|  | ||||
| int Combat::hitMob(CNSocket *sock, Mob *mob, int damage) { | ||||
|     // cannot kill mobs multiple times; cannot harm retreating mobs | ||||
|     if (mob->state != MobState::ROAMING && mob->state != MobState::COMBAT) { | ||||
|         return 0; // no damage | ||||
|     } | ||||
|  | ||||
|     if (mob->skillStyle >= 0) | ||||
|         return 0; // don't hurt a mob casting corruption | ||||
|  | ||||
|     if (mob->state == MobState::ROAMING) { | ||||
|         assert(mob->target == nullptr); | ||||
|         MobAI::enterCombat(sock, mob); | ||||
|  | ||||
|         if (mob->groupLeader != 0) | ||||
|             MobAI::followToCombat(mob); | ||||
|     } | ||||
|  | ||||
|     mob->appearanceData.iHP -= damage; | ||||
|  | ||||
|     // wake up sleeping monster | ||||
|     if (mob->appearanceData.iConditionBitFlag & CSB_BIT_MEZ) { | ||||
|         mob->appearanceData.iConditionBitFlag &= ~CSB_BIT_MEZ; | ||||
|  | ||||
|         INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1); | ||||
|         pkt1.eCT = 2; | ||||
|         pkt1.iID = mob->appearanceData.iNPC_ID; | ||||
|         pkt1.iConditionBitFlag = mob->appearanceData.iConditionBitFlag; | ||||
|         NPCManager::sendToViewable(mob, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT)); | ||||
|     } | ||||
|  | ||||
|     if (mob->appearanceData.iHP <= 0) | ||||
|         killMob(mob->target, mob); | ||||
|  | ||||
|     return damage; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * When a group of players is doing missions together, we want them to all get | ||||
|  * quest items at the same time, but we don't want the odds of quest item | ||||
| @@ -233,96 +501,16 @@ int Combat::hitMob(CNSocket *sock, Mob *mob, int damage) { | ||||
|  * single RNG roll per mission task, and every group member shares that same | ||||
|  * set of rolls. | ||||
|  */ | ||||
| static void genQItemRolls(Player *leader, std::map<int, int>& rolls) { | ||||
|     for (int i = 0; i < leader->groupCnt; i++) { | ||||
|         if (leader->groupIDs[i] == 0) | ||||
|             continue; | ||||
|  | ||||
|         CNSocket *otherSock = PlayerManager::getSockFromID(leader->groupIDs[i]); | ||||
|         if (otherSock == nullptr) | ||||
|             continue; | ||||
|  | ||||
|         Player *member = PlayerManager::getPlayer(otherSock); | ||||
| void Combat::genQItemRolls(std::vector<Player*> players, std::map<int, int>& rolls) { | ||||
|     for (int i = 0; i < players.size(); i++) { | ||||
|  | ||||
|         Player* member = players[i]; | ||||
|         for (int j = 0; j < ACTIVE_MISSION_COUNT; j++) | ||||
|             if (member->tasks[j] != 0) | ||||
|                 rolls[member->tasks[j]] = Rand::rand(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void Combat::killMob(CNSocket *sock, Mob *mob) { | ||||
|     mob->state = MobState::DEAD; | ||||
|     mob->target = nullptr; | ||||
|     mob->appearanceData.iConditionBitFlag = 0; | ||||
|     mob->skillStyle = -1; | ||||
|     mob->unbuffTimes.clear(); | ||||
|     mob->killedTime = getTime(); // XXX: maybe introduce a shard-global time for each step? | ||||
|  | ||||
|     // check for the edge case where hitting the mob did not aggro it | ||||
|     if (sock != nullptr) { | ||||
|         Player* plr = PlayerManager::getPlayer(sock); | ||||
|  | ||||
|         Items::DropRoll rolled; | ||||
|         Items::DropRoll eventRolled; | ||||
|         std::map<int, int> qitemRolls; | ||||
|  | ||||
|         Player *leader = PlayerManager::getPlayerFromID(plr->iIDGroup); | ||||
|         assert(leader != nullptr); // should never happen | ||||
|  | ||||
|         genQItemRolls(leader, qitemRolls); | ||||
|  | ||||
|         if (plr->groupCnt == 1 && plr->iIDGroup == plr->iID) { | ||||
|             Items::giveMobDrop(sock, mob, rolled, eventRolled); | ||||
|             Missions::mobKilled(sock, mob->appearanceData.iNPCType, qitemRolls); | ||||
|         } else { | ||||
|             for (int i = 0; i < leader->groupCnt; i++) { | ||||
|                 CNSocket* sockTo = PlayerManager::getSockFromID(leader->groupIDs[i]); | ||||
|                 if (sockTo == nullptr) | ||||
|                     continue; | ||||
|  | ||||
|                 Player *otherPlr = PlayerManager::getPlayer(sockTo); | ||||
|  | ||||
|                 // only contribute to group members' kills if they're close enough | ||||
|                 int dist = std::hypot(plr->x - otherPlr->x + 1, plr->y - otherPlr->y + 1); | ||||
|                 if (dist > 5000) | ||||
|                     continue; | ||||
|  | ||||
|                 Items::giveMobDrop(sockTo, mob, rolled, eventRolled); | ||||
|                 Missions::mobKilled(sockTo, mob->appearanceData.iNPCType, qitemRolls); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // delay the despawn animation | ||||
|     mob->despawned = false; | ||||
|  | ||||
|     // fire any triggered events | ||||
|     for (NPCEvent& event : NPCManager::NPCEvents) | ||||
|         if (event.trigger == ON_KILLED && event.npcType == mob->appearanceData.iNPCType) | ||||
|             event.handler(sock, mob); | ||||
|  | ||||
|     auto it = Transport::NPCQueues.find(mob->appearanceData.iNPC_ID); | ||||
|     if (it == Transport::NPCQueues.end() || it->second.empty()) | ||||
|         return; | ||||
|  | ||||
|     // rewind or empty the movement queue | ||||
|     if (mob->staticPath) { | ||||
|         /* | ||||
|          * This is inelegant, but we wind forward in the path until we find the point that | ||||
|          * corresponds with the Mob's spawn point. | ||||
|          * | ||||
|          * IMPORTANT: The check in TableData::loadPaths() must pass or else this will loop forever. | ||||
|          */ | ||||
|         auto& queue = it->second; | ||||
|         for (auto point = queue.front(); point.x != mob->spawnX || point.y != mob->spawnY; point = queue.front()) { | ||||
|             queue.pop(); | ||||
|             queue.push(point); | ||||
|         } | ||||
|     } else { | ||||
|         Transport::NPCQueues.erase(mob->appearanceData.iNPC_ID); | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void combatBegin(CNSocket *sock, CNPacketData *data) { | ||||
|     Player *plr = PlayerManager::getPlayer(sock); | ||||
|  | ||||
| @@ -345,40 +533,27 @@ static void combatEnd(CNSocket *sock, CNPacketData *data) { | ||||
|     plr->healCooldown = 4000; | ||||
| } | ||||
|  | ||||
| static void dotDamageOnOff(CNSocket *sock, CNPacketData *data) { | ||||
|     sP_CL2FE_DOT_DAMAGE_ONOFF *pkt = (sP_CL2FE_DOT_DAMAGE_ONOFF*)data->buf; | ||||
| static void dealGooDamage(CNSocket *sock) { | ||||
|     Player *plr = PlayerManager::getPlayer(sock); | ||||
|     if(plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE) | ||||
|         return; // ignore completely | ||||
|  | ||||
|     if ((plr->iConditionBitFlag & CSB_BIT_INFECTION) != (bool)pkt->iFlag) | ||||
|         plr->iConditionBitFlag ^= CSB_BIT_INFECTION; | ||||
|  | ||||
|     INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt1); | ||||
|  | ||||
|     pkt1.eCSTB = ECSB_INFECTION; // eCharStatusTimeBuffID | ||||
|     pkt1.eTBU = 1; // eTimeBuffUpdate | ||||
|     pkt1.eTBT = 0; // eTimeBuffType 1 means nano | ||||
|     pkt1.iConditionBitFlag = plr->iConditionBitFlag; | ||||
|  | ||||
|     sock->sendPacket((void*)&pkt1, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE)); | ||||
| } | ||||
|  | ||||
| static void dealGooDamage(CNSocket *sock, int amount) { | ||||
|     size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_DotDamage); | ||||
|     assert(resplen < CN_PACKET_BUFFER_SIZE - 8); | ||||
|     uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; | ||||
|     Player *plr = PlayerManager::getPlayer(sock); | ||||
|  | ||||
|     memset(respbuf, 0, resplen); | ||||
|  | ||||
|     sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK *pkt = (sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK*)respbuf; | ||||
|     sSkillResult_DotDamage *dmg = (sSkillResult_DotDamage*)(respbuf + sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK)); | ||||
|  | ||||
|     if (plr->iConditionBitFlag & CSB_BIT_PROTECT_INFECTION) { | ||||
|     int amount = PC_MAXHEALTH(plr->level) * 3 / 20; | ||||
|     Buff* protectionBuff = plr->getBuff(ECSB_PROTECT_INFECTION); | ||||
|     if (protectionBuff != nullptr) { | ||||
|         amount = -2; // -2 is the magic number for "Protected" to appear as the damage number | ||||
|         dmg->bProtected = 1; | ||||
|  | ||||
|         // eggs allow protection without nanos | ||||
|         if (plr->activeNano != -1 && (plr->iSelfConditionBitFlag & CSB_BIT_PROTECT_INFECTION)) | ||||
|         if (protectionBuff->maxClass() <= BuffClass::NANO && plr->activeNano != -1) | ||||
|             plr->Nanos[plr->activeNano].iStamina -= 3; | ||||
|     } else { | ||||
|         plr->HP -= amount; | ||||
| @@ -402,12 +577,39 @@ static void dealGooDamage(CNSocket *sock, int amount) { | ||||
|     dmg->iID = plr->iID; | ||||
|     dmg->iDamage = amount; | ||||
|     dmg->iHP = plr->HP; | ||||
|     dmg->iConditionBitFlag = plr->iConditionBitFlag; | ||||
|     dmg->iConditionBitFlag = plr->getCompositeCondition(); | ||||
|  | ||||
|     sock->sendPacket((void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen); | ||||
|     PlayerManager::sendToViewable(sock, (void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen); | ||||
| } | ||||
|  | ||||
| static void dotDamageOnOff(CNSocket *sock, CNPacketData *data) { | ||||
|     sP_CL2FE_DOT_DAMAGE_ONOFF *pkt = (sP_CL2FE_DOT_DAMAGE_ONOFF*)data->buf; | ||||
|     Player *plr = PlayerManager::getPlayer(sock); | ||||
|  | ||||
|     // infection debuff toggles as the client asks it to, | ||||
|     // so we add and remove a permanent debuff | ||||
|     if (pkt->iFlag && !plr->hasBuff(ECSB_INFECTION)) { | ||||
|         BuffStack infection = { | ||||
|             -1, // infinite | ||||
|             0, // no value | ||||
|             sock, // self-inflicted | ||||
|             BuffClass::ENVIRONMENT | ||||
|         }; | ||||
|         plr->addBuff(ECSB_INFECTION, | ||||
|             [](EntityRef self, Buff* buff, int status, BuffStack* stack) { | ||||
|                 Buffs::timeBuffUpdate(self, buff, status, stack); | ||||
|             }, | ||||
|             [](EntityRef self, Buff* buff, time_t currTime) { | ||||
|                 if(self.kind == EntityKind::PLAYER) | ||||
|                     dealGooDamage(self.sock); | ||||
|             }, | ||||
|             &infection); | ||||
|     } else if(!pkt->iFlag && plr->hasBuff(ECSB_INFECTION)) { | ||||
|         plr->removeBuff(ECSB_INFECTION); | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void pcAttackChars(CNSocket *sock, CNPacketData *data) { | ||||
|     sP_CL2FE_REQ_PC_ATTACK_CHARs* pkt = (sP_CL2FE_REQ_PC_ATTACK_CHARs*)data->buf; | ||||
|     Player *plr = PlayerManager::getPlayer(sock); | ||||
| @@ -417,12 +619,12 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) { | ||||
|         return; | ||||
|  | ||||
|     // Unlike the attack mob packet, attacking players packet has an 8-byte trail (Instead of 4 bytes). | ||||
|     if (!validInVarPacket(sizeof(sP_CL2FE_REQ_PC_ATTACK_CHARs), pkt->iTargetCnt, sizeof(int32_t) * 2, data->size)) { | ||||
|     if (!validInVarPacket(sizeof(sP_CL2FE_REQ_PC_ATTACK_CHARs), pkt->iTargetCnt, sizeof(sGM_PVPTarget), data->size)) { | ||||
|         std::cout << "[WARN] bad sP_CL2FE_REQ_PC_ATTACK_CHARs packet size\n"; | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     int32_t *pktdata = (int32_t*)((uint8_t*)data->buf + sizeof(sP_CL2FE_REQ_PC_ATTACK_CHARs)); | ||||
|     sGM_PVPTarget* pktdata = (sGM_PVPTarget*)((uint8_t*)data->buf + sizeof(sP_CL2FE_REQ_PC_ATTACK_CHARs)); | ||||
|  | ||||
|     if (!validOutVarPacket(sizeof(sP_FE2CL_PC_ATTACK_CHARs_SUCC), pkt->iTargetCnt, sizeof(sAttackResult))) { | ||||
|         std::cout << "[WARN] bad sP_FE2CL_PC_ATTACK_CHARs_SUCC packet size\n"; | ||||
| @@ -441,11 +643,19 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) { | ||||
|     resp->iTargetCnt = pkt->iTargetCnt; | ||||
|  | ||||
|     for (int i = 0; i < pkt->iTargetCnt; i++) { | ||||
|         if (pktdata[i*2+1] == 1) { // eCT == 1; attack player | ||||
|             Player *target = nullptr; | ||||
|  | ||||
|         ICombatant* target = nullptr; | ||||
|         std::pair<int, int> damage; | ||||
|  | ||||
|         if (pkt->iTargetCnt > 1) | ||||
|             damage.first = plr->groupDamage; | ||||
|         else | ||||
|             damage.first = plr->pointDamage; | ||||
|  | ||||
|         if (pktdata[i].eCT == 1) { // eCT == 1; attack player | ||||
|  | ||||
|             for (auto& pair : PlayerManager::players) { | ||||
|                 if (pair.second->iID == pktdata[i*2]) { | ||||
|                 if (pair.second->iID == pktdata[i].iID) { | ||||
|                     target = pair.second; | ||||
|                     break; | ||||
|                 } | ||||
| @@ -457,67 +667,41 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             std::pair<int,int> damage; | ||||
|             damage = getDamage(damage.first, ((Player*)target)->defense, true, (plr->batteryW > 6 + plr->level), -1, -1, 0); | ||||
|  | ||||
|             if (pkt->iTargetCnt > 1) | ||||
|                 damage.first = plr->groupDamage; | ||||
|             else | ||||
|                 damage.first = plr->pointDamage; | ||||
|  | ||||
|             damage = getDamage(damage.first, target->defense, true, (plr->batteryW > 6 + plr->level), -1, -1, 0); | ||||
|  | ||||
|             if (plr->batteryW >= 6 + plr->level) | ||||
|                 plr->batteryW -= 6 + plr->level; | ||||
|             else | ||||
|                 plr->batteryW = 0; | ||||
|  | ||||
|             target->HP -= damage.first; | ||||
|  | ||||
|             respdata[i].eCT = pktdata[i*2+1]; | ||||
|             respdata[i].iID = target->iID; | ||||
|             respdata[i].iDamage = damage.first; | ||||
|             respdata[i].iHP = target->HP; | ||||
|             respdata[i].iHitFlag = damage.second; // hitscan, not a rocket or a grenade | ||||
|         } else { // eCT == 4; attack mob | ||||
|             if (NPCManager::NPCs.find(pktdata[i*2]) == NPCManager::NPCs.end()) { | ||||
|  | ||||
|             if (NPCManager::NPCs.find(pktdata[i].iID) == NPCManager::NPCs.end()) { | ||||
|                 // not sure how to best handle this | ||||
|                 std::cout << "[WARN] pcAttackChars: NPC ID not found" << std::endl; | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             BaseNPC* npc = NPCManager::NPCs[pktdata[i * 2]]; | ||||
|             if (npc->type != EntityType::MOB) { | ||||
|             BaseNPC* npc = NPCManager::NPCs[pktdata[i].iID]; | ||||
|             if (npc->kind != EntityKind::MOB) { | ||||
|                 std::cout << "[WARN] pcAttackChars: NPC is not a mob" << std::endl; | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             Mob* mob = (Mob*)npc; | ||||
|  | ||||
|             std::pair<int,int> damage; | ||||
|  | ||||
|             if (pkt->iTargetCnt > 1) | ||||
|                 damage.first = plr->groupDamage; | ||||
|             else | ||||
|                 damage.first = plr->pointDamage; | ||||
|  | ||||
|             target = mob; | ||||
|             int difficulty = (int)mob->data["m_iNpcLevel"]; | ||||
|  | ||||
|             damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, (plr->batteryW > 6 + difficulty), | ||||
|                 Nanos::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty); | ||||
|  | ||||
|             if (plr->batteryW >= 6 + difficulty) | ||||
|                 plr->batteryW -= 6 + difficulty; | ||||
|             else | ||||
|                 plr->batteryW = 0; | ||||
|  | ||||
|             damage.first = hitMob(sock, mob, damage.first); | ||||
|  | ||||
|             respdata[i].eCT = pktdata[i*2+1]; | ||||
|             respdata[i].iID = mob->appearanceData.iNPC_ID; | ||||
|             respdata[i].iDamage = damage.first; | ||||
|             respdata[i].iHP = mob->appearanceData.iHP; | ||||
|             respdata[i].iHitFlag = damage.second; // hitscan, not a rocket or a grenade | ||||
|         } | ||||
|  | ||||
|         if (plr->batteryW >= 6 + plr->level) | ||||
|             plr->batteryW -= 6 + plr->level; | ||||
|         else | ||||
|             plr->batteryW = 0; | ||||
|  | ||||
|         damage.first = target->takeDamage(sock, damage.first); | ||||
|  | ||||
|         respdata[i].eCT = pktdata[i].eCT; | ||||
|         respdata[i].iID = target->getID(); | ||||
|         respdata[i].iDamage = damage.first; | ||||
|         respdata[i].iHP = target->getCurrentHP(); | ||||
|         respdata[i].iHitFlag = damage.second; // hitscan, not a rocket or a grenade | ||||
|     } | ||||
|  | ||||
|     sock->sendPacket((void*)respbuf, P_FE2CL_PC_ATTACK_CHARs_SUCC, resplen); | ||||
| @@ -653,21 +837,6 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // rapid fire anti-cheat | ||||
|     time_t currTime = getTime(); | ||||
|     if (currTime - plr->lastShot < plr->fireRate * 80) | ||||
|         plr->suspicionRating += plr->fireRate * 100 + plr->lastShot - currTime; // gain suspicion for rapid firing | ||||
|     else if (currTime - plr->lastShot < plr->fireRate * 180 && plr->suspicionRating > 0) | ||||
|         plr->suspicionRating += plr->fireRate * 100 + plr->lastShot - currTime; // lose suspicion for delayed firing | ||||
|  | ||||
|     plr->lastShot = currTime; | ||||
|  | ||||
|     if (plr->suspicionRating > 10000) { // kill the socket when the player is too suspicious | ||||
|         sock->kill(); | ||||
|         CNShardServer::_killConnection(sock); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|      * initialize response struct | ||||
|      * rocket style hit doesn't work properly, so we're always sending this one | ||||
| @@ -696,7 +865,7 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) { | ||||
|         } | ||||
|  | ||||
|         BaseNPC* npc = NPCManager::NPCs[pktdata[i]]; | ||||
|         if (npc->type != EntityType::MOB) { | ||||
|         if (npc->kind != EntityKind::MOB) { | ||||
|             std::cout << "[WARN] projectileHit: NPC is not a mob" << std::endl; | ||||
|             return; | ||||
|         } | ||||
| @@ -709,11 +878,11 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) { | ||||
|         int difficulty = (int)mob->data["m_iNpcLevel"]; | ||||
|         damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, bullet->weaponBoost, Nanos::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty); | ||||
|  | ||||
|         damage.first = hitMob(sock, mob, damage.first); | ||||
|         damage.first = mob->takeDamage(sock, damage.first); | ||||
|  | ||||
|         respdata[i].iID = mob->appearanceData.iNPC_ID; | ||||
|         respdata[i].iID = mob->id; | ||||
|         respdata[i].iDamage = damage.first; | ||||
|         respdata[i].iHP = mob->appearanceData.iHP; | ||||
|         respdata[i].iHP = mob->hp; | ||||
|         respdata[i].iHitFlag = damage.second; | ||||
|     } | ||||
|  | ||||
| @@ -728,6 +897,7 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) { | ||||
|  | ||||
| static void playerTick(CNServer *serv, time_t currTime) { | ||||
|     static time_t lastHealTime = 0; | ||||
|     static time_t lastCombatTIme = 0; | ||||
|  | ||||
|     for (auto& pair : PlayerManager::players) { | ||||
|         CNSocket *sock = pair.first; | ||||
| @@ -735,18 +905,13 @@ static void playerTick(CNServer *serv, time_t currTime) { | ||||
|         bool transmit = false; | ||||
|  | ||||
|         // group ticks | ||||
|         if (plr->groupCnt > 1) | ||||
|             Groups::groupTickInfo(plr); | ||||
|         if (plr->group != nullptr) | ||||
|             Groups::groupTickInfo(sock); | ||||
|  | ||||
|         // do not tick dead players | ||||
|         if (plr->HP <= 0) | ||||
|             continue; | ||||
|  | ||||
|         // fm patch/lake damage | ||||
|         if ((plr->iConditionBitFlag & CSB_BIT_INFECTION) | ||||
|             && !(plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE)) | ||||
|             dealGooDamage(sock, PC_MAXHEALTH(plr->level) * 3 / 20); | ||||
|  | ||||
|         // heal | ||||
|         if (currTime - lastHealTime >= 4000 && !plr->inCombat && plr->HP < PC_MAXHEALTH(plr->level)) { | ||||
|             if (currTime - lastHealTime - plr->healCooldown >= 4000) { | ||||
| @@ -758,22 +923,24 @@ static void playerTick(CNServer *serv, time_t currTime) { | ||||
|                 plr->healCooldown -= 4000; | ||||
|         } | ||||
|  | ||||
|         for (int i = 0; i < 3; i++) { | ||||
|             if (plr->activeNano != 0 && plr->equippedNanos[i] == plr->activeNano) { // spend stamina | ||||
|                 plr->Nanos[plr->activeNano].iStamina -= 1 + plr->nanoDrainRate / 5; | ||||
|         // combat tick | ||||
|         if(currTime - lastCombatTIme >= 2000) { | ||||
|             plr->step(currTime); | ||||
|             transmit = true; | ||||
|         } | ||||
|  | ||||
|                 if (plr->Nanos[plr->activeNano].iStamina <= 0) | ||||
|                     Nanos::summonNano(sock, -1, true); // unsummon nano silently | ||||
|  | ||||
|                 transmit = true; | ||||
|             } else if (plr->Nanos[plr->equippedNanos[i]].iStamina < 150) { // regain stamina | ||||
|                 sNano& nano = plr->Nanos[plr->equippedNanos[i]]; | ||||
|                 nano.iStamina += 1; | ||||
|  | ||||
|                 if (nano.iStamina > 150) | ||||
|                     nano.iStamina = 150; | ||||
|  | ||||
|                 transmit = true; | ||||
|         // nanos | ||||
|         if (plr->activeNano != 0) { // tick active nano | ||||
|             sNano* nano = plr->getActiveNano(); | ||||
|             if (Abilities::SkillTable.find(nano->iSkillID) != Abilities::SkillTable.end()) { | ||||
|                 // nano has skill data | ||||
|                 SkillData* skill = &Abilities::SkillTable[nano->iSkillID]; | ||||
|                 if (skill->drainType == SkillDrainType::PASSIVE) { | ||||
|                     ICombatant* src = dynamic_cast<ICombatant*>(plr); | ||||
|                     int32_t targets[] = { plr->iID }; | ||||
|                     std::vector<ICombatant*> affectedCombatants = Abilities::matchTargets(src, skill, 1, targets); | ||||
|                     Abilities::useNanoSkill(sock, skill, *nano, affectedCombatants); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @@ -789,6 +956,20 @@ static void playerTick(CNServer *serv, time_t currTime) { | ||||
|             PlayerManager::sendToViewable(sock, (void*)&dead, P_FE2CL_PC_SUDDEN_DEAD, sizeof(sP_FE2CL_PC_SUDDEN_DEAD)); | ||||
|         } | ||||
|  | ||||
|         // process buffsets | ||||
|         auto it = plr->buffs.begin(); | ||||
|         while(it != plr->buffs.end()) { | ||||
|             Buff* buff = (*it).second; | ||||
|             //buff->combatTick() gets called in Player::step | ||||
|             buff->tick(currTime); | ||||
|             if(buff->isStale()) { | ||||
|                 // garbage collect | ||||
|                 it = plr->buffs.erase(it); | ||||
|                 delete buff; | ||||
|             } | ||||
|             else it++; | ||||
|         } | ||||
|  | ||||
|         if (transmit) { | ||||
|             INITSTRUCT(sP_FE2CL_REP_PC_TICK, pkt); | ||||
|  | ||||
| @@ -803,13 +984,15 @@ static void playerTick(CNServer *serv, time_t currTime) { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // if this was a heal tick, update the counter outside of the loop | ||||
|     // if this was a heal/combat tick, update the counters outside of the loop | ||||
|     if (currTime - lastHealTime >= 4000) | ||||
|         lastHealTime = currTime; | ||||
|     if(currTime - lastCombatTIme >= 2000) | ||||
|         lastCombatTIme = currTime; | ||||
| } | ||||
|  | ||||
| void Combat::init() { | ||||
|     REGISTER_SHARD_TIMER(playerTick, 2000); | ||||
|     REGISTER_SHARD_TIMER(playerTick, MS_PER_PLAYER_TICK); | ||||
|  | ||||
|     REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ATTACK_NPCs, pcAttackNpcs); | ||||
|  | ||||
|   | ||||
| @@ -1,15 +1,10 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "core/Core.hpp" | ||||
| #include "servers/CNShardServer.hpp" | ||||
| #include "NPC.hpp" | ||||
| #include "Player.hpp" | ||||
| #include "MobAI.hpp" | ||||
|  | ||||
| #include "JSON.hpp" | ||||
|  | ||||
| #include <map> | ||||
| #include <unordered_map> | ||||
| #include <queue> | ||||
| #include <vector> | ||||
|  | ||||
| struct Bullet { | ||||
|     int pointDamage; | ||||
| @@ -24,6 +19,5 @@ namespace Combat { | ||||
|     void init(); | ||||
|  | ||||
|     void npcAttackPc(Mob *mob, time_t currTime); | ||||
|     int hitMob(CNSocket *sock, Mob *mob, int damage); | ||||
|     void killMob(CNSocket *sock, Mob *mob); | ||||
|     void genQItemRolls(std::vector<Player*> players, std::map<int, int>& rolls); | ||||
| } | ||||
|   | ||||
| @@ -1,18 +1,19 @@ | ||||
| #include "CustomCommands.hpp" | ||||
| #include "Chat.hpp" | ||||
|  | ||||
| #include "db/Database.hpp" | ||||
|  | ||||
| #include "Player.hpp" | ||||
| #include "PlayerManager.hpp" | ||||
| #include "Chat.hpp" | ||||
| #include "TableData.hpp" | ||||
| #include "NPCManager.hpp" | ||||
| #include "Eggs.hpp" | ||||
| #include "MobAI.hpp" | ||||
| #include "Items.hpp" | ||||
| #include "db/Database.hpp" | ||||
| #include "Transport.hpp" | ||||
| #include "Missions.hpp" | ||||
| #include "Eggs.hpp" | ||||
| #include "Items.hpp" | ||||
| #include "Abilities.hpp" | ||||
|  | ||||
| #include <sstream> | ||||
| #include <iterator> | ||||
| #include <math.h> | ||||
| #include <limits.h> | ||||
|  | ||||
| typedef void (*CommandHandler)(std::string fullString, std::vector<std::string>& args, CNSocket* sock); | ||||
| @@ -243,20 +244,20 @@ static void summonWCommand(std::string full, std::vector<std::string>& args, CNS | ||||
|     BaseNPC *npc = NPCManager::summonNPC(plr->x, plr->y, plr->z, plr->instanceID, type, true); | ||||
|  | ||||
|     // update angle | ||||
|     npc->appearanceData.iAngle = (plr->angle + 180) % 360; | ||||
|     NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, plr->x, plr->y, plr->z, plr->instanceID, npc->appearanceData.iAngle); | ||||
|     npc->angle = (plr->angle + 180) % 360; | ||||
|     NPCManager::updateNPCPosition(npc->id, plr->x, plr->y, plr->z, plr->instanceID, npc->angle); | ||||
|  | ||||
|     // 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) { | ||||
|         npc = NPCManager::summonNPC(plr->x, plr->y, plr->z, plr->instanceID, type, true, true); | ||||
|  | ||||
|         npc->appearanceData.iAngle = (plr->angle + 180) % 360; | ||||
|         NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, plr->x, plr->y, plr->z, npc->instanceID, npc->appearanceData.iAngle); | ||||
|         npc->angle = (plr->angle + 180) % 360; | ||||
|         NPCManager::updateNPCPosition(npc->id, plr->x, plr->y, plr->z, npc->instanceID, npc->angle); | ||||
|     } | ||||
|  | ||||
|     Chat::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 | ||||
|         ", id: " + std::to_string(npc->id)); | ||||
|     TableData::RunningMobs[npc->id] = npc; // only record the one in the template | ||||
| } | ||||
|  | ||||
| static void unsummonWCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) { | ||||
| @@ -269,24 +270,24 @@ static void unsummonWCommand(std::string full, std::vector<std::string>& args, C | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (TableData::RunningEggs.find(npc->appearanceData.iNPC_ID) != TableData::RunningEggs.end()) { | ||||
|         Chat::sendServerMessage(sock, "/unsummonW: removed egg with type: " + std::to_string(npc->appearanceData.iNPCType) + | ||||
|             ", id: " + std::to_string(npc->appearanceData.iNPC_ID)); | ||||
|         TableData::RunningEggs.erase(npc->appearanceData.iNPC_ID); | ||||
|         NPCManager::destroyNPC(npc->appearanceData.iNPC_ID); | ||||
|     if (TableData::RunningEggs.find(npc->id) != TableData::RunningEggs.end()) { | ||||
|         Chat::sendServerMessage(sock, "/unsummonW: removed egg with type: " + std::to_string(npc->type) + | ||||
|             ", id: " + std::to_string(npc->id)); | ||||
|         TableData::RunningEggs.erase(npc->id); | ||||
|         NPCManager::destroyNPC(npc->id); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (TableData::RunningMobs.find(npc->appearanceData.iNPC_ID) == TableData::RunningMobs.end() | ||||
|         && TableData::RunningGroups.find(npc->appearanceData.iNPC_ID) == TableData::RunningGroups.end()) { | ||||
|     if (TableData::RunningMobs.find(npc->id) == TableData::RunningMobs.end() | ||||
|         && TableData::RunningGroups.find(npc->id) == TableData::RunningGroups.end()) { | ||||
|         Chat::sendServerMessage(sock, "/unsummonW: Closest NPC is not a gruntwork mob."); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (NPCManager::NPCs.find(npc->appearanceData.iNPC_ID) != NPCManager::NPCs.end() && NPCManager::NPCs[npc->appearanceData.iNPC_ID]->type == EntityType::MOB) { | ||||
|     if (NPCManager::NPCs.find(npc->id) != NPCManager::NPCs.end() && NPCManager::NPCs[npc->id]->kind == EntityKind::MOB) { | ||||
|         int leadId = ((Mob*)npc)->groupLeader; | ||||
|         if (leadId != 0) { | ||||
|             if (NPCManager::NPCs.find(leadId) == NPCManager::NPCs.end() || NPCManager::NPCs[leadId]->type != EntityType::MOB) { | ||||
|             if (NPCManager::NPCs.find(leadId) == NPCManager::NPCs.end() || NPCManager::NPCs[leadId]->kind != EntityKind::MOB) { | ||||
|                 std::cout << "[WARN] unsummonW: leader not found!" << std::endl; | ||||
|             } | ||||
|             Mob* leadNpc = (Mob*)NPCManager::NPCs[leadId]; | ||||
| @@ -294,7 +295,7 @@ static void unsummonWCommand(std::string full, std::vector<std::string>& args, C | ||||
|                 if (leadNpc->groupMember[i] == 0) | ||||
|                     break; | ||||
|  | ||||
|                 if (NPCManager::NPCs.find(leadNpc->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadNpc->groupMember[i]]->type != EntityType::MOB) { | ||||
|                 if (NPCManager::NPCs.find(leadNpc->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadNpc->groupMember[i]]->kind != EntityKind::MOB) { | ||||
|                     std::cout << "[WARN] unsommonW: leader can't find a group member!" << std::endl; | ||||
|                     continue; | ||||
|                 } | ||||
| @@ -308,12 +309,12 @@ static void unsummonWCommand(std::string full, std::vector<std::string>& args, C | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     Chat::sendServerMessage(sock, "/unsummonW: removed mob with type: " + std::to_string(npc->appearanceData.iNPCType) + | ||||
|         ", id: " + std::to_string(npc->appearanceData.iNPC_ID)); | ||||
|     Chat::sendServerMessage(sock, "/unsummonW: removed mob with type: " + std::to_string(npc->type) + | ||||
|         ", id: " + std::to_string(npc->id)); | ||||
|  | ||||
|     TableData::RunningMobs.erase(npc->appearanceData.iNPC_ID); | ||||
|     TableData::RunningMobs.erase(npc->id); | ||||
|  | ||||
|     NPCManager::destroyNPC(npc->appearanceData.iNPC_ID); | ||||
|     NPCManager::destroyNPC(npc->id); | ||||
| } | ||||
|  | ||||
| static void toggleAiCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) { | ||||
| @@ -324,11 +325,11 @@ static void toggleAiCommand(std::string full, std::vector<std::string>& args, CN | ||||
|  | ||||
|     // return all mobs to their spawn points | ||||
|     for (auto& pair : NPCManager::NPCs) { | ||||
|         if (pair.second->type != EntityType::MOB) | ||||
|         if (pair.second->kind != EntityKind::MOB) | ||||
|             continue; | ||||
|  | ||||
|         Mob* mob = (Mob*)pair.second; | ||||
|         mob->state = MobState::RETREAT; | ||||
|         mob->state = AIState::RETREAT; | ||||
|         mob->target = nullptr; | ||||
|         mob->nextMovement = getTime(); | ||||
|  | ||||
| @@ -356,25 +357,21 @@ static void npcRotateCommand(std::string full, std::vector<std::string>& args, C | ||||
|     } | ||||
|  | ||||
|     int angle = (plr->angle + 180) % 360; | ||||
|     NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->x, npc->y, npc->z, npc->instanceID, angle); | ||||
|     NPCManager::updateNPCPosition(npc->id, npc->x, npc->y, npc->z, npc->instanceID, 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->x, npc->y, npc->z, npc->instanceID, angle); | ||||
|     bool isGruntworkNpc = true; | ||||
|  | ||||
|         Chat::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; | ||||
|  | ||||
|         Chat::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) + " for NPC " | ||||
|             + std::to_string(npc->appearanceData.iNPC_ID)); | ||||
|     // add a rotation entry to the gruntwork file, unless it's already a gruntwork NPC | ||||
|     if (TableData::RunningMobs.find(npc->id) == TableData::RunningMobs.end()) { | ||||
|         TableData::RunningNPCRotations[npc->id] = angle; | ||||
|         isGruntworkNpc = false; | ||||
|     } | ||||
|  | ||||
|     // update rotation clientside | ||||
|     INITSTRUCT(sP_FE2CL_NPC_ENTER, pkt); | ||||
|     pkt.NPCAppearanceData = npc->appearanceData; | ||||
|     sock->sendPacket(pkt, P_FE2CL_NPC_ENTER); | ||||
|     Chat::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) + | ||||
|         " for " + (isGruntworkNpc ? "gruntwork " : "") + "NPC " + std::to_string(npc->id)); | ||||
|  | ||||
|     // update rotation clientside by refreshing the player's chunks (same as the /refresh command) | ||||
|     PlayerManager::updatePlayerPositionForWarp(sock, plr->x, plr->y, plr->z, plr->instanceID); | ||||
| } | ||||
|  | ||||
| static void refreshCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) { | ||||
| @@ -443,9 +440,9 @@ static void npcInstanceCommand(std::string full, std::vector<std::string>& args, | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     Chat::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::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->x, npc->y, npc->z, instance, npc->appearanceData.iAngle); | ||||
|     Chat::sendServerMessage(sock, "[NPCI] Moving NPC with ID " + std::to_string(npc->id) + " to instance " + std::to_string(instance)); | ||||
|     TableData::RunningNPCMapNumbers[npc->id] = instance; | ||||
|     NPCManager::updateNPCPosition(npc->id, npc->x, npc->y, npc->z, instance, npc->angle); | ||||
| } | ||||
|  | ||||
| static void minfoCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) { | ||||
| @@ -501,9 +498,12 @@ static void buffCommand(std::string full, std::vector<std::string>& args, CNSock | ||||
|     if (*tmp) | ||||
|         return; | ||||
|  | ||||
|     if (Eggs::eggBuffPlayer(sock, skillId, 0, duration)<0) | ||||
|     if (Abilities::SkillTable.count(skillId) == 0) { | ||||
|         Chat::sendServerMessage(sock, "/buff: unknown skill Id"); | ||||
|      | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     Eggs::eggBuffPlayer(sock, skillId, 0, duration); | ||||
| } | ||||
|  | ||||
| static void eggCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) { | ||||
| @@ -532,7 +532,7 @@ static void eggCommand(std::string full, std::vector<std::string>& args, CNSocke | ||||
|     int addX = 0; //-500.0f * sin(plr->angle / 180.0f * M_PI); | ||||
|     int addY = 0;  //-500.0f * cos(plr->angle / 180.0f * M_PI); | ||||
|  | ||||
|     Egg* egg = new Egg(plr->x + addX, plr->y + addY, plr->z, plr->instanceID, eggType, id, false); // change last arg to true after gruntwork | ||||
|     Egg* egg = new Egg(plr->instanceID, eggType, id, false); // change last arg to true after gruntwork | ||||
|     NPCManager::NPCs[id] = egg; | ||||
|     NPCManager::updateNPCPosition(id, plr->x + addX, plr->y + addY, plr->z, plr->instanceID, plr->angle); | ||||
|  | ||||
| @@ -609,40 +609,40 @@ static void summonGroupCommand(std::string full, std::vector<std::string>& args, | ||||
|         } | ||||
|  | ||||
|         BaseNPC *npc = NPCManager::summonNPC(x, y, z, plr->instanceID, type, wCommand); | ||||
|         if (team == 2 && i > 0 && npc->type == EntityType::MOB) { | ||||
|             leadNpc->groupMember[i-1] = npc->appearanceData.iNPC_ID; | ||||
|             Mob* mob = (Mob*)NPCManager::NPCs[npc->appearanceData.iNPC_ID]; | ||||
|             mob->groupLeader = leadNpc->appearanceData.iNPC_ID; | ||||
|         if (team == 2 && i > 0 && npc->kind == EntityKind::MOB) { | ||||
|             leadNpc->groupMember[i-1] = npc->id; | ||||
|             Mob* mob = (Mob*)NPCManager::NPCs[npc->id]; | ||||
|             mob->groupLeader = leadNpc->id; | ||||
|             mob->offsetX = x - plr->x; | ||||
|             mob->offsetY = y - plr->y; | ||||
|         } | ||||
|  | ||||
|         npc->appearanceData.iAngle = (plr->angle + 180) % 360; | ||||
|         NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, x, y, z, plr->instanceID, npc->appearanceData.iAngle); | ||||
|         npc->angle = (plr->angle + 180) % 360; | ||||
|         NPCManager::updateNPCPosition(npc->id, x, y, z, plr->instanceID, npc->angle); | ||||
|  | ||||
|         // 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) { | ||||
|             npc = NPCManager::summonNPC(plr->x, plr->y, plr->z, plr->instanceID, type, wCommand, true); | ||||
|  | ||||
|             if (team == 2 && i > 0 && npc->type == EntityType::MOB) { | ||||
|                 leadNpc->groupMember[i-1] = npc->appearanceData.iNPC_ID; | ||||
|                 Mob* mob = (Mob*)NPCManager::NPCs[npc->appearanceData.iNPC_ID]; | ||||
|                 mob->groupLeader = leadNpc->appearanceData.iNPC_ID; | ||||
|             if (team == 2 && i > 0 && npc->kind == EntityKind::MOB) { | ||||
|                 leadNpc->groupMember[i-1] = npc->id; | ||||
|                 Mob* mob = (Mob*)NPCManager::NPCs[npc->id]; | ||||
|                 mob->groupLeader = leadNpc->id; | ||||
|                 mob->offsetX = x - plr->x; | ||||
|                 mob->offsetY = y - plr->y; | ||||
|             } | ||||
|  | ||||
|             npc->appearanceData.iAngle = (plr->angle + 180) % 360; | ||||
|             NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, x, y, z, plr->instanceID, npc->appearanceData.iAngle); | ||||
|             npc->angle = (plr->angle + 180) % 360; | ||||
|             NPCManager::updateNPCPosition(npc->id, x, y, z, plr->instanceID, npc->angle); | ||||
|         } | ||||
|  | ||||
|         Chat::sendServerMessage(sock, "/summonGroup(W): placed mob with type: " + std::to_string(type) + | ||||
|             ", id: " + std::to_string(npc->appearanceData.iNPC_ID)); | ||||
|             ", id: " + std::to_string(npc->id)); | ||||
|  | ||||
|         if (i == 0 && team == 2 && npc->type == EntityType::MOB) { | ||||
|         if (i == 0 && team == 2 && npc->kind == EntityKind::MOB) { | ||||
|             type = type2; | ||||
|             leadNpc = (Mob*)NPCManager::NPCs[npc->appearanceData.iNPC_ID]; | ||||
|             leadNpc->groupLeader = leadNpc->appearanceData.iNPC_ID; | ||||
|             leadNpc = (Mob*)NPCManager::NPCs[npc->id]; | ||||
|             leadNpc->groupLeader = leadNpc->id; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -654,7 +654,7 @@ static void summonGroupCommand(std::string full, std::vector<std::string>& args, | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     TableData::RunningGroups[leadNpc->appearanceData.iNPC_ID] = leadNpc; // only record the leader | ||||
|     TableData::RunningGroups[leadNpc->id] = leadNpc; // only record the leader | ||||
| } | ||||
|  | ||||
| static void flushCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) { | ||||
| @@ -671,15 +671,14 @@ static void whoisCommand(std::string full, std::vector<std::string>& args, CNSoc | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     Chat::sendServerMessage(sock, "[WHOIS] ID: " + std::to_string(npc->appearanceData.iNPC_ID)); | ||||
|     Chat::sendServerMessage(sock, "[WHOIS] Type: " + std::to_string(npc->appearanceData.iNPCType)); | ||||
|     Chat::sendServerMessage(sock, "[WHOIS] HP: " + std::to_string(npc->appearanceData.iHP)); | ||||
|     Chat::sendServerMessage(sock, "[WHOIS] CBF: " + std::to_string(npc->appearanceData.iConditionBitFlag)); | ||||
|     Chat::sendServerMessage(sock, "[WHOIS] EntityType: " + std::to_string((int)npc->type)); | ||||
|     Chat::sendServerMessage(sock, "[WHOIS] ID: " + std::to_string(npc->id)); | ||||
|     Chat::sendServerMessage(sock, "[WHOIS] Type: " + std::to_string(npc->type)); | ||||
|     Chat::sendServerMessage(sock, "[WHOIS] HP: " + std::to_string(npc->hp)); | ||||
|     Chat::sendServerMessage(sock, "[WHOIS] EntityType: " + std::to_string((int)npc->kind)); | ||||
|     Chat::sendServerMessage(sock, "[WHOIS] X: " + std::to_string(npc->x)); | ||||
|     Chat::sendServerMessage(sock, "[WHOIS] Y: " + std::to_string(npc->y)); | ||||
|     Chat::sendServerMessage(sock, "[WHOIS] Z: " + std::to_string(npc->z)); | ||||
|     Chat::sendServerMessage(sock, "[WHOIS] Angle: " + std::to_string(npc->appearanceData.iAngle)); | ||||
|     Chat::sendServerMessage(sock, "[WHOIS] Angle: " + std::to_string(npc->angle)); | ||||
|     std::string chunkPosition = std::to_string(std::get<0>(npc->chunkPos)) + ", " + std::to_string(std::get<1>(npc->chunkPos)) + ", " + std::to_string(std::get<2>(npc->chunkPos)); | ||||
|     Chat::sendServerMessage(sock, "[WHOIS] Chunk: {" + chunkPosition + "}"); | ||||
|     Chat::sendServerMessage(sock, "[WHOIS] MapNum: " + std::to_string(MAPNUM(npc->instanceID))); | ||||
| @@ -694,7 +693,7 @@ static void lairUnlockCommand(std::string full, std::vector<std::string>& args, | ||||
|     int lastDist = INT_MAX; | ||||
|     for (Chunk *chnk : Chunking::getViewableChunks(plr->chunkPos)) { | ||||
|         for (const EntityRef& ref : chnk->entities) { | ||||
|             if (ref.type == EntityType::PLAYER) | ||||
|             if (ref.kind == EntityKind::PLAYER) | ||||
|                 continue; | ||||
|  | ||||
|             BaseNPC* npc = (BaseNPC*)ref.getEntity(); | ||||
| @@ -705,7 +704,7 @@ static void lairUnlockCommand(std::string full, std::vector<std::string>& args, | ||||
|                 continue; | ||||
|  | ||||
|             for (auto it = NPCManager::Warps.begin(); it != NPCManager::Warps.end(); it++) { | ||||
|                 if (it->second.npcID == npc->appearanceData.iNPCType) { | ||||
|                 if (it->second.npcID == npc->type) { | ||||
|                     taskID = it->second.limitTaskID; | ||||
|                     missionID = Missions::Tasks[taskID]->task["m_iHMissionID"]; | ||||
|                     lastDist = dist; | ||||
| @@ -977,11 +976,17 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock | ||||
|  | ||||
|         // add first point at NPC's current location | ||||
|         std::vector<BaseNPC*> pathPoints; | ||||
|         BaseNPC* marker = new BaseNPC(npc->x, npc->y, npc->z, 0, plr->instanceID, 1386, NPCManager::nextId--); | ||||
|         BaseNPC* marker = new BaseNPC(0, plr->instanceID, 1386, NPCManager::nextId--); | ||||
|  | ||||
|         // assign coords manually, since we aren't actually adding markers to the world | ||||
|         marker->x = npc->x; | ||||
|         marker->y = npc->y; | ||||
|         marker->z = npc->z; | ||||
|  | ||||
|         pathPoints.push_back(marker); | ||||
|         // map from player | ||||
|         TableData::RunningNPCPaths[plr->iID] = std::make_pair(npc, pathPoints); | ||||
|         Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->appearanceData.iNPC_ID) + " is now following you"); | ||||
|         Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->id) + " is now following you"); | ||||
|         updatePathMarkers(sock); | ||||
|         return; | ||||
|     } | ||||
| @@ -998,7 +1003,12 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock | ||||
|  | ||||
|     // /path kf | ||||
|     if (args[1] == "kf") { | ||||
|         BaseNPC* marker = new BaseNPC(npc->x, npc->y, npc->z, 0, plr->instanceID, 1386, NPCManager::nextId--); | ||||
|         BaseNPC* marker = new BaseNPC(0, plr->instanceID, 1386, NPCManager::nextId--); | ||||
|  | ||||
|         marker->x = npc->x; | ||||
|         marker->y = npc->y; | ||||
|         marker->z = npc->z; | ||||
|  | ||||
|         entry->second.push_back(marker); | ||||
|         Chat::sendServerMessage(sock, "[PATH] Added keyframe"); | ||||
|         updatePathMarkers(sock); | ||||
| @@ -1008,8 +1018,8 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock | ||||
|     // /path here | ||||
|     if (args[1] == "here") { | ||||
|         // bring the NPC to where the player is standing | ||||
|         Transport::NPCQueues.erase(npc->appearanceData.iNPC_ID); // delete transport queue | ||||
|         NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, plr->x, plr->y, plr->z, npc->instanceID, 0); | ||||
|         Transport::NPCQueues.erase(npc->id); // delete transport queue | ||||
|         NPCManager::updateNPCPosition(npc->id, plr->x, plr->y, plr->z, npc->instanceID, 0); | ||||
|         npc->disappearFromViewOf(sock); | ||||
|         npc->enterIntoViewOf(sock); | ||||
|         Chat::sendServerMessage(sock, "[PATH] Come here"); | ||||
| @@ -1047,9 +1057,9 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock | ||||
|             speed = speedArg; | ||||
|         } | ||||
|         // return NPC to home | ||||
|         Transport::NPCQueues.erase(npc->appearanceData.iNPC_ID); // delete transport queue | ||||
|         Transport::NPCQueues.erase(npc->id); // delete transport queue | ||||
|         BaseNPC* home = entry->second[0]; | ||||
|         NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, home->x, home->y, home->z, npc->instanceID, 0); | ||||
|         NPCManager::updateNPCPosition(npc->id, home->x, home->y, home->z, npc->instanceID, 0); | ||||
|         npc->disappearFromViewOf(sock); | ||||
|         npc->enterIntoViewOf(sock); | ||||
|  | ||||
| @@ -1065,7 +1075,7 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock | ||||
|             Transport::lerp(&keyframes, from, to, speed); // lerp from A to B | ||||
|             from = to; // update point A | ||||
|         } | ||||
|         Transport::NPCQueues[npc->appearanceData.iNPC_ID] = keyframes; | ||||
|         Transport::NPCQueues[npc->id] = keyframes; | ||||
|         entry->second.pop_back(); // remove temp end point | ||||
|  | ||||
|         Chat::sendServerMessage(sock, "[PATH] Testing NPC path"); | ||||
| @@ -1075,9 +1085,9 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock | ||||
|     // /path cancel | ||||
|     if (args[1] == "cancel") { | ||||
|         // return NPC to home | ||||
|         Transport::NPCQueues.erase(npc->appearanceData.iNPC_ID); // delete transport queue | ||||
|         Transport::NPCQueues.erase(npc->id); // delete transport queue | ||||
|         BaseNPC* home = entry->second[0]; | ||||
|         NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, home->x, home->y, home->z, npc->instanceID, 0); | ||||
|         NPCManager::updateNPCPosition(npc->id, home->x, home->y, home->z, npc->instanceID, 0); | ||||
|         npc->disappearFromViewOf(sock); | ||||
|         npc->enterIntoViewOf(sock); | ||||
|         // deallocate markers | ||||
| @@ -1087,7 +1097,7 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock | ||||
|         } | ||||
|         // unmap | ||||
|         TableData::RunningNPCPaths.erase(plr->iID); | ||||
|         Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->appearanceData.iNPC_ID) + " is no longer following you"); | ||||
|         Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->id) + " is no longer following you"); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
| @@ -1115,9 +1125,9 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock | ||||
|         } | ||||
|  | ||||
|         // return NPC to home and set path to repeat | ||||
|         Transport::NPCQueues.erase(npc->appearanceData.iNPC_ID); // delete transport queue | ||||
|         Transport::NPCQueues.erase(npc->id); // delete transport queue | ||||
|         BaseNPC* home = entry->second[0]; | ||||
|         NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, home->x, home->y, home->z, npc->instanceID, 0); | ||||
|         NPCManager::updateNPCPosition(npc->id, home->x, home->y, home->z, npc->instanceID, 0); | ||||
|         npc->disappearFromViewOf(sock); | ||||
|         npc->enterIntoViewOf(sock); | ||||
|         npc->loopingPath = true; | ||||
| @@ -1134,7 +1144,7 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock | ||||
|             Transport::lerp(&keyframes, from, to, speed); // lerp from A to B | ||||
|             from = to; // update point A | ||||
|         } | ||||
|         Transport::NPCQueues[npc->appearanceData.iNPC_ID] = keyframes; | ||||
|         Transport::NPCQueues[npc->id] = keyframes; | ||||
|         entry->second.pop_back(); // remove temp end point | ||||
|  | ||||
|         // save to gruntwork | ||||
| @@ -1161,7 +1171,7 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock | ||||
|         finishedPath.isLoop = true; | ||||
|         finishedPath.speed = speed; | ||||
|         finishedPath.points = finalPoints; | ||||
|         finishedPath.targetIDs.push_back(npc->appearanceData.iNPC_ID); | ||||
|         finishedPath.targetIDs.push_back(npc->id); | ||||
|  | ||||
|         TableData::FinishedNPCPaths.push_back(finishedPath); | ||||
|  | ||||
| @@ -1173,7 +1183,7 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock | ||||
|         // unmap | ||||
|         TableData::RunningNPCPaths.erase(plr->iID); | ||||
|  | ||||
|         Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->appearanceData.iNPC_ID) + " is no longer following you"); | ||||
|         Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->id) + " is no longer following you"); | ||||
|  | ||||
|         TableData::flush(); | ||||
|         Chat::sendServerMessage(sock, "[PATH] Path saved to gruntwork"); | ||||
|   | ||||
| @@ -2,6 +2,8 @@ | ||||
|  | ||||
| #include "core/Core.hpp" | ||||
|  | ||||
| #include <string> | ||||
|  | ||||
| namespace CustomCommands { | ||||
|     void init(); | ||||
|  | ||||
|   | ||||
							
								
								
									
										212
									
								
								src/Eggs.cpp
									
									
									
									
									
								
							
							
						
						
									
										212
									
								
								src/Eggs.cpp
									
									
									
									
									
								
							| @@ -1,141 +1,124 @@ | ||||
| #include "core/Core.hpp" | ||||
| #include "Eggs.hpp" | ||||
|  | ||||
| #include "servers/CNShardServer.hpp" | ||||
|  | ||||
| #include "Player.hpp" | ||||
| #include "PlayerManager.hpp" | ||||
| #include "Items.hpp" | ||||
| #include "Nanos.hpp" | ||||
| #include "Abilities.hpp" | ||||
| #include "Groups.hpp" | ||||
| #include "NPCManager.hpp" | ||||
| #include "Entities.hpp" | ||||
| #include "Items.hpp" | ||||
|  | ||||
| #include <assert.h> | ||||
|  | ||||
| using namespace Eggs; | ||||
|  | ||||
| /// sock, CBFlag -> until | ||||
| std::map<std::pair<CNSocket*, int32_t>, time_t> Eggs::EggBuffs; | ||||
| std::unordered_map<int, EggType> Eggs::EggTypes; | ||||
|  | ||||
| int Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) { | ||||
| void Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) { | ||||
|     Player* plr = PlayerManager::getPlayer(sock); | ||||
|     Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup); | ||||
|  | ||||
|     int bitFlag = Groups::getGroupFlags(otherPlr); | ||||
|     int CBFlag = Nanos::applyBuff(sock, skillId, 1, 3, bitFlag); | ||||
|     // eggId might be 0 if the buff is made by the /buff command | ||||
|     EntityRef src = eggId == 0 ? sock : EntityRef(eggId); | ||||
|      | ||||
|     if(Abilities::SkillTable.count(skillId) == 0) { | ||||
|         std::cout << "[WARN] egg " << eggId << " has skill ID " << skillId << " which doesn't exist" << std::endl; | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     size_t resplen;  | ||||
|     SkillResult result = SkillResult(); | ||||
|     SkillData* skill = &Abilities::SkillTable[skillId]; | ||||
|     if(skill->drainType == SkillDrainType::PASSIVE) { | ||||
|         // apply buff | ||||
|         if(skill->targetType != SkillTargetType::PLAYERS) { | ||||
|             std::cout << "[WARN] weird skill type for egg " << eggId << " with skill " << skillId << ", should be " << (int)skill->targetType << std::endl; | ||||
|         } | ||||
|  | ||||
|     if (skillId == 183) { | ||||
|         resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + sizeof(sSkillResult_Damage); | ||||
|     } else if (skillId == 150) { | ||||
|         resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + sizeof(sSkillResult_Heal_HP); | ||||
|         int timeBuffId = Abilities::getCSTBFromST(skill->skillType); | ||||
|         int value = skill->values[0][0]; | ||||
|         BuffStack eggBuff = { | ||||
|             duration * 1000 / MS_PER_PLAYER_TICK, | ||||
|             value, | ||||
|             src, | ||||
|             BuffClass::EGG | ||||
|         }; | ||||
|         plr->addBuff(timeBuffId, | ||||
|             [](EntityRef self, Buff* buff, int status, BuffStack* stack) { | ||||
|                 Buffs::timeBuffUpdate(self, buff, status, stack); | ||||
|                 if(status == ETBU_DEL) Buffs::timeBuffTimeout(self); | ||||
|             }, | ||||
|             [](EntityRef self, Buff* buff, time_t currTime) { | ||||
|                 // no-op | ||||
|             }, | ||||
|             &eggBuff); | ||||
|  | ||||
|         sSkillResult_Buff resultBuff{}; | ||||
|         resultBuff.eCT = plr->getCharType(); | ||||
|         resultBuff.iID = plr->getID(); | ||||
|         resultBuff.bProtected = false; | ||||
|         resultBuff.iConditionBitFlag = plr->getCompositeCondition(); | ||||
|         result = SkillResult(sizeof(sSkillResult_Buff), &resultBuff); | ||||
|     } else { | ||||
|         resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + sizeof(sSkillResult_Buff); | ||||
|     } | ||||
|     assert(resplen < CN_PACKET_BUFFER_SIZE - 8); | ||||
|     // we know it's only one trailing struct, so we can skip full validation | ||||
|  | ||||
|     uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; | ||||
|     auto skillUse = (sP_FE2CL_NPC_SKILL_HIT*)respbuf; | ||||
|  | ||||
|     if (skillId == 183) { // damage egg | ||||
|         auto skill = (sSkillResult_Damage*)(respbuf + sizeof(sP_FE2CL_NPC_SKILL_HIT)); | ||||
|         memset(respbuf, 0, resplen); | ||||
|         skill->eCT = 1; | ||||
|         skill->iID = plr->iID; | ||||
|         skill->iDamage = PC_MAXHEALTH(plr->level) * Nanos::SkillTable[skillId].powerIntensity[0] / 1000; | ||||
|         plr->HP -= skill->iDamage; | ||||
|         if (plr->HP < 0) | ||||
|             plr->HP = 0; | ||||
|         skill->iHP = plr->HP; | ||||
|     } else if (skillId == 150) { // heal egg | ||||
|         auto skill = (sSkillResult_Heal_HP*)(respbuf + sizeof(sP_FE2CL_NPC_SKILL_HIT)); | ||||
|         memset(respbuf, 0, resplen); | ||||
|         skill->eCT = 1; | ||||
|         skill->iID = plr->iID; | ||||
|         skill->iHealHP = PC_MAXHEALTH(plr->level) * Nanos::SkillTable[skillId].powerIntensity[0] / 1000; | ||||
|         plr->HP += skill->iHealHP; | ||||
|         if (plr->HP > PC_MAXHEALTH(plr->level)) | ||||
|             plr->HP = PC_MAXHEALTH(plr->level); | ||||
|         skill->iHP = plr->HP; | ||||
|     } else { // regular buff egg | ||||
|         auto skill = (sSkillResult_Buff*)(respbuf + sizeof(sP_FE2CL_NPC_SKILL_HIT)); | ||||
|         memset(respbuf, 0, resplen); | ||||
|         skill->eCT = 1; | ||||
|         skill->iID = plr->iID; | ||||
|         skill->iConditionBitFlag = plr->iConditionBitFlag; | ||||
|     } | ||||
|  | ||||
|     skillUse->iNPC_ID = eggId; | ||||
|     skillUse->iSkillID = skillId; | ||||
|     skillUse->eST = Nanos::SkillTable[skillId].skillType; | ||||
|     skillUse->iTargetCnt = 1; | ||||
|  | ||||
|     sock->sendPacket((void*)&respbuf, P_FE2CL_NPC_SKILL_HIT, resplen); | ||||
|     PlayerManager::sendToViewable(sock, (void*)&respbuf, P_FE2CL_NPC_SKILL_HIT, resplen); | ||||
|  | ||||
|     if (CBFlag == 0) | ||||
|         return -1; | ||||
|  | ||||
|     std::pair<CNSocket*, int32_t> key = std::make_pair(sock, CBFlag); | ||||
|  | ||||
|     // save the buff serverside; | ||||
|     // if you get the same buff again, new duration will override the previous one | ||||
|     time_t until = getTime() + (time_t)duration * 1000; | ||||
|     EggBuffs[key] = until; | ||||
|  | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| static void eggStep(CNServer* serv, time_t currTime) { | ||||
|     // tick buffs | ||||
|     time_t timeStamp = currTime; | ||||
|     auto it = EggBuffs.begin(); | ||||
|     while (it != EggBuffs.end()) { | ||||
|         // check remaining time | ||||
|         if (it->second > timeStamp) { | ||||
|             it++; | ||||
|         } else { // if time reached 0 | ||||
|             CNSocket* sock = it->first.first; | ||||
|             int32_t CBFlag = it->first.second; | ||||
|             Player* plr = PlayerManager::getPlayer(sock); | ||||
|             Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup); | ||||
|  | ||||
|             int groupFlags = Groups::getGroupFlags(otherPlr); | ||||
|             for (auto& pwr : Nanos::NanoPowers) { | ||||
|                 if (pwr.bitFlag == CBFlag) { // pick the power with the right flag and unbuff | ||||
|                     INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, resp); | ||||
|                     resp.eCSTB = pwr.timeBuffID; | ||||
|                     resp.eTBU = 2; | ||||
|                     resp.eTBT = 3; // for egg buffs | ||||
|                     plr->iConditionBitFlag &= ~CBFlag; | ||||
|                     resp.iConditionBitFlag = plr->iConditionBitFlag |= groupFlags | plr->iSelfConditionBitFlag; | ||||
|                     sock->sendPacket(resp, P_FE2CL_PC_BUFF_UPDATE); | ||||
|  | ||||
|                     INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, resp2); // send a buff timeout to other players | ||||
|                     resp2.eCT = 1; | ||||
|                     resp2.iID = plr->iID; | ||||
|                     resp2.iConditionBitFlag = plr->iConditionBitFlag; | ||||
|                     PlayerManager::sendToViewable(sock, resp2, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT); | ||||
|                 } | ||||
|             } | ||||
|             // remove buff from the map | ||||
|             it = EggBuffs.erase(it); | ||||
|         int value = plr->getMaxHP() * skill->values[0][0] / 1000; | ||||
|         sSkillResult_Damage resultDamage{}; | ||||
|         sSkillResult_Heal_HP resultHeal{}; | ||||
|         switch(skill->skillType) | ||||
|         { | ||||
|             case SkillType::DAMAGE: | ||||
|                 resultDamage.bProtected = false; | ||||
|                 resultDamage.eCT = plr->getCharType(); | ||||
|                 resultDamage.iID = plr->getID(); | ||||
|                 resultDamage.iDamage = plr->takeDamage(src, value); | ||||
|                 resultDamage.iHP = plr->getCurrentHP(); | ||||
|                 result = SkillResult(sizeof(sSkillResult_Damage), &resultDamage); | ||||
|                 break; | ||||
|             case SkillType::HEAL_HP: | ||||
|                 resultHeal.eCT = plr->getCharType(); | ||||
|                 resultHeal.iID = plr->getID(); | ||||
|                 resultHeal.iHealHP = plr->heal(src, value); | ||||
|                 resultHeal.iHP = plr->getCurrentHP(); | ||||
|                 result = SkillResult(sizeof(sSkillResult_Heal_HP), &resultHeal); | ||||
|                 break; | ||||
|             default: | ||||
|                 std::cout << "[WARN] oops, egg with active skill type " << (int)skill->skillType << " unhandled"; | ||||
|                 return; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // initialize response struct | ||||
|     size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + result.size; | ||||
|     uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; | ||||
|     memset(respbuf, 0, resplen); | ||||
|  | ||||
|     sP_FE2CL_NPC_SKILL_HIT* pkt = (sP_FE2CL_NPC_SKILL_HIT*)respbuf; | ||||
|     pkt->iNPC_ID = eggId; | ||||
|     pkt->iSkillID = skillId; | ||||
|     pkt->eST = (int32_t)skill->skillType; | ||||
|     pkt->iTargetCnt = 1; | ||||
|  | ||||
|     if(result.size > 0) { | ||||
|         void* attached = (void*)(pkt + 1); | ||||
|         memcpy(attached, result.payload, result.size); | ||||
|     } | ||||
|      | ||||
|     NPCManager::sendToViewable(src.getEntity(), pkt, P_FE2CL_NPC_SKILL_HIT, resplen); | ||||
| } | ||||
|  | ||||
| static void eggStep(CNServer* serv, time_t currTime) { | ||||
|     // check dead eggs and eggs in inactive chunks | ||||
|     for (auto npc : NPCManager::NPCs) { | ||||
|         if (npc.second->type != EntityType::EGG) | ||||
|         if (npc.second->kind != EntityKind::EGG) | ||||
|             continue; | ||||
|  | ||||
|         auto egg = (Egg*)npc.second; | ||||
|         if (!egg->dead || !Chunking::inPopulatedChunks(&egg->viewableChunks)) | ||||
|             continue; | ||||
|  | ||||
|         if (egg->deadUntil <= timeStamp) { | ||||
|         if (egg->deadUntil <= currTime) { | ||||
|             // respawn it | ||||
|             egg->dead = false; | ||||
|             egg->deadUntil = 0; | ||||
|             egg->appearanceData.iHP = 400; | ||||
|             egg->hp = 400; | ||||
|              | ||||
|             Chunking::addEntityToChunks(Chunking::getViewableChunks(egg->chunkPos), {npc.first}); | ||||
|         } | ||||
| @@ -163,7 +146,7 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) { | ||||
|         return; | ||||
|     } | ||||
|     auto egg = (Egg*)eggRef.getEntity(); | ||||
|     if (egg->type != EntityType::EGG) { | ||||
|     if (egg->kind != EntityKind::EGG) { | ||||
|         std::cout << "[WARN] Player tried to open something other than an?!" << std::endl; | ||||
|         return; | ||||
|     } | ||||
| @@ -180,7 +163,7 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) { | ||||
|     } | ||||
|     */ | ||||
|  | ||||
|     int typeId = egg->appearanceData.iNPCType; | ||||
|     int typeId = egg->type; | ||||
|     if (EggTypes.find(typeId) == EggTypes.end()) { | ||||
|         std::cout << "[WARN] Egg Type " << typeId << " not found!" << std::endl; | ||||
|         return; | ||||
| @@ -188,16 +171,13 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) { | ||||
|  | ||||
|     EggType* type = &EggTypes[typeId]; | ||||
|  | ||||
|     // buff the player | ||||
|     if (type->effectId != 0) | ||||
|         eggBuffPlayer(sock, type->effectId, eggRef.id, type->duration); | ||||
|  | ||||
|     /* | ||||
|      * SHINY_PICKUP_SUCC is only causing a GUI effect in the client | ||||
|      * (buff icon pops up in the bottom of the screen) | ||||
|      * so we don't send it for non-effect | ||||
|      */ | ||||
|     if (type->effectId != 0) { | ||||
|         eggBuffPlayer(sock, type->effectId, eggRef.id, type->duration); | ||||
|         INITSTRUCT(sP_FE2CL_REP_SHINY_PICKUP_SUCC, resp); | ||||
|         resp.iSkillID = type->effectId; | ||||
|  | ||||
| @@ -255,7 +235,7 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) { | ||||
|         Chunking::removeEntityFromChunks(Chunking::getViewableChunks(egg->chunkPos), eggRef); | ||||
|         egg->dead = true; | ||||
|         egg->deadUntil = getTime() + (time_t)type->regen * 1000; | ||||
|         egg->appearanceData.iHP = 0; | ||||
|         egg->hp = 0; | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "core/Core.hpp" | ||||
| #include "Entities.hpp" | ||||
|  | ||||
| struct EggType { | ||||
|     int dropCrateId; | ||||
| @@ -11,12 +10,10 @@ struct EggType { | ||||
| }; | ||||
|  | ||||
| namespace Eggs { | ||||
|     extern std::map<std::pair<CNSocket*, int32_t>, time_t> EggBuffs; | ||||
|     extern std::unordered_map<int, EggType> EggTypes; | ||||
|  | ||||
|     void init(); | ||||
|  | ||||
|     /// returns -1 on fail | ||||
|     int eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration); | ||||
|     void eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration); | ||||
|     void npcDataToEggData(int x, int y, int z, sNPCAppearanceData* npc, sShinyAppearanceData* egg); | ||||
| } | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| #include "Email.hpp" | ||||
|  | ||||
| #include "core/Core.hpp" | ||||
| #include "db/Database.hpp" | ||||
| #include "servers/CNShardServer.hpp" | ||||
|  | ||||
| #include "db/Database.hpp" | ||||
| #include "PlayerManager.hpp" | ||||
| #include "Items.hpp" | ||||
| #include "Chat.hpp" | ||||
|   | ||||
| @@ -5,6 +5,6 @@ | ||||
|  | ||||
| namespace Email { | ||||
|     extern std::vector<std::string> dump; | ||||
|  | ||||
|      | ||||
|     void init(); | ||||
| } | ||||
|   | ||||
| @@ -1,18 +1,15 @@ | ||||
| #include "core/Core.hpp" | ||||
| #include "Entities.hpp" | ||||
| #include "Chunking.hpp" | ||||
| #include "PlayerManager.hpp" | ||||
| #include "NPCManager.hpp" | ||||
| #include "Eggs.hpp" | ||||
| #include "MobAI.hpp" | ||||
|  | ||||
| #include <type_traits> | ||||
| #include "NPCManager.hpp" | ||||
| #include "PlayerManager.hpp" | ||||
|  | ||||
| #include <assert.h> | ||||
|  | ||||
| static_assert(std::is_standard_layout<EntityRef>::value); | ||||
| static_assert(std::is_trivially_copyable<EntityRef>::value); | ||||
|  | ||||
| EntityRef::EntityRef(CNSocket *s) { | ||||
|     type = EntityType::PLAYER; | ||||
|     kind = EntityKind::PLAYER; | ||||
|     sock = s; | ||||
| } | ||||
|  | ||||
| @@ -20,11 +17,11 @@ EntityRef::EntityRef(int32_t i) { | ||||
|     id = i; | ||||
|  | ||||
|     assert(NPCManager::NPCs.find(id) != NPCManager::NPCs.end()); | ||||
|     type = NPCManager::NPCs[id]->type; | ||||
|     kind = NPCManager::NPCs[id]->kind; | ||||
| } | ||||
|  | ||||
| bool EntityRef::isValid() const { | ||||
|     if (type == EntityType::PLAYER) | ||||
|     if (kind == EntityKind::PLAYER) | ||||
|         return PlayerManager::players.find(sock) != PlayerManager::players.end(); | ||||
|  | ||||
|     return NPCManager::NPCs.find(id) != NPCManager::NPCs.end(); | ||||
| @@ -33,21 +30,38 @@ bool EntityRef::isValid() const { | ||||
| Entity *EntityRef::getEntity() const { | ||||
|     assert(isValid()); | ||||
|  | ||||
|     if (type == EntityType::PLAYER) | ||||
|     if (kind == EntityKind::PLAYER) | ||||
|         return PlayerManager::getPlayer(sock); | ||||
|  | ||||
|     return NPCManager::NPCs[id]; | ||||
| } | ||||
|  | ||||
| sNPCAppearanceData BaseNPC::getAppearanceData() { | ||||
|     sNPCAppearanceData data = {}; | ||||
|     data.iAngle = angle; | ||||
|     data.iBarkerType = 0; // unused? | ||||
|     data.iConditionBitFlag = 0; | ||||
|     data.iHP = hp; | ||||
|     data.iNPCType = type; | ||||
|     data.iNPC_ID = id; | ||||
|     data.iX = x; | ||||
|     data.iY = y; | ||||
|     data.iZ = z; | ||||
|     return data; | ||||
| } | ||||
|  | ||||
| sNPCAppearanceData CombatNPC::getAppearanceData() { | ||||
|     sNPCAppearanceData data = BaseNPC::getAppearanceData(); | ||||
|     data.iConditionBitFlag = getCompositeCondition(); | ||||
|     return data; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Entity coming into view. | ||||
|  */ | ||||
| void BaseNPC::enterIntoViewOf(CNSocket *sock) { | ||||
|     INITSTRUCT(sP_FE2CL_NPC_ENTER, pkt); | ||||
|     pkt.NPCAppearanceData = appearanceData; | ||||
|     pkt.NPCAppearanceData.iX = x; | ||||
|     pkt.NPCAppearanceData.iY = y; | ||||
|     pkt.NPCAppearanceData.iZ = z; | ||||
|     pkt.NPCAppearanceData = getAppearanceData(); | ||||
|     sock->sendPacket(pkt, P_FE2CL_NPC_ENTER); | ||||
| } | ||||
|  | ||||
| @@ -56,7 +70,7 @@ void Bus::enterIntoViewOf(CNSocket *sock) { | ||||
|  | ||||
|     // TODO: Potentially decouple this from BaseNPC? | ||||
|     pkt.AppearanceData = { | ||||
|         3, appearanceData.iNPC_ID, appearanceData.iNPCType, | ||||
|         3, id, type, | ||||
|         x, y, z | ||||
|     }; | ||||
|  | ||||
| @@ -66,28 +80,40 @@ void Bus::enterIntoViewOf(CNSocket *sock) { | ||||
| void Egg::enterIntoViewOf(CNSocket *sock) { | ||||
|     INITSTRUCT(sP_FE2CL_SHINY_ENTER, pkt); | ||||
|  | ||||
|     Eggs::npcDataToEggData(x, y, z, &appearanceData, &pkt.ShinyAppearanceData); | ||||
|     // TODO: Potentially decouple this from BaseNPC? | ||||
|     pkt.ShinyAppearanceData = { | ||||
|         id, type, 0, // client doesn't care about map num | ||||
|         x, y, z | ||||
|     }; | ||||
|  | ||||
|     sock->sendPacket(pkt, P_FE2CL_SHINY_ENTER); | ||||
| } | ||||
|      | ||||
|  | ||||
| sNano* Player::getActiveNano() { | ||||
|     return &Nanos[activeNano]; | ||||
| } | ||||
|  | ||||
| sPCAppearanceData Player::getAppearanceData() { | ||||
|     sPCAppearanceData data = {}; | ||||
|     data.iID = iID; | ||||
|     data.iHP = HP; | ||||
|     data.iLv = level; | ||||
|     data.iX = x; | ||||
|     data.iY = y; | ||||
|     data.iZ = z; | ||||
|     data.iAngle = angle; | ||||
|     data.PCStyle = PCStyle; | ||||
|     data.Nano = Nanos[activeNano]; | ||||
|     data.iPCState = iPCState; | ||||
|     data.iSpecialState = iSpecialState; | ||||
|     memcpy(data.ItemEquip, Equip, sizeof(sItemBase) * AEQUIP_COUNT); | ||||
|     return data; | ||||
| } | ||||
|  | ||||
| // TODO: this is less effiecient than it was, because of memset() | ||||
| void Player::enterIntoViewOf(CNSocket *sock) { | ||||
|     INITSTRUCT(sP_FE2CL_PC_NEW, pkt); | ||||
|  | ||||
|     pkt.PCAppearanceData.iID = iID; | ||||
|     pkt.PCAppearanceData.iHP = HP; | ||||
|     pkt.PCAppearanceData.iLv = level; | ||||
|     pkt.PCAppearanceData.iX = x; | ||||
|     pkt.PCAppearanceData.iY = y; | ||||
|     pkt.PCAppearanceData.iZ = z; | ||||
|     pkt.PCAppearanceData.iAngle = angle; | ||||
|     pkt.PCAppearanceData.PCStyle = PCStyle; | ||||
|     pkt.PCAppearanceData.Nano = Nanos[activeNano]; | ||||
|     pkt.PCAppearanceData.iPCState = iPCState; | ||||
|     pkt.PCAppearanceData.iSpecialState = iSpecialState; | ||||
|     memcpy(pkt.PCAppearanceData.ItemEquip, Equip, sizeof(sItemBase) * AEQUIP_COUNT); | ||||
|  | ||||
|     pkt.PCAppearanceData = getAppearanceData(); | ||||
|     sock->sendPacket(pkt, P_FE2CL_PC_NEW); | ||||
| } | ||||
|  | ||||
| @@ -96,20 +122,20 @@ void Player::enterIntoViewOf(CNSocket *sock) { | ||||
|  */ | ||||
| void BaseNPC::disappearFromViewOf(CNSocket *sock) { | ||||
|     INITSTRUCT(sP_FE2CL_NPC_EXIT, pkt); | ||||
|     pkt.iNPC_ID = appearanceData.iNPC_ID; | ||||
|     pkt.iNPC_ID = id; | ||||
|     sock->sendPacket(pkt, P_FE2CL_NPC_EXIT); | ||||
| } | ||||
|  | ||||
| void Bus::disappearFromViewOf(CNSocket *sock) { | ||||
|     INITSTRUCT(sP_FE2CL_TRANSPORTATION_EXIT, pkt); | ||||
|     pkt.eTT = 3; | ||||
|     pkt.iT_ID = appearanceData.iNPC_ID; | ||||
|     pkt.iT_ID = id; | ||||
|     sock->sendPacket(pkt, P_FE2CL_TRANSPORTATION_EXIT); | ||||
| } | ||||
|  | ||||
| void Egg::disappearFromViewOf(CNSocket *sock) { | ||||
|     INITSTRUCT(sP_FE2CL_SHINY_EXIT, pkt); | ||||
|     pkt.iShinyID = appearanceData.iNPC_ID; | ||||
|     pkt.iShinyID = id; | ||||
|     sock->sendPacket(pkt, P_FE2CL_SHINY_EXIT); | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										174
									
								
								src/Entities.hpp
									
									
									
									
									
								
							
							
						
						
									
										174
									
								
								src/Entities.hpp
									
									
									
									
									
								
							| @@ -1,23 +1,26 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "core/Core.hpp" | ||||
|  | ||||
| #include "EntityRef.hpp" | ||||
| #include "Buffs.hpp" | ||||
| #include "Chunking.hpp" | ||||
| #include "Groups.hpp" | ||||
|  | ||||
| #include <stdint.h> | ||||
| #include <set> | ||||
| #include <map> | ||||
| #include <functional> | ||||
|  | ||||
| enum class EntityType : uint8_t { | ||||
|     INVALID, | ||||
|     PLAYER, | ||||
|     SIMPLE_NPC, | ||||
|     COMBAT_NPC, | ||||
|     MOB, | ||||
|     EGG, | ||||
|     BUS | ||||
| enum class AIState { | ||||
|     INACTIVE, | ||||
|     ROAMING, | ||||
|     COMBAT, | ||||
|     RETREAT, | ||||
|     DEAD | ||||
| }; | ||||
|  | ||||
| struct Entity { | ||||
|     EntityType type = EntityType::INVALID; | ||||
|     EntityKind kind = EntityKind::INVALID; | ||||
|     int x = 0, y = 0, z = 0; | ||||
|     uint64_t instanceID = 0; | ||||
|     ChunkPos chunkPos = {}; | ||||
| @@ -26,47 +29,39 @@ struct Entity { | ||||
|     // destructor must be virtual, apparently | ||||
|     virtual ~Entity() {} | ||||
|  | ||||
|     virtual bool isAlive() { return true; } | ||||
|     virtual bool isExtant() { return true; } | ||||
|  | ||||
|     // stubs | ||||
|     virtual void enterIntoViewOf(CNSocket *sock) = 0; | ||||
|     virtual void disappearFromViewOf(CNSocket *sock) = 0; | ||||
| }; | ||||
|  | ||||
| struct EntityRef { | ||||
|     EntityType type; | ||||
|     union { | ||||
|         CNSocket *sock; | ||||
|         int32_t id; | ||||
|     }; | ||||
| /* | ||||
|  * Interfaces | ||||
|  */ | ||||
| class ICombatant { | ||||
| public: | ||||
|     ICombatant() {} | ||||
|     virtual ~ICombatant() {} | ||||
|  | ||||
|     EntityRef(CNSocket *s); | ||||
|     EntityRef(int32_t i); | ||||
|  | ||||
|     bool isValid() const; | ||||
|     Entity *getEntity() const; | ||||
|  | ||||
|     bool operator==(const EntityRef& other) const { | ||||
|         if (type != other.type) | ||||
|             return false; | ||||
|  | ||||
|         if (type == EntityType::PLAYER) | ||||
|             return sock == other.sock; | ||||
|  | ||||
|         return id == other.id; | ||||
|     } | ||||
|  | ||||
|     // arbitrary ordering | ||||
|     bool operator<(const EntityRef& other) const { | ||||
|         if (type == other.type) { | ||||
|             if (type == EntityType::PLAYER) | ||||
|                 return sock < other.sock; | ||||
|             else | ||||
|                 return id < other.id; | ||||
|         } | ||||
|  | ||||
|         return type < other.type; | ||||
|     } | ||||
|     virtual bool addBuff(int, BuffCallback<int, BuffStack*>, BuffCallback<time_t>, BuffStack*) = 0; | ||||
|     virtual Buff* getBuff(int) = 0; | ||||
|     virtual void removeBuff(int) = 0; | ||||
|     virtual void removeBuff(int, BuffClass) = 0; | ||||
|     virtual void clearBuffs(bool) = 0; | ||||
|     virtual bool hasBuff(int) = 0; | ||||
|     virtual int getCompositeCondition() = 0; | ||||
|     virtual int takeDamage(EntityRef, int) = 0; | ||||
|     virtual int heal(EntityRef, int) = 0; | ||||
|     virtual bool isAlive() = 0; | ||||
|     virtual int getCurrentHP() = 0; | ||||
|     virtual int getMaxHP() = 0; | ||||
|     virtual int getLevel() = 0; | ||||
|     virtual std::vector<EntityRef> getGroupMembers() = 0; | ||||
|     virtual int32_t getCharType() = 0; | ||||
|     virtual int32_t getID() = 0; | ||||
|     virtual EntityRef getRef() = 0; | ||||
|     virtual void step(time_t currTime) = 0; | ||||
| }; | ||||
|  | ||||
| /* | ||||
| @@ -74,75 +69,104 @@ struct EntityRef { | ||||
|  */ | ||||
| class BaseNPC : public Entity { | ||||
| public: | ||||
|     sNPCAppearanceData appearanceData = {}; | ||||
|     int id; | ||||
|     int type; | ||||
|     int hp; | ||||
|     int angle; | ||||
|     bool loopingPath = false; | ||||
|  | ||||
|     BaseNPC(int _X, int _Y, int _Z, int angle, uint64_t iID, int t, int id) { // XXX | ||||
|         x = _X; | ||||
|         y = _Y; | ||||
|         z = _Z; | ||||
|         appearanceData.iNPCType = t; | ||||
|         appearanceData.iHP = 400; | ||||
|         appearanceData.iAngle = angle; | ||||
|         appearanceData.iConditionBitFlag = 0; | ||||
|         appearanceData.iBarkerType = 0; | ||||
|         appearanceData.iNPC_ID = id; | ||||
|  | ||||
|     BaseNPC(int _A, uint64_t iID, int t, int _id) { | ||||
|         kind = EntityKind::SIMPLE_NPC; | ||||
|         type = t; | ||||
|         hp = 400; | ||||
|         angle = _A; | ||||
|         id = _id; | ||||
|         instanceID = iID; | ||||
|     }; | ||||
|  | ||||
|     virtual void enterIntoViewOf(CNSocket *sock) override; | ||||
|     virtual void disappearFromViewOf(CNSocket *sock) override; | ||||
|  | ||||
|     virtual sNPCAppearanceData getAppearanceData(); | ||||
| }; | ||||
|  | ||||
| struct CombatNPC : public BaseNPC { | ||||
| struct CombatNPC : public BaseNPC, public ICombatant { | ||||
|     int maxHealth = 0; | ||||
|     int spawnX = 0; | ||||
|     int spawnY = 0; | ||||
|     int spawnZ = 0; | ||||
|     int level = 0; | ||||
|     int speed = 300; | ||||
|     AIState state = AIState::INACTIVE; | ||||
|     Group* group = nullptr; | ||||
|     int playersInView = 0; // for optimizing away AI in empty chunks | ||||
|  | ||||
|     void (*_stepAI)(CombatNPC*, time_t) = nullptr; | ||||
|     std::map<AIState, void (*)(CombatNPC*, time_t)> stateHandlers; | ||||
|     std::map<AIState, void (*)(CombatNPC*, EntityRef)> transitionHandlers; | ||||
|  | ||||
|     // XXX | ||||
|     CombatNPC(int x, int y, int z, int angle, uint64_t iID, int t, int id, int maxHP) : | ||||
|         BaseNPC(x, y, z, angle, iID, t, id), | ||||
|         maxHealth(maxHP) {} | ||||
|     std::unordered_map<int, Buff*> buffs = {}; | ||||
|  | ||||
|     virtual void stepAI(time_t currTime) { | ||||
|         if (_stepAI != nullptr) | ||||
|             _stepAI(this, currTime); | ||||
|     CombatNPC(int spawnX, int spawnY, int spawnZ, int angle, uint64_t iID, int t, int id, int maxHP) | ||||
|         : BaseNPC(angle, iID, t, id), maxHealth(maxHP) { | ||||
|         this->spawnX = spawnX; | ||||
|         this->spawnY = spawnY; | ||||
|         this->spawnZ = spawnZ; | ||||
|  | ||||
|         kind = EntityKind::COMBAT_NPC; | ||||
|  | ||||
|         stateHandlers[AIState::INACTIVE] = {}; | ||||
|         transitionHandlers[AIState::INACTIVE] = {}; | ||||
|     } | ||||
|  | ||||
|     virtual bool isAlive() override { return appearanceData.iHP > 0; } | ||||
|     virtual sNPCAppearanceData getAppearanceData() override; | ||||
|  | ||||
|     virtual bool isExtant() override { return hp > 0; } | ||||
|  | ||||
|     virtual bool addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) override; | ||||
|     virtual Buff* getBuff(int buffId) override; | ||||
|     virtual void removeBuff(int buffId) override; | ||||
|     virtual void removeBuff(int buffId, BuffClass buffClass) override; | ||||
|     virtual void clearBuffs(bool force) override; | ||||
|     virtual bool hasBuff(int buffId) override; | ||||
|     virtual int getCompositeCondition() override; | ||||
|     virtual int takeDamage(EntityRef src, int amt) override; | ||||
|     virtual int heal(EntityRef src, int amt) override; | ||||
|     virtual bool isAlive() override; | ||||
|     virtual int getCurrentHP() override; | ||||
|     virtual int getMaxHP() override; | ||||
|     virtual int getLevel() override; | ||||
|     virtual std::vector<EntityRef> getGroupMembers() override; | ||||
|     virtual int32_t getCharType() override; | ||||
|     virtual int32_t getID() override; | ||||
|     virtual EntityRef getRef() override; | ||||
|     virtual void step(time_t currTime) override; | ||||
|  | ||||
|     virtual void transition(AIState newState, EntityRef src); | ||||
| }; | ||||
|  | ||||
| // Mob is in MobAI.hpp, Player is in Player.hpp | ||||
|  | ||||
| // TODO: decouple from BaseNPC | ||||
| struct Egg : public BaseNPC { | ||||
|     bool summoned = false; | ||||
|     bool dead = false; | ||||
|     time_t deadUntil; | ||||
|  | ||||
|     Egg(int x, int y, int z, uint64_t iID, int t, int32_t id, bool summon) | ||||
|         : BaseNPC(x, y, z, 0, iID, t, id) { | ||||
|     Egg(uint64_t iID, int t, int32_t id, bool summon) | ||||
|         : BaseNPC(0, iID, t, id) { | ||||
|         summoned = summon; | ||||
|         type = EntityType::EGG; | ||||
|         kind = EntityKind::EGG; | ||||
|     } | ||||
|  | ||||
|     virtual bool isAlive() override { return !dead; } | ||||
|     virtual bool isExtant() override { return !dead; } | ||||
|  | ||||
|     virtual void enterIntoViewOf(CNSocket *sock) override; | ||||
|     virtual void disappearFromViewOf(CNSocket *sock) override; | ||||
| }; | ||||
|  | ||||
| // TODO: decouple from BaseNPC | ||||
| struct Bus : public BaseNPC { | ||||
|     Bus(int x, int y, int z, int angle, uint64_t iID, int t, int id) : | ||||
|         BaseNPC(x, y, z, angle, iID, t, id) { | ||||
|         type = EntityType::BUS; | ||||
|     Bus(int angle, uint64_t iID, int t, int id) : | ||||
|         BaseNPC(angle, iID, t, id) { | ||||
|         kind = EntityKind::BUS; | ||||
|         loopingPath = true; | ||||
|     } | ||||
|  | ||||
|   | ||||
							
								
								
									
										56
									
								
								src/EntityRef.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/EntityRef.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "core/Core.hpp" | ||||
|  | ||||
| /* forward declaration(s) */ | ||||
| struct Entity; | ||||
|  | ||||
| enum EntityKind { | ||||
|     INVALID, | ||||
|     PLAYER, | ||||
|     SIMPLE_NPC, | ||||
|     COMBAT_NPC, | ||||
|     MOB, | ||||
|     EGG, | ||||
|     BUS | ||||
| }; | ||||
|  | ||||
| struct EntityRef { | ||||
|     EntityKind kind; | ||||
|     union { | ||||
|         CNSocket *sock; | ||||
|         int32_t id; | ||||
|     }; | ||||
|  | ||||
|     EntityRef(CNSocket *s); | ||||
|     EntityRef(int32_t i); | ||||
|  | ||||
|     bool isValid() const; | ||||
|     Entity *getEntity() const; | ||||
|  | ||||
|     bool operator==(const EntityRef& other) const { | ||||
|         if (kind != other.kind) | ||||
|             return false; | ||||
|  | ||||
|         if (kind == EntityKind::PLAYER) | ||||
|             return sock == other.sock; | ||||
|  | ||||
|         return id == other.id; | ||||
|     } | ||||
|  | ||||
|     bool operator!=(const EntityRef& other) const { | ||||
|         return !(*this == other); | ||||
|     } | ||||
|  | ||||
|     // arbitrary ordering | ||||
|     bool operator<(const EntityRef& other) const { | ||||
|         if (kind == other.kind) { | ||||
|             if (kind == EntityKind::PLAYER) | ||||
|                 return sock < other.sock; | ||||
|             else | ||||
|                 return id < other.id; | ||||
|         } | ||||
|  | ||||
|         return kind < other.kind; | ||||
|     } | ||||
| }; | ||||
							
								
								
									
										443
									
								
								src/Groups.cpp
									
									
									
									
									
								
							
							
						
						
									
										443
									
								
								src/Groups.cpp
									
									
									
									
									
								
							| @@ -1,13 +1,10 @@ | ||||
| #include "servers/CNShardServer.hpp" | ||||
| #include "PlayerManager.hpp" | ||||
| #include "Groups.hpp" | ||||
| #include "Nanos.hpp" | ||||
| #include "Abilities.hpp" | ||||
|  | ||||
| #include <iostream> | ||||
| #include <chrono> | ||||
| #include <algorithm> | ||||
| #include <thread> | ||||
| #include "servers/CNShardServer.hpp" | ||||
|  | ||||
| #include "Player.hpp" | ||||
| #include "PlayerManager.hpp" | ||||
| #include "Entities.hpp" | ||||
|  | ||||
| /* | ||||
|  * NOTE: Variadic response packets that list group members are technically | ||||
| @@ -19,22 +16,177 @@ | ||||
|  | ||||
| using namespace Groups; | ||||
|  | ||||
| Group::Group(EntityRef leader) { | ||||
|     addToGroup(this, leader); | ||||
| } | ||||
|  | ||||
| static void attachGroupData(std::vector<EntityRef>& pcs, std::vector<EntityRef>& npcs, uint8_t* pivot) { | ||||
|     for(EntityRef pcRef : pcs) { | ||||
|         sPCGroupMemberInfo* info = (sPCGroupMemberInfo*)pivot; | ||||
|  | ||||
|         Player* plr = PlayerManager::getPlayer(pcRef.sock); | ||||
|         info->iPC_ID = plr->iID; | ||||
|         info->iPCUID = plr->PCStyle.iPC_UID; | ||||
|         info->iNameCheck = plr->PCStyle.iNameCheck; | ||||
|         memcpy(info->szFirstName, plr->PCStyle.szFirstName, sizeof(plr->PCStyle.szFirstName)); | ||||
|         memcpy(info->szLastName, plr->PCStyle.szLastName, sizeof(plr->PCStyle.szLastName)); | ||||
|         info->iSpecialState = plr->iSpecialState; | ||||
|         info->iLv = plr->level; | ||||
|         info->iHP = plr->HP; | ||||
|         info->iMaxHP = PC_MAXHEALTH(plr->level); | ||||
|         // info->iMapType = 0; | ||||
|         // info->iMapNum = 0; | ||||
|         info->iX = plr->x; | ||||
|         info->iY = plr->y; | ||||
|         info->iZ = plr->z; | ||||
|         if(plr->activeNano > 0) { | ||||
|             info->Nano = *plr->getActiveNano(); | ||||
|             info->bNano = true; | ||||
|         } | ||||
|  | ||||
|         pivot = (uint8_t*)(info + 1); | ||||
|     } | ||||
|     for(EntityRef npcRef : npcs) { | ||||
|         sNPCGroupMemberInfo* info = (sNPCGroupMemberInfo*)pivot; | ||||
|  | ||||
|         // probably should not assume that the combatant is an | ||||
|         // entity, but it works for now | ||||
|         BaseNPC* npc = (BaseNPC*)npcRef.getEntity(); | ||||
|         info->iNPC_ID = npcRef.id; | ||||
|         info->iNPC_Type = npc->type; | ||||
|         info->iHP = npc->hp; | ||||
|         info->iX = npc->x; | ||||
|         info->iY = npc->y; | ||||
|         info->iZ = npc->z; | ||||
|  | ||||
|         pivot = (uint8_t*)(info + 1); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void Groups::addToGroup(Group* group, EntityRef member) { | ||||
|     if (member.kind == EntityKind::PLAYER) { | ||||
|         Player* plr = PlayerManager::getPlayer(member.sock); | ||||
|         plr->group = group; | ||||
|     } | ||||
|     else if (member.kind == EntityKind::COMBAT_NPC) { | ||||
|         CombatNPC* npc = (CombatNPC*)member.getEntity(); | ||||
|         npc->group = group; | ||||
|     } | ||||
|     else { | ||||
|         std::cout << "[WARN] Adding a weird entity type to a group" << std::endl; | ||||
|     } | ||||
|  | ||||
|     group->members.push_back(member); | ||||
|  | ||||
|     if(member.kind == EntityKind::PLAYER) { | ||||
|         std::vector<EntityRef> pcs = group->filter(EntityKind::PLAYER); | ||||
|         std::vector<EntityRef> npcs = group->filter(EntityKind::COMBAT_NPC); | ||||
|         size_t pcCount = pcs.size(); | ||||
|         size_t npcCount = npcs.size(); | ||||
|  | ||||
|         uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; | ||||
|         memset(respbuf, 0, CN_PACKET_BUFFER_SIZE); | ||||
|         sP_FE2CL_PC_GROUP_JOIN* pkt = (sP_FE2CL_PC_GROUP_JOIN*)respbuf; | ||||
|  | ||||
|         pkt->iID_NewMember = PlayerManager::getPlayer(member.sock)->iID; | ||||
|         pkt->iMemberPCCnt = (int32_t)pcCount; | ||||
|         pkt->iMemberNPCCnt = (int32_t)npcCount; | ||||
|  | ||||
|         if(!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_JOIN), pcCount, sizeof(sPCGroupMemberInfo)) | ||||
|             || !validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_JOIN) + pcCount * sizeof(sPCGroupMemberInfo), npcCount, sizeof(sNPCGroupMemberInfo))) { | ||||
|             std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_JOIN packet size" << std::endl; | ||||
|         } else { | ||||
|             uint8_t* pivot = (uint8_t*)(pkt + 1); | ||||
|             attachGroupData(pcs, npcs, pivot); | ||||
|             // PC_GROUP_JOIN_SUCC and PC_GROUP_JOIN carry identical payloads but have different IDs | ||||
|             // (and the client does care!) so we need to send one to the new member | ||||
|             // and the other to the rest | ||||
|             size_t resplen = sizeof(sP_FE2CL_PC_GROUP_JOIN) + pcCount * sizeof(sPCGroupMemberInfo) + npcCount * sizeof(sNPCGroupMemberInfo); | ||||
|             member.sock->sendPacket(respbuf, P_FE2CL_PC_GROUP_JOIN_SUCC, resplen); | ||||
|             sendToGroup(group, member, respbuf, P_FE2CL_PC_GROUP_JOIN, resplen); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| bool Groups::removeFromGroup(Group* group, EntityRef member) { | ||||
|     if (member.kind == EntityKind::PLAYER) { | ||||
|         Player* plr = PlayerManager::getPlayer(member.sock); | ||||
|         plr->group = nullptr; // no dangling pointers here muahaahahah | ||||
|  | ||||
|         INITSTRUCT(sP_FE2CL_PC_GROUP_LEAVE_SUCC, leavePkt); | ||||
|         member.sock->sendPacket(leavePkt, P_FE2CL_PC_GROUP_LEAVE_SUCC); | ||||
|     } | ||||
|     else if (member.kind == EntityKind::COMBAT_NPC) { | ||||
|         CombatNPC* npc = (CombatNPC*)member.getEntity(); | ||||
|         npc->group = nullptr; | ||||
|     } | ||||
|     else { | ||||
|         std::cout << "[WARN] Removing a weird entity type from a group" << std::endl; | ||||
|     } | ||||
|  | ||||
|     auto it = std::find(group->members.begin(), group->members.end(), member); | ||||
|     if (it == group->members.end()) { | ||||
|         std::cout << "[WARN] Tried to remove a member that isn't in the group" << std::endl; | ||||
|     } else { | ||||
|         group->members.erase(it); | ||||
|     } | ||||
|  | ||||
|     if(member.kind == EntityKind::PLAYER) { | ||||
|         std::vector<EntityRef> pcs = group->filter(EntityKind::PLAYER); | ||||
|         std::vector<EntityRef> npcs = group->filter(EntityKind::COMBAT_NPC); | ||||
|         size_t pcCount = pcs.size(); | ||||
|         size_t npcCount = npcs.size(); | ||||
|  | ||||
|         uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; | ||||
|         memset(respbuf, 0, CN_PACKET_BUFFER_SIZE); | ||||
|         sP_FE2CL_PC_GROUP_LEAVE* pkt = (sP_FE2CL_PC_GROUP_LEAVE*)respbuf; | ||||
|  | ||||
|         pkt->iID_LeaveMember = PlayerManager::getPlayer(member.sock)->iID; | ||||
|         pkt->iMemberPCCnt = (int32_t)pcCount; | ||||
|         pkt->iMemberNPCCnt = (int32_t)npcCount; | ||||
|  | ||||
|         if(!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_LEAVE), pcCount, sizeof(sPCGroupMemberInfo)) | ||||
|             || !validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_LEAVE) + pcCount * sizeof(sPCGroupMemberInfo), npcCount, sizeof(sNPCGroupMemberInfo))) { | ||||
|             std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_LEAVE packet size" << std::endl; | ||||
|         } else { | ||||
|             uint8_t* pivot = (uint8_t*)(pkt + 1); | ||||
|             attachGroupData(pcs, npcs, pivot); | ||||
|             sendToGroup(group, respbuf, P_FE2CL_PC_GROUP_LEAVE, | ||||
|                 sizeof(sP_FE2CL_PC_GROUP_LEAVE) + pcCount * sizeof(sPCGroupMemberInfo) + npcCount * sizeof(sNPCGroupMemberInfo)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (group->members.size() == 1) { | ||||
|         return removeFromGroup(group, group->members.back()); | ||||
|     } | ||||
|  | ||||
|     if (group->members.empty()) { | ||||
|         delete group; // cleanup memory | ||||
|         return true; | ||||
|     } | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| void Groups::disbandGroup(Group* group) { | ||||
|     // remove everyone from the group!! | ||||
|     bool done = false; | ||||
|     while(!done) { | ||||
|         EntityRef back = group->members.back(); | ||||
|         done = removeFromGroup(group, back); | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void requestGroup(CNSocket* sock, CNPacketData* data) { | ||||
|     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 (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->iIDGroup != otherPlr->iID || otherPlr->groupCnt > 1) { | ||||
|     if ((plr->group != nullptr && plr->group->filter(EntityKind::PLAYER).size() >= 4) || otherPlr->group != nullptr) { | ||||
|         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; | ||||
| @@ -75,255 +227,80 @@ static void joinGroup(CNSocket* sock, CNPacketData* data) { | ||||
|     Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_From); | ||||
|  | ||||
|     if (otherPlr == nullptr) | ||||
|         return; | ||||
|         return; // disconnect or something | ||||
|  | ||||
|     otherPlr = PlayerManager::getPlayerFromID(otherPlr->iIDGroup); | ||||
|  | ||||
|     if (otherPlr == nullptr) | ||||
|         return; | ||||
|     int size = otherPlr->group == nullptr ? 1 : otherPlr->group->filter(EntityKind::PLAYER).size(); | ||||
|  | ||||
|     // 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) { | ||||
|     if (plr->group != nullptr || size + 1 > 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; | ||||
|     if (otherPlr->group == nullptr) { | ||||
|         // create group | ||||
|         EntityRef otherPlrRef = PlayerManager::getSockFromID(recv->iID_From); | ||||
|         otherPlr->group = new Group(otherPlrRef); | ||||
|     } | ||||
|  | ||||
|     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 | ||||
|  | ||||
|         if (varPlr != plr) { // apply the new member's buffs to the group and the group's buffs to the new member | ||||
|             if (Nanos::SkillTable[varPlr->Nanos[varPlr->activeNano].iSkillID].targetType == 3) | ||||
|                 Nanos::applyBuff(sock, varPlr->Nanos[varPlr->activeNano].iSkillID, 1, 1, bitFlag); | ||||
|             if (Nanos::SkillTable[plr->Nanos[plr->activeNano].iSkillID].targetType == 3) | ||||
|                 Nanos::applyBuff(sockTo, plr->Nanos[plr->activeNano].iSkillID, 1, 1, bitFlag); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     sendToGroup(otherPlr, (void*)&respbuf, P_FE2CL_PC_GROUP_JOIN, resplen); | ||||
|     addToGroup(otherPlr->group, sock); | ||||
| } | ||||
|  | ||||
| static void leaveGroup(CNSocket* sock, CNPacketData* data) { | ||||
|     Player* plr = PlayerManager::getPlayer(sock); | ||||
|     groupKickPlayer(plr); | ||||
|     groupKick(plr->group, sock); | ||||
| } | ||||
|  | ||||
| void Groups::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 Groups::sendToGroup(Group* group, void* buf, uint32_t type, size_t size) { | ||||
|     auto players = group->filter(EntityKind::PLAYER); | ||||
|     for (EntityRef ref : players) { | ||||
|         ref.sock->sendPacket(buf, type, size); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void Groups::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; | ||||
| void Groups::sendToGroup(Group* group, EntityRef excluded, void* buf, uint32_t type, size_t size) { | ||||
|     auto players = group->filter(EntityKind::PLAYER); | ||||
|     for (EntityRef ref : players) { | ||||
|         if(ref != excluded) ref.sock->sendPacket(buf, type, size); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void Groups::groupTickInfo(CNSocket* sock) { | ||||
|     Player* plr = PlayerManager::getPlayer(sock); | ||||
|     Group* group = plr->group; | ||||
|     std::vector<EntityRef> pcs = group->filter(EntityKind::PLAYER); | ||||
|     std::vector<EntityRef> npcs = group->filter(EntityKind::COMBAT_NPC); | ||||
|     size_t pcCount = pcs.size(); | ||||
|     size_t npcCount = npcs.size(); | ||||
|  | ||||
|     size_t resplen = sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO) + plr->groupCnt * sizeof(sPCGroupMemberInfo); | ||||
|     uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; | ||||
|     memset(respbuf, 0, CN_PACKET_BUFFER_SIZE); | ||||
|     sP_FE2CL_PC_GROUP_MEMBER_INFO* pkt = (sP_FE2CL_PC_GROUP_MEMBER_INFO*)respbuf; | ||||
|  | ||||
|     memset(respbuf, 0, resplen); | ||||
|     pkt->iID = plr->iID; | ||||
|     pkt->iMemberPCCnt = (int32_t)pcCount; | ||||
|     pkt->iMemberNPCCnt = (int32_t)npcCount; | ||||
|  | ||||
|     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); | ||||
| } | ||||
|  | ||||
| static void groupUnbuff(Player* plr) { | ||||
|     for (int i = 0; i < plr->groupCnt; i++) { | ||||
|         for (int n = 0; n < plr->groupCnt; n++) { | ||||
|             if (i == n) | ||||
|                 continue; | ||||
|  | ||||
|             Player* otherPlr = PlayerManager::getPlayerFromID(plr->groupIDs[i]); | ||||
|             CNSocket* sock = PlayerManager::getSockFromID(plr->groupIDs[n]); | ||||
|  | ||||
|             Nanos::applyBuff(sock, otherPlr->Nanos[otherPlr->activeNano].iSkillID, 2, 1, 0); | ||||
|         } | ||||
|     if(!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO), pcCount, sizeof(sPCGroupMemberInfo)) | ||||
|         || !validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO) + pcCount * sizeof(sPCGroupMemberInfo), npcCount, sizeof(sNPCGroupMemberInfo))) { | ||||
|         std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_MEMBER_INFO packet size" << std::endl; | ||||
|     } else { | ||||
|         uint8_t* pivot = (uint8_t*)(pkt + 1); | ||||
|         attachGroupData(pcs, npcs, pivot); | ||||
|         sock->sendPacket(respbuf, P_FE2CL_PC_GROUP_MEMBER_INFO, | ||||
|             sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO) + pcCount * sizeof(sPCGroupMemberInfo) + npcCount * sizeof(sNPCGroupMemberInfo)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void Groups::groupKickPlayer(Player* plr) { | ||||
| void Groups::groupKick(Group* group, EntityRef ref) { | ||||
|  | ||||
|     // 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; | ||||
|     if (group->members[0] == ref) { | ||||
|         disbandGroup(group); | ||||
|         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 bitFlag = getGroupFlags(otherPlr) & ~plr->iGroupConditionBitFlag; | ||||
|     int moveDown = 0; | ||||
|  | ||||
|     CNSocket* sock = PlayerManager::getSockFromID(plr->iID); | ||||
|  | ||||
|     if (sock == nullptr) | ||||
|         return; | ||||
|  | ||||
|     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; | ||||
|         } else { // remove the leaving member's buffs from the group and remove the group buffs from the leaving member. | ||||
|             if (Nanos::SkillTable[varPlr->Nanos[varPlr->activeNano].iSkillID].targetType == 3) | ||||
|                 Nanos::applyBuff(sock, varPlr->Nanos[varPlr->activeNano].iSkillID, 2, 1, 0); | ||||
|             if (Nanos::SkillTable[plr->Nanos[varPlr->activeNano].iSkillID].targetType == 3) | ||||
|                 Nanos::applyBuff(sockTo, plr->Nanos[plr->activeNano].iSkillID, 2, 1, bitFlag); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     plr->iIDGroup = plr->iID; | ||||
|     otherPlr->groupCnt -= 1; | ||||
|  | ||||
|     sendToGroup(otherPlr, (void*)&respbuf, P_FE2CL_PC_GROUP_LEAVE, resplen); | ||||
|  | ||||
|     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)); | ||||
| } | ||||
|  | ||||
| int Groups::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; | ||||
|     removeFromGroup(group, ref); | ||||
| } | ||||
|  | ||||
| void Groups::init() { | ||||
|   | ||||
| @@ -1,17 +1,37 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "Player.hpp" | ||||
| #include "core/Core.hpp" | ||||
| #include "servers/CNShardServer.hpp" | ||||
| #include "EntityRef.hpp" | ||||
|  | ||||
| #include <map> | ||||
| #include <list> | ||||
| #include <vector> | ||||
| #include <assert.h> | ||||
|  | ||||
| struct Group { | ||||
|     std::vector<EntityRef> members; | ||||
|  | ||||
|     std::vector<EntityRef> filter(EntityKind kind) { | ||||
|         std::vector<EntityRef> filtered; | ||||
|         std::copy_if(members.begin(), members.end(), std::back_inserter(filtered), [kind](EntityRef e) { | ||||
|             return e.kind == kind; | ||||
|             }); | ||||
|         return filtered; | ||||
|     } | ||||
|     EntityRef getLeader() { | ||||
|         assert(members.size() > 0); | ||||
|         return members[0]; | ||||
|     } | ||||
|  | ||||
|     Group(EntityRef leader); | ||||
| }; | ||||
|  | ||||
| namespace Groups { | ||||
|     void init(); | ||||
|  | ||||
|     void sendToGroup(Player* plr, void* buf, uint32_t type, size_t size); | ||||
|     void groupTickInfo(Player* plr); | ||||
|     void groupKickPlayer(Player* plr); | ||||
|     int getGroupFlags(Player* plr); | ||||
|     void sendToGroup(Group* group, void* buf, uint32_t type, size_t size); | ||||
|     void sendToGroup(Group* group, EntityRef excluded, void* buf, uint32_t type, size_t size); | ||||
|     void groupTickInfo(CNSocket* sock); | ||||
|  | ||||
|     void groupKick(Group* group, EntityRef ref); | ||||
|     void addToGroup(Group* group, EntityRef member); | ||||
|     bool removeFromGroup(Group* group, EntityRef member); // true iff group deleted | ||||
|     void disbandGroup(Group* group); | ||||
| } | ||||
|   | ||||
| @@ -1,16 +1,19 @@ | ||||
| #include "servers/CNShardServer.hpp" | ||||
| #include "Items.hpp" | ||||
|  | ||||
| #include "servers/CNShardServer.hpp" | ||||
|  | ||||
| #include "Player.hpp" | ||||
| #include "PlayerManager.hpp" | ||||
| #include "Nanos.hpp" | ||||
| #include "NPCManager.hpp" | ||||
| #include "Player.hpp" | ||||
| #include "Abilities.hpp" | ||||
| #include "Missions.hpp" | ||||
| #include "Eggs.hpp" | ||||
| #include "Rand.hpp" | ||||
| #include "MobAI.hpp" | ||||
| #include "Missions.hpp" | ||||
| #include "Buffs.hpp" | ||||
|  | ||||
| #include <string.h> // for memset() | ||||
| #include <assert.h> | ||||
| #include <numeric> | ||||
|  | ||||
| using namespace Items; | ||||
|  | ||||
| @@ -479,30 +482,34 @@ static void itemUseHandler(CNSocket* sock, CNPacketData* data) { | ||||
|     resp->iSlotNum = request->iSlotNum; | ||||
|     resp->RemainItem = gumball; | ||||
|     resp->iTargetCnt = 1; | ||||
|     resp->eST = EST_NANOSTIMPAK; | ||||
|     resp->eST = (int32_t)SkillType::NANOSTIMPAK; | ||||
|     resp->iSkillID = 144; | ||||
|  | ||||
|     int value1 = CSB_BIT_STIMPAKSLOT1 << request->iNanoSlot; | ||||
|     int value2 = ECSB_STIMPAKSLOT1 + request->iNanoSlot; | ||||
|     int eCSB = ECSB_STIMPAKSLOT1 + request->iNanoSlot; | ||||
|  | ||||
|     respdata->eCT = 1; | ||||
|     respdata->iID = player->iID; | ||||
|     respdata->iConditionBitFlag = value1; | ||||
|     respdata->iConditionBitFlag = CSB_FROM_ECSB(eCSB); | ||||
|  | ||||
|     INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt); | ||||
|     pkt.eCSTB = value2; // eCharStatusTimeBuffID | ||||
|     pkt.eTBU = 1; // eTimeBuffUpdate | ||||
|     pkt.eTBT = 1; // eTimeBuffType 1 means nano | ||||
|     pkt.iConditionBitFlag = player->iConditionBitFlag |= value1; | ||||
|     sock->sendPacket(pkt, P_FE2CL_PC_BUFF_UPDATE); | ||||
|     int durationMilliseconds = Abilities::SkillTable[144].durationTime[0] * 100; | ||||
|     BuffStack gumballBuff = { | ||||
|         durationMilliseconds / MS_PER_PLAYER_TICK, | ||||
|         0, | ||||
|         sock, | ||||
|         BuffClass::CASH_ITEM // or BuffClass::ITEM? | ||||
|     }; | ||||
|     player->addBuff(eCSB, | ||||
|         [](EntityRef self, Buff* buff, int status, BuffStack* stack) { | ||||
|             Buffs::timeBuffUpdate(self, buff, status, stack); | ||||
|         }, | ||||
|         [](EntityRef self, Buff* buff, time_t currTime) { | ||||
|             // no-op | ||||
|         }, | ||||
|         &gumballBuff); | ||||
|  | ||||
|     sock->sendPacket((void*)&respbuf, P_FE2CL_REP_PC_ITEM_USE_SUCC, resplen); | ||||
|     // update inventory serverside | ||||
|     player->Inven[resp->iSlotNum] = resp->RemainItem; | ||||
|  | ||||
|     std::pair<CNSocket*, int32_t> key = std::make_pair(sock, value1); | ||||
|     time_t until = getTime() + (time_t)Nanos::SkillTable[144].durationTime[0] * 100; | ||||
|     Eggs::EggBuffs[key] = until; | ||||
| } | ||||
|  | ||||
| static void itemBankOpenHandler(CNSocket* sock, CNPacketData* data) { | ||||
| @@ -755,7 +762,7 @@ static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRo | ||||
|     if (rolled.taros % miscDropChance.taroDropChanceTotal < miscDropChance.taroDropChance) { | ||||
|         plr->money += miscDropType.taroAmount; | ||||
|         // money nano boost | ||||
|         if (plr->iConditionBitFlag & CSB_BIT_REWARD_CASH) { | ||||
|         if (plr->hasBuff(ECSB_REWARD_CASH)) { | ||||
|             int boost = 0; | ||||
|             if (Nanos::getNanoBoost(plr)) // for gumballs | ||||
|                 boost = 1; | ||||
| @@ -770,7 +777,7 @@ static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRo | ||||
|         if (levelDifference > 0) | ||||
|             fm = levelDifference < 10 ? fm - (levelDifference * fm / 10) : 0; | ||||
|         // scavenger nano boost | ||||
|         if (plr->iConditionBitFlag & CSB_BIT_REWARD_BLOB) { | ||||
|         if (plr->hasBuff(ECSB_REWARD_BLOB)) { | ||||
|             int boost = 0; | ||||
|             if (Nanos::getNanoBoost(plr)) // for gumballs | ||||
|                 boost = 1; | ||||
| @@ -822,12 +829,12 @@ static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRo | ||||
|  | ||||
| void Items::giveMobDrop(CNSocket *sock, Mob* mob, const DropRoll& rolled, const DropRoll& eventRolled) { | ||||
|     // sanity check | ||||
|     if (Items::MobToDropMap.find(mob->appearanceData.iNPCType) == Items::MobToDropMap.end()) { | ||||
|         std::cout << "[WARN] Mob ID " << mob->appearanceData.iNPCType << " has no drops assigned" << std::endl; | ||||
|     if (Items::MobToDropMap.find(mob->type) == Items::MobToDropMap.end()) { | ||||
|         std::cout << "[WARN] Mob ID " << mob->type << " has no drops assigned" << std::endl; | ||||
|         return; | ||||
|     } | ||||
|     // find mob drop id | ||||
|     int mobDropId = Items::MobToDropMap[mob->appearanceData.iNPCType]; | ||||
|     int mobDropId = Items::MobToDropMap[mob->type]; | ||||
|  | ||||
|     giveSingleDrop(sock, mob, mobDropId, rolled); | ||||
|  | ||||
|   | ||||
| @@ -1,9 +1,13 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "servers/CNShardServer.hpp" | ||||
| #include "core/Core.hpp" | ||||
|  | ||||
| #include "Player.hpp" | ||||
| #include "MobAI.hpp" | ||||
| #include "Rand.hpp" | ||||
| #include "MobAI.hpp" | ||||
|  | ||||
| #include <vector> | ||||
| #include <map> | ||||
|  | ||||
| struct CrocPotEntry { | ||||
|     int multStats, multLooks; | ||||
|   | ||||
| @@ -1,11 +1,10 @@ | ||||
| #include "servers/CNShardServer.hpp" | ||||
| #include "Missions.hpp" | ||||
| #include "PlayerManager.hpp" | ||||
| #include "Nanos.hpp" | ||||
| #include "Items.hpp" | ||||
| #include "Transport.hpp" | ||||
|  | ||||
| #include "string.h" | ||||
| #include "servers/CNShardServer.hpp" | ||||
|  | ||||
| #include "PlayerManager.hpp" | ||||
| #include "Items.hpp" | ||||
| #include "Nanos.hpp" | ||||
|  | ||||
| using namespace Missions; | ||||
|  | ||||
| @@ -164,14 +163,14 @@ static int giveMissionReward(CNSocket *sock, int task, int choice=0) { | ||||
|  | ||||
|     // update player | ||||
|     plr->money += reward->money; | ||||
|     if (plr->iConditionBitFlag & CSB_BIT_REWARD_CASH) { // nano boost for taros | ||||
|     if (plr->hasBuff(ECSB_REWARD_CASH)) { // nano boost for taros | ||||
|         int boost = 0; | ||||
|         if (Nanos::getNanoBoost(plr)) // for gumballs | ||||
|             boost = 1; | ||||
|         plr->money += reward->money * (5 + boost) / 25; | ||||
|     } | ||||
|  | ||||
|     if (plr->iConditionBitFlag & CSB_BIT_REWARD_BLOB) { // nano boost for fm | ||||
|     if (plr->hasBuff(ECSB_REWARD_BLOB)) { // nano boost for fm | ||||
|         int boost = 0; | ||||
|         if (Nanos::getNanoBoost(plr)) // for gumballs | ||||
|             boost = 1; | ||||
| @@ -367,15 +366,15 @@ static void taskStart(CNSocket* sock, CNPacketData* data) { | ||||
|     sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_START_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_START_SUCC)); | ||||
|  | ||||
|     // if escort task, assign matching paths to all nearby NPCs | ||||
|     if (task["m_iHTaskType"] == 6) { | ||||
|     if (task["m_iHTaskType"] == (int)eTaskTypeProperty::EscortDefence) { | ||||
|         for (ChunkPos& chunkPos : Chunking::getChunksInMap(plr->instanceID)) { // check all NPCs in the instance | ||||
|             Chunk* chunk = Chunking::chunks[chunkPos]; | ||||
|             for (EntityRef ref : chunk->entities) { | ||||
|                 if (ref.type != EntityType::PLAYER) { | ||||
|                 if (ref.kind != EntityKind::PLAYER) { | ||||
|                     BaseNPC* npc = (BaseNPC*)ref.getEntity(); | ||||
|                     NPCPath* path = Transport::findApplicablePath(npc->appearanceData.iNPC_ID, npc->appearanceData.iNPCType, missionData->iTaskNum); | ||||
|                     NPCPath* path = Transport::findApplicablePath(npc->id, npc->type, missionData->iTaskNum); | ||||
|                     if (path != nullptr) { | ||||
|                         Transport::constructPathNPC(npc->appearanceData.iNPC_ID, path); | ||||
|                         Transport::constructPathNPC(npc->id, path); | ||||
|                         return; | ||||
|                     } | ||||
|                 } | ||||
| @@ -399,7 +398,7 @@ static void taskEnd(CNSocket* sock, CNPacketData* data) { | ||||
|          * once we comb over mission logic more throughly | ||||
|          */ | ||||
|         bool mobsAreKilled = false; | ||||
|         if (task->task["m_iHTaskType"] == 5) { | ||||
|         if (task->task["m_iHTaskType"] == (int)eTaskTypeProperty::Defeat) { | ||||
|             mobsAreKilled = true; | ||||
|             for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) { | ||||
|                 if (plr->tasks[i] == missionData->iTaskNum) { | ||||
|   | ||||
| @@ -1,9 +1,11 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "servers/CNShardServer.hpp" | ||||
| #include "core/Core.hpp" | ||||
| #include "JSON.hpp" | ||||
|  | ||||
| #include "Player.hpp" | ||||
|  | ||||
| #include "JSON.hpp" | ||||
| #include <map> | ||||
|  | ||||
| struct Reward { | ||||
|     int32_t id; | ||||
|   | ||||
							
								
								
									
										665
									
								
								src/MobAI.cpp
									
									
									
									
									
								
							
							
						
						
									
										665
									
								
								src/MobAI.cpp
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,26 +1,26 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "core/Core.hpp" | ||||
| #include "NPCManager.hpp" | ||||
| #include "JSON.hpp" | ||||
|  | ||||
| enum class MobState { | ||||
|     INACTIVE, | ||||
|     ROAMING, | ||||
|     COMBAT, | ||||
|     RETREAT, | ||||
|     DEAD | ||||
| }; | ||||
| #include "Entities.hpp" | ||||
|  | ||||
| #include <unordered_map> | ||||
| #include <string> | ||||
|  | ||||
| namespace MobAI { | ||||
|     // needs to be declared before Mob's constructor | ||||
|     void step(CombatNPC*, time_t); | ||||
| }; | ||||
|     void deadStep(CombatNPC* self, time_t currTime); | ||||
|     void combatStep(CombatNPC* self, time_t currTime); | ||||
|     void roamingStep(CombatNPC* self, time_t currTime); | ||||
|     void retreatStep(CombatNPC* self, time_t currTime); | ||||
|  | ||||
|     void onRoamStart(CombatNPC* self, EntityRef src); | ||||
|     void onCombatStart(CombatNPC* self, EntityRef src); | ||||
|     void onRetreat(CombatNPC* self, EntityRef src); | ||||
|     void onDeath(CombatNPC* self, EntityRef src); | ||||
| } | ||||
|  | ||||
| struct Mob : public CombatNPC { | ||||
|     // general | ||||
|     MobState state = MobState::INACTIVE; | ||||
|  | ||||
|     std::unordered_map<int32_t,time_t> unbuffTimes = {}; | ||||
|  | ||||
|     // dead | ||||
|     time_t killedTime = 0; | ||||
| @@ -47,16 +47,13 @@ struct Mob : public CombatNPC { | ||||
|     int offsetX = 0, offsetY = 0; | ||||
|     int groupMember[4] = {}; | ||||
|  | ||||
|     // for optimizing away AI in empty chunks | ||||
|     int playersInView = 0; | ||||
|  | ||||
|     // temporary; until we're sure what's what | ||||
|     nlohmann::json data = {}; | ||||
|  | ||||
|     Mob(int x, int y, int z, int angle, uint64_t iID, int t, nlohmann::json d, int32_t id) | ||||
|         : CombatNPC(x, y, z, angle, iID, t, id, d["m_iHP"]), | ||||
|     Mob(int spawnX, int spawnY, int spawnZ, int angle, uint64_t iID, int t, nlohmann::json d, int32_t id) | ||||
|         : CombatNPC(spawnX, spawnY, spawnZ, angle, iID, t, id, d["m_iHP"]), | ||||
|           sightRange(d["m_iSightRange"]) { | ||||
|         state = MobState::ROAMING; | ||||
|         state = AIState::ROAMING; | ||||
|  | ||||
|         data = d; | ||||
|  | ||||
| @@ -65,20 +62,28 @@ struct Mob : public CombatNPC { | ||||
|         idleRange = (int)data["m_iIdleRange"]; | ||||
|         level = data["m_iNpcLevel"]; | ||||
|  | ||||
|         roamX = spawnX = x; | ||||
|         roamY = spawnY = y; | ||||
|         roamZ = spawnZ = z; | ||||
|         roamX = spawnX; | ||||
|         roamY = spawnY; | ||||
|         roamZ = spawnZ; | ||||
|  | ||||
|         offsetX = 0; | ||||
|         offsetY = 0; | ||||
|  | ||||
|         appearanceData.iConditionBitFlag = 0; | ||||
|  | ||||
|         // NOTE: there appear to be discrepancies in the dump | ||||
|         appearanceData.iHP = maxHealth; | ||||
|         hp = maxHealth; | ||||
|  | ||||
|         type = EntityType::MOB; | ||||
|         _stepAI = MobAI::step; | ||||
|         kind = EntityKind::MOB; | ||||
|  | ||||
|         // AI | ||||
|         stateHandlers[AIState::DEAD] = MobAI::deadStep; | ||||
|         stateHandlers[AIState::COMBAT] = MobAI::combatStep; | ||||
|         stateHandlers[AIState::ROAMING] = MobAI::roamingStep; | ||||
|         stateHandlers[AIState::RETREAT] = MobAI::retreatStep; | ||||
|  | ||||
|         transitionHandlers[AIState::DEAD] = MobAI::onDeath; | ||||
|         transitionHandlers[AIState::COMBAT] = MobAI::onCombatStart; | ||||
|         transitionHandlers[AIState::ROAMING] = MobAI::onRoamStart; | ||||
|         transitionHandlers[AIState::RETREAT] = MobAI::onRetreat; | ||||
|     } | ||||
|  | ||||
|     // constructor for /summon | ||||
| @@ -89,6 +94,9 @@ struct Mob : public CombatNPC { | ||||
|  | ||||
|     ~Mob() {} | ||||
|  | ||||
|     virtual int takeDamage(EntityRef src, int amt) override; | ||||
|     virtual void step(time_t currTime) override; | ||||
|  | ||||
|     auto operator[](std::string s) { | ||||
|         return data[s]; | ||||
|     } | ||||
| @@ -103,5 +111,4 @@ namespace MobAI { | ||||
|     void clearDebuff(Mob *mob); | ||||
|     void followToCombat(Mob *mob); | ||||
|     void groupRetreat(Mob *mob); | ||||
|     void enterCombat(CNSocket *sock, Mob *mob); | ||||
| } | ||||
|   | ||||
| @@ -1,4 +0,0 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "Chunking.hpp" | ||||
| #include "Entities.hpp" | ||||
| @@ -1,4 +1,8 @@ | ||||
| #include "NPCManager.hpp" | ||||
|  | ||||
| #include "servers/CNShardServer.hpp" | ||||
|  | ||||
| #include "PlayerManager.hpp" | ||||
| #include "Items.hpp" | ||||
| #include "settings.hpp" | ||||
| #include "Combat.hpp" | ||||
| @@ -20,8 +24,6 @@ | ||||
| #include <assert.h> | ||||
| #include <limits.h> | ||||
|  | ||||
| #include "JSON.hpp" | ||||
|  | ||||
| using namespace NPCManager; | ||||
|  | ||||
| std::unordered_map<int32_t, BaseNPC*> NPCManager::NPCs; | ||||
| @@ -67,7 +69,7 @@ void NPCManager::destroyNPC(int32_t id) { | ||||
|  | ||||
| void NPCManager::updateNPCPosition(int32_t id, int X, int Y, int Z, uint64_t I, int angle) { | ||||
|     BaseNPC* npc = NPCs[id]; | ||||
|     npc->appearanceData.iAngle = angle; | ||||
|     npc->angle = angle; | ||||
|     ChunkPos oldChunk = npc->chunkPos; | ||||
|     ChunkPos newChunk = Chunking::chunkPosAt(X, Y, I); | ||||
|     npc->x = X; | ||||
| @@ -79,11 +81,11 @@ void NPCManager::updateNPCPosition(int32_t id, int X, int Y, int Z, uint64_t I, | ||||
|     Chunking::updateEntityChunk({id}, oldChunk, newChunk); | ||||
| } | ||||
|  | ||||
| void NPCManager::sendToViewable(BaseNPC *npc, void *buf, uint32_t type, size_t size) { | ||||
| void NPCManager::sendToViewable(Entity *npc, void *buf, uint32_t type, size_t size) { | ||||
|     for (auto it = npc->viewableChunks.begin(); it != npc->viewableChunks.end(); it++) { | ||||
|         Chunk* chunk = *it; | ||||
|         for (const EntityRef& ref : chunk->entities) { | ||||
|             if (ref.type == EntityType::PLAYER) | ||||
|             if (ref.kind == EntityKind::PLAYER) | ||||
|                 ref.sock->sendPacket(buf, type, size); | ||||
|         } | ||||
|     } | ||||
| @@ -120,22 +122,20 @@ static void npcUnsummonHandler(CNSocket* sock, CNPacketData* data) { | ||||
| } | ||||
|  | ||||
| // type must already be checked and updateNPCPosition() must be called on the result | ||||
| BaseNPC *NPCManager::summonNPC(int x, int y, int z, uint64_t instance, int type, bool respawn, bool baseInstance) { | ||||
| BaseNPC *NPCManager::summonNPC(int spawnX, int spawnY, int spawnZ, uint64_t instance, int type, bool respawn, bool baseInstance) { | ||||
|     uint64_t inst = baseInstance ? MAPNUM(instance) : instance; | ||||
| #define EXTRA_HEIGHT 0 | ||||
|  | ||||
|     //assert(nextId < INT32_MAX); | ||||
|     int id = nextId--; | ||||
|     int team = NPCData[type]["m_iTeam"]; | ||||
|     BaseNPC *npc = nullptr; | ||||
|  | ||||
|     if (team == 2) { | ||||
|         npc = new Mob(x, y, z + EXTRA_HEIGHT, inst, type, NPCData[type], id); | ||||
|         npc = new Mob(spawnX, spawnY, spawnZ, inst, type, NPCData[type], id); | ||||
|  | ||||
|         // re-enable respawning, if desired | ||||
|         ((Mob*)npc)->summoned = !respawn; | ||||
|     } else | ||||
|         npc = new BaseNPC(x, y, z + EXTRA_HEIGHT, 0, inst, type, id); | ||||
|         npc = new BaseNPC(0, inst, type, id); | ||||
|  | ||||
|     NPCs[id] = npc; | ||||
|  | ||||
| @@ -154,7 +154,7 @@ static void npcSummonHandler(CNSocket* sock, CNPacketData* data) { | ||||
|  | ||||
|     for (int i = 0; i < req->iNPCCnt; i++) { | ||||
|         BaseNPC *npc = summonNPC(plr->x, plr->y, plr->z, plr->instanceID, req->iNPCType); | ||||
|         updateNPCPosition(npc->appearanceData.iNPC_ID, plr->x, plr->y, plr->z, plr->instanceID, 0); | ||||
|         updateNPCPosition(npc->id, plr->x, plr->y, plr->z, plr->instanceID, 0); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -183,9 +183,12 @@ static void handleWarp(CNSocket* sock, int32_t warpId) { | ||||
|     if (Warps[warpId].isInstance) { | ||||
|         uint64_t instanceID = Warps[warpId].instanceID; | ||||
|  | ||||
|         Player* leader = plr; | ||||
|         if (plr->group != nullptr) leader = PlayerManager::getPlayer(plr->group->filter(EntityKind::PLAYER)[0].sock); | ||||
|  | ||||
|         // if warp requires you to be on a mission, it's gotta be a unique instance | ||||
|         if (Warps[warpId].limitTaskID != 0 || instanceID == 14) { // 14 is a special case for the Time Lab | ||||
|             instanceID += ((uint64_t)plr->iIDGroup << 32); // upper 32 bits are leader ID | ||||
|             instanceID += ((uint64_t)leader->iID << 32); // upper 32 bits are leader ID | ||||
|             Chunking::createInstance(instanceID); | ||||
|  | ||||
|             // save Lair entrance coords as a pseudo-Resurrect 'Em | ||||
| @@ -195,14 +198,13 @@ static void handleWarp(CNSocket* sock, int32_t warpId) { | ||||
|             plr->recallInstance = instanceID; | ||||
|         } | ||||
|  | ||||
|         if (plr->iID == plr->iIDGroup && plr->groupCnt == 1) | ||||
|         if (plr->group == nullptr) | ||||
|             PlayerManager::sendPlayerTo(sock, Warps[warpId].x, Warps[warpId].y, Warps[warpId].z, instanceID); | ||||
|         else { | ||||
|             Player* leaderPlr = PlayerManager::getPlayerFromID(plr->iIDGroup); | ||||
|  | ||||
|             for (int i = 0; i < leaderPlr->groupCnt; i++) { | ||||
|                 Player* otherPlr = PlayerManager::getPlayerFromID(leaderPlr->groupIDs[i]); | ||||
|                 CNSocket* sockTo = PlayerManager::getSockFromID(leaderPlr->groupIDs[i]); | ||||
|             auto players = plr->group->filter(EntityKind::PLAYER); | ||||
|             for (int i = 0; i < players.size(); i++) { | ||||
|                 CNSocket* sockTo = players[i].sock; | ||||
|                 Player* otherPlr = PlayerManager::getPlayer(sockTo); | ||||
|  | ||||
|                 if (otherPlr == nullptr || sockTo == nullptr) | ||||
|                     continue; | ||||
| @@ -276,7 +278,7 @@ BaseNPC* NPCManager::getNearestNPC(std::set<Chunk*>* chunks, int X, int Y, int Z | ||||
|     for (auto c = chunks->begin(); c != chunks->end(); c++) { // haha get it | ||||
|         Chunk* chunk = *c; | ||||
|         for (auto ent = chunk->entities.begin(); ent != chunk->entities.end(); ent++) { | ||||
|             if (ent->type == EntityType::PLAYER) | ||||
|             if (ent->kind == EntityKind::PLAYER) | ||||
|                 continue; | ||||
|  | ||||
|             BaseNPC* npcTemp = (BaseNPC*)ent->getEntity(); | ||||
| @@ -291,57 +293,55 @@ BaseNPC* NPCManager::getNearestNPC(std::set<Chunk*>* chunks, int X, int Y, int Z | ||||
|     return npc; | ||||
| } | ||||
|  | ||||
| // TODO: Move this to MobAI, possibly | ||||
| // TODO: Move this to separate file in ai/ subdir when implementing more events | ||||
| #pragma region NPCEvents | ||||
|  | ||||
| // summon right arm and stage 2 body | ||||
| static void lordFuseStageTwo(CNSocket *sock, BaseNPC *npc) { | ||||
| static void lordFuseStageTwo(CombatNPC *npc) { | ||||
|     Mob *oldbody = (Mob*)npc; // adaptium, stun | ||||
|     Player *plr = PlayerManager::getPlayer(sock); | ||||
|  | ||||
|     std::cout << "Lord Fuse stage two" << std::endl; | ||||
|  | ||||
|     // Fuse doesn't move; spawnX, etc. is shorter to write than *appearanceData* | ||||
|     // Fuse doesn't move | ||||
|     // Blastons, Heal | ||||
|     Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->spawnX, oldbody->spawnY, oldbody->spawnZ, plr->instanceID, 2467); | ||||
|     Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->x, oldbody->y, oldbody->z, oldbody->instanceID, 2467); | ||||
|  | ||||
|     newbody->appearanceData.iAngle = oldbody->appearanceData.iAngle; | ||||
|     NPCManager::updateNPCPosition(newbody->appearanceData.iNPC_ID, newbody->spawnX, newbody->spawnY, newbody->spawnZ, | ||||
|         plr->instanceID, oldbody->appearanceData.iAngle); | ||||
|     newbody->angle = oldbody->angle; | ||||
|     NPCManager::updateNPCPosition(newbody->id, newbody->spawnX, newbody->spawnY, newbody->spawnZ, | ||||
|         oldbody->instanceID, oldbody->angle); | ||||
|  | ||||
|     // right arm, Adaptium, Stun | ||||
|     Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->spawnX - 600, oldbody->spawnY, oldbody->spawnZ, plr->instanceID, 2469); | ||||
|     Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->x - 600, oldbody->y, oldbody->z, oldbody->instanceID, 2469); | ||||
|  | ||||
|     arm->appearanceData.iAngle = oldbody->appearanceData.iAngle; | ||||
|     NPCManager::updateNPCPosition(arm->appearanceData.iNPC_ID, arm->spawnX, arm->spawnY, arm->spawnZ, | ||||
|         plr->instanceID, oldbody->appearanceData.iAngle); | ||||
|     arm->angle = oldbody->angle; | ||||
|     NPCManager::updateNPCPosition(arm->id, arm->spawnX, arm->spawnY, arm->spawnZ, | ||||
|         oldbody->instanceID, oldbody->angle); | ||||
| } | ||||
|  | ||||
| // summon left arm and stage 3 body | ||||
| static void lordFuseStageThree(CNSocket *sock, BaseNPC *npc) { | ||||
| static void lordFuseStageThree(CombatNPC *npc) { | ||||
|     Mob *oldbody = (Mob*)npc; | ||||
|     Player *plr = PlayerManager::getPlayer(sock); | ||||
|  | ||||
|     std::cout << "Lord Fuse stage three" << std::endl; | ||||
|  | ||||
|     // Cosmix, Damage Point | ||||
|     Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->spawnX, oldbody->spawnY, oldbody->spawnZ, plr->instanceID, 2468); | ||||
|     Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->x, oldbody->y, oldbody->z, oldbody->instanceID, 2468); | ||||
|  | ||||
|     newbody->appearanceData.iAngle = oldbody->appearanceData.iAngle; | ||||
|     NPCManager::updateNPCPosition(newbody->appearanceData.iNPC_ID, newbody->spawnX, newbody->spawnY, newbody->spawnZ, | ||||
|         plr->instanceID, oldbody->appearanceData.iAngle); | ||||
|     newbody->angle = oldbody->angle; | ||||
|     NPCManager::updateNPCPosition(newbody->id, newbody->spawnX, newbody->spawnY, newbody->spawnZ, | ||||
|         newbody->instanceID, oldbody->angle); | ||||
|  | ||||
|     // Blastons, Heal | ||||
|     Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->spawnX + 600, oldbody->spawnY, oldbody->spawnZ, plr->instanceID, 2470); | ||||
|     Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->x + 600, oldbody->y, oldbody->z, oldbody->instanceID, 2470); | ||||
|  | ||||
|     arm->appearanceData.iAngle = oldbody->appearanceData.iAngle; | ||||
|     NPCManager::updateNPCPosition(arm->appearanceData.iNPC_ID, arm->spawnX, arm->spawnY, arm->spawnZ, | ||||
|         plr->instanceID, oldbody->appearanceData.iAngle); | ||||
|     arm->angle = oldbody->angle; | ||||
|     NPCManager::updateNPCPosition(arm->id, arm->spawnX, arm->spawnY, arm->spawnZ, | ||||
|         arm->instanceID, oldbody->angle); | ||||
| } | ||||
|  | ||||
| std::vector<NPCEvent> NPCManager::NPCEvents = { | ||||
|     NPCEvent(2466, ON_KILLED, lordFuseStageTwo), | ||||
|     NPCEvent(2467, ON_KILLED, lordFuseStageThree), | ||||
|     NPCEvent(2466, AIState::DEAD, lordFuseStageTwo), | ||||
|     NPCEvent(2467, AIState::DEAD, lordFuseStageThree), | ||||
| }; | ||||
|  | ||||
| #pragma endregion NPCEvents | ||||
| @@ -352,11 +352,11 @@ void NPCManager::queueNPCRemoval(int32_t id) { | ||||
|  | ||||
| static void step(CNServer *serv, time_t currTime) { | ||||
|     for (auto& pair : NPCs) { | ||||
|         if (pair.second->type != EntityType::COMBAT_NPC && pair.second->type != EntityType::MOB) | ||||
|         if (pair.second->kind != EntityKind::COMBAT_NPC && pair.second->kind != EntityKind::MOB) | ||||
|             continue; | ||||
|         auto npc = (CombatNPC*)pair.second; | ||||
|  | ||||
|         npc->stepAI(currTime); | ||||
|         npc->step(currTime); | ||||
|     } | ||||
|  | ||||
|     // deallocate all NPCs queued for removal | ||||
| @@ -373,5 +373,5 @@ void NPCManager::init() { | ||||
|     REGISTER_SHARD_PACKET(P_CL2FE_REQ_NPC_UNSUMMON, npcUnsummonHandler); | ||||
|     REGISTER_SHARD_PACKET(P_CL2FE_REQ_BARKER, npcBarkHandler); | ||||
|  | ||||
|     REGISTER_SHARD_TIMER(step, 200); | ||||
|     REGISTER_SHARD_TIMER(step, MS_PER_COMBAT_TICK); | ||||
| } | ||||
|   | ||||
| @@ -1,36 +1,30 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "core/Core.hpp" | ||||
| #include "PlayerManager.hpp" | ||||
| #include "NPC.hpp" | ||||
| #include "Transport.hpp" | ||||
|  | ||||
| #include "JSON.hpp" | ||||
|  | ||||
| #include "Transport.hpp" | ||||
| #include "Chunking.hpp" | ||||
| #include "Entities.hpp" | ||||
|  | ||||
| #include <map> | ||||
| #include <unordered_map> | ||||
| #include <vector> | ||||
| #include <set> | ||||
|  | ||||
| #define RESURRECT_HEIGHT 400 | ||||
|  | ||||
| enum Trigger { | ||||
|     ON_KILLED, | ||||
|     ON_COMBAT | ||||
| }; | ||||
|  | ||||
| typedef void (*NPCEventHandler)(CNSocket*, BaseNPC*); | ||||
| typedef void (*NPCEventHandler)(CombatNPC*); | ||||
|  | ||||
| struct NPCEvent { | ||||
|     int32_t npcType; | ||||
|     int trigger; | ||||
|     AIState triggerState; | ||||
|     NPCEventHandler handler; | ||||
|  | ||||
|     NPCEvent(int32_t t, int tr, NPCEventHandler hndlr) | ||||
|         : npcType(t), trigger(tr), handler(hndlr) {} | ||||
|     NPCEvent(int32_t t, AIState tr, NPCEventHandler hndlr) | ||||
|         : npcType(t), triggerState(tr), handler(hndlr) {} | ||||
| }; | ||||
|  | ||||
| struct WarpLocation; | ||||
|  | ||||
| namespace NPCManager { | ||||
|     extern std::unordered_map<int32_t, BaseNPC*> NPCs; | ||||
|     extern std::map<int32_t, WarpLocation> Warps; | ||||
| @@ -44,7 +38,7 @@ namespace NPCManager { | ||||
|     void destroyNPC(int32_t); | ||||
|     void updateNPCPosition(int32_t, int X, int Y, int Z, uint64_t I, int angle); | ||||
|  | ||||
|     void sendToViewable(BaseNPC* npc, void* buf, uint32_t type, size_t size); | ||||
|     void sendToViewable(Entity* npc, void* buf, uint32_t type, size_t size); | ||||
|  | ||||
|     BaseNPC *summonNPC(int x, int y, int z, uint64_t instance, int type, bool respawn=false, bool baseInstance=false); | ||||
|  | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
| #include "servers/CNShardServer.hpp" | ||||
| #include "Nanos.hpp" | ||||
|  | ||||
| #include "servers/CNShardServer.hpp" | ||||
|  | ||||
| #include "PlayerManager.hpp" | ||||
| #include "NPCManager.hpp" | ||||
| #include "Combat.hpp" | ||||
| #include "Missions.hpp" | ||||
| #include "Groups.hpp" | ||||
| #include "Abilities.hpp" | ||||
| #include "NPCManager.hpp" | ||||
|  | ||||
| #include <cmath> | ||||
|  | ||||
| @@ -82,40 +82,21 @@ void Nanos::summonNano(CNSocket *sock, int slot, bool silent) { | ||||
|     if (slot != -1 && plr->Nanos[nanoID].iSkillID == 0) | ||||
|         return; // prevent powerless nanos from summoning | ||||
|  | ||||
|     plr->nanoDrainRate = 0; | ||||
|     int16_t skillID = plr->Nanos[plr->activeNano].iSkillID; | ||||
|  | ||||
|     // passive nano unbuffing | ||||
|     if (SkillTable[skillID].drainType == 2) { | ||||
|         std::vector<int> targetData = findTargets(plr, skillID); | ||||
|  | ||||
|         for (auto& pwr : NanoPowers) | ||||
|             if (pwr.skillType == SkillTable[skillID].skillType) | ||||
|                 nanoUnbuff(sock, targetData, pwr.bitFlag, pwr.timeBuffID, 0,(SkillTable[skillID].targetType == 3)); | ||||
|     } | ||||
|  | ||||
|     if (nanoID >= NANO_COUNT || nanoID < 0) | ||||
|         return; // sanity check | ||||
|  | ||||
|     plr->activeNano = nanoID; | ||||
|     skillID = plr->Nanos[nanoID].iSkillID; | ||||
|     sNano& nano = plr->Nanos[nanoID]; | ||||
|  | ||||
|     // passive nano buffing | ||||
|     if (SkillTable[skillID].drainType == 2) { | ||||
|         std::vector<int> targetData = findTargets(plr, skillID); | ||||
|  | ||||
|         int boost = 0; | ||||
|         if (getNanoBoost(plr)) | ||||
|             boost = 1; | ||||
|  | ||||
|         for (auto& pwr : NanoPowers) { | ||||
|             if (pwr.skillType == SkillTable[skillID].skillType) { | ||||
|                 resp.eCSTB___Add = 1; // the part that makes nano go ZOOMAZOOM | ||||
|                 plr->nanoDrainRate = SkillTable[skillID].batteryUse[boost*3]; | ||||
|  | ||||
|                 pwr.handle(sock, targetData, nanoID, skillID, 0, SkillTable[skillID].powerIntensity[boost]); | ||||
|             } | ||||
|         } | ||||
|     SkillData* skill = Abilities::SkillTable.count(nano.iSkillID) > 0 | ||||
|                         ? &Abilities::SkillTable[nano.iSkillID] : nullptr; | ||||
|     if (slot != -1 && skill != nullptr && skill->drainType == SkillDrainType::PASSIVE) { | ||||
|         // passive buff effect | ||||
|         resp.eCSTB___Add = 1; | ||||
|         ICombatant* src = dynamic_cast<ICombatant*>(plr); | ||||
|         int32_t targets[] = { plr->iID }; | ||||
|         std::vector<ICombatant*> affectedCombatants = Abilities::matchTargets(src, skill, 1, targets); | ||||
|         Abilities::useNanoSkill(sock, skill, nano, affectedCombatants); | ||||
|     } | ||||
|  | ||||
|     if (!silent) // silent nano death but only for the summoning player | ||||
| @@ -124,7 +105,7 @@ void Nanos::summonNano(CNSocket *sock, int slot, bool silent) { | ||||
|     // Send to other players, these players can't handle silent nano deaths so this packet needs to be sent. | ||||
|     INITSTRUCT(sP_FE2CL_NANO_ACTIVE, pkt1); | ||||
|     pkt1.iPC_ID = plr->iID; | ||||
|     pkt1.Nano = plr->Nanos[nanoID]; | ||||
|     pkt1.Nano = nano; | ||||
|     PlayerManager::sendToViewable(sock, pkt1, P_FE2CL_NANO_ACTIVE); | ||||
| } | ||||
|  | ||||
| @@ -211,7 +192,7 @@ int Nanos::nanoStyle(int nanoID) { | ||||
| bool Nanos::getNanoBoost(Player* plr) { | ||||
|     for (int i = 0; i < 3; i++)  | ||||
|         if (plr->equippedNanos[i] == plr->activeNano) | ||||
|             if (plr->iConditionBitFlag & (CSB_BIT_STIMPAKSLOT1 << i)) | ||||
|             if (plr->hasBuff(ECSB_STIMPAKSLOT1 + i)) | ||||
|                 return true; | ||||
|     return false; | ||||
| } | ||||
| @@ -235,18 +216,6 @@ static void nanoEquipHandler(CNSocket* sock, CNPacketData* data) { | ||||
|     // Update player | ||||
|     plr->equippedNanos[nano->iNanoSlotNum] = nano->iNanoID; | ||||
|  | ||||
|     // Unbuff gumballs | ||||
|     int value1 = CSB_BIT_STIMPAKSLOT1 << nano->iNanoSlotNum; | ||||
|     if (plr->iConditionBitFlag & value1) { | ||||
|         int value2 = ECSB_STIMPAKSLOT1 + nano->iNanoSlotNum; | ||||
|         INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt); | ||||
|         pkt.eCSTB = value2; // eCharStatusTimeBuffID | ||||
|         pkt.eTBU = 2; // eTimeBuffUpdate | ||||
|         pkt.eTBT = 1; // eTimeBuffType 1 means nano | ||||
|         pkt.iConditionBitFlag = plr->iConditionBitFlag &= ~value1; | ||||
|         sock->sendPacket(pkt, P_FE2CL_PC_BUFF_UPDATE); | ||||
|     } | ||||
|  | ||||
|     // unsummon nano if replaced | ||||
|     if (plr->activeNano == plr->equippedNanos[nano->iNanoSlotNum]) | ||||
|         summonNano(sock, -1); | ||||
| @@ -289,28 +258,26 @@ static void nanoSummonHandler(CNSocket* sock, CNPacketData* data) { | ||||
| static void nanoSkillUseHandler(CNSocket* sock, CNPacketData* data) { | ||||
|     Player *plr = PlayerManager::getPlayer(sock); | ||||
|  | ||||
|     int16_t nanoID = plr->activeNano; | ||||
|     int16_t skillID = plr->Nanos[nanoID].iSkillID; | ||||
|     // validate request check | ||||
|     sP_CL2FE_REQ_NANO_SKILL_USE* pkt = (sP_CL2FE_REQ_NANO_SKILL_USE*)data->buf; | ||||
|     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; | ||||
|     } | ||||
|  | ||||
|     sNano& nano = plr->Nanos[plr->activeNano]; | ||||
|     int16_t skillID = nano.iSkillID; | ||||
|     SkillData* skillData = &Abilities::SkillTable[skillID]; | ||||
|  | ||||
|     DEBUGLOG( | ||||
|         std::cout << PlayerManager::getPlayerName(plr) << " requested to summon nano skill " << std::endl; | ||||
|     ) | ||||
|  | ||||
|     std::vector<int> targetData = findTargets(plr, skillID, data); | ||||
|     ICombatant* plrCombatant = dynamic_cast<ICombatant*>(plr); | ||||
|     std::vector<ICombatant*> targetData = Abilities::matchTargets(plrCombatant, skillData, pkt->iTargetCnt, (int32_t*)(pkt + 1)); | ||||
|     Abilities::useNanoSkill(sock, skillData, nano, targetData); | ||||
|  | ||||
|     int boost = 0; | ||||
|     if (getNanoBoost(plr)) | ||||
|         boost = 1; | ||||
|  | ||||
|     plr->Nanos[plr->activeNano].iStamina -= SkillTable[skillID].batteryUse[boost*3]; | ||||
|     if (plr->Nanos[plr->activeNano].iStamina < 0) | ||||
|         plr->Nanos[plr->activeNano].iStamina = 0; | ||||
|  | ||||
|     for (auto& pwr : NanoPowers) | ||||
|         if (pwr.skillType == SkillTable[skillID].skillType) | ||||
|             pwr.handle(sock, targetData, nanoID, skillID, SkillTable[skillID].durationTime[boost], SkillTable[skillID].powerIntensity[boost]); | ||||
|  | ||||
|     if (plr->Nanos[plr->activeNano].iStamina < 0) | ||||
|     if (plr->Nanos[plr->activeNano].iStamina <= 0) | ||||
|         summonNano(sock, -1); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,10 +1,11 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <set> | ||||
| #include <vector> | ||||
| #include "core/Core.hpp" | ||||
|  | ||||
| #include "Player.hpp" | ||||
| #include "servers/CNShardServer.hpp" | ||||
| #include "Abilities.hpp" | ||||
|  | ||||
| #include <map> | ||||
|  | ||||
| struct NanoData { | ||||
|     int style; | ||||
|   | ||||
| @@ -1,17 +1,21 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <string> | ||||
| #include <cstring> | ||||
|  | ||||
| #include "core/Core.hpp" | ||||
| #include "Chunking.hpp" | ||||
|  | ||||
| #include "Entities.hpp" | ||||
| #include "Groups.hpp" | ||||
|  | ||||
| #include <vector> | ||||
|  | ||||
| /* forward declaration(s) */ | ||||
| class Buff; | ||||
| struct BuffStack; | ||||
|  | ||||
| #define ACTIVE_MISSION_COUNT 6 | ||||
|  | ||||
| #define PC_MAXHEALTH(level) (925 + 75 * (level)) | ||||
|  | ||||
| struct Player : public Entity { | ||||
| struct Player : public Entity, public ICombatant { | ||||
|     int accountId = 0; | ||||
|     int accountLevel = 0; // permission level (see CN_ACCOUNT_LEVEL enums) | ||||
|     int32_t iID = 0; | ||||
| @@ -32,9 +36,8 @@ struct Player : public Entity { | ||||
|     int8_t iPCState = 0; | ||||
|     int32_t iWarpLocationFlag = 0; | ||||
|     int64_t aSkywayLocationFlag[2] = {}; | ||||
|     int32_t iConditionBitFlag = 0; | ||||
|     int32_t iSelfConditionBitFlag = 0; | ||||
|     int8_t iSpecialState = 0; | ||||
|     std::unordered_map<int, Buff*> buffs = {}; | ||||
|  | ||||
|     int angle = 0; | ||||
|     int lastX = 0, lastY = 0, lastZ = 0, lastAngle = 0; | ||||
| @@ -49,7 +52,6 @@ struct Player : public Entity { | ||||
|  | ||||
|     bool inCombat = false; | ||||
|     bool onMonkey = false; | ||||
|     int nanoDrainRate = 0; | ||||
|     int healCooldown = 0; | ||||
|  | ||||
|     int pointDamage = 0; | ||||
| @@ -65,10 +67,7 @@ struct Player : public Entity { | ||||
|  | ||||
|     sTimeLimitItemDeleteInfo2CL toRemoveVehicle = {}; | ||||
|  | ||||
|     int32_t iIDGroup = 0; | ||||
|     int groupCnt = 0; | ||||
|     int32_t groupIDs[4] = {}; | ||||
|     int32_t iGroupConditionBitFlag = 0; | ||||
|     Group* group = nullptr; | ||||
|  | ||||
|     bool notify = false; | ||||
|     bool hidden = false; | ||||
| @@ -85,8 +84,31 @@ struct Player : public Entity { | ||||
|     time_t lastShot = 0; | ||||
|     std::vector<sItemBase> buyback = {}; | ||||
|  | ||||
|     Player() { type = EntityType::PLAYER; } | ||||
|     Player() { kind = EntityKind::PLAYER; } | ||||
|  | ||||
|     virtual void enterIntoViewOf(CNSocket *sock) override; | ||||
|     virtual void disappearFromViewOf(CNSocket *sock) override; | ||||
|  | ||||
|     virtual bool addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) override; | ||||
|     virtual Buff* getBuff(int buffId) override; | ||||
|     virtual void removeBuff(int buffId) override; | ||||
|     virtual void removeBuff(int buffId, BuffClass buffClass) override; | ||||
|     virtual void clearBuffs(bool force) override; | ||||
|     virtual bool hasBuff(int buffId) override; | ||||
|     virtual int getCompositeCondition() override; | ||||
|     virtual int takeDamage(EntityRef src, int amt) override; | ||||
|     virtual int heal(EntityRef src, int amt) override; | ||||
|     virtual bool isAlive() override; | ||||
|     virtual int getCurrentHP() override; | ||||
|     virtual int getMaxHP() override; | ||||
|     virtual int getLevel() override; | ||||
|     virtual std::vector<EntityRef> getGroupMembers() override; | ||||
|     virtual int32_t getCharType() override; | ||||
|     virtual int32_t getID() override; | ||||
|     virtual EntityRef getRef() override; | ||||
|  | ||||
|     virtual void step(time_t currTime) override; | ||||
|  | ||||
|     sNano* getActiveNano(); | ||||
|     sPCAppearanceData getAppearanceData(); | ||||
| }; | ||||
|   | ||||
| @@ -1,25 +1,20 @@ | ||||
| #include "core/Core.hpp" | ||||
| #include "PlayerManager.hpp" | ||||
|  | ||||
| #include "db/Database.hpp" | ||||
| #include "core/CNShared.hpp" | ||||
| #include "servers/CNShardServer.hpp" | ||||
| #include "db/Database.hpp" | ||||
| #include "PlayerManager.hpp" | ||||
|  | ||||
| #include "NPCManager.hpp" | ||||
| #include "Missions.hpp" | ||||
| #include "Items.hpp" | ||||
| #include "Nanos.hpp" | ||||
| #include "Groups.hpp" | ||||
| #include "Chat.hpp" | ||||
| #include "Buddies.hpp" | ||||
| #include "Combat.hpp" | ||||
| #include "Racing.hpp" | ||||
| #include "BuiltinCommands.hpp" | ||||
| #include "Abilities.hpp" | ||||
| #include "Eggs.hpp" | ||||
|  | ||||
| #include "settings.hpp" | ||||
| #include "Missions.hpp" | ||||
| #include "Chat.hpp" | ||||
| #include "Items.hpp" | ||||
| #include "Buddies.hpp" | ||||
| #include "BuiltinCommands.hpp" | ||||
|  | ||||
| #include <assert.h> | ||||
|  | ||||
| #include <algorithm> | ||||
| #include <vector> | ||||
| #include <cmath> | ||||
| @@ -41,7 +36,13 @@ void PlayerManager::removePlayer(CNSocket* key) { | ||||
|     Player* plr = getPlayer(key); | ||||
|     uint64_t fromInstance = plr->instanceID; | ||||
|  | ||||
|     Groups::groupKickPlayer(plr); | ||||
|     // free buff memory | ||||
|     for(auto buffEntry : plr->buffs) | ||||
|         delete buffEntry.second; | ||||
|  | ||||
|     // leave group | ||||
|     if(plr->group != nullptr) | ||||
|         Groups::groupKick(plr->group, key); | ||||
|  | ||||
|     // remove player's bullets | ||||
|     Combat::Bullets.erase(plr->iID); | ||||
| @@ -65,16 +66,6 @@ void PlayerManager::removePlayer(CNSocket* key) { | ||||
|     // if the player was in a lair, clean it up | ||||
|     Chunking::destroyInstanceIfEmpty(fromInstance); | ||||
|  | ||||
|     // remove player's buffs from the server | ||||
|     auto it = Eggs::EggBuffs.begin(); | ||||
|     while (it != Eggs::EggBuffs.end()) { | ||||
|         if (it->first.first == key) { | ||||
|             it = Eggs::EggBuffs.erase(it); | ||||
|         } | ||||
|         else | ||||
|             it++; | ||||
|     } | ||||
|  | ||||
|     std::cout << players.size() << " players" << std::endl; | ||||
| } | ||||
|  | ||||
| @@ -232,8 +223,7 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) { | ||||
|         Database::getPlayer(plr, lm->playerId); | ||||
|     } | ||||
|  | ||||
|     plr->groupCnt = 1; | ||||
|     plr->iIDGroup = plr->groupIDs[0] = plr->iID; | ||||
|     plr->group = nullptr; | ||||
|  | ||||
|     response.iID = plr->iID; | ||||
|     response.uiSvrTime = getTime(); | ||||
| @@ -348,12 +338,20 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) { | ||||
|     delete lm; | ||||
| } | ||||
|  | ||||
| void PlayerManager::sendToGroup(CNSocket* sock, void* buf, uint32_t type, size_t size) { | ||||
|     Player* plr = getPlayer(sock); | ||||
|     if (plr->group == nullptr) | ||||
|         return; | ||||
|     for(const EntityRef& ref : plr->group->filter(EntityKind::PLAYER)) | ||||
|         ref.sock->sendPacket(buf, type, size); | ||||
| } | ||||
|  | ||||
| void PlayerManager::sendToViewable(CNSocket* sock, void* buf, uint32_t type, size_t size) { | ||||
|     Player* plr = getPlayer(sock); | ||||
|     for (auto it = plr->viewableChunks.begin(); it != plr->viewableChunks.end(); it++) { | ||||
|         Chunk* chunk = *it; | ||||
|         for (const EntityRef& ref : chunk->entities) { | ||||
|             if (ref.type != EntityType::PLAYER || ref.sock == sock) | ||||
|             if (ref.kind != EntityKind::PLAYER || ref.sock == sock) | ||||
|                 continue; | ||||
|  | ||||
|             ref.sock->sendPacket(buf, type, size); | ||||
| @@ -406,21 +404,23 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) { | ||||
|     int activeSlot = -1; | ||||
|     bool move = false; | ||||
|  | ||||
|     if (reviveData->iRegenType == 3 && plr->iConditionBitFlag & CSB_BIT_PHOENIX) { | ||||
|         // nano revive | ||||
|     switch ((ePCRegenType)reviveData->iRegenType) { | ||||
|     case ePCRegenType::HereByPhoenix: // nano revive | ||||
|         if (!(plr->hasBuff(ECSB_PHOENIX))) | ||||
|             return; // sanity check | ||||
|         plr->Nanos[plr->activeNano].iStamina = 0; | ||||
|         // fallthrough | ||||
|     case ePCRegenType::HereByPhoenixGroup: // revived by group member's nano | ||||
|         plr->HP = PC_MAXHEALTH(plr->level) / 2; | ||||
|         Nanos::applyBuff(sock, plr->Nanos[plr->activeNano].iSkillID, 2, 1, 0); | ||||
|     } else if (reviveData->iRegenType == 4) { | ||||
|         // revived by group member's nano | ||||
|         break; | ||||
|  | ||||
|     default: // plain respawn | ||||
|         plr->HP = PC_MAXHEALTH(plr->level) / 2; | ||||
|     } else if (reviveData->iRegenType == 5) { | ||||
|         // warp away | ||||
|         plr->clearBuffs(false); | ||||
|         // fallthrough | ||||
|     case ePCRegenType::Unstick: // warp away | ||||
|         move = true; | ||||
|     } else { | ||||
|         // plain respawn | ||||
|         move = true; | ||||
|         plr->HP = PC_MAXHEALTH(plr->level) / 2; | ||||
|         break; | ||||
|     } | ||||
|  | ||||
|     for (int i = 0; i < 3; i++) { | ||||
| @@ -472,11 +472,9 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) { | ||||
|     resp2.PCRegenDataForOtherPC.iHP = plr->HP; | ||||
|     resp2.PCRegenDataForOtherPC.iAngle = plr->angle; | ||||
|  | ||||
|     Player *otherPlr = getPlayerFromID(plr->iIDGroup); | ||||
|     if (otherPlr != nullptr) { | ||||
|         int bitFlag = Groups::getGroupFlags(otherPlr); | ||||
|         resp2.PCRegenDataForOtherPC.iConditionBitFlag = plr->iConditionBitFlag = plr->iSelfConditionBitFlag | bitFlag; | ||||
|  | ||||
|     if (plr->group != nullptr) { | ||||
|          | ||||
|         resp2.PCRegenDataForOtherPC.iConditionBitFlag = plr->getCompositeCondition(); | ||||
|         resp2.PCRegenDataForOtherPC.iPCState = plr->iPCState; | ||||
|         resp2.PCRegenDataForOtherPC.iSpecialState = plr->iSpecialState; | ||||
|         resp2.PCRegenDataForOtherPC.Nano = plr->Nanos[plr->activeNano]; | ||||
| @@ -667,16 +665,16 @@ CNSocket *PlayerManager::getSockFromName(std::string firstname, std::string last | ||||
| } | ||||
|  | ||||
| CNSocket *PlayerManager::getSockFromAny(int by, int id, int uid, std::string firstname, std::string lastname) { | ||||
|     switch (by) { | ||||
|     case eCN_GM_TargetSearchBy__PC_ID: | ||||
|     switch ((eCN_GM_TargetSearchBy)by) { | ||||
|     case eCN_GM_TargetSearchBy::PC_ID: | ||||
|         assert(id != 0); | ||||
|         return getSockFromID(id); | ||||
|     case eCN_GM_TargetSearchBy__PC_UID: // account id; not player id | ||||
|     case eCN_GM_TargetSearchBy::PC_UID: // account id; not player id | ||||
|         assert(uid != 0); | ||||
|         for (auto& pair : players) | ||||
|             if (pair.second->accountId == uid) | ||||
|                 return pair.first; | ||||
|     case eCN_GM_TargetSearchBy__PC_Name: | ||||
|     case eCN_GM_TargetSearchBy::PC_Name: | ||||
|         assert(firstname != "" && lastname != ""); // XXX: remove this if we start messing around with edited names? | ||||
|         return getSockFromName(firstname, lastname); | ||||
|     } | ||||
|   | ||||
| @@ -1,16 +1,15 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "Player.hpp" | ||||
| #include "core/Core.hpp" | ||||
| #include "servers/CNShardServer.hpp" | ||||
| #include "Chunking.hpp" | ||||
|  | ||||
| #include <utility> | ||||
| #include "Player.hpp" | ||||
| #include "Chunking.hpp" | ||||
| #include "Transport.hpp" | ||||
| #include "Entities.hpp" | ||||
|  | ||||
| #include <map> | ||||
| #include <list> | ||||
|  | ||||
| struct WarpLocation; | ||||
|  | ||||
| namespace PlayerManager { | ||||
|     extern std::map<CNSocket*, Player*> players; | ||||
|     void init(); | ||||
| @@ -34,6 +33,7 @@ namespace PlayerManager { | ||||
|     CNSocket *getSockFromAny(int by, int id, int uid, std::string firstname, std::string lastname); | ||||
|     WarpLocation *getRespawnPoint(Player *plr); | ||||
|  | ||||
|     void sendToGroup(CNSocket *sock, void* buf, uint32_t type, size_t size); | ||||
|     void sendToViewable(CNSocket *sock, void* buf, uint32_t type, size_t size); | ||||
|  | ||||
|     // TODO: unify this under the new Entity system | ||||
| @@ -43,7 +43,7 @@ namespace PlayerManager { | ||||
|         for (auto it = plr->viewableChunks.begin(); it != plr->viewableChunks.end(); it++) { | ||||
|             Chunk* chunk = *it; | ||||
|             for (const EntityRef& ref : chunk->entities) { | ||||
|                 if (ref.type != EntityType::PLAYER || ref.sock == sock) | ||||
|                 if (ref.kind != EntityKind::PLAYER || ref.sock == sock) | ||||
|                     continue; | ||||
|  | ||||
|                 ref.sock->sendPacket(pkt, type); | ||||
|   | ||||
| @@ -1,4 +1,7 @@ | ||||
| #include "PlayerMovement.hpp" | ||||
|  | ||||
| #include "servers/CNShardServer.hpp" | ||||
|  | ||||
| #include "PlayerManager.hpp" | ||||
| #include "TableData.hpp" | ||||
| #include "core/Core.hpp" | ||||
| @@ -33,7 +36,7 @@ static void movePlayer(CNSocket* sock, CNPacketData* data) { | ||||
|     // [gruntwork] check if player has a follower and move it | ||||
|     if (TableData::RunningNPCPaths.find(plr->iID) != TableData::RunningNPCPaths.end()) { | ||||
|         BaseNPC* follower = TableData::RunningNPCPaths[plr->iID].first; | ||||
|         Transport::NPCQueues.erase(follower->appearanceData.iNPC_ID); // erase existing points | ||||
|         Transport::NPCQueues.erase(follower->id); // erase existing points | ||||
|         std::queue<Vec3> queue; | ||||
|         Vec3 from = { follower->x, follower->y, follower->z }; | ||||
|         float drag = 0.95f; // this ensures that they don't bump into the player | ||||
| @@ -45,7 +48,7 @@ static void movePlayer(CNSocket* sock, CNPacketData* data) { | ||||
|  | ||||
|         // add a route to the queue; to be processed in Transport::stepNPCPathing() | ||||
|         Transport::lerp(&queue, from, to, NPC_DEFAULT_SPEED * 1.5); // little faster than typical | ||||
|         Transport::NPCQueues[follower->appearanceData.iNPC_ID] = queue; | ||||
|         Transport::NPCQueues[follower->id] = queue; | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,10 +1,12 @@ | ||||
| #include "servers/CNShardServer.hpp" | ||||
| #include "Racing.hpp" | ||||
|  | ||||
| #include "db/Database.hpp" | ||||
| #include "servers/CNShardServer.hpp" | ||||
|  | ||||
| #include "NPCManager.hpp" | ||||
| #include "PlayerManager.hpp" | ||||
| #include "Missions.hpp" | ||||
| #include "Items.hpp" | ||||
| #include "db/Database.hpp" | ||||
| #include "NPCManager.hpp" | ||||
|  | ||||
| using namespace Racing; | ||||
|  | ||||
|   | ||||
| @@ -1,8 +1,10 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <set> | ||||
| #include "core/Core.hpp" | ||||
|  | ||||
| #include "servers/CNShardServer.hpp" | ||||
| #include <map> | ||||
| #include <vector> | ||||
| #include <set> | ||||
|  | ||||
| struct EPInfo { | ||||
|     int zoneX, zoneY, EPID, maxScore, maxTime; | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
|  | ||||
| #include <random> | ||||
| #include <memory> | ||||
| #include <vector> | ||||
|  | ||||
| namespace Rand { | ||||
|     extern std::unique_ptr<std::mt19937> generator; | ||||
|   | ||||
| @@ -1,18 +1,14 @@ | ||||
| #include "TableData.hpp" | ||||
|  | ||||
| #include "NPCManager.hpp" | ||||
| #include "Transport.hpp" | ||||
| #include "Items.hpp" | ||||
| #include "settings.hpp" | ||||
| #include "Missions.hpp" | ||||
| #include "Chunking.hpp" | ||||
| #include "Nanos.hpp" | ||||
| #include "Racing.hpp" | ||||
| #include "Items.hpp" | ||||
| #include "Vendors.hpp" | ||||
| #include "Racing.hpp" | ||||
| #include "Nanos.hpp" | ||||
| #include "Abilities.hpp" | ||||
| #include "Eggs.hpp" | ||||
|  | ||||
| #include "JSON.hpp" | ||||
|  | ||||
| #include <fstream> | ||||
| #include <sstream> | ||||
| #include <cmath> | ||||
| @@ -231,18 +227,34 @@ static void loadXDT(json& xdtData) { | ||||
|         // load nano powers | ||||
|         json skills = xdtData["m_pSkillTable"]["m_pSkillData"]; | ||||
|  | ||||
|         for (json::iterator _skills = skills.begin(); _skills != skills.end(); _skills++) { | ||||
|             auto skills = _skills.value(); | ||||
|             SkillData skillData = { skills["m_iSkillType"], skills["m_iTargetType"], skills["m_iBatteryDrainType"], skills["m_iEffectArea"] }; | ||||
|         for (json::iterator _skill = skills.begin(); _skill != skills.end(); _skill++) { | ||||
|             auto skill = _skill.value(); | ||||
|             SkillData skillData = { | ||||
|                 skill["m_iSkillType"], | ||||
|                 skill["m_iEffectTarget"], | ||||
|                 skill["m_iEffectType"], | ||||
|                 skill["m_iTargetType"], | ||||
|                 skill["m_iBatteryDrainType"], | ||||
|                 skill["m_iEffectArea"] | ||||
|             }; | ||||
|  | ||||
|             skillData.valueTypes[0] = skill["m_iValueA_Type"]; | ||||
|             skillData.valueTypes[1] = skill["m_iValueB_Type"]; | ||||
|             skillData.valueTypes[2] = skill["m_iValueC_Type"]; | ||||
|  | ||||
|             for (int i = 0; i < 4; i++) { | ||||
|                 skillData.batteryUse[i] = skills["m_iBatteryDrainUse"][i]; | ||||
|                 skillData.durationTime[i] = skills["m_iDurationTime"][i]; | ||||
|                 skillData.powerIntensity[i] = skills["m_iValueA"][i]; | ||||
|                 skillData.batteryUse[i] = skill["m_iBatteryDrainUse"][i]; | ||||
|                 skillData.durationTime[i] = skill["m_iDurationTime"][i]; | ||||
|  | ||||
|                 skillData.values[0][i] = skill["m_iValueA"][i]; | ||||
|                 skillData.values[1][i] = skill["m_iValueB"][i]; | ||||
|                 skillData.values[2][i] = skill["m_iValueC"][i]; | ||||
|             } | ||||
|             Nanos::SkillTable[skills["m_iSkillNumber"]] = skillData; | ||||
|  | ||||
|             Abilities::SkillTable[skill["m_iSkillNumber"]] = skillData; | ||||
|         } | ||||
|  | ||||
|         std::cout << "[INFO] Loaded " << Nanos::SkillTable.size() << " nano skills" << std::endl; | ||||
|         std::cout << "[INFO] Loaded " << Abilities::SkillTable.size() << " nano skills" << std::endl; | ||||
|  | ||||
|         // load EP data | ||||
|         json instances = xdtData["m_pInstanceTable"]["m_pInstanceData"]; | ||||
| @@ -314,10 +326,10 @@ static void loadPaths(json& pathData, int32_t* nextId) { | ||||
|             if (passedDistance >= SLIDER_GAP_SIZE) { // space them out uniformaly | ||||
|                 passedDistance -= SLIDER_GAP_SIZE; // step down | ||||
|                 // spawn a slider | ||||
|                 Bus* slider = new Bus(point.x, point.y, point.z, 0, INSTANCE_OVERWORLD, 1, (*nextId)--); | ||||
|                 NPCManager::NPCs[slider->appearanceData.iNPC_ID] = slider; | ||||
|                 NPCManager::updateNPCPosition(slider->appearanceData.iNPC_ID, slider->x, slider->y, slider->z, INSTANCE_OVERWORLD, 0); | ||||
|                 Transport::NPCQueues[slider->appearanceData.iNPC_ID] = route; | ||||
|                 Bus* slider = new Bus(0, INSTANCE_OVERWORLD, 1, (*nextId)--); | ||||
|                 NPCManager::NPCs[slider->id] = slider; | ||||
|                 NPCManager::updateNPCPosition(slider->id, point.x, point.y, point.z, INSTANCE_OVERWORLD, 0); | ||||
|                 Transport::NPCQueues[slider->id] = route; | ||||
|             } | ||||
|             // rotate | ||||
|             route.pop(); | ||||
| @@ -659,7 +671,7 @@ static void loadEggs(json& eggData, int32_t* nextId) { | ||||
|             int id = (*nextId)--; | ||||
|             uint64_t instanceID = egg.find("iMapNum") == egg.end() ? INSTANCE_OVERWORLD : (int)egg["iMapNum"]; | ||||
|  | ||||
|             Egg* addEgg = new Egg((int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, (int)egg["iType"], id, false); | ||||
|             Egg* addEgg = new Egg(instanceID, (int)egg["iType"], id, false); | ||||
|             NPCManager::NPCs[id] = addEgg; | ||||
|             eggCount++; | ||||
|             NPCManager::updateNPCPosition(id, (int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, 0); | ||||
| @@ -755,7 +767,7 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) { | ||||
|             if (NPCManager::NPCs.find(npcID) == NPCManager::NPCs.end()) | ||||
|                 continue; // NPC not found | ||||
|             BaseNPC* npc = NPCManager::NPCs[npcID]; | ||||
|             npc->appearanceData.iAngle = angle; | ||||
|             npc->angle = angle; | ||||
|  | ||||
|             RunningNPCRotations[npcID] = angle; | ||||
|         } | ||||
| @@ -768,8 +780,8 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) { | ||||
|             if (NPCManager::NPCs.find(npcID) == NPCManager::NPCs.end()) | ||||
|                 continue; // NPC not found | ||||
|             BaseNPC* npc = NPCManager::NPCs[npcID]; | ||||
|             NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->x, npc->y, | ||||
|                 npc->z, instanceID, npc->appearanceData.iAngle); | ||||
|             NPCManager::updateNPCPosition(npc->id, npc->x, npc->y, | ||||
|                 npc->z, instanceID, npc->angle); | ||||
|  | ||||
|             RunningNPCMapNumbers[npcID] = instanceID; | ||||
|         } | ||||
| @@ -791,12 +803,12 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) { | ||||
|                 // re-enable respawning | ||||
|                 ((Mob*)npc)->summoned = false; | ||||
|             } else { | ||||
|                 npc = new BaseNPC(mob["iX"], mob["iY"], mob["iZ"], mob["iAngle"], instanceID, mob["iNPCType"], id); | ||||
|                 npc = new BaseNPC(mob["iAngle"], instanceID, mob["iNPCType"], id); | ||||
|             } | ||||
|  | ||||
|             NPCManager::NPCs[npc->appearanceData.iNPC_ID] = npc; | ||||
|             RunningMobs[npc->appearanceData.iNPC_ID] = npc; | ||||
|             NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, mob["iX"], mob["iY"], mob["iZ"], instanceID, mob["iAngle"]); | ||||
|             NPCManager::NPCs[npc->id] = npc; | ||||
|             RunningMobs[npc->id] = npc; | ||||
|             NPCManager::updateNPCPosition(npc->id, mob["iX"], mob["iY"], mob["iZ"], instanceID, mob["iAngle"]); | ||||
|         } | ||||
|  | ||||
|         // mob groups | ||||
| @@ -840,7 +852,7 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) { | ||||
|  | ||||
|                     tmpFol->offsetX = follower.find("iOffsetX") == follower.end() ? 0 : (int)follower["iOffsetX"]; | ||||
|                     tmpFol->offsetY = follower.find("iOffsetY") == follower.end() ? 0 : (int)follower["iOffsetY"]; | ||||
|                     tmpFol->groupLeader = tmp->appearanceData.iNPC_ID; | ||||
|                     tmpFol->groupLeader = tmp->id; | ||||
|                     tmp->groupMember[followerCount++] = *nextId; | ||||
|  | ||||
|                     (*nextId)--; | ||||
| @@ -850,7 +862,7 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) { | ||||
|                 std::cout << "[WARN] Mob group leader with ID " << *nextId << " has too many followers (" << followers.size() << ")\n"; | ||||
|             } | ||||
|  | ||||
|             RunningGroups[tmp->appearanceData.iNPC_ID] = tmp; // store as running | ||||
|             RunningGroups[tmp->id] = tmp; // store as running | ||||
|         } | ||||
|  | ||||
|         auto eggs = gruntwork["eggs"]; | ||||
| @@ -859,7 +871,7 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) { | ||||
|             int id = (*nextId)--; | ||||
|             uint64_t instanceID = egg.find("iMapNum") == egg.end() ? INSTANCE_OVERWORLD : (int)egg["iMapNum"]; | ||||
|  | ||||
|             Egg* addEgg = new Egg((int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, (int)egg["iType"], id, false); | ||||
|             Egg* addEgg = new Egg(instanceID, (int)egg["iType"], id, false); | ||||
|             NPCManager::NPCs[id] = addEgg; | ||||
|             NPCManager::updateNPCPosition(id, (int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, 0); | ||||
|             RunningEggs[id] = addEgg; | ||||
| @@ -893,7 +905,7 @@ static void loadNPCs(json& npcData) { | ||||
|             if (npc["iX"] > 512000 && npc["iY"] < 256000) | ||||
|                 continue; | ||||
| #endif | ||||
|             BaseNPC* tmp = new BaseNPC(npc["iX"], npc["iY"], npc["iZ"], npc["iAngle"], instanceID, type, npcID); | ||||
|             BaseNPC* tmp = new BaseNPC(npc["iAngle"], instanceID, type, npcID); | ||||
|  | ||||
|             NPCManager::NPCs[npcID] = tmp; | ||||
|             NPCManager::updateNPCPosition(npcID, npc["iX"], npc["iY"], npc["iZ"], instanceID, npc["iAngle"]); | ||||
| @@ -1003,7 +1015,7 @@ static void loadMobs(json& npcData, int32_t* nextId) { | ||||
|  | ||||
|                     tmpFol->offsetX = follower.find("iOffsetX") == follower.end() ? 0 : (int)follower["iOffsetX"]; | ||||
|                     tmpFol->offsetY = follower.find("iOffsetY") == follower.end() ? 0 : (int)follower["iOffsetY"]; | ||||
|                     tmpFol->groupLeader = tmp->appearanceData.iNPC_ID; | ||||
|                     tmpFol->groupLeader = tmp->id; | ||||
|                     tmp->groupMember[followerCount++] = *nextId; | ||||
|  | ||||
|                     (*nextId)--; | ||||
| @@ -1086,33 +1098,6 @@ static void patchJSON(json* base, json* patch) { | ||||
|  | ||||
| void TableData::init() { | ||||
|     int32_t nextId = INT32_MAX; // next dynamic ID to hand out | ||||
|     json patchmap; | ||||
|  | ||||
|     // load patch map | ||||
|     { | ||||
|         std::fstream fstream; | ||||
|         fstream.open(settings::TDATADIR + "/" + settings::PATCHMAPJSON); | ||||
|  | ||||
|         if (fstream.fail()) { | ||||
|             std::cerr << "[FATAL] Critical tdata file missing: " << settings::PATCHMAPJSON << std::endl; | ||||
|             exit(1); | ||||
|         } | ||||
|  | ||||
|         if (fstream.peek() == std::ifstream::traits_type::eof()) { | ||||
|             std::cerr << "[FATAL] Critical tdata file is empty: " << settings::PATCHMAPJSON << std::endl; | ||||
|             exit(1); | ||||
|         } | ||||
|  | ||||
|         fstream >> patchmap; | ||||
|         fstream.close(); | ||||
|     } | ||||
|  | ||||
|     // ensure that there is a patch list for the current build | ||||
|     if (patchmap["patchmap"].find(settings::BUILDNAME) == patchmap["patchmap"].end()) { | ||||
|         std::cerr << "[FATAL] Build name " << settings::BUILDNAME << " not found in " << | ||||
|             settings::PATCHMAPJSON << std::endl; | ||||
|         exit(1); | ||||
|     } | ||||
|  | ||||
|     // base JSON tables | ||||
|     json xdt, paths, drops, eggs, npcs, mobs, gruntwork; | ||||
| @@ -1161,13 +1146,15 @@ void TableData::init() { | ||||
|             fstream >> *table.first; | ||||
|         } | ||||
|  | ||||
|         // patching: load each patch directory specified in patchmap.json | ||||
|         // patching: load each patch directory specified in the config file | ||||
|  | ||||
|         // split config field into individual patch entries | ||||
|         std::stringstream ss(settings::ENABLEDPATCHES); | ||||
|         std::istream_iterator<std::string> begin(ss); | ||||
|         std::istream_iterator<std::string> end; | ||||
|  | ||||
|         // fetch list of patches that need to be applied for the current build | ||||
|         json patch; | ||||
|         json patchlist = patchmap["patchmap"][settings::BUILDNAME]; | ||||
|  | ||||
|         for (auto it = patchlist.begin(); it != patchlist.end(); it++) { | ||||
|         for (auto it = begin; it != end; it++) { | ||||
|             // this is the theoretical path of a corresponding patch for this file | ||||
|             std::string patchModuleName = *it; | ||||
|             std::string patchFile = settings::PATCHDIR + patchModuleName + "/" + table.second; | ||||
| @@ -1251,7 +1238,7 @@ void TableData::flush() { | ||||
|             continue; | ||||
|  | ||||
|         int x, y, z; | ||||
|         if (npc->type == EntityType::MOB) { | ||||
|         if (npc->kind == EntityKind::MOB) { | ||||
|             Mob *m = (Mob*)npc; | ||||
|             x = m->spawnX; | ||||
|             y = m->spawnY; | ||||
| @@ -1263,13 +1250,13 @@ void TableData::flush() { | ||||
|         } | ||||
|  | ||||
|         // NOTE: this format deviates slightly from the one in mobs.json | ||||
|         mob["iNPCType"] = (int)npc->appearanceData.iNPCType; | ||||
|         mob["iNPCType"] = (int)npc->type; | ||||
|         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; | ||||
|         mob["iAngle"] = npc->angle; | ||||
|  | ||||
|         // it's called mobs, but really it's everything | ||||
|         gruntwork["mobs"].push_back(mob); | ||||
| @@ -1284,19 +1271,19 @@ void TableData::flush() { | ||||
|  | ||||
|         int x, y, z; | ||||
|         std::vector<Mob*> followers; | ||||
|         if (npc->type == EntityType::MOB) { | ||||
|         if (npc->kind == EntityKind::MOB) { | ||||
|             Mob* m = (Mob*)npc; | ||||
|             x = m->spawnX; | ||||
|             y = m->spawnY; | ||||
|             z = m->spawnZ; | ||||
|             if (m->groupLeader != m->appearanceData.iNPC_ID) { // make sure this is a leader | ||||
|             if (m->groupLeader != m->id) { // make sure this is a leader | ||||
|                 std::cout << "[WARN] Non-leader mob found in running groups; ignoring\n"; | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             // add follower data to vector; go until OOB or until follower ID is 0 | ||||
|             for (int i = 0; i < 4 && m->groupMember[i] > 0; i++) { | ||||
|                 if (NPCManager::NPCs.find(m->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[m->groupMember[i]]->type != EntityType::MOB) { | ||||
|                 if (NPCManager::NPCs.find(m->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[m->groupMember[i]]->kind != EntityKind::MOB) { | ||||
|                     std::cout << "[WARN] Follower with ID " << m->groupMember[i] << " not found; skipping\n"; | ||||
|                     continue; | ||||
|                 } | ||||
| @@ -1310,13 +1297,13 @@ void TableData::flush() { | ||||
|         } | ||||
|  | ||||
|         // NOTE: this format deviates slightly from the one in mobs.json | ||||
|         mob["iNPCType"] = (int)npc->appearanceData.iNPCType; | ||||
|         mob["iNPCType"] = (int)npc->type; | ||||
|         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; | ||||
|         mob["iAngle"] = npc->angle; | ||||
|  | ||||
|         // followers | ||||
|         while (followers.size() > 0) { | ||||
| @@ -1325,7 +1312,7 @@ void TableData::flush() { | ||||
|  | ||||
|             // populate JSON entry | ||||
|             json fol; | ||||
|             fol["iNPCType"] = follower->appearanceData.iNPCType; | ||||
|             fol["iNPCType"] = follower->type; | ||||
|             fol["iOffsetX"] = follower->offsetX; | ||||
|             fol["iOffsetY"] = follower->offsetY; | ||||
|  | ||||
| @@ -1350,7 +1337,7 @@ void TableData::flush() { | ||||
|         int mapnum = MAPNUM(npc->instanceID); | ||||
|         if (mapnum != 0) | ||||
|             egg["iMapNum"] = mapnum; | ||||
|         egg["iType"] = npc->appearanceData.iNPCType; | ||||
|         egg["iType"] = npc->type; | ||||
|  | ||||
|         gruntwork["eggs"].push_back(egg); | ||||
|     } | ||||
|   | ||||
| @@ -1,8 +1,13 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <map> | ||||
| #include "JSON.hpp" | ||||
|  | ||||
| #include "NPCManager.hpp" | ||||
| #include "Entities.hpp" | ||||
| #include "Transport.hpp" | ||||
|  | ||||
| #include <map> | ||||
| #include <unordered_map> | ||||
| #include <vector> | ||||
|  | ||||
| // these are added to the NPC's static key to avoid collisions | ||||
| const int NPC_ID_OFFSET = 1; | ||||
|   | ||||
| @@ -1,4 +1,7 @@ | ||||
| #include "Trading.hpp" | ||||
|  | ||||
| #include "servers/CNShardServer.hpp" | ||||
|  | ||||
| #include "PlayerManager.hpp" | ||||
| #include "db/Database.hpp" | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,5 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "Items.hpp" | ||||
|  | ||||
| namespace Trading { | ||||
|     void init(); | ||||
| } | ||||
| @@ -1,9 +1,12 @@ | ||||
| #include "Transport.hpp" | ||||
|  | ||||
| #include "servers/CNShardServer.hpp" | ||||
|  | ||||
| #include "PlayerManager.hpp" | ||||
| #include "Nanos.hpp" | ||||
| #include "Transport.hpp" | ||||
| #include "TableData.hpp" | ||||
| #include "Combat.hpp" | ||||
| #include "Entities.hpp" | ||||
| #include "NPCManager.hpp" | ||||
| #include "MobAI.hpp" | ||||
|  | ||||
| #include <unordered_map> | ||||
| @@ -257,13 +260,13 @@ static void stepNPCPathing() { | ||||
|         } | ||||
|  | ||||
|         // skip if not simulating mobs | ||||
|         if (npc->type == EntityType::MOB && !MobAI::simulateMobs) { | ||||
|         if (npc->kind == EntityKind::MOB && !MobAI::simulateMobs) { | ||||
|             it++; | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         // do not roam if not roaming | ||||
|         if (npc->type == EntityType::MOB && ((Mob*)npc)->state != MobState::ROAMING) { | ||||
|         if (npc->kind == EntityKind::MOB && ((Mob*)npc)->state != AIState::ROAMING) { | ||||
|             it++; | ||||
|             continue; | ||||
|         } | ||||
| @@ -276,15 +279,15 @@ static void stepNPCPathing() { | ||||
|         int distanceBetween = hypot(dXY, point.z - npc->z); // total distance | ||||
|  | ||||
|         // update NPC location to update viewables | ||||
|         NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, point.x, point.y, point.z, npc->instanceID, npc->appearanceData.iAngle); | ||||
|         NPCManager::updateNPCPosition(npc->id, point.x, point.y, point.z, npc->instanceID, npc->angle); | ||||
|  | ||||
|         // TODO: move walking logic into Entity stack | ||||
|         switch (npc->type) { | ||||
|         case EntityType::BUS: | ||||
|         switch (npc->kind) { | ||||
|         case EntityKind::BUS: | ||||
|             INITSTRUCT(sP_FE2CL_TRANSPORTATION_MOVE, busMove); | ||||
|  | ||||
|             busMove.eTT = 3; | ||||
|             busMove.iT_ID = npc->appearanceData.iNPC_ID; | ||||
|             busMove.iT_ID = npc->id; | ||||
|             busMove.iMoveStyle = 0; // ??? | ||||
|             busMove.iToX = point.x; | ||||
|             busMove.iToY = point.y; | ||||
| @@ -293,12 +296,12 @@ static void stepNPCPathing() { | ||||
|  | ||||
|             NPCManager::sendToViewable(npc, &busMove, P_FE2CL_TRANSPORTATION_MOVE, sizeof(sP_FE2CL_TRANSPORTATION_MOVE)); | ||||
|             break; | ||||
|         case EntityType::MOB: | ||||
|         case EntityKind::MOB: | ||||
|             MobAI::incNextMovement((Mob*)npc); | ||||
|             /* fallthrough */ | ||||
|         default: | ||||
|             INITSTRUCT(sP_FE2CL_NPC_MOVE, move); | ||||
|             move.iNPC_ID = npc->appearanceData.iNPC_ID; | ||||
|             move.iNPC_ID = npc->id; | ||||
|             move.iMoveStyle = 0; // ??? | ||||
|             move.iToX = point.x; | ||||
|             move.iToY = point.y; | ||||
| @@ -385,7 +388,7 @@ NPCPath* Transport::findApplicablePath(int32_t id, int32_t type, int taskID) { | ||||
|  | ||||
| void Transport::constructPathNPC(int32_t id, NPCPath* path) { | ||||
|     BaseNPC* npc = NPCManager::NPCs[id]; | ||||
|     if (npc->type == EntityType::MOB) | ||||
|     if (npc->kind == EntityKind::MOB) | ||||
|         ((Mob*)(npc))->staticPath = true; | ||||
|     npc->loopingPath = path->isLoop; | ||||
|  | ||||
|   | ||||
| @@ -1,8 +1,11 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "servers/CNShardServer.hpp" | ||||
| #include "core/Core.hpp" | ||||
|  | ||||
| #include <unordered_map> | ||||
| #include <map> | ||||
| #include <vector> | ||||
| #include <queue> | ||||
|  | ||||
| const int SLIDER_SPEED = 1200; | ||||
| const int SLIDER_STOP_TICKS = 16; | ||||
|   | ||||
| @@ -1,4 +1,9 @@ | ||||
| #include "Vendors.hpp" | ||||
|  | ||||
| #include "servers/CNShardServer.hpp" | ||||
|  | ||||
| #include "PlayerManager.hpp" | ||||
| #include "Items.hpp" | ||||
| #include "Rand.hpp" | ||||
|  | ||||
| // 7 days | ||||
|   | ||||
| @@ -1,10 +1,9 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "core/Core.hpp" | ||||
| #include "servers/CNShardServer.hpp" | ||||
|  | ||||
| #include "Items.hpp" | ||||
| #include "PlayerManager.hpp" | ||||
| #include <vector> | ||||
| #include <map> | ||||
|  | ||||
| struct VendorListing { | ||||
|     int sort, type, id; | ||||
|   | ||||
| @@ -93,7 +93,7 @@ inline constexpr bool isOutboundPacketID(uint32_t id) { | ||||
|  | ||||
| // overflow-safe validation of variable-length packets | ||||
| // for outbound packets | ||||
| inline constexpr bool validOutVarPacket(size_t base, int32_t npayloads, size_t plsize) { | ||||
| inline constexpr bool validOutVarPacket(size_t base, size_t npayloads, size_t plsize) { | ||||
|     // check for multiplication overflow | ||||
|     if (npayloads > 0 && (CN_PACKET_BUFFER_SIZE - 8) / (size_t)npayloads < plsize) | ||||
|         return false; | ||||
| @@ -110,7 +110,7 @@ inline constexpr bool validOutVarPacket(size_t base, int32_t npayloads, size_t p | ||||
| } | ||||
|  | ||||
| // for inbound packets | ||||
| inline constexpr bool validInVarPacket(size_t base, int32_t npayloads, size_t plsize, size_t datasize) { | ||||
| inline constexpr bool validInVarPacket(size_t base, size_t npayloads, size_t plsize, size_t datasize) { | ||||
|     // check for multiplication overflow | ||||
|     if (npayloads > 0 && (CN_PACKET_BUFFER_SIZE - 8) / (size_t)npayloads < plsize) | ||||
|         return false; | ||||
|   | ||||
| @@ -23,6 +23,7 @@ | ||||
| #include <string> | ||||
| #include <locale> | ||||
| #include <codecvt> | ||||
| #include <tuple> | ||||
|  | ||||
| // 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; \ | ||||
|   | ||||
| @@ -10,63 +10,44 @@ const float CN_EP_RANK_4 = 0.3f; | ||||
| const float CN_EP_RANK_5 = 0.29f; | ||||
|  | ||||
| // methods of finding players for GM commands | ||||
| enum eCN_GM_TargetSearchBy { | ||||
|     eCN_GM_TargetSearchBy__PC_ID, // player id | ||||
|     eCN_GM_TargetSearchBy__PC_Name, // firstname, lastname | ||||
|     eCN_GM_TargetSearchBy__PC_UID // account id | ||||
| enum class eCN_GM_TargetSearchBy { | ||||
|     PC_ID, // player id | ||||
|     PC_Name, // firstname, lastname | ||||
|     PC_UID // account id | ||||
| }; | ||||
|  | ||||
| enum eCN_GM_TeleportType { | ||||
|     eCN_GM_TeleportMapType__XYZ, | ||||
|     eCN_GM_TeleportMapType__MapXYZ, | ||||
|     eCN_GM_TeleportMapType__MyLocation, | ||||
|     eCN_GM_TeleportMapType__SomeoneLocation, | ||||
|     eCN_GM_TeleportMapType__Unstick | ||||
| enum class eCN_GM_TeleportType { | ||||
|     XYZ, | ||||
|     MapXYZ, | ||||
|     MyLocation, | ||||
|     SomeoneLocation, | ||||
|     Unstick | ||||
| }; | ||||
|  | ||||
| // nano powers | ||||
| enum class eTaskTypeProperty { | ||||
|     None = -1, | ||||
|     Talk = 1, | ||||
|     GotoLocation = 2, | ||||
|     UseItems = 3, | ||||
|     Delivery = 4, | ||||
|     Defeat = 5, | ||||
|     EscortDefence = 6, | ||||
|     Max = 7 | ||||
| }; | ||||
|  | ||||
| enum class ePCRegenType { | ||||
|     None, | ||||
|     Xcom, | ||||
|     Here, | ||||
|     HereByPhoenix, | ||||
|     HereByPhoenixGroup, | ||||
|     Unstick, | ||||
|     HereByPhoenixItem, | ||||
|     End | ||||
| }; | ||||
|  | ||||
| // nano power flags | ||||
| 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, | ||||
| @@ -96,6 +77,27 @@ enum { | ||||
|     ECSTB__END = 26, | ||||
| }; | ||||
|  | ||||
| enum { | ||||
| 	ETBU_NONE = 0, | ||||
| 	ETBU_ADD = 1, | ||||
| 	ETBU_DEL = 2, | ||||
| 	ETBU_CHANGE = 3, | ||||
|     ETBU__END = 4, | ||||
| }; | ||||
|  | ||||
| enum  { | ||||
| 	ETBT_NONE = 0, | ||||
| 	ETBT_NANO = 1, | ||||
| 	ETBT_GROUPNANO = 2, | ||||
| 	ETBT_SHINY = 3, | ||||
| 	ETBT_LANDEFFECT = 4, | ||||
| 	ETBT_ITEM = 5, | ||||
| 	ETBT_CASHITEM = 6, | ||||
| 	ETBT__END = 7, | ||||
| 	ETBT_SKILL = 1, | ||||
| 	ETBT_GROUPSKILL = 2 | ||||
| }; | ||||
|  | ||||
| enum { | ||||
|     SUCC = 1, | ||||
|     FAIL = 0, | ||||
|   | ||||
| @@ -47,8 +47,8 @@ struct PacketDesc { | ||||
|  * really should. | ||||
|  */ | ||||
| struct sGM_PVPTarget { | ||||
|     uint32_t eCT; | ||||
|     uint32_t iID; | ||||
|     uint32_t eCT; | ||||
| }; | ||||
|  | ||||
| struct sSkillResult_Leech { | ||||
|   | ||||
| @@ -1,13 +1,15 @@ | ||||
| #include "servers/CNLoginServer.hpp" | ||||
|  | ||||
| #include "core/CNShared.hpp" | ||||
| #include "db/Database.hpp" | ||||
| #include "PlayerManager.hpp" | ||||
| #include "Items.hpp" | ||||
| #include <regex> | ||||
| #include "bcrypt/BCrypt.hpp" | ||||
|  | ||||
| #include "PlayerManager.hpp" | ||||
| #include "Items.hpp" | ||||
| #include "settings.hpp" | ||||
|  | ||||
| #include <regex> | ||||
|  | ||||
| std::map<CNSocket*, CNLoginData> CNLoginServer::loginSessions; | ||||
|  | ||||
| CNLoginServer::CNLoginServer(uint16_t p) { | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "core/Core.hpp" | ||||
|  | ||||
| #include "Player.hpp" | ||||
|  | ||||
| #include <map> | ||||
|   | ||||
| @@ -1,10 +1,12 @@ | ||||
| #include "servers/CNShardServer.hpp" | ||||
|  | ||||
| #include "core/Core.hpp" | ||||
| #include "core/CNShared.hpp" | ||||
| #include "db/Database.hpp" | ||||
| #include "servers/Monitor.hpp" | ||||
| #include "servers/CNShardServer.hpp" | ||||
|  | ||||
| #include "PlayerManager.hpp" | ||||
| #include "MobAI.hpp" | ||||
| #include "core/CNShared.hpp" | ||||
| #include "settings.hpp" | ||||
| #include "TableData.hpp" // for flush() | ||||
|  | ||||
|   | ||||
| @@ -3,9 +3,12 @@ | ||||
| #include "core/Core.hpp" | ||||
|  | ||||
| #include <map> | ||||
| #include <list> | ||||
|  | ||||
| #define REGISTER_SHARD_PACKET(pactype, handlr) CNShardServer::ShardPackets[pactype] = handlr; | ||||
| #define REGISTER_SHARD_TIMER(handlr, delta) CNShardServer::Timers.push_back(TimerEvent(handlr, delta)); | ||||
| #define MS_PER_PLAYER_TICK 500 | ||||
| #define MS_PER_COMBAT_TICK 200 | ||||
|  | ||||
| class CNShardServer : public CNServer { | ||||
| private: | ||||
|   | ||||
| @@ -1,8 +1,10 @@ | ||||
| #include "servers/Monitor.hpp" | ||||
|  | ||||
| #include "servers/CNShardServer.hpp" | ||||
|  | ||||
| #include "PlayerManager.hpp" | ||||
| #include "Chat.hpp" | ||||
| #include "Email.hpp" | ||||
| #include "servers/Monitor.hpp" | ||||
| #include "settings.hpp" | ||||
|  | ||||
| #include <cstdio> | ||||
|   | ||||
| @@ -2,9 +2,6 @@ | ||||
|  | ||||
| #include "core/Core.hpp" | ||||
|  | ||||
| #include <list> | ||||
| #include <mutex> | ||||
|  | ||||
| namespace Monitor { | ||||
|     SOCKET init(); | ||||
|     bool acceptConnection(SOCKET, uint16_t); | ||||
|   | ||||
| @@ -1,9 +1,10 @@ | ||||
| #include <iostream> | ||||
|  | ||||
| #include "settings.hpp" | ||||
|  | ||||
| #include "core/CNStructs.hpp" // so we get the ACADEMY definition | ||||
| #include "INIReader.hpp" | ||||
|  | ||||
| // so we get the ACADEMY definition | ||||
| #include "core/CNStructs.hpp" | ||||
| #include <iostream> | ||||
|  | ||||
| // defaults :) | ||||
| int settings::VERBOSITY = 1; | ||||
| @@ -47,13 +48,12 @@ std::string settings::GRUNTWORKJSON = "gruntwork.json"; | ||||
| std::string settings::MOTDSTRING = "Welcome to OpenFusion!"; | ||||
| std::string settings::DROPSJSON = "drops.json"; | ||||
| std::string settings::PATHJSON = "paths.json"; | ||||
| std::string settings::PATCHMAPJSON = "patchmap.json"; | ||||
| #ifdef ACADEMY | ||||
| std::string settings::XDTJSON = "xdt1013.json"; | ||||
| std::string settings::BUILDNAME = "beta-20111013"; | ||||
| std::string settings::ENABLEDPATCHES = "1013"; | ||||
| #else | ||||
| std::string settings::XDTJSON = "xdt.json"; | ||||
| std::string settings::BUILDNAME = "beta-20100104"; | ||||
| std::string settings::ENABLEDPATCHES = ""; | ||||
| #endif // ACADEMY | ||||
|  | ||||
| int settings::ACCLEVEL = 1; | ||||
| @@ -79,7 +79,6 @@ void settings::init() { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     BUILDNAME = reader.Get("", "buildname", BUILDNAME); | ||||
|     VERBOSITY = reader.GetInteger("", "verbosity", VERBOSITY); | ||||
|     SANDBOX = reader.GetBoolean("", "sandbox", SANDBOX); | ||||
|     LOGINPORT = reader.GetInteger("login", "port", LOGINPORT); | ||||
| @@ -107,7 +106,7 @@ void settings::init() { | ||||
|     DBPATH = reader.Get("shard", "dbpath", DBPATH); | ||||
|     TDATADIR = reader.Get("shard", "tdatadir", TDATADIR); | ||||
|     PATCHDIR = reader.Get("shard", "patchdir", PATCHDIR); | ||||
|     PATCHMAPJSON = reader.Get("shard", "patchmapdata", PATCHMAPJSON); | ||||
|     ENABLEDPATCHES = reader.Get("shard", "enabledpatches", ENABLEDPATCHES); | ||||
|     ACCLEVEL = reader.GetInteger("shard", "accountlevel", ACCLEVEL); | ||||
|     EVENTMODE = reader.GetInteger("shard", "eventmode", EVENTMODE); | ||||
|     DISABLEFIRSTUSEFLAG = reader.GetBoolean("shard", "disablefirstuseflag", DISABLEFIRSTUSEFLAG); | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <string> | ||||
|  | ||||
| namespace settings { | ||||
|     extern int VERBOSITY; | ||||
|     extern bool SANDBOX; | ||||
| @@ -29,8 +31,7 @@ namespace settings { | ||||
|     extern std::string GRUNTWORKJSON; | ||||
|     extern std::string DBPATH; | ||||
|     extern std::string PATCHDIR; | ||||
|     extern std::string PATCHMAPJSON; | ||||
|     extern std::string BUILDNAME; | ||||
|     extern std::string ENABLEDPATCHES; | ||||
|     extern std::string TDATADIR; | ||||
|     extern int EVENTMODE; | ||||
|     extern bool MONITORENABLED; | ||||
|   | ||||
							
								
								
									
										2
									
								
								vendor/JSON.hpp
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/JSON.hpp
									
									
									
									
										vendored
									
									
								
							| @@ -27,6 +27,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE | ||||
| SOFTWARE. | ||||
| */ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #ifndef INCLUDE_NLOHMANN_JSON_HPP_ | ||||
| #define INCLUDE_NLOHMANN_JSON_HPP_ | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user