mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2025-10-21 04:40:21 +00:00
Compare commits
41 Commits
9cdec59810
...
1.5.1
Author | SHA1 | Date | |
---|---|---|---|
![]() |
cd265af8e0 | ||
![]() |
38c68f351b | ||
edfbe4d005 | |||
96c430c994 | |||
![]() |
4592fc42af | ||
![]() |
70a27afad1 | ||
![]() |
6cfb3bf532 | ||
9b2a65f8fd | |||
6a69388822 | |||
2924a27eb4 | |||
ba20f5a401 | |||
![]() |
eb88fa05cb | ||
![]() |
0b73cef187 | ||
![]() |
7af39b3d04 | ||
![]() |
33206b1207 | ||
![]() |
e325f7a40b | ||
![]() |
82bee2051a | ||
![]() |
4ece1bb89b | ||
![]() |
31677e2638 | ||
![]() |
d32827b692 | ||
![]() |
13c009b448 | ||
![]() |
a032497bed | ||
![]() |
3b6b61d087 | ||
![]() |
6d760f5bce | ||
![]() |
2a622f901c | ||
![]() |
03d28bf4e4 | ||
![]() |
4b834579c5 | ||
![]() |
07fe8ca367 | ||
![]() |
2f3f8a3951 | ||
4f890a9c07 | |||
8517e0c7de | |||
5fb0cbbcf7 | |||
55e9f6531d | |||
![]() |
7726357fbe | ||
![]() |
564c275d51 | ||
![]() |
3ce9ae5f77 | ||
![]() |
7c5b9a8105 | ||
![]() |
258ff35e20 | ||
![]() |
ab480d88f1 | ||
![]() |
89772d763b | ||
bd0cc3c212 |
7
.github/workflows/check-builds.yaml
vendored
7
.github/workflows/check-builds.yaml
vendored
@@ -9,12 +9,13 @@ on:
|
|||||||
- CMakeLists.txt
|
- CMakeLists.txt
|
||||||
- Makefile
|
- Makefile
|
||||||
pull_request:
|
pull_request:
|
||||||
types: ready_for_review
|
types: [opened, reopened, synchronize, ready_for_review]
|
||||||
paths:
|
paths:
|
||||||
- src/**
|
- src/**
|
||||||
- vendor/**
|
- vendor/**
|
||||||
- CMakeLists.txt
|
- CMakeLists.txt
|
||||||
- Makefile
|
- Makefile
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
ubuntu-build:
|
ubuntu-build:
|
||||||
@@ -53,7 +54,7 @@ jobs:
|
|||||||
- name: Upload build artifact
|
- name: Upload build artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: 'ubuntu20_04-bin-x64-${{ env.SHORT_SHA }}'
|
name: 'ubuntu22_04-bin-x64-${{ env.SHORT_SHA }}'
|
||||||
path: bin
|
path: bin
|
||||||
|
|
||||||
windows-build:
|
windows-build:
|
||||||
@@ -112,7 +113,7 @@ jobs:
|
|||||||
|
|
||||||
copy-artifacts:
|
copy-artifacts:
|
||||||
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/master'
|
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/master'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-22.04
|
||||||
needs: [windows-build, ubuntu-build]
|
needs: [windows-build, ubuntu-build]
|
||||||
env:
|
env:
|
||||||
BOT_SSH_KEY: ${{ secrets.BOT_SSH_KEY }}
|
BOT_SSH_KEY: ${{ secrets.BOT_SSH_KEY }}
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -16,3 +16,5 @@ build/
|
|||||||
version.h
|
version.h
|
||||||
infer-out
|
infer-out
|
||||||
gmon.out
|
gmon.out
|
||||||
|
*.bak
|
||||||
|
|
||||||
|
@@ -3,7 +3,8 @@ project(OpenFusion)
|
|||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
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
|
# OpenFusion supports multiple packet/struct versions
|
||||||
# 104 is the default version to build which can be changed
|
# 104 is the default version to build which can be changed
|
||||||
|
@@ -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.
|
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.
|
Many Pull Requests OpenFusion receives fail to present a clean set of commits to merge.
|
||||||
These are generally either:
|
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.
|
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.
|
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.
|
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).
|
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).
|
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.
|
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:
|
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.
|
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`)
|
(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.
|
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`.
|
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`.
|
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.
|
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.
|
||||||
|
2
Makefile
2
Makefile
@@ -50,6 +50,7 @@ CXXSRC=\
|
|||||||
src/db/email.cpp\
|
src/db/email.cpp\
|
||||||
src/sandbox/seccomp.cpp\
|
src/sandbox/seccomp.cpp\
|
||||||
src/sandbox/openbsd.cpp\
|
src/sandbox/openbsd.cpp\
|
||||||
|
src/Buffs.cpp\
|
||||||
src/Chat.cpp\
|
src/Chat.cpp\
|
||||||
src/CustomCommands.cpp\
|
src/CustomCommands.cpp\
|
||||||
src/Entities.cpp\
|
src/Entities.cpp\
|
||||||
@@ -96,6 +97,7 @@ CXXHDR=\
|
|||||||
vendor/JSON.hpp\
|
vendor/JSON.hpp\
|
||||||
vendor/INIReader.hpp\
|
vendor/INIReader.hpp\
|
||||||
vendor/JSON.hpp\
|
vendor/JSON.hpp\
|
||||||
|
src/Buffs.hpp\
|
||||||
src/Chat.hpp\
|
src/Chat.hpp\
|
||||||
src/CustomCommands.hpp\
|
src/CustomCommands.hpp\
|
||||||
src/Entities.hpp\
|
src/Entities.hpp\
|
||||||
|
39
README.md
39
README.md
@@ -13,13 +13,13 @@ OpenFusion is a reverse-engineered server for FusionFall. It primarily targets v
|
|||||||
|
|
||||||
### Getting Started
|
### Getting Started
|
||||||
#### Method A: Installer (Easiest)
|
#### Method A: Installer (Easiest)
|
||||||
1. Download the client installer by clicking [here](https://github.com/OpenFusionProject/OpenFusion/releases/download/1.4/OpenFusionClient-1.4-Installer.exe) - choose to run the file.
|
1. Download the client installer by clicking [here](https://github.com/OpenFusionProject/OpenFusion/releases/download/1.5/OpenFusionClient-1.5-Installer.exe) - choose to run the file.
|
||||||
2. After a few moments, the client should open: you will be given a choice between two public servers by default. Select the one you wish to play and click connect.
|
2. After a few moments, the client should open: you will be given a choice between two public servers by default. Select the one you wish to play and click connect.
|
||||||
3. To create an account, simply enter the details you wish to use at the login screen then click Log In. Do *not* click register, as this will just lead to a blank screen.
|
3. To create an account, simply enter the details you wish to use at the login screen then click Log In. Do *not* click register, as this will just lead to a blank screen.
|
||||||
4. Make a new character, and enjoy the game! Your progress will be saved automatically, and you can resume playing by entering the login details you used in step 3.
|
4. Make a new character, and enjoy the game! Your progress will be saved automatically, and you can resume playing by entering the login details you used in step 3.
|
||||||
|
|
||||||
#### Method B: Standalone .zip file
|
#### Method B: Standalone .zip file
|
||||||
1. Download the client from [here](https://github.com/OpenFusionProject/OpenFusion/releases/download/1.4/OpenFusionClient-1.4.zip).
|
1. Download the client from [here](https://github.com/OpenFusionProject/OpenFusion/releases/download/1.5/OpenFusionClient-1.5.zip).
|
||||||
2. Extract it to a folder of your choice. Note: if you are upgrading from an older version, it is preferable to start with a fresh folder rather than overwriting a previous install.
|
2. Extract it to a folder of your choice. Note: if you are upgrading from an older version, it is preferable to start with a fresh folder rather than overwriting a previous install.
|
||||||
3. Run OpenFusionClient.exe - you will be given a choice between two public servers by default. Select the one you wish to play and click connect.
|
3. Run OpenFusionClient.exe - you will be given a choice between two public servers by default. Select the one you wish to play and click connect.
|
||||||
4. To create an account, simply enter the details you wish to use at the login screen then click Log In. Do *not* click register, as this will just lead to a blank screen.
|
4. To create an account, simply enter the details you wish to use at the login screen then click Log In. Do *not* click register, as this will just lead to a blank screen.
|
||||||
@@ -28,8 +28,7 @@ OpenFusion is a reverse-engineered server for FusionFall. It primarily targets v
|
|||||||
Instructions for getting the client to run on Linux through Wine can be found [here](https://github.com/OpenFusionProject/OpenFusion/wiki/Running-the-game-client-on-Linux).
|
Instructions for getting the client to run on Linux through Wine can be found [here](https://github.com/OpenFusionProject/OpenFusion/wiki/Running-the-game-client-on-Linux).
|
||||||
|
|
||||||
### Hosting a server
|
### Hosting a server
|
||||||
|
1. Grab `OpenFusionServer-1.5-Original.zip` or `OpenFusionServer-1.5-Academy.zip` from [here](https://github.com/OpenFusionProject/OpenFusion/releases/tag/1.5).
|
||||||
1. Grab `OpenFusionServer-1.4-original.zip` or `OpenFusionServer-1.4-academy.zip` from [here](https://github.com/OpenFusionProject/OpenFusion/releases/tag/1.4).
|
|
||||||
2. Extract it to a folder of your choice, then run `winfusion.exe` (Windows) or `fusion` (Linux) to start the server.
|
2. Extract it to a folder of your choice, then run `winfusion.exe` (Windows) or `fusion` (Linux) to start the server.
|
||||||
3. Add a new server to the client's list:
|
3. Add a new server to the client's list:
|
||||||
1. For Description, enter anything you want. This is what will show up in the server list.
|
1. For Description, enter anything you want. This is what will show up in the server list.
|
||||||
@@ -54,10 +53,7 @@ FusionFall consists of the following components:
|
|||||||
|
|
||||||
The original game made use of the player's actual web browser to launch the game, but since then the NPAPI plugin interface the game relied on has been deprecated and is no longer available in most modern browsers. Both Retro and OpenFusion get around this issue by distributing an older version of Electron, a software package that is essentially a specialized web browser.
|
The original game made use of the player's actual web browser to launch the game, but since then the NPAPI plugin interface the game relied on has been deprecated and is no longer available in most modern browsers. Both Retro and OpenFusion get around this issue by distributing an older version of Electron, a software package that is essentially a specialized web browser.
|
||||||
|
|
||||||
The browser/Electron client opens a web page with an `<embed>` tag of MIME type `application/vnd.unity`, where the `src` param is the address of the game's `.unity3d` entrypoint.
|
The browser/Electron client opens a web page with an `<embed>` tag of the appropriate MIME type, where the `src` param is the address of the game's `.unity3d` entrypoint. This triggers the browser to load an NPAPI plugin that handles said MIME type, in this case the Unity Web Player.
|
||||||
|
|
||||||
This triggers the browser to load an NPAPI plugin that handles this MIME type, the Unity Web Player, which the browser looks for in `C:\Users\%USERNAME%\AppData\LocalLow\Unity\WebPlayer`.
|
|
||||||
The Web Player was previously copied there by `installUnity.bat`.
|
|
||||||
|
|
||||||
Note that the version of the web player distributed with OpenFusion expects a standard `UnityWeb` magic number for all assets, instead of Retro's modified `streamed` magic number.
|
Note that the version of the web player distributed with OpenFusion expects a standard `UnityWeb` magic number for all assets, instead of Retro's modified `streamed` magic number.
|
||||||
This will potentially become relevant later, as people start experimenting and mixing and matching versions.
|
This will potentially become relevant later, as people start experimenting and mixing and matching versions.
|
||||||
@@ -66,7 +62,7 @@ The web player will execute the game code, which will request the following file
|
|||||||
|
|
||||||
`/assetInfo.php` contains the address from which to fetch the rest of the game's assets (the "dongresources").
|
`/assetInfo.php` contains the address from which to fetch the rest of the game's assets (the "dongresources").
|
||||||
Normally those would be hosted on the same web server as the gateway, but the OpenFusion distribution (in it's default configuration) doesn't use a web server at all!
|
Normally those would be hosted on the same web server as the gateway, but the OpenFusion distribution (in it's default configuration) doesn't use a web server at all!
|
||||||
It loads the web pages locally using the `file://` schema, and fetches the game's assets from Turner's CDN (which is still hosting them to this day!).
|
It instead loads the web pages locally using the `file://` schema, and fetches the game's assets from a standard web server.
|
||||||
|
|
||||||
`/loginInfo.php` contains the IP:port pair of the FusionFall login server, which the client will connect to. This login server drives the client while it's in the Character Selection menu, as well as Character Creation and the Tutorial.
|
`/loginInfo.php` contains the IP:port pair of the FusionFall login server, which the client will connect to. This login server drives the client while it's in the Character Selection menu, as well as Character Creation and the Tutorial.
|
||||||
|
|
||||||
@@ -102,26 +98,13 @@ If you'd like to contribute to this project, please read [CONTRIBUTING.md](CONTR
|
|||||||
## Gameplay
|
## Gameplay
|
||||||
|
|
||||||
The goal of the project is to faithfully recreate the game as it was at the time of the targeted build.
|
The goal of the project is to faithfully recreate the game as it was at the time of the targeted build.
|
||||||
The server is not yet complete, however, and some functionality is still missing.
|
While most features are implemented and the game is playable start to finish, there may be missing functionality or bugs present.
|
||||||
|
|
||||||
Because the server is still in development, ordinary players are allowed access to a few admin commands:
|
Depending on the server configuration, you'll have access to certain commands.
|
||||||
|
|
||||||

|
For the public servers: Original has item spawning, the ability to set player speed/jump height, and teleportation enabled (default account level 50).
|
||||||
|
Meanwhile the Academy server is more meant for legitimate playthroughs (default account level 99).
|
||||||
|
|
||||||
### Movement commands
|
When hosting a local server, you will have access to all commands by default (account level 1).
|
||||||
* A `/speed` of around 2400 or 3000 is nice.
|
|
||||||
* A `/jump` of about 50 will send you soaring
|
|
||||||
* [This map](res/dong_number_map.png) (credit to Danny O) is useful for `/warp` coordinates.
|
|
||||||
* `/goto` is useful for more precise teleportation (ie. for getting into Infected Zones, etc.).
|
|
||||||
|
|
||||||
### Item commands
|
For a list of available commands, see [this wiki page](https://github.com/OpenFusionProject/OpenFusion/wiki/Ingame-Command-list).
|
||||||
* `/itemN [type] [itemId] [amount]`
|
|
||||||
(Refer to the [item list](https://docs.google.com/spreadsheets/d/1mpoJ9iTHl_xLI4wQ_9UvIDYNcsDYscdkyaGizs43TCg/))
|
|
||||||
|
|
||||||
### Nano commands
|
|
||||||
* `/nano [id] (1-36)`
|
|
||||||
* `/nano_equip [id] (1-36) [slot] (0-2)`
|
|
||||||
* `/nano_unequip [slot] (0-2)`
|
|
||||||
* `/nano_active [slot] (0-2)`
|
|
||||||
|
|
||||||
### A full list of commands can be found [here](https://github.com/OpenFusionProject/OpenFusion/wiki/Ingame-Command-list).
|
|
||||||
|
1271
src/Abilities.cpp
1271
src/Abilities.cpp
File diff suppressed because it is too large
Load Diff
@@ -1,64 +1,112 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "core/Core.hpp"
|
#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 {
|
#include <map>
|
||||||
int16_t skillType;
|
#include <vector>
|
||||||
int32_t bitFlag;
|
#include <assert.h>
|
||||||
int16_t timeBuffID;
|
|
||||||
PowerHandler handler;
|
|
||||||
|
|
||||||
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) {
|
enum class SkillType {
|
||||||
if (handler == nullptr)
|
DAMAGE = 1,
|
||||||
return;
|
HEAL_HP = 2,
|
||||||
|
KNOCKDOWN = 3, // uses DamageNDebuff
|
||||||
handler(sock, targetData, nanoID, skillID, duration, amount, skillType, bitFlag, timeBuffID);
|
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 {
|
enum class SkillTargetType {
|
||||||
int16_t skillType;
|
MOBS = 1,
|
||||||
int32_t bitFlag;
|
PLAYERS = 2,
|
||||||
int16_t timeBuffID;
|
GROUP = 3
|
||||||
MobPowerHandler handler;
|
};
|
||||||
|
|
||||||
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) {
|
struct SkillResult {
|
||||||
if (handler == nullptr)
|
size_t size;
|
||||||
return;
|
uint8_t payload[MAX_SKILLRESULT_SIZE];
|
||||||
|
SkillResult(size_t len, void* dat) {
|
||||||
handler(mob, targetData, skillID, duration, amount, skillType, bitFlag, timeBuffID);
|
assert(len <= MAX_SKILLRESULT_SIZE);
|
||||||
|
size = len;
|
||||||
|
memcpy(payload, dat, len);
|
||||||
|
}
|
||||||
|
SkillResult() {
|
||||||
|
size = 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SkillData {
|
struct SkillData {
|
||||||
int skillType;
|
SkillType skillType; // eST
|
||||||
int targetType;
|
SkillEffectTarget effectTarget;
|
||||||
int drainType;
|
int effectType; // always 1?
|
||||||
|
SkillTargetType targetType;
|
||||||
|
SkillDrainType drainType;
|
||||||
int effectArea;
|
int effectArea;
|
||||||
|
|
||||||
int batteryUse[4];
|
int batteryUse[4];
|
||||||
int durationTime[4];
|
int durationTime[4];
|
||||||
int powerIntensity[4];
|
|
||||||
|
int valueTypes[3];
|
||||||
|
int values[3][4];
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace Nanos {
|
namespace Abilities {
|
||||||
extern std::vector<NanoPower> NanoPowers;
|
|
||||||
extern std::map<int32_t, SkillData> SkillTable;
|
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);
|
void useNanoSkill(CNSocket*, SkillData*, sNano&, std::vector<ICombatant*>);
|
||||||
int applyBuff(CNSocket* sock, int skillID, int eTBU, int eTBT, int32_t groupFlags);
|
void useNPCSkill(EntityRef, int skillID, std::vector<ICombatant*>);
|
||||||
|
|
||||||
std::vector<int> findTargets(Player* plr, int skillID, CNPacketData* data = nullptr);
|
std::vector<ICombatant*> matchTargets(ICombatant*, SkillData*, int, int32_t*);
|
||||||
}
|
int getCSTBFromST(SkillType skillType);
|
||||||
|
|
||||||
namespace Combat {
|
|
||||||
extern std::vector<MobPower> MobPowers;
|
|
||||||
}
|
}
|
||||||
|
@@ -1,15 +1,10 @@
|
|||||||
#include "servers/CNShardServer.hpp"
|
|
||||||
#include "Buddies.hpp"
|
#include "Buddies.hpp"
|
||||||
#include "PlayerManager.hpp"
|
|
||||||
#include "Buddies.hpp"
|
|
||||||
#include "db/Database.hpp"
|
|
||||||
#include "Items.hpp"
|
|
||||||
#include "db/Database.hpp"
|
|
||||||
|
|
||||||
#include <iostream>
|
#include "db/Database.hpp"
|
||||||
#include <chrono>
|
#include "servers/CNShardServer.hpp"
|
||||||
#include <algorithm>
|
|
||||||
#include <thread>
|
#include "Player.hpp"
|
||||||
|
#include "PlayerManager.hpp"
|
||||||
|
|
||||||
using namespace Buddies;
|
using namespace Buddies;
|
||||||
|
|
||||||
|
@@ -1,7 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Player.hpp"
|
|
||||||
#include "core/Core.hpp"
|
|
||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
|
|
||||||
namespace Buddies {
|
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 "BuiltinCommands.hpp"
|
||||||
|
|
||||||
|
#include "servers/CNShardServer.hpp"
|
||||||
|
|
||||||
|
#include "Player.hpp"
|
||||||
#include "PlayerManager.hpp"
|
#include "PlayerManager.hpp"
|
||||||
#include "Chat.hpp"
|
|
||||||
#include "Items.hpp"
|
#include "Items.hpp"
|
||||||
#include "Missions.hpp"
|
#include "Missions.hpp"
|
||||||
|
#include "Chat.hpp"
|
||||||
#include "Nanos.hpp"
|
#include "Nanos.hpp"
|
||||||
#include "Rand.hpp"
|
|
||||||
|
|
||||||
// helper function, not a packet handler
|
// helper function, not a packet handler
|
||||||
void BuiltinCommands::setSpecialState(CNSocket* sock, CNPacketData* data) {
|
void BuiltinCommands::setSpecialState(CNSocket* sock, CNPacketData* data) {
|
||||||
@@ -69,10 +72,10 @@ static void setValuePlayer(CNSocket* sock, CNPacketData* data) {
|
|||||||
|
|
||||||
// Handle serverside value-changes
|
// Handle serverside value-changes
|
||||||
switch (setData->iSetValueType) {
|
switch (setData->iSetValueType) {
|
||||||
case 1:
|
case CN_GM_SET_VALUE_TYPE__HP:
|
||||||
response.iSetValue = plr->HP = setData->iSetValue;
|
response.iSetValue = plr->HP = setData->iSetValue;
|
||||||
break;
|
break;
|
||||||
case 2:
|
case CN_GM_SET_VALUE_TYPE__WEAPON_BATTERY :
|
||||||
plr->batteryW = setData->iSetValue;
|
plr->batteryW = setData->iSetValue;
|
||||||
|
|
||||||
// caps
|
// caps
|
||||||
@@ -81,7 +84,7 @@ static void setValuePlayer(CNSocket* sock, CNPacketData* data) {
|
|||||||
|
|
||||||
response.iSetValue = plr->batteryW;
|
response.iSetValue = plr->batteryW;
|
||||||
break;
|
break;
|
||||||
case 3:
|
case CN_GM_SET_VALUE_TYPE__NANO_BATTERY:
|
||||||
plr->batteryN = setData->iSetValue;
|
plr->batteryN = setData->iSetValue;
|
||||||
|
|
||||||
// caps
|
// caps
|
||||||
@@ -90,13 +93,17 @@ static void setValuePlayer(CNSocket* sock, CNPacketData* data) {
|
|||||||
|
|
||||||
response.iSetValue = plr->batteryN;
|
response.iSetValue = plr->batteryN;
|
||||||
break;
|
break;
|
||||||
case 4:
|
case CN_GM_SET_VALUE_TYPE__FUSION_MATTER:
|
||||||
Missions::updateFusionMatter(sock, setData->iSetValue - plr->fusionmatter);
|
Missions::updateFusionMatter(sock, setData->iSetValue - plr->fusionmatter);
|
||||||
response.iSetValue = plr->fusionmatter;
|
response.iSetValue = plr->fusionmatter;
|
||||||
break;
|
break;
|
||||||
case 5:
|
case CN_GM_SET_VALUE_TYPE__CANDY:
|
||||||
response.iSetValue = plr->money = setData->iSetValue;
|
response.iSetValue = plr->money = setData->iSetValue;
|
||||||
break;
|
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.iPC_ID = setData->iPC_ID;
|
||||||
@@ -253,17 +260,17 @@ static void teleportPlayer(CNSocket *sock, CNPacketData *data) {
|
|||||||
uint64_t instance = plr->instanceID;
|
uint64_t instance = plr->instanceID;
|
||||||
const int unstickRange = 400;
|
const int unstickRange = 400;
|
||||||
|
|
||||||
switch (req->eTeleportType) {
|
switch ((eCN_GM_TeleportType)req->eTeleportType) {
|
||||||
case eCN_GM_TeleportMapType__MyLocation:
|
case eCN_GM_TeleportType::MyLocation:
|
||||||
PlayerManager::sendPlayerTo(targetSock, plr->x, plr->y, plr->z, instance);
|
PlayerManager::sendPlayerTo(targetSock, plr->x, plr->y, plr->z, instance);
|
||||||
break;
|
break;
|
||||||
case eCN_GM_TeleportMapType__MapXYZ:
|
case eCN_GM_TeleportType::MapXYZ:
|
||||||
instance = req->iToMap;
|
instance = req->iToMap;
|
||||||
// fallthrough
|
// fallthrough
|
||||||
case eCN_GM_TeleportMapType__XYZ:
|
case eCN_GM_TeleportType::XYZ:
|
||||||
PlayerManager::sendPlayerTo(targetSock, req->iToX, req->iToY, req->iToZ, instance);
|
PlayerManager::sendPlayerTo(targetSock, req->iToX, req->iToY, req->iToZ, instance);
|
||||||
break;
|
break;
|
||||||
case eCN_GM_TeleportMapType__SomeoneLocation:
|
case eCN_GM_TeleportType::SomeoneLocation:
|
||||||
// player to teleport to
|
// player to teleport to
|
||||||
goalSock = PlayerManager::getSockFromAny(req->eGoalPCSearchBy, req->iGoalPC_ID, req->iGoalPC_UID,
|
goalSock = PlayerManager::getSockFromAny(req->eGoalPCSearchBy, req->iGoalPC_ID, req->iGoalPC_UID,
|
||||||
AUTOU16TOU8(req->szGoalPC_FirstName), AUTOU16TOU8(req->szGoalPC_LastName));
|
AUTOU16TOU8(req->szGoalPC_FirstName), AUTOU16TOU8(req->szGoalPC_LastName));
|
||||||
@@ -275,7 +282,7 @@ static void teleportPlayer(CNSocket *sock, CNPacketData *data) {
|
|||||||
|
|
||||||
PlayerManager::sendPlayerTo(targetSock, goalPlr->x, goalPlr->y, goalPlr->z, goalPlr->instanceID);
|
PlayerManager::sendPlayerTo(targetSock, goalPlr->x, goalPlr->y, goalPlr->z, goalPlr->instanceID);
|
||||||
break;
|
break;
|
||||||
case eCN_GM_TeleportMapType__Unstick:
|
case eCN_GM_TeleportType::Unstick:
|
||||||
targetPlr = PlayerManager::getPlayer(targetSock);
|
targetPlr = PlayerManager::getPlayer(targetSock);
|
||||||
|
|
||||||
PlayerManager::sendPlayerTo(targetSock, targetPlr->x - unstickRange/2 + Rand::rand(unstickRange),
|
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 "Chat.hpp"
|
||||||
|
|
||||||
|
#include "servers/CNShardServer.hpp"
|
||||||
|
|
||||||
|
#include "Player.hpp"
|
||||||
#include "PlayerManager.hpp"
|
#include "PlayerManager.hpp"
|
||||||
#include "Groups.hpp"
|
|
||||||
#include "CustomCommands.hpp"
|
#include "CustomCommands.hpp"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
@@ -225,10 +228,6 @@ static void tradeChatHandler(CNSocket* sock, CNPacketData* data) {
|
|||||||
static void groupChatHandler(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;
|
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* plr = PlayerManager::getPlayer(sock);
|
||||||
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
|
||||||
|
|
||||||
if (otherPlr == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
std::string fullChat = sanitizeText(AUTOU16TOU8(chat->szFreeChat));
|
std::string fullChat = sanitizeText(AUTOU16TOU8(chat->szFreeChat));
|
||||||
|
|
||||||
@@ -251,16 +250,15 @@ static void groupChatHandler(CNSocket* sock, CNPacketData* data) {
|
|||||||
resp.iSendPCID = plr->iID;
|
resp.iSendPCID = plr->iID;
|
||||||
resp.iEmoteCode = chat->iEmoteCode;
|
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) {
|
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;
|
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* plr = PlayerManager::getPlayer(sock);
|
||||||
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
|
||||||
|
|
||||||
if (otherPlr == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
std::string fullChat = sanitizeText(AUTOU16TOU8(chat->szFreeChat));
|
std::string fullChat = sanitizeText(AUTOU16TOU8(chat->szFreeChat));
|
||||||
std::string logLine = "[GroupMenuChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat;
|
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.iSendPCID = plr->iID;
|
||||||
resp.iEmoteCode = chat->iEmoteCode;
|
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
|
// we only allow plain ascii, at least for now
|
||||||
|
@@ -2,7 +2,10 @@
|
|||||||
|
|
||||||
#define CMD_PREFIX '/'
|
#define CMD_PREFIX '/'
|
||||||
|
|
||||||
#include "servers/CNShardServer.hpp"
|
#include "core/Core.hpp"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace Chat {
|
namespace Chat {
|
||||||
extern std::vector<std::string> dump;
|
extern std::vector<std::string> dump;
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
#include "Chunking.hpp"
|
#include "Chunking.hpp"
|
||||||
#include "PlayerManager.hpp"
|
|
||||||
|
#include "MobAI.hpp"
|
||||||
#include "NPCManager.hpp"
|
#include "NPCManager.hpp"
|
||||||
#include "settings.hpp"
|
|
||||||
#include "Combat.hpp"
|
#include <assert.h>
|
||||||
#include "Eggs.hpp"
|
|
||||||
|
|
||||||
using namespace Chunking;
|
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
|
// add the chunk to the cache of all players and NPCs in the surrounding chunks
|
||||||
std::set<Chunk*> surroundings = getViewableChunks(pos);
|
std::set<Chunk*> surroundings = getViewableChunks(pos);
|
||||||
for (Chunk* c : surroundings)
|
for (Chunk* c : surroundings)
|
||||||
for (const EntityRef& ref : c->entities)
|
for (const EntityRef ref : c->entities)
|
||||||
ref.getEntity()->viewableChunks.insert(chunk);
|
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
|
// remove the chunk from the cache of all players and NPCs in the surrounding chunks
|
||||||
std::set<Chunk*> surroundings = getViewableChunks(pos);
|
std::set<Chunk*> surroundings = getViewableChunks(pos);
|
||||||
for(Chunk* c : surroundings)
|
for(Chunk* c : surroundings)
|
||||||
for (const EntityRef& ref : c->entities)
|
for (const EntityRef ref : c->entities)
|
||||||
ref.getEntity()->viewableChunks.erase(chunk);
|
ref.getEntity()->viewableChunks.erase(chunk);
|
||||||
|
|
||||||
chunks.erase(pos); // remove from map
|
chunks.erase(pos); // remove from map
|
||||||
delete chunk; // free from memory
|
delete chunk; // free from memory
|
||||||
}
|
}
|
||||||
|
|
||||||
void Chunking::trackEntity(ChunkPos chunkPos, const EntityRef& ref) {
|
void Chunking::trackEntity(ChunkPos chunkPos, const EntityRef ref) {
|
||||||
if (!chunkExists(chunkPos))
|
if (!chunkExists(chunkPos))
|
||||||
return; // shouldn't happen
|
return; // shouldn't happen
|
||||||
|
|
||||||
chunks[chunkPos]->entities.insert(ref);
|
chunks[chunkPos]->entities.insert(ref);
|
||||||
|
|
||||||
if (ref.type == EntityType::PLAYER)
|
if (ref.kind == EntityKind::PLAYER)
|
||||||
chunks[chunkPos]->nplayers++;
|
chunks[chunkPos]->nplayers++;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef& ref) {
|
void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef ref) {
|
||||||
if (!chunkExists(chunkPos))
|
if (!chunkExists(chunkPos))
|
||||||
return; // do nothing if chunk doesn't even exist
|
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
|
chunk->entities.erase(ref); // gone
|
||||||
|
|
||||||
if (ref.type == EntityType::PLAYER)
|
if (ref.kind == EntityKind::PLAYER)
|
||||||
chunks[chunkPos]->nplayers--;
|
chunks[chunkPos]->nplayers--;
|
||||||
assert(chunks[chunkPos]->nplayers >= 0);
|
assert(chunks[chunkPos]->nplayers >= 0);
|
||||||
|
|
||||||
@@ -75,13 +75,13 @@ void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef& ref) {
|
|||||||
deleteChunk(chunkPos);
|
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();
|
Entity *ent = ref.getEntity();
|
||||||
bool alive = ent->isAlive();
|
bool alive = ent->isExtant();
|
||||||
|
|
||||||
// TODO: maybe optimize this, potentially using AROUND packets?
|
// TODO: maybe optimize this, potentially using AROUND packets?
|
||||||
for (Chunk *chunk : chnks) {
|
for (Chunk *chunk : chnks) {
|
||||||
for (const EntityRef& otherRef : chunk->entities) {
|
for (const EntityRef otherRef : chunk->entities) {
|
||||||
// skip oneself
|
// skip oneself
|
||||||
if (ref == otherRef)
|
if (ref == otherRef)
|
||||||
continue;
|
continue;
|
||||||
@@ -89,31 +89,31 @@ void Chunking::addEntityToChunks(std::set<Chunk*> chnks, const EntityRef& ref) {
|
|||||||
Entity *other = otherRef.getEntity();
|
Entity *other = otherRef.getEntity();
|
||||||
|
|
||||||
// notify all visible players of the existence of this Entity
|
// 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);
|
ent->enterIntoViewOf(otherRef.sock);
|
||||||
}
|
}
|
||||||
|
|
||||||
// notify this *player* of the existence of all visible Entities
|
// 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);
|
other->enterIntoViewOf(ref.sock);
|
||||||
}
|
}
|
||||||
|
|
||||||
// for mobs, increment playersInView
|
// 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++;
|
((Mob*)ent)->playersInView++;
|
||||||
if (otherRef.type == EntityType::MOB && ref.type == EntityType::PLAYER)
|
if (otherRef.kind == EntityKind::MOB && ref.kind == EntityKind::PLAYER)
|
||||||
((Mob*)other)->playersInView++;
|
((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();
|
Entity *ent = ref.getEntity();
|
||||||
bool alive = ent->isAlive();
|
bool alive = ent->isExtant();
|
||||||
|
|
||||||
// TODO: same as above
|
// TODO: same as above
|
||||||
for (Chunk *chunk : chnks) {
|
for (Chunk *chunk : chnks) {
|
||||||
for (const EntityRef& otherRef : chunk->entities) {
|
for (const EntityRef otherRef : chunk->entities) {
|
||||||
// skip oneself
|
// skip oneself
|
||||||
if (ref == otherRef)
|
if (ref == otherRef)
|
||||||
continue;
|
continue;
|
||||||
@@ -121,19 +121,19 @@ void Chunking::removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef& r
|
|||||||
Entity *other = otherRef.getEntity();
|
Entity *other = otherRef.getEntity();
|
||||||
|
|
||||||
// notify all visible players of the departure of this Entity
|
// 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);
|
ent->disappearFromViewOf(otherRef.sock);
|
||||||
}
|
}
|
||||||
|
|
||||||
// notify this *player* of the departure of all visible Entities
|
// 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);
|
other->disappearFromViewOf(ref.sock);
|
||||||
}
|
}
|
||||||
|
|
||||||
// for mobs, decrement playersInView
|
// 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--;
|
((Mob*)ent)->playersInView--;
|
||||||
if (otherRef.type == EntityType::MOB && ref.type == EntityType::PLAYER)
|
if (otherRef.kind == EntityKind::MOB && ref.kind == EntityKind::PLAYER)
|
||||||
((Mob*)other)->playersInView--;
|
((Mob*)other)->playersInView--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,8 +154,8 @@ static void emptyChunk(ChunkPos chunkPos) {
|
|||||||
|
|
||||||
// unspawn all of the mobs/npcs
|
// unspawn all of the mobs/npcs
|
||||||
std::set refs(chunk->entities);
|
std::set refs(chunk->entities);
|
||||||
for (const EntityRef& ref : refs) {
|
for (const EntityRef ref : refs) {
|
||||||
if (ref.type == EntityType::PLAYER)
|
if (ref.kind == EntityKind::PLAYER)
|
||||||
assert(0);
|
assert(0);
|
||||||
|
|
||||||
// every call of this will check if the chunk is empty and delete it if so
|
// 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();
|
Entity* ent = ref.getEntity();
|
||||||
|
|
||||||
// move to other chunk's player set
|
// move to other chunk's player set
|
||||||
@@ -267,54 +267,53 @@ void Chunking::createInstance(uint64_t instanceID) {
|
|||||||
|
|
||||||
std::cout << "Creating instance " << instanceID << std::endl;
|
std::cout << "Creating instance " << instanceID << std::endl;
|
||||||
for (ChunkPos &coords : templateChunks) {
|
for (ChunkPos &coords : templateChunks) {
|
||||||
for (const EntityRef& ref : chunks[coords]->entities) {
|
for (const EntityRef ref : chunks[coords]->entities) {
|
||||||
if (ref.type == EntityType::PLAYER)
|
if (ref.kind == EntityKind::PLAYER)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
int npcID = ref.id;
|
int npcID = ref.id;
|
||||||
BaseNPC* baseNPC = (BaseNPC*)ref.getEntity();
|
BaseNPC* baseNPC = (BaseNPC*)ref.getEntity();
|
||||||
|
|
||||||
// make a copy of each NPC in the template chunks and put them in the new instance
|
// 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)
|
if (((Mob*)baseNPC)->groupLeader != 0 && ((Mob*)baseNPC)->groupLeader != npcID)
|
||||||
continue; // follower; don't copy individually
|
continue; // follower; don't copy individually
|
||||||
|
|
||||||
Mob* newMob = new Mob(baseNPC->x, baseNPC->y, baseNPC->z, baseNPC->appearanceData.iAngle,
|
Mob* newMob = new Mob(baseNPC->x, baseNPC->y, baseNPC->z, baseNPC->angle,
|
||||||
instanceID, baseNPC->appearanceData.iNPCType, NPCManager::NPCData[baseNPC->appearanceData.iNPCType], NPCManager::nextId--);
|
instanceID, baseNPC->type, NPCManager::NPCData[baseNPC->type], NPCManager::nextId--);
|
||||||
NPCManager::NPCs[newMob->appearanceData.iNPC_ID] = newMob;
|
NPCManager::NPCs[newMob->id] = newMob;
|
||||||
|
|
||||||
// if in a group, copy over group members as well
|
// if in a group, copy over group members as well
|
||||||
if (((Mob*)baseNPC)->groupLeader != 0) {
|
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;
|
Mob* mobData = (Mob*)baseNPC;
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
if (mobData->groupMember[i] != 0) {
|
if (mobData->groupMember[i] != 0) {
|
||||||
int followerID = NPCManager::nextId--; // id for follower
|
int followerID = NPCManager::nextId--; // id for follower
|
||||||
BaseNPC* baseFollower = NPCManager::NPCs[mobData->groupMember[i]]; // follower from template
|
BaseNPC* baseFollower = NPCManager::NPCs[mobData->groupMember[i]]; // follower from template
|
||||||
// new follower instance
|
// new follower instance
|
||||||
Mob* newMobFollower = new Mob(baseFollower->x, baseFollower->y, baseFollower->z, baseFollower->appearanceData.iAngle,
|
Mob* newMobFollower = new Mob(baseFollower->x, baseFollower->y, baseFollower->z, baseFollower->angle,
|
||||||
instanceID, baseFollower->appearanceData.iNPCType, NPCManager::NPCData[baseFollower->appearanceData.iNPCType], followerID);
|
instanceID, baseFollower->type, NPCManager::NPCData[baseFollower->type], followerID);
|
||||||
// add follower to NPC maps
|
// add follower to NPC maps
|
||||||
NPCManager::NPCs[followerID] = newMobFollower;
|
NPCManager::NPCs[followerID] = newMobFollower;
|
||||||
// set follower-specific properties
|
// set follower-specific properties
|
||||||
newMobFollower->groupLeader = newMob->appearanceData.iNPC_ID;
|
newMobFollower->groupLeader = newMob->id;
|
||||||
newMobFollower->offsetX = ((Mob*)baseFollower)->offsetX;
|
newMobFollower->offsetX = ((Mob*)baseFollower)->offsetX;
|
||||||
newMobFollower->offsetY = ((Mob*)baseFollower)->offsetY;
|
newMobFollower->offsetY = ((Mob*)baseFollower)->offsetY;
|
||||||
// add follower copy to leader copy
|
// add follower copy to leader copy
|
||||||
newMob->groupMember[i] = followerID;
|
newMob->groupMember[i] = followerID;
|
||||||
NPCManager::updateNPCPosition(followerID, baseFollower->x, baseFollower->y, baseFollower->z,
|
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,
|
NPCManager::updateNPCPosition(newMob->id, baseNPC->x, baseNPC->y, baseNPC->z,
|
||||||
instanceID, baseNPC->appearanceData.iAngle);
|
instanceID, baseNPC->angle);
|
||||||
} else {
|
} else {
|
||||||
BaseNPC* newNPC = new BaseNPC(baseNPC->x, baseNPC->y, baseNPC->z, baseNPC->appearanceData.iAngle,
|
BaseNPC* newNPC = new BaseNPC(baseNPC->angle, instanceID, baseNPC->type, NPCManager::nextId--);
|
||||||
instanceID, baseNPC->appearanceData.iNPCType, NPCManager::nextId--);
|
NPCManager::NPCs[newNPC->id] = newNPC;
|
||||||
NPCManager::NPCs[newNPC->appearanceData.iNPC_ID] = newNPC;
|
NPCManager::updateNPCPosition(newNPC->id, baseNPC->x, baseNPC->y, baseNPC->z,
|
||||||
NPCManager::updateNPCPosition(newNPC->appearanceData.iNPC_ID, baseNPC->x, baseNPC->y, baseNPC->z,
|
instanceID, baseNPC->angle);
|
||||||
instanceID, baseNPC->appearanceData.iAngle);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,14 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "core/Core.hpp"
|
#include "EntityRef.hpp"
|
||||||
|
|
||||||
#include <utility>
|
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <tuple>
|
#include <vector>
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
struct EntityRef;
|
|
||||||
|
|
||||||
class Chunk {
|
class Chunk {
|
||||||
public:
|
public:
|
||||||
@@ -36,13 +32,13 @@ namespace Chunking {
|
|||||||
|
|
||||||
extern const ChunkPos INVALID_CHUNK;
|
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 trackEntity(ChunkPos chunkPos, const EntityRef ref);
|
||||||
void untrackEntity(ChunkPos chunkPos, const EntityRef& ref);
|
void untrackEntity(ChunkPos chunkPos, const EntityRef ref);
|
||||||
|
|
||||||
void addEntityToChunks(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);
|
void removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef ref);
|
||||||
|
|
||||||
bool chunkExists(ChunkPos chunk);
|
bool chunkExists(ChunkPos chunk);
|
||||||
ChunkPos chunkPosAt(int posX, int posY, uint64_t instanceID);
|
ChunkPos chunkPosAt(int posX, int posY, uint64_t instanceID);
|
||||||
|
697
src/Combat.cpp
697
src/Combat.cpp
@@ -1,22 +1,331 @@
|
|||||||
#include "Combat.hpp"
|
#include "Combat.hpp"
|
||||||
#include "PlayerManager.hpp"
|
|
||||||
#include "Nanos.hpp"
|
#include "servers/CNShardServer.hpp"
|
||||||
#include "NPCManager.hpp"
|
|
||||||
#include "Items.hpp"
|
|
||||||
#include "Missions.hpp"
|
|
||||||
#include "Groups.hpp"
|
|
||||||
#include "Transport.hpp"
|
|
||||||
#include "Racing.hpp"
|
|
||||||
#include "Abilities.hpp"
|
|
||||||
#include "Rand.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 <assert.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
using namespace Combat;
|
using namespace Combat;
|
||||||
|
|
||||||
/// Player Id -> Bullet Id -> Bullet
|
/// Player Id -> Bullet Id -> Bullet
|
||||||
std::map<int32_t, std::map<int8_t, Bullet>> Combat::Bullets;
|
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,
|
static std::pair<int,int> getDamage(int attackPower, int defensePower, bool shouldCrit,
|
||||||
bool batteryBoost, int attackerStyle,
|
bool batteryBoost, int attackerStyle,
|
||||||
int defenderStyle, int difficulty) {
|
int defenderStyle, int difficulty) {
|
||||||
@@ -56,7 +365,7 @@ static std::pair<int,int> getDamage(int attackPower, int defensePower, bool shou
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool checkRapidFire(CNSocket *sock, int targetCount) {
|
static bool checkRapidFire(CNSocket *sock, int targetCount, bool allowManyTargets) {
|
||||||
Player *plr = PlayerManager::getPlayer(sock);
|
Player *plr = PlayerManager::getPlayer(sock);
|
||||||
time_t currTime = getTime();
|
time_t currTime = getTime();
|
||||||
|
|
||||||
@@ -68,7 +377,7 @@ static bool checkRapidFire(CNSocket *sock, int targetCount) {
|
|||||||
plr->lastShot = currTime;
|
plr->lastShot = currTime;
|
||||||
|
|
||||||
// 3+ targets should never be possible
|
// 3+ targets should never be possible
|
||||||
if (targetCount > 3)
|
if (!allowManyTargets && targetCount > 3)
|
||||||
plr->suspicionRating += 10001;
|
plr->suspicionRating += 10001;
|
||||||
|
|
||||||
// kill the socket when the player is too suspicious
|
// kill the socket when the player is too suspicious
|
||||||
@@ -87,7 +396,7 @@ static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) {
|
|||||||
auto targets = (int32_t*)data->trailers;
|
auto targets = (int32_t*)data->trailers;
|
||||||
|
|
||||||
// kick the player if firing too rapidly
|
// kick the player if firing too rapidly
|
||||||
if (settings::ANTICHEAT && checkRapidFire(sock, pkt->iNPCCnt))
|
if (settings::ANTICHEAT && checkRapidFire(sock, pkt->iNPCCnt, false))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -114,7 +423,7 @@ static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) {
|
|||||||
|
|
||||||
|
|
||||||
BaseNPC* npc = NPCManager::NPCs[targets[i]];
|
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;
|
std::cout << "[WARN] pcAttackNpcs: NPC is not a mob" << std::endl;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -137,11 +446,11 @@ static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) {
|
|||||||
else
|
else
|
||||||
plr->batteryW = 0;
|
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].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
|
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))
|
if (!(plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE))
|
||||||
plr->HP -= damage.first;
|
plr->HP -= damage.first;
|
||||||
|
|
||||||
pkt->iNPC_ID = mob->appearanceData.iNPC_ID;
|
pkt->iNPC_ID = mob->id;
|
||||||
pkt->iPCCnt = 1;
|
pkt->iPCCnt = 1;
|
||||||
|
|
||||||
atk->iID = plr->iID;
|
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);
|
PlayerManager::sendToViewable(mob->target, respbuf, P_FE2CL_NPC_ATTACK_PCs);
|
||||||
|
|
||||||
if (plr->HP <= 0) {
|
if (plr->HP <= 0) {
|
||||||
mob->target = nullptr;
|
if (!MobAI::aggroCheck(mob, getTime()))
|
||||||
mob->state = MobState::RETREAT;
|
mob->transition(AIState::RETREAT, mob->target);
|
||||||
if (!MobAI::aggroCheck(mob, currTime)) {
|
|
||||||
MobAI::clearDebuff(mob);
|
|
||||||
if (mob->groupLeader != 0)
|
|
||||||
MobAI::groupRetreat(mob);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
* 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
|
* 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
|
* single RNG roll per mission task, and every group member shares that same
|
||||||
* set of rolls.
|
* set of rolls.
|
||||||
*/
|
*/
|
||||||
static void genQItemRolls(Player *leader, std::map<int, int>& rolls) {
|
void Combat::genQItemRolls(std::vector<Player*> players, std::map<int, int>& rolls) {
|
||||||
for (int i = 0; i < leader->groupCnt; i++) {
|
for (int i = 0; i < players.size(); i++) {
|
||||||
if (leader->groupIDs[i] == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
CNSocket *otherSock = PlayerManager::getSockFromID(leader->groupIDs[i]);
|
|
||||||
if (otherSock == nullptr)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
Player *member = PlayerManager::getPlayer(otherSock);
|
|
||||||
|
|
||||||
|
Player* member = players[i];
|
||||||
for (int j = 0; j < ACTIVE_MISSION_COUNT; j++)
|
for (int j = 0; j < ACTIVE_MISSION_COUNT; j++)
|
||||||
if (member->tasks[j] != 0)
|
if (member->tasks[j] != 0)
|
||||||
rolls[member->tasks[j]] = Rand::rand();
|
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) {
|
static void combatBegin(CNSocket *sock, CNPacketData *data) {
|
||||||
Player *plr = PlayerManager::getPlayer(sock);
|
Player *plr = PlayerManager::getPlayer(sock);
|
||||||
|
|
||||||
@@ -345,40 +533,27 @@ static void combatEnd(CNSocket *sock, CNPacketData *data) {
|
|||||||
plr->healCooldown = 4000;
|
plr->healCooldown = 4000;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dotDamageOnOff(CNSocket *sock, CNPacketData *data) {
|
static void dealGooDamage(CNSocket *sock) {
|
||||||
sP_CL2FE_DOT_DAMAGE_ONOFF *pkt = (sP_CL2FE_DOT_DAMAGE_ONOFF*)data->buf;
|
|
||||||
Player *plr = PlayerManager::getPlayer(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);
|
size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_DotDamage);
|
||||||
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
|
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
|
||||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||||
Player *plr = PlayerManager::getPlayer(sock);
|
|
||||||
|
|
||||||
memset(respbuf, 0, resplen);
|
memset(respbuf, 0, resplen);
|
||||||
|
|
||||||
sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK *pkt = (sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK*)respbuf;
|
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));
|
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
|
amount = -2; // -2 is the magic number for "Protected" to appear as the damage number
|
||||||
dmg->bProtected = 1;
|
dmg->bProtected = 1;
|
||||||
|
|
||||||
// eggs allow protection without nanos
|
// 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;
|
plr->Nanos[plr->activeNano].iStamina -= 3;
|
||||||
} else {
|
} else {
|
||||||
plr->HP -= amount;
|
plr->HP -= amount;
|
||||||
@@ -402,12 +577,39 @@ static void dealGooDamage(CNSocket *sock, int amount) {
|
|||||||
dmg->iID = plr->iID;
|
dmg->iID = plr->iID;
|
||||||
dmg->iDamage = amount;
|
dmg->iDamage = amount;
|
||||||
dmg->iHP = plr->HP;
|
dmg->iHP = plr->HP;
|
||||||
dmg->iConditionBitFlag = plr->iConditionBitFlag;
|
dmg->iConditionBitFlag = plr->getCompositeCondition();
|
||||||
|
|
||||||
sock->sendPacket((void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen);
|
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);
|
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) {
|
static void pcAttackChars(CNSocket *sock, CNPacketData *data) {
|
||||||
sP_CL2FE_REQ_PC_ATTACK_CHARs* pkt = (sP_CL2FE_REQ_PC_ATTACK_CHARs*)data->buf;
|
sP_CL2FE_REQ_PC_ATTACK_CHARs* pkt = (sP_CL2FE_REQ_PC_ATTACK_CHARs*)data->buf;
|
||||||
Player *plr = PlayerManager::getPlayer(sock);
|
Player *plr = PlayerManager::getPlayer(sock);
|
||||||
@@ -417,12 +619,12 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// Unlike the attack mob packet, attacking players packet has an 8-byte trail (Instead of 4 bytes).
|
// 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";
|
std::cout << "[WARN] bad sP_CL2FE_REQ_PC_ATTACK_CHARs packet size\n";
|
||||||
return;
|
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))) {
|
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";
|
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;
|
resp->iTargetCnt = pkt->iTargetCnt;
|
||||||
|
|
||||||
for (int i = 0; i < pkt->iTargetCnt; i++) {
|
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) {
|
for (auto& pair : PlayerManager::players) {
|
||||||
if (pair.second->iID == pktdata[i*2]) {
|
if (pair.second->iID == pktdata[i].iID) {
|
||||||
target = pair.second;
|
target = pair.second;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -457,67 +667,41 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) {
|
|||||||
return;
|
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
|
} 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
|
// not sure how to best handle this
|
||||||
std::cout << "[WARN] pcAttackChars: NPC ID not found" << std::endl;
|
std::cout << "[WARN] pcAttackChars: NPC ID not found" << std::endl;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
BaseNPC* npc = NPCManager::NPCs[pktdata[i * 2]];
|
BaseNPC* npc = NPCManager::NPCs[pktdata[i].iID];
|
||||||
if (npc->type != EntityType::MOB) {
|
if (npc->kind != EntityKind::MOB) {
|
||||||
std::cout << "[WARN] pcAttackChars: NPC is not a mob" << std::endl;
|
std::cout << "[WARN] pcAttackChars: NPC is not a mob" << std::endl;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Mob* mob = (Mob*)npc;
|
Mob* mob = (Mob*)npc;
|
||||||
|
target = mob;
|
||||||
std::pair<int,int> damage;
|
|
||||||
|
|
||||||
if (pkt->iTargetCnt > 1)
|
|
||||||
damage.first = plr->groupDamage;
|
|
||||||
else
|
|
||||||
damage.first = plr->pointDamage;
|
|
||||||
|
|
||||||
int difficulty = (int)mob->data["m_iNpcLevel"];
|
int difficulty = (int)mob->data["m_iNpcLevel"];
|
||||||
|
|
||||||
damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, (plr->batteryW > 6 + difficulty),
|
damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, (plr->batteryW > 6 + difficulty),
|
||||||
Nanos::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], 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);
|
sock->sendPacket((void*)respbuf, P_FE2CL_PC_ATTACK_CHARs_SUCC, resplen);
|
||||||
@@ -653,20 +837,9 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// rapid fire anti-cheat
|
// kick the player if firing too rapidly
|
||||||
time_t currTime = getTime();
|
if (settings::ANTICHEAT && checkRapidFire(sock, pkt->iTargetCnt, true))
|
||||||
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;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* initialize response struct
|
* initialize response struct
|
||||||
@@ -696,7 +869,7 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
BaseNPC* npc = NPCManager::NPCs[pktdata[i]];
|
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;
|
std::cout << "[WARN] projectileHit: NPC is not a mob" << std::endl;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -709,11 +882,11 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) {
|
|||||||
int difficulty = (int)mob->data["m_iNpcLevel"];
|
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 = 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].iDamage = damage.first;
|
||||||
respdata[i].iHP = mob->appearanceData.iHP;
|
respdata[i].iHP = mob->hp;
|
||||||
respdata[i].iHitFlag = damage.second;
|
respdata[i].iHitFlag = damage.second;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -728,6 +901,7 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) {
|
|||||||
|
|
||||||
static void playerTick(CNServer *serv, time_t currTime) {
|
static void playerTick(CNServer *serv, time_t currTime) {
|
||||||
static time_t lastHealTime = 0;
|
static time_t lastHealTime = 0;
|
||||||
|
static time_t lastCombatTIme = 0;
|
||||||
|
|
||||||
for (auto& pair : PlayerManager::players) {
|
for (auto& pair : PlayerManager::players) {
|
||||||
CNSocket *sock = pair.first;
|
CNSocket *sock = pair.first;
|
||||||
@@ -735,18 +909,13 @@ static void playerTick(CNServer *serv, time_t currTime) {
|
|||||||
bool transmit = false;
|
bool transmit = false;
|
||||||
|
|
||||||
// group ticks
|
// group ticks
|
||||||
if (plr->groupCnt > 1)
|
if (plr->group != nullptr)
|
||||||
Groups::groupTickInfo(plr);
|
Groups::groupTickInfo(sock);
|
||||||
|
|
||||||
// do not tick dead players
|
// do not tick dead players
|
||||||
if (plr->HP <= 0)
|
if (plr->HP <= 0)
|
||||||
continue;
|
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
|
// heal
|
||||||
if (currTime - lastHealTime >= 4000 && !plr->inCombat && plr->HP < PC_MAXHEALTH(plr->level)) {
|
if (currTime - lastHealTime >= 4000 && !plr->inCombat && plr->HP < PC_MAXHEALTH(plr->level)) {
|
||||||
if (currTime - lastHealTime - plr->healCooldown >= 4000) {
|
if (currTime - lastHealTime - plr->healCooldown >= 4000) {
|
||||||
@@ -758,22 +927,24 @@ static void playerTick(CNServer *serv, time_t currTime) {
|
|||||||
plr->healCooldown -= 4000;
|
plr->healCooldown -= 4000;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < 3; i++) {
|
// combat tick
|
||||||
if (plr->activeNano != 0 && plr->equippedNanos[i] == plr->activeNano) { // spend stamina
|
if(currTime - lastCombatTIme >= 2000) {
|
||||||
plr->Nanos[plr->activeNano].iStamina -= 1 + plr->nanoDrainRate / 5;
|
plr->step(currTime);
|
||||||
|
transmit = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (plr->Nanos[plr->activeNano].iStamina <= 0)
|
// nanos
|
||||||
Nanos::summonNano(sock, -1, true); // unsummon nano silently
|
if (plr->activeNano != 0) { // tick active nano
|
||||||
|
sNano* nano = plr->getActiveNano();
|
||||||
transmit = true;
|
if (Abilities::SkillTable.find(nano->iSkillID) != Abilities::SkillTable.end()) {
|
||||||
} else if (plr->Nanos[plr->equippedNanos[i]].iStamina < 150) { // regain stamina
|
// nano has skill data
|
||||||
sNano& nano = plr->Nanos[plr->equippedNanos[i]];
|
SkillData* skill = &Abilities::SkillTable[nano->iSkillID];
|
||||||
nano.iStamina += 1;
|
if (skill->drainType == SkillDrainType::PASSIVE) {
|
||||||
|
ICombatant* src = dynamic_cast<ICombatant*>(plr);
|
||||||
if (nano.iStamina > 150)
|
int32_t targets[] = { plr->iID };
|
||||||
nano.iStamina = 150;
|
std::vector<ICombatant*> affectedCombatants = Abilities::matchTargets(src, skill, 1, targets);
|
||||||
|
Abilities::useNanoSkill(sock, skill, *nano, affectedCombatants);
|
||||||
transmit = true;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -789,6 +960,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));
|
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) {
|
if (transmit) {
|
||||||
INITSTRUCT(sP_FE2CL_REP_PC_TICK, pkt);
|
INITSTRUCT(sP_FE2CL_REP_PC_TICK, pkt);
|
||||||
|
|
||||||
@@ -803,13 +988,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)
|
if (currTime - lastHealTime >= 4000)
|
||||||
lastHealTime = currTime;
|
lastHealTime = currTime;
|
||||||
|
if(currTime - lastCombatTIme >= 2000)
|
||||||
|
lastCombatTIme = currTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Combat::init() {
|
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);
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ATTACK_NPCs, pcAttackNpcs);
|
||||||
|
|
||||||
|
@@ -1,15 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "core/Core.hpp"
|
#include "Player.hpp"
|
||||||
#include "servers/CNShardServer.hpp"
|
|
||||||
#include "NPC.hpp"
|
|
||||||
#include "MobAI.hpp"
|
#include "MobAI.hpp"
|
||||||
|
|
||||||
#include "JSON.hpp"
|
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <unordered_map>
|
#include <vector>
|
||||||
#include <queue>
|
|
||||||
|
|
||||||
struct Bullet {
|
struct Bullet {
|
||||||
int pointDamage;
|
int pointDamage;
|
||||||
@@ -24,6 +19,5 @@ namespace Combat {
|
|||||||
void init();
|
void init();
|
||||||
|
|
||||||
void npcAttackPc(Mob *mob, time_t currTime);
|
void npcAttackPc(Mob *mob, time_t currTime);
|
||||||
int hitMob(CNSocket *sock, Mob *mob, int damage);
|
void genQItemRolls(std::vector<Player*> players, std::map<int, int>& rolls);
|
||||||
void killMob(CNSocket *sock, Mob *mob);
|
|
||||||
}
|
}
|
||||||
|
@@ -1,18 +1,19 @@
|
|||||||
#include "CustomCommands.hpp"
|
#include "CustomCommands.hpp"
|
||||||
#include "Chat.hpp"
|
|
||||||
|
#include "db/Database.hpp"
|
||||||
|
|
||||||
|
#include "Player.hpp"
|
||||||
#include "PlayerManager.hpp"
|
#include "PlayerManager.hpp"
|
||||||
|
#include "Chat.hpp"
|
||||||
#include "TableData.hpp"
|
#include "TableData.hpp"
|
||||||
#include "NPCManager.hpp"
|
#include "NPCManager.hpp"
|
||||||
#include "Eggs.hpp"
|
|
||||||
#include "MobAI.hpp"
|
#include "MobAI.hpp"
|
||||||
#include "Items.hpp"
|
|
||||||
#include "db/Database.hpp"
|
|
||||||
#include "Transport.hpp"
|
|
||||||
#include "Missions.hpp"
|
#include "Missions.hpp"
|
||||||
|
#include "Eggs.hpp"
|
||||||
|
#include "Items.hpp"
|
||||||
|
#include "Abilities.hpp"
|
||||||
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <iterator>
|
|
||||||
#include <math.h>
|
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
|
||||||
typedef void (*CommandHandler)(std::string fullString, std::vector<std::string>& args, CNSocket* sock);
|
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);
|
BaseNPC *npc = NPCManager::summonNPC(plr->x, plr->y, plr->z, plr->instanceID, type, true);
|
||||||
|
|
||||||
// update angle
|
// update angle
|
||||||
npc->appearanceData.iAngle = (plr->angle + 180) % 360;
|
npc->angle = (plr->angle + 180) % 360;
|
||||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, plr->x, plr->y, plr->z, plr->instanceID, npc->appearanceData.iAngle);
|
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 we're in a lair, we need to spawn the NPC in both the private instance and the template
|
||||||
if (PLAYERID(plr->instanceID) != 0) {
|
if (PLAYERID(plr->instanceID) != 0) {
|
||||||
npc = NPCManager::summonNPC(plr->x, plr->y, plr->z, plr->instanceID, type, true, true);
|
npc = NPCManager::summonNPC(plr->x, plr->y, plr->z, plr->instanceID, type, true, true);
|
||||||
|
|
||||||
npc->appearanceData.iAngle = (plr->angle + 180) % 360;
|
npc->angle = (plr->angle + 180) % 360;
|
||||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, plr->x, plr->y, plr->z, npc->instanceID, npc->appearanceData.iAngle);
|
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) +
|
Chat::sendServerMessage(sock, "/summonW: placed mob with type: " + std::to_string(type) +
|
||||||
", id: " + std::to_string(npc->appearanceData.iNPC_ID));
|
", id: " + std::to_string(npc->id));
|
||||||
TableData::RunningMobs[npc->appearanceData.iNPC_ID] = npc; // only record the one in the template
|
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) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TableData::RunningEggs.find(npc->appearanceData.iNPC_ID) != TableData::RunningEggs.end()) {
|
if (TableData::RunningEggs.find(npc->id) != TableData::RunningEggs.end()) {
|
||||||
Chat::sendServerMessage(sock, "/unsummonW: removed egg with type: " + std::to_string(npc->appearanceData.iNPCType) +
|
Chat::sendServerMessage(sock, "/unsummonW: removed egg with type: " + std::to_string(npc->type) +
|
||||||
", id: " + std::to_string(npc->appearanceData.iNPC_ID));
|
", id: " + std::to_string(npc->id));
|
||||||
TableData::RunningEggs.erase(npc->appearanceData.iNPC_ID);
|
TableData::RunningEggs.erase(npc->id);
|
||||||
NPCManager::destroyNPC(npc->appearanceData.iNPC_ID);
|
NPCManager::destroyNPC(npc->id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TableData::RunningMobs.find(npc->appearanceData.iNPC_ID) == TableData::RunningMobs.end()
|
if (TableData::RunningMobs.find(npc->id) == TableData::RunningMobs.end()
|
||||||
&& TableData::RunningGroups.find(npc->appearanceData.iNPC_ID) == TableData::RunningGroups.end()) {
|
&& TableData::RunningGroups.find(npc->id) == TableData::RunningGroups.end()) {
|
||||||
Chat::sendServerMessage(sock, "/unsummonW: Closest NPC is not a gruntwork mob.");
|
Chat::sendServerMessage(sock, "/unsummonW: Closest NPC is not a gruntwork mob.");
|
||||||
return;
|
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;
|
int leadId = ((Mob*)npc)->groupLeader;
|
||||||
if (leadId != 0) {
|
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;
|
std::cout << "[WARN] unsummonW: leader not found!" << std::endl;
|
||||||
}
|
}
|
||||||
Mob* leadNpc = (Mob*)NPCManager::NPCs[leadId];
|
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)
|
if (leadNpc->groupMember[i] == 0)
|
||||||
break;
|
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;
|
std::cout << "[WARN] unsommonW: leader can't find a group member!" << std::endl;
|
||||||
continue;
|
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) +
|
Chat::sendServerMessage(sock, "/unsummonW: removed mob with type: " + std::to_string(npc->type) +
|
||||||
", id: " + std::to_string(npc->appearanceData.iNPC_ID));
|
", 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) {
|
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
|
// return all mobs to their spawn points
|
||||||
for (auto& pair : NPCManager::NPCs) {
|
for (auto& pair : NPCManager::NPCs) {
|
||||||
if (pair.second->type != EntityType::MOB)
|
if (pair.second->kind != EntityKind::MOB)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
Mob* mob = (Mob*)pair.second;
|
Mob* mob = (Mob*)pair.second;
|
||||||
mob->state = MobState::RETREAT;
|
mob->state = AIState::RETREAT;
|
||||||
mob->target = nullptr;
|
mob->target = nullptr;
|
||||||
mob->nextMovement = getTime();
|
mob->nextMovement = getTime();
|
||||||
|
|
||||||
@@ -356,18 +357,18 @@ static void npcRotateCommand(std::string full, std::vector<std::string>& args, C
|
|||||||
}
|
}
|
||||||
|
|
||||||
int angle = (plr->angle + 180) % 360;
|
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);
|
||||||
|
|
||||||
bool isGruntworkNpc = true;
|
bool isGruntworkNpc = true;
|
||||||
|
|
||||||
// add a rotation entry to the gruntwork file, unless it's already a gruntwork NPC
|
// add a rotation entry to the gruntwork file, unless it's already a gruntwork NPC
|
||||||
if (TableData::RunningMobs.find(npc->appearanceData.iNPC_ID) == TableData::RunningMobs.end()) {
|
if (TableData::RunningMobs.find(npc->id) == TableData::RunningMobs.end()) {
|
||||||
TableData::RunningNPCRotations[npc->appearanceData.iNPC_ID] = angle;
|
TableData::RunningNPCRotations[npc->id] = angle;
|
||||||
isGruntworkNpc = false;
|
isGruntworkNpc = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Chat::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) +
|
Chat::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) +
|
||||||
" for " + (isGruntworkNpc ? "gruntwork " : "") + "NPC " + std::to_string(npc->appearanceData.iNPC_ID));
|
" for " + (isGruntworkNpc ? "gruntwork " : "") + "NPC " + std::to_string(npc->id));
|
||||||
|
|
||||||
// update rotation clientside by refreshing the player's chunks (same as the /refresh command)
|
// 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);
|
PlayerManager::updatePlayerPositionForWarp(sock, plr->x, plr->y, plr->z, plr->instanceID);
|
||||||
@@ -439,9 +440,9 @@ static void npcInstanceCommand(std::string full, std::vector<std::string>& args,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Chat::sendServerMessage(sock, "[NPCI] Moving NPC with ID " + std::to_string(npc->appearanceData.iNPC_ID) + " to instance " + std::to_string(instance));
|
Chat::sendServerMessage(sock, "[NPCI] Moving NPC with ID " + std::to_string(npc->id) + " to instance " + std::to_string(instance));
|
||||||
TableData::RunningNPCMapNumbers[npc->appearanceData.iNPC_ID] = instance;
|
TableData::RunningNPCMapNumbers[npc->id] = instance;
|
||||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->x, npc->y, npc->z, instance, npc->appearanceData.iAngle);
|
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) {
|
static void minfoCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||||
@@ -497,9 +498,12 @@ static void buffCommand(std::string full, std::vector<std::string>& args, CNSock
|
|||||||
if (*tmp)
|
if (*tmp)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (Eggs::eggBuffPlayer(sock, skillId, 0, duration)<0)
|
if (Abilities::SkillTable.count(skillId) == 0) {
|
||||||
Chat::sendServerMessage(sock, "/buff: unknown skill Id");
|
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) {
|
static void eggCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||||
@@ -528,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 addX = 0; //-500.0f * sin(plr->angle / 180.0f * M_PI);
|
||||||
int addY = 0; //-500.0f * cos(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::NPCs[id] = egg;
|
||||||
NPCManager::updateNPCPosition(id, plr->x + addX, plr->y + addY, plr->z, plr->instanceID, plr->angle);
|
NPCManager::updateNPCPosition(id, plr->x + addX, plr->y + addY, plr->z, plr->instanceID, plr->angle);
|
||||||
|
|
||||||
@@ -605,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);
|
BaseNPC *npc = NPCManager::summonNPC(x, y, z, plr->instanceID, type, wCommand);
|
||||||
if (team == 2 && i > 0 && npc->type == EntityType::MOB) {
|
if (team == 2 && i > 0 && npc->kind == EntityKind::MOB) {
|
||||||
leadNpc->groupMember[i-1] = npc->appearanceData.iNPC_ID;
|
leadNpc->groupMember[i-1] = npc->id;
|
||||||
Mob* mob = (Mob*)NPCManager::NPCs[npc->appearanceData.iNPC_ID];
|
Mob* mob = (Mob*)NPCManager::NPCs[npc->id];
|
||||||
mob->groupLeader = leadNpc->appearanceData.iNPC_ID;
|
mob->groupLeader = leadNpc->id;
|
||||||
mob->offsetX = x - plr->x;
|
mob->offsetX = x - plr->x;
|
||||||
mob->offsetY = y - plr->y;
|
mob->offsetY = y - plr->y;
|
||||||
}
|
}
|
||||||
|
|
||||||
npc->appearanceData.iAngle = (plr->angle + 180) % 360;
|
npc->angle = (plr->angle + 180) % 360;
|
||||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, x, y, z, plr->instanceID, npc->appearanceData.iAngle);
|
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 we're in a lair, we need to spawn the NPC in both the private instance and the template
|
||||||
if (PLAYERID(plr->instanceID) != 0) {
|
if (PLAYERID(plr->instanceID) != 0) {
|
||||||
npc = NPCManager::summonNPC(plr->x, plr->y, plr->z, plr->instanceID, type, wCommand, true);
|
npc = NPCManager::summonNPC(plr->x, plr->y, plr->z, plr->instanceID, type, wCommand, true);
|
||||||
|
|
||||||
if (team == 2 && i > 0 && npc->type == EntityType::MOB) {
|
if (team == 2 && i > 0 && npc->kind == EntityKind::MOB) {
|
||||||
leadNpc->groupMember[i-1] = npc->appearanceData.iNPC_ID;
|
leadNpc->groupMember[i-1] = npc->id;
|
||||||
Mob* mob = (Mob*)NPCManager::NPCs[npc->appearanceData.iNPC_ID];
|
Mob* mob = (Mob*)NPCManager::NPCs[npc->id];
|
||||||
mob->groupLeader = leadNpc->appearanceData.iNPC_ID;
|
mob->groupLeader = leadNpc->id;
|
||||||
mob->offsetX = x - plr->x;
|
mob->offsetX = x - plr->x;
|
||||||
mob->offsetY = y - plr->y;
|
mob->offsetY = y - plr->y;
|
||||||
}
|
}
|
||||||
|
|
||||||
npc->appearanceData.iAngle = (plr->angle + 180) % 360;
|
npc->angle = (plr->angle + 180) % 360;
|
||||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, x, y, z, plr->instanceID, npc->appearanceData.iAngle);
|
NPCManager::updateNPCPosition(npc->id, x, y, z, plr->instanceID, npc->angle);
|
||||||
}
|
}
|
||||||
|
|
||||||
Chat::sendServerMessage(sock, "/summonGroup(W): placed mob with type: " + std::to_string(type) +
|
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;
|
type = type2;
|
||||||
leadNpc = (Mob*)NPCManager::NPCs[npc->appearanceData.iNPC_ID];
|
leadNpc = (Mob*)NPCManager::NPCs[npc->id];
|
||||||
leadNpc->groupLeader = leadNpc->appearanceData.iNPC_ID;
|
leadNpc->groupLeader = leadNpc->id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -650,7 +654,7 @@ static void summonGroupCommand(std::string full, std::vector<std::string>& args,
|
|||||||
return;
|
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) {
|
static void flushCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||||
@@ -667,15 +671,14 @@ static void whoisCommand(std::string full, std::vector<std::string>& args, CNSoc
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Chat::sendServerMessage(sock, "[WHOIS] ID: " + std::to_string(npc->appearanceData.iNPC_ID));
|
Chat::sendServerMessage(sock, "[WHOIS] ID: " + std::to_string(npc->id));
|
||||||
Chat::sendServerMessage(sock, "[WHOIS] Type: " + std::to_string(npc->appearanceData.iNPCType));
|
Chat::sendServerMessage(sock, "[WHOIS] Type: " + std::to_string(npc->type));
|
||||||
Chat::sendServerMessage(sock, "[WHOIS] HP: " + std::to_string(npc->appearanceData.iHP));
|
Chat::sendServerMessage(sock, "[WHOIS] HP: " + std::to_string(npc->hp));
|
||||||
Chat::sendServerMessage(sock, "[WHOIS] CBF: " + std::to_string(npc->appearanceData.iConditionBitFlag));
|
Chat::sendServerMessage(sock, "[WHOIS] EntityType: " + std::to_string((int)npc->kind));
|
||||||
Chat::sendServerMessage(sock, "[WHOIS] EntityType: " + std::to_string((int)npc->type));
|
|
||||||
Chat::sendServerMessage(sock, "[WHOIS] X: " + std::to_string(npc->x));
|
Chat::sendServerMessage(sock, "[WHOIS] X: " + std::to_string(npc->x));
|
||||||
Chat::sendServerMessage(sock, "[WHOIS] Y: " + std::to_string(npc->y));
|
Chat::sendServerMessage(sock, "[WHOIS] Y: " + std::to_string(npc->y));
|
||||||
Chat::sendServerMessage(sock, "[WHOIS] Z: " + std::to_string(npc->z));
|
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));
|
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] Chunk: {" + chunkPosition + "}");
|
||||||
Chat::sendServerMessage(sock, "[WHOIS] MapNum: " + std::to_string(MAPNUM(npc->instanceID)));
|
Chat::sendServerMessage(sock, "[WHOIS] MapNum: " + std::to_string(MAPNUM(npc->instanceID)));
|
||||||
@@ -690,7 +693,7 @@ static void lairUnlockCommand(std::string full, std::vector<std::string>& args,
|
|||||||
int lastDist = INT_MAX;
|
int lastDist = INT_MAX;
|
||||||
for (Chunk *chnk : Chunking::getViewableChunks(plr->chunkPos)) {
|
for (Chunk *chnk : Chunking::getViewableChunks(plr->chunkPos)) {
|
||||||
for (const EntityRef& ref : chnk->entities) {
|
for (const EntityRef& ref : chnk->entities) {
|
||||||
if (ref.type == EntityType::PLAYER)
|
if (ref.kind == EntityKind::PLAYER)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
BaseNPC* npc = (BaseNPC*)ref.getEntity();
|
BaseNPC* npc = (BaseNPC*)ref.getEntity();
|
||||||
@@ -701,7 +704,7 @@ static void lairUnlockCommand(std::string full, std::vector<std::string>& args,
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
for (auto it = NPCManager::Warps.begin(); it != NPCManager::Warps.end(); it++) {
|
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;
|
taskID = it->second.limitTaskID;
|
||||||
missionID = Missions::Tasks[taskID]->task["m_iHMissionID"];
|
missionID = Missions::Tasks[taskID]->task["m_iHMissionID"];
|
||||||
lastDist = dist;
|
lastDist = dist;
|
||||||
@@ -973,11 +976,17 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
|
|||||||
|
|
||||||
// add first point at NPC's current location
|
// add first point at NPC's current location
|
||||||
std::vector<BaseNPC*> pathPoints;
|
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);
|
pathPoints.push_back(marker);
|
||||||
// map from player
|
// map from player
|
||||||
TableData::RunningNPCPaths[plr->iID] = std::make_pair(npc, pathPoints);
|
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);
|
updatePathMarkers(sock);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -994,7 +1003,12 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
|
|||||||
|
|
||||||
// /path kf
|
// /path kf
|
||||||
if (args[1] == "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);
|
entry->second.push_back(marker);
|
||||||
Chat::sendServerMessage(sock, "[PATH] Added keyframe");
|
Chat::sendServerMessage(sock, "[PATH] Added keyframe");
|
||||||
updatePathMarkers(sock);
|
updatePathMarkers(sock);
|
||||||
@@ -1004,8 +1018,8 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
|
|||||||
// /path here
|
// /path here
|
||||||
if (args[1] == "here") {
|
if (args[1] == "here") {
|
||||||
// bring the NPC to where the player is standing
|
// bring the NPC to where the player is standing
|
||||||
Transport::NPCQueues.erase(npc->appearanceData.iNPC_ID); // delete transport queue
|
Transport::NPCQueues.erase(npc->id); // delete transport queue
|
||||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, plr->x, plr->y, plr->z, npc->instanceID, 0);
|
NPCManager::updateNPCPosition(npc->id, plr->x, plr->y, plr->z, npc->instanceID, 0);
|
||||||
npc->disappearFromViewOf(sock);
|
npc->disappearFromViewOf(sock);
|
||||||
npc->enterIntoViewOf(sock);
|
npc->enterIntoViewOf(sock);
|
||||||
Chat::sendServerMessage(sock, "[PATH] Come here");
|
Chat::sendServerMessage(sock, "[PATH] Come here");
|
||||||
@@ -1043,9 +1057,9 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
|
|||||||
speed = speedArg;
|
speed = speedArg;
|
||||||
}
|
}
|
||||||
// return NPC to home
|
// 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];
|
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->disappearFromViewOf(sock);
|
||||||
npc->enterIntoViewOf(sock);
|
npc->enterIntoViewOf(sock);
|
||||||
|
|
||||||
@@ -1061,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
|
Transport::lerp(&keyframes, from, to, speed); // lerp from A to B
|
||||||
from = to; // update point A
|
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
|
entry->second.pop_back(); // remove temp end point
|
||||||
|
|
||||||
Chat::sendServerMessage(sock, "[PATH] Testing NPC path");
|
Chat::sendServerMessage(sock, "[PATH] Testing NPC path");
|
||||||
@@ -1071,9 +1085,9 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
|
|||||||
// /path cancel
|
// /path cancel
|
||||||
if (args[1] == "cancel") {
|
if (args[1] == "cancel") {
|
||||||
// return NPC to home
|
// 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];
|
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->disappearFromViewOf(sock);
|
||||||
npc->enterIntoViewOf(sock);
|
npc->enterIntoViewOf(sock);
|
||||||
// deallocate markers
|
// deallocate markers
|
||||||
@@ -1083,7 +1097,7 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
|
|||||||
}
|
}
|
||||||
// unmap
|
// unmap
|
||||||
TableData::RunningNPCPaths.erase(plr->iID);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1111,9 +1125,9 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
|
|||||||
}
|
}
|
||||||
|
|
||||||
// return NPC to home and set path to repeat
|
// 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];
|
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->disappearFromViewOf(sock);
|
||||||
npc->enterIntoViewOf(sock);
|
npc->enterIntoViewOf(sock);
|
||||||
npc->loopingPath = true;
|
npc->loopingPath = true;
|
||||||
@@ -1130,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
|
Transport::lerp(&keyframes, from, to, speed); // lerp from A to B
|
||||||
from = to; // update point A
|
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
|
entry->second.pop_back(); // remove temp end point
|
||||||
|
|
||||||
// save to gruntwork
|
// save to gruntwork
|
||||||
@@ -1157,7 +1171,7 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
|
|||||||
finishedPath.isLoop = true;
|
finishedPath.isLoop = true;
|
||||||
finishedPath.speed = speed;
|
finishedPath.speed = speed;
|
||||||
finishedPath.points = finalPoints;
|
finishedPath.points = finalPoints;
|
||||||
finishedPath.targetIDs.push_back(npc->appearanceData.iNPC_ID);
|
finishedPath.targetIDs.push_back(npc->id);
|
||||||
|
|
||||||
TableData::FinishedNPCPaths.push_back(finishedPath);
|
TableData::FinishedNPCPaths.push_back(finishedPath);
|
||||||
|
|
||||||
@@ -1169,7 +1183,7 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
|
|||||||
// unmap
|
// unmap
|
||||||
TableData::RunningNPCPaths.erase(plr->iID);
|
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();
|
TableData::flush();
|
||||||
Chat::sendServerMessage(sock, "[PATH] Path saved to gruntwork");
|
Chat::sendServerMessage(sock, "[PATH] Path saved to gruntwork");
|
||||||
|
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace CustomCommands {
|
namespace CustomCommands {
|
||||||
void init();
|
void init();
|
||||||
|
|
||||||
|
212
src/Eggs.cpp
212
src/Eggs.cpp
@@ -1,141 +1,124 @@
|
|||||||
#include "core/Core.hpp"
|
|
||||||
#include "Eggs.hpp"
|
#include "Eggs.hpp"
|
||||||
|
|
||||||
|
#include "servers/CNShardServer.hpp"
|
||||||
|
|
||||||
|
#include "Player.hpp"
|
||||||
#include "PlayerManager.hpp"
|
#include "PlayerManager.hpp"
|
||||||
#include "Items.hpp"
|
|
||||||
#include "Nanos.hpp"
|
|
||||||
#include "Abilities.hpp"
|
#include "Abilities.hpp"
|
||||||
#include "Groups.hpp"
|
#include "NPCManager.hpp"
|
||||||
|
#include "Entities.hpp"
|
||||||
|
#include "Items.hpp"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
using namespace Eggs;
|
using namespace Eggs;
|
||||||
|
|
||||||
/// sock, CBFlag -> until
|
|
||||||
std::map<std::pair<CNSocket*, int32_t>, time_t> Eggs::EggBuffs;
|
|
||||||
std::unordered_map<int, EggType> Eggs::EggTypes;
|
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* plr = PlayerManager::getPlayer(sock);
|
||||||
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
|
||||||
|
|
||||||
int bitFlag = Groups::getGroupFlags(otherPlr);
|
// eggId might be 0 if the buff is made by the /buff command
|
||||||
int CBFlag = Nanos::applyBuff(sock, skillId, 1, 3, bitFlag);
|
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) {
|
int timeBuffId = Abilities::getCSTBFromST(skill->skillType);
|
||||||
resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + sizeof(sSkillResult_Damage);
|
int value = skill->values[0][0];
|
||||||
} else if (skillId == 150) {
|
BuffStack eggBuff = {
|
||||||
resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + sizeof(sSkillResult_Heal_HP);
|
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 {
|
} else {
|
||||||
resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + sizeof(sSkillResult_Buff);
|
int value = plr->getMaxHP() * skill->values[0][0] / 1000;
|
||||||
}
|
sSkillResult_Damage resultDamage{};
|
||||||
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
|
sSkillResult_Heal_HP resultHeal{};
|
||||||
// we know it's only one trailing struct, so we can skip full validation
|
switch(skill->skillType)
|
||||||
|
{
|
||||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
case SkillType::DAMAGE:
|
||||||
auto skillUse = (sP_FE2CL_NPC_SKILL_HIT*)respbuf;
|
resultDamage.bProtected = false;
|
||||||
|
resultDamage.eCT = plr->getCharType();
|
||||||
if (skillId == 183) { // damage egg
|
resultDamage.iID = plr->getID();
|
||||||
auto skill = (sSkillResult_Damage*)(respbuf + sizeof(sP_FE2CL_NPC_SKILL_HIT));
|
resultDamage.iDamage = plr->takeDamage(src, value);
|
||||||
memset(respbuf, 0, resplen);
|
resultDamage.iHP = plr->getCurrentHP();
|
||||||
skill->eCT = 1;
|
result = SkillResult(sizeof(sSkillResult_Damage), &resultDamage);
|
||||||
skill->iID = plr->iID;
|
break;
|
||||||
skill->iDamage = PC_MAXHEALTH(plr->level) * Nanos::SkillTable[skillId].powerIntensity[0] / 1000;
|
case SkillType::HEAL_HP:
|
||||||
plr->HP -= skill->iDamage;
|
resultHeal.eCT = plr->getCharType();
|
||||||
if (plr->HP < 0)
|
resultHeal.iID = plr->getID();
|
||||||
plr->HP = 0;
|
resultHeal.iHealHP = plr->heal(src, value);
|
||||||
skill->iHP = plr->HP;
|
resultHeal.iHP = plr->getCurrentHP();
|
||||||
} else if (skillId == 150) { // heal egg
|
result = SkillResult(sizeof(sSkillResult_Heal_HP), &resultHeal);
|
||||||
auto skill = (sSkillResult_Heal_HP*)(respbuf + sizeof(sP_FE2CL_NPC_SKILL_HIT));
|
break;
|
||||||
memset(respbuf, 0, resplen);
|
default:
|
||||||
skill->eCT = 1;
|
std::cout << "[WARN] oops, egg with active skill type " << (int)skill->skillType << " unhandled";
|
||||||
skill->iID = plr->iID;
|
return;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
// check dead eggs and eggs in inactive chunks
|
||||||
for (auto npc : NPCManager::NPCs) {
|
for (auto npc : NPCManager::NPCs) {
|
||||||
if (npc.second->type != EntityType::EGG)
|
if (npc.second->kind != EntityKind::EGG)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
auto egg = (Egg*)npc.second;
|
auto egg = (Egg*)npc.second;
|
||||||
if (!egg->dead || !Chunking::inPopulatedChunks(&egg->viewableChunks))
|
if (!egg->dead || !Chunking::inPopulatedChunks(&egg->viewableChunks))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (egg->deadUntil <= timeStamp) {
|
if (egg->deadUntil <= currTime) {
|
||||||
// respawn it
|
// respawn it
|
||||||
egg->dead = false;
|
egg->dead = false;
|
||||||
egg->deadUntil = 0;
|
egg->deadUntil = 0;
|
||||||
egg->appearanceData.iHP = 400;
|
egg->hp = 400;
|
||||||
|
|
||||||
Chunking::addEntityToChunks(Chunking::getViewableChunks(egg->chunkPos), {npc.first});
|
Chunking::addEntityToChunks(Chunking::getViewableChunks(egg->chunkPos), {npc.first});
|
||||||
}
|
}
|
||||||
@@ -163,7 +146,7 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto egg = (Egg*)eggRef.getEntity();
|
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;
|
std::cout << "[WARN] Player tried to open something other than an?!" << std::endl;
|
||||||
return;
|
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()) {
|
if (EggTypes.find(typeId) == EggTypes.end()) {
|
||||||
std::cout << "[WARN] Egg Type " << typeId << " not found!" << std::endl;
|
std::cout << "[WARN] Egg Type " << typeId << " not found!" << std::endl;
|
||||||
return;
|
return;
|
||||||
@@ -188,16 +171,13 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) {
|
|||||||
|
|
||||||
EggType* type = &EggTypes[typeId];
|
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
|
* SHINY_PICKUP_SUCC is only causing a GUI effect in the client
|
||||||
* (buff icon pops up in the bottom of the screen)
|
* (buff icon pops up in the bottom of the screen)
|
||||||
* so we don't send it for non-effect
|
* so we don't send it for non-effect
|
||||||
*/
|
*/
|
||||||
if (type->effectId != 0) {
|
if (type->effectId != 0) {
|
||||||
|
eggBuffPlayer(sock, type->effectId, eggRef.id, type->duration);
|
||||||
INITSTRUCT(sP_FE2CL_REP_SHINY_PICKUP_SUCC, resp);
|
INITSTRUCT(sP_FE2CL_REP_SHINY_PICKUP_SUCC, resp);
|
||||||
resp.iSkillID = type->effectId;
|
resp.iSkillID = type->effectId;
|
||||||
|
|
||||||
@@ -255,7 +235,7 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) {
|
|||||||
Chunking::removeEntityFromChunks(Chunking::getViewableChunks(egg->chunkPos), eggRef);
|
Chunking::removeEntityFromChunks(Chunking::getViewableChunks(egg->chunkPos), eggRef);
|
||||||
egg->dead = true;
|
egg->dead = true;
|
||||||
egg->deadUntil = getTime() + (time_t)type->regen * 1000;
|
egg->deadUntil = getTime() + (time_t)type->regen * 1000;
|
||||||
egg->appearanceData.iHP = 0;
|
egg->hp = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
#include "Entities.hpp"
|
|
||||||
|
|
||||||
struct EggType {
|
struct EggType {
|
||||||
int dropCrateId;
|
int dropCrateId;
|
||||||
@@ -11,12 +10,10 @@ struct EggType {
|
|||||||
};
|
};
|
||||||
|
|
||||||
namespace Eggs {
|
namespace Eggs {
|
||||||
extern std::map<std::pair<CNSocket*, int32_t>, time_t> EggBuffs;
|
|
||||||
extern std::unordered_map<int, EggType> EggTypes;
|
extern std::unordered_map<int, EggType> EggTypes;
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
|
|
||||||
/// returns -1 on fail
|
void eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration);
|
||||||
int eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration);
|
|
||||||
void npcDataToEggData(int x, int y, int z, sNPCAppearanceData* npc, sShinyAppearanceData* egg);
|
void npcDataToEggData(int x, int y, int z, sNPCAppearanceData* npc, sShinyAppearanceData* egg);
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
#include "Email.hpp"
|
#include "Email.hpp"
|
||||||
|
|
||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
|
#include "db/Database.hpp"
|
||||||
#include "servers/CNShardServer.hpp"
|
#include "servers/CNShardServer.hpp"
|
||||||
|
|
||||||
#include "db/Database.hpp"
|
|
||||||
#include "PlayerManager.hpp"
|
#include "PlayerManager.hpp"
|
||||||
#include "Items.hpp"
|
#include "Items.hpp"
|
||||||
#include "Chat.hpp"
|
#include "Chat.hpp"
|
||||||
|
@@ -5,6 +5,6 @@
|
|||||||
|
|
||||||
namespace Email {
|
namespace Email {
|
||||||
extern std::vector<std::string> dump;
|
extern std::vector<std::string> dump;
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
}
|
}
|
||||||
|
@@ -1,18 +1,15 @@
|
|||||||
#include "core/Core.hpp"
|
|
||||||
#include "Entities.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_standard_layout<EntityRef>::value);
|
||||||
static_assert(std::is_trivially_copyable<EntityRef>::value);
|
static_assert(std::is_trivially_copyable<EntityRef>::value);
|
||||||
|
|
||||||
EntityRef::EntityRef(CNSocket *s) {
|
EntityRef::EntityRef(CNSocket *s) {
|
||||||
type = EntityType::PLAYER;
|
kind = EntityKind::PLAYER;
|
||||||
sock = s;
|
sock = s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,11 +17,11 @@ EntityRef::EntityRef(int32_t i) {
|
|||||||
id = i;
|
id = i;
|
||||||
|
|
||||||
assert(NPCManager::NPCs.find(id) != NPCManager::NPCs.end());
|
assert(NPCManager::NPCs.find(id) != NPCManager::NPCs.end());
|
||||||
type = NPCManager::NPCs[id]->type;
|
kind = NPCManager::NPCs[id]->kind;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EntityRef::isValid() const {
|
bool EntityRef::isValid() const {
|
||||||
if (type == EntityType::PLAYER)
|
if (kind == EntityKind::PLAYER)
|
||||||
return PlayerManager::players.find(sock) != PlayerManager::players.end();
|
return PlayerManager::players.find(sock) != PlayerManager::players.end();
|
||||||
|
|
||||||
return NPCManager::NPCs.find(id) != NPCManager::NPCs.end();
|
return NPCManager::NPCs.find(id) != NPCManager::NPCs.end();
|
||||||
@@ -33,21 +30,38 @@ bool EntityRef::isValid() const {
|
|||||||
Entity *EntityRef::getEntity() const {
|
Entity *EntityRef::getEntity() const {
|
||||||
assert(isValid());
|
assert(isValid());
|
||||||
|
|
||||||
if (type == EntityType::PLAYER)
|
if (kind == EntityKind::PLAYER)
|
||||||
return PlayerManager::getPlayer(sock);
|
return PlayerManager::getPlayer(sock);
|
||||||
|
|
||||||
return NPCManager::NPCs[id];
|
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.
|
* Entity coming into view.
|
||||||
*/
|
*/
|
||||||
void BaseNPC::enterIntoViewOf(CNSocket *sock) {
|
void BaseNPC::enterIntoViewOf(CNSocket *sock) {
|
||||||
INITSTRUCT(sP_FE2CL_NPC_ENTER, pkt);
|
INITSTRUCT(sP_FE2CL_NPC_ENTER, pkt);
|
||||||
pkt.NPCAppearanceData = appearanceData;
|
pkt.NPCAppearanceData = getAppearanceData();
|
||||||
pkt.NPCAppearanceData.iX = x;
|
|
||||||
pkt.NPCAppearanceData.iY = y;
|
|
||||||
pkt.NPCAppearanceData.iZ = z;
|
|
||||||
sock->sendPacket(pkt, P_FE2CL_NPC_ENTER);
|
sock->sendPacket(pkt, P_FE2CL_NPC_ENTER);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +70,7 @@ void Bus::enterIntoViewOf(CNSocket *sock) {
|
|||||||
|
|
||||||
// TODO: Potentially decouple this from BaseNPC?
|
// TODO: Potentially decouple this from BaseNPC?
|
||||||
pkt.AppearanceData = {
|
pkt.AppearanceData = {
|
||||||
3, appearanceData.iNPC_ID, appearanceData.iNPCType,
|
3, id, type,
|
||||||
x, y, z
|
x, y, z
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -66,28 +80,40 @@ void Bus::enterIntoViewOf(CNSocket *sock) {
|
|||||||
void Egg::enterIntoViewOf(CNSocket *sock) {
|
void Egg::enterIntoViewOf(CNSocket *sock) {
|
||||||
INITSTRUCT(sP_FE2CL_SHINY_ENTER, pkt);
|
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);
|
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()
|
// TODO: this is less effiecient than it was, because of memset()
|
||||||
void Player::enterIntoViewOf(CNSocket *sock) {
|
void Player::enterIntoViewOf(CNSocket *sock) {
|
||||||
INITSTRUCT(sP_FE2CL_PC_NEW, pkt);
|
INITSTRUCT(sP_FE2CL_PC_NEW, pkt);
|
||||||
|
pkt.PCAppearanceData = getAppearanceData();
|
||||||
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);
|
|
||||||
|
|
||||||
sock->sendPacket(pkt, P_FE2CL_PC_NEW);
|
sock->sendPacket(pkt, P_FE2CL_PC_NEW);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,20 +122,20 @@ void Player::enterIntoViewOf(CNSocket *sock) {
|
|||||||
*/
|
*/
|
||||||
void BaseNPC::disappearFromViewOf(CNSocket *sock) {
|
void BaseNPC::disappearFromViewOf(CNSocket *sock) {
|
||||||
INITSTRUCT(sP_FE2CL_NPC_EXIT, pkt);
|
INITSTRUCT(sP_FE2CL_NPC_EXIT, pkt);
|
||||||
pkt.iNPC_ID = appearanceData.iNPC_ID;
|
pkt.iNPC_ID = id;
|
||||||
sock->sendPacket(pkt, P_FE2CL_NPC_EXIT);
|
sock->sendPacket(pkt, P_FE2CL_NPC_EXIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bus::disappearFromViewOf(CNSocket *sock) {
|
void Bus::disappearFromViewOf(CNSocket *sock) {
|
||||||
INITSTRUCT(sP_FE2CL_TRANSPORTATION_EXIT, pkt);
|
INITSTRUCT(sP_FE2CL_TRANSPORTATION_EXIT, pkt);
|
||||||
pkt.eTT = 3;
|
pkt.eTT = 3;
|
||||||
pkt.iT_ID = appearanceData.iNPC_ID;
|
pkt.iT_ID = id;
|
||||||
sock->sendPacket(pkt, P_FE2CL_TRANSPORTATION_EXIT);
|
sock->sendPacket(pkt, P_FE2CL_TRANSPORTATION_EXIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Egg::disappearFromViewOf(CNSocket *sock) {
|
void Egg::disappearFromViewOf(CNSocket *sock) {
|
||||||
INITSTRUCT(sP_FE2CL_SHINY_EXIT, pkt);
|
INITSTRUCT(sP_FE2CL_SHINY_EXIT, pkt);
|
||||||
pkt.iShinyID = appearanceData.iNPC_ID;
|
pkt.iShinyID = id;
|
||||||
sock->sendPacket(pkt, P_FE2CL_SHINY_EXIT);
|
sock->sendPacket(pkt, P_FE2CL_SHINY_EXIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
174
src/Entities.hpp
174
src/Entities.hpp
@@ -1,23 +1,26 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
|
|
||||||
|
#include "EntityRef.hpp"
|
||||||
|
#include "Buffs.hpp"
|
||||||
#include "Chunking.hpp"
|
#include "Chunking.hpp"
|
||||||
|
#include "Groups.hpp"
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <set>
|
#include <set>
|
||||||
|
#include <map>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
enum class EntityType : uint8_t {
|
enum class AIState {
|
||||||
INVALID,
|
INACTIVE,
|
||||||
PLAYER,
|
ROAMING,
|
||||||
SIMPLE_NPC,
|
COMBAT,
|
||||||
COMBAT_NPC,
|
RETREAT,
|
||||||
MOB,
|
DEAD
|
||||||
EGG,
|
|
||||||
BUS
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Entity {
|
struct Entity {
|
||||||
EntityType type = EntityType::INVALID;
|
EntityKind kind = EntityKind::INVALID;
|
||||||
int x = 0, y = 0, z = 0;
|
int x = 0, y = 0, z = 0;
|
||||||
uint64_t instanceID = 0;
|
uint64_t instanceID = 0;
|
||||||
ChunkPos chunkPos = {};
|
ChunkPos chunkPos = {};
|
||||||
@@ -26,47 +29,39 @@ struct Entity {
|
|||||||
// destructor must be virtual, apparently
|
// destructor must be virtual, apparently
|
||||||
virtual ~Entity() {}
|
virtual ~Entity() {}
|
||||||
|
|
||||||
virtual bool isAlive() { return true; }
|
virtual bool isExtant() { return true; }
|
||||||
|
|
||||||
// stubs
|
// stubs
|
||||||
virtual void enterIntoViewOf(CNSocket *sock) = 0;
|
virtual void enterIntoViewOf(CNSocket *sock) = 0;
|
||||||
virtual void disappearFromViewOf(CNSocket *sock) = 0;
|
virtual void disappearFromViewOf(CNSocket *sock) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct EntityRef {
|
/*
|
||||||
EntityType type;
|
* Interfaces
|
||||||
union {
|
*/
|
||||||
CNSocket *sock;
|
class ICombatant {
|
||||||
int32_t id;
|
public:
|
||||||
};
|
ICombatant() {}
|
||||||
|
virtual ~ICombatant() {}
|
||||||
|
|
||||||
EntityRef(CNSocket *s);
|
virtual bool addBuff(int, BuffCallback<int, BuffStack*>, BuffCallback<time_t>, BuffStack*) = 0;
|
||||||
EntityRef(int32_t i);
|
virtual Buff* getBuff(int) = 0;
|
||||||
|
virtual void removeBuff(int) = 0;
|
||||||
bool isValid() const;
|
virtual void removeBuff(int, BuffClass) = 0;
|
||||||
Entity *getEntity() const;
|
virtual void clearBuffs(bool) = 0;
|
||||||
|
virtual bool hasBuff(int) = 0;
|
||||||
bool operator==(const EntityRef& other) const {
|
virtual int getCompositeCondition() = 0;
|
||||||
if (type != other.type)
|
virtual int takeDamage(EntityRef, int) = 0;
|
||||||
return false;
|
virtual int heal(EntityRef, int) = 0;
|
||||||
|
virtual bool isAlive() = 0;
|
||||||
if (type == EntityType::PLAYER)
|
virtual int getCurrentHP() = 0;
|
||||||
return sock == other.sock;
|
virtual int getMaxHP() = 0;
|
||||||
|
virtual int getLevel() = 0;
|
||||||
return id == other.id;
|
virtual std::vector<EntityRef> getGroupMembers() = 0;
|
||||||
}
|
virtual int32_t getCharType() = 0;
|
||||||
|
virtual int32_t getID() = 0;
|
||||||
// arbitrary ordering
|
virtual EntityRef getRef() = 0;
|
||||||
bool operator<(const EntityRef& other) const {
|
virtual void step(time_t currTime) = 0;
|
||||||
if (type == other.type) {
|
|
||||||
if (type == EntityType::PLAYER)
|
|
||||||
return sock < other.sock;
|
|
||||||
else
|
|
||||||
return id < other.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return type < other.type;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -74,75 +69,104 @@ struct EntityRef {
|
|||||||
*/
|
*/
|
||||||
class BaseNPC : public Entity {
|
class BaseNPC : public Entity {
|
||||||
public:
|
public:
|
||||||
sNPCAppearanceData appearanceData = {};
|
int id;
|
||||||
|
int type;
|
||||||
|
int hp;
|
||||||
|
int angle;
|
||||||
bool loopingPath = false;
|
bool loopingPath = false;
|
||||||
|
|
||||||
BaseNPC(int _X, int _Y, int _Z, int angle, uint64_t iID, int t, int id) { // XXX
|
BaseNPC(int _A, uint64_t iID, int t, int _id) {
|
||||||
x = _X;
|
kind = EntityKind::SIMPLE_NPC;
|
||||||
y = _Y;
|
type = t;
|
||||||
z = _Z;
|
hp = 400;
|
||||||
appearanceData.iNPCType = t;
|
angle = _A;
|
||||||
appearanceData.iHP = 400;
|
id = _id;
|
||||||
appearanceData.iAngle = angle;
|
|
||||||
appearanceData.iConditionBitFlag = 0;
|
|
||||||
appearanceData.iBarkerType = 0;
|
|
||||||
appearanceData.iNPC_ID = id;
|
|
||||||
|
|
||||||
instanceID = iID;
|
instanceID = iID;
|
||||||
};
|
};
|
||||||
|
|
||||||
virtual void enterIntoViewOf(CNSocket *sock) override;
|
virtual void enterIntoViewOf(CNSocket *sock) override;
|
||||||
virtual void disappearFromViewOf(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 maxHealth = 0;
|
||||||
int spawnX = 0;
|
int spawnX = 0;
|
||||||
int spawnY = 0;
|
int spawnY = 0;
|
||||||
int spawnZ = 0;
|
int spawnZ = 0;
|
||||||
int level = 0;
|
int level = 0;
|
||||||
int speed = 300;
|
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
|
std::unordered_map<int, Buff*> buffs = {};
|
||||||
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) {}
|
|
||||||
|
|
||||||
virtual void stepAI(time_t currTime) {
|
CombatNPC(int spawnX, int spawnY, int spawnZ, int angle, uint64_t iID, int t, int id, int maxHP)
|
||||||
if (_stepAI != nullptr)
|
: BaseNPC(angle, iID, t, id), maxHealth(maxHP) {
|
||||||
_stepAI(this, currTime);
|
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
|
// Mob is in MobAI.hpp, Player is in Player.hpp
|
||||||
|
|
||||||
// TODO: decouple from BaseNPC
|
|
||||||
struct Egg : public BaseNPC {
|
struct Egg : public BaseNPC {
|
||||||
bool summoned = false;
|
bool summoned = false;
|
||||||
bool dead = false;
|
bool dead = false;
|
||||||
time_t deadUntil;
|
time_t deadUntil;
|
||||||
|
|
||||||
Egg(int x, int y, int z, uint64_t iID, int t, int32_t id, bool summon)
|
Egg(uint64_t iID, int t, int32_t id, bool summon)
|
||||||
: BaseNPC(x, y, z, 0, iID, t, id) {
|
: BaseNPC(0, iID, t, id) {
|
||||||
summoned = summon;
|
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 enterIntoViewOf(CNSocket *sock) override;
|
||||||
virtual void disappearFromViewOf(CNSocket *sock) override;
|
virtual void disappearFromViewOf(CNSocket *sock) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: decouple from BaseNPC
|
|
||||||
struct Bus : public BaseNPC {
|
struct Bus : public BaseNPC {
|
||||||
Bus(int x, int y, int z, int angle, uint64_t iID, int t, int id) :
|
Bus(int angle, uint64_t iID, int t, int id) :
|
||||||
BaseNPC(x, y, z, angle, iID, t, id) {
|
BaseNPC(angle, iID, t, id) {
|
||||||
type = EntityType::BUS;
|
kind = EntityKind::BUS;
|
||||||
loopingPath = true;
|
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 "Groups.hpp"
|
||||||
#include "Nanos.hpp"
|
|
||||||
#include "Abilities.hpp"
|
|
||||||
|
|
||||||
#include <iostream>
|
#include "servers/CNShardServer.hpp"
|
||||||
#include <chrono>
|
|
||||||
#include <algorithm>
|
#include "Player.hpp"
|
||||||
#include <thread>
|
#include "PlayerManager.hpp"
|
||||||
|
#include "Entities.hpp"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* NOTE: Variadic response packets that list group members are technically
|
* NOTE: Variadic response packets that list group members are technically
|
||||||
@@ -19,22 +16,177 @@
|
|||||||
|
|
||||||
using namespace Groups;
|
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) {
|
static void requestGroup(CNSocket* sock, CNPacketData* data) {
|
||||||
sP_CL2FE_REQ_PC_GROUP_INVITE* recv = (sP_CL2FE_REQ_PC_GROUP_INVITE*)data->buf;
|
sP_CL2FE_REQ_PC_GROUP_INVITE* recv = (sP_CL2FE_REQ_PC_GROUP_INVITE*)data->buf;
|
||||||
|
|
||||||
Player* plr = PlayerManager::getPlayer(sock);
|
Player* plr = PlayerManager::getPlayer(sock);
|
||||||
Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_To);
|
Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_To);
|
||||||
|
|
||||||
if (otherPlr == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
otherPlr = PlayerManager::getPlayerFromID(otherPlr->iIDGroup);
|
|
||||||
|
|
||||||
if (otherPlr == nullptr)
|
if (otherPlr == nullptr)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// fail if the group is full or the other player is already in a group
|
// 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);
|
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));
|
sock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_INVITE_FAIL, sizeof(sP_FE2CL_PC_GROUP_INVITE_FAIL));
|
||||||
return;
|
return;
|
||||||
@@ -75,255 +227,80 @@ static void joinGroup(CNSocket* sock, CNPacketData* data) {
|
|||||||
Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_From);
|
Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_From);
|
||||||
|
|
||||||
if (otherPlr == nullptr)
|
if (otherPlr == nullptr)
|
||||||
return;
|
return; // disconnect or something
|
||||||
|
|
||||||
otherPlr = PlayerManager::getPlayerFromID(otherPlr->iIDGroup);
|
int size = otherPlr->group == nullptr ? 1 : otherPlr->group->filter(EntityKind::PLAYER).size();
|
||||||
|
|
||||||
if (otherPlr == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// fail if the group is full or the other player is already in a group
|
// 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);
|
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));
|
sock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_JOIN_FAIL, sizeof(sP_FE2CL_PC_GROUP_JOIN_FAIL));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_JOIN), otherPlr->groupCnt + 1, sizeof(sPCGroupMemberInfo))) {
|
if (otherPlr->group == nullptr) {
|
||||||
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_JOIN packet size\n";
|
// create group
|
||||||
return;
|
EntityRef otherPlrRef = PlayerManager::getSockFromID(recv->iID_From);
|
||||||
|
otherPlr->group = new Group(otherPlrRef);
|
||||||
}
|
}
|
||||||
|
addToGroup(otherPlr->group, sock);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void leaveGroup(CNSocket* sock, CNPacketData* data) {
|
static void leaveGroup(CNSocket* sock, CNPacketData* data) {
|
||||||
Player* plr = PlayerManager::getPlayer(sock);
|
Player* plr = PlayerManager::getPlayer(sock);
|
||||||
groupKickPlayer(plr);
|
groupKick(plr->group, sock);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Groups::sendToGroup(Player* plr, void* buf, uint32_t type, size_t size) {
|
void Groups::sendToGroup(Group* group, void* buf, uint32_t type, size_t size) {
|
||||||
for (int i = 0; i < plr->groupCnt; i++) {
|
auto players = group->filter(EntityKind::PLAYER);
|
||||||
CNSocket* sock = PlayerManager::getSockFromID(plr->groupIDs[i]);
|
for (EntityRef ref : players) {
|
||||||
|
ref.sock->sendPacket(buf, type, size);
|
||||||
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::groupTickInfo(Player* plr) {
|
void Groups::sendToGroup(Group* group, EntityRef excluded, void* buf, uint32_t type, size_t size) {
|
||||||
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO), plr->groupCnt, sizeof(sPCGroupMemberInfo))) {
|
auto players = group->filter(EntityKind::PLAYER);
|
||||||
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_JOIN packet size\n";
|
for (EntityRef ref : players) {
|
||||||
return;
|
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];
|
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;
|
if(!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO), pcCount, sizeof(sPCGroupMemberInfo))
|
||||||
sPCGroupMemberInfo *respdata = (sPCGroupMemberInfo*)(respbuf+sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO));
|
|| !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;
|
||||||
resp->iID = plr->iID;
|
} else {
|
||||||
resp->iMemberPCCnt = plr->groupCnt;
|
uint8_t* pivot = (uint8_t*)(pkt + 1);
|
||||||
|
attachGroupData(pcs, npcs, pivot);
|
||||||
for (int i = 0; i < plr->groupCnt; i++) {
|
sock->sendPacket(respbuf, P_FE2CL_PC_GROUP_MEMBER_INFO,
|
||||||
Player* varPlr = PlayerManager::getPlayerFromID(plr->groupIDs[i]);
|
sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO) + pcCount * sizeof(sPCGroupMemberInfo) + npcCount * sizeof(sNPCGroupMemberInfo));
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 you are the group leader, destroy your own group and kick everybody
|
||||||
if (plr->iID == plr->iIDGroup) {
|
if (group->members[0] == ref) {
|
||||||
groupUnbuff(plr);
|
disbandGroup(group);
|
||||||
INITSTRUCT(sP_FE2CL_PC_GROUP_LEAVE_SUCC, resp1);
|
|
||||||
sendToGroup(plr, (void*)&resp1, P_FE2CL_PC_GROUP_LEAVE_SUCC, sizeof(sP_FE2CL_PC_GROUP_LEAVE_SUCC));
|
|
||||||
plr->groupCnt = 1;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
removeFromGroup(group, ref);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Groups::init() {
|
void Groups::init() {
|
||||||
|
@@ -1,17 +1,37 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Player.hpp"
|
#include "EntityRef.hpp"
|
||||||
#include "core/Core.hpp"
|
|
||||||
#include "servers/CNShardServer.hpp"
|
|
||||||
|
|
||||||
#include <map>
|
#include <vector>
|
||||||
#include <list>
|
#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 {
|
namespace Groups {
|
||||||
void init();
|
void init();
|
||||||
|
|
||||||
void sendToGroup(Player* plr, void* buf, uint32_t type, size_t size);
|
void sendToGroup(Group* group, void* buf, uint32_t type, size_t size);
|
||||||
void groupTickInfo(Player* plr);
|
void sendToGroup(Group* group, EntityRef excluded, void* buf, uint32_t type, size_t size);
|
||||||
void groupKickPlayer(Player* plr);
|
void groupTickInfo(CNSocket* sock);
|
||||||
int getGroupFlags(Player* plr);
|
|
||||||
|
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 "Items.hpp"
|
||||||
|
|
||||||
|
#include "servers/CNShardServer.hpp"
|
||||||
|
|
||||||
|
#include "Player.hpp"
|
||||||
#include "PlayerManager.hpp"
|
#include "PlayerManager.hpp"
|
||||||
#include "Nanos.hpp"
|
#include "Nanos.hpp"
|
||||||
#include "NPCManager.hpp"
|
|
||||||
#include "Player.hpp"
|
|
||||||
#include "Abilities.hpp"
|
#include "Abilities.hpp"
|
||||||
#include "Missions.hpp"
|
|
||||||
#include "Eggs.hpp"
|
#include "Eggs.hpp"
|
||||||
#include "Rand.hpp"
|
#include "MobAI.hpp"
|
||||||
|
#include "Missions.hpp"
|
||||||
|
#include "Buffs.hpp"
|
||||||
|
|
||||||
#include <string.h> // for memset()
|
#include <string.h> // for memset()
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <numeric>
|
||||||
|
|
||||||
using namespace Items;
|
using namespace Items;
|
||||||
|
|
||||||
@@ -479,30 +482,34 @@ static void itemUseHandler(CNSocket* sock, CNPacketData* data) {
|
|||||||
resp->iSlotNum = request->iSlotNum;
|
resp->iSlotNum = request->iSlotNum;
|
||||||
resp->RemainItem = gumball;
|
resp->RemainItem = gumball;
|
||||||
resp->iTargetCnt = 1;
|
resp->iTargetCnt = 1;
|
||||||
resp->eST = EST_NANOSTIMPAK;
|
resp->eST = (int32_t)SkillType::NANOSTIMPAK;
|
||||||
resp->iSkillID = 144;
|
resp->iSkillID = 144;
|
||||||
|
|
||||||
int value1 = CSB_BIT_STIMPAKSLOT1 << request->iNanoSlot;
|
int eCSB = ECSB_STIMPAKSLOT1 + request->iNanoSlot;
|
||||||
int value2 = ECSB_STIMPAKSLOT1 + request->iNanoSlot;
|
|
||||||
|
|
||||||
respdata->eCT = 1;
|
respdata->eCT = 1;
|
||||||
respdata->iID = player->iID;
|
respdata->iID = player->iID;
|
||||||
respdata->iConditionBitFlag = value1;
|
respdata->iConditionBitFlag = CSB_FROM_ECSB(eCSB);
|
||||||
|
|
||||||
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt);
|
int durationMilliseconds = Abilities::SkillTable[144].durationTime[0] * 100;
|
||||||
pkt.eCSTB = value2; // eCharStatusTimeBuffID
|
BuffStack gumballBuff = {
|
||||||
pkt.eTBU = 1; // eTimeBuffUpdate
|
durationMilliseconds / MS_PER_PLAYER_TICK,
|
||||||
pkt.eTBT = 1; // eTimeBuffType 1 means nano
|
0,
|
||||||
pkt.iConditionBitFlag = player->iConditionBitFlag |= value1;
|
sock,
|
||||||
sock->sendPacket(pkt, P_FE2CL_PC_BUFF_UPDATE);
|
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);
|
sock->sendPacket((void*)&respbuf, P_FE2CL_REP_PC_ITEM_USE_SUCC, resplen);
|
||||||
// update inventory serverside
|
// update inventory serverside
|
||||||
player->Inven[resp->iSlotNum] = resp->RemainItem;
|
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) {
|
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) {
|
if (rolled.taros % miscDropChance.taroDropChanceTotal < miscDropChance.taroDropChance) {
|
||||||
plr->money += miscDropType.taroAmount;
|
plr->money += miscDropType.taroAmount;
|
||||||
// money nano boost
|
// money nano boost
|
||||||
if (plr->iConditionBitFlag & CSB_BIT_REWARD_CASH) {
|
if (plr->hasBuff(ECSB_REWARD_CASH)) {
|
||||||
int boost = 0;
|
int boost = 0;
|
||||||
if (Nanos::getNanoBoost(plr)) // for gumballs
|
if (Nanos::getNanoBoost(plr)) // for gumballs
|
||||||
boost = 1;
|
boost = 1;
|
||||||
@@ -770,7 +777,7 @@ static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRo
|
|||||||
if (levelDifference > 0)
|
if (levelDifference > 0)
|
||||||
fm = levelDifference < 10 ? fm - (levelDifference * fm / 10) : 0;
|
fm = levelDifference < 10 ? fm - (levelDifference * fm / 10) : 0;
|
||||||
// scavenger nano boost
|
// scavenger nano boost
|
||||||
if (plr->iConditionBitFlag & CSB_BIT_REWARD_BLOB) {
|
if (plr->hasBuff(ECSB_REWARD_BLOB)) {
|
||||||
int boost = 0;
|
int boost = 0;
|
||||||
if (Nanos::getNanoBoost(plr)) // for gumballs
|
if (Nanos::getNanoBoost(plr)) // for gumballs
|
||||||
boost = 1;
|
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) {
|
void Items::giveMobDrop(CNSocket *sock, Mob* mob, const DropRoll& rolled, const DropRoll& eventRolled) {
|
||||||
// sanity check
|
// sanity check
|
||||||
if (Items::MobToDropMap.find(mob->appearanceData.iNPCType) == Items::MobToDropMap.end()) {
|
if (Items::MobToDropMap.find(mob->type) == Items::MobToDropMap.end()) {
|
||||||
std::cout << "[WARN] Mob ID " << mob->appearanceData.iNPCType << " has no drops assigned" << std::endl;
|
std::cout << "[WARN] Mob ID " << mob->type << " has no drops assigned" << std::endl;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// find mob drop id
|
// find mob drop id
|
||||||
int mobDropId = Items::MobToDropMap[mob->appearanceData.iNPCType];
|
int mobDropId = Items::MobToDropMap[mob->type];
|
||||||
|
|
||||||
giveSingleDrop(sock, mob, mobDropId, rolled);
|
giveSingleDrop(sock, mob, mobDropId, rolled);
|
||||||
|
|
||||||
|
@@ -1,9 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "servers/CNShardServer.hpp"
|
#include "core/Core.hpp"
|
||||||
|
|
||||||
#include "Player.hpp"
|
#include "Player.hpp"
|
||||||
#include "MobAI.hpp"
|
|
||||||
#include "Rand.hpp"
|
#include "Rand.hpp"
|
||||||
|
#include "MobAI.hpp"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
struct CrocPotEntry {
|
struct CrocPotEntry {
|
||||||
int multStats, multLooks;
|
int multStats, multLooks;
|
||||||
|
@@ -1,11 +1,10 @@
|
|||||||
#include "servers/CNShardServer.hpp"
|
|
||||||
#include "Missions.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;
|
using namespace Missions;
|
||||||
|
|
||||||
@@ -164,14 +163,14 @@ static int giveMissionReward(CNSocket *sock, int task, int choice=0) {
|
|||||||
|
|
||||||
// update player
|
// update player
|
||||||
plr->money += reward->money;
|
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;
|
int boost = 0;
|
||||||
if (Nanos::getNanoBoost(plr)) // for gumballs
|
if (Nanos::getNanoBoost(plr)) // for gumballs
|
||||||
boost = 1;
|
boost = 1;
|
||||||
plr->money += reward->money * (5 + boost) / 25;
|
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;
|
int boost = 0;
|
||||||
if (Nanos::getNanoBoost(plr)) // for gumballs
|
if (Nanos::getNanoBoost(plr)) // for gumballs
|
||||||
boost = 1;
|
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));
|
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 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
|
for (ChunkPos& chunkPos : Chunking::getChunksInMap(plr->instanceID)) { // check all NPCs in the instance
|
||||||
Chunk* chunk = Chunking::chunks[chunkPos];
|
Chunk* chunk = Chunking::chunks[chunkPos];
|
||||||
for (EntityRef ref : chunk->entities) {
|
for (EntityRef ref : chunk->entities) {
|
||||||
if (ref.type != EntityType::PLAYER) {
|
if (ref.kind != EntityKind::PLAYER) {
|
||||||
BaseNPC* npc = (BaseNPC*)ref.getEntity();
|
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) {
|
if (path != nullptr) {
|
||||||
Transport::constructPathNPC(npc->appearanceData.iNPC_ID, path);
|
Transport::constructPathNPC(npc->id, path);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -399,7 +398,7 @@ static void taskEnd(CNSocket* sock, CNPacketData* data) {
|
|||||||
* once we comb over mission logic more throughly
|
* once we comb over mission logic more throughly
|
||||||
*/
|
*/
|
||||||
bool mobsAreKilled = false;
|
bool mobsAreKilled = false;
|
||||||
if (task->task["m_iHTaskType"] == 5) {
|
if (task->task["m_iHTaskType"] == (int)eTaskTypeProperty::Defeat) {
|
||||||
mobsAreKilled = true;
|
mobsAreKilled = true;
|
||||||
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
||||||
if (plr->tasks[i] == missionData->iTaskNum) {
|
if (plr->tasks[i] == missionData->iTaskNum) {
|
||||||
|
@@ -1,9 +1,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "servers/CNShardServer.hpp"
|
#include "core/Core.hpp"
|
||||||
|
#include "JSON.hpp"
|
||||||
|
|
||||||
#include "Player.hpp"
|
#include "Player.hpp"
|
||||||
|
|
||||||
#include "JSON.hpp"
|
#include <map>
|
||||||
|
|
||||||
struct Reward {
|
struct Reward {
|
||||||
int32_t id;
|
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
|
#pragma once
|
||||||
|
|
||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
#include "NPCManager.hpp"
|
#include "JSON.hpp"
|
||||||
|
|
||||||
enum class MobState {
|
#include "Entities.hpp"
|
||||||
INACTIVE,
|
|
||||||
ROAMING,
|
#include <unordered_map>
|
||||||
COMBAT,
|
#include <string>
|
||||||
RETREAT,
|
|
||||||
DEAD
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace MobAI {
|
namespace MobAI {
|
||||||
// needs to be declared before Mob's constructor
|
void deadStep(CombatNPC* self, time_t currTime);
|
||||||
void step(CombatNPC*, time_t);
|
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 {
|
struct Mob : public CombatNPC {
|
||||||
// general
|
|
||||||
MobState state = MobState::INACTIVE;
|
|
||||||
|
|
||||||
std::unordered_map<int32_t,time_t> unbuffTimes = {};
|
|
||||||
|
|
||||||
// dead
|
// dead
|
||||||
time_t killedTime = 0;
|
time_t killedTime = 0;
|
||||||
@@ -47,16 +47,13 @@ struct Mob : public CombatNPC {
|
|||||||
int offsetX = 0, offsetY = 0;
|
int offsetX = 0, offsetY = 0;
|
||||||
int groupMember[4] = {};
|
int groupMember[4] = {};
|
||||||
|
|
||||||
// for optimizing away AI in empty chunks
|
|
||||||
int playersInView = 0;
|
|
||||||
|
|
||||||
// temporary; until we're sure what's what
|
// temporary; until we're sure what's what
|
||||||
nlohmann::json data = {};
|
nlohmann::json data = {};
|
||||||
|
|
||||||
Mob(int x, int y, int z, int angle, uint64_t iID, int t, nlohmann::json d, int32_t id)
|
Mob(int spawnX, int spawnY, int spawnZ, int angle, uint64_t iID, int t, nlohmann::json d, int32_t id)
|
||||||
: CombatNPC(x, y, z, angle, iID, t, id, d["m_iHP"]),
|
: CombatNPC(spawnX, spawnY, spawnZ, angle, iID, t, id, d["m_iHP"]),
|
||||||
sightRange(d["m_iSightRange"]) {
|
sightRange(d["m_iSightRange"]) {
|
||||||
state = MobState::ROAMING;
|
state = AIState::ROAMING;
|
||||||
|
|
||||||
data = d;
|
data = d;
|
||||||
|
|
||||||
@@ -65,20 +62,28 @@ struct Mob : public CombatNPC {
|
|||||||
idleRange = (int)data["m_iIdleRange"];
|
idleRange = (int)data["m_iIdleRange"];
|
||||||
level = data["m_iNpcLevel"];
|
level = data["m_iNpcLevel"];
|
||||||
|
|
||||||
roamX = spawnX = x;
|
roamX = spawnX;
|
||||||
roamY = spawnY = y;
|
roamY = spawnY;
|
||||||
roamZ = spawnZ = z;
|
roamZ = spawnZ;
|
||||||
|
|
||||||
offsetX = 0;
|
offsetX = 0;
|
||||||
offsetY = 0;
|
offsetY = 0;
|
||||||
|
|
||||||
appearanceData.iConditionBitFlag = 0;
|
|
||||||
|
|
||||||
// NOTE: there appear to be discrepancies in the dump
|
// NOTE: there appear to be discrepancies in the dump
|
||||||
appearanceData.iHP = maxHealth;
|
hp = maxHealth;
|
||||||
|
|
||||||
type = EntityType::MOB;
|
kind = EntityKind::MOB;
|
||||||
_stepAI = MobAI::step;
|
|
||||||
|
// 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
|
// constructor for /summon
|
||||||
@@ -89,6 +94,9 @@ struct Mob : public CombatNPC {
|
|||||||
|
|
||||||
~Mob() {}
|
~Mob() {}
|
||||||
|
|
||||||
|
virtual int takeDamage(EntityRef src, int amt) override;
|
||||||
|
virtual void step(time_t currTime) override;
|
||||||
|
|
||||||
auto operator[](std::string s) {
|
auto operator[](std::string s) {
|
||||||
return data[s];
|
return data[s];
|
||||||
}
|
}
|
||||||
@@ -103,5 +111,4 @@ namespace MobAI {
|
|||||||
void clearDebuff(Mob *mob);
|
void clearDebuff(Mob *mob);
|
||||||
void followToCombat(Mob *mob);
|
void followToCombat(Mob *mob);
|
||||||
void groupRetreat(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 "NPCManager.hpp"
|
||||||
|
|
||||||
|
#include "servers/CNShardServer.hpp"
|
||||||
|
|
||||||
|
#include "PlayerManager.hpp"
|
||||||
#include "Items.hpp"
|
#include "Items.hpp"
|
||||||
#include "settings.hpp"
|
#include "settings.hpp"
|
||||||
#include "Combat.hpp"
|
#include "Combat.hpp"
|
||||||
@@ -20,8 +24,6 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
|
||||||
#include "JSON.hpp"
|
|
||||||
|
|
||||||
using namespace NPCManager;
|
using namespace NPCManager;
|
||||||
|
|
||||||
std::unordered_map<int32_t, BaseNPC*> NPCManager::NPCs;
|
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) {
|
void NPCManager::updateNPCPosition(int32_t id, int X, int Y, int Z, uint64_t I, int angle) {
|
||||||
BaseNPC* npc = NPCs[id];
|
BaseNPC* npc = NPCs[id];
|
||||||
npc->appearanceData.iAngle = angle;
|
npc->angle = angle;
|
||||||
ChunkPos oldChunk = npc->chunkPos;
|
ChunkPos oldChunk = npc->chunkPos;
|
||||||
ChunkPos newChunk = Chunking::chunkPosAt(X, Y, I);
|
ChunkPos newChunk = Chunking::chunkPosAt(X, Y, I);
|
||||||
npc->x = X;
|
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);
|
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++) {
|
for (auto it = npc->viewableChunks.begin(); it != npc->viewableChunks.end(); it++) {
|
||||||
Chunk* chunk = *it;
|
Chunk* chunk = *it;
|
||||||
for (const EntityRef& ref : chunk->entities) {
|
for (const EntityRef& ref : chunk->entities) {
|
||||||
if (ref.type == EntityType::PLAYER)
|
if (ref.kind == EntityKind::PLAYER)
|
||||||
ref.sock->sendPacket(buf, type, size);
|
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
|
// 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;
|
uint64_t inst = baseInstance ? MAPNUM(instance) : instance;
|
||||||
#define EXTRA_HEIGHT 0
|
|
||||||
|
|
||||||
//assert(nextId < INT32_MAX);
|
|
||||||
int id = nextId--;
|
int id = nextId--;
|
||||||
int team = NPCData[type]["m_iTeam"];
|
int team = NPCData[type]["m_iTeam"];
|
||||||
BaseNPC *npc = nullptr;
|
BaseNPC *npc = nullptr;
|
||||||
|
|
||||||
if (team == 2) {
|
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
|
// re-enable respawning, if desired
|
||||||
((Mob*)npc)->summoned = !respawn;
|
((Mob*)npc)->summoned = !respawn;
|
||||||
} else
|
} else
|
||||||
npc = new BaseNPC(x, y, z + EXTRA_HEIGHT, 0, inst, type, id);
|
npc = new BaseNPC(0, inst, type, id);
|
||||||
|
|
||||||
NPCs[id] = npc;
|
NPCs[id] = npc;
|
||||||
|
|
||||||
@@ -154,7 +154,7 @@ static void npcSummonHandler(CNSocket* sock, CNPacketData* data) {
|
|||||||
|
|
||||||
for (int i = 0; i < req->iNPCCnt; i++) {
|
for (int i = 0; i < req->iNPCCnt; i++) {
|
||||||
BaseNPC *npc = summonNPC(plr->x, plr->y, plr->z, plr->instanceID, req->iNPCType);
|
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) {
|
if (Warps[warpId].isInstance) {
|
||||||
uint64_t instanceID = Warps[warpId].instanceID;
|
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 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
|
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);
|
Chunking::createInstance(instanceID);
|
||||||
|
|
||||||
// save Lair entrance coords as a pseudo-Resurrect 'Em
|
// save Lair entrance coords as a pseudo-Resurrect 'Em
|
||||||
@@ -195,14 +198,13 @@ static void handleWarp(CNSocket* sock, int32_t warpId) {
|
|||||||
plr->recallInstance = instanceID;
|
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);
|
PlayerManager::sendPlayerTo(sock, Warps[warpId].x, Warps[warpId].y, Warps[warpId].z, instanceID);
|
||||||
else {
|
else {
|
||||||
Player* leaderPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
auto players = plr->group->filter(EntityKind::PLAYER);
|
||||||
|
for (int i = 0; i < players.size(); i++) {
|
||||||
for (int i = 0; i < leaderPlr->groupCnt; i++) {
|
CNSocket* sockTo = players[i].sock;
|
||||||
Player* otherPlr = PlayerManager::getPlayerFromID(leaderPlr->groupIDs[i]);
|
Player* otherPlr = PlayerManager::getPlayer(sockTo);
|
||||||
CNSocket* sockTo = PlayerManager::getSockFromID(leaderPlr->groupIDs[i]);
|
|
||||||
|
|
||||||
if (otherPlr == nullptr || sockTo == nullptr)
|
if (otherPlr == nullptr || sockTo == nullptr)
|
||||||
continue;
|
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
|
for (auto c = chunks->begin(); c != chunks->end(); c++) { // haha get it
|
||||||
Chunk* chunk = *c;
|
Chunk* chunk = *c;
|
||||||
for (auto ent = chunk->entities.begin(); ent != chunk->entities.end(); ent++) {
|
for (auto ent = chunk->entities.begin(); ent != chunk->entities.end(); ent++) {
|
||||||
if (ent->type == EntityType::PLAYER)
|
if (ent->kind == EntityKind::PLAYER)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
BaseNPC* npcTemp = (BaseNPC*)ent->getEntity();
|
BaseNPC* npcTemp = (BaseNPC*)ent->getEntity();
|
||||||
@@ -291,57 +293,55 @@ BaseNPC* NPCManager::getNearestNPC(std::set<Chunk*>* chunks, int X, int Y, int Z
|
|||||||
return npc;
|
return npc;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Move this to MobAI, possibly
|
// TODO: Move this to separate file in ai/ subdir when implementing more events
|
||||||
#pragma region NPCEvents
|
#pragma region NPCEvents
|
||||||
|
|
||||||
// summon right arm and stage 2 body
|
// 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
|
Mob *oldbody = (Mob*)npc; // adaptium, stun
|
||||||
Player *plr = PlayerManager::getPlayer(sock);
|
|
||||||
|
|
||||||
std::cout << "Lord Fuse stage two" << std::endl;
|
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
|
// 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;
|
newbody->angle = oldbody->angle;
|
||||||
NPCManager::updateNPCPosition(newbody->appearanceData.iNPC_ID, newbody->spawnX, newbody->spawnY, newbody->spawnZ,
|
NPCManager::updateNPCPosition(newbody->id, newbody->spawnX, newbody->spawnY, newbody->spawnZ,
|
||||||
plr->instanceID, oldbody->appearanceData.iAngle);
|
oldbody->instanceID, oldbody->angle);
|
||||||
|
|
||||||
// right arm, Adaptium, Stun
|
// 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;
|
arm->angle = oldbody->angle;
|
||||||
NPCManager::updateNPCPosition(arm->appearanceData.iNPC_ID, arm->spawnX, arm->spawnY, arm->spawnZ,
|
NPCManager::updateNPCPosition(arm->id, arm->spawnX, arm->spawnY, arm->spawnZ,
|
||||||
plr->instanceID, oldbody->appearanceData.iAngle);
|
oldbody->instanceID, oldbody->angle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// summon left arm and stage 3 body
|
// summon left arm and stage 3 body
|
||||||
static void lordFuseStageThree(CNSocket *sock, BaseNPC *npc) {
|
static void lordFuseStageThree(CombatNPC *npc) {
|
||||||
Mob *oldbody = (Mob*)npc;
|
Mob *oldbody = (Mob*)npc;
|
||||||
Player *plr = PlayerManager::getPlayer(sock);
|
|
||||||
|
|
||||||
std::cout << "Lord Fuse stage three" << std::endl;
|
std::cout << "Lord Fuse stage three" << std::endl;
|
||||||
|
|
||||||
// Cosmix, Damage Point
|
// 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;
|
newbody->angle = oldbody->angle;
|
||||||
NPCManager::updateNPCPosition(newbody->appearanceData.iNPC_ID, newbody->spawnX, newbody->spawnY, newbody->spawnZ,
|
NPCManager::updateNPCPosition(newbody->id, newbody->spawnX, newbody->spawnY, newbody->spawnZ,
|
||||||
plr->instanceID, oldbody->appearanceData.iAngle);
|
newbody->instanceID, oldbody->angle);
|
||||||
|
|
||||||
// Blastons, Heal
|
// 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;
|
arm->angle = oldbody->angle;
|
||||||
NPCManager::updateNPCPosition(arm->appearanceData.iNPC_ID, arm->spawnX, arm->spawnY, arm->spawnZ,
|
NPCManager::updateNPCPosition(arm->id, arm->spawnX, arm->spawnY, arm->spawnZ,
|
||||||
plr->instanceID, oldbody->appearanceData.iAngle);
|
arm->instanceID, oldbody->angle);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<NPCEvent> NPCManager::NPCEvents = {
|
std::vector<NPCEvent> NPCManager::NPCEvents = {
|
||||||
NPCEvent(2466, ON_KILLED, lordFuseStageTwo),
|
NPCEvent(2466, AIState::DEAD, lordFuseStageTwo),
|
||||||
NPCEvent(2467, ON_KILLED, lordFuseStageThree),
|
NPCEvent(2467, AIState::DEAD, lordFuseStageThree),
|
||||||
};
|
};
|
||||||
|
|
||||||
#pragma endregion NPCEvents
|
#pragma endregion NPCEvents
|
||||||
@@ -352,11 +352,11 @@ void NPCManager::queueNPCRemoval(int32_t id) {
|
|||||||
|
|
||||||
static void step(CNServer *serv, time_t currTime) {
|
static void step(CNServer *serv, time_t currTime) {
|
||||||
for (auto& pair : NPCs) {
|
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;
|
continue;
|
||||||
auto npc = (CombatNPC*)pair.second;
|
auto npc = (CombatNPC*)pair.second;
|
||||||
|
|
||||||
npc->stepAI(currTime);
|
npc->step(currTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
// deallocate all NPCs queued for removal
|
// 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_NPC_UNSUMMON, npcUnsummonHandler);
|
||||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_BARKER, npcBarkHandler);
|
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
|
#pragma once
|
||||||
|
|
||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
#include "PlayerManager.hpp"
|
|
||||||
#include "NPC.hpp"
|
|
||||||
#include "Transport.hpp"
|
|
||||||
|
|
||||||
#include "JSON.hpp"
|
#include "JSON.hpp"
|
||||||
|
|
||||||
|
#include "Transport.hpp"
|
||||||
|
#include "Chunking.hpp"
|
||||||
|
#include "Entities.hpp"
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
#define RESURRECT_HEIGHT 400
|
#define RESURRECT_HEIGHT 400
|
||||||
|
|
||||||
enum Trigger {
|
typedef void (*NPCEventHandler)(CombatNPC*);
|
||||||
ON_KILLED,
|
|
||||||
ON_COMBAT
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef void (*NPCEventHandler)(CNSocket*, BaseNPC*);
|
|
||||||
|
|
||||||
struct NPCEvent {
|
struct NPCEvent {
|
||||||
int32_t npcType;
|
int32_t npcType;
|
||||||
int trigger;
|
AIState triggerState;
|
||||||
NPCEventHandler handler;
|
NPCEventHandler handler;
|
||||||
|
|
||||||
NPCEvent(int32_t t, int tr, NPCEventHandler hndlr)
|
NPCEvent(int32_t t, AIState tr, NPCEventHandler hndlr)
|
||||||
: npcType(t), trigger(tr), handler(hndlr) {}
|
: npcType(t), triggerState(tr), handler(hndlr) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct WarpLocation;
|
|
||||||
|
|
||||||
namespace NPCManager {
|
namespace NPCManager {
|
||||||
extern std::unordered_map<int32_t, BaseNPC*> NPCs;
|
extern std::unordered_map<int32_t, BaseNPC*> NPCs;
|
||||||
extern std::map<int32_t, WarpLocation> Warps;
|
extern std::map<int32_t, WarpLocation> Warps;
|
||||||
@@ -44,7 +38,7 @@ namespace NPCManager {
|
|||||||
void destroyNPC(int32_t);
|
void destroyNPC(int32_t);
|
||||||
void updateNPCPosition(int32_t, int X, int Y, int Z, uint64_t I, int angle);
|
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);
|
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 "Nanos.hpp"
|
||||||
|
|
||||||
|
#include "servers/CNShardServer.hpp"
|
||||||
|
|
||||||
#include "PlayerManager.hpp"
|
#include "PlayerManager.hpp"
|
||||||
#include "NPCManager.hpp"
|
|
||||||
#include "Combat.hpp"
|
|
||||||
#include "Missions.hpp"
|
#include "Missions.hpp"
|
||||||
#include "Groups.hpp"
|
|
||||||
#include "Abilities.hpp"
|
#include "Abilities.hpp"
|
||||||
|
#include "NPCManager.hpp"
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
@@ -82,40 +82,21 @@ void Nanos::summonNano(CNSocket *sock, int slot, bool silent) {
|
|||||||
if (slot != -1 && plr->Nanos[nanoID].iSkillID == 0)
|
if (slot != -1 && plr->Nanos[nanoID].iSkillID == 0)
|
||||||
return; // prevent powerless nanos from summoning
|
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)
|
if (nanoID >= NANO_COUNT || nanoID < 0)
|
||||||
return; // sanity check
|
return; // sanity check
|
||||||
|
|
||||||
plr->activeNano = nanoID;
|
plr->activeNano = nanoID;
|
||||||
skillID = plr->Nanos[nanoID].iSkillID;
|
sNano& nano = plr->Nanos[nanoID];
|
||||||
|
|
||||||
// passive nano buffing
|
SkillData* skill = Abilities::SkillTable.count(nano.iSkillID) > 0
|
||||||
if (SkillTable[skillID].drainType == 2) {
|
? &Abilities::SkillTable[nano.iSkillID] : nullptr;
|
||||||
std::vector<int> targetData = findTargets(plr, skillID);
|
if (slot != -1 && skill != nullptr && skill->drainType == SkillDrainType::PASSIVE) {
|
||||||
|
// passive buff effect
|
||||||
int boost = 0;
|
resp.eCSTB___Add = 1;
|
||||||
if (getNanoBoost(plr))
|
ICombatant* src = dynamic_cast<ICombatant*>(plr);
|
||||||
boost = 1;
|
int32_t targets[] = { plr->iID };
|
||||||
|
std::vector<ICombatant*> affectedCombatants = Abilities::matchTargets(src, skill, 1, targets);
|
||||||
for (auto& pwr : NanoPowers) {
|
Abilities::useNanoSkill(sock, skill, nano, affectedCombatants);
|
||||||
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]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!silent) // silent nano death but only for the summoning player
|
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.
|
// 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);
|
INITSTRUCT(sP_FE2CL_NANO_ACTIVE, pkt1);
|
||||||
pkt1.iPC_ID = plr->iID;
|
pkt1.iPC_ID = plr->iID;
|
||||||
pkt1.Nano = plr->Nanos[nanoID];
|
pkt1.Nano = nano;
|
||||||
PlayerManager::sendToViewable(sock, pkt1, P_FE2CL_NANO_ACTIVE);
|
PlayerManager::sendToViewable(sock, pkt1, P_FE2CL_NANO_ACTIVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,7 +192,7 @@ int Nanos::nanoStyle(int nanoID) {
|
|||||||
bool Nanos::getNanoBoost(Player* plr) {
|
bool Nanos::getNanoBoost(Player* plr) {
|
||||||
for (int i = 0; i < 3; i++)
|
for (int i = 0; i < 3; i++)
|
||||||
if (plr->equippedNanos[i] == plr->activeNano)
|
if (plr->equippedNanos[i] == plr->activeNano)
|
||||||
if (plr->iConditionBitFlag & (CSB_BIT_STIMPAKSLOT1 << i))
|
if (plr->hasBuff(ECSB_STIMPAKSLOT1 + i))
|
||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -235,18 +216,6 @@ static void nanoEquipHandler(CNSocket* sock, CNPacketData* data) {
|
|||||||
// Update player
|
// Update player
|
||||||
plr->equippedNanos[nano->iNanoSlotNum] = nano->iNanoID;
|
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
|
// unsummon nano if replaced
|
||||||
if (plr->activeNano == plr->equippedNanos[nano->iNanoSlotNum])
|
if (plr->activeNano == plr->equippedNanos[nano->iNanoSlotNum])
|
||||||
summonNano(sock, -1);
|
summonNano(sock, -1);
|
||||||
@@ -289,28 +258,26 @@ static void nanoSummonHandler(CNSocket* sock, CNPacketData* data) {
|
|||||||
static void nanoSkillUseHandler(CNSocket* sock, CNPacketData* data) {
|
static void nanoSkillUseHandler(CNSocket* sock, CNPacketData* data) {
|
||||||
Player *plr = PlayerManager::getPlayer(sock);
|
Player *plr = PlayerManager::getPlayer(sock);
|
||||||
|
|
||||||
int16_t nanoID = plr->activeNano;
|
// validate request check
|
||||||
int16_t skillID = plr->Nanos[nanoID].iSkillID;
|
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(
|
DEBUGLOG(
|
||||||
std::cout << PlayerManager::getPlayerName(plr) << " requested to summon nano skill " << std::endl;
|
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 (plr->Nanos[plr->activeNano].iStamina <= 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)
|
|
||||||
summonNano(sock, -1);
|
summonNano(sock, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <set>
|
#include "core/Core.hpp"
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "Player.hpp"
|
#include "Player.hpp"
|
||||||
#include "servers/CNShardServer.hpp"
|
#include "Abilities.hpp"
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
|
||||||
struct NanoData {
|
struct NanoData {
|
||||||
int style;
|
int style;
|
||||||
|
@@ -1,17 +1,21 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
#include "Chunking.hpp"
|
|
||||||
#include "Entities.hpp"
|
#include "Entities.hpp"
|
||||||
|
#include "Groups.hpp"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
/* forward declaration(s) */
|
||||||
|
class Buff;
|
||||||
|
struct BuffStack;
|
||||||
|
|
||||||
#define ACTIVE_MISSION_COUNT 6
|
#define ACTIVE_MISSION_COUNT 6
|
||||||
|
|
||||||
#define PC_MAXHEALTH(level) (925 + 75 * (level))
|
#define PC_MAXHEALTH(level) (925 + 75 * (level))
|
||||||
|
|
||||||
struct Player : public Entity {
|
struct Player : public Entity, public ICombatant {
|
||||||
int accountId = 0;
|
int accountId = 0;
|
||||||
int accountLevel = 0; // permission level (see CN_ACCOUNT_LEVEL enums)
|
int accountLevel = 0; // permission level (see CN_ACCOUNT_LEVEL enums)
|
||||||
int32_t iID = 0;
|
int32_t iID = 0;
|
||||||
@@ -32,9 +36,8 @@ struct Player : public Entity {
|
|||||||
int8_t iPCState = 0;
|
int8_t iPCState = 0;
|
||||||
int32_t iWarpLocationFlag = 0;
|
int32_t iWarpLocationFlag = 0;
|
||||||
int64_t aSkywayLocationFlag[2] = {};
|
int64_t aSkywayLocationFlag[2] = {};
|
||||||
int32_t iConditionBitFlag = 0;
|
|
||||||
int32_t iSelfConditionBitFlag = 0;
|
|
||||||
int8_t iSpecialState = 0;
|
int8_t iSpecialState = 0;
|
||||||
|
std::unordered_map<int, Buff*> buffs = {};
|
||||||
|
|
||||||
int angle = 0;
|
int angle = 0;
|
||||||
int lastX = 0, lastY = 0, lastZ = 0, lastAngle = 0;
|
int lastX = 0, lastY = 0, lastZ = 0, lastAngle = 0;
|
||||||
@@ -49,7 +52,6 @@ struct Player : public Entity {
|
|||||||
|
|
||||||
bool inCombat = false;
|
bool inCombat = false;
|
||||||
bool onMonkey = false;
|
bool onMonkey = false;
|
||||||
int nanoDrainRate = 0;
|
|
||||||
int healCooldown = 0;
|
int healCooldown = 0;
|
||||||
|
|
||||||
int pointDamage = 0;
|
int pointDamage = 0;
|
||||||
@@ -65,10 +67,7 @@ struct Player : public Entity {
|
|||||||
|
|
||||||
sTimeLimitItemDeleteInfo2CL toRemoveVehicle = {};
|
sTimeLimitItemDeleteInfo2CL toRemoveVehicle = {};
|
||||||
|
|
||||||
int32_t iIDGroup = 0;
|
Group* group = nullptr;
|
||||||
int groupCnt = 0;
|
|
||||||
int32_t groupIDs[4] = {};
|
|
||||||
int32_t iGroupConditionBitFlag = 0;
|
|
||||||
|
|
||||||
bool notify = false;
|
bool notify = false;
|
||||||
bool hidden = false;
|
bool hidden = false;
|
||||||
@@ -85,8 +84,31 @@ struct Player : public Entity {
|
|||||||
time_t lastShot = 0;
|
time_t lastShot = 0;
|
||||||
std::vector<sItemBase> buyback = {};
|
std::vector<sItemBase> buyback = {};
|
||||||
|
|
||||||
Player() { type = EntityType::PLAYER; }
|
Player() { kind = EntityKind::PLAYER; }
|
||||||
|
|
||||||
virtual void enterIntoViewOf(CNSocket *sock) override;
|
virtual void enterIntoViewOf(CNSocket *sock) override;
|
||||||
virtual void disappearFromViewOf(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 "core/CNShared.hpp"
|
||||||
#include "servers/CNShardServer.hpp"
|
#include "servers/CNShardServer.hpp"
|
||||||
#include "db/Database.hpp"
|
|
||||||
#include "PlayerManager.hpp"
|
|
||||||
#include "NPCManager.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 "Combat.hpp"
|
||||||
#include "Racing.hpp"
|
#include "Racing.hpp"
|
||||||
#include "BuiltinCommands.hpp"
|
|
||||||
#include "Abilities.hpp"
|
|
||||||
#include "Eggs.hpp"
|
#include "Eggs.hpp"
|
||||||
|
#include "Missions.hpp"
|
||||||
#include "settings.hpp"
|
#include "Chat.hpp"
|
||||||
|
#include "Items.hpp"
|
||||||
|
#include "Buddies.hpp"
|
||||||
|
#include "BuiltinCommands.hpp"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
@@ -41,7 +36,13 @@ void PlayerManager::removePlayer(CNSocket* key) {
|
|||||||
Player* plr = getPlayer(key);
|
Player* plr = getPlayer(key);
|
||||||
uint64_t fromInstance = plr->instanceID;
|
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
|
// remove player's bullets
|
||||||
Combat::Bullets.erase(plr->iID);
|
Combat::Bullets.erase(plr->iID);
|
||||||
@@ -65,16 +66,6 @@ void PlayerManager::removePlayer(CNSocket* key) {
|
|||||||
// if the player was in a lair, clean it up
|
// if the player was in a lair, clean it up
|
||||||
Chunking::destroyInstanceIfEmpty(fromInstance);
|
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;
|
std::cout << players.size() << " players" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +77,10 @@ void PlayerManager::updatePlayerPosition(CNSocket* sock, int X, int Y, int Z, ui
|
|||||||
plr->x = X;
|
plr->x = X;
|
||||||
plr->y = Y;
|
plr->y = Y;
|
||||||
plr->z = Z;
|
plr->z = Z;
|
||||||
plr->instanceID = I;
|
if (plr->instanceID != I) {
|
||||||
|
plr->instanceID = I;
|
||||||
|
plr->recallInstance = INSTANCE_OVERWORLD;
|
||||||
|
}
|
||||||
if (oldChunk == newChunk)
|
if (oldChunk == newChunk)
|
||||||
return; // didn't change chunks
|
return; // didn't change chunks
|
||||||
Chunking::updateEntityChunk({sock}, oldChunk, newChunk);
|
Chunking::updateEntityChunk({sock}, oldChunk, newChunk);
|
||||||
@@ -132,24 +126,6 @@ void PlayerManager::sendPlayerTo(CNSocket* sock, int X, int Y, int Z, uint64_t I
|
|||||||
sock->sendPacket(resp, P_FE2CL_REP_PC_WARP_USE_NPC_SUCC);
|
sock->sendPacket(resp, P_FE2CL_REP_PC_WARP_USE_NPC_SUCC);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (I != INSTANCE_OVERWORLD) {
|
|
||||||
INITSTRUCT(sP_FE2CL_INSTANCE_MAP_INFO, pkt);
|
|
||||||
pkt.iInstanceMapNum = (int32_t)MAPNUM(I); // lower 32 bits are mapnum
|
|
||||||
if (I != fromInstance // do not retransmit MAP_INFO on recall
|
|
||||||
&& Racing::EPData.find(pkt.iInstanceMapNum) != Racing::EPData.end()) {
|
|
||||||
EPInfo* ep = &Racing::EPData[pkt.iInstanceMapNum];
|
|
||||||
pkt.iEP_ID = ep->EPID;
|
|
||||||
pkt.iMapCoordX_Min = ep->zoneX * 51200;
|
|
||||||
pkt.iMapCoordX_Max = (ep->zoneX + 1) * 51200;
|
|
||||||
pkt.iMapCoordY_Min = ep->zoneY * 51200;
|
|
||||||
pkt.iMapCoordY_Max = (ep->zoneY + 1) * 51200;
|
|
||||||
pkt.iMapCoordZ_Min = INT32_MIN;
|
|
||||||
pkt.iMapCoordZ_Max = INT32_MAX;
|
|
||||||
}
|
|
||||||
|
|
||||||
sock->sendPacket(pkt, P_FE2CL_INSTANCE_MAP_INFO);
|
|
||||||
}
|
|
||||||
|
|
||||||
INITSTRUCT(sP_FE2CL_REP_PC_GOTO_SUCC, pkt2);
|
INITSTRUCT(sP_FE2CL_REP_PC_GOTO_SUCC, pkt2);
|
||||||
pkt2.iX = X;
|
pkt2.iX = X;
|
||||||
pkt2.iY = Y;
|
pkt2.iY = Y;
|
||||||
@@ -232,8 +208,7 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
|
|||||||
Database::getPlayer(plr, lm->playerId);
|
Database::getPlayer(plr, lm->playerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
plr->groupCnt = 1;
|
plr->group = nullptr;
|
||||||
plr->iIDGroup = plr->groupIDs[0] = plr->iID;
|
|
||||||
|
|
||||||
response.iID = plr->iID;
|
response.iID = plr->iID;
|
||||||
response.uiSvrTime = getTime();
|
response.uiSvrTime = getTime();
|
||||||
@@ -348,12 +323,20 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
|
|||||||
delete lm;
|
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) {
|
void PlayerManager::sendToViewable(CNSocket* sock, void* buf, uint32_t type, size_t size) {
|
||||||
Player* plr = getPlayer(sock);
|
Player* plr = getPlayer(sock);
|
||||||
for (auto it = plr->viewableChunks.begin(); it != plr->viewableChunks.end(); it++) {
|
for (auto it = plr->viewableChunks.begin(); it != plr->viewableChunks.end(); it++) {
|
||||||
Chunk* chunk = *it;
|
Chunk* chunk = *it;
|
||||||
for (const EntityRef& ref : chunk->entities) {
|
for (const EntityRef& ref : chunk->entities) {
|
||||||
if (ref.type != EntityType::PLAYER || ref.sock == sock)
|
if (ref.kind != EntityKind::PLAYER || ref.sock == sock)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
ref.sock->sendPacket(buf, type, size);
|
ref.sock->sendPacket(buf, type, size);
|
||||||
@@ -376,6 +359,24 @@ static void loadPlayer(CNSocket* sock, CNPacketData* data) {
|
|||||||
updatePlayerPosition(sock, plr->x, plr->y, plr->z, plr->instanceID, plr->angle);
|
updatePlayerPosition(sock, plr->x, plr->y, plr->z, plr->instanceID, plr->angle);
|
||||||
|
|
||||||
sock->sendPacket(response, P_FE2CL_REP_PC_LOADING_COMPLETE_SUCC);
|
sock->sendPacket(response, P_FE2CL_REP_PC_LOADING_COMPLETE_SUCC);
|
||||||
|
|
||||||
|
if (plr->instanceID != INSTANCE_OVERWORLD) {
|
||||||
|
INITSTRUCT(sP_FE2CL_INSTANCE_MAP_INFO, pkt);
|
||||||
|
pkt.iInstanceMapNum = (int32_t)MAPNUM(plr->instanceID); // lower 32 bits are mapnum
|
||||||
|
if (pkt.iInstanceMapNum != plr->recallInstance // do not retransmit MAP_INFO on recall
|
||||||
|
&& Racing::EPData.find(pkt.iInstanceMapNum) != Racing::EPData.end()) {
|
||||||
|
EPInfo* ep = &Racing::EPData[pkt.iInstanceMapNum];
|
||||||
|
pkt.iEP_ID = ep->EPID;
|
||||||
|
pkt.iMapCoordX_Min = ep->zoneX * 51200;
|
||||||
|
pkt.iMapCoordX_Max = (ep->zoneX + 1) * 51200;
|
||||||
|
pkt.iMapCoordY_Min = ep->zoneY * 51200;
|
||||||
|
pkt.iMapCoordY_Max = (ep->zoneY + 1) * 51200;
|
||||||
|
pkt.iMapCoordZ_Min = INT32_MIN;
|
||||||
|
pkt.iMapCoordZ_Max = INT32_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
sock->sendPacket(pkt, P_FE2CL_INSTANCE_MAP_INFO);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void heartbeatPlayer(CNSocket* sock, CNPacketData* data) {
|
static void heartbeatPlayer(CNSocket* sock, CNPacketData* data) {
|
||||||
@@ -406,21 +407,23 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) {
|
|||||||
int activeSlot = -1;
|
int activeSlot = -1;
|
||||||
bool move = false;
|
bool move = false;
|
||||||
|
|
||||||
if (reviveData->iRegenType == 3 && plr->iConditionBitFlag & CSB_BIT_PHOENIX) {
|
switch ((ePCRegenType)reviveData->iRegenType) {
|
||||||
// nano revive
|
case ePCRegenType::HereByPhoenix: // nano revive
|
||||||
|
if (!(plr->hasBuff(ECSB_PHOENIX)))
|
||||||
|
return; // sanity check
|
||||||
plr->Nanos[plr->activeNano].iStamina = 0;
|
plr->Nanos[plr->activeNano].iStamina = 0;
|
||||||
|
// fallthrough
|
||||||
|
case ePCRegenType::HereByPhoenixGroup: // revived by group member's nano
|
||||||
plr->HP = PC_MAXHEALTH(plr->level) / 2;
|
plr->HP = PC_MAXHEALTH(plr->level) / 2;
|
||||||
Nanos::applyBuff(sock, plr->Nanos[plr->activeNano].iSkillID, 2, 1, 0);
|
break;
|
||||||
} else if (reviveData->iRegenType == 4) {
|
|
||||||
// revived by group member's nano
|
default: // plain respawn
|
||||||
plr->HP = PC_MAXHEALTH(plr->level) / 2;
|
plr->HP = PC_MAXHEALTH(plr->level) / 2;
|
||||||
} else if (reviveData->iRegenType == 5) {
|
plr->clearBuffs(false);
|
||||||
// warp away
|
// fallthrough
|
||||||
|
case ePCRegenType::Unstick: // warp away
|
||||||
move = true;
|
move = true;
|
||||||
} else {
|
break;
|
||||||
// plain respawn
|
|
||||||
move = true;
|
|
||||||
plr->HP = PC_MAXHEALTH(plr->level) / 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
@@ -472,11 +475,9 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) {
|
|||||||
resp2.PCRegenDataForOtherPC.iHP = plr->HP;
|
resp2.PCRegenDataForOtherPC.iHP = plr->HP;
|
||||||
resp2.PCRegenDataForOtherPC.iAngle = plr->angle;
|
resp2.PCRegenDataForOtherPC.iAngle = plr->angle;
|
||||||
|
|
||||||
Player *otherPlr = getPlayerFromID(plr->iIDGroup);
|
if (plr->group != nullptr) {
|
||||||
if (otherPlr != nullptr) {
|
|
||||||
int bitFlag = Groups::getGroupFlags(otherPlr);
|
resp2.PCRegenDataForOtherPC.iConditionBitFlag = plr->getCompositeCondition();
|
||||||
resp2.PCRegenDataForOtherPC.iConditionBitFlag = plr->iConditionBitFlag = plr->iSelfConditionBitFlag | bitFlag;
|
|
||||||
|
|
||||||
resp2.PCRegenDataForOtherPC.iPCState = plr->iPCState;
|
resp2.PCRegenDataForOtherPC.iPCState = plr->iPCState;
|
||||||
resp2.PCRegenDataForOtherPC.iSpecialState = plr->iSpecialState;
|
resp2.PCRegenDataForOtherPC.iSpecialState = plr->iSpecialState;
|
||||||
resp2.PCRegenDataForOtherPC.Nano = plr->Nanos[plr->activeNano];
|
resp2.PCRegenDataForOtherPC.Nano = plr->Nanos[plr->activeNano];
|
||||||
@@ -579,7 +580,7 @@ static void setFirstUseFlag(CNSocket* sock, CNPacketData* data) {
|
|||||||
std::cout << "[WARN] Client submitted invalid first use flag number?!" << std::endl;
|
std::cout << "[WARN] Client submitted invalid first use flag number?!" << std::endl;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flag->iFlagCode <= 64)
|
if (flag->iFlagCode <= 64)
|
||||||
plr->iFirstUseFlag[0] |= (1ULL << (flag->iFlagCode - 1));
|
plr->iFirstUseFlag[0] |= (1ULL << (flag->iFlagCode - 1));
|
||||||
else
|
else
|
||||||
@@ -667,16 +668,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) {
|
CNSocket *PlayerManager::getSockFromAny(int by, int id, int uid, std::string firstname, std::string lastname) {
|
||||||
switch (by) {
|
switch ((eCN_GM_TargetSearchBy)by) {
|
||||||
case eCN_GM_TargetSearchBy__PC_ID:
|
case eCN_GM_TargetSearchBy::PC_ID:
|
||||||
assert(id != 0);
|
assert(id != 0);
|
||||||
return getSockFromID(id);
|
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);
|
assert(uid != 0);
|
||||||
for (auto& pair : players)
|
for (auto& pair : players)
|
||||||
if (pair.second->accountId == uid)
|
if (pair.second->accountId == uid)
|
||||||
return pair.first;
|
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?
|
assert(firstname != "" && lastname != ""); // XXX: remove this if we start messing around with edited names?
|
||||||
return getSockFromName(firstname, lastname);
|
return getSockFromName(firstname, lastname);
|
||||||
}
|
}
|
||||||
|
@@ -1,16 +1,15 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Player.hpp"
|
|
||||||
#include "core/Core.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 <map>
|
||||||
#include <list>
|
#include <list>
|
||||||
|
|
||||||
struct WarpLocation;
|
|
||||||
|
|
||||||
namespace PlayerManager {
|
namespace PlayerManager {
|
||||||
extern std::map<CNSocket*, Player*> players;
|
extern std::map<CNSocket*, Player*> players;
|
||||||
void init();
|
void init();
|
||||||
@@ -34,6 +33,7 @@ namespace PlayerManager {
|
|||||||
CNSocket *getSockFromAny(int by, int id, int uid, std::string firstname, std::string lastname);
|
CNSocket *getSockFromAny(int by, int id, int uid, std::string firstname, std::string lastname);
|
||||||
WarpLocation *getRespawnPoint(Player *plr);
|
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);
|
void sendToViewable(CNSocket *sock, void* buf, uint32_t type, size_t size);
|
||||||
|
|
||||||
// TODO: unify this under the new Entity system
|
// 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++) {
|
for (auto it = plr->viewableChunks.begin(); it != plr->viewableChunks.end(); it++) {
|
||||||
Chunk* chunk = *it;
|
Chunk* chunk = *it;
|
||||||
for (const EntityRef& ref : chunk->entities) {
|
for (const EntityRef& ref : chunk->entities) {
|
||||||
if (ref.type != EntityType::PLAYER || ref.sock == sock)
|
if (ref.kind != EntityKind::PLAYER || ref.sock == sock)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
ref.sock->sendPacket(pkt, type);
|
ref.sock->sendPacket(pkt, type);
|
||||||
|
@@ -1,4 +1,7 @@
|
|||||||
#include "PlayerMovement.hpp"
|
#include "PlayerMovement.hpp"
|
||||||
|
|
||||||
|
#include "servers/CNShardServer.hpp"
|
||||||
|
|
||||||
#include "PlayerManager.hpp"
|
#include "PlayerManager.hpp"
|
||||||
#include "TableData.hpp"
|
#include "TableData.hpp"
|
||||||
#include "core/Core.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
|
// [gruntwork] check if player has a follower and move it
|
||||||
if (TableData::RunningNPCPaths.find(plr->iID) != TableData::RunningNPCPaths.end()) {
|
if (TableData::RunningNPCPaths.find(plr->iID) != TableData::RunningNPCPaths.end()) {
|
||||||
BaseNPC* follower = TableData::RunningNPCPaths[plr->iID].first;
|
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;
|
std::queue<Vec3> queue;
|
||||||
Vec3 from = { follower->x, follower->y, follower->z };
|
Vec3 from = { follower->x, follower->y, follower->z };
|
||||||
float drag = 0.95f; // this ensures that they don't bump into the player
|
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()
|
// 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::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 "Racing.hpp"
|
||||||
|
|
||||||
|
#include "db/Database.hpp"
|
||||||
|
#include "servers/CNShardServer.hpp"
|
||||||
|
|
||||||
|
#include "NPCManager.hpp"
|
||||||
#include "PlayerManager.hpp"
|
#include "PlayerManager.hpp"
|
||||||
#include "Missions.hpp"
|
#include "Missions.hpp"
|
||||||
#include "Items.hpp"
|
#include "Items.hpp"
|
||||||
#include "db/Database.hpp"
|
|
||||||
#include "NPCManager.hpp"
|
|
||||||
|
|
||||||
using namespace Racing;
|
using namespace Racing;
|
||||||
|
|
||||||
@@ -64,7 +66,7 @@ static void racingCancel(CNSocket* sock, CNPacketData* data) {
|
|||||||
INITSTRUCT(sP_FE2CL_REP_EP_RACE_CANCEL_SUCC, resp);
|
INITSTRUCT(sP_FE2CL_REP_EP_RACE_CANCEL_SUCC, resp);
|
||||||
sock->sendPacket(resp, P_FE2CL_REP_EP_RACE_CANCEL_SUCC);
|
sock->sendPacket(resp, P_FE2CL_REP_EP_RACE_CANCEL_SUCC);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This request packet is used for both cancelling the race via the
|
* This request packet is used for both cancelling the race via the
|
||||||
* NPC at the start, *and* failing the race by running out of time.
|
* NPC at the start, *and* failing the race by running out of time.
|
||||||
* If the latter is to happen, the client disables movement until it
|
* If the latter is to happen, the client disables movement until it
|
||||||
@@ -97,31 +99,37 @@ static void racingEnd(CNSocket* sock, CNPacketData* data) {
|
|||||||
if (EPData.find(mapNum) == EPData.end() || EPData[mapNum].EPID == 0)
|
if (EPData.find(mapNum) == EPData.end() || EPData[mapNum].EPID == 0)
|
||||||
return; // IZ not found
|
return; // IZ not found
|
||||||
|
|
||||||
uint64_t now = getTime() / 1000;
|
EPInfo& epInfo = EPData[mapNum];
|
||||||
|
EPRace& epRace = EPRaces[sock];
|
||||||
|
|
||||||
int timeDiff = now - EPRaces[sock].startTime;
|
uint64_t now = getTime() / 1000;
|
||||||
int score = 500 * EPRaces[sock].collectedRings.size() - 10 * timeDiff;
|
int timeDiff = now - epRace.startTime;
|
||||||
if (score < 0) score = 0; // lol
|
int podsCollected = epRace.collectedRings.size();
|
||||||
int fm = score * plr->level * (1.0f / 36) * 0.3f;
|
|
||||||
|
int score = std::min(epInfo.maxScore, (int)std::exp(
|
||||||
|
(epInfo.podFactor * podsCollected) / epInfo.maxPods
|
||||||
|
- (epInfo.timeFactor * timeDiff) / epInfo.maxTime
|
||||||
|
+ epInfo.scaleFactor));
|
||||||
|
int fm = (1.0 + std::exp(epInfo.scaleFactor - 1.0) * epInfo.podFactor * podsCollected) / epInfo.maxPods;
|
||||||
|
|
||||||
// we submit the ranking first...
|
// we submit the ranking first...
|
||||||
Database::RaceRanking postRanking = {};
|
Database::RaceRanking postRanking = {};
|
||||||
postRanking.EPID = EPData[mapNum].EPID;
|
postRanking.EPID = epInfo.EPID;
|
||||||
postRanking.PlayerID = plr->iID;
|
postRanking.PlayerID = plr->iID;
|
||||||
postRanking.RingCount = EPRaces[sock].collectedRings.size();
|
postRanking.RingCount = podsCollected;
|
||||||
postRanking.Score = score;
|
postRanking.Score = score;
|
||||||
postRanking.Time = timeDiff;
|
postRanking.Time = timeDiff;
|
||||||
postRanking.Timestamp = getTimestamp();
|
postRanking.Timestamp = getTimestamp();
|
||||||
Database::postRaceRanking(postRanking);
|
Database::postRaceRanking(postRanking);
|
||||||
|
|
||||||
// ...then we get the top ranking, which may or may not be what we just submitted
|
// ...then we get the top ranking, which may or may not be what we just submitted
|
||||||
Database::RaceRanking topRankingPlayer = Database::getTopRaceRanking(EPData[mapNum].EPID, plr->iID);
|
Database::RaceRanking topRankingPlayer = Database::getTopRaceRanking(epInfo.EPID, plr->iID);
|
||||||
|
|
||||||
INITSTRUCT(sP_FE2CL_REP_EP_RACE_END_SUCC, resp);
|
INITSTRUCT(sP_FE2CL_REP_EP_RACE_END_SUCC, resp);
|
||||||
|
|
||||||
// get rank scores and rewards
|
// get rank scores and rewards
|
||||||
std::vector<int>* rankScores = &EPRewards[EPData[mapNum].EPID].first;
|
std::vector<int>* rankScores = &EPRewards[epInfo.EPID].first;
|
||||||
std::vector<int>* rankRewards = &EPRewards[EPData[mapNum].EPID].second;
|
std::vector<int>* rankRewards = &EPRewards[epInfo.EPID].second;
|
||||||
|
|
||||||
// top ranking
|
// top ranking
|
||||||
int topRank = 0;
|
int topRank = 0;
|
||||||
|
@@ -1,11 +1,17 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/Core.hpp"
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
|
||||||
#include "servers/CNShardServer.hpp"
|
|
||||||
|
|
||||||
struct EPInfo {
|
struct EPInfo {
|
||||||
int zoneX, zoneY, EPID, maxScore, maxTime;
|
// available through XDT (maxScore may be updated by drops)
|
||||||
|
int zoneX, zoneY, EPID, maxScore;
|
||||||
|
// available through drops
|
||||||
|
int maxTime, maxPods;
|
||||||
|
double scaleFactor, podFactor, timeFactor;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct EPRace {
|
struct EPRace {
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace Rand {
|
namespace Rand {
|
||||||
extern std::unique_ptr<std::mt19937> generator;
|
extern std::unique_ptr<std::mt19937> generator;
|
||||||
|
@@ -1,18 +1,14 @@
|
|||||||
#include "TableData.hpp"
|
#include "TableData.hpp"
|
||||||
|
|
||||||
#include "NPCManager.hpp"
|
#include "NPCManager.hpp"
|
||||||
#include "Transport.hpp"
|
|
||||||
#include "Items.hpp"
|
|
||||||
#include "settings.hpp"
|
|
||||||
#include "Missions.hpp"
|
#include "Missions.hpp"
|
||||||
#include "Chunking.hpp"
|
#include "Items.hpp"
|
||||||
#include "Nanos.hpp"
|
|
||||||
#include "Racing.hpp"
|
|
||||||
#include "Vendors.hpp"
|
#include "Vendors.hpp"
|
||||||
|
#include "Racing.hpp"
|
||||||
|
#include "Nanos.hpp"
|
||||||
#include "Abilities.hpp"
|
#include "Abilities.hpp"
|
||||||
#include "Eggs.hpp"
|
#include "Eggs.hpp"
|
||||||
|
|
||||||
#include "JSON.hpp"
|
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
@@ -231,18 +227,34 @@ static void loadXDT(json& xdtData) {
|
|||||||
// load nano powers
|
// load nano powers
|
||||||
json skills = xdtData["m_pSkillTable"]["m_pSkillData"];
|
json skills = xdtData["m_pSkillTable"]["m_pSkillData"];
|
||||||
|
|
||||||
for (json::iterator _skills = skills.begin(); _skills != skills.end(); _skills++) {
|
for (json::iterator _skill = skills.begin(); _skill != skills.end(); _skill++) {
|
||||||
auto skills = _skills.value();
|
auto skill = _skill.value();
|
||||||
SkillData skillData = { skills["m_iSkillType"], skills["m_iTargetType"], skills["m_iBatteryDrainType"], skills["m_iEffectArea"] };
|
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++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
skillData.batteryUse[i] = skills["m_iBatteryDrainUse"][i];
|
skillData.batteryUse[i] = skill["m_iBatteryDrainUse"][i];
|
||||||
skillData.durationTime[i] = skills["m_iDurationTime"][i];
|
skillData.durationTime[i] = skill["m_iDurationTime"][i];
|
||||||
skillData.powerIntensity[i] = skills["m_iValueA"][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
|
// load EP data
|
||||||
json instances = xdtData["m_pInstanceTable"]["m_pInstanceData"];
|
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
|
if (passedDistance >= SLIDER_GAP_SIZE) { // space them out uniformaly
|
||||||
passedDistance -= SLIDER_GAP_SIZE; // step down
|
passedDistance -= SLIDER_GAP_SIZE; // step down
|
||||||
// spawn a slider
|
// spawn a slider
|
||||||
Bus* slider = new Bus(point.x, point.y, point.z, 0, INSTANCE_OVERWORLD, 1, (*nextId)--);
|
Bus* slider = new Bus(0, INSTANCE_OVERWORLD, 1, (*nextId)--);
|
||||||
NPCManager::NPCs[slider->appearanceData.iNPC_ID] = slider;
|
NPCManager::NPCs[slider->id] = slider;
|
||||||
NPCManager::updateNPCPosition(slider->appearanceData.iNPC_ID, slider->x, slider->y, slider->z, INSTANCE_OVERWORLD, 0);
|
NPCManager::updateNPCPosition(slider->id, point.x, point.y, point.z, INSTANCE_OVERWORLD, 0);
|
||||||
Transport::NPCQueues[slider->appearanceData.iNPC_ID] = route;
|
Transport::NPCQueues[slider->id] = route;
|
||||||
}
|
}
|
||||||
// rotate
|
// rotate
|
||||||
route.pop();
|
route.pop();
|
||||||
@@ -363,7 +375,7 @@ static void loadPaths(json& pathData, int32_t* nextId) {
|
|||||||
Transport::NPCPaths.push_back(pathTemplate);
|
Transport::NPCPaths.push_back(pathTemplate);
|
||||||
}
|
}
|
||||||
std::cout << "[INFO] Loaded " << Transport::NPCPaths.size() << " NPC paths" << std::endl;
|
std::cout << "[INFO] Loaded " << Transport::NPCPaths.size() << " NPC paths" << std::endl;
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (const std::exception& err) {
|
catch (const std::exception& err) {
|
||||||
std::cerr << "[FATAL] Malformed paths.json file! Reason:" << err.what() << std::endl;
|
std::cerr << "[FATAL] Malformed paths.json file! Reason:" << err.what() << std::endl;
|
||||||
@@ -572,8 +584,17 @@ static void loadDrops(json& dropData) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// time limit isn't stored in the XDT, so we include it in the reward table instead
|
EPInfo& epInfo = Racing::EPData[EPMap];
|
||||||
Racing::EPData[EPMap].maxTime = race["TimeLimit"];
|
|
||||||
|
// max score is specified in the XDT, but can be updated if specified in the drops JSON
|
||||||
|
epInfo.maxScore = (int)race["ScoreCap"];
|
||||||
|
// time limit and total pods are not stored in the XDT, so we include it in the drops JSON
|
||||||
|
epInfo.maxTime = (int)race["TimeLimit"];
|
||||||
|
epInfo.maxPods = (int)race["TotalPods"];
|
||||||
|
// IZ-specific calculated constants included in the drops JSON
|
||||||
|
epInfo.scaleFactor = (double)race["ScaleFactor"];
|
||||||
|
epInfo.podFactor = (double)race["PodFactor"];
|
||||||
|
epInfo.timeFactor = (double)race["TimeFactor"];
|
||||||
|
|
||||||
// score cutoffs
|
// score cutoffs
|
||||||
std::vector<int> rankScores;
|
std::vector<int> rankScores;
|
||||||
@@ -659,7 +680,7 @@ static void loadEggs(json& eggData, int32_t* nextId) {
|
|||||||
int id = (*nextId)--;
|
int id = (*nextId)--;
|
||||||
uint64_t instanceID = egg.find("iMapNum") == egg.end() ? INSTANCE_OVERWORLD : (int)egg["iMapNum"];
|
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::NPCs[id] = addEgg;
|
||||||
eggCount++;
|
eggCount++;
|
||||||
NPCManager::updateNPCPosition(id, (int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, 0);
|
NPCManager::updateNPCPosition(id, (int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, 0);
|
||||||
@@ -674,7 +695,7 @@ static void loadEggs(json& eggData, int32_t* nextId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Load gruntwork output, if it exists
|
* Load gruntwork output, if it exists
|
||||||
*/
|
*/
|
||||||
static void loadGruntworkPre(json& gruntwork, int32_t* nextId) {
|
static void loadGruntworkPre(json& gruntwork, int32_t* nextId) {
|
||||||
@@ -755,7 +776,7 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
|
|||||||
if (NPCManager::NPCs.find(npcID) == NPCManager::NPCs.end())
|
if (NPCManager::NPCs.find(npcID) == NPCManager::NPCs.end())
|
||||||
continue; // NPC not found
|
continue; // NPC not found
|
||||||
BaseNPC* npc = NPCManager::NPCs[npcID];
|
BaseNPC* npc = NPCManager::NPCs[npcID];
|
||||||
npc->appearanceData.iAngle = angle;
|
npc->angle = angle;
|
||||||
|
|
||||||
RunningNPCRotations[npcID] = angle;
|
RunningNPCRotations[npcID] = angle;
|
||||||
}
|
}
|
||||||
@@ -768,8 +789,8 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
|
|||||||
if (NPCManager::NPCs.find(npcID) == NPCManager::NPCs.end())
|
if (NPCManager::NPCs.find(npcID) == NPCManager::NPCs.end())
|
||||||
continue; // NPC not found
|
continue; // NPC not found
|
||||||
BaseNPC* npc = NPCManager::NPCs[npcID];
|
BaseNPC* npc = NPCManager::NPCs[npcID];
|
||||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->x, npc->y,
|
NPCManager::updateNPCPosition(npc->id, npc->x, npc->y,
|
||||||
npc->z, instanceID, npc->appearanceData.iAngle);
|
npc->z, instanceID, npc->angle);
|
||||||
|
|
||||||
RunningNPCMapNumbers[npcID] = instanceID;
|
RunningNPCMapNumbers[npcID] = instanceID;
|
||||||
}
|
}
|
||||||
@@ -791,12 +812,12 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
|
|||||||
// re-enable respawning
|
// re-enable respawning
|
||||||
((Mob*)npc)->summoned = false;
|
((Mob*)npc)->summoned = false;
|
||||||
} else {
|
} 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;
|
NPCManager::NPCs[npc->id] = npc;
|
||||||
RunningMobs[npc->appearanceData.iNPC_ID] = npc;
|
RunningMobs[npc->id] = npc;
|
||||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, mob["iX"], mob["iY"], mob["iZ"], instanceID, mob["iAngle"]);
|
NPCManager::updateNPCPosition(npc->id, mob["iX"], mob["iY"], mob["iZ"], instanceID, mob["iAngle"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// mob groups
|
// mob groups
|
||||||
@@ -840,7 +861,7 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
|
|||||||
|
|
||||||
tmpFol->offsetX = follower.find("iOffsetX") == follower.end() ? 0 : (int)follower["iOffsetX"];
|
tmpFol->offsetX = follower.find("iOffsetX") == follower.end() ? 0 : (int)follower["iOffsetX"];
|
||||||
tmpFol->offsetY = follower.find("iOffsetY") == follower.end() ? 0 : (int)follower["iOffsetY"];
|
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;
|
tmp->groupMember[followerCount++] = *nextId;
|
||||||
|
|
||||||
(*nextId)--;
|
(*nextId)--;
|
||||||
@@ -850,7 +871,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";
|
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"];
|
auto eggs = gruntwork["eggs"];
|
||||||
@@ -859,7 +880,7 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
|
|||||||
int id = (*nextId)--;
|
int id = (*nextId)--;
|
||||||
uint64_t instanceID = egg.find("iMapNum") == egg.end() ? INSTANCE_OVERWORLD : (int)egg["iMapNum"];
|
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::NPCs[id] = addEgg;
|
||||||
NPCManager::updateNPCPosition(id, (int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, 0);
|
NPCManager::updateNPCPosition(id, (int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, 0);
|
||||||
RunningEggs[id] = addEgg;
|
RunningEggs[id] = addEgg;
|
||||||
@@ -893,7 +914,7 @@ static void loadNPCs(json& npcData) {
|
|||||||
if (npc["iX"] > 512000 && npc["iY"] < 256000)
|
if (npc["iX"] > 512000 && npc["iY"] < 256000)
|
||||||
continue;
|
continue;
|
||||||
#endif
|
#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::NPCs[npcID] = tmp;
|
||||||
NPCManager::updateNPCPosition(npcID, npc["iX"], npc["iY"], npc["iZ"], instanceID, npc["iAngle"]);
|
NPCManager::updateNPCPosition(npcID, npc["iX"], npc["iY"], npc["iZ"], instanceID, npc["iAngle"]);
|
||||||
@@ -1003,7 +1024,7 @@ static void loadMobs(json& npcData, int32_t* nextId) {
|
|||||||
|
|
||||||
tmpFol->offsetX = follower.find("iOffsetX") == follower.end() ? 0 : (int)follower["iOffsetX"];
|
tmpFol->offsetX = follower.find("iOffsetX") == follower.end() ? 0 : (int)follower["iOffsetX"];
|
||||||
tmpFol->offsetY = follower.find("iOffsetY") == follower.end() ? 0 : (int)follower["iOffsetY"];
|
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;
|
tmp->groupMember[followerCount++] = *nextId;
|
||||||
|
|
||||||
(*nextId)--;
|
(*nextId)--;
|
||||||
@@ -1226,7 +1247,7 @@ void TableData::flush() {
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
int x, y, z;
|
int x, y, z;
|
||||||
if (npc->type == EntityType::MOB) {
|
if (npc->kind == EntityKind::MOB) {
|
||||||
Mob *m = (Mob*)npc;
|
Mob *m = (Mob*)npc;
|
||||||
x = m->spawnX;
|
x = m->spawnX;
|
||||||
y = m->spawnY;
|
y = m->spawnY;
|
||||||
@@ -1238,13 +1259,13 @@ void TableData::flush() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: this format deviates slightly from the one in mobs.json
|
// 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["iX"] = x;
|
||||||
mob["iY"] = y;
|
mob["iY"] = y;
|
||||||
mob["iZ"] = z;
|
mob["iZ"] = z;
|
||||||
mob["iMapNum"] = MAPNUM(npc->instanceID);
|
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
|
// 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
|
// it's called mobs, but really it's everything
|
||||||
gruntwork["mobs"].push_back(mob);
|
gruntwork["mobs"].push_back(mob);
|
||||||
@@ -1259,19 +1280,19 @@ void TableData::flush() {
|
|||||||
|
|
||||||
int x, y, z;
|
int x, y, z;
|
||||||
std::vector<Mob*> followers;
|
std::vector<Mob*> followers;
|
||||||
if (npc->type == EntityType::MOB) {
|
if (npc->kind == EntityKind::MOB) {
|
||||||
Mob* m = (Mob*)npc;
|
Mob* m = (Mob*)npc;
|
||||||
x = m->spawnX;
|
x = m->spawnX;
|
||||||
y = m->spawnY;
|
y = m->spawnY;
|
||||||
z = m->spawnZ;
|
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";
|
std::cout << "[WARN] Non-leader mob found in running groups; ignoring\n";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// add follower data to vector; go until OOB or until follower ID is 0
|
// 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++) {
|
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";
|
std::cout << "[WARN] Follower with ID " << m->groupMember[i] << " not found; skipping\n";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -1285,13 +1306,13 @@ void TableData::flush() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: this format deviates slightly from the one in mobs.json
|
// 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["iX"] = x;
|
||||||
mob["iY"] = y;
|
mob["iY"] = y;
|
||||||
mob["iZ"] = z;
|
mob["iZ"] = z;
|
||||||
mob["iMapNum"] = MAPNUM(npc->instanceID);
|
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
|
// 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
|
// followers
|
||||||
while (followers.size() > 0) {
|
while (followers.size() > 0) {
|
||||||
@@ -1300,7 +1321,7 @@ void TableData::flush() {
|
|||||||
|
|
||||||
// populate JSON entry
|
// populate JSON entry
|
||||||
json fol;
|
json fol;
|
||||||
fol["iNPCType"] = follower->appearanceData.iNPCType;
|
fol["iNPCType"] = follower->type;
|
||||||
fol["iOffsetX"] = follower->offsetX;
|
fol["iOffsetX"] = follower->offsetX;
|
||||||
fol["iOffsetY"] = follower->offsetY;
|
fol["iOffsetY"] = follower->offsetY;
|
||||||
|
|
||||||
@@ -1325,7 +1346,7 @@ void TableData::flush() {
|
|||||||
int mapnum = MAPNUM(npc->instanceID);
|
int mapnum = MAPNUM(npc->instanceID);
|
||||||
if (mapnum != 0)
|
if (mapnum != 0)
|
||||||
egg["iMapNum"] = mapnum;
|
egg["iMapNum"] = mapnum;
|
||||||
egg["iType"] = npc->appearanceData.iNPCType;
|
egg["iType"] = npc->type;
|
||||||
|
|
||||||
gruntwork["eggs"].push_back(egg);
|
gruntwork["eggs"].push_back(egg);
|
||||||
}
|
}
|
||||||
@@ -1349,7 +1370,7 @@ void TableData::flush() {
|
|||||||
targetIDs.push_back(tID);
|
targetIDs.push_back(tID);
|
||||||
for (int32_t tType : path.targetTypes)
|
for (int32_t tType : path.targetTypes)
|
||||||
targetTypes.push_back(tType);
|
targetTypes.push_back(tType);
|
||||||
|
|
||||||
pathObj["iBaseSpeed"] = path.speed;
|
pathObj["iBaseSpeed"] = path.speed;
|
||||||
pathObj["iTaskID"] = path.escortTaskID;
|
pathObj["iTaskID"] = path.escortTaskID;
|
||||||
pathObj["bRelative"] = path.isRelative;
|
pathObj["bRelative"] = path.isRelative;
|
||||||
|
@@ -1,8 +1,13 @@
|
|||||||
#pragma once
|
#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
|
// these are added to the NPC's static key to avoid collisions
|
||||||
const int NPC_ID_OFFSET = 1;
|
const int NPC_ID_OFFSET = 1;
|
||||||
|
@@ -1,4 +1,7 @@
|
|||||||
#include "Trading.hpp"
|
#include "Trading.hpp"
|
||||||
|
|
||||||
|
#include "servers/CNShardServer.hpp"
|
||||||
|
|
||||||
#include "PlayerManager.hpp"
|
#include "PlayerManager.hpp"
|
||||||
#include "db/Database.hpp"
|
#include "db/Database.hpp"
|
||||||
|
|
||||||
|
@@ -1,7 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Items.hpp"
|
|
||||||
|
|
||||||
namespace Trading {
|
namespace Trading {
|
||||||
void init();
|
void init();
|
||||||
}
|
}
|
@@ -1,9 +1,12 @@
|
|||||||
|
#include "Transport.hpp"
|
||||||
|
|
||||||
#include "servers/CNShardServer.hpp"
|
#include "servers/CNShardServer.hpp"
|
||||||
|
|
||||||
#include "PlayerManager.hpp"
|
#include "PlayerManager.hpp"
|
||||||
#include "Nanos.hpp"
|
#include "Nanos.hpp"
|
||||||
#include "Transport.hpp"
|
|
||||||
#include "TableData.hpp"
|
#include "TableData.hpp"
|
||||||
#include "Combat.hpp"
|
#include "Entities.hpp"
|
||||||
|
#include "NPCManager.hpp"
|
||||||
#include "MobAI.hpp"
|
#include "MobAI.hpp"
|
||||||
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
@@ -257,13 +260,13 @@ static void stepNPCPathing() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// skip if not simulating mobs
|
// skip if not simulating mobs
|
||||||
if (npc->type == EntityType::MOB && !MobAI::simulateMobs) {
|
if (npc->kind == EntityKind::MOB && !MobAI::simulateMobs) {
|
||||||
it++;
|
it++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// do not roam if not roaming
|
// 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++;
|
it++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -276,15 +279,15 @@ static void stepNPCPathing() {
|
|||||||
int distanceBetween = hypot(dXY, point.z - npc->z); // total distance
|
int distanceBetween = hypot(dXY, point.z - npc->z); // total distance
|
||||||
|
|
||||||
// update NPC location to update viewables
|
// 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
|
// TODO: move walking logic into Entity stack
|
||||||
switch (npc->type) {
|
switch (npc->kind) {
|
||||||
case EntityType::BUS:
|
case EntityKind::BUS:
|
||||||
INITSTRUCT(sP_FE2CL_TRANSPORTATION_MOVE, busMove);
|
INITSTRUCT(sP_FE2CL_TRANSPORTATION_MOVE, busMove);
|
||||||
|
|
||||||
busMove.eTT = 3;
|
busMove.eTT = 3;
|
||||||
busMove.iT_ID = npc->appearanceData.iNPC_ID;
|
busMove.iT_ID = npc->id;
|
||||||
busMove.iMoveStyle = 0; // ???
|
busMove.iMoveStyle = 0; // ???
|
||||||
busMove.iToX = point.x;
|
busMove.iToX = point.x;
|
||||||
busMove.iToY = point.y;
|
busMove.iToY = point.y;
|
||||||
@@ -293,12 +296,12 @@ static void stepNPCPathing() {
|
|||||||
|
|
||||||
NPCManager::sendToViewable(npc, &busMove, P_FE2CL_TRANSPORTATION_MOVE, sizeof(sP_FE2CL_TRANSPORTATION_MOVE));
|
NPCManager::sendToViewable(npc, &busMove, P_FE2CL_TRANSPORTATION_MOVE, sizeof(sP_FE2CL_TRANSPORTATION_MOVE));
|
||||||
break;
|
break;
|
||||||
case EntityType::MOB:
|
case EntityKind::MOB:
|
||||||
MobAI::incNextMovement((Mob*)npc);
|
MobAI::incNextMovement((Mob*)npc);
|
||||||
/* fallthrough */
|
/* fallthrough */
|
||||||
default:
|
default:
|
||||||
INITSTRUCT(sP_FE2CL_NPC_MOVE, move);
|
INITSTRUCT(sP_FE2CL_NPC_MOVE, move);
|
||||||
move.iNPC_ID = npc->appearanceData.iNPC_ID;
|
move.iNPC_ID = npc->id;
|
||||||
move.iMoveStyle = 0; // ???
|
move.iMoveStyle = 0; // ???
|
||||||
move.iToX = point.x;
|
move.iToX = point.x;
|
||||||
move.iToY = point.y;
|
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) {
|
void Transport::constructPathNPC(int32_t id, NPCPath* path) {
|
||||||
BaseNPC* npc = NPCManager::NPCs[id];
|
BaseNPC* npc = NPCManager::NPCs[id];
|
||||||
if (npc->type == EntityType::MOB)
|
if (npc->kind == EntityKind::MOB)
|
||||||
((Mob*)(npc))->staticPath = true;
|
((Mob*)(npc))->staticPath = true;
|
||||||
npc->loopingPath = path->isLoop;
|
npc->loopingPath = path->isLoop;
|
||||||
|
|
||||||
|
@@ -1,8 +1,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "servers/CNShardServer.hpp"
|
#include "core/Core.hpp"
|
||||||
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
const int SLIDER_SPEED = 1200;
|
const int SLIDER_SPEED = 1200;
|
||||||
const int SLIDER_STOP_TICKS = 16;
|
const int SLIDER_STOP_TICKS = 16;
|
||||||
|
@@ -1,4 +1,9 @@
|
|||||||
#include "Vendors.hpp"
|
#include "Vendors.hpp"
|
||||||
|
|
||||||
|
#include "servers/CNShardServer.hpp"
|
||||||
|
|
||||||
|
#include "PlayerManager.hpp"
|
||||||
|
#include "Items.hpp"
|
||||||
#include "Rand.hpp"
|
#include "Rand.hpp"
|
||||||
|
|
||||||
// 7 days
|
// 7 days
|
||||||
|
@@ -1,10 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
#include "servers/CNShardServer.hpp"
|
|
||||||
|
|
||||||
#include "Items.hpp"
|
#include <vector>
|
||||||
#include "PlayerManager.hpp"
|
#include <map>
|
||||||
|
|
||||||
struct VendorListing {
|
struct VendorListing {
|
||||||
int sort, type, id;
|
int sort, type, id;
|
||||||
|
@@ -93,7 +93,7 @@ inline constexpr bool isOutboundPacketID(uint32_t id) {
|
|||||||
|
|
||||||
// overflow-safe validation of variable-length packets
|
// overflow-safe validation of variable-length packets
|
||||||
// for outbound 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
|
// check for multiplication overflow
|
||||||
if (npayloads > 0 && (CN_PACKET_BUFFER_SIZE - 8) / (size_t)npayloads < plsize)
|
if (npayloads > 0 && (CN_PACKET_BUFFER_SIZE - 8) / (size_t)npayloads < plsize)
|
||||||
return false;
|
return false;
|
||||||
@@ -110,7 +110,7 @@ inline constexpr bool validOutVarPacket(size_t base, int32_t npayloads, size_t p
|
|||||||
}
|
}
|
||||||
|
|
||||||
// for inbound packets
|
// 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
|
// check for multiplication overflow
|
||||||
if (npayloads > 0 && (CN_PACKET_BUFFER_SIZE - 8) / (size_t)npayloads < plsize)
|
if (npayloads > 0 && (CN_PACKET_BUFFER_SIZE - 8) / (size_t)npayloads < plsize)
|
||||||
return false;
|
return false;
|
||||||
|
@@ -23,6 +23,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <locale>
|
#include <locale>
|
||||||
#include <codecvt>
|
#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.
|
// 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; \
|
#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;
|
const float CN_EP_RANK_5 = 0.29f;
|
||||||
|
|
||||||
// methods of finding players for GM commands
|
// methods of finding players for GM commands
|
||||||
enum eCN_GM_TargetSearchBy {
|
enum class eCN_GM_TargetSearchBy {
|
||||||
eCN_GM_TargetSearchBy__PC_ID, // player id
|
PC_ID, // player id
|
||||||
eCN_GM_TargetSearchBy__PC_Name, // firstname, lastname
|
PC_Name, // firstname, lastname
|
||||||
eCN_GM_TargetSearchBy__PC_UID // account id
|
PC_UID // account id
|
||||||
};
|
};
|
||||||
|
|
||||||
enum eCN_GM_TeleportType {
|
enum class eCN_GM_TeleportType {
|
||||||
eCN_GM_TeleportMapType__XYZ,
|
XYZ,
|
||||||
eCN_GM_TeleportMapType__MapXYZ,
|
MapXYZ,
|
||||||
eCN_GM_TeleportMapType__MyLocation,
|
MyLocation,
|
||||||
eCN_GM_TeleportMapType__SomeoneLocation,
|
SomeoneLocation,
|
||||||
eCN_GM_TeleportMapType__Unstick
|
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 {
|
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_NONE = 0,
|
||||||
ECSB_UP_MOVE_SPEED = 1,
|
ECSB_UP_MOVE_SPEED = 1,
|
||||||
ECSB_UP_SWIM_SPEED = 2,
|
ECSB_UP_SWIM_SPEED = 2,
|
||||||
@@ -96,6 +77,27 @@ enum {
|
|||||||
ECSTB__END = 26,
|
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 {
|
enum {
|
||||||
SUCC = 1,
|
SUCC = 1,
|
||||||
FAIL = 0,
|
FAIL = 0,
|
||||||
|
@@ -47,8 +47,8 @@ struct PacketDesc {
|
|||||||
* really should.
|
* really should.
|
||||||
*/
|
*/
|
||||||
struct sGM_PVPTarget {
|
struct sGM_PVPTarget {
|
||||||
uint32_t eCT;
|
|
||||||
uint32_t iID;
|
uint32_t iID;
|
||||||
|
uint32_t eCT;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sSkillResult_Leech {
|
struct sSkillResult_Leech {
|
||||||
|
@@ -64,7 +64,7 @@ void terminate(int arg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
static BOOL winTerminate(DWORD arg) {
|
static BOOL WINAPI winTerminate(DWORD arg) {
|
||||||
terminate(0);
|
terminate(0);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
@@ -1,13 +1,15 @@
|
|||||||
#include "servers/CNLoginServer.hpp"
|
#include "servers/CNLoginServer.hpp"
|
||||||
|
|
||||||
#include "core/CNShared.hpp"
|
#include "core/CNShared.hpp"
|
||||||
#include "db/Database.hpp"
|
#include "db/Database.hpp"
|
||||||
#include "PlayerManager.hpp"
|
|
||||||
#include "Items.hpp"
|
|
||||||
#include <regex>
|
|
||||||
#include "bcrypt/BCrypt.hpp"
|
#include "bcrypt/BCrypt.hpp"
|
||||||
|
|
||||||
|
#include "PlayerManager.hpp"
|
||||||
|
#include "Items.hpp"
|
||||||
#include "settings.hpp"
|
#include "settings.hpp"
|
||||||
|
|
||||||
|
#include <regex>
|
||||||
|
|
||||||
std::map<CNSocket*, CNLoginData> CNLoginServer::loginSessions;
|
std::map<CNSocket*, CNLoginData> CNLoginServer::loginSessions;
|
||||||
|
|
||||||
CNLoginServer::CNLoginServer(uint16_t p) {
|
CNLoginServer::CNLoginServer(uint16_t p) {
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
|
|
||||||
#include "Player.hpp"
|
#include "Player.hpp"
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
@@ -1,10 +1,12 @@
|
|||||||
|
#include "servers/CNShardServer.hpp"
|
||||||
|
|
||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
|
#include "core/CNShared.hpp"
|
||||||
#include "db/Database.hpp"
|
#include "db/Database.hpp"
|
||||||
#include "servers/Monitor.hpp"
|
#include "servers/Monitor.hpp"
|
||||||
#include "servers/CNShardServer.hpp"
|
|
||||||
#include "PlayerManager.hpp"
|
#include "PlayerManager.hpp"
|
||||||
#include "MobAI.hpp"
|
#include "MobAI.hpp"
|
||||||
#include "core/CNShared.hpp"
|
|
||||||
#include "settings.hpp"
|
#include "settings.hpp"
|
||||||
#include "TableData.hpp" // for flush()
|
#include "TableData.hpp" // for flush()
|
||||||
|
|
||||||
|
@@ -3,9 +3,12 @@
|
|||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <list>
|
||||||
|
|
||||||
#define REGISTER_SHARD_PACKET(pactype, handlr) CNShardServer::ShardPackets[pactype] = handlr;
|
#define REGISTER_SHARD_PACKET(pactype, handlr) CNShardServer::ShardPackets[pactype] = handlr;
|
||||||
#define REGISTER_SHARD_TIMER(handlr, delta) CNShardServer::Timers.push_back(TimerEvent(handlr, delta));
|
#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 {
|
class CNShardServer : public CNServer {
|
||||||
private:
|
private:
|
||||||
|
@@ -1,8 +1,10 @@
|
|||||||
|
#include "servers/Monitor.hpp"
|
||||||
|
|
||||||
#include "servers/CNShardServer.hpp"
|
#include "servers/CNShardServer.hpp"
|
||||||
|
|
||||||
#include "PlayerManager.hpp"
|
#include "PlayerManager.hpp"
|
||||||
#include "Chat.hpp"
|
#include "Chat.hpp"
|
||||||
#include "Email.hpp"
|
#include "Email.hpp"
|
||||||
#include "servers/Monitor.hpp"
|
|
||||||
#include "settings.hpp"
|
#include "settings.hpp"
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
@@ -2,9 +2,6 @@
|
|||||||
|
|
||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
|
|
||||||
#include <list>
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
namespace Monitor {
|
namespace Monitor {
|
||||||
SOCKET init();
|
SOCKET init();
|
||||||
bool acceptConnection(SOCKET, uint16_t);
|
bool acceptConnection(SOCKET, uint16_t);
|
||||||
|
@@ -1,9 +1,10 @@
|
|||||||
#include <iostream>
|
|
||||||
#include "settings.hpp"
|
#include "settings.hpp"
|
||||||
|
|
||||||
|
#include "core/CNStructs.hpp" // so we get the ACADEMY definition
|
||||||
#include "INIReader.hpp"
|
#include "INIReader.hpp"
|
||||||
|
|
||||||
// so we get the ACADEMY definition
|
#include <iostream>
|
||||||
#include "core/CNStructs.hpp"
|
|
||||||
|
|
||||||
// defaults :)
|
// defaults :)
|
||||||
int settings::VERBOSITY = 1;
|
int settings::VERBOSITY = 1;
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace settings {
|
namespace settings {
|
||||||
extern int VERBOSITY;
|
extern int VERBOSITY;
|
||||||
extern bool SANDBOX;
|
extern bool SANDBOX;
|
||||||
|
2
tdata
2
tdata
Submodule tdata updated: cc65dbb402...8c98c83682
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.
|
SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
#ifndef INCLUDE_NLOHMANN_JSON_HPP_
|
#ifndef INCLUDE_NLOHMANN_JSON_HPP_
|
||||||
#define INCLUDE_NLOHMANN_JSON_HPP_
|
#define INCLUDE_NLOHMANN_JSON_HPP_
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user