mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2025-11-17 15:30:06 +00:00
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:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user