mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2025-11-29 12:00:06 +00:00
Compare commits
56 Commits
lua-more-b
...
bbaaa53df2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbaaa53df2 | ||
|
|
273b73379e | ||
|
|
1709d2b0ef | ||
|
|
457c320d48 | ||
|
|
29024e9351 | ||
|
|
0994aaff71 | ||
|
|
cb94018e46 | ||
|
|
e0a0cc186e | ||
|
|
d4253c3b49 | ||
|
|
5b58055924 | ||
|
|
7a7cdb1330 | ||
|
|
0e054e21b6 | ||
|
|
ad9bf2a9e3 | ||
|
|
a38bf0e7be | ||
|
|
3d22103113 | ||
|
|
761581afd7 | ||
|
|
daec7a33b7 | ||
|
|
462ced6685 | ||
|
|
cc190efc63 | ||
|
|
df1b4ff160 | ||
|
|
9f7c8a18df | ||
|
|
ea61ed1aaa | ||
|
|
9982d9bdda | ||
|
|
398e9fddf8 | ||
|
|
fd664c4f19 | ||
|
|
feaaf4cab9 | ||
|
|
d3e6bae5d9 | ||
|
|
0f042c4233 | ||
|
|
3dc7124f58 | ||
|
|
71e78afa0b | ||
|
|
0a2b3fbdad | ||
|
|
911dbaab83 | ||
|
|
7183180be5 | ||
|
|
3f1f78942e | ||
|
|
a9dea36882 | ||
|
|
de23779209 | ||
|
|
748a82e223 | ||
|
|
231e88fd55 | ||
|
|
abfb562489 | ||
|
|
589c5a8732 | ||
|
|
0b8b92b7f6 | ||
|
|
f0cf6326e5 | ||
|
|
82bc94c01c | ||
|
|
5d9dcb8609 | ||
|
|
166e148878 | ||
|
|
14b02ec5d2 | ||
| d50c312227 | |||
| 18ed96493f | |||
| 71a7d3d164 | |||
| 520efd6dd5 | |||
|
|
0a8ef2ebbd | ||
|
|
3f4aca8a5d | ||
|
|
b0de75d80e | ||
|
|
8f88edaad1 | ||
|
|
6896d35427 | ||
|
|
d74a9747d9 |
@@ -1 +0,0 @@
|
||||
version.h
|
||||
7
.github/workflows/check-builds.yaml
vendored
7
.github/workflows/check-builds.yaml
vendored
@@ -9,13 +9,12 @@ on:
|
||||
- CMakeLists.txt
|
||||
- Makefile
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize, ready_for_review]
|
||||
types: ready_for_review
|
||||
paths:
|
||||
- src/**
|
||||
- vendor/**
|
||||
- CMakeLists.txt
|
||||
- Makefile
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
ubuntu-build:
|
||||
@@ -54,7 +53,7 @@ jobs:
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: 'ubuntu22_04-bin-x64-${{ env.SHORT_SHA }}'
|
||||
name: 'ubuntu20_04-bin-x64-${{ env.SHORT_SHA }}'
|
||||
path: bin
|
||||
|
||||
windows-build:
|
||||
@@ -113,7 +112,7 @@ jobs:
|
||||
|
||||
copy-artifacts:
|
||||
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/master'
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
needs: [windows-build, ubuntu-build]
|
||||
env:
|
||||
BOT_SSH_KEY: ${{ secrets.BOT_SSH_KEY }}
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,6 +1,3 @@
|
||||
[submodule "tdata"]
|
||||
path = tdata
|
||||
url = https://github.com/OpenFusionProject/tabledata.git
|
||||
[submodule "vendor/Lua"]
|
||||
path = vendor/Lua
|
||||
url = https://github.com/walterschell/Lua.git
|
||||
|
||||
26
.vscode/launch.json
vendored
26
.vscode/launch.json
vendored
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug (Linux)",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/bin/fusion",
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"name": "Debug (Windows)",
|
||||
"type": "cppvsdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/bin/Debug/winfusion.exe",
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"name": "Release (Windows)",
|
||||
"type": "cppvsdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/bin/Release/winfusion.exe",
|
||||
"cwd": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -34,28 +34,20 @@ else()
|
||||
set(BIN_NAME fusion)
|
||||
endif()
|
||||
|
||||
# add lua
|
||||
option(LUA_BUILD_COMPILER OFF)
|
||||
option(LUA_ENABLE_SHARED OFF)
|
||||
option(LUA_ENABLE_TESTING OFF)
|
||||
add_subdirectory(vendor/Lua)
|
||||
|
||||
include_directories(src vendor)
|
||||
|
||||
file(GLOB_RECURSE VENDOR_SOURCES vendor/bcrypt/**.[ch] vendor/mingw/**.h vendor/INIReader.hpp vendor/JSON.hpp)
|
||||
|
||||
file(GLOB_RECURSE SOURCES src/**.[ch]pp version.h)
|
||||
file(GLOB_RECURSE SOURCES src/**.[ch]pp vendor/**.[ch]pp vendor/**.[ch] version.h)
|
||||
|
||||
configure_file(version.h.in ${CMAKE_SOURCE_DIR}/version.h @ONLY)
|
||||
|
||||
add_executable(openfusion ${SOURCES} ${VENDOR_SOURCES})
|
||||
add_executable(openfusion ${SOURCES})
|
||||
|
||||
set_target_properties(openfusion PROPERTIES OUTPUT_NAME ${BIN_NAME})
|
||||
|
||||
# find sqlite3 and use it
|
||||
find_package(SQLite3 REQUIRED)
|
||||
find_package(sqlite3 REQUIRED)
|
||||
target_include_directories(openfusion PRIVATE ${SQLite3_INCLUDE_DIRS})
|
||||
target_link_libraries(openfusion PRIVATE lua_static ${SQLite3_LIBRARIES})
|
||||
target_link_libraries(openfusion PRIVATE ${SQLite3_LIBRARIES})
|
||||
|
||||
# Makes it so config, tdata, etc. get picked up when starting via the debugger in VS
|
||||
set_property(TARGET openfusion PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
|
||||
@@ -65,5 +57,5 @@ set_property(TARGET openfusion PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_S
|
||||
# It's not something you should do, but it's there if you need it...
|
||||
if (NOT CMAKE_GENERATOR MATCHES "Visual Studio" AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND NOT CMAKE_GENERATOR MATCHES "MinGW Makefiles")
|
||||
find_package(Threads REQUIRED)
|
||||
target_link_libraries(openfusion PRIVATE pthread)
|
||||
target_link_libraries(openfusion pthread)
|
||||
endif()
|
||||
|
||||
@@ -26,7 +26,7 @@ Both are pretty short reads and following them will get you up to speed with bra
|
||||
|
||||
I will now cover a few examples of the complications people have encountered contributing to this project, how to understand them, overcome them and henceforth avoid them.
|
||||
|
||||
### Dirty pull requests
|
||||
## Dirty pull requests
|
||||
|
||||
Many Pull Requests OpenFusion receives fail to present a clean set of commits to merge.
|
||||
These are generally either:
|
||||
@@ -44,7 +44,7 @@ If you read the above links, you'll note that this isn't exactly a perfect solut
|
||||
The obvious issue, then, is that the people submitting dirty PRs are the exact people who don't *know* how to rebase their fork to continue submitting their work cleanly.
|
||||
So they end up creating countless merge commits when pulling upstream on top of their own incompatible histories, and then submitting those merge commits in their PRs and the cycle continues.
|
||||
|
||||
### The details
|
||||
## The details
|
||||
|
||||
A git commit is uniquely identified by its SHA1 hash.
|
||||
Its hash is generated from its contents, as well as the hash(es) of its parent commit(s).
|
||||
@@ -52,7 +52,7 @@ Its hash is generated from its contents, as well as the hash(es) of its parent c
|
||||
That means that even if two commits are exactly the same in terms of content, they're not the same commit if their parent commits differ (ex. if they've been rebased).
|
||||
So if you keep issuing `git pull`s after upstream has merged a rebased version of your PR, you will never re-synchronize with it, and will instead construct an alternate history polluted by pointless merge commits.
|
||||
|
||||
### The solution
|
||||
## The solution
|
||||
|
||||
If you already have a messed-up fork and you have no changes on it that you're afraid to lose, the solution is simple:
|
||||
|
||||
@@ -67,7 +67,7 @@ If you do have some committed changes that haven't yet been merged upstream, you
|
||||
If you do end up messing something up, don't worry, it most likely isn't really lost and `git reflog` is your friend.
|
||||
(You can checkout an arbitrary commit, and make it into its own branch with `git checkout -b BRANCH` or set a pre-exisitng branch to it with `git reset --hard COMMIT`)
|
||||
|
||||
### Avoiding the problem
|
||||
## Avoiding the problem
|
||||
|
||||
When working on a changeset you want to submit back upstream, don't do it on the main branch.
|
||||
Create a work branch just for your changeset with `git checkout -b work`.
|
||||
@@ -81,34 +81,3 @@ That way you can always keep master in sync with upstream with `git pull --ff-on
|
||||
Creating new branches for the rebase isn't strictly necessary since you can always return a branch to its previous state with `git reflog`.
|
||||
|
||||
For moving uncommited changes around between branches, `git stash` is a real blessing.
|
||||
|
||||
## Code guidelines
|
||||
|
||||
Alright, you're up to speed on Git and ready to go. Here are a few specific code guidelines to try and follow:
|
||||
|
||||
### Match the styling
|
||||
|
||||
Pretty straightforward, make sure your code looks similar to the code around it. Match whitespacing, bracket styling, variable naming conventions, etc.
|
||||
|
||||
### Prefer short-circuiting
|
||||
|
||||
To minimize branching complexity (as this makes the code hard to read), we prefer to keep the number of `if-else` statements as low as possible. One easy way to achieve this is by doing an early return after branching into an `if` block and then writing the code for the other path outside the block entirely. You can find examples of this in practically every source file. Note that in a few select situations, this might actually make your code less elegant, which is why this isn't a strict rule. Lean towards short-circuiting and use your better judgement.
|
||||
|
||||
### Follow the include convention
|
||||
|
||||
This one matters a lot as it can cause cyclic dependencies and other code-breaking issues.
|
||||
|
||||
FOR HEADER FILES (.hpp):
|
||||
- everything you use IN THE HEADER must be EXPLICITLY INCLUDED with the exception of things that fall under Core.hpp
|
||||
- you may NOT include ANYTHING ELSE
|
||||
|
||||
FOR SOURCE FILES (.cpp):
|
||||
- you can #include whatever you want as long as the partner header is included first
|
||||
- anything that gets included by another include is fair game
|
||||
- redundant includes are ok because they'll be harmless AS LONG AS our header files stay lean.
|
||||
|
||||
The point of this is NOT to optimize the number of includes used all around or make things more efficient necessarily. it's to improve readability & coherence and make it easier to avoid cyclical issues.
|
||||
|
||||
## When in doubt, ask
|
||||
|
||||
If you still have questions that were not answered here, feel free to ping a dev in the Discord server or on GitHub.
|
||||
|
||||
21
Dockerfile
21
Dockerfile
@@ -1,21 +0,0 @@
|
||||
FROM debian:latest
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
RUN apt-get -y update && apt-get install -y \
|
||||
git \
|
||||
clang \
|
||||
make \
|
||||
libsqlite3-dev
|
||||
|
||||
COPY . ./
|
||||
|
||||
RUN make -j8
|
||||
|
||||
# tabledata should be copied from the host;
|
||||
# clone it there before building the container
|
||||
#RUN git submodule update --init --recursive
|
||||
|
||||
CMD ["./bin/fusion"]
|
||||
|
||||
LABEL Name=openfusion Version=0.0.1
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-2023 OpenFusion Contributors
|
||||
Copyright (c) 2020-2022 OpenFusion Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
45
README.md
45
README.md
@@ -13,22 +13,21 @@ OpenFusion is a reverse-engineered server for FusionFall. It primarily targets v
|
||||
|
||||
### Getting Started
|
||||
#### Method A: Installer (Easiest)
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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
|
||||
1. Download the client from [here](https://github.com/OpenFusionProject/OpenFusion/releases/download/1.5/OpenFusionClient-1.5.zip).
|
||||
1. Download the client from [here](https://github.com/OpenFusionProject/OpenFusion/releases/download/1.4/OpenFusionClient-1.4.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.
|
||||
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.
|
||||
5. 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 4.
|
||||
|
||||
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
|
||||
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.
|
||||
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.
|
||||
@@ -36,7 +35,7 @@ Instructions for getting the client to run on Linux through Wine can be found [h
|
||||
3. Lastly Game Version - select `beta-20100104` if you downloaded the original zip, or `beta-20111013` if you downloaded the academy zip.
|
||||
5. Once you've added the server to the list, connect to it and log in. If you're having trouble with this, refer to steps 4 and 5 from the previous section.
|
||||
|
||||
If you want to run the latest development builds of the server, [compiled binaries (artifacts) for each functional commit can be found here.](http://cdn.dexlabs.systems/of-builds/)
|
||||
If you want, [compiled binaries (artifacts) for each functional commit can be found here.](http://cdn.dexlabs.systems/of-builds/)
|
||||
|
||||
For a more detailed overview of the game's architecture and how to configure it, read the following sections.
|
||||
|
||||
@@ -53,7 +52,10 @@ 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 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.
|
||||
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.
|
||||
|
||||
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.
|
||||
This will potentially become relevant later, as people start experimenting and mixing and matching versions.
|
||||
@@ -62,7 +64,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").
|
||||
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 instead loads the web pages locally using the `file://` schema, and fetches the game's assets from a standard web server.
|
||||
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!).
|
||||
|
||||
`/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.
|
||||
|
||||
@@ -79,7 +81,7 @@ This just works if you're all under the same LAN, but if you want to play over t
|
||||
|
||||
## Compiling
|
||||
|
||||
OpenFusion has one external dependency: SQLite. The oldest compatible version is `3.33.0`. You can install it on Windows using `vcpkg`, and on Unix/Linux using your distribution's package manager. For a more indepth guide on how to set up vcpkg, [read this guide on the wiki](https://github.com/OpenFusionProject/OpenFusion/wiki/Installing-SQLite-on-Windows-using-vcpkg).
|
||||
OpenFusion has one external dependency: SQLite. You can install it on Windows using `vcpkg`, and on Unix/Linux using your distribution's package manager. For a more indepth guide on how to set up vcpkg, [read this guide on the wiki](https://github.com/OpenFusionProject/OpenFusion/wiki/Installing-SQLite-on-Windows-using-vcpkg).
|
||||
|
||||
You have two choices for compiling OpenFusion: the included Makefile and the included CMakeLists file.
|
||||
|
||||
@@ -98,13 +100,26 @@ If you'd like to contribute to this project, please read [CONTRIBUTING.md](CONTR
|
||||
## Gameplay
|
||||
|
||||
The goal of the project is to faithfully recreate the game as it was at the time of the targeted build.
|
||||
While most features are implemented and the game is playable start to finish, there may be missing functionality or bugs present.
|
||||
The server is not yet complete, however, and some functionality is still missing.
|
||||
|
||||
Depending on the server configuration, you'll have access to certain commands.
|
||||
Because the server is still in development, ordinary players are allowed access to a few admin 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).
|
||||

|
||||
|
||||
When hosting a local server, you will have access to all commands by default (account level 1).
|
||||
### Movement commands
|
||||
* 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.).
|
||||
|
||||
For a list of available commands, see [this wiki page](https://github.com/OpenFusionProject/OpenFusion/wiki/Ingame-Command-list).
|
||||
### Item commands
|
||||
* `/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).
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
version: '3.4'
|
||||
|
||||
services:
|
||||
openfusion:
|
||||
image: openfusion
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./Dockerfile
|
||||
ports:
|
||||
- "23000:23000"
|
||||
- "23001:23001"
|
||||
- "8003:8003"
|
||||
@@ -1,12 +1,11 @@
|
||||
#include "Abilities.hpp"
|
||||
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include "NPCManager.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Buffs.hpp"
|
||||
#include "Nanos.hpp"
|
||||
#include "MobAI.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
using namespace Abilities;
|
||||
|
||||
@@ -48,107 +47,16 @@ static SkillResult handleSkillHealHP(SkillData* skill, int power, ICombatant* so
|
||||
}
|
||||
|
||||
static SkillResult handleSkillDamageNDebuff(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
||||
// take aggro
|
||||
target->takeDamage(source->getRef(), 0);
|
||||
|
||||
int duration = 0;
|
||||
int strength = 0;
|
||||
bool blocked = target->hasBuff(ECSB_FREEDOM);
|
||||
if(!blocked) {
|
||||
duration = skill->durationTime[power];
|
||||
strength = skill->values[0][power];
|
||||
BuffStack debuff = {
|
||||
(duration * 100) / MS_PER_COMBAT_TICK, // ticks
|
||||
strength, // value
|
||||
source->getRef(), // source
|
||||
BuffClass::NANO, // buff class
|
||||
};
|
||||
int timeBuffId = Abilities::getCSTBFromST(skill->skillType);
|
||||
target->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) {
|
||||
Buffs::timeBuffTick(self, buff);
|
||||
},
|
||||
&debuff);
|
||||
}
|
||||
|
||||
// TODO abilities
|
||||
sSkillResult_Damage_N_Debuff result{};
|
||||
|
||||
result.iDamage = duration / 10; // we use the duration as the damage number (why?)
|
||||
result.iHP = target->getCurrentHP();
|
||||
result.eCT = target->getCharType();
|
||||
result.iID = target->getID();
|
||||
result.bProtected = blocked;
|
||||
result.bProtected = false;
|
||||
result.iConditionBitFlag = target->getCompositeCondition();
|
||||
|
||||
// for player targets, make sure to update Nano stamina
|
||||
if (target->getCharType() == 1) {
|
||||
Player *plr = dynamic_cast<Player*>(target);
|
||||
result.iStamina = plr->getActiveNano()->iStamina;
|
||||
}
|
||||
|
||||
return SkillResult(sizeof(sSkillResult_Damage_N_Debuff), &result);
|
||||
}
|
||||
|
||||
static SkillResult handleSkillLeech(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
||||
EntityRef sourceRef = source->getRef();
|
||||
int heal = skill->values[0][power];
|
||||
int healed = source->heal(sourceRef, heal);
|
||||
int damage = heal * 2;
|
||||
int dealt = target->takeDamage(sourceRef, damage);
|
||||
|
||||
sSkillResult_Leech result{};
|
||||
|
||||
result.Damage.eCT = target->getCharType();
|
||||
result.Damage.iID = target->getID();
|
||||
result.Damage.bProtected = dealt <= 0;
|
||||
result.Damage.iDamage = dealt;
|
||||
result.Damage.iHP = target->getCurrentHP();
|
||||
|
||||
result.Heal.eCT = result.Damage.eCT;
|
||||
result.Heal.iID = result.Damage.iID;
|
||||
result.Heal.iHealHP = healed;
|
||||
result.Heal.iHP = source->getCurrentHP();
|
||||
|
||||
return SkillResult(sizeof(sSkillResult_Leech), &result);
|
||||
}
|
||||
|
||||
static SkillResult handleSkillBuff(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
||||
int duration = skill->durationTime[power];
|
||||
int strength = skill->values[0][power];
|
||||
BuffStack passiveBuff = {
|
||||
// if the duration is 0, it needs to be recast every tick
|
||||
duration == 0 ? 1 : (duration * 100) / MS_PER_COMBAT_TICK, // ticks
|
||||
strength, // value
|
||||
source->getRef(), // source
|
||||
source == target ? BuffClass::NANO : BuffClass::GROUP_NANO, // buff class
|
||||
};
|
||||
|
||||
int timeBuffId = Abilities::getCSTBFromST(skill->skillType);
|
||||
SkillDrainType drainType = skill->drainType;
|
||||
int combatLifetime = 0;
|
||||
if(!target->addBuff(timeBuffId,
|
||||
[drainType](EntityRef self, Buff* buff, int status, BuffStack* stack) {
|
||||
if(buff->id == ECSB_BOUNDINGBALL && status == ETBU_ADD) {
|
||||
// drain
|
||||
ICombatant* combatant = dynamic_cast<ICombatant*>(self.getEntity());
|
||||
combatant->takeDamage(buff->getLastSource(), 0); // aggro
|
||||
}
|
||||
Buffs::timeBuffUpdate(self, buff, status, stack);
|
||||
if(drainType == SkillDrainType::ACTIVE && status == ETBU_DEL)
|
||||
Buffs::timeBuffTimeout(self);
|
||||
},
|
||||
[combatLifetime](EntityRef self, Buff* buff, time_t currTime) mutable {
|
||||
if(buff->id == ECSB_BOUNDINGBALL &&
|
||||
combatLifetime % COMBAT_TICKS_PER_DRAIN_PROC == 0)
|
||||
Buffs::tickDrain(self, buff, COMBAT_TICKS_PER_DRAIN_PROC); // drain
|
||||
combatLifetime++;
|
||||
},
|
||||
&passiveBuff)) return SkillResult();
|
||||
|
||||
sSkillResult_Buff result{};
|
||||
result.eCT = target->getCharType();
|
||||
result.iID = target->getID();
|
||||
@@ -163,24 +71,19 @@ static SkillResult handleSkillBatteryDrain(SkillData* skill, int power, ICombata
|
||||
Player* plr = dynamic_cast<Player*>(target);
|
||||
|
||||
const double scalingFactor = (18 + source->getLevel()) / 36.0;
|
||||
const bool blocked = target->hasBuff(ECSB_PROTECT_BATTERY);
|
||||
|
||||
int boostDrain = 0;
|
||||
int potionDrain = 0;
|
||||
if(!blocked) {
|
||||
boostDrain = (int)(skill->values[0][power] * scalingFactor);
|
||||
if(boostDrain > plr->batteryW) boostDrain = plr->batteryW;
|
||||
plr->batteryW -= boostDrain;
|
||||
int boostDrain = (int)(skill->values[0][power] * scalingFactor);
|
||||
if(boostDrain > plr->batteryW) boostDrain = plr->batteryW;
|
||||
plr->batteryW -= boostDrain;
|
||||
|
||||
potionDrain = (int)(skill->values[1][power] * scalingFactor);
|
||||
if(potionDrain > plr->batteryN) potionDrain = plr->batteryN;
|
||||
plr->batteryN -= potionDrain;
|
||||
}
|
||||
int potionDrain = (int)(skill->values[1][power] * scalingFactor);
|
||||
if(potionDrain > plr->batteryN) potionDrain = plr->batteryN;
|
||||
plr->batteryN -= potionDrain;
|
||||
|
||||
sSkillResult_BatteryDrain result{};
|
||||
result.eCT = target->getCharType();
|
||||
result.iID = target->getID();
|
||||
result.bProtected = blocked;
|
||||
result.bProtected = target->hasBuff(ECSB_PROTECT_BATTERY);
|
||||
result.iDrainW = boostDrain;
|
||||
result.iBatteryW = plr->batteryW;
|
||||
result.iDrainN = potionDrain;
|
||||
@@ -194,13 +97,8 @@ static SkillResult handleSkillBatteryDrain(SkillData* skill, int power, ICombata
|
||||
static SkillResult handleSkillMove(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
||||
if(source->getCharType() != 1)
|
||||
return SkillResult(); // only Players are valid sources for recall
|
||||
|
||||
Player* plr = dynamic_cast<Player*>(source);
|
||||
if(source == target) {
|
||||
// no trailing struct for self
|
||||
PlayerManager::sendPlayerTo(target->getRef().sock, plr->recallX, plr->recallY, plr->recallZ, plr->recallInstance);
|
||||
return SkillResult();
|
||||
}
|
||||
PlayerManager::sendPlayerTo(source->getRef().sock, plr->recallX, plr->recallY, plr->recallZ, plr->recallInstance);
|
||||
|
||||
sSkillResult_Move result{};
|
||||
result.eCT = target->getCharType();
|
||||
@@ -222,66 +120,56 @@ static SkillResult handleSkillResurrect(SkillData* skill, int power, ICombatant*
|
||||
#pragma endregion
|
||||
|
||||
static std::vector<SkillResult> handleSkill(SkillData* skill, int power, ICombatant* src, std::vector<ICombatant*> targets) {
|
||||
size_t resultSize = 0;
|
||||
SkillResult (*skillHandler)(SkillData*, int, ICombatant*, ICombatant*) = nullptr;
|
||||
std::vector<SkillResult> results;
|
||||
|
||||
switch(skill->skillType)
|
||||
{
|
||||
case SkillType::CORRUPTIONATTACK:
|
||||
case SkillType::CORRUPTIONATTACKLOSE:
|
||||
case SkillType::CORRUPTIONATTACKWIN:
|
||||
// skillHandler = handleSkillCorruptionReflect;
|
||||
// break;
|
||||
case SkillType::DAMAGE:
|
||||
case EST_DAMAGE:
|
||||
resultSize = sizeof(sSkillResult_Damage);
|
||||
skillHandler = handleSkillDamage;
|
||||
break;
|
||||
case SkillType::HEAL_HP:
|
||||
case SkillType::RETURNHOMEHEAL:
|
||||
case EST_HEAL_HP:
|
||||
case EST_RETURNHOMEHEAL:
|
||||
resultSize = sizeof(sSkillResult_Heal_HP);
|
||||
skillHandler = handleSkillHealHP;
|
||||
break;
|
||||
case SkillType::KNOCKDOWN:
|
||||
case SkillType::SLEEP:
|
||||
case SkillType::SNARE:
|
||||
case SkillType::STUN:
|
||||
skillHandler = handleSkillDamageNDebuff;
|
||||
break;
|
||||
case SkillType::JUMP:
|
||||
case SkillType::RUN:
|
||||
case SkillType::STEALTH:
|
||||
case SkillType::MINIMAPENEMY:
|
||||
case SkillType::MINIMAPTRESURE:
|
||||
case SkillType::PHOENIX:
|
||||
case SkillType::PROTECTBATTERY:
|
||||
case SkillType::PROTECTINFECTION:
|
||||
case SkillType::REWARDBLOB:
|
||||
case SkillType::REWARDCASH:
|
||||
// case SkillType::INFECTIONDAMAGE:
|
||||
case SkillType::FREEDOM:
|
||||
case SkillType::BOUNDINGBALL:
|
||||
case SkillType::INVULNERABLE:
|
||||
case SkillType::STAMINA_SELF:
|
||||
case SkillType::NANOSTIMPAK:
|
||||
case SkillType::BUFFHEAL:
|
||||
case EST_JUMP:
|
||||
case EST_RUN:
|
||||
case EST_FREEDOM:
|
||||
case EST_PHOENIX:
|
||||
case EST_INVULNERABLE:
|
||||
case EST_MINIMAPENEMY:
|
||||
case EST_MINIMAPTRESURE:
|
||||
case EST_NANOSTIMPAK:
|
||||
case EST_PROTECTBATTERY:
|
||||
case EST_PROTECTINFECTION:
|
||||
case EST_REWARDBLOB:
|
||||
case EST_REWARDCASH:
|
||||
case EST_STAMINA_SELF:
|
||||
case EST_STEALTH:
|
||||
resultSize = sizeof(sSkillResult_Buff);
|
||||
skillHandler = handleSkillBuff;
|
||||
break;
|
||||
case SkillType::BLOODSUCKING:
|
||||
skillHandler = handleSkillLeech;
|
||||
break;
|
||||
case SkillType::RETROROCKET_SELF:
|
||||
// no-op
|
||||
return results;
|
||||
case SkillType::PHOENIX_GROUP:
|
||||
skillHandler = handleSkillResurrect;
|
||||
break;
|
||||
case SkillType::RECALL:
|
||||
case SkillType::RECALL_GROUP:
|
||||
skillHandler = handleSkillMove;
|
||||
break;
|
||||
case SkillType::BATTERYDRAIN:
|
||||
case EST_BATTERYDRAIN:
|
||||
resultSize = sizeof(sSkillResult_BatteryDrain);
|
||||
skillHandler = handleSkillBatteryDrain;
|
||||
break;
|
||||
case EST_RECALL: // still soft lock
|
||||
case EST_RECALL_GROUP: // works for player who uses it
|
||||
resultSize = sizeof(sSkillResult_Move);
|
||||
skillHandler = handleSkillMove;
|
||||
break;
|
||||
case EST_PHOENIX_GROUP: // broken
|
||||
resultSize = sizeof(sSkillResult_Resurrect);
|
||||
skillHandler = handleSkillResurrect;
|
||||
break;
|
||||
case EST_RETROROCKET_SELF:
|
||||
// no-op
|
||||
return results;
|
||||
default:
|
||||
std::cout << "[WARN] Unhandled skill type " << (int)skill->skillType << std::endl;
|
||||
std::cout << "[WARN] Unhandled skill type " << skill->skillType << std::endl;
|
||||
return results;
|
||||
}
|
||||
|
||||
@@ -289,8 +177,8 @@ static std::vector<SkillResult> handleSkill(SkillData* skill, int power, ICombat
|
||||
assert(target != nullptr);
|
||||
SkillResult result = skillHandler(skill, power, src != nullptr ? src : target, target);
|
||||
if(result.size == 0) continue; // skill not applicable
|
||||
if(result.size > MAX_SKILLRESULT_SIZE) {
|
||||
std::cout << "[WARN] bad skill result size for " << (int)skill->skillType << " from " << (void*)handleSkillBuff << std::endl;
|
||||
if(result.size != resultSize) {
|
||||
std::cout << "[WARN] bad skill result size for " << skill->skillType << " from " << (void*)handleSkillBuff << std::endl;
|
||||
continue;
|
||||
}
|
||||
results.push_back(result);
|
||||
@@ -298,42 +186,36 @@ static std::vector<SkillResult> handleSkill(SkillData* skill, int power, ICombat
|
||||
return results;
|
||||
}
|
||||
|
||||
static void attachSkillResults(std::vector<SkillResult> results, uint8_t* pivot) {
|
||||
static void attachSkillResults(std::vector<SkillResult> results, size_t resultSize, uint8_t* pivot) {
|
||||
for(SkillResult& result : results) {
|
||||
size_t sz = result.size;
|
||||
memcpy(pivot, result.payload, sz);
|
||||
pivot += sz;
|
||||
memcpy(pivot, result.payload, resultSize);
|
||||
pivot += resultSize;
|
||||
}
|
||||
}
|
||||
|
||||
void Abilities::useNanoSkill(CNSocket* sock, SkillData* skill, sNano& nano, std::vector<ICombatant*> affected) {
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
ICombatant* combatant = dynamic_cast<ICombatant*>(plr);
|
||||
|
||||
int boost = 0;
|
||||
if (Nanos::getNanoBoost(plr))
|
||||
boost = 3;
|
||||
|
||||
if(skill->drainType == SkillDrainType::ACTIVE) {
|
||||
nano.iStamina -= skill->batteryUse[boost];
|
||||
if (nano.iStamina <= 0)
|
||||
nano.iStamina = 0;
|
||||
}
|
||||
nano.iStamina -= skill->batteryUse[boost];
|
||||
if (nano.iStamina < 0)
|
||||
nano.iStamina = 0;
|
||||
|
||||
std::vector<SkillResult> results = handleSkill(skill, boost, combatant, affected);
|
||||
if(results.empty()) return; // no effect; no need for confirmation packets
|
||||
std::vector<SkillResult> results = handleSkill(skill, boost, plr, affected);
|
||||
size_t resultSize = 0; // guaranteed to be the same for every item
|
||||
if (!results.empty()) resultSize = results.back().size;
|
||||
|
||||
// lazy validation since skill results might be different sizes
|
||||
if (!validOutVarPacket(sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC), results.size(), MAX_SKILLRESULT_SIZE)) {
|
||||
if (!validOutVarPacket(sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC), results.size(), resultSize)) {
|
||||
std::cout << "[WARN] bad sP_FE2CL_NANO_SKILL_USE_SUCC packet size\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// initialize response struct
|
||||
size_t resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC);
|
||||
for(SkillResult& sr : results)
|
||||
resplen += sr.size;
|
||||
size_t resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC) + results.size() * resultSize;
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
@@ -343,17 +225,15 @@ void Abilities::useNanoSkill(CNSocket* sock, SkillData* skill, sNano& nano, std:
|
||||
pkt->iSkillID = nano.iSkillID;
|
||||
pkt->iNanoStamina = nano.iStamina;
|
||||
pkt->bNanoDeactive = nano.iStamina <= 0;
|
||||
pkt->eST = (int32_t)skill->skillType;
|
||||
pkt->eST = skill->skillType;
|
||||
pkt->iTargetCnt = (int32_t)results.size();
|
||||
|
||||
attachSkillResults(results, (uint8_t*)(pkt + 1));
|
||||
attachSkillResults(results, resultSize, (uint8_t*)(pkt + 1));
|
||||
sock->sendPacket(pkt, P_FE2CL_NANO_SKILL_USE_SUCC, resplen);
|
||||
PlayerManager::sendToViewable(sock, pkt, P_FE2CL_NANO_SKILL_USE_SUCC, resplen);
|
||||
|
||||
if(skill->skillType == SkillType::RECALL_GROUP)
|
||||
// group recall packet is sent only to group members
|
||||
PlayerManager::sendToGroup(sock, pkt, P_FE2CL_NANO_SKILL_USE, resplen);
|
||||
else
|
||||
PlayerManager::sendToViewable(sock, pkt, P_FE2CL_NANO_SKILL_USE, resplen);
|
||||
if (nano.iStamina <= 0)
|
||||
Nanos::summonNano(sock, -1);
|
||||
}
|
||||
|
||||
void Abilities::useNPCSkill(EntityRef npc, int skillID, std::vector<ICombatant*> affected) {
|
||||
@@ -368,59 +248,32 @@ void Abilities::useNPCSkill(EntityRef npc, int skillID, std::vector<ICombatant*>
|
||||
SkillData* skill = &SkillTable[skillID];
|
||||
|
||||
std::vector<SkillResult> results = handleSkill(skill, 0, src, affected);
|
||||
if(results.empty()) return; // no effect; no need for confirmation packets
|
||||
size_t resultSize = results.back().size; // guaranteed to be the same for every item
|
||||
|
||||
// lazy validation since skill results might be different sizes
|
||||
if (!validOutVarPacket(sizeof(sP_FE2CL_NPC_SKILL_HIT), results.size(), MAX_SKILLRESULT_SIZE)) {
|
||||
if (!validOutVarPacket(sizeof(sP_FE2CL_NPC_SKILL_HIT), results.size(), resultSize)) {
|
||||
std::cout << "[WARN] bad sP_FE2CL_NPC_SKILL_HIT packet size\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// initialize response struct
|
||||
size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT);
|
||||
for(SkillResult& sr : results)
|
||||
resplen += sr.size;
|
||||
size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + results.size() * resultSize;
|
||||
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 = npc.id;
|
||||
pkt->iSkillID = skillID;
|
||||
pkt->eST = (int32_t)skill->skillType;
|
||||
pkt->eST = skill->skillType;
|
||||
pkt->iTargetCnt = (int32_t)results.size();
|
||||
if(npc.kind == EntityKind::MOB) {
|
||||
Mob* mob = dynamic_cast<Mob*>(entity);
|
||||
pkt->iValue1 = mob->hitX;
|
||||
pkt->iValue2 = mob->hitY;
|
||||
pkt->iValue3 = mob->hitZ;
|
||||
}
|
||||
|
||||
attachSkillResults(results, (uint8_t*)(pkt + 1));
|
||||
attachSkillResults(results, resultSize, (uint8_t*)(pkt + 1));
|
||||
NPCManager::sendToViewable(entity, pkt, P_FE2CL_NPC_SKILL_HIT, resplen);
|
||||
}
|
||||
|
||||
static std::vector<ICombatant*> entityRefsToCombatants(std::vector<EntityRef> refs) {
|
||||
std::vector<ICombatant*> combatants;
|
||||
for(EntityRef ref : refs) {
|
||||
if(ref.kind == EntityKind::PLAYER)
|
||||
combatants.push_back(dynamic_cast<ICombatant*>(PlayerManager::getPlayer(ref.sock)));
|
||||
else if(ref.kind == EntityKind::COMBAT_NPC || ref.kind == EntityKind::MOB)
|
||||
combatants.push_back(dynamic_cast<ICombatant*>(ref.getEntity()));
|
||||
}
|
||||
return combatants;
|
||||
}
|
||||
std::vector<ICombatant*> Abilities::matchTargets(SkillData* skill, int count, int32_t *ids) {
|
||||
|
||||
std::vector<ICombatant*> Abilities::matchTargets(ICombatant* src, SkillData* skill, int count, int32_t *ids) {
|
||||
|
||||
if(skill->targetType == SkillTargetType::GROUP)
|
||||
return entityRefsToCombatants(src->getGroupMembers());
|
||||
|
||||
// this check *has* to happen after the group check above due to cases like group recall that use both
|
||||
if(skill->effectTarget == SkillEffectTarget::SELF)
|
||||
return {src}; // client sends 0 targets for certain self-targeting skills (recall)
|
||||
|
||||
// individuals
|
||||
std::vector<ICombatant*> targets;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
int32_t id = ids[i];
|
||||
if (skill->targetType == SkillTargetType::MOBS) {
|
||||
@@ -433,8 +286,8 @@ std::vector<ICombatant*> Abilities::matchTargets(ICombatant* src, SkillData* ski
|
||||
}
|
||||
}
|
||||
std::cout << "[WARN] skill: invalid mob target (id " << id << ")\n";
|
||||
} else if(skill->targetType == SkillTargetType::PLAYERS) {
|
||||
// player
|
||||
} else if(skill->targetType == SkillTargetType::SELF || skill->targetType == SkillTargetType::GROUP) {
|
||||
// players (?)
|
||||
Player* plr = PlayerManager::getPlayerFromID(id);
|
||||
if (plr != nullptr) {
|
||||
targets.push_back(dynamic_cast<ICombatant*>(plr));
|
||||
@@ -448,69 +301,63 @@ std::vector<ICombatant*> Abilities::matchTargets(ICombatant* src, SkillData* ski
|
||||
}
|
||||
|
||||
/* ripped from client (enums emplaced) */
|
||||
int Abilities::getCSTBFromST(SkillType skillType) {
|
||||
int Abilities::getCSTBFromST(int eSkillType) {
|
||||
int result = 0;
|
||||
switch (skillType)
|
||||
switch (eSkillType)
|
||||
{
|
||||
case SkillType::RUN:
|
||||
case EST_RUN:
|
||||
result = ECSB_UP_MOVE_SPEED;
|
||||
break;
|
||||
case SkillType::JUMP:
|
||||
case EST_JUMP:
|
||||
result = ECSB_UP_JUMP_HEIGHT;
|
||||
break;
|
||||
case SkillType::STEALTH:
|
||||
case EST_STEALTH:
|
||||
result = ECSB_UP_STEALTH;
|
||||
break;
|
||||
case SkillType::PHOENIX:
|
||||
case EST_PHOENIX:
|
||||
result = ECSB_PHOENIX;
|
||||
break;
|
||||
case SkillType::PROTECTBATTERY:
|
||||
case EST_PROTECTBATTERY:
|
||||
result = ECSB_PROTECT_BATTERY;
|
||||
break;
|
||||
case SkillType::PROTECTINFECTION:
|
||||
case EST_PROTECTINFECTION:
|
||||
result = ECSB_PROTECT_INFECTION;
|
||||
break;
|
||||
case SkillType::MINIMAPENEMY:
|
||||
result = ECSB_MINIMAP_ENEMY;
|
||||
break;
|
||||
case SkillType::MINIMAPTRESURE:
|
||||
result = ECSB_MINIMAP_TRESURE;
|
||||
break;
|
||||
case SkillType::REWARDBLOB:
|
||||
result = ECSB_REWARD_BLOB;
|
||||
break;
|
||||
case SkillType::REWARDCASH:
|
||||
result = ECSB_REWARD_CASH;
|
||||
break;
|
||||
case SkillType::FREEDOM:
|
||||
result = ECSB_FREEDOM;
|
||||
break;
|
||||
case SkillType::INVULNERABLE:
|
||||
result = ECSB_INVULNERABLE;
|
||||
break;
|
||||
case SkillType::BUFFHEAL:
|
||||
result = ECSB_HEAL;
|
||||
break;
|
||||
case SkillType::NANOSTIMPAK:
|
||||
result = ECSB_STIMPAKSLOT1;
|
||||
// shift as necessary
|
||||
break;
|
||||
case SkillType::SNARE:
|
||||
case EST_SNARE:
|
||||
result = ECSB_DN_MOVE_SPEED;
|
||||
break;
|
||||
case SkillType::STUN:
|
||||
result = ECSB_STUN;
|
||||
break;
|
||||
case SkillType::SLEEP:
|
||||
case EST_SLEEP:
|
||||
result = ECSB_MEZ;
|
||||
break;
|
||||
case SkillType::INFECTIONDAMAGE:
|
||||
case EST_MINIMAPENEMY:
|
||||
result = ECSB_MINIMAP_ENEMY;
|
||||
break;
|
||||
case EST_MINIMAPTRESURE:
|
||||
result = ECSB_MINIMAP_TRESURE;
|
||||
break;
|
||||
case EST_REWARDBLOB:
|
||||
result = ECSB_REWARD_BLOB;
|
||||
break;
|
||||
case EST_REWARDCASH:
|
||||
result = ECSB_REWARD_CASH;
|
||||
break;
|
||||
case EST_INFECTIONDAMAGE:
|
||||
result = ECSB_INFECTION;
|
||||
break;
|
||||
case SkillType::BOUNDINGBALL:
|
||||
case EST_FREEDOM:
|
||||
result = ECSB_FREEDOM;
|
||||
break;
|
||||
case EST_BOUNDINGBALL:
|
||||
result = ECSB_BOUNDINGBALL;
|
||||
break;
|
||||
default:
|
||||
case EST_INVULNERABLE:
|
||||
result = ECSB_INVULNERABLE;
|
||||
break;
|
||||
case EST_BUFFHEAL:
|
||||
result = ECSB_HEAL;
|
||||
break;
|
||||
case EST_NANOSTIMPAK:
|
||||
result = ECSB_STIMPAKSLOT1;
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -7,52 +7,9 @@
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <assert.h>
|
||||
|
||||
const int COMBAT_TICKS_PER_DRAIN_PROC = 2;
|
||||
constexpr size_t MAX_SKILLRESULT_SIZE = sizeof(sSkillResult_BatteryDrain);
|
||||
|
||||
enum class SkillType {
|
||||
DAMAGE = 1,
|
||||
HEAL_HP = 2,
|
||||
KNOCKDOWN = 3, // uses DamageNDebuff
|
||||
SLEEP = 4, // uses DamageNDebuff
|
||||
SNARE = 5, // uses DamageNDebuff
|
||||
HEAL_STAMINA = 6,
|
||||
STAMINA_SELF = 7,
|
||||
STUN = 8, // uses DamageNDebuff
|
||||
WEAPONSLOW = 9,
|
||||
JUMP = 10,
|
||||
RUN = 11,
|
||||
STEALTH = 12,
|
||||
SWIM = 13,
|
||||
MINIMAPENEMY = 14,
|
||||
MINIMAPTRESURE = 15,
|
||||
PHOENIX = 16,
|
||||
PROTECTBATTERY = 17,
|
||||
PROTECTINFECTION = 18,
|
||||
REWARDBLOB = 19,
|
||||
REWARDCASH = 20,
|
||||
BATTERYDRAIN = 21,
|
||||
CORRUPTIONATTACK = 22,
|
||||
INFECTIONDAMAGE = 23,
|
||||
KNOCKBACK = 24,
|
||||
FREEDOM = 25,
|
||||
PHOENIX_GROUP = 26,
|
||||
RECALL = 27,
|
||||
RECALL_GROUP = 28,
|
||||
RETROROCKET_SELF = 29,
|
||||
BLOODSUCKING = 30,
|
||||
BOUNDINGBALL = 31,
|
||||
INVULNERABLE = 32,
|
||||
NANOSTIMPAK = 33,
|
||||
RETURNHOMEHEAL = 34,
|
||||
BUFFHEAL = 35,
|
||||
EXTRABANK = 36,
|
||||
CORRUPTIONATTACKWIN = 38,
|
||||
CORRUPTIONATTACKLOSE = 39,
|
||||
};
|
||||
|
||||
enum class SkillEffectTarget {
|
||||
POINT = 1,
|
||||
SELF = 2,
|
||||
@@ -64,7 +21,7 @@ enum class SkillEffectTarget {
|
||||
|
||||
enum class SkillTargetType {
|
||||
MOBS = 1,
|
||||
PLAYERS = 2,
|
||||
SELF = 2,
|
||||
GROUP = 3
|
||||
};
|
||||
|
||||
@@ -77,7 +34,6 @@ struct SkillResult {
|
||||
size_t size;
|
||||
uint8_t payload[MAX_SKILLRESULT_SIZE];
|
||||
SkillResult(size_t len, void* dat) {
|
||||
assert(len <= MAX_SKILLRESULT_SIZE);
|
||||
size = len;
|
||||
memcpy(payload, dat, len);
|
||||
}
|
||||
@@ -87,7 +43,7 @@ struct SkillResult {
|
||||
};
|
||||
|
||||
struct SkillData {
|
||||
SkillType skillType; // eST
|
||||
int skillType; // eST
|
||||
SkillEffectTarget effectTarget;
|
||||
int effectType; // always 1?
|
||||
SkillTargetType targetType;
|
||||
@@ -107,6 +63,6 @@ namespace Abilities {
|
||||
void useNanoSkill(CNSocket*, SkillData*, sNano&, std::vector<ICombatant*>);
|
||||
void useNPCSkill(EntityRef, int skillID, std::vector<ICombatant*>);
|
||||
|
||||
std::vector<ICombatant*> matchTargets(ICombatant*, SkillData*, int, int32_t*);
|
||||
int getCSTBFromST(SkillType skillType);
|
||||
std::vector<ICombatant*> matchTargets(SkillData*, int, int32_t*);
|
||||
int getCSTBFromST(int eSkillType);
|
||||
}
|
||||
|
||||
@@ -95,12 +95,6 @@ int Buff::getValue(BuffValueSelector selector) {
|
||||
return value;
|
||||
}
|
||||
|
||||
EntityRef Buff::getLastSource() {
|
||||
if(stacks.empty())
|
||||
return self;
|
||||
return stacks.back().source;
|
||||
}
|
||||
|
||||
bool Buff::isStale() {
|
||||
return stacks.empty();
|
||||
}
|
||||
@@ -143,56 +137,15 @@ void Buffs::timeBuffUpdate(EntityRef self, Buff* buff, int status, BuffStack* st
|
||||
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.eCT = combatant->getCharType();
|
||||
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
|
||||
|
||||
@@ -66,7 +66,6 @@ public:
|
||||
BuffClass maxClass();
|
||||
|
||||
int getValue(BuffValueSelector selector);
|
||||
EntityRef getLastSource();
|
||||
|
||||
/*
|
||||
* In general, a Buff object won't exist
|
||||
@@ -87,7 +86,5 @@ public:
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -72,41 +72,31 @@ static void setValuePlayer(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
// Handle serverside value-changes
|
||||
switch (setData->iSetValueType) {
|
||||
case CN_GM_SET_VALUE_TYPE__HP:
|
||||
response.iSetValue = plr->HP = setData->iSetValue;
|
||||
case 1:
|
||||
plr->HP = setData->iSetValue;
|
||||
break;
|
||||
case CN_GM_SET_VALUE_TYPE__WEAPON_BATTERY :
|
||||
case 2:
|
||||
plr->batteryW = setData->iSetValue;
|
||||
|
||||
// caps
|
||||
if (plr->batteryW > 9999)
|
||||
plr->batteryW = 9999;
|
||||
|
||||
response.iSetValue = plr->batteryW;
|
||||
break;
|
||||
case CN_GM_SET_VALUE_TYPE__NANO_BATTERY:
|
||||
case 3:
|
||||
plr->batteryN = setData->iSetValue;
|
||||
|
||||
// caps
|
||||
if (plr->batteryN > 9999)
|
||||
plr->batteryN = 9999;
|
||||
|
||||
response.iSetValue = plr->batteryN;
|
||||
break;
|
||||
case CN_GM_SET_VALUE_TYPE__FUSION_MATTER:
|
||||
case 4:
|
||||
Missions::updateFusionMatter(sock, setData->iSetValue - plr->fusionmatter);
|
||||
response.iSetValue = plr->fusionmatter;
|
||||
break;
|
||||
case CN_GM_SET_VALUE_TYPE__CANDY:
|
||||
response.iSetValue = plr->money = setData->iSetValue;
|
||||
break;
|
||||
case CN_GM_SET_VALUE_TYPE__SPEED:
|
||||
case CN_GM_SET_VALUE_TYPE__JUMP:
|
||||
response.iSetValue = setData->iSetValue;
|
||||
case 5:
|
||||
plr->money = setData->iSetValue;
|
||||
break;
|
||||
}
|
||||
|
||||
response.iPC_ID = setData->iPC_ID;
|
||||
response.iSetValue = setData->iSetValue;
|
||||
response.iSetValueType = setData->iSetValueType;
|
||||
|
||||
sock->sendPacket(response, P_FE2CL_GM_REP_PC_SET_VALUE);
|
||||
|
||||
144
src/Combat.cpp
144
src/Combat.cpp
@@ -21,14 +21,13 @@ 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;
|
||||
EntityRef self = PlayerManager::getSockFromID(iID);
|
||||
|
||||
if(!hasBuff(buffId)) {
|
||||
buffs[buffId] = new Buff(buffId, getRef(), onUpdate, onTick, stack);
|
||||
buffs[buffId] = new Buff(buffId, self, onUpdate, onTick, stack);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
buffs[buffId]->updateCallbacks(onUpdate, onTick);
|
||||
buffs[buffId]->addStack(stack);
|
||||
return false;
|
||||
@@ -49,10 +48,9 @@ void Player::removeBuff(int buffId) {
|
||||
}
|
||||
}
|
||||
|
||||
void Player::removeBuff(int buffId, BuffClass buffClass) {
|
||||
void Player::removeBuff(int buffId, int buffClass) {
|
||||
if(hasBuff(buffId)) {
|
||||
buffs[buffId]->clear(buffClass);
|
||||
// buff might not be stale since another buff class might remain
|
||||
buffs[buffId]->clear((BuffClass)buffClass);
|
||||
if(buffs[buffId]->isStale()) {
|
||||
delete buffs[buffId];
|
||||
buffs.erase(buffId);
|
||||
@@ -60,16 +58,6 @@ void Player::removeBuff(int buffId, BuffClass buffClass) {
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
@@ -168,78 +156,29 @@ void Player::step(time_t currTime) {
|
||||
// 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);
|
||||
bool CombatNPC::addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) { /* stubbed */
|
||||
return false;
|
||||
}
|
||||
|
||||
Buff* CombatNPC::getBuff(int buffId) {
|
||||
if(hasBuff(buffId)) {
|
||||
return buffs[buffId];
|
||||
}
|
||||
Buff* CombatNPC::getBuff(int buffId) { /* stubbed */
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void CombatNPC::removeBuff(int buffId) {
|
||||
if(hasBuff(buffId)) {
|
||||
buffs[buffId]->clear();
|
||||
delete buffs[buffId];
|
||||
buffs.erase(buffId);
|
||||
}
|
||||
void CombatNPC::removeBuff(int buffId) { /* stubbed */ }
|
||||
|
||||
void CombatNPC::removeBuff(int buffId, int buffClass) { /* stubbed */ }
|
||||
|
||||
bool CombatNPC::hasBuff(int buffId) { /* stubbed */
|
||||
return false;
|
||||
}
|
||||
|
||||
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::getCompositeCondition() { /* stubbed */
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CombatNPC::takeDamage(EntityRef src, int amt) {
|
||||
@@ -310,19 +249,19 @@ void CombatNPC::step(time_t currTime) {
|
||||
}
|
||||
|
||||
void CombatNPC::transition(AIState newState, EntityRef src) {
|
||||
state = newState;
|
||||
|
||||
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
|
||||
/* TODO: fire any triggered events
|
||||
for (NPCEvent& event : NPCManager::NPCEvents)
|
||||
if (event.triggerState == newState && event.npcType == type)
|
||||
event.handler(this);
|
||||
if (event.trigger == ON_KILLED && event.npcType == type)
|
||||
event.handler(src, this);
|
||||
*/
|
||||
}
|
||||
#pragma endregion
|
||||
|
||||
@@ -365,40 +304,11 @@ static std::pair<int,int> getDamage(int attackPower, int defensePower, bool shou
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool checkRapidFire(CNSocket *sock, int targetCount, bool allowManyTargets) {
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
time_t currTime = getTime();
|
||||
|
||||
if (currTime - plr->lastShot < plr->fireRate * 80)
|
||||
plr->suspicionRating += plr->fireRate * 100 + plr->lastShot - currTime; // gain suspicion for rapid firing
|
||||
else if (currTime - plr->lastShot < plr->fireRate * 180 && plr->suspicionRating > 0)
|
||||
plr->suspicionRating += plr->fireRate * 100 + plr->lastShot - currTime; // lose suspicion for delayed firing
|
||||
|
||||
plr->lastShot = currTime;
|
||||
|
||||
// 3+ targets should never be possible
|
||||
if (!allowManyTargets && targetCount > 3)
|
||||
plr->suspicionRating += 10001;
|
||||
|
||||
// kill the socket when the player is too suspicious
|
||||
if (plr->suspicionRating > 10000) {
|
||||
sock->kill();
|
||||
CNShardServer::_killConnection(sock);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) {
|
||||
auto pkt = (sP_CL2FE_REQ_PC_ATTACK_NPCs*)data->buf;
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
auto targets = (int32_t*)data->trailers;
|
||||
|
||||
// kick the player if firing too rapidly
|
||||
if (settings::ANTICHEAT && checkRapidFire(sock, pkt->iNPCCnt, false))
|
||||
return;
|
||||
|
||||
/*
|
||||
* IMPORTANT: This validates memory safety in addition to preventing
|
||||
* ordinary cheating. If the client sends a very large number of trailing
|
||||
@@ -837,10 +747,6 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) {
|
||||
return;
|
||||
}
|
||||
|
||||
// kick the player if firing too rapidly
|
||||
if (settings::ANTICHEAT && checkRapidFire(sock, pkt->iTargetCnt, true))
|
||||
return;
|
||||
|
||||
/*
|
||||
* initialize response struct
|
||||
* rocket style hit doesn't work properly, so we're always sending this one
|
||||
@@ -939,12 +845,9 @@ static void playerTick(CNServer *serv, time_t currTime) {
|
||||
if (Abilities::SkillTable.find(nano->iSkillID) != Abilities::SkillTable.end()) {
|
||||
// nano has skill data
|
||||
SkillData* skill = &Abilities::SkillTable[nano->iSkillID];
|
||||
if (skill->drainType == SkillDrainType::PASSIVE) {
|
||||
ICombatant* src = dynamic_cast<ICombatant*>(plr);
|
||||
int32_t targets[] = { plr->iID };
|
||||
std::vector<ICombatant*> affectedCombatants = Abilities::matchTargets(src, skill, 1, targets);
|
||||
Abilities::useNanoSkill(sock, skill, *nano, affectedCombatants);
|
||||
}
|
||||
if (skill->drainType == SkillDrainType::PASSIVE)
|
||||
Nanos::applyNanoBuff(skill, plr);
|
||||
// ^ composite condition calculation is separate from combat for responsiveness
|
||||
}
|
||||
}
|
||||
|
||||
@@ -964,7 +867,6 @@ static void playerTick(CNServer *serv, time_t currTime) {
|
||||
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
|
||||
|
||||
@@ -359,19 +359,23 @@ static void npcRotateCommand(std::string full, std::vector<std::string>& args, C
|
||||
int angle = (plr->angle + 180) % 360;
|
||||
NPCManager::updateNPCPosition(npc->id, npc->x, npc->y, npc->z, npc->instanceID, angle);
|
||||
|
||||
bool isGruntworkNpc = true;
|
||||
// if it's a gruntwork NPC, rotate in-place
|
||||
if (TableData::RunningMobs.find(npc->id) != TableData::RunningMobs.end()) {
|
||||
NPCManager::updateNPCPosition(npc->id, npc->x, npc->y, npc->z, npc->instanceID, angle);
|
||||
|
||||
// add a rotation entry to the gruntwork file, unless it's already a gruntwork NPC
|
||||
if (TableData::RunningMobs.find(npc->id) == TableData::RunningMobs.end()) {
|
||||
Chat::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) + " for gruntwork NPC "
|
||||
+ std::to_string(npc->id));
|
||||
} else {
|
||||
TableData::RunningNPCRotations[npc->id] = angle;
|
||||
isGruntworkNpc = false;
|
||||
|
||||
Chat::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) + " for NPC "
|
||||
+ std::to_string(npc->id));
|
||||
}
|
||||
|
||||
Chat::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) +
|
||||
" for " + (isGruntworkNpc ? "gruntwork " : "") + "NPC " + std::to_string(npc->id));
|
||||
|
||||
// update rotation clientside by refreshing the player's chunks (same as the /refresh command)
|
||||
PlayerManager::updatePlayerPositionForWarp(sock, plr->x, plr->y, plr->z, plr->instanceID);
|
||||
// update rotation clientside
|
||||
INITSTRUCT(sP_FE2CL_NPC_ENTER, pkt);
|
||||
pkt.NPCAppearanceData = npc->getAppearanceData();
|
||||
sock->sendPacket(pkt, P_FE2CL_NPC_ENTER);
|
||||
}
|
||||
|
||||
static void refreshCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||
@@ -674,6 +678,7 @@ static void whoisCommand(std::string full, std::vector<std::string>& args, CNSoc
|
||||
Chat::sendServerMessage(sock, "[WHOIS] ID: " + std::to_string(npc->id));
|
||||
Chat::sendServerMessage(sock, "[WHOIS] Type: " + std::to_string(npc->type));
|
||||
Chat::sendServerMessage(sock, "[WHOIS] HP: " + std::to_string(npc->hp));
|
||||
Chat::sendServerMessage(sock, "[WHOIS] CBF: " + std::to_string(npc->cbf));
|
||||
Chat::sendServerMessage(sock, "[WHOIS] EntityType: " + std::to_string((int)npc->kind));
|
||||
Chat::sendServerMessage(sock, "[WHOIS] X: " + std::to_string(npc->x));
|
||||
Chat::sendServerMessage(sock, "[WHOIS] Y: " + std::to_string(npc->y));
|
||||
|
||||
56
src/Eggs.cpp
56
src/Eggs.cpp
@@ -26,11 +26,10 @@ void Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) {
|
||||
return;
|
||||
}
|
||||
|
||||
SkillResult result = SkillResult();
|
||||
SkillData* skill = &Abilities::SkillTable[skillId];
|
||||
if(skill->drainType == SkillDrainType::PASSIVE) {
|
||||
// apply buff
|
||||
if(skill->targetType != SkillTargetType::PLAYERS) {
|
||||
if(skill->targetType != SkillTargetType::SELF) {
|
||||
std::cout << "[WARN] weird skill type for egg " << eggId << " with skill " << skillId << ", should be " << (int)skill->targetType << std::endl;
|
||||
}
|
||||
|
||||
@@ -51,57 +50,12 @@ void Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) {
|
||||
// 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 {
|
||||
int value = plr->getMaxHP() * skill->values[0][0] / 1000;
|
||||
sSkillResult_Damage resultDamage{};
|
||||
sSkillResult_Heal_HP resultHeal{};
|
||||
switch(skill->skillType)
|
||||
{
|
||||
case SkillType::DAMAGE:
|
||||
resultDamage.bProtected = false;
|
||||
resultDamage.eCT = plr->getCharType();
|
||||
resultDamage.iID = plr->getID();
|
||||
resultDamage.iDamage = plr->takeDamage(src, value);
|
||||
resultDamage.iHP = plr->getCurrentHP();
|
||||
result = SkillResult(sizeof(sSkillResult_Damage), &resultDamage);
|
||||
break;
|
||||
case SkillType::HEAL_HP:
|
||||
resultHeal.eCT = plr->getCharType();
|
||||
resultHeal.iID = plr->getID();
|
||||
resultHeal.iHealHP = plr->heal(src, value);
|
||||
resultHeal.iHP = plr->getCurrentHP();
|
||||
result = SkillResult(sizeof(sSkillResult_Heal_HP), &resultHeal);
|
||||
break;
|
||||
default:
|
||||
std::cout << "[WARN] oops, egg with active skill type " << (int)skill->skillType << " unhandled";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// initialize response struct
|
||||
size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + result.size;
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
sP_FE2CL_NPC_SKILL_HIT* pkt = (sP_FE2CL_NPC_SKILL_HIT*)respbuf;
|
||||
pkt->iNPC_ID = eggId;
|
||||
pkt->iSkillID = skillId;
|
||||
pkt->eST = (int32_t)skill->skillType;
|
||||
pkt->iTargetCnt = 1;
|
||||
|
||||
if(result.size > 0) {
|
||||
void* attached = (void*)(pkt + 1);
|
||||
memcpy(attached, result.payload, result.size);
|
||||
}
|
||||
|
||||
NPCManager::sendToViewable(src.getEntity(), pkt, P_FE2CL_NPC_SKILL_HIT, resplen);
|
||||
// use skill
|
||||
std::vector<ICombatant*> targets;
|
||||
targets.push_back(dynamic_cast<ICombatant*>(plr));
|
||||
Abilities::useNPCSkill(src, skillId, targets);
|
||||
}
|
||||
|
||||
static void eggStep(CNServer* serv, time_t currTime) {
|
||||
|
||||
@@ -93,7 +93,7 @@ static void emailReceiveItemSingle(CNSocket* sock, CNPacketData* data) {
|
||||
auto pkt = (sP_CL2FE_REQ_PC_RECV_EMAIL_ITEM*)data->buf;
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (pkt->iSlotNum < 0 || pkt->iSlotNum >= AINVEN_COUNT || pkt->iEmailItemSlot < 1 || pkt->iEmailItemSlot > 4)
|
||||
if (pkt->iSlotNum < 0 || pkt->iSlotNum >= AINVEN_COUNT || pkt->iSlotNum < 1 || pkt->iSlotNum > 4)
|
||||
return; // sanity check
|
||||
|
||||
// get email item from db and delete it
|
||||
@@ -252,26 +252,11 @@ static void emailSend(CNSocket* sock, CNPacketData* data) {
|
||||
if (attachment.ItemInven.iID == 0)
|
||||
continue;
|
||||
|
||||
sItemBase* item = &pkt->aItem[i].ItemInven;
|
||||
sItemBase* real = &plr->Inven[attachment.iSlotNum];
|
||||
|
||||
resp.aItem[i] = attachment;
|
||||
attachments.push_back(attachment.ItemInven);
|
||||
attSlots.push_back(attachment.iSlotNum);
|
||||
if (real->iOpt <= item->iOpt) // delete item (if they attached the whole stack)
|
||||
*real = { 0, 0, 0, 0 };
|
||||
else // otherwise, decrement the item
|
||||
real->iOpt -= item->iOpt;
|
||||
|
||||
// HACK: update the slot
|
||||
INITSTRUCT(sP_FE2CL_PC_ITEM_MOVE_SUCC, itemResp);
|
||||
itemResp.iFromSlotNum = attachment.iSlotNum;
|
||||
itemResp.iToSlotNum = attachment.iSlotNum;
|
||||
itemResp.FromSlotItem = *real;
|
||||
itemResp.ToSlotItem = *real;
|
||||
itemResp.eFrom = (int32_t)Items::SlotType::INVENTORY;
|
||||
itemResp.eTo = (int32_t)Items::SlotType::INVENTORY;
|
||||
sock->sendPacket(itemResp, P_FE2CL_PC_ITEM_MOVE_SUCC);
|
||||
// delete item
|
||||
plr->Inven[attachment.iSlotNum] = { 0, 0, 0, 0 };
|
||||
}
|
||||
|
||||
int cost = pkt->iCash + 50 + 20 * attachments.size(); // attached taros + postage
|
||||
@@ -291,7 +276,7 @@ static void emailSend(CNSocket* sock, CNPacketData* data) {
|
||||
0 // DeleteTime (unimplemented)
|
||||
};
|
||||
|
||||
if (!Database::sendEmail(&email, attachments, plr)) {
|
||||
if (!Database::sendEmail(&email, attachments)) {
|
||||
plr->money += cost; // give money back
|
||||
// give items back
|
||||
while (!attachments.empty()) {
|
||||
|
||||
@@ -40,7 +40,7 @@ sNPCAppearanceData BaseNPC::getAppearanceData() {
|
||||
sNPCAppearanceData data = {};
|
||||
data.iAngle = angle;
|
||||
data.iBarkerType = 0; // unused?
|
||||
data.iConditionBitFlag = 0;
|
||||
data.iConditionBitFlag = cbf;
|
||||
data.iHP = hp;
|
||||
data.iNPCType = type;
|
||||
data.iNPC_ID = id;
|
||||
@@ -50,12 +50,6 @@ sNPCAppearanceData BaseNPC::getAppearanceData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
sNPCAppearanceData CombatNPC::getAppearanceData() {
|
||||
sNPCAppearanceData data = BaseNPC::getAppearanceData();
|
||||
data.iConditionBitFlag = getCompositeCondition();
|
||||
return data;
|
||||
}
|
||||
|
||||
/*
|
||||
* Entity coming into view.
|
||||
*/
|
||||
|
||||
@@ -47,8 +47,7 @@ public:
|
||||
virtual bool addBuff(int, BuffCallback<int, BuffStack*>, BuffCallback<time_t>, BuffStack*) = 0;
|
||||
virtual Buff* getBuff(int) = 0;
|
||||
virtual void removeBuff(int) = 0;
|
||||
virtual void removeBuff(int, BuffClass) = 0;
|
||||
virtual void clearBuffs(bool) = 0;
|
||||
virtual void removeBuff(int, int) = 0;
|
||||
virtual bool hasBuff(int) = 0;
|
||||
virtual int getCompositeCondition() = 0;
|
||||
virtual int takeDamage(EntityRef, int) = 0;
|
||||
@@ -73,6 +72,7 @@ public:
|
||||
int type;
|
||||
int hp;
|
||||
int angle;
|
||||
int cbf;
|
||||
bool loopingPath = false;
|
||||
|
||||
BaseNPC(int _A, uint64_t iID, int t, int _id) {
|
||||
@@ -80,6 +80,7 @@ public:
|
||||
type = t;
|
||||
hp = 400;
|
||||
angle = _A;
|
||||
cbf = 0;
|
||||
id = _id;
|
||||
instanceID = iID;
|
||||
};
|
||||
@@ -87,7 +88,7 @@ public:
|
||||
virtual void enterIntoViewOf(CNSocket *sock) override;
|
||||
virtual void disappearFromViewOf(CNSocket *sock) override;
|
||||
|
||||
virtual sNPCAppearanceData getAppearanceData();
|
||||
sNPCAppearanceData getAppearanceData();
|
||||
};
|
||||
|
||||
struct CombatNPC : public BaseNPC, public ICombatant {
|
||||
@@ -104,13 +105,11 @@ struct CombatNPC : public BaseNPC, public ICombatant {
|
||||
std::map<AIState, void (*)(CombatNPC*, time_t)> stateHandlers;
|
||||
std::map<AIState, void (*)(CombatNPC*, EntityRef)> transitionHandlers;
|
||||
|
||||
std::unordered_map<int, Buff*> buffs = {};
|
||||
|
||||
CombatNPC(int spawnX, int spawnY, int spawnZ, int angle, uint64_t iID, int t, int id, int maxHP)
|
||||
CombatNPC(int x, int y, int z, int angle, uint64_t iID, int t, int id, int maxHP)
|
||||
: BaseNPC(angle, iID, t, id), maxHealth(maxHP) {
|
||||
this->spawnX = spawnX;
|
||||
this->spawnY = spawnY;
|
||||
this->spawnZ = spawnZ;
|
||||
spawnX = x;
|
||||
spawnY = y;
|
||||
spawnZ = z;
|
||||
|
||||
kind = EntityKind::COMBAT_NPC;
|
||||
|
||||
@@ -118,15 +117,12 @@ struct CombatNPC : public BaseNPC, public ICombatant {
|
||||
transitionHandlers[AIState::INACTIVE] = {};
|
||||
}
|
||||
|
||||
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 void removeBuff(int buffId, int buffClass) override;
|
||||
virtual bool hasBuff(int buffId) override;
|
||||
virtual int getCompositeCondition() override;
|
||||
virtual int takeDamage(EntityRef src, int amt) override;
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "EntityRef.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <assert.h>
|
||||
|
||||
struct Group {
|
||||
std::vector<EntityRef> members;
|
||||
@@ -15,10 +14,6 @@ struct Group {
|
||||
});
|
||||
return filtered;
|
||||
}
|
||||
EntityRef getLeader() {
|
||||
assert(members.size() > 0);
|
||||
return members[0];
|
||||
}
|
||||
|
||||
Group(EntityRef leader);
|
||||
};
|
||||
|
||||
@@ -482,7 +482,7 @@ static void itemUseHandler(CNSocket* sock, CNPacketData* data) {
|
||||
resp->iSlotNum = request->iSlotNum;
|
||||
resp->RemainItem = gumball;
|
||||
resp->iTargetCnt = 1;
|
||||
resp->eST = (int32_t)SkillType::NANOSTIMPAK;
|
||||
resp->eST = EST_NANOSTIMPAK;
|
||||
resp->iSkillID = 144;
|
||||
|
||||
int eCSB = ECSB_STIMPAKSLOT1 + request->iNanoSlot;
|
||||
|
||||
179
src/MobAI.cpp
179
src/MobAI.cpp
@@ -51,13 +51,13 @@ int Mob::takeDamage(EntityRef src, int amt) {
|
||||
}
|
||||
|
||||
// wake up sleeping monster
|
||||
if (hasBuff(ECSB_MEZ)) {
|
||||
removeBuff(ECSB_MEZ);
|
||||
if (cbf & CSB_BIT_MEZ) {
|
||||
cbf &= ~CSB_BIT_MEZ;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1);
|
||||
pkt1.eCT = 2;
|
||||
pkt1.iID = id;
|
||||
pkt1.iConditionBitFlag = getCompositeCondition();
|
||||
pkt1.iConditionBitFlag = cbf;
|
||||
NPCManager::sendToViewable(this, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
|
||||
}
|
||||
|
||||
@@ -96,12 +96,13 @@ static std::pair<int,int> lerp(int x1, int y1, int x2, int y2, int speed) {
|
||||
|
||||
void MobAI::clearDebuff(Mob *mob) {
|
||||
mob->skillStyle = -1;
|
||||
mob->clearBuffs(false);
|
||||
mob->cbf = 0;
|
||||
mob->unbuffTimes.clear();
|
||||
|
||||
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1);
|
||||
pkt1.eCT = 2;
|
||||
pkt1.iID = mob->id;
|
||||
pkt1.iConditionBitFlag = mob->getCompositeCondition();
|
||||
pkt1.iConditionBitFlag = mob->cbf;
|
||||
NPCManager::sendToViewable(mob, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
|
||||
}
|
||||
|
||||
@@ -227,7 +228,7 @@ bool MobAI::aggroCheck(Mob *mob, time_t currTime) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static void dealCorruption(Mob *mob, std::vector<int> targetData, int skillID, int mobStyle) {
|
||||
static void dealCorruption(Mob *mob, std::vector<int> targetData, int skillID, int style) {
|
||||
Player *plr = PlayerManager::getPlayer(mob->target);
|
||||
|
||||
size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_CORRUPTION_HIT) + targetData[0] * sizeof(sCAttackResult);
|
||||
@@ -246,7 +247,7 @@ static void dealCorruption(Mob *mob, std::vector<int> targetData, int skillID, i
|
||||
|
||||
resp->iNPC_ID = mob->id;
|
||||
resp->iSkillID = skillID;
|
||||
resp->iStyle = mobStyle;
|
||||
resp->iStyle = style;
|
||||
resp->iValue1 = plr->x;
|
||||
resp->iValue2 = plr->y;
|
||||
resp->iValue3 = plr->z;
|
||||
@@ -280,38 +281,26 @@ static void dealCorruption(Mob *mob, std::vector<int> targetData, int skillID, i
|
||||
respdata[i].iActiveNanoSlotNum = n;
|
||||
respdata[i].iNanoID = plr->activeNano;
|
||||
|
||||
int nanoStyle = Nanos::nanoStyle(plr->activeNano);
|
||||
if (nanoStyle == -1) { // no nano
|
||||
int style2 = Nanos::nanoStyle(plr->activeNano);
|
||||
if (style2 == -1) { // no nano
|
||||
respdata[i].iHitFlag = HF_BIT_STYLE_TIE;
|
||||
respdata[i].iDamage = Abilities::SkillTable[skillID].values[0][0] * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500;
|
||||
} else if (mobStyle == nanoStyle) {
|
||||
} else if (style == style2) {
|
||||
respdata[i].iHitFlag = HF_BIT_STYLE_TIE;
|
||||
respdata[i].iDamage = 0;
|
||||
respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina;
|
||||
} else if (mobStyle - nanoStyle == 1 || nanoStyle - mobStyle == 2) {
|
||||
} else if (style - style2 == 1 || style2 - style == 2) {
|
||||
respdata[i].iHitFlag = HF_BIT_STYLE_WIN;
|
||||
respdata[i].iDamage = 0;
|
||||
respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina += 45;
|
||||
if (plr->Nanos[plr->activeNano].iStamina > 150)
|
||||
respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina = 150;
|
||||
// fire damage power disguised as a corruption attack back at the enemy
|
||||
SkillData skill = {
|
||||
SkillType::DAMAGE, // skillType
|
||||
SkillEffectTarget::POINT, // effectTarget
|
||||
1, // effectType
|
||||
SkillTargetType::MOBS, // targetType
|
||||
SkillDrainType::ACTIVE, // drainType
|
||||
0, // effectArea
|
||||
{0, 0, 0, 0}, // batteryUse
|
||||
{0, 0, 0, 0}, // durationTime
|
||||
{0, 0, 0}, // valueTypes (unused)
|
||||
{
|
||||
{200, 200, 200, 200},
|
||||
{200, 200, 200, 200},
|
||||
{200, 200, 200, 200},
|
||||
}
|
||||
};
|
||||
Abilities::useNanoSkill(sock, &skill, *plr->getActiveNano(), { mob });
|
||||
// TODO ABILITIES
|
||||
/*std::vector<int> targetData2 = {1, mob->id, 0, 0, 0};
|
||||
for (auto& pwr : Abilities::Powers)
|
||||
if (pwr.skillType == EST_DAMAGE)
|
||||
pwr.handle(sock, targetData2, plr->activeNano, skillID, 0, 200);*/
|
||||
} else {
|
||||
respdata[i].iHitFlag = HF_BIT_STYLE_LOSE;
|
||||
respdata[i].iDamage = Abilities::SkillTable[skillID].values[0][0] * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500;
|
||||
@@ -338,6 +327,12 @@ static void dealCorruption(Mob *mob, std::vector<int> targetData, int skillID, i
|
||||
}
|
||||
|
||||
static void useAbilities(Mob *mob, time_t currTime) {
|
||||
/*
|
||||
* targetData approach
|
||||
* first integer is the count
|
||||
* second to fifth integers are IDs, these can be either player iID or mob's iID
|
||||
* whether the skill targets players or mobs is determined by the skill packet being fired
|
||||
*/
|
||||
Player *plr = PlayerManager::getPlayer(mob->target);
|
||||
|
||||
if (mob->skillStyle >= 0) { // corruption hit
|
||||
@@ -352,7 +347,7 @@ static void useAbilities(Mob *mob, time_t currTime) {
|
||||
|
||||
if (mob->skillStyle == -2) { // eruption hit
|
||||
int skillID = (int)mob->data["m_iMegaType"];
|
||||
std::vector<ICombatant*> targets{};
|
||||
std::vector<int> targetData = {0, 0, 0, 0, 0};
|
||||
|
||||
// find the players within range of eruption
|
||||
for (auto it = mob->viewableChunks.begin(); it != mob->viewableChunks.end(); it++) {
|
||||
@@ -362,22 +357,26 @@ static void useAbilities(Mob *mob, time_t currTime) {
|
||||
if (ref.kind != EntityKind::PLAYER)
|
||||
continue;
|
||||
|
||||
CNSocket *s = ref.sock;
|
||||
CNSocket *s= ref.sock;
|
||||
Player *plr = PlayerManager::getPlayer(s);
|
||||
|
||||
if (!plr->isAlive())
|
||||
if (plr->HP <= 0)
|
||||
continue;
|
||||
|
||||
int distance = hypot(mob->hitX - plr->x, mob->hitY - plr->y);
|
||||
if (distance < Abilities::SkillTable[skillID].effectArea) {
|
||||
targets.push_back(plr);
|
||||
if (targets.size() > 3) // make sure not to have more than 4
|
||||
targetData[0] += 1;
|
||||
targetData[targetData[0]] = plr->iID;
|
||||
if (targetData[0] > 3) // make sure not to have more than 4
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Abilities::useNPCSkill(mob->id, skillID, targets);
|
||||
// TODO ABILITIES
|
||||
/*for (auto& pwr : Abilities::Powers)
|
||||
if (pwr.skillType == Abilities::SkillTable[skillID].skillType)
|
||||
pwr.handle(mob->id, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]);*/
|
||||
mob->skillStyle = -3; // eruption cooldown
|
||||
mob->nextAttack = currTime + 1000;
|
||||
return;
|
||||
@@ -395,11 +394,14 @@ static void useAbilities(Mob *mob, time_t currTime) {
|
||||
|
||||
if (random < prob1) { // active skill hit
|
||||
int skillID = (int)mob->data["m_iActiveSkill1"];
|
||||
SkillData* skill = &Abilities::SkillTable[skillID];
|
||||
int debuffID = Abilities::getCSTBFromST(skill->skillType);
|
||||
if(plr->hasBuff(debuffID))
|
||||
return; // prevent debuffing a player twice
|
||||
Abilities::useNPCSkill(mob->getRef(), skillID, { plr });
|
||||
// TODO ABILITIES
|
||||
//std::vector<int> targetData = {1, plr->iID, 0, 0, 0};
|
||||
//for (auto& pwr : Abilities::Powers)
|
||||
// if (pwr.skillType == Abilities::SkillTable[skillID].skillType) {
|
||||
// if (pwr.bitFlag != 0 && (plr->iConditionBitFlag & pwr.bitFlag))
|
||||
// return; // prevent debuffing a player twice
|
||||
// pwr.handle(mob->id, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]);
|
||||
// }
|
||||
mob->nextAttack = currTime + (int)mob->data["m_iDelayTime"] * 100;
|
||||
return;
|
||||
}
|
||||
@@ -440,6 +442,31 @@ static void useAbilities(Mob *mob, time_t currTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
static void drainMobHP(Mob *mob, int amount) {
|
||||
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;
|
||||
sSkillResult_Damage *drain = (sSkillResult_Damage*)(respbuf + sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK));
|
||||
|
||||
pkt->iID = mob->id;
|
||||
pkt->eCT = 4; // mob
|
||||
pkt->iTB_ID = ECSB_BOUNDINGBALL;
|
||||
|
||||
drain->eCT = 4;
|
||||
drain->iID = mob->id;
|
||||
drain->iDamage = amount;
|
||||
drain->iHP = mob->hp -= amount;
|
||||
|
||||
NPCManager::sendToViewable(mob, (void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen);
|
||||
|
||||
if (mob->hp <= 0)
|
||||
mob->transition(AIState::DEAD, mob->target);
|
||||
}
|
||||
|
||||
void MobAI::incNextMovement(Mob* mob, time_t currTime) {
|
||||
if (currTime == 0)
|
||||
currTime = getTime();
|
||||
@@ -526,27 +553,38 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
// tick buffs
|
||||
auto it = npc->buffs.begin();
|
||||
while(it != npc->buffs.end()) {
|
||||
Buff* buff = (*it).second;
|
||||
buff->combatTick(currTime);
|
||||
// drain
|
||||
if (self->skillStyle < 0 && (self->lastDrainTime == 0 || currTime - self->lastDrainTime >= 1000)
|
||||
&& self->cbf & CSB_BIT_BOUNDINGBALL) {
|
||||
drainMobHP(self, self->maxHealth / 20); // lose 5% every second
|
||||
self->lastDrainTime = currTime;
|
||||
}
|
||||
|
||||
// if mob state changed, end the step
|
||||
if(self->state != AIState::COMBAT)
|
||||
return;
|
||||
// if drain killed the mob, return early
|
||||
if (self->hp <= 0)
|
||||
return;
|
||||
|
||||
buff->tick(currTime);
|
||||
if(buff->isStale()) {
|
||||
// garbage collect
|
||||
it = npc->buffs.erase(it);
|
||||
delete buff;
|
||||
// unbuffing
|
||||
std::unordered_map<int32_t, time_t>::iterator it = self->unbuffTimes.begin();
|
||||
while (it != self->unbuffTimes.end()) {
|
||||
|
||||
if (currTime >= it->second) {
|
||||
self->cbf &= ~it->first;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1);
|
||||
pkt1.eCT = 2;
|
||||
pkt1.iID = self->id;
|
||||
pkt1.iConditionBitFlag = self->cbf;
|
||||
NPCManager::sendToViewable(self, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
|
||||
|
||||
it = self->unbuffTimes.erase(it);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
else it++;
|
||||
}
|
||||
|
||||
// skip attack if stunned or asleep
|
||||
if (self->hasBuff(ECSB_STUN) || self->hasBuff(ECSB_MEZ)) {
|
||||
if (self->cbf & (CSB_BIT_STUN|CSB_BIT_MEZ)) {
|
||||
self->skillStyle = -1; // in this case we also reset the any outlying abilities the mob might be winding up.
|
||||
return;
|
||||
}
|
||||
@@ -562,7 +600,6 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) {
|
||||
}
|
||||
|
||||
int distanceToTravel = INT_MAX;
|
||||
int speed = self->speed;
|
||||
// movement logic: move when out of range but don't move while casting a skill
|
||||
if (distance > mobRange && self->skillStyle == -1) {
|
||||
if (self->nextMovement != 0 && currTime < self->nextMovement)
|
||||
@@ -572,8 +609,8 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) {
|
||||
self->nextAttack = 0;
|
||||
|
||||
// halve movement speed if snared
|
||||
if (self->hasBuff(ECSB_DN_MOVE_SPEED))
|
||||
speed /= 2;
|
||||
if (self->cbf & CSB_BIT_DN_MOVE_SPEED)
|
||||
self->speed /= 2;
|
||||
|
||||
int targetX = plr->x;
|
||||
int targetY = plr->y;
|
||||
@@ -582,9 +619,9 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) {
|
||||
targetY += self->offsetY*distance/(self->idleRange + 1);
|
||||
}
|
||||
|
||||
distanceToTravel = std::min(distance-mobRange+1, speed*2/5);
|
||||
distanceToTravel = std::min(distance-mobRange+1, self->speed*2/5);
|
||||
auto targ = lerp(self->x, self->y, targetX, targetY, distanceToTravel);
|
||||
if (distanceToTravel < speed*2/5 && currTime >= self->nextAttack)
|
||||
if (distanceToTravel < self->speed*2/5 && currTime >= self->nextAttack)
|
||||
self->nextAttack = 0;
|
||||
|
||||
NPCManager::updateNPCPosition(self->id, targ.first, targ.second, self->z, self->instanceID, self->angle);
|
||||
@@ -592,7 +629,7 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) {
|
||||
INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt);
|
||||
|
||||
pkt.iNPC_ID = self->id;
|
||||
pkt.iSpeed = speed;
|
||||
pkt.iSpeed = self->speed;
|
||||
pkt.iToX = self->x = targ.first;
|
||||
pkt.iToY = self->y = targ.second;
|
||||
pkt.iToZ = plr->z;
|
||||
@@ -678,7 +715,7 @@ void MobAI::roamingStep(CombatNPC* npc, time_t currTime) {
|
||||
farY = std::clamp(farY, yStart, yStart + self->idleRange);
|
||||
|
||||
// halve movement speed if snared
|
||||
if (self->hasBuff(ECSB_DN_MOVE_SPEED))
|
||||
if (self->cbf & CSB_BIT_DN_MOVE_SPEED)
|
||||
self->speed /= 2;
|
||||
|
||||
std::queue<Vec3> queue;
|
||||
@@ -751,10 +788,14 @@ void MobAI::onRoamStart(CombatNPC* npc, EntityRef src) {
|
||||
self->hp = self->maxHealth;
|
||||
self->killedTime = 0;
|
||||
self->nextAttack = 0;
|
||||
self->cbf = 0;
|
||||
|
||||
// cast a return home heal spell, this is the right way(tm)
|
||||
Abilities::useNPCSkill(npc->getRef(), 110, { npc });
|
||||
|
||||
// TODO ABILITIES
|
||||
/*std::vector<int> targetData = { 1, 0, 0, 0, 0 };
|
||||
for (auto& pwr : Abilities::Powers)
|
||||
if (pwr.skillType == Abilities::SkillTable[110].skillType)
|
||||
pwr.handle(self->id, targetData, 110, Abilities::SkillTable[110].durationTime[0], Abilities::SkillTable[110].powerIntensity[0]);*/
|
||||
// clear outlying debuffs
|
||||
clearDebuff(self);
|
||||
}
|
||||
@@ -771,9 +812,12 @@ void MobAI::onCombatStart(CombatNPC* npc, EntityRef src) {
|
||||
self->roamY = self->y;
|
||||
self->roamZ = self->z;
|
||||
|
||||
int skillID = (int)self->data["m_iPassiveBuff"];
|
||||
if(skillID != 0) // cast passive
|
||||
Abilities::useNPCSkill(npc->getRef(), skillID, { npc });
|
||||
int skillID = (int)self->data["m_iPassiveBuff"]; // cast passive
|
||||
// TODO ABILITIES
|
||||
/*std::vector<int> targetData = { 1, self->id, 0, 0, 0 };
|
||||
for (auto& pwr : Abilities::Powers)
|
||||
if (pwr.skillType == Abilities::SkillTable[skillID].skillType)
|
||||
pwr.handle(self->id, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]);*/
|
||||
}
|
||||
|
||||
void MobAI::onRetreat(CombatNPC* npc, EntityRef src) {
|
||||
@@ -789,8 +833,9 @@ void MobAI::onDeath(CombatNPC* npc, EntityRef src) {
|
||||
Mob* self = (Mob*)npc;
|
||||
|
||||
self->target = nullptr;
|
||||
self->cbf = 0;
|
||||
self->skillStyle = -1;
|
||||
self->clearBuffs(true);
|
||||
self->unbuffTimes.clear();
|
||||
self->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
|
||||
|
||||
@@ -21,6 +21,8 @@ namespace MobAI {
|
||||
}
|
||||
|
||||
struct Mob : public CombatNPC {
|
||||
// general
|
||||
std::unordered_map<int32_t,time_t> unbuffTimes = {};
|
||||
|
||||
// dead
|
||||
time_t killedTime = 0;
|
||||
@@ -50,8 +52,8 @@ struct Mob : public CombatNPC {
|
||||
// temporary; until we're sure what's what
|
||||
nlohmann::json data = {};
|
||||
|
||||
Mob(int spawnX, int spawnY, int spawnZ, int angle, uint64_t iID, int t, nlohmann::json d, int32_t id)
|
||||
: CombatNPC(spawnX, spawnY, spawnZ, angle, iID, t, id, d["m_iHP"]),
|
||||
Mob(int x, int y, int z, int angle, uint64_t iID, int t, nlohmann::json d, int32_t id)
|
||||
: CombatNPC(x, y, z, angle, iID, t, id, d["m_iHP"]),
|
||||
sightRange(d["m_iSightRange"]) {
|
||||
state = AIState::ROAMING;
|
||||
|
||||
@@ -62,13 +64,15 @@ struct Mob : public CombatNPC {
|
||||
idleRange = (int)data["m_iIdleRange"];
|
||||
level = data["m_iNpcLevel"];
|
||||
|
||||
roamX = spawnX;
|
||||
roamY = spawnY;
|
||||
roamZ = spawnZ;
|
||||
roamX = x;
|
||||
roamY = y;
|
||||
roamZ = z;
|
||||
|
||||
offsetX = 0;
|
||||
offsetY = 0;
|
||||
|
||||
cbf = 0;
|
||||
|
||||
// NOTE: there appear to be discrepancies in the dump
|
||||
hp = maxHealth;
|
||||
|
||||
|
||||
@@ -94,49 +94,20 @@ void NPCManager::sendToViewable(Entity *npc, void *buf, uint32_t type, size_t si
|
||||
static void npcBarkHandler(CNSocket* sock, CNPacketData* data) {
|
||||
sP_CL2FE_REQ_BARKER* req = (sP_CL2FE_REQ_BARKER*)data->buf;
|
||||
|
||||
int taskID = req->iMissionTaskID;
|
||||
// ignore req->iNPC_ID as it is often fixated on a single npc in the region
|
||||
|
||||
if (Missions::Tasks.find(taskID) == Missions::Tasks.end()) {
|
||||
std::cout << "mission task not found: " << taskID << std::endl;
|
||||
return;
|
||||
// get bark IDs from task data
|
||||
TaskData* td = Missions::Tasks[req->iMissionTaskID];
|
||||
std::vector<int> barks;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (td->task["m_iHBarkerTextID"][i] != 0) // non-zeroes only
|
||||
barks.push_back(td->task["m_iHBarkerTextID"][i]);
|
||||
}
|
||||
|
||||
TaskData* td = Missions::Tasks[taskID];
|
||||
auto& barks = td->task["m_iHBarkerTextID"];
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
std::vector<std::pair<int32_t, int32_t>> npcLines;
|
||||
|
||||
for (Chunk* chunk : plr->viewableChunks) {
|
||||
for (auto ent = chunk->entities.begin(); ent != chunk->entities.end(); ent++) {
|
||||
if (ent->kind != EntityKind::SIMPLE_NPC)
|
||||
continue;
|
||||
|
||||
BaseNPC* npc = (BaseNPC*)ent->getEntity();
|
||||
if (npc->type < 0 || npc->type >= NPCData.size())
|
||||
continue; // npc unknown ?!
|
||||
|
||||
int barkType = NPCData[npc->type]["m_iBarkerType"];
|
||||
if (barkType < 1 || barkType > 4)
|
||||
continue; // no barks
|
||||
|
||||
int barkID = barks[barkType - 1];
|
||||
if (barkID == 0)
|
||||
continue; // no barks
|
||||
|
||||
npcLines.push_back(std::make_pair(npc->id, barkID));
|
||||
}
|
||||
}
|
||||
|
||||
if (npcLines.size() == 0)
|
||||
return; // totally no barks
|
||||
|
||||
auto& [npcID, missionStringID] = npcLines[Rand::rand(npcLines.size())];
|
||||
if (barks.empty())
|
||||
return; // no barks
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_BARKER, resp);
|
||||
resp.iNPC_ID = npcID;
|
||||
resp.iMissionStringID = missionStringID;
|
||||
resp.iNPC_ID = req->iNPC_ID;
|
||||
resp.iMissionStringID = barks[Rand::rand(barks.size())];
|
||||
sock->sendPacket(resp, P_FE2CL_REP_BARKER);
|
||||
}
|
||||
|
||||
@@ -151,15 +122,16 @@ static void npcUnsummonHandler(CNSocket* sock, CNPacketData* data) {
|
||||
}
|
||||
|
||||
// type must already be checked and updateNPCPosition() must be called on the result
|
||||
BaseNPC *NPCManager::summonNPC(int spawnX, int spawnY, int spawnZ, uint64_t instance, int type, bool respawn, bool baseInstance) {
|
||||
BaseNPC *NPCManager::summonNPC(int x, int y, int z, uint64_t instance, int type, bool respawn, bool baseInstance) {
|
||||
uint64_t inst = baseInstance ? MAPNUM(instance) : instance;
|
||||
|
||||
//assert(nextId < INT32_MAX);
|
||||
int id = nextId--;
|
||||
int team = NPCData[type]["m_iTeam"];
|
||||
BaseNPC *npc = nullptr;
|
||||
|
||||
if (team == 2) {
|
||||
npc = new Mob(spawnX, spawnY, spawnZ, inst, type, NPCData[type], id);
|
||||
npc = new Mob(x, y, z, inst, type, NPCData[type], id);
|
||||
|
||||
// re-enable respawning, if desired
|
||||
((Mob*)npc)->summoned = !respawn;
|
||||
@@ -322,55 +294,57 @@ BaseNPC* NPCManager::getNearestNPC(std::set<Chunk*>* chunks, int X, int Y, int Z
|
||||
return npc;
|
||||
}
|
||||
|
||||
// TODO: Move this to separate file in ai/ subdir when implementing more events
|
||||
// TODO: Move this to MobAI, possibly
|
||||
#pragma region NPCEvents
|
||||
|
||||
// summon right arm and stage 2 body
|
||||
static void lordFuseStageTwo(CombatNPC *npc) {
|
||||
static void lordFuseStageTwo(CNSocket *sock, BaseNPC *npc) {
|
||||
Mob *oldbody = (Mob*)npc; // adaptium, stun
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
std::cout << "Lord Fuse stage two" << std::endl;
|
||||
|
||||
// Fuse doesn't move
|
||||
// Blastons, Heal
|
||||
Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->x, oldbody->y, oldbody->z, oldbody->instanceID, 2467);
|
||||
Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->x, oldbody->y, oldbody->z, plr->instanceID, 2467);
|
||||
|
||||
newbody->angle = oldbody->angle;
|
||||
NPCManager::updateNPCPosition(newbody->id, newbody->spawnX, newbody->spawnY, newbody->spawnZ,
|
||||
oldbody->instanceID, oldbody->angle);
|
||||
NPCManager::updateNPCPosition(newbody->id, newbody->x, newbody->y, newbody->z,
|
||||
plr->instanceID, oldbody->angle);
|
||||
|
||||
// right arm, Adaptium, Stun
|
||||
Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->x - 600, oldbody->y, oldbody->z, oldbody->instanceID, 2469);
|
||||
Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->x - 600, oldbody->y, oldbody->z, plr->instanceID, 2469);
|
||||
|
||||
arm->angle = oldbody->angle;
|
||||
NPCManager::updateNPCPosition(arm->id, arm->spawnX, arm->spawnY, arm->spawnZ,
|
||||
oldbody->instanceID, oldbody->angle);
|
||||
NPCManager::updateNPCPosition(arm->id, arm->x, arm->y, arm->z,
|
||||
plr->instanceID, oldbody->angle);
|
||||
}
|
||||
|
||||
// summon left arm and stage 3 body
|
||||
static void lordFuseStageThree(CombatNPC *npc) {
|
||||
static void lordFuseStageThree(CNSocket *sock, BaseNPC *npc) {
|
||||
Mob *oldbody = (Mob*)npc;
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
std::cout << "Lord Fuse stage three" << std::endl;
|
||||
|
||||
// Cosmix, Damage Point
|
||||
Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->x, oldbody->y, oldbody->z, oldbody->instanceID, 2468);
|
||||
Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->x, oldbody->y, oldbody->z, plr->instanceID, 2468);
|
||||
|
||||
newbody->angle = oldbody->angle;
|
||||
NPCManager::updateNPCPosition(newbody->id, newbody->spawnX, newbody->spawnY, newbody->spawnZ,
|
||||
newbody->instanceID, oldbody->angle);
|
||||
NPCManager::updateNPCPosition(newbody->id, newbody->x, newbody->y, newbody->z,
|
||||
plr->instanceID, oldbody->angle);
|
||||
|
||||
// Blastons, Heal
|
||||
Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->x + 600, oldbody->y, oldbody->z, oldbody->instanceID, 2470);
|
||||
Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->x + 600, oldbody->y, oldbody->z, plr->instanceID, 2470);
|
||||
|
||||
arm->angle = oldbody->angle;
|
||||
NPCManager::updateNPCPosition(arm->id, arm->spawnX, arm->spawnY, arm->spawnZ,
|
||||
arm->instanceID, oldbody->angle);
|
||||
NPCManager::updateNPCPosition(arm->id, arm->x, arm->y, arm->z,
|
||||
plr->instanceID, oldbody->angle);
|
||||
}
|
||||
|
||||
std::vector<NPCEvent> NPCManager::NPCEvents = {
|
||||
NPCEvent(2466, AIState::DEAD, lordFuseStageTwo),
|
||||
NPCEvent(2467, AIState::DEAD, lordFuseStageThree),
|
||||
NPCEvent(2466, ON_KILLED, lordFuseStageTwo),
|
||||
NPCEvent(2467, ON_KILLED, lordFuseStageThree),
|
||||
};
|
||||
|
||||
#pragma endregion NPCEvents
|
||||
@@ -402,5 +376,5 @@ void NPCManager::init() {
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_NPC_UNSUMMON, npcUnsummonHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_BARKER, npcBarkHandler);
|
||||
|
||||
REGISTER_SHARD_TIMER(step, MS_PER_COMBAT_TICK);
|
||||
REGISTER_SHARD_TIMER(step, 200);
|
||||
}
|
||||
|
||||
@@ -14,15 +14,20 @@
|
||||
|
||||
#define RESURRECT_HEIGHT 400
|
||||
|
||||
typedef void (*NPCEventHandler)(CombatNPC*);
|
||||
enum Trigger {
|
||||
ON_KILLED,
|
||||
ON_COMBAT
|
||||
};
|
||||
|
||||
typedef void (*NPCEventHandler)(CNSocket*, BaseNPC*);
|
||||
|
||||
struct NPCEvent {
|
||||
int32_t npcType;
|
||||
AIState triggerState;
|
||||
int trigger;
|
||||
NPCEventHandler handler;
|
||||
|
||||
NPCEvent(int32_t t, AIState tr, NPCEventHandler hndlr)
|
||||
: npcType(t), triggerState(tr), handler(hndlr) {}
|
||||
NPCEvent(int32_t t, int tr, NPCEventHandler hndlr)
|
||||
: npcType(t), trigger(tr), handler(hndlr) {}
|
||||
};
|
||||
|
||||
namespace NPCManager {
|
||||
|
||||
@@ -69,6 +69,52 @@ void Nanos::addNano(CNSocket* sock, int16_t nanoID, int16_t slot, bool spendfm)
|
||||
PlayerManager::sendToViewable(sock, resp2, P_FE2CL_REP_PC_CHANGE_LEVEL);
|
||||
}
|
||||
|
||||
std::vector<ICombatant*> Nanos::applyNanoBuff(SkillData* skill, Player* plr) {
|
||||
assert(skill->drainType == SkillDrainType::PASSIVE);
|
||||
|
||||
EntityRef self = PlayerManager::getSockFromID(plr->iID);
|
||||
std::vector<ICombatant*> affected;
|
||||
std::vector<EntityRef> targets;
|
||||
if (skill->targetType == SkillTargetType::GROUP) {
|
||||
targets = plr->getGroupMembers(); // group
|
||||
}
|
||||
else if(skill->targetType == SkillTargetType::SELF) {
|
||||
targets.push_back(self); // self
|
||||
} else {
|
||||
std::cout << "[WARN] Passive skill with type " << skill->skillType << " has target type MOB" << std::endl;
|
||||
}
|
||||
|
||||
int timeBuffId = Abilities::getCSTBFromST(skill->skillType);
|
||||
int boost = Nanos::getNanoBoost(plr) ? 3 : 0;
|
||||
int value = skill->values[0][boost];
|
||||
|
||||
BuffStack passiveBuff = {
|
||||
1, // passive nano buffs refreshed every tick
|
||||
value,
|
||||
self,
|
||||
BuffClass::NONE, // overwritten per target
|
||||
};
|
||||
|
||||
for (EntityRef target : targets) {
|
||||
Entity* entity = target.getEntity();
|
||||
if (entity->kind != PLAYER && entity->kind != COMBAT_NPC && entity->kind != MOB)
|
||||
continue; // not a combatant
|
||||
|
||||
passiveBuff.buffStackClass = target == self ? BuffClass::NANO : BuffClass::GROUP_NANO;
|
||||
ICombatant* combatant = dynamic_cast<ICombatant*>(entity);
|
||||
if(combatant->addBuff(timeBuffId,
|
||||
[](EntityRef self, Buff* buff, int status, BuffStack* stack) {
|
||||
Buffs::timeBuffUpdate(self, buff, status, stack);
|
||||
},
|
||||
[](EntityRef self, Buff* buff, time_t currTime) {
|
||||
// no-op
|
||||
},
|
||||
&passiveBuff)) affected.push_back(combatant);
|
||||
}
|
||||
|
||||
return affected;
|
||||
}
|
||||
|
||||
void Nanos::summonNano(CNSocket *sock, int slot, bool silent) {
|
||||
INITSTRUCT(sP_FE2CL_REP_NANO_ACTIVE_SUCC, resp);
|
||||
resp.iActiveNanoSlotNum = slot;
|
||||
@@ -93,10 +139,8 @@ void Nanos::summonNano(CNSocket *sock, int slot, bool silent) {
|
||||
if (slot != -1 && skill != nullptr && skill->drainType == SkillDrainType::PASSIVE) {
|
||||
// passive buff effect
|
||||
resp.eCSTB___Add = 1;
|
||||
ICombatant* src = dynamic_cast<ICombatant*>(plr);
|
||||
int32_t targets[] = { plr->iID };
|
||||
std::vector<ICombatant*> affectedCombatants = Abilities::matchTargets(src, skill, 1, targets);
|
||||
Abilities::useNanoSkill(sock, skill, nano, affectedCombatants);
|
||||
std::vector<ICombatant*> affectedCombatants = applyNanoBuff(skill, plr);
|
||||
if(!affectedCombatants.empty()) Abilities::useNanoSkill(sock, skill, nano, affectedCombatants);
|
||||
}
|
||||
|
||||
if (!silent) // silent nano death but only for the summoning player
|
||||
@@ -273,11 +317,11 @@ static void nanoSkillUseHandler(CNSocket* sock, CNPacketData* data) {
|
||||
std::cout << PlayerManager::getPlayerName(plr) << " requested to summon nano skill " << std::endl;
|
||||
)
|
||||
|
||||
ICombatant* plrCombatant = dynamic_cast<ICombatant*>(plr);
|
||||
std::vector<ICombatant*> targetData = Abilities::matchTargets(plrCombatant, skillData, pkt->iTargetCnt, (int32_t*)(pkt + 1));
|
||||
// TODO ABILITIES
|
||||
std::vector<ICombatant*> targetData = Abilities::matchTargets(skillData, pkt->iTargetCnt, (int32_t*)(pkt + 1));
|
||||
Abilities::useNanoSkill(sock, skillData, nano, targetData);
|
||||
|
||||
if (plr->Nanos[plr->activeNano].iStamina <= 0)
|
||||
if (plr->Nanos[plr->activeNano].iStamina < 0)
|
||||
summonNano(sock, -1);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,4 +26,5 @@ namespace Nanos {
|
||||
void summonNano(CNSocket* sock, int slot, bool silent = false);
|
||||
int nanoStyle(int nanoID);
|
||||
bool getNanoBoost(Player* plr);
|
||||
std::vector<ICombatant*> applyNanoBuff(SkillData* skill, Player* plr);
|
||||
}
|
||||
|
||||
@@ -80,8 +80,6 @@ struct Player : public Entity, public ICombatant {
|
||||
uint64_t iFirstUseFlag[2] = {};
|
||||
time_t lastHeartbeat = 0;
|
||||
|
||||
int suspicionRating = 0;
|
||||
time_t lastShot = 0;
|
||||
std::vector<sItemBase> buyback = {};
|
||||
|
||||
Player() { kind = EntityKind::PLAYER; }
|
||||
@@ -92,8 +90,7 @@ struct Player : public Entity, public ICombatant {
|
||||
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 void removeBuff(int buffId, int buffClass) override;
|
||||
virtual bool hasBuff(int buffId) override;
|
||||
virtual int getCompositeCondition() override;
|
||||
virtual int takeDamage(EntityRef src, int amt) override;
|
||||
|
||||
@@ -23,12 +23,17 @@ using namespace PlayerManager;
|
||||
|
||||
std::map<CNSocket*, Player*> PlayerManager::players;
|
||||
|
||||
static void addPlayer(CNSocket* key, Player *plr) {
|
||||
players[key] = plr;
|
||||
plr->chunkPos = Chunking::INVALID_CHUNK;
|
||||
plr->lastHeartbeat = 0;
|
||||
static void addPlayer(CNSocket* key, Player& plr) {
|
||||
Player *p = new Player();
|
||||
|
||||
std::cout << getPlayerName(plr) << " has joined!" << std::endl;
|
||||
// copy object into heap memory
|
||||
*p = plr;
|
||||
|
||||
players[key] = p;
|
||||
p->chunkPos = Chunking::INVALID_CHUNK;
|
||||
p->lastHeartbeat = 0;
|
||||
|
||||
std::cout << getPlayerName(p) << " has joined!" << std::endl;
|
||||
std::cout << players.size() << " players" << std::endl;
|
||||
}
|
||||
|
||||
@@ -77,10 +82,7 @@ void PlayerManager::updatePlayerPosition(CNSocket* sock, int X, int Y, int Z, ui
|
||||
plr->x = X;
|
||||
plr->y = Y;
|
||||
plr->z = Z;
|
||||
if (plr->instanceID != I) {
|
||||
plr->instanceID = I;
|
||||
plr->recallInstance = INSTANCE_OVERWORLD;
|
||||
}
|
||||
plr->instanceID = I;
|
||||
if (oldChunk == newChunk)
|
||||
return; // didn't change chunks
|
||||
Chunking::updateEntityChunk({sock}, oldChunk, newChunk);
|
||||
@@ -126,6 +128,24 @@ 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);
|
||||
}
|
||||
|
||||
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);
|
||||
pkt2.iX = X;
|
||||
pkt2.iY = Y;
|
||||
@@ -195,72 +215,66 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player *plr = new Player();
|
||||
Database::getPlayer(plr, lm->playerId);
|
||||
// for convenience
|
||||
Player& plr = lm->plr;
|
||||
|
||||
// check if account is already in use
|
||||
if (isAccountInUse(plr->accountId)) {
|
||||
if (isAccountInUse(plr.accountId)) {
|
||||
// kick the other player
|
||||
exitDuplicate(plr->accountId);
|
||||
|
||||
// re-read the player from disk, in case it was just flushed
|
||||
*plr = {};
|
||||
Database::getPlayer(plr, lm->playerId);
|
||||
exitDuplicate(plr.accountId);
|
||||
}
|
||||
|
||||
plr->group = nullptr;
|
||||
|
||||
response.iID = plr->iID;
|
||||
response.iID = plr.iID;
|
||||
response.uiSvrTime = getTime();
|
||||
response.PCLoadData2CL.iUserLevel = plr->accountLevel;
|
||||
response.PCLoadData2CL.iHP = plr->HP;
|
||||
response.PCLoadData2CL.iLevel = plr->level;
|
||||
response.PCLoadData2CL.iCandy = plr->money;
|
||||
response.PCLoadData2CL.iFusionMatter = plr->fusionmatter;
|
||||
response.PCLoadData2CL.iMentor = plr->mentor;
|
||||
response.PCLoadData2CL.iUserLevel = plr.accountLevel;
|
||||
response.PCLoadData2CL.iHP = plr.HP;
|
||||
response.PCLoadData2CL.iLevel = plr.level;
|
||||
response.PCLoadData2CL.iCandy = plr.money;
|
||||
response.PCLoadData2CL.iFusionMatter = plr.fusionmatter;
|
||||
response.PCLoadData2CL.iMentor = plr.mentor;
|
||||
response.PCLoadData2CL.iMentorCount = 1; // how many guides the player has had
|
||||
response.PCLoadData2CL.iX = plr->x;
|
||||
response.PCLoadData2CL.iY = plr->y;
|
||||
response.PCLoadData2CL.iZ = plr->z;
|
||||
response.PCLoadData2CL.iAngle = plr->angle;
|
||||
response.PCLoadData2CL.iBatteryN = plr->batteryN;
|
||||
response.PCLoadData2CL.iBatteryW = plr->batteryW;
|
||||
response.PCLoadData2CL.iX = plr.x;
|
||||
response.PCLoadData2CL.iY = plr.y;
|
||||
response.PCLoadData2CL.iZ = plr.z;
|
||||
response.PCLoadData2CL.iAngle = plr.angle;
|
||||
response.PCLoadData2CL.iBatteryN = plr.batteryN;
|
||||
response.PCLoadData2CL.iBatteryW = plr.batteryW;
|
||||
response.PCLoadData2CL.iBuddyWarpTime = 60; // sets 60s warp cooldown on login
|
||||
|
||||
response.PCLoadData2CL.iWarpLocationFlag = plr->iWarpLocationFlag;
|
||||
response.PCLoadData2CL.aWyvernLocationFlag[0] = plr->aSkywayLocationFlag[0];
|
||||
response.PCLoadData2CL.aWyvernLocationFlag[1] = plr->aSkywayLocationFlag[1];
|
||||
response.PCLoadData2CL.iWarpLocationFlag = plr.iWarpLocationFlag;
|
||||
response.PCLoadData2CL.aWyvernLocationFlag[0] = plr.aSkywayLocationFlag[0];
|
||||
response.PCLoadData2CL.aWyvernLocationFlag[1] = plr.aSkywayLocationFlag[1];
|
||||
|
||||
response.PCLoadData2CL.iActiveNanoSlotNum = -1;
|
||||
response.PCLoadData2CL.iFatigue = 50;
|
||||
response.PCLoadData2CL.PCStyle = plr->PCStyle;
|
||||
response.PCLoadData2CL.PCStyle = plr.PCStyle;
|
||||
|
||||
// client doesnt read this, it gets it from charinfo
|
||||
// response.PCLoadData2CL.PCStyle2 = plr->PCStyle2;
|
||||
// response.PCLoadData2CL.PCStyle2 = plr.PCStyle2;
|
||||
// inventory
|
||||
for (int i = 0; i < AEQUIP_COUNT; i++)
|
||||
response.PCLoadData2CL.aEquip[i] = plr->Equip[i];
|
||||
response.PCLoadData2CL.aEquip[i] = plr.Equip[i];
|
||||
for (int i = 0; i < AINVEN_COUNT; i++)
|
||||
response.PCLoadData2CL.aInven[i] = plr->Inven[i];
|
||||
response.PCLoadData2CL.aInven[i] = plr.Inven[i];
|
||||
// quest inventory
|
||||
for (int i = 0; i < AQINVEN_COUNT; i++)
|
||||
response.PCLoadData2CL.aQInven[i] = plr->QInven[i];
|
||||
response.PCLoadData2CL.aQInven[i] = plr.QInven[i];
|
||||
// nanos
|
||||
for (int i = 1; i < SIZEOF_NANO_BANK_SLOT; i++) {
|
||||
response.PCLoadData2CL.aNanoBank[i] = plr->Nanos[i];
|
||||
response.PCLoadData2CL.aNanoBank[i] = plr.Nanos[i];
|
||||
}
|
||||
for (int i = 0; i < 3; i++) {
|
||||
response.PCLoadData2CL.aNanoSlots[i] = plr->equippedNanos[i];
|
||||
response.PCLoadData2CL.aNanoSlots[i] = plr.equippedNanos[i];
|
||||
}
|
||||
// missions in progress
|
||||
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
||||
if (plr->tasks[i] == 0)
|
||||
if (plr.tasks[i] == 0)
|
||||
break;
|
||||
response.PCLoadData2CL.aRunningQuest[i].m_aCurrTaskID = plr->tasks[i];
|
||||
TaskData &task = *Missions::Tasks[plr->tasks[i]];
|
||||
response.PCLoadData2CL.aRunningQuest[i].m_aCurrTaskID = plr.tasks[i];
|
||||
TaskData &task = *Missions::Tasks[plr.tasks[i]];
|
||||
for (int j = 0; j < 3; j++) {
|
||||
response.PCLoadData2CL.aRunningQuest[i].m_aKillNPCID[j] = (int)task["m_iCSUEnemyID"][j];
|
||||
response.PCLoadData2CL.aRunningQuest[i].m_aKillNPCCount[j] = plr->RemainingNPCCount[i][j];
|
||||
response.PCLoadData2CL.aRunningQuest[i].m_aKillNPCCount[j] = plr.RemainingNPCCount[i][j];
|
||||
/*
|
||||
* client doesn't care about NeededItem ID and Count,
|
||||
* it gets Count from Quest Inventory
|
||||
@@ -270,12 +284,12 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
*/
|
||||
}
|
||||
}
|
||||
response.PCLoadData2CL.iCurrentMissionID = plr->CurrentMissionID;
|
||||
response.PCLoadData2CL.iCurrentMissionID = plr.CurrentMissionID;
|
||||
|
||||
// completed missions
|
||||
// the packet requires 32 items, but the client only checks the first 16 (shrug)
|
||||
for (int i = 0; i < 16; i++) {
|
||||
response.PCLoadData2CL.aQuestFlag[i] = plr->aQuestFlag[i];
|
||||
response.PCLoadData2CL.aQuestFlag[i] = plr.aQuestFlag[i];
|
||||
}
|
||||
|
||||
// Computress tips
|
||||
@@ -284,11 +298,11 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
response.PCLoadData2CL.iFirstUseFlag2 = UINT64_MAX;
|
||||
}
|
||||
else {
|
||||
response.PCLoadData2CL.iFirstUseFlag1 = plr->iFirstUseFlag[0];
|
||||
response.PCLoadData2CL.iFirstUseFlag2 = plr->iFirstUseFlag[1];
|
||||
response.PCLoadData2CL.iFirstUseFlag1 = plr.iFirstUseFlag[0];
|
||||
response.PCLoadData2CL.iFirstUseFlag2 = plr.iFirstUseFlag[1];
|
||||
}
|
||||
|
||||
plr->instanceID = INSTANCE_OVERWORLD; // the player should never be in an instance on enter
|
||||
plr.instanceID = INSTANCE_OVERWORLD; // the player should never be in an instance on enter
|
||||
|
||||
sock->setEKey(CNSocketEncryption::createNewKey(response.uiSvrTime, response.iID + 1, response.PCLoadData2CL.iFusionMatter + 1));
|
||||
sock->setFEKey(lm->FEKey);
|
||||
@@ -299,14 +313,14 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
// transmit MOTD after entering the game, so the client hopefully changes modes on time
|
||||
Chat::sendServerMessage(sock, settings::MOTDSTRING);
|
||||
|
||||
// transfer ownership of Player object into the shard (still valid in this function though)
|
||||
// copy Player object into the shard
|
||||
addPlayer(sock, plr);
|
||||
|
||||
// check if there is an expiring vehicle
|
||||
Items::checkItemExpire(sock, plr);
|
||||
Items::checkItemExpire(sock, getPlayer(sock));
|
||||
|
||||
// set player equip stats
|
||||
Items::setItemStats(plr);
|
||||
Items::setItemStats(getPlayer(sock));
|
||||
|
||||
Missions::failInstancedMissions(sock);
|
||||
|
||||
@@ -317,20 +331,12 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
for (auto& pair : players)
|
||||
if (pair.second->notify)
|
||||
Chat::sendServerMessage(pair.first, "[ADMIN]" + getPlayerName(plr) + " has joined.");
|
||||
Chat::sendServerMessage(pair.first, "[ADMIN]" + getPlayerName(&plr) + " has joined.");
|
||||
|
||||
// deallocate lm
|
||||
// deallocate lm (and therefore the plr object)
|
||||
delete lm;
|
||||
}
|
||||
|
||||
void PlayerManager::sendToGroup(CNSocket* sock, void* buf, uint32_t type, size_t size) {
|
||||
Player* plr = getPlayer(sock);
|
||||
if (plr->group == nullptr)
|
||||
return;
|
||||
for(const EntityRef& ref : plr->group->filter(EntityKind::PLAYER))
|
||||
ref.sock->sendPacket(buf, type, size);
|
||||
}
|
||||
|
||||
void PlayerManager::sendToViewable(CNSocket* sock, void* buf, uint32_t type, size_t size) {
|
||||
Player* plr = getPlayer(sock);
|
||||
for (auto it = plr->viewableChunks.begin(); it != plr->viewableChunks.end(); it++) {
|
||||
@@ -359,24 +365,6 @@ static void loadPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
updatePlayerPosition(sock, plr->x, plr->y, plr->z, plr->instanceID, plr->angle);
|
||||
|
||||
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) {
|
||||
@@ -412,14 +400,14 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) {
|
||||
if (!(plr->hasBuff(ECSB_PHOENIX)))
|
||||
return; // sanity check
|
||||
plr->Nanos[plr->activeNano].iStamina = 0;
|
||||
// TODO ABILITIES
|
||||
//Abilities::applyBuff(sock, plr->Nanos[plr->activeNano].iSkillID, 2, 1, 0);
|
||||
// fallthrough
|
||||
case ePCRegenType::HereByPhoenixGroup: // revived by group member's nano
|
||||
plr->HP = PC_MAXHEALTH(plr->level) / 2;
|
||||
break;
|
||||
|
||||
default: // plain respawn
|
||||
plr->HP = PC_MAXHEALTH(plr->level) / 2;
|
||||
plr->clearBuffs(false);
|
||||
// fallthrough
|
||||
case ePCRegenType::Unstick: // warp away
|
||||
move = true;
|
||||
@@ -580,7 +568,7 @@ static void setFirstUseFlag(CNSocket* sock, CNPacketData* data) {
|
||||
std::cout << "[WARN] Client submitted invalid first use flag number?!" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (flag->iFlagCode <= 64)
|
||||
plr->iFirstUseFlag[0] |= (1ULL << (flag->iFlagCode - 1));
|
||||
else
|
||||
|
||||
@@ -33,7 +33,6 @@ namespace PlayerManager {
|
||||
CNSocket *getSockFromAny(int by, int id, int uid, std::string firstname, std::string lastname);
|
||||
WarpLocation *getRespawnPoint(Player *plr);
|
||||
|
||||
void sendToGroup(CNSocket *sock, void* buf, uint32_t type, size_t size);
|
||||
void sendToViewable(CNSocket *sock, void* buf, uint32_t type, size_t size);
|
||||
|
||||
// TODO: unify this under the new Entity system
|
||||
|
||||
@@ -66,7 +66,7 @@ static void racingCancel(CNSocket* sock, CNPacketData* data) {
|
||||
INITSTRUCT(sP_FE2CL_REP_EP_RACE_CANCEL_SUCC, resp);
|
||||
sock->sendPacket(resp, P_FE2CL_REP_EP_RACE_CANCEL_SUCC);
|
||||
|
||||
/*
|
||||
/*
|
||||
* 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.
|
||||
* If the latter is to happen, the client disables movement until it
|
||||
@@ -99,37 +99,31 @@ static void racingEnd(CNSocket* sock, CNPacketData* data) {
|
||||
if (EPData.find(mapNum) == EPData.end() || EPData[mapNum].EPID == 0)
|
||||
return; // IZ not found
|
||||
|
||||
EPInfo& epInfo = EPData[mapNum];
|
||||
EPRace& epRace = EPRaces[sock];
|
||||
|
||||
uint64_t now = getTime() / 1000;
|
||||
int timeDiff = now - epRace.startTime;
|
||||
int podsCollected = epRace.collectedRings.size();
|
||||
|
||||
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;
|
||||
int timeDiff = now - EPRaces[sock].startTime;
|
||||
int score = 500 * EPRaces[sock].collectedRings.size() - 10 * timeDiff;
|
||||
if (score < 0) score = 0; // lol
|
||||
int fm = score * plr->level * (1.0f / 36) * 0.3f;
|
||||
|
||||
// we submit the ranking first...
|
||||
Database::RaceRanking postRanking = {};
|
||||
postRanking.EPID = epInfo.EPID;
|
||||
postRanking.EPID = EPData[mapNum].EPID;
|
||||
postRanking.PlayerID = plr->iID;
|
||||
postRanking.RingCount = podsCollected;
|
||||
postRanking.RingCount = EPRaces[sock].collectedRings.size();
|
||||
postRanking.Score = score;
|
||||
postRanking.Time = timeDiff;
|
||||
postRanking.Timestamp = getTimestamp();
|
||||
Database::postRaceRanking(postRanking);
|
||||
|
||||
// ...then we get the top ranking, which may or may not be what we just submitted
|
||||
Database::RaceRanking topRankingPlayer = Database::getTopRaceRanking(epInfo.EPID, plr->iID);
|
||||
Database::RaceRanking topRankingPlayer = Database::getTopRaceRanking(EPData[mapNum].EPID, plr->iID);
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_EP_RACE_END_SUCC, resp);
|
||||
|
||||
// get rank scores and rewards
|
||||
std::vector<int>* rankScores = &EPRewards[epInfo.EPID].first;
|
||||
std::vector<int>* rankRewards = &EPRewards[epInfo.EPID].second;
|
||||
std::vector<int>* rankScores = &EPRewards[EPData[mapNum].EPID].first;
|
||||
std::vector<int>* rankRewards = &EPRewards[EPData[mapNum].EPID].second;
|
||||
|
||||
// top ranking
|
||||
int topRank = 0;
|
||||
|
||||
@@ -7,11 +7,7 @@
|
||||
#include <set>
|
||||
|
||||
struct EPInfo {
|
||||
// available through XDT (maxScore may be updated by drops)
|
||||
int zoneX, zoneY, EPID, maxScore;
|
||||
// available through drops
|
||||
int maxTime, maxPods;
|
||||
double scaleFactor, podFactor, timeFactor;
|
||||
int zoneX, zoneY, EPID, maxScore, maxTime;
|
||||
};
|
||||
|
||||
struct EPRace {
|
||||
|
||||
@@ -33,22 +33,6 @@ public:
|
||||
const char *what() const throw() { return msg.c_str(); }
|
||||
};
|
||||
|
||||
/*
|
||||
* We must refuse to run if an invalid NPC type is found in the JSONs, especially
|
||||
* the gruntwork file. If we were to just skip loading invalid NPCs, they would get
|
||||
* silently dropped from the gruntwork file, which would be confusing in situations
|
||||
* where a gruntwork file for the wrong game build was accidentally loaded.
|
||||
*/
|
||||
static void ensureValidNPCType(int type, std::string filename) {
|
||||
// last known NPC type
|
||||
int npcLimit = NPCManager::NPCData.back()["m_iNpcNumber"];
|
||||
|
||||
if (type > npcLimit) {
|
||||
std::cout << "[FATAL] " << filename << " contains an invalid NPC type: " << type << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a full and properly-paced path by interpolating between keyframes.
|
||||
*/
|
||||
@@ -375,7 +359,7 @@ static void loadPaths(json& pathData, int32_t* nextId) {
|
||||
Transport::NPCPaths.push_back(pathTemplate);
|
||||
}
|
||||
std::cout << "[INFO] Loaded " << Transport::NPCPaths.size() << " NPC paths" << std::endl;
|
||||
|
||||
|
||||
}
|
||||
catch (const std::exception& err) {
|
||||
std::cerr << "[FATAL] Malformed paths.json file! Reason:" << err.what() << std::endl;
|
||||
@@ -584,17 +568,8 @@ static void loadDrops(json& dropData) {
|
||||
continue;
|
||||
}
|
||||
|
||||
EPInfo& epInfo = Racing::EPData[EPMap];
|
||||
|
||||
// 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"];
|
||||
// time limit isn't stored in the XDT, so we include it in the reward table instead
|
||||
Racing::EPData[EPMap].maxTime = race["TimeLimit"];
|
||||
|
||||
// score cutoffs
|
||||
std::vector<int> rankScores;
|
||||
@@ -695,12 +670,10 @@ static void loadEggs(json& eggData, int32_t* nextId) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* Load gruntwork output, if it exists
|
||||
*/
|
||||
static void loadGruntworkPre(json& gruntwork, int32_t* nextId) {
|
||||
if (gruntwork.is_null())
|
||||
return;
|
||||
|
||||
try {
|
||||
auto paths = gruntwork["paths"];
|
||||
@@ -750,8 +723,8 @@ static void loadGruntworkPre(json& gruntwork, int32_t* nextId) {
|
||||
}
|
||||
|
||||
static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
|
||||
if (gruntwork.is_null())
|
||||
return;
|
||||
|
||||
if (gruntwork.is_null()) return;
|
||||
|
||||
try {
|
||||
// skyway paths
|
||||
@@ -803,8 +776,6 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
|
||||
int id = (*nextId)--;
|
||||
uint64_t instanceID = mob.find("iMapNum") == mob.end() ? INSTANCE_OVERWORLD : (int)mob["iMapNum"];
|
||||
|
||||
ensureValidNPCType((int)mob["iNPCType"], settings::GRUNTWORKJSON);
|
||||
|
||||
if (NPCManager::NPCData[(int)mob["iNPCType"]]["m_iTeam"] == 2) {
|
||||
npc = new Mob(mob["iX"], mob["iY"], mob["iZ"], instanceID, mob["iNPCType"],
|
||||
NPCManager::NPCData[(int)mob["iNPCType"]], id);
|
||||
@@ -824,9 +795,6 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
|
||||
auto groups = gruntwork["groups"];
|
||||
for (auto _group = groups.begin(); _group != groups.end(); _group++) {
|
||||
auto leader = _group.value();
|
||||
|
||||
ensureValidNPCType((int)leader["iNPCType"], settings::GRUNTWORKJSON);
|
||||
|
||||
auto td = NPCManager::NPCData[(int)leader["iNPCType"]];
|
||||
uint64_t instanceID = leader.find("iMapNum") == leader.end() ? INSTANCE_OVERWORLD : (int)leader["iMapNum"];
|
||||
|
||||
@@ -847,9 +815,6 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
|
||||
int followerCount = 0;
|
||||
for (json::iterator _fol = followers.begin(); _fol != followers.end(); _fol++) {
|
||||
auto follower = _fol.value();
|
||||
|
||||
ensureValidNPCType((int)follower["iNPCType"], settings::GRUNTWORKJSON);
|
||||
|
||||
auto tdFol = NPCManager::NPCData[(int)follower["iNPCType"]];
|
||||
Mob* tmpFol = new Mob((int)leader["iX"] + (int)follower["iOffsetX"], (int)leader["iY"] + (int)follower["iOffsetY"], leader["iZ"], leader["iAngle"], instanceID, follower["iNPCType"], tdFol, *nextId);
|
||||
|
||||
@@ -906,9 +871,10 @@ static void loadNPCs(json& npcData) {
|
||||
npcID += NPC_ID_OFFSET;
|
||||
int instanceID = npc.find("iMapNum") == npc.end() ? INSTANCE_OVERWORLD : (int)npc["iMapNum"];
|
||||
int type = (int)npc["iNPCType"];
|
||||
|
||||
ensureValidNPCType(type, settings::NPCJSON);
|
||||
|
||||
if (NPCManager::NPCData[type].is_null()) {
|
||||
std::cout << "[WARN] NPC type " << type << " not found; skipping (json#" << _npc.key() << ")" << std::endl;
|
||||
continue;
|
||||
}
|
||||
#ifdef ACADEMY
|
||||
// do not spawn NPCs in the future
|
||||
if (npc["iX"] > 512000 && npc["iY"] < 256000)
|
||||
@@ -950,9 +916,10 @@ static void loadMobs(json& npcData, int32_t* nextId) {
|
||||
int npcID = std::strtol(_npc.key().c_str(), nullptr, 10); // parse ID string to integer
|
||||
npcID += MOB_ID_OFFSET;
|
||||
int type = (int)npc["iNPCType"];
|
||||
|
||||
ensureValidNPCType(type, settings::MOBJSON);
|
||||
|
||||
if (NPCManager::NPCData[type].is_null()) {
|
||||
std::cout << "[WARN] NPC type " << type << " not found; skipping (json#" << _npc.key() << ")" << std::endl;
|
||||
continue;
|
||||
}
|
||||
auto td = NPCManager::NPCData[type];
|
||||
uint64_t instanceID = npc.find("iMapNum") == npc.end() ? INSTANCE_OVERWORLD : (int)npc["iMapNum"];
|
||||
|
||||
@@ -980,10 +947,7 @@ static void loadMobs(json& npcData, int32_t* nextId) {
|
||||
for (json::iterator _group = groupData.begin(); _group != groupData.end(); _group++) {
|
||||
auto leader = _group.value();
|
||||
int leadID = std::strtol(_group.key().c_str(), nullptr, 10); // parse ID string to integer
|
||||
|
||||
leadID += MOB_GROUP_ID_OFFSET;
|
||||
ensureValidNPCType(leader["iNPCType"], settings::MOBJSON);
|
||||
|
||||
auto td = NPCManager::NPCData[(int)leader["iNPCType"]];
|
||||
uint64_t instanceID = leader.find("iMapNum") == leader.end() ? INSTANCE_OVERWORLD : (int)leader["iMapNum"];
|
||||
auto followers = leader["aFollowers"];
|
||||
@@ -1013,9 +977,6 @@ static void loadMobs(json& npcData, int32_t* nextId) {
|
||||
int followerCount = 0;
|
||||
for (json::iterator _fol = followers.begin(); _fol != followers.end(); _fol++) {
|
||||
auto follower = _fol.value();
|
||||
|
||||
ensureValidNPCType(follower["iNPCType"], settings::MOBJSON);
|
||||
|
||||
auto tdFol = NPCManager::NPCData[(int)follower["iNPCType"]];
|
||||
Mob* tmpFol = new Mob((int)leader["iX"] + (int)follower["iOffsetX"], (int)leader["iY"] + (int)follower["iOffsetY"], leader["iZ"], leader["iAngle"], instanceID, follower["iNPCType"], tdFol, *nextId);
|
||||
|
||||
@@ -1121,39 +1082,19 @@ void TableData::init() {
|
||||
};
|
||||
|
||||
// load JSON data into tables
|
||||
std::ifstream fstream;
|
||||
for (int i = 0; i < 7; i++) {
|
||||
std::pair<json*, std::string>& table = tables[i];
|
||||
|
||||
// scope for fstream
|
||||
{
|
||||
std::ifstream fstream;
|
||||
fstream.open(settings::TDATADIR + "/" + table.second); // open file
|
||||
|
||||
// did we fail to open the file?
|
||||
if (fstream.fail()) {
|
||||
// gruntwork isn't critical
|
||||
if (table.first == &gruntwork)
|
||||
continue;
|
||||
|
||||
fstream.open(settings::TDATADIR + "/" + table.second); // open file
|
||||
if (!fstream.fail()) {
|
||||
fstream >> *table.first; // load file contents into table
|
||||
} else {
|
||||
if (table.first != &gruntwork) { // gruntwork isn't critical
|
||||
std::cerr << "[FATAL] Critical tdata file missing: " << table.second << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// is the file empty?
|
||||
if (fstream.peek() == std::ifstream::traits_type::eof()) {
|
||||
// tolerate empty gruntwork file
|
||||
if (table.first == &gruntwork) {
|
||||
std::cout << "[WARN] The gruntwork file is empty" << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::cerr << "[FATAL] Critical tdata file is empty: " << table.second << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// load file contents into table
|
||||
fstream >> *table.first;
|
||||
}
|
||||
fstream.close();
|
||||
|
||||
// patching: load each patch directory specified in the config file
|
||||
|
||||
@@ -1168,11 +1109,11 @@ void TableData::init() {
|
||||
std::string patchModuleName = *it;
|
||||
std::string patchFile = settings::PATCHDIR + patchModuleName + "/" + table.second;
|
||||
try {
|
||||
std::ifstream fstream;
|
||||
fstream.open(patchFile);
|
||||
fstream >> patch; // load into temporary json object
|
||||
std::cout << "[INFO] Patching " << patchFile << std::endl;
|
||||
patchJSON(table.first, &patch); // patch
|
||||
fstream.close();
|
||||
} catch (const std::exception& err) {
|
||||
// no-op
|
||||
}
|
||||
@@ -1370,7 +1311,7 @@ void TableData::flush() {
|
||||
targetIDs.push_back(tID);
|
||||
for (int32_t tType : path.targetTypes)
|
||||
targetTypes.push_back(tType);
|
||||
|
||||
|
||||
pathObj["iBaseSpeed"] = path.speed;
|
||||
pathObj["iTaskID"] = path.escortTaskID;
|
||||
pathObj["bRelative"] = path.isRelative;
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "servers/CNShardServer.hpp"
|
||||
|
||||
#include "PlayerManager.hpp"
|
||||
#include "db/Database.hpp"
|
||||
|
||||
using namespace Trading;
|
||||
|
||||
@@ -273,8 +272,6 @@ static void tradeConfirm(CNSocket* sock, CNPacketData* data) {
|
||||
otherSock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
|
||||
return;
|
||||
}
|
||||
|
||||
Database::commitTrade(plr, plr2);
|
||||
}
|
||||
|
||||
static void tradeConfirmCancel(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
@@ -6,9 +6,6 @@
|
||||
#include "Items.hpp"
|
||||
#include "Rand.hpp"
|
||||
|
||||
// 7 days
|
||||
#define VEHICLE_EXPIRY_DURATION 604800
|
||||
|
||||
using namespace Vendors;
|
||||
|
||||
std::map<int32_t, std::vector<VendorListing>> Vendors::VendorTables;
|
||||
@@ -61,8 +58,8 @@ static void vendorBuy(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
// if vehicle
|
||||
if (req->Item.iType == 10) {
|
||||
// set time limit: current time + expiry duration
|
||||
req->Item.iTimeLimit = getTimestamp() + VEHICLE_EXPIRY_DURATION;
|
||||
// set time limit: current time + 7days
|
||||
req->Item.iTimeLimit = getTimestamp() + 604800;
|
||||
}
|
||||
|
||||
if (slot != req->iInvenSlotNum) {
|
||||
@@ -232,20 +229,12 @@ static void vendorTable(CNSocket* sock, CNPacketData* data) {
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_TABLE_UPDATE_SUCC, resp);
|
||||
|
||||
for (int i = 0; i < (int)listings.size() && i < 20; i++) { // 20 is the max
|
||||
sItemBase base = {};
|
||||
sItemBase base;
|
||||
base.iID = listings[i].id;
|
||||
base.iOpt = 0;
|
||||
base.iTimeLimit = 0;
|
||||
base.iType = listings[i].type;
|
||||
|
||||
/*
|
||||
* Set vehicle expiry value.
|
||||
*
|
||||
* Note: sItemBase.iTimeLimit in the context of vendor listings contains
|
||||
* a duration, unlike in most other contexts where it contains the
|
||||
* expiration timestamp.
|
||||
*/
|
||||
if (listings[i].type == 10)
|
||||
base.iTimeLimit = VEHICLE_EXPIRY_DURATION;
|
||||
|
||||
sItemVendor vItem;
|
||||
vItem.item = base;
|
||||
vItem.iSortNum = listings[i].sort;
|
||||
|
||||
@@ -41,8 +41,7 @@ int CNSocketEncryption::xorData(uint8_t* buffer, uint8_t* key, int size) {
|
||||
uint64_t CNSocketEncryption::createNewKey(uint64_t uTime, int32_t iv1, int32_t iv2) {
|
||||
uint64_t num = (uint64_t)(iv1 + 1);
|
||||
uint64_t num2 = (uint64_t)(iv2 + 1);
|
||||
uint64_t dEKey;
|
||||
memcpy(&dEKey, defaultKey, sizeof(dEKey));
|
||||
uint64_t dEKey = (uint64_t)(*(uint64_t*)&defaultKey[0]);
|
||||
return dEKey * (uTime * num * num2);
|
||||
}
|
||||
|
||||
@@ -66,7 +65,7 @@ CNPacketData::CNPacketData(void *b, uint32_t t, int l, int trnum, void *trs):
|
||||
// ========================================================[[ CNSocket ]]========================================================
|
||||
|
||||
CNSocket::CNSocket(SOCKET s, struct sockaddr_in &addr, PacketHandler ph): sock(s), sockaddr(addr), pHandler(ph) {
|
||||
memcpy(&EKey, CNSocketEncryption::defaultKey, sizeof(EKey));
|
||||
EKey = (uint64_t)(*(uint64_t*)&CNSocketEncryption::defaultKey[0]);
|
||||
}
|
||||
|
||||
bool CNSocket::sendData(uint8_t* data, int size) {
|
||||
@@ -110,11 +109,7 @@ bool CNSocket::isAlive() {
|
||||
}
|
||||
|
||||
void CNSocket::kill() {
|
||||
if (!alive)
|
||||
return;
|
||||
|
||||
alive = false;
|
||||
|
||||
#ifdef _WIN32
|
||||
shutdown(sock, SD_BOTH);
|
||||
closesocket(sock);
|
||||
@@ -246,10 +241,9 @@ void CNSocket::step() {
|
||||
if (readSize <= 0) {
|
||||
// we aren't reading a packet yet, try to start looking for one
|
||||
int recved = recv(sock, (buffer_t*)readBuffer, sizeof(int32_t), 0);
|
||||
if (recved >= 0 && recved < sizeof(int32_t)) {
|
||||
// too little data for readSize or the socket was closed normally (when 0 bytes were read)
|
||||
if (recved == 0) {
|
||||
// the socket was closed normally
|
||||
kill();
|
||||
return;
|
||||
} else if (!SOCKETERROR(recved)) {
|
||||
// we got our packet size!!!!
|
||||
readSize = *((int32_t*)readBuffer);
|
||||
@@ -270,12 +264,11 @@ void CNSocket::step() {
|
||||
}
|
||||
|
||||
if (readSize > 0 && readBufferIndex < readSize) {
|
||||
// read until the end of the packet (or at least try to)
|
||||
// read until the end of the packet! (or at least try too)
|
||||
int recved = recv(sock, (buffer_t*)(readBuffer + readBufferIndex), readSize - readBufferIndex, 0);
|
||||
if (recved == 0) {
|
||||
// the socket was closed normally
|
||||
kill();
|
||||
return;
|
||||
} else if (!SOCKETERROR(recved))
|
||||
readBufferIndex += recved;
|
||||
else if (OF_ERRNO != OF_EWOULD) {
|
||||
@@ -418,9 +411,9 @@ void CNServer::addPollFD(SOCKET s) {
|
||||
fds.push_back({s, POLLIN});
|
||||
}
|
||||
|
||||
void CNServer::removePollFD(int fd) {
|
||||
void CNServer::removePollFD(int i) {
|
||||
auto it = fds.begin();
|
||||
while (it != fds.end() && it->fd != fd)
|
||||
while (it != fds.end() && it->fd != fds[i].fd)
|
||||
it++;
|
||||
assert(it != fds.end());
|
||||
|
||||
@@ -465,7 +458,7 @@ void CNServer::start() {
|
||||
if (!setSockNonblocking(sock, newConnectionSocket))
|
||||
continue;
|
||||
|
||||
std::cout << "New " << serverType << " connection! " << inet_ntoa(address.sin_addr) << std::endl;
|
||||
std::cout << "New connection! " << inet_ntoa(address.sin_addr) << std::endl;
|
||||
|
||||
addPollFD(newConnectionSocket);
|
||||
|
||||
@@ -480,15 +473,10 @@ void CNServer::start() {
|
||||
} else {
|
||||
std::lock_guard<std::mutex> lock(activeCrit); // protect operations on connections
|
||||
|
||||
// halt packet handling if server is shutting down
|
||||
if (!active)
|
||||
return;
|
||||
|
||||
// player sockets
|
||||
if (connections.find(fds[i].fd) == connections.end()) {
|
||||
std::cout << "[FATAL] Event on non-existent socket: " << fds[i].fd << std::endl;
|
||||
assert(0);
|
||||
/* not reached */
|
||||
std::cout << "[WARN] Event on non-existant socket?" << std::endl;
|
||||
continue; // just to be safe
|
||||
}
|
||||
|
||||
CNSocket* cSock = connections[fds[i].fd];
|
||||
@@ -497,29 +485,22 @@ void CNServer::start() {
|
||||
if (fds[i].revents & ~POLLIN)
|
||||
cSock->kill();
|
||||
|
||||
if (cSock->isAlive())
|
||||
if (cSock->isAlive()) {
|
||||
cSock->step();
|
||||
} else {
|
||||
killConnection(cSock);
|
||||
connections.erase(fds[i].fd);
|
||||
delete cSock;
|
||||
|
||||
removePollFD(i);
|
||||
|
||||
// a new entry was moved to this position, so we check it again
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onStep();
|
||||
|
||||
// clean up dead connection sockets
|
||||
auto it = connections.begin();
|
||||
while (it != connections.end()) {
|
||||
CNSocket *cSock = it->second;
|
||||
|
||||
if (!cSock->isAlive()) {
|
||||
killConnection(cSock);
|
||||
it = connections.erase(it);
|
||||
|
||||
removePollFD(cSock->sock);
|
||||
|
||||
delete cSock;
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -230,7 +230,6 @@ protected:
|
||||
const size_t STARTFDSCOUNT = 8; // number of initial PollFD slots
|
||||
std::vector<PollFD> fds;
|
||||
|
||||
std::string serverType = "invalid";
|
||||
SOCKET sock;
|
||||
uint16_t port;
|
||||
socklen_t addressSize;
|
||||
|
||||
@@ -32,7 +32,7 @@ void CNShared::pruneLoginMetadata(CNServer *serv, time_t currTime) {
|
||||
auto& sk = it->first;
|
||||
auto& lm = it->second;
|
||||
|
||||
if (currTime > lm->timestamp + CNSHARED_TIMEOUT) {
|
||||
if (lm->timestamp + CNSHARED_TIMEOUT > currTime) {
|
||||
std::cout << "[WARN] Pruning hung connection attempt" << std::endl;
|
||||
|
||||
// deallocate object and remove map entry
|
||||
|
||||
@@ -11,14 +11,14 @@
|
||||
#include "Player.hpp"
|
||||
|
||||
/*
|
||||
* Connecions time out after 5 minutes, checked every 30 seconds.
|
||||
* Connecions time out after 15 minutes, checked every 30 seconds.
|
||||
*/
|
||||
#define CNSHARED_TIMEOUT 300000
|
||||
#define CNSHARED_TIMEOUT 900000
|
||||
#define CNSHARED_PERIOD 30000
|
||||
|
||||
struct LoginMetadata {
|
||||
uint64_t FEKey;
|
||||
int32_t playerId;
|
||||
Player plr;
|
||||
time_t timestamp;
|
||||
};
|
||||
|
||||
|
||||
@@ -46,8 +46,49 @@ enum class ePCRegenType {
|
||||
End
|
||||
};
|
||||
|
||||
// nano power flags
|
||||
// nano powers
|
||||
enum {
|
||||
EST_NONE = 0,
|
||||
EST_DAMAGE = 1,
|
||||
EST_HEAL_HP = 2,
|
||||
EST_KNOCKDOWN = 3,
|
||||
EST_SLEEP = 4,
|
||||
EST_SNARE = 5,
|
||||
EST_HEAL_STAMINA = 6,
|
||||
EST_STAMINA_SELF = 7,
|
||||
EST_STUN = 8,
|
||||
EST_WEAPONSLOW = 9,
|
||||
EST_JUMP = 10,
|
||||
EST_RUN = 11,
|
||||
EST_STEALTH = 12,
|
||||
EST_SWIM = 13,
|
||||
EST_MINIMAPENEMY = 14,
|
||||
EST_MINIMAPTRESURE = 15,
|
||||
EST_PHOENIX = 16,
|
||||
EST_PROTECTBATTERY = 17,
|
||||
EST_PROTECTINFECTION = 18,
|
||||
EST_REWARDBLOB = 19,
|
||||
EST_REWARDCASH = 20,
|
||||
EST_BATTERYDRAIN = 21,
|
||||
EST_CORRUPTIONATTACK = 22,
|
||||
EST_INFECTIONDAMAGE = 23,
|
||||
EST_KNOCKBACK = 24,
|
||||
EST_FREEDOM = 25,
|
||||
EST_PHOENIX_GROUP = 26,
|
||||
EST_RECALL = 27,
|
||||
EST_RECALL_GROUP = 28,
|
||||
EST_RETROROCKET_SELF = 29,
|
||||
EST_BLOODSUCKING = 30,
|
||||
EST_BOUNDINGBALL = 31,
|
||||
EST_INVULNERABLE = 32,
|
||||
EST_NANOSTIMPAK = 33,
|
||||
EST_RETURNHOMEHEAL = 34,
|
||||
EST_BUFFHEAL = 35,
|
||||
EST_EXTRABANK = 36,
|
||||
EST__END = 37,
|
||||
EST_CORRUPTIONATTACKWIN = 38,
|
||||
EST_CORRUPTIONATTACKLOSE = 39,
|
||||
|
||||
ECSB_NONE = 0,
|
||||
ECSB_UP_MOVE_SPEED = 1,
|
||||
ECSB_UP_SWIM_SPEED = 2,
|
||||
|
||||
@@ -41,7 +41,6 @@ namespace Database {
|
||||
uint64_t Timestamp;
|
||||
};
|
||||
|
||||
void init();
|
||||
void open();
|
||||
void close();
|
||||
|
||||
@@ -53,8 +52,7 @@ namespace Database {
|
||||
bool banPlayer(int playerId, std::string& reason);
|
||||
bool unbanPlayer(int playerId);
|
||||
|
||||
void updateSelected(int accountId, int slot);
|
||||
void updateSelectedByPlayerId(int accountId, int playerId);
|
||||
void updateSelected(int accountId, int playerId);
|
||||
|
||||
bool validateCharacter(int characterID, int userID);
|
||||
bool isNameFree(std::string firstName, std::string lastName);
|
||||
@@ -80,9 +78,7 @@ namespace Database {
|
||||
|
||||
// getting players
|
||||
void getPlayer(Player* plr, int id);
|
||||
bool _updatePlayer(Player *player);
|
||||
void updatePlayer(Player *player);
|
||||
void commitTrade(Player *plr1, Player *plr2);
|
||||
|
||||
// buddies
|
||||
int getNumBuddies(Player* player);
|
||||
@@ -102,7 +98,7 @@ namespace Database {
|
||||
void deleteEmailAttachments(int playerID, int index, int slot);
|
||||
void deleteEmails(int playerID, int64_t* indices);
|
||||
int getNextEmailIndex(int playerID);
|
||||
bool sendEmail(EmailData* data, std::vector<sItemBase> attachments, Player *sender);
|
||||
bool sendEmail(EmailData* data, std::vector<sItemBase> attachments);
|
||||
|
||||
// racing
|
||||
RaceRanking getTopRaceRanking(int epID, int playerID);
|
||||
|
||||
@@ -152,8 +152,7 @@ void Database::updateEmailContent(EmailData* data) {
|
||||
sqlite3_step(stmt);
|
||||
int attachmentsCount = sqlite3_column_int(stmt, 0);
|
||||
|
||||
// set attachment flag dynamically
|
||||
data->ItemFlag = (data->Taros > 0 || attachmentsCount > 0) ? 1 : 0;
|
||||
data->ItemFlag = (data->Taros > 0 || attachmentsCount > 0) ? 1 : 0; // set attachment flag dynamically
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
@@ -266,7 +265,7 @@ int Database::getNextEmailIndex(int playerID) {
|
||||
return (index > 0 ? index + 1 : 1);
|
||||
}
|
||||
|
||||
bool Database::sendEmail(EmailData* data, std::vector<sItemBase> attachments, Player *sender) {
|
||||
bool Database::sendEmail(EmailData* data, std::vector<sItemBase> attachments) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
|
||||
@@ -331,13 +330,6 @@ bool Database::sendEmail(EmailData* data, std::vector<sItemBase> attachments, Pl
|
||||
sqlite3_reset(stmt);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
if (!_updatePlayer(sender)) {
|
||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -236,20 +236,7 @@ static int getTableSize(std::string tableName) {
|
||||
return result;
|
||||
}
|
||||
|
||||
void Database::init() {
|
||||
std::cout << "[INFO] Built with libsqlite " SQLITE_VERSION << std::endl;
|
||||
|
||||
if (sqlite3_libversion_number() != SQLITE_VERSION_NUMBER)
|
||||
std::cout << "[INFO] Using libsqlite " << std::string(sqlite3_libversion()) << std::endl;
|
||||
|
||||
if (sqlite3_libversion_number() < MIN_SUPPORTED_SQLITE_NUMBER) {
|
||||
std::cerr << "[FATAL] Runtime sqlite version too old. Minimum compatible version: " MIN_SUPPORTED_SQLITE << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void Database::open() {
|
||||
|
||||
// XXX: move locks here
|
||||
int rc = sqlite3_open(settings::DBPATH.c_str(), &db);
|
||||
if (rc != SQLITE_OK) {
|
||||
|
||||
@@ -3,15 +3,6 @@
|
||||
#include "db/Database.hpp"
|
||||
#include <sqlite3.h>
|
||||
|
||||
#define MIN_SUPPORTED_SQLITE_NUMBER 3033000
|
||||
#define MIN_SUPPORTED_SQLITE "3.33.0"
|
||||
// we can't use this in #error, since it doesn't expand macros
|
||||
|
||||
// Compile-time libsqlite version check
|
||||
#if SQLITE_VERSION_NUMBER < MIN_SUPPORTED_SQLITE_NUMBER
|
||||
#error libsqlite version too old. Minimum compatible version: 3.33.0
|
||||
#endif
|
||||
|
||||
extern std::mutex dbCrit;
|
||||
extern sqlite3 *db;
|
||||
|
||||
|
||||
@@ -79,29 +79,6 @@ void Database::updateSelected(int accountId, int slot) {
|
||||
std::cout << "[WARN] Database fail on updateSelected(): " << sqlite3_errmsg(db) << std::endl;
|
||||
}
|
||||
|
||||
void Database::updateSelectedByPlayerId(int accountId, int32_t playerId) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
const char* sql = R"(
|
||||
UPDATE Accounts SET
|
||||
Selected = p.Slot,
|
||||
LastLogin = (strftime('%s', 'now'))
|
||||
FROM (SELECT Slot From Players WHERE PlayerId = ?) AS p
|
||||
WHERE AccountID = ?;
|
||||
)";
|
||||
|
||||
sqlite3_stmt* stmt;
|
||||
|
||||
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
||||
sqlite3_bind_int(stmt, 1, playerId);
|
||||
sqlite3_bind_int(stmt, 2, accountId);
|
||||
int rc = sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
if (rc != SQLITE_DONE)
|
||||
std::cout << "[WARN] Database fail on updateSelectedByPlayerId(): " << sqlite3_errmsg(db) << std::endl;
|
||||
}
|
||||
|
||||
bool Database::validateCharacter(int characterID, int userID) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
|
||||
@@ -285,13 +285,11 @@ void Database::getPlayer(Player* plr, int id) {
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
|
||||
/*
|
||||
* Low-level function to save a player to DB.
|
||||
* Must be run in a SQL transaction and with dbCrit locked.
|
||||
* The caller manages the transacstion, so if this function returns false,
|
||||
* the caller must roll it back.
|
||||
*/
|
||||
bool Database::_updatePlayer(Player *player) {
|
||||
void Database::updatePlayer(Player *player) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
|
||||
|
||||
const char* sql = R"(
|
||||
UPDATE Players
|
||||
SET
|
||||
@@ -338,8 +336,10 @@ bool Database::_updatePlayer(Player *player) {
|
||||
sqlite3_bind_int(stmt, 21, player->iID);
|
||||
|
||||
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
||||
sqlite3_finalize(stmt);
|
||||
return false;
|
||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
@@ -375,8 +375,10 @@ bool Database::_updatePlayer(Player *player) {
|
||||
rc = sqlite3_step(stmt);
|
||||
|
||||
if (rc != SQLITE_DONE) {
|
||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
||||
sqlite3_finalize(stmt);
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
sqlite3_reset(stmt);
|
||||
}
|
||||
@@ -393,8 +395,10 @@ bool Database::_updatePlayer(Player *player) {
|
||||
sqlite3_bind_int(stmt, 6, player->Inven[i].iTimeLimit);
|
||||
|
||||
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
||||
sqlite3_finalize(stmt);
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
sqlite3_reset(stmt);
|
||||
}
|
||||
@@ -411,8 +415,10 @@ bool Database::_updatePlayer(Player *player) {
|
||||
sqlite3_bind_int(stmt, 6, player->Bank[i].iTimeLimit);
|
||||
|
||||
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
||||
sqlite3_finalize(stmt);
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
sqlite3_reset(stmt);
|
||||
}
|
||||
@@ -445,8 +451,10 @@ bool Database::_updatePlayer(Player *player) {
|
||||
sqlite3_bind_int(stmt, 4, player->QInven[i].iID);
|
||||
|
||||
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
||||
sqlite3_finalize(stmt);
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
sqlite3_reset(stmt);
|
||||
}
|
||||
@@ -479,8 +487,10 @@ bool Database::_updatePlayer(Player *player) {
|
||||
sqlite3_bind_int(stmt, 4, player->Nanos[i].iStamina);
|
||||
|
||||
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
||||
sqlite3_finalize(stmt);
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
sqlite3_reset(stmt);
|
||||
}
|
||||
@@ -514,47 +524,14 @@ bool Database::_updatePlayer(Player *player) {
|
||||
sqlite3_bind_int(stmt, 5, player->RemainingNPCCount[i][2]);
|
||||
|
||||
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
||||
sqlite3_finalize(stmt);
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
sqlite3_reset(stmt);
|
||||
}
|
||||
|
||||
sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Database::updatePlayer(Player *player) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
|
||||
|
||||
if (!_updatePlayer(player)) {
|
||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
void Database::commitTrade(Player *plr1, Player *plr2) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
|
||||
|
||||
if (!_updatePlayer(plr1)) {
|
||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_updatePlayer(plr2)) {
|
||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
#include "lua/Manager.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using namespace LuaManager;
|
||||
|
||||
static lua_State *globalState = nullptr;
|
||||
|
||||
void LuaManager::init() {
|
||||
lua_State *L = luaL_newstate();
|
||||
luaL_openlibs(L);
|
||||
|
||||
static LuaThread *startScript(const char *script) {
|
||||
lua_State *L = lua_newthread(globalState);
|
||||
LuaThread *T = new LuaThread(L, luaL_ref(L, LUA_REGISTRYINDEX));
|
||||
|
||||
if (luaL_dostring(L, script)) {
|
||||
std::cout << "Lua error: " << lua_tostring(L, -1) << std::endl;
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
return T;
|
||||
}
|
||||
|
||||
void LuaManager::init() {
|
||||
globalState = luaL_newstate();
|
||||
luaL_openlibs(globalState);
|
||||
|
||||
delete startScript("print(\"Hello from Lua!\")");
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "lua/Thread.hpp"
|
||||
|
||||
namespace LuaManager {
|
||||
void init();
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
extern "C" {
|
||||
#include "lua.h"
|
||||
#include "lualib.h"
|
||||
#include "lauxlib.h"
|
||||
}
|
||||
|
||||
struct LuaThread {
|
||||
lua_State *L;
|
||||
int ref;
|
||||
|
||||
LuaThread(lua_State *L, int ref) : L(L), ref(ref) {}
|
||||
~LuaThread() {
|
||||
luaL_unref(L, LUA_REGISTRYINDEX, ref);
|
||||
}
|
||||
};
|
||||
19
src/main.cpp
19
src/main.cpp
@@ -24,8 +24,6 @@
|
||||
#include "Eggs.hpp"
|
||||
#include "Rand.hpp"
|
||||
|
||||
#include "lua/Manager.hpp"
|
||||
|
||||
#include "settings.hpp"
|
||||
#include "sandbox/Sandbox.hpp"
|
||||
|
||||
@@ -66,7 +64,7 @@ void terminate(int arg) {
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
static BOOL WINAPI winTerminate(DWORD arg) {
|
||||
static BOOL winTerminate(DWORD arg) {
|
||||
terminate(0);
|
||||
return FALSE;
|
||||
}
|
||||
@@ -100,9 +98,6 @@ void initsignals() {
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::cout << "[INFO] OpenFusion v" GIT_VERSION << std::endl;
|
||||
std::cout << "[INFO] Protocol version: " << PROTOCOL_VERSION << std::endl;
|
||||
|
||||
#ifdef _WIN32
|
||||
WSADATA wsaData;
|
||||
if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0) {
|
||||
@@ -110,15 +105,15 @@ int main() {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
#endif
|
||||
|
||||
initsignals();
|
||||
settings::init();
|
||||
Database::init();
|
||||
Rand::init(getTime());
|
||||
TableData::init();
|
||||
|
||||
std::cout << "[INFO] OpenFusion v" GIT_VERSION << std::endl;
|
||||
std::cout << "[INFO] Protocol version: " << PROTOCOL_VERSION << std::endl;
|
||||
std::cout << "[INFO] Intializing Packet Managers..." << std::endl;
|
||||
|
||||
Rand::init(getTime());
|
||||
TableData::init();
|
||||
PlayerManager::init();
|
||||
PlayerMovement::init();
|
||||
BuiltinCommands::init();
|
||||
@@ -137,10 +132,8 @@ int main() {
|
||||
Email::init();
|
||||
Groups::init();
|
||||
Racing::init();
|
||||
Trading::init();
|
||||
|
||||
Database::open();
|
||||
LuaManager::init();
|
||||
Trading::init();
|
||||
|
||||
switch (settings::EVENTMODE) {
|
||||
case 0: break; // no event
|
||||
|
||||
@@ -153,18 +153,15 @@ static sock_filter filter[] = {
|
||||
ALLOW_SYSCALL(read),
|
||||
ALLOW_SYSCALL(write),
|
||||
ALLOW_SYSCALL(close),
|
||||
#ifdef __NR_stat
|
||||
#if __NR_stat
|
||||
ALLOW_SYSCALL(stat),
|
||||
#endif
|
||||
ALLOW_SYSCALL(fstat),
|
||||
#ifdef __NR_newfstatat
|
||||
ALLOW_SYSCALL(newfstatat),
|
||||
#endif
|
||||
ALLOW_SYSCALL(fsync), // maybe
|
||||
#ifdef __NR_creat
|
||||
#if __NR_creat
|
||||
ALLOW_SYSCALL(creat), // maybe; for DB journal
|
||||
#endif
|
||||
#ifdef __NR_unlink
|
||||
#if __NR_unlink
|
||||
ALLOW_SYSCALL(unlink), // for DB journal
|
||||
#endif
|
||||
ALLOW_SYSCALL(lseek), // musl-libc; alt DB
|
||||
@@ -195,9 +192,6 @@ static sock_filter filter[] = {
|
||||
ALLOW_SYSCALL(exit_group),
|
||||
ALLOW_SYSCALL(rt_sigprocmask), // musl-libc
|
||||
ALLOW_SYSCALL(clock_nanosleep), // gets called very rarely
|
||||
#ifdef __NR_rseq
|
||||
ALLOW_SYSCALL(rseq),
|
||||
#endif
|
||||
|
||||
// to crash properly on SIGSEGV
|
||||
DENY_SYSCALL_ERRNO(tgkill, EPERM),
|
||||
@@ -280,7 +274,7 @@ static sock_filter filter[] = {
|
||||
#endif
|
||||
|
||||
// AArch64 (ARM64)
|
||||
#ifdef __NR_unlinkat
|
||||
#if __NR_unlinkat
|
||||
ALLOW_SYSCALL(unlinkat),
|
||||
#endif
|
||||
#ifdef __NR_fstatat64
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
std::map<CNSocket*, CNLoginData> CNLoginServer::loginSessions;
|
||||
|
||||
CNLoginServer::CNLoginServer(uint16_t p) {
|
||||
serverType = "login";
|
||||
port = p;
|
||||
pHandler = &CNLoginServer::handlePacket;
|
||||
init();
|
||||
@@ -211,12 +210,9 @@ void CNLoginServer::login(CNSocket* sock, CNPacketData* data) {
|
||||
// send the resp in with original key
|
||||
sock->sendPacket(resp, P_LS2CL_REP_LOGIN_SUCC);
|
||||
|
||||
uint64_t defaultKey;
|
||||
memcpy(&defaultKey, CNSocketEncryption::defaultKey, sizeof(defaultKey));
|
||||
|
||||
// update keys
|
||||
sock->setEKey(CNSocketEncryption::createNewKey(resp.uiSvrTime, resp.iCharCount + 1, resp.iSlotNum + 1));
|
||||
sock->setFEKey(CNSocketEncryption::createNewKey(defaultKey, login->iClientVerC, 1));
|
||||
sock->setFEKey(CNSocketEncryption::createNewKey((uint64_t)(*(uint64_t*)&CNSocketEncryption::defaultKey[0]), login->iClientVerC, 1));
|
||||
|
||||
DEBUGLOG(
|
||||
std::cout << "Login Server: Login success. Welcome " << userLogin << " [" << loginSessions[sock].userID << "]" << std::endl;
|
||||
@@ -477,7 +473,11 @@ void CNLoginServer::characterSelect(CNSocket* sock, CNPacketData* data) {
|
||||
LoginMetadata *lm = new LoginMetadata();
|
||||
lm->FEKey = sock->getFEKey();
|
||||
lm->timestamp = getTime();
|
||||
lm->playerId = selection->iPC_UID;
|
||||
|
||||
Database::getPlayer(&lm->plr, selection->iPC_UID);
|
||||
// this should never happen but for extra safety
|
||||
if (lm->plr.iID == 0)
|
||||
return invalidCharacter(sock);
|
||||
|
||||
resp.iEnterSerialKey = Rand::cryptoRand();
|
||||
|
||||
@@ -487,7 +487,7 @@ void CNLoginServer::characterSelect(CNSocket* sock, CNPacketData* data) {
|
||||
sock->sendPacket(resp, P_LS2CL_REP_SHARD_SELECT_SUCC);
|
||||
|
||||
// update current slot in DB
|
||||
Database::updateSelectedByPlayerId(loginSessions[sock].userID, selection->iPC_UID);
|
||||
Database::updateSelected(loginSessions[sock].userID, lm->plr.slot);
|
||||
}
|
||||
|
||||
void CNLoginServer::finishTutorial(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
@@ -18,7 +18,6 @@ std::map<uint32_t, PacketHandler> CNShardServer::ShardPackets;
|
||||
std::list<TimerEvent> CNShardServer::Timers;
|
||||
|
||||
CNShardServer::CNShardServer(uint16_t p) {
|
||||
serverType = "shard";
|
||||
port = p;
|
||||
pHandler = &CNShardServer::handlePacket;
|
||||
REGISTER_SHARD_TIMER(keepAliveTimer, 4000);
|
||||
@@ -118,10 +117,6 @@ void CNShardServer::kill() {
|
||||
void CNShardServer::onStep() {
|
||||
time_t currTime = getTime();
|
||||
|
||||
// do not evaluate timers if the server is shutting down
|
||||
if (!active)
|
||||
return;
|
||||
|
||||
for (TimerEvent& event : Timers) {
|
||||
if (event.scheduledEvent == 0) {
|
||||
// event hasn't been queued yet, go ahead and do that
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#define REGISTER_SHARD_PACKET(pactype, handlr) CNShardServer::ShardPackets[pactype] = handlr;
|
||||
#define REGISTER_SHARD_TIMER(handlr, delta) CNShardServer::Timers.push_back(TimerEvent(handlr, delta));
|
||||
#define MS_PER_PLAYER_TICK 500
|
||||
#define MS_PER_COMBAT_TICK 200
|
||||
|
||||
class CNShardServer : public CNServer {
|
||||
private:
|
||||
|
||||
@@ -21,7 +21,6 @@ bool settings::LOCALHOSTWORKAROUND = true;
|
||||
time_t settings::TIMEOUT = 60000;
|
||||
int settings::VIEWDISTANCE = 25600;
|
||||
bool settings::SIMULATEMOBS = true;
|
||||
bool settings::ANTICHEAT = true;
|
||||
|
||||
// default spawn point
|
||||
#ifndef ACADEMY
|
||||
@@ -110,7 +109,6 @@ void settings::init() {
|
||||
ACCLEVEL = reader.GetInteger("shard", "accountlevel", ACCLEVEL);
|
||||
EVENTMODE = reader.GetInteger("shard", "eventmode", EVENTMODE);
|
||||
DISABLEFIRSTUSEFLAG = reader.GetBoolean("shard", "disablefirstuseflag", DISABLEFIRSTUSEFLAG);
|
||||
ANTICHEAT = reader.GetBoolean("shard", "anticheat", ANTICHEAT);
|
||||
MONITORENABLED = reader.GetBoolean("monitor", "enabled", MONITORENABLED);
|
||||
MONITORPORT = reader.GetInteger("monitor", "port", MONITORPORT);
|
||||
MONITORINTERVAL = reader.GetInteger("monitor", "interval", MONITORINTERVAL);
|
||||
|
||||
@@ -12,7 +12,6 @@ namespace settings {
|
||||
extern int SHARDPORT;
|
||||
extern std::string SHARDSERVERIP;
|
||||
extern bool LOCALHOSTWORKAROUND;
|
||||
extern bool ANTICHEAT;
|
||||
extern time_t TIMEOUT;
|
||||
extern int VIEWDISTANCE;
|
||||
extern bool SIMULATEMOBS;
|
||||
|
||||
2
tdata
2
tdata
Submodule tdata updated: 8c98c83682...8230fb8649
1
vendor/Lua
vendored
1
vendor/Lua
vendored
Submodule vendor/Lua deleted from 88246d621a
Reference in New Issue
Block a user