mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2025-12-20 19:20:05 +00:00
Compare commits
68 Commits
refactor
...
e124bc0645
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e124bc0645 | ||
|
|
5671adbd74 | ||
|
|
550b26db39 | ||
|
|
68be9cc381 | ||
|
|
ef33a182d1 | ||
|
|
9bd8eabed7 | ||
|
|
2249b5381b | ||
|
|
9b7656117d | ||
|
|
9394825d41 | ||
|
|
6e0d55c4b7 | ||
|
|
33396f8d28 | ||
|
|
cd908666af | ||
|
|
74588f2c77 | ||
|
|
829f75112c | ||
|
|
343668fbcd | ||
|
|
2e572169c0 | ||
|
|
b3e28ddea3 | ||
|
|
1670dfd830 | ||
|
|
e768ebcabe | ||
|
|
9bc7e8de62 | ||
|
|
f249599ab5 | ||
|
|
2901f5f285 | ||
|
|
36f329c302 | ||
|
|
eb7daf8eaa | ||
|
|
0c5a9400ce | ||
|
|
f150595f70 | ||
|
|
c6528eb2ac | ||
|
|
d631ca1aa1 | ||
|
|
a94fb0ed6d | ||
|
|
d48aa21135 | ||
|
|
c60c4dac38 | ||
|
|
215da0130d | ||
|
|
0e8a4742eb | ||
|
|
85bb4d163e | ||
|
|
09b74a5711 | ||
|
|
90819bea8e | ||
|
|
3cc5c09a91 | ||
|
|
536d5fbcfa | ||
|
|
4ec3a3acb7 | ||
|
|
89ed0b99a3 | ||
|
|
db73b85bc8 | ||
|
|
6cab203401 | ||
|
|
92846e0eac | ||
|
|
5963ea06be | ||
|
|
c28970f2e1 | ||
|
|
8be853c2dc | ||
|
|
a811e73fed | ||
|
|
a58971c270 | ||
|
|
07429a0e51 | ||
|
|
7c9038cf10 | ||
|
|
c1e391d86a | ||
|
|
e23af08838 | ||
|
|
8a26ae2f01 | ||
|
|
d17694e12e | ||
|
|
4b612f35d2 | ||
|
|
d9e0a4a281 | ||
|
|
dd9891f668 | ||
|
|
962141e54f | ||
| bea41132b4 | |||
| f305d7252d | |||
| ff62129eec | |||
| 55f3ab8bad | |||
|
|
a732cde117 | ||
|
|
703eaff2b4 | ||
|
|
f4f5f2e0bd | ||
|
|
1858938280 | ||
|
|
0af8f7e91d | ||
|
|
168a85e8ff |
@@ -1 +0,0 @@
|
|||||||
version.h
|
|
||||||
@@ -26,7 +26,7 @@ Both are pretty short reads and following them will get you up to speed with bra
|
|||||||
|
|
||||||
I will now cover a few examples of the complications people have encountered contributing to this project, how to understand them, overcome them and henceforth avoid them.
|
I will now cover a few examples of the complications people have encountered contributing to this project, how to understand them, overcome them and henceforth avoid them.
|
||||||
|
|
||||||
### Dirty pull requests
|
## Dirty pull requests
|
||||||
|
|
||||||
Many Pull Requests OpenFusion receives fail to present a clean set of commits to merge.
|
Many Pull Requests OpenFusion receives fail to present a clean set of commits to merge.
|
||||||
These are generally either:
|
These are generally either:
|
||||||
@@ -44,7 +44,7 @@ If you read the above links, you'll note that this isn't exactly a perfect solut
|
|||||||
The obvious issue, then, is that the people submitting dirty PRs are the exact people who don't *know* how to rebase their fork to continue submitting their work cleanly.
|
The obvious issue, then, is that the people submitting dirty PRs are the exact people who don't *know* how to rebase their fork to continue submitting their work cleanly.
|
||||||
So they end up creating countless merge commits when pulling upstream on top of their own incompatible histories, and then submitting those merge commits in their PRs and the cycle continues.
|
So they end up creating countless merge commits when pulling upstream on top of their own incompatible histories, and then submitting those merge commits in their PRs and the cycle continues.
|
||||||
|
|
||||||
### The details
|
## The details
|
||||||
|
|
||||||
A git commit is uniquely identified by its SHA1 hash.
|
A git commit is uniquely identified by its SHA1 hash.
|
||||||
Its hash is generated from its contents, as well as the hash(es) of its parent commit(s).
|
Its hash is generated from its contents, as well as the hash(es) of its parent commit(s).
|
||||||
@@ -52,7 +52,7 @@ Its hash is generated from its contents, as well as the hash(es) of its parent c
|
|||||||
That means that even if two commits are exactly the same in terms of content, they're not the same commit if their parent commits differ (ex. if they've been rebased).
|
That means that even if two commits are exactly the same in terms of content, they're not the same commit if their parent commits differ (ex. if they've been rebased).
|
||||||
So if you keep issuing `git pull`s after upstream has merged a rebased version of your PR, you will never re-synchronize with it, and will instead construct an alternate history polluted by pointless merge commits.
|
So if you keep issuing `git pull`s after upstream has merged a rebased version of your PR, you will never re-synchronize with it, and will instead construct an alternate history polluted by pointless merge commits.
|
||||||
|
|
||||||
### The solution
|
## The solution
|
||||||
|
|
||||||
If you already have a messed-up fork and you have no changes on it that you're afraid to lose, the solution is simple:
|
If you already have a messed-up fork and you have no changes on it that you're afraid to lose, the solution is simple:
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ If you do have some committed changes that haven't yet been merged upstream, you
|
|||||||
If you do end up messing something up, don't worry, it most likely isn't really lost and `git reflog` is your friend.
|
If you do end up messing something up, don't worry, it most likely isn't really lost and `git reflog` is your friend.
|
||||||
(You can checkout an arbitrary commit, and make it into its own branch with `git checkout -b BRANCH` or set a pre-exisitng branch to it with `git reset --hard COMMIT`)
|
(You can checkout an arbitrary commit, and make it into its own branch with `git checkout -b BRANCH` or set a pre-exisitng branch to it with `git reset --hard COMMIT`)
|
||||||
|
|
||||||
### Avoiding the problem
|
## Avoiding the problem
|
||||||
|
|
||||||
When working on a changeset you want to submit back upstream, don't do it on the main branch.
|
When working on a changeset you want to submit back upstream, don't do it on the main branch.
|
||||||
Create a work branch just for your changeset with `git checkout -b work`.
|
Create a work branch just for your changeset with `git checkout -b work`.
|
||||||
@@ -81,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`.
|
Creating new branches for the rebase isn't strictly necessary since you can always return a branch to its previous state with `git reflog`.
|
||||||
|
|
||||||
For moving uncommited changes around between branches, `git stash` is a real blessing.
|
For moving uncommited changes around between branches, `git stash` is a real blessing.
|
||||||
|
|
||||||
## Code guidelines
|
|
||||||
|
|
||||||
Alright, you're up to speed on Git and ready to go. Here are a few specific code guidelines to try and follow:
|
|
||||||
|
|
||||||
### Match the styling
|
|
||||||
|
|
||||||
Pretty straightforward, make sure your code looks similar to the code around it. Match whitespacing, bracket styling, variable naming conventions, etc.
|
|
||||||
|
|
||||||
### Prefer short-circuiting
|
|
||||||
|
|
||||||
To minimize branching complexity (as this makes the code hard to read), we prefer to keep the number of `if-else` statements as low as possible. One easy way to achieve this is by doing an early return after branching into an `if` block and then writing the code for the other path outside the block entirely. You can find examples of this in practically every source file. Note that in a few select situations, this might actually make your code less elegant, which is why this isn't a strict rule. Lean towards short-circuiting and use your better judgement.
|
|
||||||
|
|
||||||
### Follow the include convention
|
|
||||||
|
|
||||||
This one matters a lot as it can cause cyclic dependencies and other code-breaking issues.
|
|
||||||
|
|
||||||
FOR HEADER FILES (.hpp):
|
|
||||||
- everything you use IN THE HEADER must be EXPLICITLY INCLUDED with the exception of things that fall under Core.hpp
|
|
||||||
- you may NOT include ANYTHING ELSE
|
|
||||||
|
|
||||||
FOR SOURCE FILES (.cpp):
|
|
||||||
- you can #include whatever you want as long as the partner header is included first
|
|
||||||
- anything that gets included by another include is fair game
|
|
||||||
- redundant includes are ok because they'll be harmless AS LONG AS our header files stay lean.
|
|
||||||
|
|
||||||
The point of this is NOT to optimize the number of includes used all around or make things more efficient necessarily. it's to improve readability & coherence and make it easier to avoid cyclical issues.
|
|
||||||
|
|
||||||
## When in doubt, ask
|
|
||||||
|
|
||||||
If you still have questions that were not answered here, feel free to ping a dev in the Discord server or on GitHub.
|
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
#include "Abilities.hpp"
|
#include "Abilities.hpp"
|
||||||
|
|
||||||
#include "servers/CNShardServer.hpp"
|
|
||||||
|
|
||||||
#include "NPCManager.hpp"
|
#include "NPCManager.hpp"
|
||||||
#include "PlayerManager.hpp"
|
#include "PlayerManager.hpp"
|
||||||
#include "Buffs.hpp"
|
#include "Buffs.hpp"
|
||||||
#include "Nanos.hpp"
|
#include "Nanos.hpp"
|
||||||
#include "MobAI.hpp"
|
|
||||||
|
|
||||||
using namespace Abilities;
|
using namespace Abilities;
|
||||||
|
|
||||||
@@ -48,106 +45,37 @@ static SkillResult handleSkillHealHP(SkillData* skill, int power, ICombatant* so
|
|||||||
}
|
}
|
||||||
|
|
||||||
static SkillResult handleSkillDamageNDebuff(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
static SkillResult handleSkillDamageNDebuff(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
||||||
// take aggro
|
// TODO abilities
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
sSkillResult_Damage_N_Debuff result{};
|
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.eCT = target->getCharType();
|
||||||
result.iID = target->getID();
|
result.iID = target->getID();
|
||||||
result.bProtected = blocked;
|
result.bProtected = false;
|
||||||
result.iConditionBitFlag = target->getCompositeCondition();
|
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);
|
return SkillResult(sizeof(sSkillResult_Damage_N_Debuff), &result);
|
||||||
}
|
}
|
||||||
|
|
||||||
static SkillResult handleSkillLeech(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
static SkillResult handleSkillLeech(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
||||||
EntityRef sourceRef = source->getRef();
|
// TODO abilities
|
||||||
int heal = skill->values[0][power];
|
return SkillResult();
|
||||||
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) {
|
static SkillResult handleSkillBuff(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
||||||
int duration = skill->durationTime[power];
|
|
||||||
int strength = skill->values[0][power];
|
|
||||||
BuffStack passiveBuff = {
|
BuffStack passiveBuff = {
|
||||||
// if the duration is 0, it needs to be recast every tick
|
skill->drainType == SkillDrainType::PASSIVE ? 1 : skill->durationTime[power], // ticks
|
||||||
duration == 0 ? 1 : (duration * 100) / MS_PER_COMBAT_TICK, // ticks
|
skill->values[0][power], // value
|
||||||
strength, // value
|
|
||||||
source->getRef(), // source
|
source->getRef(), // source
|
||||||
source == target ? BuffClass::NANO : BuffClass::GROUP_NANO, // buff class
|
source == target ? BuffClass::NANO : BuffClass::GROUP_NANO, // buff class
|
||||||
};
|
};
|
||||||
|
|
||||||
int timeBuffId = Abilities::getCSTBFromST(skill->skillType);
|
int timeBuffId = Abilities::getCSTBFromST(skill->skillType);
|
||||||
SkillDrainType drainType = skill->drainType;
|
|
||||||
int combatLifetime = 0;
|
|
||||||
if(!target->addBuff(timeBuffId,
|
if(!target->addBuff(timeBuffId,
|
||||||
[drainType](EntityRef self, Buff* buff, int status, BuffStack* stack) {
|
[](EntityRef self, Buff* buff, int status, BuffStack* stack) {
|
||||||
if(buff->id == ECSB_BOUNDINGBALL && status == ETBU_ADD) {
|
Buffs::timeBuffUpdate(self, buff, status, stack);
|
||||||
// drain
|
},
|
||||||
ICombatant* combatant = dynamic_cast<ICombatant*>(self.getEntity());
|
[](EntityRef self, Buff* buff, time_t currTime) {
|
||||||
combatant->takeDamage(buff->getLastSource(), 0); // aggro
|
// no-op
|
||||||
}
|
},
|
||||||
Buffs::timeBuffUpdate(self, buff, status, stack);
|
&passiveBuff)) return SkillResult(); // no result if already buffed
|
||||||
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{};
|
sSkillResult_Buff result{};
|
||||||
result.eCT = target->getCharType();
|
result.eCT = target->getCharType();
|
||||||
@@ -163,24 +91,19 @@ static SkillResult handleSkillBatteryDrain(SkillData* skill, int power, ICombata
|
|||||||
Player* plr = dynamic_cast<Player*>(target);
|
Player* plr = dynamic_cast<Player*>(target);
|
||||||
|
|
||||||
const double scalingFactor = (18 + source->getLevel()) / 36.0;
|
const double scalingFactor = (18 + source->getLevel()) / 36.0;
|
||||||
const bool blocked = target->hasBuff(ECSB_PROTECT_BATTERY);
|
|
||||||
|
|
||||||
int boostDrain = 0;
|
int boostDrain = (int)(skill->values[0][power] * scalingFactor);
|
||||||
int potionDrain = 0;
|
if(boostDrain > plr->batteryW) boostDrain = plr->batteryW;
|
||||||
if(!blocked) {
|
plr->batteryW -= boostDrain;
|
||||||
boostDrain = (int)(skill->values[0][power] * scalingFactor);
|
|
||||||
if(boostDrain > plr->batteryW) boostDrain = plr->batteryW;
|
|
||||||
plr->batteryW -= boostDrain;
|
|
||||||
|
|
||||||
potionDrain = (int)(skill->values[1][power] * scalingFactor);
|
int potionDrain = (int)(skill->values[1][power] * scalingFactor);
|
||||||
if(potionDrain > plr->batteryN) potionDrain = plr->batteryN;
|
if(potionDrain > plr->batteryN) potionDrain = plr->batteryN;
|
||||||
plr->batteryN -= potionDrain;
|
plr->batteryN -= potionDrain;
|
||||||
}
|
|
||||||
|
|
||||||
sSkillResult_BatteryDrain result{};
|
sSkillResult_BatteryDrain result{};
|
||||||
result.eCT = target->getCharType();
|
result.eCT = target->getCharType();
|
||||||
result.iID = target->getID();
|
result.iID = target->getID();
|
||||||
result.bProtected = blocked;
|
result.bProtected = target->hasBuff(ECSB_PROTECT_BATTERY);
|
||||||
result.iDrainW = boostDrain;
|
result.iDrainW = boostDrain;
|
||||||
result.iBatteryW = plr->batteryW;
|
result.iBatteryW = plr->batteryW;
|
||||||
result.iDrainN = potionDrain;
|
result.iDrainN = potionDrain;
|
||||||
@@ -227,11 +150,6 @@ static std::vector<SkillResult> handleSkill(SkillData* skill, int power, ICombat
|
|||||||
|
|
||||||
switch(skill->skillType)
|
switch(skill->skillType)
|
||||||
{
|
{
|
||||||
case SkillType::CORRUPTIONATTACK:
|
|
||||||
case SkillType::CORRUPTIONATTACKLOSE:
|
|
||||||
case SkillType::CORRUPTIONATTACKWIN:
|
|
||||||
// skillHandler = handleSkillCorruptionReflect;
|
|
||||||
// break;
|
|
||||||
case SkillType::DAMAGE:
|
case SkillType::DAMAGE:
|
||||||
skillHandler = handleSkillDamage;
|
skillHandler = handleSkillDamage;
|
||||||
break;
|
break;
|
||||||
@@ -298,18 +216,16 @@ static std::vector<SkillResult> handleSkill(SkillData* skill, int power, ICombat
|
|||||||
return results;
|
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) {
|
for(SkillResult& result : results) {
|
||||||
size_t sz = result.size;
|
memcpy(pivot, result.payload, resultSize);
|
||||||
memcpy(pivot, result.payload, sz);
|
pivot += resultSize;
|
||||||
pivot += sz;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Abilities::useNanoSkill(CNSocket* sock, SkillData* skill, sNano& nano, std::vector<ICombatant*> affected) {
|
void Abilities::useNanoSkill(CNSocket* sock, SkillData* skill, sNano& nano, std::vector<ICombatant*> affected) {
|
||||||
|
|
||||||
Player* plr = PlayerManager::getPlayer(sock);
|
Player* plr = PlayerManager::getPlayer(sock);
|
||||||
ICombatant* combatant = dynamic_cast<ICombatant*>(plr);
|
|
||||||
|
|
||||||
int boost = 0;
|
int boost = 0;
|
||||||
if (Nanos::getNanoBoost(plr))
|
if (Nanos::getNanoBoost(plr))
|
||||||
@@ -321,19 +237,17 @@ void Abilities::useNanoSkill(CNSocket* sock, SkillData* skill, sNano& nano, std:
|
|||||||
nano.iStamina = 0;
|
nano.iStamina = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<SkillResult> results = handleSkill(skill, boost, combatant, affected);
|
std::vector<SkillResult> results = handleSkill(skill, boost, plr, affected);
|
||||||
if(results.empty()) return; // no effect; no need for confirmation packets
|
if(results.empty()) return; // no effect; no need for confirmation packets
|
||||||
|
|
||||||
// lazy validation since skill results might be different sizes
|
size_t resultSize = MAX_SKILLRESULT_SIZE; // lazy
|
||||||
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";
|
std::cout << "[WARN] bad sP_FE2CL_NANO_SKILL_USE_SUCC packet size\n";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize response struct
|
// initialize response struct
|
||||||
size_t resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC);
|
size_t resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC) + results.size() * resultSize;
|
||||||
for(SkillResult& sr : results)
|
|
||||||
resplen += sr.size;
|
|
||||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||||
memset(respbuf, 0, resplen);
|
memset(respbuf, 0, resplen);
|
||||||
|
|
||||||
@@ -346,7 +260,7 @@ void Abilities::useNanoSkill(CNSocket* sock, SkillData* skill, sNano& nano, std:
|
|||||||
pkt->eST = (int32_t)skill->skillType;
|
pkt->eST = (int32_t)skill->skillType;
|
||||||
pkt->iTargetCnt = (int32_t)results.size();
|
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);
|
sock->sendPacket(pkt, P_FE2CL_NANO_SKILL_USE_SUCC, resplen);
|
||||||
|
|
||||||
if(skill->skillType == SkillType::RECALL_GROUP)
|
if(skill->skillType == SkillType::RECALL_GROUP)
|
||||||
@@ -370,16 +284,15 @@ void Abilities::useNPCSkill(EntityRef npc, int skillID, std::vector<ICombatant*>
|
|||||||
std::vector<SkillResult> results = handleSkill(skill, 0, src, affected);
|
std::vector<SkillResult> results = handleSkill(skill, 0, src, affected);
|
||||||
if(results.empty()) return; // no effect; no need for confirmation packets
|
if(results.empty()) return; // no effect; no need for confirmation packets
|
||||||
|
|
||||||
// lazy validation since skill results might be different sizes
|
size_t resultSize = results.back().size; // guaranteed to be the same for every item
|
||||||
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";
|
std::cout << "[WARN] bad sP_FE2CL_NPC_SKILL_HIT packet size\n";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize response struct
|
// initialize response struct
|
||||||
size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT);
|
size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + results.size() * resultSize;
|
||||||
for(SkillResult& sr : results)
|
|
||||||
resplen += sr.size;
|
|
||||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||||
memset(respbuf, 0, resplen);
|
memset(respbuf, 0, resplen);
|
||||||
|
|
||||||
@@ -388,14 +301,8 @@ void Abilities::useNPCSkill(EntityRef npc, int skillID, std::vector<ICombatant*>
|
|||||||
pkt->iSkillID = skillID;
|
pkt->iSkillID = skillID;
|
||||||
pkt->eST = (int32_t)skill->skillType;
|
pkt->eST = (int32_t)skill->skillType;
|
||||||
pkt->iTargetCnt = (int32_t)results.size();
|
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);
|
NPCManager::sendToViewable(entity, pkt, P_FE2CL_NPC_SKILL_HIT, resplen);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -470,6 +377,12 @@ int Abilities::getCSTBFromST(SkillType skillType) {
|
|||||||
case SkillType::PROTECTINFECTION:
|
case SkillType::PROTECTINFECTION:
|
||||||
result = ECSB_PROTECT_INFECTION;
|
result = ECSB_PROTECT_INFECTION;
|
||||||
break;
|
break;
|
||||||
|
case SkillType::SNARE:
|
||||||
|
result = ECSB_DN_MOVE_SPEED;
|
||||||
|
break;
|
||||||
|
case SkillType::SLEEP:
|
||||||
|
result = ECSB_MEZ;
|
||||||
|
break;
|
||||||
case SkillType::MINIMAPENEMY:
|
case SkillType::MINIMAPENEMY:
|
||||||
result = ECSB_MINIMAP_ENEMY;
|
result = ECSB_MINIMAP_ENEMY;
|
||||||
break;
|
break;
|
||||||
@@ -482,9 +395,15 @@ int Abilities::getCSTBFromST(SkillType skillType) {
|
|||||||
case SkillType::REWARDCASH:
|
case SkillType::REWARDCASH:
|
||||||
result = ECSB_REWARD_CASH;
|
result = ECSB_REWARD_CASH;
|
||||||
break;
|
break;
|
||||||
|
case SkillType::INFECTIONDAMAGE:
|
||||||
|
result = ECSB_INFECTION;
|
||||||
|
break;
|
||||||
case SkillType::FREEDOM:
|
case SkillType::FREEDOM:
|
||||||
result = ECSB_FREEDOM;
|
result = ECSB_FREEDOM;
|
||||||
break;
|
break;
|
||||||
|
case SkillType::BOUNDINGBALL:
|
||||||
|
result = ECSB_BOUNDINGBALL;
|
||||||
|
break;
|
||||||
case SkillType::INVULNERABLE:
|
case SkillType::INVULNERABLE:
|
||||||
result = ECSB_INVULNERABLE;
|
result = ECSB_INVULNERABLE;
|
||||||
break;
|
break;
|
||||||
@@ -493,22 +412,6 @@ int Abilities::getCSTBFromST(SkillType skillType) {
|
|||||||
break;
|
break;
|
||||||
case SkillType::NANOSTIMPAK:
|
case SkillType::NANOSTIMPAK:
|
||||||
result = ECSB_STIMPAKSLOT1;
|
result = ECSB_STIMPAKSLOT1;
|
||||||
// shift as necessary
|
|
||||||
break;
|
|
||||||
case SkillType::SNARE:
|
|
||||||
result = ECSB_DN_MOVE_SPEED;
|
|
||||||
break;
|
|
||||||
case SkillType::STUN:
|
|
||||||
result = ECSB_STUN;
|
|
||||||
break;
|
|
||||||
case SkillType::SLEEP:
|
|
||||||
result = ECSB_MEZ;
|
|
||||||
break;
|
|
||||||
case SkillType::INFECTIONDAMAGE:
|
|
||||||
result = ECSB_INFECTION;
|
|
||||||
break;
|
|
||||||
case SkillType::BOUNDINGBALL:
|
|
||||||
result = ECSB_BOUNDINGBALL;
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -9,18 +9,17 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
const int COMBAT_TICKS_PER_DRAIN_PROC = 2;
|
|
||||||
constexpr size_t MAX_SKILLRESULT_SIZE = sizeof(sSkillResult_BatteryDrain);
|
constexpr size_t MAX_SKILLRESULT_SIZE = sizeof(sSkillResult_BatteryDrain);
|
||||||
|
|
||||||
enum class SkillType {
|
enum class SkillType {
|
||||||
DAMAGE = 1,
|
DAMAGE = 1,
|
||||||
HEAL_HP = 2,
|
HEAL_HP = 2,
|
||||||
KNOCKDOWN = 3, // uses DamageNDebuff
|
KNOCKDOWN = 3, // dnd
|
||||||
SLEEP = 4, // uses DamageNDebuff
|
SLEEP = 4, // dnd
|
||||||
SNARE = 5, // uses DamageNDebuff
|
SNARE = 5, // dnd
|
||||||
HEAL_STAMINA = 6,
|
HEAL_STAMINA = 6,
|
||||||
STAMINA_SELF = 7,
|
STAMINA_SELF = 7,
|
||||||
STUN = 8, // uses DamageNDebuff
|
STUN = 8, // dnd
|
||||||
WEAPONSLOW = 9,
|
WEAPONSLOW = 9,
|
||||||
JUMP = 10,
|
JUMP = 10,
|
||||||
RUN = 11,
|
RUN = 11,
|
||||||
|
|||||||
@@ -95,12 +95,6 @@ int Buff::getValue(BuffValueSelector selector) {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
EntityRef Buff::getLastSource() {
|
|
||||||
if(stacks.empty())
|
|
||||||
return self;
|
|
||||||
return stacks.back().source;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Buff::isStale() {
|
bool Buff::isStale() {
|
||||||
return stacks.empty();
|
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));
|
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) {
|
void Buffs::timeBuffTimeout(EntityRef self) {
|
||||||
if(self.kind != EntityKind::PLAYER && self.kind != EntityKind::COMBAT_NPC && self.kind != EntityKind::MOB)
|
if(self.kind != EntityKind::PLAYER && self.kind != EntityKind::COMBAT_NPC && self.kind != EntityKind::MOB)
|
||||||
return; // not a combatant
|
return; // not a combatant
|
||||||
Entity* entity = self.getEntity();
|
Entity* entity = self.getEntity();
|
||||||
ICombatant* combatant = dynamic_cast<ICombatant*>(entity);
|
ICombatant* combatant = dynamic_cast<ICombatant*>(entity);
|
||||||
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt); // send a buff timeout to other players
|
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt); // send a buff timeout to other players
|
||||||
int32_t eCharType = combatant->getCharType();
|
pkt.eCT = combatant->getCharType();
|
||||||
pkt.eCT = eCharType == 4 ? 2 : eCharType; // convention not followed by client here
|
|
||||||
pkt.iID = combatant->getID();
|
pkt.iID = combatant->getID();
|
||||||
pkt.iConditionBitFlag = combatant->getCompositeCondition();
|
pkt.iConditionBitFlag = combatant->getCompositeCondition();
|
||||||
NPCManager::sendToViewable(entity, &pkt, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
|
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
|
#pragma endregion
|
||||||
|
|||||||
@@ -66,7 +66,6 @@ public:
|
|||||||
BuffClass maxClass();
|
BuffClass maxClass();
|
||||||
|
|
||||||
int getValue(BuffValueSelector selector);
|
int getValue(BuffValueSelector selector);
|
||||||
EntityRef getLastSource();
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* In general, a Buff object won't exist
|
* In general, a Buff object won't exist
|
||||||
@@ -87,7 +86,5 @@ public:
|
|||||||
|
|
||||||
namespace Buffs {
|
namespace Buffs {
|
||||||
void timeBuffUpdate(EntityRef self, Buff* buff, int status, BuffStack* stack);
|
void timeBuffUpdate(EntityRef self, Buff* buff, int status, BuffStack* stack);
|
||||||
void timeBuffTick(EntityRef self, Buff* buff);
|
|
||||||
void timeBuffTimeout(EntityRef self);
|
void timeBuffTimeout(EntityRef self);
|
||||||
void tickDrain(EntityRef self, Buff* buff, int mult);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,10 @@ bool Player::addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCal
|
|||||||
if(!isAlive())
|
if(!isAlive())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
EntityRef self = PlayerManager::getSockFromID(iID);
|
||||||
|
|
||||||
if(!hasBuff(buffId)) {
|
if(!hasBuff(buffId)) {
|
||||||
buffs[buffId] = new Buff(buffId, getRef(), onUpdate, onTick, stack);
|
buffs[buffId] = new Buff(buffId, self, onUpdate, onTick, stack);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,10 +51,9 @@ void Player::removeBuff(int buffId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::removeBuff(int buffId, BuffClass buffClass) {
|
void Player::removeBuff(int buffId, int buffClass) {
|
||||||
if(hasBuff(buffId)) {
|
if(hasBuff(buffId)) {
|
||||||
buffs[buffId]->clear(buffClass);
|
buffs[buffId]->clear((BuffClass)buffClass);
|
||||||
// buff might not be stale since another buff class might remain
|
|
||||||
if(buffs[buffId]->isStale()) {
|
if(buffs[buffId]->isStale()) {
|
||||||
delete buffs[buffId];
|
delete buffs[buffId];
|
||||||
buffs.erase(buffId);
|
buffs.erase(buffId);
|
||||||
@@ -60,16 +61,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) {
|
bool Player::hasBuff(int buffId) {
|
||||||
auto buff = buffs.find(buffId);
|
auto buff = buffs.find(buffId);
|
||||||
return buff != buffs.end() && !buff->second->isStale();
|
return buff != buffs.end() && !buff->second->isStale();
|
||||||
@@ -168,78 +159,29 @@ void Player::step(time_t currTime) {
|
|||||||
// buffs
|
// buffs
|
||||||
for(auto buffEntry : buffs) {
|
for(auto buffEntry : buffs) {
|
||||||
buffEntry.second->combatTick(currTime);
|
buffEntry.second->combatTick(currTime);
|
||||||
if(!isAlive())
|
|
||||||
break; // unsafe to keep ticking if we're dead
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#pragma endregion
|
#pragma endregion
|
||||||
|
|
||||||
#pragma region CombatNPC
|
#pragma region CombatNPC
|
||||||
bool CombatNPC::addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) {
|
bool CombatNPC::addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) { /* stubbed */
|
||||||
if(!isAlive())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (this->state != AIState::COMBAT && this->state != AIState::ROAMING)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if(!hasBuff(buffId)) {
|
|
||||||
buffs[buffId] = new Buff(buffId, getRef(), onUpdate, onTick, stack);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
buffs[buffId]->updateCallbacks(onUpdate, onTick);
|
|
||||||
buffs[buffId]->addStack(stack);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Buff* CombatNPC::getBuff(int buffId) {
|
Buff* CombatNPC::getBuff(int buffId) { /* stubbed */
|
||||||
if(hasBuff(buffId)) {
|
|
||||||
return buffs[buffId];
|
|
||||||
}
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CombatNPC::removeBuff(int buffId) {
|
void CombatNPC::removeBuff(int buffId) { /* stubbed */ }
|
||||||
if(hasBuff(buffId)) {
|
|
||||||
buffs[buffId]->clear();
|
void CombatNPC::removeBuff(int buffId, int buffClass) { /* stubbed */ }
|
||||||
delete buffs[buffId];
|
|
||||||
buffs.erase(buffId);
|
bool CombatNPC::hasBuff(int buffId) { /* stubbed */
|
||||||
}
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CombatNPC::removeBuff(int buffId, BuffClass buffClass) {
|
int CombatNPC::getCompositeCondition() { /* stubbed */
|
||||||
if(hasBuff(buffId)) {
|
return 0;
|
||||||
buffs[buffId]->clear(buffClass);
|
|
||||||
// buff might not be stale since another buff class might remain
|
|
||||||
if(buffs[buffId]->isStale()) {
|
|
||||||
delete buffs[buffId];
|
|
||||||
buffs.erase(buffId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CombatNPC::clearBuffs(bool force) {
|
|
||||||
auto it = buffs.begin();
|
|
||||||
while(it != buffs.end()) {
|
|
||||||
Buff* buff = (*it).second;
|
|
||||||
if(!force) buff->clear();
|
|
||||||
delete buff;
|
|
||||||
it = buffs.erase(it);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CombatNPC::hasBuff(int buffId) {
|
|
||||||
auto buff = buffs.find(buffId);
|
|
||||||
return buff != buffs.end() && !buff->second->isStale();
|
|
||||||
}
|
|
||||||
|
|
||||||
int CombatNPC::getCompositeCondition() {
|
|
||||||
int conditionBitFlag = 0;
|
|
||||||
for(auto buff : buffs) {
|
|
||||||
if(!buff.second->isStale() && buff.second->id > 0)
|
|
||||||
conditionBitFlag |= CSB_FROM_ECSB(buff.first);
|
|
||||||
}
|
|
||||||
return conditionBitFlag;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int CombatNPC::takeDamage(EntityRef src, int amt) {
|
int CombatNPC::takeDamage(EntityRef src, int amt) {
|
||||||
@@ -310,19 +252,19 @@ void CombatNPC::step(time_t currTime) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CombatNPC::transition(AIState newState, EntityRef src) {
|
void CombatNPC::transition(AIState newState, EntityRef src) {
|
||||||
state = newState;
|
|
||||||
|
|
||||||
|
state = newState;
|
||||||
if (transitionHandlers.find(newState) != transitionHandlers.end())
|
if (transitionHandlers.find(newState) != transitionHandlers.end())
|
||||||
transitionHandlers[newState](this, src);
|
transitionHandlers[newState](this, src);
|
||||||
else {
|
else {
|
||||||
std::cout << "[WARN] Transition to " << (int)state << " has no handler; going inactive" << std::endl;
|
std::cout << "[WARN] Transition to " << (int)state << " has no handler; going inactive" << std::endl;
|
||||||
transition(AIState::INACTIVE, id);
|
transition(AIState::INACTIVE, id);
|
||||||
}
|
}
|
||||||
|
/* TODO: fire any triggered events
|
||||||
// trigger special NPCEvents, if applicable
|
|
||||||
for (NPCEvent& event : NPCManager::NPCEvents)
|
for (NPCEvent& event : NPCManager::NPCEvents)
|
||||||
if (event.triggerState == newState && event.npcType == type)
|
if (event.trigger == ON_KILLED && event.npcType == type)
|
||||||
event.handler(this);
|
event.handler(src, this);
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
#pragma endregion
|
#pragma endregion
|
||||||
|
|
||||||
@@ -960,7 +902,6 @@ static void playerTick(CNServer *serv, time_t currTime) {
|
|||||||
auto it = plr->buffs.begin();
|
auto it = plr->buffs.begin();
|
||||||
while(it != plr->buffs.end()) {
|
while(it != plr->buffs.end()) {
|
||||||
Buff* buff = (*it).second;
|
Buff* buff = (*it).second;
|
||||||
//buff->combatTick() gets called in Player::step
|
|
||||||
buff->tick(currTime);
|
buff->tick(currTime);
|
||||||
if(buff->isStale()) {
|
if(buff->isStale()) {
|
||||||
// garbage collect
|
// garbage collect
|
||||||
|
|||||||
@@ -678,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] ID: " + std::to_string(npc->id));
|
||||||
Chat::sendServerMessage(sock, "[WHOIS] Type: " + std::to_string(npc->type));
|
Chat::sendServerMessage(sock, "[WHOIS] Type: " + std::to_string(npc->type));
|
||||||
Chat::sendServerMessage(sock, "[WHOIS] HP: " + std::to_string(npc->hp));
|
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] EntityType: " + std::to_string((int)npc->kind));
|
||||||
Chat::sendServerMessage(sock, "[WHOIS] X: " + std::to_string(npc->x));
|
Chat::sendServerMessage(sock, "[WHOIS] X: " + std::to_string(npc->x));
|
||||||
Chat::sendServerMessage(sock, "[WHOIS] Y: " + std::to_string(npc->y));
|
Chat::sendServerMessage(sock, "[WHOIS] Y: " + std::to_string(npc->y));
|
||||||
|
|||||||
54
src/Eggs.cpp
54
src/Eggs.cpp
@@ -26,7 +26,6 @@ void Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SkillResult result = SkillResult();
|
|
||||||
SkillData* skill = &Abilities::SkillTable[skillId];
|
SkillData* skill = &Abilities::SkillTable[skillId];
|
||||||
if(skill->drainType == SkillDrainType::PASSIVE) {
|
if(skill->drainType == SkillDrainType::PASSIVE) {
|
||||||
// apply buff
|
// apply buff
|
||||||
@@ -51,57 +50,12 @@ void Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) {
|
|||||||
// no-op
|
// no-op
|
||||||
},
|
},
|
||||||
&eggBuff);
|
&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
|
// use skill
|
||||||
size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + result.size;
|
std::vector<ICombatant*> targets;
|
||||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
targets.push_back(dynamic_cast<ICombatant*>(plr));
|
||||||
memset(respbuf, 0, resplen);
|
Abilities::useNPCSkill(src, skillId, targets);
|
||||||
|
|
||||||
sP_FE2CL_NPC_SKILL_HIT* pkt = (sP_FE2CL_NPC_SKILL_HIT*)respbuf;
|
|
||||||
pkt->iNPC_ID = eggId;
|
|
||||||
pkt->iSkillID = skillId;
|
|
||||||
pkt->eST = (int32_t)skill->skillType;
|
|
||||||
pkt->iTargetCnt = 1;
|
|
||||||
|
|
||||||
if(result.size > 0) {
|
|
||||||
void* attached = (void*)(pkt + 1);
|
|
||||||
memcpy(attached, result.payload, result.size);
|
|
||||||
}
|
|
||||||
|
|
||||||
NPCManager::sendToViewable(src.getEntity(), pkt, P_FE2CL_NPC_SKILL_HIT, resplen);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void eggStep(CNServer* serv, time_t currTime) {
|
static void eggStep(CNServer* serv, time_t currTime) {
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ sNPCAppearanceData BaseNPC::getAppearanceData() {
|
|||||||
sNPCAppearanceData data = {};
|
sNPCAppearanceData data = {};
|
||||||
data.iAngle = angle;
|
data.iAngle = angle;
|
||||||
data.iBarkerType = 0; // unused?
|
data.iBarkerType = 0; // unused?
|
||||||
data.iConditionBitFlag = 0;
|
data.iConditionBitFlag = cbf;
|
||||||
data.iHP = hp;
|
data.iHP = hp;
|
||||||
data.iNPCType = type;
|
data.iNPCType = type;
|
||||||
data.iNPC_ID = id;
|
data.iNPC_ID = id;
|
||||||
@@ -50,12 +50,6 @@ sNPCAppearanceData BaseNPC::getAppearanceData() {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
sNPCAppearanceData CombatNPC::getAppearanceData() {
|
|
||||||
sNPCAppearanceData data = BaseNPC::getAppearanceData();
|
|
||||||
data.iConditionBitFlag = getCompositeCondition();
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Entity coming into view.
|
* Entity coming into view.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -47,8 +47,7 @@ public:
|
|||||||
virtual bool addBuff(int, BuffCallback<int, BuffStack*>, BuffCallback<time_t>, BuffStack*) = 0;
|
virtual bool addBuff(int, BuffCallback<int, BuffStack*>, BuffCallback<time_t>, BuffStack*) = 0;
|
||||||
virtual Buff* getBuff(int) = 0;
|
virtual Buff* getBuff(int) = 0;
|
||||||
virtual void removeBuff(int) = 0;
|
virtual void removeBuff(int) = 0;
|
||||||
virtual void removeBuff(int, BuffClass) = 0;
|
virtual void removeBuff(int, int) = 0;
|
||||||
virtual void clearBuffs(bool) = 0;
|
|
||||||
virtual bool hasBuff(int) = 0;
|
virtual bool hasBuff(int) = 0;
|
||||||
virtual int getCompositeCondition() = 0;
|
virtual int getCompositeCondition() = 0;
|
||||||
virtual int takeDamage(EntityRef, int) = 0;
|
virtual int takeDamage(EntityRef, int) = 0;
|
||||||
@@ -73,6 +72,7 @@ public:
|
|||||||
int type;
|
int type;
|
||||||
int hp;
|
int hp;
|
||||||
int angle;
|
int angle;
|
||||||
|
int cbf;
|
||||||
bool loopingPath = false;
|
bool loopingPath = false;
|
||||||
|
|
||||||
BaseNPC(int _A, uint64_t iID, int t, int _id) {
|
BaseNPC(int _A, uint64_t iID, int t, int _id) {
|
||||||
@@ -80,6 +80,7 @@ public:
|
|||||||
type = t;
|
type = t;
|
||||||
hp = 400;
|
hp = 400;
|
||||||
angle = _A;
|
angle = _A;
|
||||||
|
cbf = 0;
|
||||||
id = _id;
|
id = _id;
|
||||||
instanceID = iID;
|
instanceID = iID;
|
||||||
};
|
};
|
||||||
@@ -87,7 +88,7 @@ public:
|
|||||||
virtual void enterIntoViewOf(CNSocket *sock) override;
|
virtual void enterIntoViewOf(CNSocket *sock) override;
|
||||||
virtual void disappearFromViewOf(CNSocket *sock) override;
|
virtual void disappearFromViewOf(CNSocket *sock) override;
|
||||||
|
|
||||||
virtual sNPCAppearanceData getAppearanceData();
|
sNPCAppearanceData getAppearanceData();
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CombatNPC : public BaseNPC, public ICombatant {
|
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*, time_t)> stateHandlers;
|
||||||
std::map<AIState, void (*)(CombatNPC*, EntityRef)> transitionHandlers;
|
std::map<AIState, void (*)(CombatNPC*, EntityRef)> transitionHandlers;
|
||||||
|
|
||||||
std::unordered_map<int, Buff*> buffs = {};
|
CombatNPC(int x, int y, int z, int angle, uint64_t iID, int t, int id, int maxHP)
|
||||||
|
|
||||||
CombatNPC(int spawnX, int spawnY, int spawnZ, int angle, uint64_t iID, int t, int id, int maxHP)
|
|
||||||
: BaseNPC(angle, iID, t, id), maxHealth(maxHP) {
|
: BaseNPC(angle, iID, t, id), maxHealth(maxHP) {
|
||||||
this->spawnX = spawnX;
|
spawnX = x;
|
||||||
this->spawnY = spawnY;
|
spawnY = y;
|
||||||
this->spawnZ = spawnZ;
|
spawnZ = z;
|
||||||
|
|
||||||
kind = EntityKind::COMBAT_NPC;
|
kind = EntityKind::COMBAT_NPC;
|
||||||
|
|
||||||
@@ -118,15 +117,12 @@ struct CombatNPC : public BaseNPC, public ICombatant {
|
|||||||
transitionHandlers[AIState::INACTIVE] = {};
|
transitionHandlers[AIState::INACTIVE] = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual sNPCAppearanceData getAppearanceData() override;
|
|
||||||
|
|
||||||
virtual bool isExtant() override { return hp > 0; }
|
virtual bool isExtant() override { return hp > 0; }
|
||||||
|
|
||||||
virtual bool addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) override;
|
virtual bool addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) override;
|
||||||
virtual Buff* getBuff(int buffId) override;
|
virtual Buff* getBuff(int buffId) override;
|
||||||
virtual void removeBuff(int buffId) override;
|
virtual void removeBuff(int buffId) override;
|
||||||
virtual void removeBuff(int buffId, BuffClass buffClass) override;
|
virtual void removeBuff(int buffId, int buffClass) override;
|
||||||
virtual void clearBuffs(bool force) override;
|
|
||||||
virtual bool hasBuff(int buffId) override;
|
virtual bool hasBuff(int buffId) override;
|
||||||
virtual int getCompositeCondition() override;
|
virtual int getCompositeCondition() override;
|
||||||
virtual int takeDamage(EntityRef src, int amt) override;
|
virtual int takeDamage(EntityRef src, int amt) override;
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
#include "EntityRef.hpp"
|
#include "EntityRef.hpp"
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
struct Group {
|
struct Group {
|
||||||
std::vector<EntityRef> members;
|
std::vector<EntityRef> members;
|
||||||
@@ -15,10 +14,6 @@ struct Group {
|
|||||||
});
|
});
|
||||||
return filtered;
|
return filtered;
|
||||||
}
|
}
|
||||||
EntityRef getLeader() {
|
|
||||||
assert(members.size() > 0);
|
|
||||||
return members[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
Group(EntityRef leader);
|
Group(EntityRef leader);
|
||||||
};
|
};
|
||||||
|
|||||||
179
src/MobAI.cpp
179
src/MobAI.cpp
@@ -51,13 +51,13 @@ int Mob::takeDamage(EntityRef src, int amt) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// wake up sleeping monster
|
// wake up sleeping monster
|
||||||
if (hasBuff(ECSB_MEZ)) {
|
if (cbf & CSB_BIT_MEZ) {
|
||||||
removeBuff(ECSB_MEZ);
|
cbf &= ~CSB_BIT_MEZ;
|
||||||
|
|
||||||
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1);
|
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1);
|
||||||
pkt1.eCT = 2;
|
pkt1.eCT = 2;
|
||||||
pkt1.iID = id;
|
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));
|
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) {
|
void MobAI::clearDebuff(Mob *mob) {
|
||||||
mob->skillStyle = -1;
|
mob->skillStyle = -1;
|
||||||
mob->clearBuffs(false);
|
mob->cbf = 0;
|
||||||
|
mob->unbuffTimes.clear();
|
||||||
|
|
||||||
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1);
|
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1);
|
||||||
pkt1.eCT = 2;
|
pkt1.eCT = 2;
|
||||||
pkt1.iID = mob->id;
|
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));
|
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;
|
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);
|
Player *plr = PlayerManager::getPlayer(mob->target);
|
||||||
|
|
||||||
size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_CORRUPTION_HIT) + targetData[0] * sizeof(sCAttackResult);
|
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->iNPC_ID = mob->id;
|
||||||
resp->iSkillID = skillID;
|
resp->iSkillID = skillID;
|
||||||
resp->iStyle = mobStyle;
|
resp->iStyle = style;
|
||||||
resp->iValue1 = plr->x;
|
resp->iValue1 = plr->x;
|
||||||
resp->iValue2 = plr->y;
|
resp->iValue2 = plr->y;
|
||||||
resp->iValue3 = plr->z;
|
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].iActiveNanoSlotNum = n;
|
||||||
respdata[i].iNanoID = plr->activeNano;
|
respdata[i].iNanoID = plr->activeNano;
|
||||||
|
|
||||||
int nanoStyle = Nanos::nanoStyle(plr->activeNano);
|
int style2 = Nanos::nanoStyle(plr->activeNano);
|
||||||
if (nanoStyle == -1) { // no nano
|
if (style2 == -1) { // no nano
|
||||||
respdata[i].iHitFlag = HF_BIT_STYLE_TIE;
|
respdata[i].iHitFlag = HF_BIT_STYLE_TIE;
|
||||||
respdata[i].iDamage = Abilities::SkillTable[skillID].values[0][0] * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500;
|
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].iHitFlag = HF_BIT_STYLE_TIE;
|
||||||
respdata[i].iDamage = 0;
|
respdata[i].iDamage = 0;
|
||||||
respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina;
|
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].iHitFlag = HF_BIT_STYLE_WIN;
|
||||||
respdata[i].iDamage = 0;
|
respdata[i].iDamage = 0;
|
||||||
respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina += 45;
|
respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina += 45;
|
||||||
if (plr->Nanos[plr->activeNano].iStamina > 150)
|
if (plr->Nanos[plr->activeNano].iStamina > 150)
|
||||||
respdata[i].iNanoStamina = 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
|
// fire damage power disguised as a corruption attack back at the enemy
|
||||||
SkillData skill = {
|
// TODO ABILITIES
|
||||||
SkillType::DAMAGE, // skillType
|
/*std::vector<int> targetData2 = {1, mob->id, 0, 0, 0};
|
||||||
SkillEffectTarget::POINT, // effectTarget
|
for (auto& pwr : Abilities::Powers)
|
||||||
1, // effectType
|
if (pwr.skillType == EST_DAMAGE)
|
||||||
SkillTargetType::MOBS, // targetType
|
pwr.handle(sock, targetData2, plr->activeNano, skillID, 0, 200);*/
|
||||||
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 });
|
|
||||||
} else {
|
} else {
|
||||||
respdata[i].iHitFlag = HF_BIT_STYLE_LOSE;
|
respdata[i].iHitFlag = HF_BIT_STYLE_LOSE;
|
||||||
respdata[i].iDamage = Abilities::SkillTable[skillID].values[0][0] * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500;
|
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) {
|
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);
|
Player *plr = PlayerManager::getPlayer(mob->target);
|
||||||
|
|
||||||
if (mob->skillStyle >= 0) { // corruption hit
|
if (mob->skillStyle >= 0) { // corruption hit
|
||||||
@@ -352,7 +347,7 @@ static void useAbilities(Mob *mob, time_t currTime) {
|
|||||||
|
|
||||||
if (mob->skillStyle == -2) { // eruption hit
|
if (mob->skillStyle == -2) { // eruption hit
|
||||||
int skillID = (int)mob->data["m_iMegaType"];
|
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
|
// find the players within range of eruption
|
||||||
for (auto it = mob->viewableChunks.begin(); it != mob->viewableChunks.end(); it++) {
|
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)
|
if (ref.kind != EntityKind::PLAYER)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
CNSocket *s = ref.sock;
|
CNSocket *s= ref.sock;
|
||||||
Player *plr = PlayerManager::getPlayer(s);
|
Player *plr = PlayerManager::getPlayer(s);
|
||||||
|
|
||||||
if (!plr->isAlive())
|
if (plr->HP <= 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
int distance = hypot(mob->hitX - plr->x, mob->hitY - plr->y);
|
int distance = hypot(mob->hitX - plr->x, mob->hitY - plr->y);
|
||||||
if (distance < Abilities::SkillTable[skillID].effectArea) {
|
if (distance < Abilities::SkillTable[skillID].effectArea) {
|
||||||
targets.push_back(plr);
|
targetData[0] += 1;
|
||||||
if (targets.size() > 3) // make sure not to have more than 4
|
targetData[targetData[0]] = plr->iID;
|
||||||
|
if (targetData[0] > 3) // make sure not to have more than 4
|
||||||
break;
|
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->skillStyle = -3; // eruption cooldown
|
||||||
mob->nextAttack = currTime + 1000;
|
mob->nextAttack = currTime + 1000;
|
||||||
return;
|
return;
|
||||||
@@ -395,11 +394,14 @@ static void useAbilities(Mob *mob, time_t currTime) {
|
|||||||
|
|
||||||
if (random < prob1) { // active skill hit
|
if (random < prob1) { // active skill hit
|
||||||
int skillID = (int)mob->data["m_iActiveSkill1"];
|
int skillID = (int)mob->data["m_iActiveSkill1"];
|
||||||
SkillData* skill = &Abilities::SkillTable[skillID];
|
// TODO ABILITIES
|
||||||
int debuffID = Abilities::getCSTBFromST(skill->skillType);
|
//std::vector<int> targetData = {1, plr->iID, 0, 0, 0};
|
||||||
if(plr->hasBuff(debuffID))
|
//for (auto& pwr : Abilities::Powers)
|
||||||
return; // prevent debuffing a player twice
|
// if (pwr.skillType == Abilities::SkillTable[skillID].skillType) {
|
||||||
Abilities::useNPCSkill(mob->getRef(), skillID, { plr });
|
// 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;
|
mob->nextAttack = currTime + (int)mob->data["m_iDelayTime"] * 100;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -440,6 +442,31 @@ static void useAbilities(Mob *mob, time_t currTime) {
|
|||||||
return;
|
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) {
|
void MobAI::incNextMovement(Mob* mob, time_t currTime) {
|
||||||
if (currTime == 0)
|
if (currTime == 0)
|
||||||
currTime = getTime();
|
currTime = getTime();
|
||||||
@@ -526,27 +553,38 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// tick buffs
|
// drain
|
||||||
auto it = npc->buffs.begin();
|
if (self->skillStyle < 0 && (self->lastDrainTime == 0 || currTime - self->lastDrainTime >= 1000)
|
||||||
while(it != npc->buffs.end()) {
|
&& self->cbf & CSB_BIT_BOUNDINGBALL) {
|
||||||
Buff* buff = (*it).second;
|
drainMobHP(self, self->maxHealth / 20); // lose 5% every second
|
||||||
buff->combatTick(currTime);
|
self->lastDrainTime = currTime;
|
||||||
|
}
|
||||||
|
|
||||||
// if mob state changed, end the step
|
// if drain killed the mob, return early
|
||||||
if(self->state != AIState::COMBAT)
|
if (self->hp <= 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
buff->tick(currTime);
|
// unbuffing
|
||||||
if(buff->isStale()) {
|
std::unordered_map<int32_t, time_t>::iterator it = self->unbuffTimes.begin();
|
||||||
// garbage collect
|
while (it != self->unbuffTimes.end()) {
|
||||||
it = npc->buffs.erase(it);
|
|
||||||
delete buff;
|
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
|
// 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.
|
self->skillStyle = -1; // in this case we also reset the any outlying abilities the mob might be winding up.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -562,7 +600,6 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int distanceToTravel = INT_MAX;
|
int distanceToTravel = INT_MAX;
|
||||||
int speed = self->speed;
|
|
||||||
// movement logic: move when out of range but don't move while casting a skill
|
// movement logic: move when out of range but don't move while casting a skill
|
||||||
if (distance > mobRange && self->skillStyle == -1) {
|
if (distance > mobRange && self->skillStyle == -1) {
|
||||||
if (self->nextMovement != 0 && currTime < self->nextMovement)
|
if (self->nextMovement != 0 && currTime < self->nextMovement)
|
||||||
@@ -572,8 +609,8 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) {
|
|||||||
self->nextAttack = 0;
|
self->nextAttack = 0;
|
||||||
|
|
||||||
// halve movement speed if snared
|
// halve movement speed if snared
|
||||||
if (self->hasBuff(ECSB_DN_MOVE_SPEED))
|
if (self->cbf & CSB_BIT_DN_MOVE_SPEED)
|
||||||
speed /= 2;
|
self->speed /= 2;
|
||||||
|
|
||||||
int targetX = plr->x;
|
int targetX = plr->x;
|
||||||
int targetY = plr->y;
|
int targetY = plr->y;
|
||||||
@@ -582,9 +619,9 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) {
|
|||||||
targetY += self->offsetY*distance/(self->idleRange + 1);
|
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);
|
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;
|
self->nextAttack = 0;
|
||||||
|
|
||||||
NPCManager::updateNPCPosition(self->id, targ.first, targ.second, self->z, self->instanceID, self->angle);
|
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);
|
INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt);
|
||||||
|
|
||||||
pkt.iNPC_ID = self->id;
|
pkt.iNPC_ID = self->id;
|
||||||
pkt.iSpeed = speed;
|
pkt.iSpeed = self->speed;
|
||||||
pkt.iToX = self->x = targ.first;
|
pkt.iToX = self->x = targ.first;
|
||||||
pkt.iToY = self->y = targ.second;
|
pkt.iToY = self->y = targ.second;
|
||||||
pkt.iToZ = plr->z;
|
pkt.iToZ = plr->z;
|
||||||
@@ -678,7 +715,7 @@ void MobAI::roamingStep(CombatNPC* npc, time_t currTime) {
|
|||||||
farY = std::clamp(farY, yStart, yStart + self->idleRange);
|
farY = std::clamp(farY, yStart, yStart + self->idleRange);
|
||||||
|
|
||||||
// halve movement speed if snared
|
// halve movement speed if snared
|
||||||
if (self->hasBuff(ECSB_DN_MOVE_SPEED))
|
if (self->cbf & CSB_BIT_DN_MOVE_SPEED)
|
||||||
self->speed /= 2;
|
self->speed /= 2;
|
||||||
|
|
||||||
std::queue<Vec3> queue;
|
std::queue<Vec3> queue;
|
||||||
@@ -751,10 +788,14 @@ void MobAI::onRoamStart(CombatNPC* npc, EntityRef src) {
|
|||||||
self->hp = self->maxHealth;
|
self->hp = self->maxHealth;
|
||||||
self->killedTime = 0;
|
self->killedTime = 0;
|
||||||
self->nextAttack = 0;
|
self->nextAttack = 0;
|
||||||
|
self->cbf = 0;
|
||||||
|
|
||||||
// cast a return home heal spell, this is the right way(tm)
|
// 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
|
// clear outlying debuffs
|
||||||
clearDebuff(self);
|
clearDebuff(self);
|
||||||
}
|
}
|
||||||
@@ -771,9 +812,12 @@ void MobAI::onCombatStart(CombatNPC* npc, EntityRef src) {
|
|||||||
self->roamY = self->y;
|
self->roamY = self->y;
|
||||||
self->roamZ = self->z;
|
self->roamZ = self->z;
|
||||||
|
|
||||||
int skillID = (int)self->data["m_iPassiveBuff"];
|
int skillID = (int)self->data["m_iPassiveBuff"]; // cast passive
|
||||||
if(skillID != 0) // cast passive
|
// TODO ABILITIES
|
||||||
Abilities::useNPCSkill(npc->getRef(), skillID, { npc });
|
/*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) {
|
void MobAI::onRetreat(CombatNPC* npc, EntityRef src) {
|
||||||
@@ -789,8 +833,9 @@ void MobAI::onDeath(CombatNPC* npc, EntityRef src) {
|
|||||||
Mob* self = (Mob*)npc;
|
Mob* self = (Mob*)npc;
|
||||||
|
|
||||||
self->target = nullptr;
|
self->target = nullptr;
|
||||||
|
self->cbf = 0;
|
||||||
self->skillStyle = -1;
|
self->skillStyle = -1;
|
||||||
self->clearBuffs(true);
|
self->unbuffTimes.clear();
|
||||||
self->killedTime = getTime(); // XXX: maybe introduce a shard-global time for each step?
|
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
|
// check for the edge case where hitting the mob did not aggro it
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ namespace MobAI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct Mob : public CombatNPC {
|
struct Mob : public CombatNPC {
|
||||||
|
// general
|
||||||
|
std::unordered_map<int32_t,time_t> unbuffTimes = {};
|
||||||
|
|
||||||
// dead
|
// dead
|
||||||
time_t killedTime = 0;
|
time_t killedTime = 0;
|
||||||
@@ -50,8 +52,8 @@ struct Mob : public CombatNPC {
|
|||||||
// temporary; until we're sure what's what
|
// temporary; until we're sure what's what
|
||||||
nlohmann::json data = {};
|
nlohmann::json data = {};
|
||||||
|
|
||||||
Mob(int spawnX, int spawnY, int spawnZ, int angle, uint64_t iID, int t, nlohmann::json d, int32_t id)
|
Mob(int x, int y, int z, int angle, uint64_t iID, int t, nlohmann::json d, int32_t id)
|
||||||
: CombatNPC(spawnX, spawnY, spawnZ, angle, iID, t, id, d["m_iHP"]),
|
: CombatNPC(x, y, z, angle, iID, t, id, d["m_iHP"]),
|
||||||
sightRange(d["m_iSightRange"]) {
|
sightRange(d["m_iSightRange"]) {
|
||||||
state = AIState::ROAMING;
|
state = AIState::ROAMING;
|
||||||
|
|
||||||
@@ -62,13 +64,15 @@ struct Mob : public CombatNPC {
|
|||||||
idleRange = (int)data["m_iIdleRange"];
|
idleRange = (int)data["m_iIdleRange"];
|
||||||
level = data["m_iNpcLevel"];
|
level = data["m_iNpcLevel"];
|
||||||
|
|
||||||
roamX = spawnX;
|
roamX = x;
|
||||||
roamY = spawnY;
|
roamY = y;
|
||||||
roamZ = spawnZ;
|
roamZ = z;
|
||||||
|
|
||||||
offsetX = 0;
|
offsetX = 0;
|
||||||
offsetY = 0;
|
offsetY = 0;
|
||||||
|
|
||||||
|
cbf = 0;
|
||||||
|
|
||||||
// NOTE: there appear to be discrepancies in the dump
|
// NOTE: there appear to be discrepancies in the dump
|
||||||
hp = maxHealth;
|
hp = maxHealth;
|
||||||
|
|
||||||
|
|||||||
@@ -122,15 +122,16 @@ static void npcUnsummonHandler(CNSocket* sock, CNPacketData* data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// type must already be checked and updateNPCPosition() must be called on the result
|
// type must already be checked and updateNPCPosition() must be called on the result
|
||||||
BaseNPC *NPCManager::summonNPC(int 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;
|
uint64_t inst = baseInstance ? MAPNUM(instance) : instance;
|
||||||
|
|
||||||
|
//assert(nextId < INT32_MAX);
|
||||||
int id = nextId--;
|
int id = nextId--;
|
||||||
int team = NPCData[type]["m_iTeam"];
|
int team = NPCData[type]["m_iTeam"];
|
||||||
BaseNPC *npc = nullptr;
|
BaseNPC *npc = nullptr;
|
||||||
|
|
||||||
if (team == 2) {
|
if (team == 2) {
|
||||||
npc = new Mob(spawnX, spawnY, spawnZ, inst, type, NPCData[type], id);
|
npc = new Mob(x, y, z, inst, type, NPCData[type], id);
|
||||||
|
|
||||||
// re-enable respawning, if desired
|
// re-enable respawning, if desired
|
||||||
((Mob*)npc)->summoned = !respawn;
|
((Mob*)npc)->summoned = !respawn;
|
||||||
@@ -293,55 +294,57 @@ BaseNPC* NPCManager::getNearestNPC(std::set<Chunk*>* chunks, int X, int Y, int Z
|
|||||||
return npc;
|
return npc;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Move this to separate file in ai/ subdir when implementing more events
|
// TODO: Move this to MobAI, possibly
|
||||||
#pragma region NPCEvents
|
#pragma region NPCEvents
|
||||||
|
|
||||||
// summon right arm and stage 2 body
|
// 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
|
Mob *oldbody = (Mob*)npc; // adaptium, stun
|
||||||
|
Player *plr = PlayerManager::getPlayer(sock);
|
||||||
|
|
||||||
std::cout << "Lord Fuse stage two" << std::endl;
|
std::cout << "Lord Fuse stage two" << std::endl;
|
||||||
|
|
||||||
// Fuse doesn't move
|
// Fuse doesn't move
|
||||||
// Blastons, Heal
|
// 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;
|
newbody->angle = oldbody->angle;
|
||||||
NPCManager::updateNPCPosition(newbody->id, newbody->spawnX, newbody->spawnY, newbody->spawnZ,
|
NPCManager::updateNPCPosition(newbody->id, newbody->x, newbody->y, newbody->z,
|
||||||
oldbody->instanceID, oldbody->angle);
|
plr->instanceID, oldbody->angle);
|
||||||
|
|
||||||
// right arm, Adaptium, Stun
|
// 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;
|
arm->angle = oldbody->angle;
|
||||||
NPCManager::updateNPCPosition(arm->id, arm->spawnX, arm->spawnY, arm->spawnZ,
|
NPCManager::updateNPCPosition(arm->id, arm->x, arm->y, arm->z,
|
||||||
oldbody->instanceID, oldbody->angle);
|
plr->instanceID, oldbody->angle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// summon left arm and stage 3 body
|
// summon left arm and stage 3 body
|
||||||
static void lordFuseStageThree(CombatNPC *npc) {
|
static void lordFuseStageThree(CNSocket *sock, BaseNPC *npc) {
|
||||||
Mob *oldbody = (Mob*)npc;
|
Mob *oldbody = (Mob*)npc;
|
||||||
|
Player *plr = PlayerManager::getPlayer(sock);
|
||||||
|
|
||||||
std::cout << "Lord Fuse stage three" << std::endl;
|
std::cout << "Lord Fuse stage three" << std::endl;
|
||||||
|
|
||||||
// Cosmix, Damage Point
|
// Cosmix, Damage Point
|
||||||
Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->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;
|
newbody->angle = oldbody->angle;
|
||||||
NPCManager::updateNPCPosition(newbody->id, newbody->spawnX, newbody->spawnY, newbody->spawnZ,
|
NPCManager::updateNPCPosition(newbody->id, newbody->x, newbody->y, newbody->z,
|
||||||
newbody->instanceID, oldbody->angle);
|
plr->instanceID, oldbody->angle);
|
||||||
|
|
||||||
// Blastons, Heal
|
// 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;
|
arm->angle = oldbody->angle;
|
||||||
NPCManager::updateNPCPosition(arm->id, arm->spawnX, arm->spawnY, arm->spawnZ,
|
NPCManager::updateNPCPosition(arm->id, arm->x, arm->y, arm->z,
|
||||||
arm->instanceID, oldbody->angle);
|
plr->instanceID, oldbody->angle);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<NPCEvent> NPCManager::NPCEvents = {
|
std::vector<NPCEvent> NPCManager::NPCEvents = {
|
||||||
NPCEvent(2466, AIState::DEAD, lordFuseStageTwo),
|
NPCEvent(2466, ON_KILLED, lordFuseStageTwo),
|
||||||
NPCEvent(2467, AIState::DEAD, lordFuseStageThree),
|
NPCEvent(2467, ON_KILLED, lordFuseStageThree),
|
||||||
};
|
};
|
||||||
|
|
||||||
#pragma endregion NPCEvents
|
#pragma endregion NPCEvents
|
||||||
@@ -373,5 +376,5 @@ void NPCManager::init() {
|
|||||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_NPC_UNSUMMON, npcUnsummonHandler);
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_NPC_UNSUMMON, npcUnsummonHandler);
|
||||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_BARKER, npcBarkHandler);
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_BARKER, npcBarkHandler);
|
||||||
|
|
||||||
REGISTER_SHARD_TIMER(step, MS_PER_COMBAT_TICK);
|
REGISTER_SHARD_TIMER(step, 200);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,15 +14,20 @@
|
|||||||
|
|
||||||
#define RESURRECT_HEIGHT 400
|
#define RESURRECT_HEIGHT 400
|
||||||
|
|
||||||
typedef void (*NPCEventHandler)(CombatNPC*);
|
enum Trigger {
|
||||||
|
ON_KILLED,
|
||||||
|
ON_COMBAT
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef void (*NPCEventHandler)(CNSocket*, BaseNPC*);
|
||||||
|
|
||||||
struct NPCEvent {
|
struct NPCEvent {
|
||||||
int32_t npcType;
|
int32_t npcType;
|
||||||
AIState triggerState;
|
int trigger;
|
||||||
NPCEventHandler handler;
|
NPCEventHandler handler;
|
||||||
|
|
||||||
NPCEvent(int32_t t, AIState tr, NPCEventHandler hndlr)
|
NPCEvent(int32_t t, int tr, NPCEventHandler hndlr)
|
||||||
: npcType(t), triggerState(tr), handler(hndlr) {}
|
: npcType(t), trigger(tr), handler(hndlr) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace NPCManager {
|
namespace NPCManager {
|
||||||
|
|||||||
@@ -92,8 +92,7 @@ struct Player : public Entity, public ICombatant {
|
|||||||
virtual bool addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) override;
|
virtual bool addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) override;
|
||||||
virtual Buff* getBuff(int buffId) override;
|
virtual Buff* getBuff(int buffId) override;
|
||||||
virtual void removeBuff(int buffId) override;
|
virtual void removeBuff(int buffId) override;
|
||||||
virtual void removeBuff(int buffId, BuffClass buffClass) override;
|
virtual void removeBuff(int buffId, int buffClass) override;
|
||||||
virtual void clearBuffs(bool force) override;
|
|
||||||
virtual bool hasBuff(int buffId) override;
|
virtual bool hasBuff(int buffId) override;
|
||||||
virtual int getCompositeCondition() override;
|
virtual int getCompositeCondition() override;
|
||||||
virtual int takeDamage(EntityRef src, int amt) override;
|
virtual int takeDamage(EntityRef src, int amt) override;
|
||||||
|
|||||||
@@ -416,7 +416,6 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) {
|
|||||||
|
|
||||||
default: // plain respawn
|
default: // plain respawn
|
||||||
plr->HP = PC_MAXHEALTH(plr->level) / 2;
|
plr->HP = PC_MAXHEALTH(plr->level) / 2;
|
||||||
plr->clearBuffs(false);
|
|
||||||
// fallthrough
|
// fallthrough
|
||||||
case ePCRegenType::Unstick: // warp away
|
case ePCRegenType::Unstick: // warp away
|
||||||
move = true;
|
move = true;
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
#define REGISTER_SHARD_PACKET(pactype, handlr) CNShardServer::ShardPackets[pactype] = handlr;
|
#define REGISTER_SHARD_PACKET(pactype, handlr) CNShardServer::ShardPackets[pactype] = handlr;
|
||||||
#define REGISTER_SHARD_TIMER(handlr, delta) CNShardServer::Timers.push_back(TimerEvent(handlr, delta));
|
#define REGISTER_SHARD_TIMER(handlr, delta) CNShardServer::Timers.push_back(TimerEvent(handlr, delta));
|
||||||
#define MS_PER_PLAYER_TICK 500
|
#define MS_PER_PLAYER_TICK 500
|
||||||
#define MS_PER_COMBAT_TICK 200
|
|
||||||
|
|
||||||
class CNShardServer : public CNServer {
|
class CNShardServer : public CNServer {
|
||||||
private:
|
private:
|
||||||
|
|||||||
Reference in New Issue
Block a user