Groups, Group Nano powers and Revive (#129)

* Initial Group Implementation

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

* Added more nano powers

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

* Further Nano powers + Bugfixes

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

* Dismiss nano when starting a MSS ride

* Sneak, Invisibility and Bugfixes

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

* Group PR cleanup.

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

Didn't have the opportunity to actually test groups.

Co-authored-by: CakeLancelot <CakeLancelot@users.noreply.github.com>
Co-authored-by: CPunch <sethtstubbs@gmail.com>
Co-authored-by: dongresource <dongresource@protonmail.com>
This commit is contained in:
JadeShrineMaiden
2020-10-05 00:54:08 +01:00
committed by GitHub
parent 131eb94919
commit b8f586bc10
15 changed files with 806 additions and 130 deletions

View File

@@ -4,6 +4,7 @@
#include "NPCManager.hpp"
#include "ItemManager.hpp"
#include "MissionManager.hpp"
#include "GroupManager.hpp"
#include <cmath>
#include <assert.h>
@@ -95,7 +96,7 @@ void MobManager::pcAttackNpcs(CNSocket *sock, CNPacketData *data) {
PlayerManager::sendToViewable(sock, (void*)respbuf, P_FE2CL_PC_ATTACK_NPCs, resplen);
}
void MobManager::npcAttackPc(Mob *mob) {
void MobManager::npcAttackPc(Mob *mob, time_t currTime) {
Player *plr = PlayerManager::getPlayer(mob->target);
if (plr == nullptr)
@@ -126,6 +127,7 @@ void MobManager::npcAttackPc(Mob *mob) {
if (plr->HP <= 0) {
mob->target = nullptr;
mob->state = MobState::RETREAT;
aggroCheck(mob, currTime);
}
}
@@ -267,8 +269,10 @@ void MobManager::combatStep(Mob *mob, time_t currTime) {
if (PlayerManager::players.find(mob->target) == PlayerManager::players.end()) {
mob->target = nullptr;
mob->state = MobState::RETREAT;
aggroCheck(mob, currTime);
return;
}
Player *plr = PlayerManager::getPlayer(mob->target);
if (plr == nullptr)
@@ -278,6 +282,7 @@ void MobManager::combatStep(Mob *mob, time_t currTime) {
if (plr->HP <= 0) {
mob->target = nullptr;
mob->state = MobState::RETREAT;
aggroCheck(mob, currTime);
return;
}
@@ -291,10 +296,10 @@ void MobManager::combatStep(Mob *mob, time_t currTime) {
// attack logic
if (mob->nextAttack == 0) {
mob->nextAttack = currTime + (int)mob->data["m_iInitalTime"] * 100; // I *think* this is what this is
npcAttackPc(mob);
npcAttackPc(mob, currTime);
} else if (mob->nextAttack != 0 && currTime >= mob->nextAttack) {
mob->nextAttack = currTime + (int)mob->data["m_iDelayTime"] * 100;
npcAttackPc(mob);
npcAttackPc(mob, currTime);
}
} else {
// movement logic
@@ -344,31 +349,9 @@ void MobManager::roamingStep(Mob *mob, time_t currTime) {
* do so more often than if we waited for nextMovement (which is way too slow).
*/
if (mob->nextAttack == 0 || currTime >= mob->nextAttack) {
mob->nextAttack = currTime + (int)mob->data["m_iDelayTime"] * 100;
/*
* Aggro on nearby players.
* Even if they're in range, we can't assume they're all in the same one chunk
* as the mob, since it might be near a chunk boundary.
*/
for (Chunk *chunk : mob->currentChunks) {
for (CNSocket *s : chunk->players) {
Player *plr = s->plr;
// height is relevant for aggro distance because of platforming
int xyDistance = hypot(mob->appearanceData.iX - plr->x, mob->appearanceData.iY - plr->y);
int distance = hypot(xyDistance, mob->appearanceData.iZ - plr->z);
if (distance > mob->data["m_iSightRange"])
continue;
// found player. engage.
mob->target = s;
mob->state = MobState::COMBAT;
mob->nextMovement = currTime;
mob->nextAttack = 0;
return;
}
}
mob->nextAttack = currTime + 500;
if (aggroCheck(mob, currTime))
return;
}
// some mobs don't move (and we mustn't divide/modulus by zero)
@@ -439,15 +422,11 @@ void MobManager::retreatStep(Mob *mob, time_t currTime) {
mob->nextAttack = 0;
mob->appearanceData.iConditionBitFlag = 0;
//INITSTRUCT(sP_FE2CL_NPC_ENTER, enterData);
//enterData.NPCAppearanceData = mob->appearanceData;
//NPCManager::sendToViewable(mob, &enterData, P_FE2CL_NPC_ENTER, sizeof(sP_FE2CL_NPC_ENTER));
resendMobHP(mob);
}
}
void MobManager::step(CNServer *serv, time_t currTime) {
for (auto& pair : Mobs) {
int x = pair.second->appearanceData.iX;
int y = pair.second->appearanceData.iY;
@@ -525,8 +504,21 @@ std::pair<int,int> MobManager::lerp(int x1, int y1, int x2, int y2, int speed) {
void MobManager::combatBegin(CNSocket *sock, CNPacketData *data) {
Player *plr = PlayerManager::getPlayer(sock);
if (plr != nullptr)
plr->inCombat = true;
if (plr == nullptr) {
std::cout << "[WARN] combatBegin: null player!" << std::endl;
return;
}
plr->inCombat = true;
// HACK: make sure the player has the right weapon out for combat
INITSTRUCT(sP_FE2CL_PC_EQUIP_CHANGE, resp);
resp.iPC_ID = plr->iID;
resp.iEquipSlotNum = 0;
resp.EquipSlotItem = plr->Equip[0];
PlayerManager::sendToViewable(sock, (void*)&resp, P_FE2CL_PC_EQUIP_CHANGE, sizeof(sP_FE2CL_PC_EQUIP_CHANGE));
}
void MobManager::combatEnd(CNSocket *sock, CNPacketData *data) {
@@ -540,26 +532,58 @@ void MobManager::dotDamageOnOff(CNSocket *sock, CNPacketData *data) {
sP_CL2FE_DOT_DAMAGE_ONOFF *pkt = (sP_CL2FE_DOT_DAMAGE_ONOFF*)data->buf;
Player *plr = PlayerManager::getPlayer(sock);
if (plr != nullptr)
plr->dotDamage = (bool)pkt->iFlag;
if (plr == nullptr) {
std::cout << "[WARN] dotDamageOnOff: null player!" << std::endl;
return;
}
if ((plr->iConditionBitFlag & CSB_BIT_INFECTION) != (bool)pkt->iFlag)
plr->iConditionBitFlag ^= CSB_BIT_INFECTION;
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt1);
pkt1.eCSTB = ECSB_INFECTION; //eCharStatusTimeBuffID
pkt1.eTBU = 1; //eTimeBuffUpdate
pkt1.eTBT = 0; //eTimeBuffType 1 means nano
pkt1.iConditionBitFlag = plr->iConditionBitFlag;
sock->sendPacket((void*)&pkt1, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
}
void MobManager::dealGooDamage(CNSocket *sock, int amount) {
size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_DotDamage);
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
Player *plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return;
memset(respbuf, 0, resplen);
sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK *pkt = (sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK*)respbuf;
sSkillResult_DotDamage *dmg = (sSkillResult_DotDamage*)(respbuf + sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK));
Player *plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return;
if (plr->iConditionBitFlag & CSB_BIT_PROTECT_INFECTION) {
amount = -2; // -2 is the magic number for "Protected" to appear as the damage number
dmg->bProtected = 1;
// update player
plr->HP -= amount;
// it's hypothetically possible to have the protection bit without a nano
if (plr->activeNano != -1)
plr->Nanos[plr->activeNano].iStamina -= 3;
} else {
plr->HP -= amount;
}
if (plr->activeNano != -1) {
dmg->iStamina = plr->Nanos[plr->activeNano].iStamina;
if (plr->Nanos[plr->activeNano].iStamina <= 0) {
dmg->bNanoDeactive = 1;
plr->Nanos[plr->activeNano].iStamina = 0;
NanoManager::summonNano(PlayerManager::getSockFromID(plr->iID), -1);
}
}
pkt->iID = plr->iID;
pkt->eCT = 1; // player
@@ -569,6 +593,7 @@ void MobManager::dealGooDamage(CNSocket *sock, int amount) {
dmg->iID = plr->iID;
dmg->iDamage = amount;
dmg->iHP = plr->HP;
dmg->iConditionBitFlag = plr->iConditionBitFlag;
sock->sendPacket((void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen);
PlayerManager::sendToViewable(sock, (void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen);
@@ -581,13 +606,17 @@ void MobManager::playerTick(CNServer *serv, time_t currTime) {
CNSocket *sock = pair.first;
Player *plr = pair.second.plr;
bool transmit = false;
// group ticks
if (plr->groupCnt > 1)
GroupManager::groupTickInfo(plr);
// do not tick dead players
if (plr->HP <= 0)
continue;
// fm patch/lake damage
if (plr->dotDamage)
if (plr->iConditionBitFlag & CSB_BIT_INFECTION)
dealGooDamage(sock, PC_MAXHEALTH(plr->level) * 3 / 20);
// heal
@@ -605,8 +634,10 @@ void MobManager::playerTick(CNServer *serv, time_t currTime) {
if (plr->passiveNanoOut)
plr->Nanos[plr->activeNano].iStamina -= 1;
if (plr->Nanos[plr->activeNano].iStamina < 0)
if (plr->Nanos[plr->activeNano].iStamina <= 0) {
plr->Nanos[plr->activeNano].iStamina = 0;
NanoManager::summonNano(PlayerManager::getSockFromID(plr->iID), -1);
}
transmit = true;
} else if (plr->Nanos[plr->equippedNanos[i]].iStamina < 150) { // regain stamina
@@ -754,6 +785,7 @@ void MobManager::pcAttackChars(CNSocket *sock, CNPacketData *data) {
PlayerManager::sendToViewable(sock, (void*)respbuf, P_FE2CL_PC_ATTACK_CHARs, resplen);
}
// HACK: we haven't found a better way to refresh a mob's client-side status
void MobManager::resendMobHP(Mob *mob) {
size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_Heal_HP);
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
@@ -775,3 +807,43 @@ void MobManager::resendMobHP(Mob *mob) {
NPCManager::sendToViewable(mob, (void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen);
}
/*
* Aggro on nearby players.
* Even if they're in range, we can't assume they're all in the same one chunk
* as the mob, since it might be near a chunk boundary.
*/
bool MobManager::aggroCheck(Mob *mob, time_t currTime) {
for (Chunk *chunk : mob->currentChunks) {
for (CNSocket *s : chunk->players) {
Player *plr = s->plr;
if (plr->HP <= 0)
continue;
int mobRange = mob->data["m_iSightRange"];
if (plr->iConditionBitFlag & CSB_BIT_UP_STEALTH)
mobRange /= 3;
if (plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVISIBLE)
mobRange = -1;
// height is relevant for aggro distance because of platforming
int xyDistance = hypot(mob->appearanceData.iX - plr->x, mob->appearanceData.iY - plr->y);
int distance = hypot(xyDistance, mob->appearanceData.iZ - plr->z);
if (distance > mobRange)
continue;
// found player. engage.
mob->target = s;
mob->state = MobState::COMBAT;
mob->nextMovement = currTime;
mob->nextAttack = 0;
return true;
}
}
return false;
}