2021-03-13 22:55:16 +00:00
|
|
|
#include "Combat.hpp"
|
2020-08-27 20:16:52 +00:00
|
|
|
#include "PlayerManager.hpp"
|
2021-03-16 22:29:13 +00:00
|
|
|
#include "Nanos.hpp"
|
2020-08-27 20:16:52 +00:00
|
|
|
#include "NPCManager.hpp"
|
2021-03-16 22:29:13 +00:00
|
|
|
#include "Items.hpp"
|
|
|
|
#include "Missions.hpp"
|
|
|
|
#include "Groups.hpp"
|
|
|
|
#include "Transport.hpp"
|
|
|
|
#include "Racing.hpp"
|
2021-03-13 20:22:29 +00:00
|
|
|
#include "Abilities.hpp"
|
2021-03-28 20:57:43 +00:00
|
|
|
#include "Rand.hpp"
|
2020-08-27 20:16:52 +00:00
|
|
|
|
2020-08-28 16:25:03 +00:00
|
|
|
#include <assert.h>
|
2020-08-27 20:16:52 +00:00
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
using namespace Combat;
|
|
|
|
|
2020-11-12 00:46:53 +00:00
|
|
|
/// Player Id -> Bullet Id -> Bullet
|
2021-03-13 22:55:16 +00:00
|
|
|
std::map<int32_t, std::map<int8_t, Bullet>> Combat::Bullets;
|
2020-10-07 18:38:32 +00:00
|
|
|
|
2022-04-11 14:26:57 +00:00
|
|
|
int Player::takeDamage(EntityRef src, int amt) {
|
|
|
|
HP -= amt;
|
|
|
|
return amt;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Player::heal(EntityRef src, int amt) {
|
|
|
|
// stubbed
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Player::isAlive() {
|
|
|
|
return HP > 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int Player::getCurrentHP() {
|
|
|
|
return HP;
|
|
|
|
}
|
|
|
|
|
|
|
|
int32_t Player::getID() {
|
|
|
|
return iID;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Player::step(time_t currTime) {
|
|
|
|
// no-op
|
|
|
|
}
|
|
|
|
|
|
|
|
int CombatNPC::takeDamage(EntityRef src, int amt) {
|
|
|
|
|
|
|
|
hp -= amt;
|
2022-04-13 19:09:43 +00:00
|
|
|
if (hp <= 0)
|
2022-04-11 14:26:57 +00:00
|
|
|
transition(AIState::DEAD, src);
|
|
|
|
|
|
|
|
return amt;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CombatNPC::heal(EntityRef src, int amt) {
|
|
|
|
// stubbed
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CombatNPC::isAlive() {
|
|
|
|
return hp > 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int CombatNPC::getCurrentHP() {
|
|
|
|
return hp;
|
|
|
|
}
|
|
|
|
|
|
|
|
int32_t CombatNPC::getID() {
|
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CombatNPC::step(time_t currTime) {
|
2022-04-13 19:09:43 +00:00
|
|
|
|
|
|
|
if(stateHandlers.find(state) != stateHandlers.end())
|
|
|
|
stateHandlers[state](this, currTime);
|
|
|
|
else {
|
|
|
|
std::cout << "[WARN] State " << (int)state << " has no handler; going inactive" << std::endl;
|
|
|
|
transition(AIState::INACTIVE, id);
|
2022-04-11 14:26:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CombatNPC::transition(AIState newState, EntityRef src) {
|
2022-04-13 19:09:43 +00:00
|
|
|
|
2022-04-11 14:26:57 +00:00
|
|
|
state = newState;
|
2022-04-13 19:09:43 +00:00
|
|
|
if (transitionHandlers.find(newState) != transitionHandlers.end())
|
|
|
|
transitionHandlers[newState](this, src);
|
|
|
|
else {
|
|
|
|
std::cout << "[WARN] Transition to " << (int)state << " has no handler; going inactive" << std::endl;
|
|
|
|
transition(AIState::INACTIVE, id);
|
2022-04-11 14:26:57 +00:00
|
|
|
}
|
2022-04-13 19:09:43 +00:00
|
|
|
/* TODO: fire any triggered events
|
|
|
|
for (NPCEvent& event : NPCManager::NPCEvents)
|
|
|
|
if (event.trigger == ON_KILLED && event.npcType == type)
|
|
|
|
event.handler(src, this);
|
|
|
|
*/
|
2022-04-11 14:26:57 +00:00
|
|
|
}
|
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
static std::pair<int,int> getDamage(int attackPower, int defensePower, bool shouldCrit,
|
|
|
|
bool batteryBoost, int attackerStyle,
|
|
|
|
int defenderStyle, int difficulty) {
|
|
|
|
std::pair<int,int> ret = {0, 1};
|
|
|
|
if (attackPower + defensePower * 2 == 0)
|
|
|
|
return ret;
|
2020-09-22 19:31:08 +00:00
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
// base calculation
|
|
|
|
int damage = attackPower * attackPower / (attackPower + defensePower);
|
|
|
|
damage = std::max(10 + attackPower / 10, damage - (defensePower - attackPower / 6) * difficulty / 100);
|
2021-03-28 20:57:43 +00:00
|
|
|
damage = damage * (Rand::rand(40) + 80) / 100;
|
2020-08-27 20:16:52 +00:00
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
// Adaptium/Blastons/Cosmix
|
|
|
|
if (attackerStyle != -1 && defenderStyle != -1 && attackerStyle != defenderStyle) {
|
|
|
|
if (attackerStyle - defenderStyle == 2)
|
|
|
|
defenderStyle += 3;
|
|
|
|
if (defenderStyle - attackerStyle == 2)
|
|
|
|
defenderStyle -= 3;
|
2021-03-28 20:57:43 +00:00
|
|
|
if (attackerStyle < defenderStyle)
|
2021-03-16 21:06:10 +00:00
|
|
|
damage = damage * 5 / 4;
|
|
|
|
else
|
|
|
|
damage = damage * 4 / 5;
|
|
|
|
}
|
2020-10-07 18:38:32 +00:00
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
// weapon boosts
|
|
|
|
if (batteryBoost)
|
|
|
|
damage = damage * 5 / 4;
|
|
|
|
|
|
|
|
ret.first = damage;
|
|
|
|
ret.second = 1;
|
|
|
|
|
2021-03-28 20:57:43 +00:00
|
|
|
if (shouldCrit && Rand::rand(20) == 0) {
|
2021-03-16 21:06:10 +00:00
|
|
|
ret.first *= 2; // critical hit
|
|
|
|
ret.second = 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
2020-08-27 20:16:52 +00:00
|
|
|
}
|
|
|
|
|
2022-11-26 18:36:10 +00:00
|
|
|
static bool checkRapidFire(CNSocket *sock, int targetCount) {
|
2020-08-28 20:01:49 +00:00
|
|
|
Player *plr = PlayerManager::getPlayer(sock);
|
2020-12-31 05:54:57 +00:00
|
|
|
time_t currTime = getTime();
|
2022-11-26 18:36:10 +00:00
|
|
|
|
2020-12-31 05:54:57 +00:00
|
|
|
if (currTime - plr->lastShot < plr->fireRate * 80)
|
|
|
|
plr->suspicionRating += plr->fireRate * 100 + plr->lastShot - currTime; // gain suspicion for rapid firing
|
|
|
|
else if (currTime - plr->lastShot < plr->fireRate * 180 && plr->suspicionRating > 0)
|
|
|
|
plr->suspicionRating += plr->fireRate * 100 + plr->lastShot - currTime; // lose suspicion for delayed firing
|
|
|
|
|
|
|
|
plr->lastShot = currTime;
|
|
|
|
|
2022-11-26 18:36:10 +00:00
|
|
|
// 3+ targets should never be possible
|
|
|
|
if (targetCount > 3)
|
2023-03-01 17:18:41 +00:00
|
|
|
plr->suspicionRating += 10001;
|
2020-12-31 05:54:57 +00:00
|
|
|
|
2022-11-26 18:36:10 +00:00
|
|
|
// kill the socket when the player is too suspicious
|
|
|
|
if (plr->suspicionRating > 10000) {
|
2020-12-31 05:54:57 +00:00
|
|
|
sock->kill();
|
2021-05-27 04:23:05 +00:00
|
|
|
CNShardServer::_killConnection(sock);
|
2022-11-26 18:36:10 +00:00
|
|
|
return true;
|
2021-05-27 04:23:05 +00:00
|
|
|
}
|
2022-11-26 18:36:10 +00:00
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) {
|
|
|
|
auto pkt = (sP_CL2FE_REQ_PC_ATTACK_NPCs*)data->buf;
|
|
|
|
Player *plr = PlayerManager::getPlayer(sock);
|
|
|
|
auto targets = (int32_t*)data->trailers;
|
|
|
|
|
|
|
|
// kick the player if firing too rapidly
|
|
|
|
if (settings::ANTICHEAT && checkRapidFire(sock, pkt->iNPCCnt))
|
|
|
|
return;
|
2020-12-31 05:54:57 +00:00
|
|
|
|
2021-03-04 18:48:02 +00:00
|
|
|
/*
|
2021-03-19 21:29:14 +00:00
|
|
|
* IMPORTANT: This validates memory safety in addition to preventing
|
|
|
|
* ordinary cheating. If the client sends a very large number of trailing
|
|
|
|
* values, it could overflow the *response* buffer, which isn't otherwise
|
|
|
|
* being validated anymore.
|
2021-03-04 18:48:02 +00:00
|
|
|
*/
|
2021-03-19 21:29:14 +00:00
|
|
|
if (pkt->iNPCCnt > 3) {
|
|
|
|
std::cout << "[WARN] Player tried to attack more than 3 NPCs at once" << std::endl;
|
2021-03-04 18:48:02 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-03-19 21:29:14 +00:00
|
|
|
INITVARPACKET(respbuf, sP_FE2CL_PC_ATTACK_NPCs_SUCC, resp, sAttackResult, respdata);
|
2020-08-27 20:16:52 +00:00
|
|
|
|
|
|
|
resp->iNPCCnt = pkt->iNPCCnt;
|
|
|
|
|
2021-03-19 21:29:14 +00:00
|
|
|
for (int i = 0; i < data->trCnt; i++) {
|
2021-04-07 00:43:43 +00:00
|
|
|
if (NPCManager::NPCs.find(targets[i]) == NPCManager::NPCs.end()) {
|
2020-08-27 20:16:52 +00:00
|
|
|
// not sure how to best handle this
|
2021-04-07 00:43:43 +00:00
|
|
|
std::cout << "[WARN] pcAttackNpcs: NPC ID not found" << std::endl;
|
2020-08-27 20:16:52 +00:00
|
|
|
return;
|
|
|
|
}
|
2021-04-07 00:43:43 +00:00
|
|
|
|
|
|
|
|
|
|
|
BaseNPC* npc = NPCManager::NPCs[targets[i]];
|
2022-04-13 19:09:43 +00:00
|
|
|
if (npc->kind != EntityKind::MOB) {
|
2021-04-07 00:43:43 +00:00
|
|
|
std::cout << "[WARN] pcAttackNpcs: NPC is not a mob" << std::endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Mob* mob = (Mob*)npc;
|
2020-08-27 20:16:52 +00:00
|
|
|
|
2020-09-27 01:53:03 +00:00
|
|
|
std::pair<int,int> damage;
|
|
|
|
|
|
|
|
if (pkt->iNPCCnt > 1)
|
2020-10-09 00:01:35 +00:00
|
|
|
damage.first = plr->groupDamage;
|
2020-09-27 01:53:03 +00:00
|
|
|
else
|
2020-10-09 00:01:35 +00:00
|
|
|
damage.first = plr->pointDamage;
|
2020-10-19 17:26:14 +00:00
|
|
|
|
2020-10-09 00:01:35 +00:00
|
|
|
int difficulty = (int)mob->data["m_iNpcLevel"];
|
2021-03-19 21:29:14 +00:00
|
|
|
damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, (plr->batteryW > 6 + difficulty),
|
|
|
|
Nanos::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty);
|
2021-03-28 20:57:43 +00:00
|
|
|
|
2020-10-21 05:24:51 +00:00
|
|
|
if (plr->batteryW >= 6 + difficulty)
|
|
|
|
plr->batteryW -= 6 + difficulty;
|
|
|
|
else
|
|
|
|
plr->batteryW = 0;
|
2020-09-27 01:53:03 +00:00
|
|
|
|
2022-04-11 14:26:57 +00:00
|
|
|
damage.first = mob->takeDamage(sock, damage.first);
|
2020-09-16 23:43:48 +00:00
|
|
|
|
2021-06-20 18:37:37 +00:00
|
|
|
respdata[i].iID = mob->id;
|
2020-09-27 01:53:03 +00:00
|
|
|
respdata[i].iDamage = damage.first;
|
2021-06-20 18:37:37 +00:00
|
|
|
respdata[i].iHP = mob->hp;
|
2020-09-27 01:53:03 +00:00
|
|
|
respdata[i].iHitFlag = damage.second; // hitscan, not a rocket or a grenade
|
2020-08-27 20:16:52 +00:00
|
|
|
}
|
|
|
|
|
2020-09-27 06:58:33 +00:00
|
|
|
resp->iBatteryW = plr->batteryW;
|
2021-03-19 21:29:14 +00:00
|
|
|
sock->sendPacket(respbuf, P_FE2CL_PC_ATTACK_NPCs_SUCC);
|
2020-08-28 20:01:49 +00:00
|
|
|
|
2020-09-09 19:08:31 +00:00
|
|
|
// a bit of a hack: these are the same size, so we can reuse the response packet
|
2020-08-28 20:01:49 +00:00
|
|
|
assert(sizeof(sP_FE2CL_PC_ATTACK_NPCs_SUCC) == sizeof(sP_FE2CL_PC_ATTACK_NPCs));
|
2021-03-19 21:29:14 +00:00
|
|
|
auto *resp1 = (sP_FE2CL_PC_ATTACK_NPCs*)respbuf;
|
2020-08-28 20:01:49 +00:00
|
|
|
|
|
|
|
resp1->iPC_ID = plr->iID;
|
|
|
|
|
|
|
|
// send to other players
|
2021-03-19 21:29:14 +00:00
|
|
|
PlayerManager::sendToViewable(sock, respbuf, P_FE2CL_PC_ATTACK_NPCs);
|
2020-08-27 20:16:52 +00:00
|
|
|
}
|
|
|
|
|
2021-03-13 22:55:16 +00:00
|
|
|
void Combat::npcAttackPc(Mob *mob, time_t currTime) {
|
2020-09-24 01:18:41 +00:00
|
|
|
Player *plr = PlayerManager::getPlayer(mob->target);
|
|
|
|
|
2021-03-19 21:29:14 +00:00
|
|
|
INITVARPACKET(respbuf, sP_FE2CL_NPC_ATTACK_PCs, pkt, sAttackResult, atk);
|
2020-09-24 01:18:41 +00:00
|
|
|
|
2021-09-05 18:34:27 +00:00
|
|
|
auto damage = getDamage(450 + (int)mob->data["m_iPower"], plr->defense, true, false, -1, -1, 0);
|
2020-12-01 20:34:14 +00:00
|
|
|
|
|
|
|
if (!(plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE))
|
|
|
|
plr->HP -= damage.first;
|
2020-09-24 01:18:41 +00:00
|
|
|
|
2021-06-20 18:37:37 +00:00
|
|
|
pkt->iNPC_ID = mob->id;
|
2020-09-24 01:18:41 +00:00
|
|
|
pkt->iPCCnt = 1;
|
|
|
|
|
|
|
|
atk->iID = plr->iID;
|
2020-09-27 01:53:03 +00:00
|
|
|
atk->iDamage = damage.first;
|
2020-09-24 01:18:41 +00:00
|
|
|
atk->iHP = plr->HP;
|
2020-09-27 01:53:03 +00:00
|
|
|
atk->iHitFlag = damage.second;
|
2020-09-24 01:18:41 +00:00
|
|
|
|
2021-03-19 21:29:14 +00:00
|
|
|
mob->target->sendPacket(respbuf, P_FE2CL_NPC_ATTACK_PCs);
|
|
|
|
PlayerManager::sendToViewable(mob->target, respbuf, P_FE2CL_NPC_ATTACK_PCs);
|
2020-09-24 01:18:41 +00:00
|
|
|
|
|
|
|
if (plr->HP <= 0) {
|
2022-04-11 14:26:57 +00:00
|
|
|
if (!MobAI::aggroCheck(mob, getTime()))
|
|
|
|
mob->transition(AIState::RETREAT, mob->target);
|
2020-09-24 01:18:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-12 22:53:04 +00:00
|
|
|
/*
|
|
|
|
* When a group of players is doing missions together, we want them to all get
|
|
|
|
* quest items at the same time, but we don't want the odds of quest item
|
|
|
|
* drops from different missions to be linked together. That's why we use a
|
|
|
|
* single RNG roll per mission task, and every group member shares that same
|
|
|
|
* set of rolls.
|
|
|
|
*/
|
2022-04-11 14:26:57 +00:00
|
|
|
void Combat::genQItemRolls(Player *leader, std::map<int, int>& rolls) {
|
2022-02-12 22:53:04 +00:00
|
|
|
for (int i = 0; i < leader->groupCnt; i++) {
|
|
|
|
if (leader->groupIDs[i] == 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
CNSocket *otherSock = PlayerManager::getSockFromID(leader->groupIDs[i]);
|
|
|
|
if (otherSock == nullptr)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
Player *member = PlayerManager::getPlayer(otherSock);
|
|
|
|
|
|
|
|
for (int j = 0; j < ACTIVE_MISSION_COUNT; j++)
|
|
|
|
if (member->tasks[j] != 0)
|
|
|
|
rolls[member->tasks[j]] = Rand::rand();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
static void combatBegin(CNSocket *sock, CNPacketData *data) {
|
2020-09-26 22:16:15 +00:00
|
|
|
Player *plr = PlayerManager::getPlayer(sock);
|
2020-09-28 18:11:13 +00:00
|
|
|
|
2020-10-04 23:54:08 +00:00
|
|
|
plr->inCombat = true;
|
2020-10-05 00:03:13 +00:00
|
|
|
|
2020-10-04 23:54:08 +00:00
|
|
|
// 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];
|
2020-10-05 00:03:13 +00:00
|
|
|
|
2020-10-04 23:54:08 +00:00
|
|
|
PlayerManager::sendToViewable(sock, (void*)&resp, P_FE2CL_PC_EQUIP_CHANGE, sizeof(sP_FE2CL_PC_EQUIP_CHANGE));
|
2020-09-26 22:16:15 +00:00
|
|
|
}
|
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
static void combatEnd(CNSocket *sock, CNPacketData *data) {
|
2020-09-26 22:16:15 +00:00
|
|
|
Player *plr = PlayerManager::getPlayer(sock);
|
2020-09-28 18:11:13 +00:00
|
|
|
|
2021-03-19 21:29:14 +00:00
|
|
|
plr->inCombat = false;
|
|
|
|
plr->healCooldown = 4000;
|
2020-09-26 22:16:15 +00:00
|
|
|
}
|
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
static void dotDamageOnOff(CNSocket *sock, CNPacketData *data) {
|
2020-09-26 22:16:15 +00:00
|
|
|
sP_CL2FE_DOT_DAMAGE_ONOFF *pkt = (sP_CL2FE_DOT_DAMAGE_ONOFF*)data->buf;
|
|
|
|
Player *plr = PlayerManager::getPlayer(sock);
|
|
|
|
|
2020-10-04 23:54:08 +00:00
|
|
|
if ((plr->iConditionBitFlag & CSB_BIT_INFECTION) != (bool)pkt->iFlag)
|
|
|
|
plr->iConditionBitFlag ^= CSB_BIT_INFECTION;
|
2020-10-05 00:03:13 +00:00
|
|
|
|
2020-10-04 23:54:08 +00:00
|
|
|
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt1);
|
|
|
|
|
2020-10-19 17:26:14 +00:00
|
|
|
pkt1.eCSTB = ECSB_INFECTION; // eCharStatusTimeBuffID
|
|
|
|
pkt1.eTBU = 1; // eTimeBuffUpdate
|
|
|
|
pkt1.eTBT = 0; // eTimeBuffType 1 means nano
|
2020-11-13 03:20:51 +00:00
|
|
|
pkt1.iConditionBitFlag = plr->iConditionBitFlag;
|
2020-10-04 23:54:08 +00:00
|
|
|
|
|
|
|
sock->sendPacket((void*)&pkt1, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
|
2020-09-26 22:16:15 +00:00
|
|
|
}
|
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
static void dealGooDamage(CNSocket *sock, int amount) {
|
2020-09-26 22:16:15 +00:00
|
|
|
size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_DotDamage);
|
|
|
|
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
|
2020-09-26 23:00:31 +00:00
|
|
|
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
2020-10-04 23:54:08 +00:00
|
|
|
Player *plr = PlayerManager::getPlayer(sock);
|
2020-10-05 00:03:13 +00:00
|
|
|
|
2020-09-26 22:16:15 +00:00
|
|
|
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));
|
|
|
|
|
2020-10-04 23:54:08 +00:00
|
|
|
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;
|
2020-09-28 18:11:13 +00:00
|
|
|
|
2020-11-23 23:42:34 +00:00
|
|
|
// eggs allow protection without nanos
|
|
|
|
if (plr->activeNano != -1 && (plr->iSelfConditionBitFlag & CSB_BIT_PROTECT_INFECTION))
|
2020-10-04 23:54:08 +00:00
|
|
|
plr->Nanos[plr->activeNano].iStamina -= 3;
|
|
|
|
} else {
|
|
|
|
plr->HP -= amount;
|
|
|
|
}
|
|
|
|
|
2020-12-04 16:59:53 +00:00
|
|
|
if (plr->activeNano != 0) {
|
2020-10-04 23:54:08 +00:00
|
|
|
dmg->iStamina = plr->Nanos[plr->activeNano].iStamina;
|
|
|
|
|
|
|
|
if (plr->Nanos[plr->activeNano].iStamina <= 0) {
|
|
|
|
dmg->bNanoDeactive = 1;
|
|
|
|
plr->Nanos[plr->activeNano].iStamina = 0;
|
2021-03-16 22:29:13 +00:00
|
|
|
Nanos::summonNano(PlayerManager::getSockFromID(plr->iID), -1, true);
|
2020-10-04 23:54:08 +00:00
|
|
|
}
|
|
|
|
}
|
2020-09-26 22:16:15 +00:00
|
|
|
|
|
|
|
pkt->iID = plr->iID;
|
|
|
|
pkt->eCT = 1; // player
|
|
|
|
pkt->iTB_ID = ECSB_INFECTION; // sSkillResult_DotDamage
|
|
|
|
|
|
|
|
dmg->eCT = 1;
|
|
|
|
dmg->iID = plr->iID;
|
|
|
|
dmg->iDamage = amount;
|
|
|
|
dmg->iHP = plr->HP;
|
2020-11-13 03:20:51 +00:00
|
|
|
dmg->iConditionBitFlag = plr->iConditionBitFlag;
|
2020-09-26 22:16:15 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
static void pcAttackChars(CNSocket *sock, CNPacketData *data) {
|
2020-09-22 15:30:49 +00:00
|
|
|
sP_CL2FE_REQ_PC_ATTACK_CHARs* pkt = (sP_CL2FE_REQ_PC_ATTACK_CHARs*)data->buf;
|
|
|
|
Player *plr = PlayerManager::getPlayer(sock);
|
|
|
|
|
2022-02-12 22:53:04 +00:00
|
|
|
// only GMs can use this variant
|
2021-01-01 20:38:03 +00:00
|
|
|
if (plr->accountLevel > 30)
|
|
|
|
return;
|
|
|
|
|
2020-09-22 15:30:49 +00:00
|
|
|
// Unlike the attack mob packet, attacking players packet has an 8-byte trail (Instead of 4 bytes).
|
2022-04-11 14:26:57 +00:00
|
|
|
if (!validInVarPacket(sizeof(sP_CL2FE_REQ_PC_ATTACK_CHARs), pkt->iTargetCnt, sizeof(sGM_PVPTarget), data->size)) {
|
2020-09-22 15:30:49 +00:00
|
|
|
std::cout << "[WARN] bad sP_CL2FE_REQ_PC_ATTACK_CHARs packet size\n";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-04-13 19:09:43 +00:00
|
|
|
sGM_PVPTarget* pktdata = (sGM_PVPTarget*)((uint8_t*)data->buf + sizeof(sP_CL2FE_REQ_PC_ATTACK_CHARs));
|
2020-09-22 15:30:49 +00:00
|
|
|
|
|
|
|
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_ATTACK_CHARs_SUCC), pkt->iTargetCnt, sizeof(sAttackResult))) {
|
|
|
|
std::cout << "[WARN] bad sP_FE2CL_PC_ATTACK_CHARs_SUCC packet size\n";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// initialize response struct
|
|
|
|
size_t resplen = sizeof(sP_FE2CL_PC_ATTACK_CHARs_SUCC) + pkt->iTargetCnt * sizeof(sAttackResult);
|
|
|
|
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
|
|
|
|
|
|
|
memset(respbuf, 0, resplen);
|
|
|
|
|
|
|
|
sP_FE2CL_PC_ATTACK_CHARs_SUCC *resp = (sP_FE2CL_PC_ATTACK_CHARs_SUCC*)respbuf;
|
|
|
|
sAttackResult *respdata = (sAttackResult*)(respbuf+sizeof(sP_FE2CL_PC_ATTACK_CHARs_SUCC));
|
|
|
|
|
|
|
|
resp->iTargetCnt = pkt->iTargetCnt;
|
|
|
|
|
|
|
|
for (int i = 0; i < pkt->iTargetCnt; i++) {
|
2022-04-11 14:26:57 +00:00
|
|
|
|
|
|
|
ICombatant* target = nullptr;
|
|
|
|
std::pair<int, int> damage;
|
|
|
|
|
|
|
|
if (pkt->iTargetCnt > 1)
|
|
|
|
damage.first = plr->groupDamage;
|
|
|
|
else
|
|
|
|
damage.first = plr->pointDamage;
|
|
|
|
|
2022-04-13 19:09:43 +00:00
|
|
|
if (pktdata[i].eCT == 1) { // eCT == 1; attack player
|
2020-09-22 15:30:49 +00:00
|
|
|
|
|
|
|
for (auto& pair : PlayerManager::players) {
|
2022-04-13 19:09:43 +00:00
|
|
|
if (pair.second->iID == pktdata[i].iID) {
|
2020-11-17 23:16:16 +00:00
|
|
|
target = pair.second;
|
2020-09-22 15:30:49 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (target == nullptr) {
|
|
|
|
// you shall not pass
|
|
|
|
std::cout << "[WARN] pcAttackChars: player ID not found" << std::endl;
|
|
|
|
return;
|
|
|
|
}
|
2020-10-05 00:03:13 +00:00
|
|
|
|
2022-04-11 14:26:57 +00:00
|
|
|
damage = getDamage(damage.first, ((Player*)target)->defense, true, (plr->batteryW > 6 + plr->level), -1, -1, 0);
|
2020-10-05 00:03:13 +00:00
|
|
|
|
2020-09-22 15:30:49 +00:00
|
|
|
} else { // eCT == 4; attack mob
|
2022-04-11 14:26:57 +00:00
|
|
|
|
2022-04-13 19:09:43 +00:00
|
|
|
if (NPCManager::NPCs.find(pktdata[i].iID) == NPCManager::NPCs.end()) {
|
2020-09-22 15:30:49 +00:00
|
|
|
// not sure how to best handle this
|
2021-04-07 00:43:43 +00:00
|
|
|
std::cout << "[WARN] pcAttackChars: NPC ID not found" << std::endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-04-13 19:09:43 +00:00
|
|
|
BaseNPC* npc = NPCManager::NPCs[pktdata[i].iID];
|
|
|
|
if (npc->kind != EntityKind::MOB) {
|
2021-04-07 00:43:43 +00:00
|
|
|
std::cout << "[WARN] pcAttackChars: NPC is not a mob" << std::endl;
|
2020-09-22 15:30:49 +00:00
|
|
|
return;
|
|
|
|
}
|
2021-04-07 00:43:43 +00:00
|
|
|
|
|
|
|
Mob* mob = (Mob*)npc;
|
2022-04-11 14:26:57 +00:00
|
|
|
target = mob;
|
2020-10-09 00:01:35 +00:00
|
|
|
int difficulty = (int)mob->data["m_iNpcLevel"];
|
2020-10-21 05:24:51 +00:00
|
|
|
damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, (plr->batteryW > 6 + difficulty),
|
2021-03-16 22:29:13 +00:00
|
|
|
Nanos::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty);
|
2022-04-11 14:26:57 +00:00
|
|
|
}
|
2020-10-19 17:26:14 +00:00
|
|
|
|
2022-04-11 14:26:57 +00:00
|
|
|
if (plr->batteryW >= 6 + plr->level)
|
|
|
|
plr->batteryW -= 6 + plr->level;
|
|
|
|
else
|
|
|
|
plr->batteryW = 0;
|
2020-09-22 15:30:49 +00:00
|
|
|
|
2022-04-11 14:26:57 +00:00
|
|
|
damage.first = target->takeDamage(sock, damage.first);
|
2020-09-22 15:30:49 +00:00
|
|
|
|
2022-04-13 19:09:43 +00:00
|
|
|
respdata[i].eCT = pktdata[i].eCT;
|
2022-04-11 14:26:57 +00:00
|
|
|
respdata[i].iID = target->getID();
|
|
|
|
respdata[i].iDamage = damage.first;
|
|
|
|
respdata[i].iHP = target->getCurrentHP();
|
|
|
|
respdata[i].iHitFlag = damage.second; // hitscan, not a rocket or a grenade
|
2020-09-22 15:30:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
sock->sendPacket((void*)respbuf, P_FE2CL_PC_ATTACK_CHARs_SUCC, resplen);
|
|
|
|
|
|
|
|
// a bit of a hack: these are the same size, so we can reuse the response packet
|
2020-10-09 00:01:35 +00:00
|
|
|
assert(sizeof(sP_FE2CL_PC_ATTACK_CHARs_SUCC) == sizeof(sP_FE2CL_PC_ATTACK_CHARs));
|
2020-09-22 15:30:49 +00:00
|
|
|
sP_FE2CL_PC_ATTACK_CHARs *resp1 = (sP_FE2CL_PC_ATTACK_CHARs*)respbuf;
|
|
|
|
|
|
|
|
resp1->iPC_ID = plr->iID;
|
|
|
|
|
|
|
|
// send to other players
|
|
|
|
PlayerManager::sendToViewable(sock, (void*)respbuf, P_FE2CL_PC_ATTACK_CHARs, resplen);
|
|
|
|
}
|
2020-09-29 21:27:48 +00:00
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
static int8_t addBullet(Player* plr, bool isGrenade) {
|
|
|
|
|
|
|
|
int8_t findId = 0;
|
|
|
|
if (Bullets.find(plr->iID) != Bullets.end()) {
|
|
|
|
// find first free id
|
|
|
|
for (; findId < 127; findId++)
|
|
|
|
if (Bullets[plr->iID].find(findId) == Bullets[plr->iID].end())
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// sanity check
|
|
|
|
if (findId == 127) {
|
|
|
|
std::cout << "[WARN] Player has more than 127 active projectiles?!" << std::endl;
|
|
|
|
findId = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
Bullet toAdd;
|
|
|
|
toAdd.pointDamage = plr->pointDamage;
|
|
|
|
toAdd.groupDamage = plr->groupDamage;
|
|
|
|
// for grenade we need to send 1, for rocket - weapon id
|
|
|
|
toAdd.bulletType = isGrenade ? 1 : plr->Equip[0].iID;
|
|
|
|
|
|
|
|
// temp solution Jade fix plz
|
|
|
|
toAdd.weaponBoost = plr->batteryW > 0;
|
|
|
|
if (toAdd.weaponBoost) {
|
2021-03-28 20:57:43 +00:00
|
|
|
int boostCost = Rand::rand(11) + 20;
|
2021-03-16 21:06:10 +00:00
|
|
|
plr->batteryW = boostCost > plr->batteryW ? 0 : plr->batteryW - boostCost;
|
|
|
|
}
|
|
|
|
|
|
|
|
Bullets[plr->iID][findId] = toAdd;
|
|
|
|
return findId;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void grenadeFire(CNSocket* sock, CNPacketData* data) {
|
2020-11-12 00:46:53 +00:00
|
|
|
sP_CL2FE_REQ_PC_GRENADE_STYLE_FIRE* grenade = (sP_CL2FE_REQ_PC_GRENADE_STYLE_FIRE*)data->buf;
|
|
|
|
Player* plr = PlayerManager::getPlayer(sock);
|
|
|
|
|
|
|
|
INITSTRUCT(sP_FE2CL_REP_PC_GRENADE_STYLE_FIRE_SUCC, resp);
|
|
|
|
resp.iToX = grenade->iToX;
|
|
|
|
resp.iToY = grenade->iToY;
|
|
|
|
resp.iToZ = grenade->iToZ;
|
|
|
|
|
|
|
|
resp.iBulletID = addBullet(plr, true);
|
|
|
|
resp.iBatteryW = plr->batteryW;
|
|
|
|
|
|
|
|
// 1 means grenade
|
|
|
|
resp.Bullet.iID = 1;
|
|
|
|
sock->sendPacket(&resp, P_FE2CL_REP_PC_GRENADE_STYLE_FIRE_SUCC, sizeof(sP_FE2CL_REP_PC_GRENADE_STYLE_FIRE_SUCC));
|
|
|
|
|
|
|
|
// send packet to nearby players
|
|
|
|
INITSTRUCT(sP_FE2CL_PC_GRENADE_STYLE_FIRE, toOthers);
|
|
|
|
toOthers.iPC_ID = plr->iID;
|
|
|
|
toOthers.iToX = resp.iToX;
|
|
|
|
toOthers.iToY = resp.iToY;
|
|
|
|
toOthers.iToZ = resp.iToZ;
|
|
|
|
toOthers.iBulletID = resp.iBulletID;
|
|
|
|
toOthers.Bullet.iID = resp.Bullet.iID;
|
|
|
|
|
|
|
|
PlayerManager::sendToViewable(sock, &toOthers, P_FE2CL_PC_GRENADE_STYLE_FIRE, sizeof(sP_FE2CL_PC_GRENADE_STYLE_FIRE));
|
|
|
|
}
|
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
static void rocketFire(CNSocket* sock, CNPacketData* data) {
|
2020-11-12 00:46:53 +00:00
|
|
|
sP_CL2FE_REQ_PC_ROCKET_STYLE_FIRE* rocket = (sP_CL2FE_REQ_PC_ROCKET_STYLE_FIRE*)data->buf;
|
|
|
|
Player* plr = PlayerManager::getPlayer(sock);
|
|
|
|
|
|
|
|
// We should be sending back rocket succ packet, but it doesn't work, and this one works
|
|
|
|
INITSTRUCT(sP_FE2CL_REP_PC_GRENADE_STYLE_FIRE_SUCC, resp);
|
|
|
|
resp.iToX = rocket->iToX;
|
|
|
|
resp.iToY = rocket->iToY;
|
|
|
|
// rocket->iToZ is broken, this seems like a good height
|
|
|
|
resp.iToZ = plr->z + 100;
|
|
|
|
|
|
|
|
resp.iBulletID = addBullet(plr, false);
|
|
|
|
// we have to send it weapon id
|
|
|
|
resp.Bullet.iID = plr->Equip[0].iID;
|
|
|
|
resp.iBatteryW = plr->batteryW;
|
|
|
|
|
|
|
|
sock->sendPacket(&resp, P_FE2CL_REP_PC_GRENADE_STYLE_FIRE_SUCC, sizeof(sP_FE2CL_REP_PC_GRENADE_STYLE_FIRE_SUCC));
|
|
|
|
|
|
|
|
// send packet to nearby players
|
|
|
|
INITSTRUCT(sP_FE2CL_PC_GRENADE_STYLE_FIRE, toOthers);
|
|
|
|
toOthers.iPC_ID = plr->iID;
|
|
|
|
toOthers.iToX = resp.iToX;
|
|
|
|
toOthers.iToY = resp.iToY;
|
|
|
|
toOthers.iToZ = resp.iToZ;
|
|
|
|
toOthers.iBulletID = resp.iBulletID;
|
|
|
|
toOthers.Bullet.iID = resp.Bullet.iID;
|
|
|
|
|
|
|
|
PlayerManager::sendToViewable(sock, &toOthers, P_FE2CL_PC_GRENADE_STYLE_FIRE, sizeof(sP_FE2CL_PC_GRENADE_STYLE_FIRE));
|
|
|
|
}
|
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
static void projectileHit(CNSocket* sock, CNPacketData* data) {
|
2020-11-12 00:46:53 +00:00
|
|
|
sP_CL2FE_REQ_PC_ROCKET_STYLE_HIT* pkt = (sP_CL2FE_REQ_PC_ROCKET_STYLE_HIT*)data->buf;
|
|
|
|
Player* plr = PlayerManager::getPlayer(sock);
|
|
|
|
|
|
|
|
if (pkt->iTargetCnt == 0) {
|
|
|
|
Bullets[plr->iID].erase(pkt->iBulletID);
|
|
|
|
// no targets hit, don't send response
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// sanity check
|
|
|
|
if (!validInVarPacket(sizeof(sP_CL2FE_REQ_PC_ROCKET_STYLE_HIT), pkt->iTargetCnt, sizeof(int64_t), data->size)) {
|
|
|
|
std::cout << "[WARN] bad sP_CL2FE_REQ_PC_ROCKET_STYLE_HIT packet size\n";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-03-19 21:29:14 +00:00
|
|
|
// client sends us 8 bytes, where last 4 bytes are mob ID,
|
2020-11-12 00:46:53 +00:00
|
|
|
// we use int64 pointer to move around but have to remember to cast it to int32
|
|
|
|
int64_t* pktdata = (int64_t*)((uint8_t*)data->buf + sizeof(sP_CL2FE_REQ_PC_ROCKET_STYLE_HIT));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Due to the possibility of multiplication overflow (and regular buffer overflow),
|
|
|
|
* both incoming and outgoing variable-length packets must be validated, at least if
|
|
|
|
* the number of trailing structs isn't well known (ie. it's from the client).
|
|
|
|
*/
|
|
|
|
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GRENADE_STYLE_HIT), pkt->iTargetCnt, sizeof(sAttackResult))) {
|
|
|
|
std::cout << "[WARN] bad sP_FE2CL_PC_GRENADE_STYLE_HIT packet size\n";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-12-31 05:54:57 +00:00
|
|
|
// rapid fire anti-cheat
|
|
|
|
time_t currTime = getTime();
|
|
|
|
if (currTime - plr->lastShot < plr->fireRate * 80)
|
|
|
|
plr->suspicionRating += plr->fireRate * 100 + plr->lastShot - currTime; // gain suspicion for rapid firing
|
|
|
|
else if (currTime - plr->lastShot < plr->fireRate * 180 && plr->suspicionRating > 0)
|
|
|
|
plr->suspicionRating += plr->fireRate * 100 + plr->lastShot - currTime; // lose suspicion for delayed firing
|
|
|
|
|
|
|
|
plr->lastShot = currTime;
|
|
|
|
|
2021-05-27 04:23:05 +00:00
|
|
|
if (plr->suspicionRating > 10000) { // kill the socket when the player is too suspicious
|
2020-12-31 05:54:57 +00:00
|
|
|
sock->kill();
|
2021-05-27 04:23:05 +00:00
|
|
|
CNShardServer::_killConnection(sock);
|
|
|
|
return;
|
|
|
|
}
|
2020-12-31 05:54:57 +00:00
|
|
|
|
2020-11-12 08:31:55 +00:00
|
|
|
/*
|
|
|
|
* initialize response struct
|
|
|
|
* rocket style hit doesn't work properly, so we're always sending this one
|
|
|
|
*/
|
2021-03-28 20:57:43 +00:00
|
|
|
|
2020-11-12 00:46:53 +00:00
|
|
|
size_t resplen = sizeof(sP_FE2CL_PC_GRENADE_STYLE_HIT) + pkt->iTargetCnt * sizeof(sAttackResult);
|
|
|
|
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
|
|
|
|
|
|
|
memset(respbuf, 0, resplen);
|
|
|
|
|
|
|
|
sP_FE2CL_PC_GRENADE_STYLE_HIT* resp = (sP_FE2CL_PC_GRENADE_STYLE_HIT*)respbuf;
|
|
|
|
sAttackResult* respdata = (sAttackResult*)(respbuf + sizeof(sP_FE2CL_PC_GRENADE_STYLE_HIT));
|
|
|
|
|
|
|
|
resp->iTargetCnt = pkt->iTargetCnt;
|
|
|
|
if (Bullets.find(plr->iID) == Bullets.end() || Bullets[plr->iID].find(pkt->iBulletID) == Bullets[plr->iID].end()) {
|
|
|
|
std::cout << "[WARN] projectileHit: bullet not found" << std::endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Bullet* bullet = &Bullets[plr->iID][pkt->iBulletID];
|
|
|
|
|
|
|
|
for (int i = 0; i < pkt->iTargetCnt; i++) {
|
2021-04-07 00:43:43 +00:00
|
|
|
if (NPCManager::NPCs.find(pktdata[i]) == NPCManager::NPCs.end()) {
|
2020-11-12 00:46:53 +00:00
|
|
|
// not sure how to best handle this
|
2021-04-07 00:43:43 +00:00
|
|
|
std::cout << "[WARN] projectileHit: NPC ID not found" << std::endl;
|
2020-11-12 00:46:53 +00:00
|
|
|
return;
|
2021-03-28 20:57:43 +00:00
|
|
|
}
|
2020-11-12 00:46:53 +00:00
|
|
|
|
2021-04-07 00:43:43 +00:00
|
|
|
BaseNPC* npc = NPCManager::NPCs[pktdata[i]];
|
2022-04-13 19:09:43 +00:00
|
|
|
if (npc->kind != EntityKind::MOB) {
|
2021-04-07 00:43:43 +00:00
|
|
|
std::cout << "[WARN] projectileHit: NPC is not a mob" << std::endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Mob* mob = (Mob*)npc;
|
2020-11-12 00:46:53 +00:00
|
|
|
std::pair<int, int> damage;
|
|
|
|
|
|
|
|
damage.first = pkt->iTargetCnt > 1 ? bullet->groupDamage : bullet->pointDamage;
|
|
|
|
|
|
|
|
int difficulty = (int)mob->data["m_iNpcLevel"];
|
2021-03-16 22:29:13 +00:00
|
|
|
damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, bullet->weaponBoost, Nanos::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty);
|
2020-11-12 00:46:53 +00:00
|
|
|
|
2022-04-11 14:26:57 +00:00
|
|
|
damage.first = mob->takeDamage(sock, damage.first);
|
2020-11-12 00:46:53 +00:00
|
|
|
|
2021-06-20 18:37:37 +00:00
|
|
|
respdata[i].iID = mob->id;
|
2020-11-12 00:46:53 +00:00
|
|
|
respdata[i].iDamage = damage.first;
|
2021-06-20 18:37:37 +00:00
|
|
|
respdata[i].iHP = mob->hp;
|
2020-11-12 00:46:53 +00:00
|
|
|
respdata[i].iHitFlag = damage.second;
|
|
|
|
}
|
|
|
|
|
|
|
|
resp->iPC_ID = plr->iID;
|
|
|
|
resp->iBulletID = pkt->iBulletID;
|
|
|
|
resp->Bullet.iID = bullet->bulletType;
|
|
|
|
sock->sendPacket((void*)respbuf, P_FE2CL_PC_GRENADE_STYLE_HIT, resplen);
|
|
|
|
PlayerManager::sendToViewable(sock, (void*)respbuf, P_FE2CL_PC_GRENADE_STYLE_HIT, resplen);
|
2021-03-28 20:57:43 +00:00
|
|
|
|
2020-11-12 00:46:53 +00:00
|
|
|
Bullets[plr->iID].erase(resp->iBulletID);
|
|
|
|
}
|
2020-11-16 03:13:40 +00:00
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
static void playerTick(CNServer *serv, time_t currTime) {
|
2021-03-13 22:55:16 +00:00
|
|
|
static time_t lastHealTime = 0;
|
2020-11-25 02:51:17 +00:00
|
|
|
|
2021-03-13 22:55:16 +00:00
|
|
|
for (auto& pair : PlayerManager::players) {
|
|
|
|
CNSocket *sock = pair.first;
|
|
|
|
Player *plr = pair.second;
|
|
|
|
bool transmit = false;
|
2020-12-30 22:07:10 +00:00
|
|
|
|
2021-03-13 22:55:16 +00:00
|
|
|
// group ticks
|
|
|
|
if (plr->groupCnt > 1)
|
2021-03-16 22:29:13 +00:00
|
|
|
Groups::groupTickInfo(plr);
|
2020-12-30 22:07:10 +00:00
|
|
|
|
2021-03-13 22:55:16 +00:00
|
|
|
// do not tick dead players
|
|
|
|
if (plr->HP <= 0)
|
2020-12-30 22:07:10 +00:00
|
|
|
continue;
|
|
|
|
|
2021-03-13 22:55:16 +00:00
|
|
|
// fm patch/lake damage
|
|
|
|
if ((plr->iConditionBitFlag & CSB_BIT_INFECTION)
|
|
|
|
&& !(plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE))
|
|
|
|
dealGooDamage(sock, PC_MAXHEALTH(plr->level) * 3 / 20);
|
2020-12-30 22:07:10 +00:00
|
|
|
|
2021-03-13 22:55:16 +00:00
|
|
|
// heal
|
|
|
|
if (currTime - lastHealTime >= 4000 && !plr->inCombat && plr->HP < PC_MAXHEALTH(plr->level)) {
|
|
|
|
if (currTime - lastHealTime - plr->healCooldown >= 4000) {
|
|
|
|
plr->HP += PC_MAXHEALTH(plr->level) / 5;
|
|
|
|
if (plr->HP > PC_MAXHEALTH(plr->level))
|
|
|
|
plr->HP = PC_MAXHEALTH(plr->level);
|
|
|
|
transmit = true;
|
|
|
|
} else
|
|
|
|
plr->healCooldown -= 4000;
|
|
|
|
}
|
2020-12-01 18:58:34 +00:00
|
|
|
|
2021-03-13 22:55:16 +00:00
|
|
|
for (int i = 0; i < 3; i++) {
|
|
|
|
if (plr->activeNano != 0 && plr->equippedNanos[i] == plr->activeNano) { // spend stamina
|
|
|
|
plr->Nanos[plr->activeNano].iStamina -= 1 + plr->nanoDrainRate / 5;
|
2020-11-25 08:10:05 +00:00
|
|
|
|
2021-03-13 22:55:16 +00:00
|
|
|
if (plr->Nanos[plr->activeNano].iStamina <= 0)
|
2021-03-16 22:29:13 +00:00
|
|
|
Nanos::summonNano(sock, -1, true); // unsummon nano silently
|
2020-11-25 08:10:05 +00:00
|
|
|
|
2021-03-13 22:55:16 +00:00
|
|
|
transmit = true;
|
|
|
|
} else if (plr->Nanos[plr->equippedNanos[i]].iStamina < 150) { // regain stamina
|
|
|
|
sNano& nano = plr->Nanos[plr->equippedNanos[i]];
|
|
|
|
nano.iStamina += 1;
|
2020-11-25 08:10:05 +00:00
|
|
|
|
2021-03-13 22:55:16 +00:00
|
|
|
if (nano.iStamina > 150)
|
|
|
|
nano.iStamina = 150;
|
2020-11-25 08:10:05 +00:00
|
|
|
|
2021-03-13 22:55:16 +00:00
|
|
|
transmit = true;
|
2020-11-25 08:10:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-13 22:55:16 +00:00
|
|
|
// check if the player has fallen out of the world
|
|
|
|
if (plr->z < -30000) {
|
|
|
|
INITSTRUCT(sP_FE2CL_PC_SUDDEN_DEAD, dead);
|
2020-11-25 08:10:05 +00:00
|
|
|
|
2021-03-13 22:55:16 +00:00
|
|
|
dead.iPC_ID = plr->iID;
|
|
|
|
dead.iDamage = plr->HP;
|
|
|
|
dead.iHP = plr->HP = 0;
|
2020-11-25 08:10:05 +00:00
|
|
|
|
2021-03-13 22:55:16 +00:00
|
|
|
sock->sendPacket((void*)&dead, P_FE2CL_PC_SUDDEN_DEAD, sizeof(sP_FE2CL_PC_SUDDEN_DEAD));
|
|
|
|
PlayerManager::sendToViewable(sock, (void*)&dead, P_FE2CL_PC_SUDDEN_DEAD, sizeof(sP_FE2CL_PC_SUDDEN_DEAD));
|
2020-11-25 08:10:05 +00:00
|
|
|
}
|
|
|
|
|
2021-03-13 22:55:16 +00:00
|
|
|
if (transmit) {
|
|
|
|
INITSTRUCT(sP_FE2CL_REP_PC_TICK, pkt);
|
2020-11-25 08:10:05 +00:00
|
|
|
|
2021-03-13 22:55:16 +00:00
|
|
|
pkt.iHP = plr->HP;
|
|
|
|
pkt.iBatteryN = plr->batteryN;
|
2020-12-01 20:34:14 +00:00
|
|
|
|
2021-03-13 22:55:16 +00:00
|
|
|
pkt.aNano[0] = plr->Nanos[plr->equippedNanos[0]];
|
|
|
|
pkt.aNano[1] = plr->Nanos[plr->equippedNanos[1]];
|
|
|
|
pkt.aNano[2] = plr->Nanos[plr->equippedNanos[2]];
|
2020-11-25 08:10:05 +00:00
|
|
|
|
2021-03-13 22:55:16 +00:00
|
|
|
sock->sendPacket((void*)&pkt, P_FE2CL_REP_PC_TICK, sizeof(sP_FE2CL_REP_PC_TICK));
|
2020-11-25 08:10:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-13 22:55:16 +00:00
|
|
|
// if this was a heal tick, update the counter outside of the loop
|
|
|
|
if (currTime - lastHealTime >= 4000)
|
|
|
|
lastHealTime = currTime;
|
2021-01-09 13:16:29 +00:00
|
|
|
}
|
2021-03-16 21:06:10 +00:00
|
|
|
|
|
|
|
void Combat::init() {
|
|
|
|
REGISTER_SHARD_TIMER(playerTick, 2000);
|
|
|
|
|
|
|
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ATTACK_NPCs, pcAttackNpcs);
|
|
|
|
|
|
|
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_COMBAT_BEGIN, combatBegin);
|
|
|
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_COMBAT_END, combatEnd);
|
|
|
|
REGISTER_SHARD_PACKET(P_CL2FE_DOT_DAMAGE_ONOFF, dotDamageOnOff);
|
|
|
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ATTACK_CHARs, pcAttackChars);
|
|
|
|
|
|
|
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GRENADE_STYLE_FIRE, grenadeFire);
|
|
|
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ROCKET_STYLE_FIRE, rocketFire);
|
|
|
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ROCKET_STYLE_HIT, projectileHit);
|
|
|
|
}
|