mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2024-11-24 22:11:04 +00:00
[refactor] New buff framework (player implementation)
Get rid of `iConditionBitFlag` in favor of a system of individual buff objects that get composited to a bitflag on-the-fly. Buff objects can have callbacks for application, expiration, and tick, making them pretty flexible. Scripting languages can eventually use these for custom behavior, too. TODO: - Get rid of bitflag in BaseNPC - Apply buffs from passive nano powers - Apply buffs from active nano powers - Move eggs to new system - ???
This commit is contained in:
parent
d32827b692
commit
31677e2638
2
Makefile
2
Makefile
@ -50,6 +50,7 @@ CXXSRC=\
|
|||||||
src/db/email.cpp\
|
src/db/email.cpp\
|
||||||
src/sandbox/seccomp.cpp\
|
src/sandbox/seccomp.cpp\
|
||||||
src/sandbox/openbsd.cpp\
|
src/sandbox/openbsd.cpp\
|
||||||
|
src/Buffs.cpp\
|
||||||
src/Chat.cpp\
|
src/Chat.cpp\
|
||||||
src/CustomCommands.cpp\
|
src/CustomCommands.cpp\
|
||||||
src/Entities.cpp\
|
src/Entities.cpp\
|
||||||
@ -96,6 +97,7 @@ CXXHDR=\
|
|||||||
vendor/JSON.hpp\
|
vendor/JSON.hpp\
|
||||||
vendor/INIReader.hpp\
|
vendor/INIReader.hpp\
|
||||||
vendor/JSON.hpp\
|
vendor/JSON.hpp\
|
||||||
|
src/Buffs.hpp\
|
||||||
src/Chat.hpp\
|
src/Chat.hpp\
|
||||||
src/CustomCommands.hpp\
|
src/CustomCommands.hpp\
|
||||||
src/Entities.hpp\
|
src/Entities.hpp\
|
||||||
|
@ -2,17 +2,260 @@
|
|||||||
|
|
||||||
#include "NPCManager.hpp"
|
#include "NPCManager.hpp"
|
||||||
#include "PlayerManager.hpp"
|
#include "PlayerManager.hpp"
|
||||||
|
#include "Buffs.hpp"
|
||||||
|
#include "Nanos.hpp"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
using namespace Abilities;
|
||||||
|
|
||||||
std::map<int32_t, SkillData> Abilities::SkillTable;
|
std::map<int32_t, SkillData> Abilities::SkillTable;
|
||||||
|
|
||||||
/*
|
#pragma region Skill handlers
|
||||||
// New email notification
|
static SkillResult handleSkillDamage(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
||||||
static void emailUpdateCheck(CNSocket* sock, CNPacketData* data) {
|
EntityRef sourceRef = source->getRef();
|
||||||
INITSTRUCT(sP_FE2CL_REP_PC_NEW_EMAIL, resp);
|
double scalingFactor = 1;
|
||||||
resp.iNewEmailCnt = Database::getUnreadEmailCount(PlayerManager::getPlayer(sock)->iID);
|
if(sourceRef.kind == EntityKind::PLAYER)
|
||||||
sock->sendPacket(resp, P_FE2CL_REP_PC_NEW_EMAIL);
|
scalingFactor = std::max(source->getMaxHP(), target->getMaxHP()) / 1000.0;
|
||||||
|
else
|
||||||
|
scalingFactor = source->getMaxHP() / 1500.0;
|
||||||
|
|
||||||
|
int damage = (int)(skill->values[0][power] * scalingFactor);
|
||||||
|
int dealt = target->takeDamage(sourceRef, damage);
|
||||||
|
|
||||||
|
sSkillResult_Damage result{};
|
||||||
|
result.eCT = target->getCharType();
|
||||||
|
result.iID = target->getID();
|
||||||
|
result.bProtected = dealt <= 0;
|
||||||
|
result.iDamage = dealt;
|
||||||
|
result.iHP = target->getCurrentHP();
|
||||||
|
return SkillResult(sizeof(sSkillResult_Damage), &result);
|
||||||
|
}
|
||||||
|
|
||||||
|
static SkillResult handleSkillHealHP(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
||||||
|
EntityRef sourceRef = source->getRef();
|
||||||
|
int heal = skill->values[0][power];
|
||||||
|
int healed = target->heal(sourceRef, heal);
|
||||||
|
|
||||||
|
sSkillResult_Heal_HP result{};
|
||||||
|
result.eCT = target->getCharType();
|
||||||
|
result.iID = target->getID();
|
||||||
|
result.iHealHP = healed;
|
||||||
|
result.iHP = target->getCurrentHP();
|
||||||
|
return SkillResult(sizeof(sSkillResult_Heal_HP), &result);
|
||||||
|
}
|
||||||
|
|
||||||
|
static SkillResult handleSkillDamageNDebuff(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
||||||
|
// TODO abilities
|
||||||
|
sSkillResult_Damage_N_Debuff result{};
|
||||||
|
result.eCT = target->getCharType();
|
||||||
|
result.iID = target->getID();
|
||||||
|
result.bProtected = false;
|
||||||
|
result.iConditionBitFlag = target->getCompositeCondition();
|
||||||
|
return SkillResult(sizeof(sSkillResult_Damage_N_Debuff), &result);
|
||||||
|
}
|
||||||
|
|
||||||
|
static SkillResult handleSkillBuff(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
||||||
|
sSkillResult_Buff result{};
|
||||||
|
result.eCT = target->getCharType();
|
||||||
|
result.iID = target->getID();
|
||||||
|
result.bProtected = false;
|
||||||
|
result.iConditionBitFlag = target->getCompositeCondition();
|
||||||
|
return SkillResult(sizeof(sSkillResult_Buff), &result);
|
||||||
|
}
|
||||||
|
|
||||||
|
static SkillResult handleSkillBatteryDrain(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
||||||
|
if(target->getCharType() != 1)
|
||||||
|
return SkillResult(); // only Players are valid targets for battery drain
|
||||||
|
Player* plr = dynamic_cast<Player*>(target);
|
||||||
|
|
||||||
|
const double scalingFactor = (18 + source->getLevel()) / 36.0;
|
||||||
|
|
||||||
|
int boostDrain = (int)(skill->values[0][power] * scalingFactor);
|
||||||
|
if(boostDrain > plr->batteryW) boostDrain = plr->batteryW;
|
||||||
|
plr->batteryW -= boostDrain;
|
||||||
|
|
||||||
|
int potionDrain = (int)(skill->values[1][power] * scalingFactor);
|
||||||
|
if(potionDrain > plr->batteryN) potionDrain = plr->batteryN;
|
||||||
|
plr->batteryN -= potionDrain;
|
||||||
|
|
||||||
|
sSkillResult_BatteryDrain result{};
|
||||||
|
result.eCT = target->getCharType();
|
||||||
|
result.iID = target->getID();
|
||||||
|
result.bProtected = target->hasBuff(ECSB_PROTECT_BATTERY);
|
||||||
|
result.iDrainW = boostDrain;
|
||||||
|
result.iBatteryW = plr->batteryW;
|
||||||
|
result.iDrainN = potionDrain;
|
||||||
|
result.iBatteryN = plr->batteryN;
|
||||||
|
result.iStamina = plr->getActiveNano()->iStamina;
|
||||||
|
result.bNanoDeactive = plr->getActiveNano()->iStamina <= 0;
|
||||||
|
result.iConditionBitFlag = target->getCompositeCondition();
|
||||||
|
return SkillResult(sizeof(sSkillResult_BatteryDrain), &result);
|
||||||
|
}
|
||||||
|
|
||||||
|
static SkillResult handleSkillMove(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
||||||
|
if(source->getCharType() != 1)
|
||||||
|
return SkillResult(); // only Players are valid sources for recall
|
||||||
|
Player* plr = dynamic_cast<Player*>(source);
|
||||||
|
|
||||||
|
sSkillResult_Move result{};
|
||||||
|
result.eCT = target->getCharType();
|
||||||
|
result.iID = target->getID();
|
||||||
|
result.iMapNum = plr->recallInstance;
|
||||||
|
result.iMoveX = plr->recallX;
|
||||||
|
result.iMoveY = plr->recallY;
|
||||||
|
result.iMoveZ = plr->recallZ;
|
||||||
|
return SkillResult(sizeof(sSkillResult_Move), &result);
|
||||||
|
}
|
||||||
|
|
||||||
|
static SkillResult handleSkillResurrect(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
||||||
|
sSkillResult_Resurrect result{};
|
||||||
|
result.eCT = target->getCharType();
|
||||||
|
result.iID = target->getID();
|
||||||
|
result.iRegenHP = target->getCurrentHP();
|
||||||
|
return SkillResult(sizeof(sSkillResult_Resurrect), &result);
|
||||||
|
}
|
||||||
|
#pragma endregion
|
||||||
|
|
||||||
|
static std::vector<SkillResult> handleSkill(SkillData* skill, int power, ICombatant* src, std::vector<ICombatant*> targets) {
|
||||||
|
size_t resultSize = 0;
|
||||||
|
SkillResult (*skillHandler)(SkillData*, int, ICombatant*, ICombatant*) = nullptr;
|
||||||
|
std::vector<SkillResult> results;
|
||||||
|
|
||||||
|
switch(skill->skillType)
|
||||||
|
{
|
||||||
|
case EST_DAMAGE:
|
||||||
|
resultSize = sizeof(sSkillResult_Damage);
|
||||||
|
skillHandler = handleSkillDamage;
|
||||||
|
break;
|
||||||
|
case EST_HEAL_HP:
|
||||||
|
case EST_RETURNHOMEHEAL:
|
||||||
|
resultSize = sizeof(sSkillResult_Heal_HP);
|
||||||
|
skillHandler = handleSkillHealHP;
|
||||||
|
break;
|
||||||
|
case EST_JUMP:
|
||||||
|
case EST_RUN:
|
||||||
|
case EST_FREEDOM:
|
||||||
|
case EST_PHOENIX:
|
||||||
|
case EST_INVULNERABLE:
|
||||||
|
case EST_MINIMAPENEMY:
|
||||||
|
case EST_MINIMAPTRESURE:
|
||||||
|
case EST_NANOSTIMPAK:
|
||||||
|
case EST_PROTECTBATTERY:
|
||||||
|
case EST_PROTECTINFECTION:
|
||||||
|
case EST_REWARDBLOB:
|
||||||
|
case EST_REWARDCASH:
|
||||||
|
case EST_STAMINA_SELF:
|
||||||
|
case EST_STEALTH:
|
||||||
|
resultSize = sizeof(sSkillResult_Buff);
|
||||||
|
skillHandler = handleSkillBuff;
|
||||||
|
break;
|
||||||
|
case EST_BATTERYDRAIN:
|
||||||
|
resultSize = sizeof(sSkillResult_BatteryDrain);
|
||||||
|
skillHandler = handleSkillBatteryDrain;
|
||||||
|
break;
|
||||||
|
case EST_RECALL:
|
||||||
|
case EST_RECALL_GROUP:
|
||||||
|
resultSize = sizeof(sSkillResult_Move);
|
||||||
|
skillHandler = handleSkillMove;
|
||||||
|
break;
|
||||||
|
case EST_PHOENIX_GROUP:
|
||||||
|
resultSize = sizeof(sSkillResult_Resurrect);
|
||||||
|
skillHandler = handleSkillResurrect;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
std::cout << "[WARN] Unhandled skill type " << skill->skillType << std::endl;
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
assert(skillHandler != nullptr);
|
||||||
|
|
||||||
|
for(ICombatant* target : targets) {
|
||||||
|
assert(target != nullptr);
|
||||||
|
SkillResult result = skillHandler(skill, power, src != nullptr ? src : target, target);
|
||||||
|
if(result.size == 0) continue; // skill not applicable
|
||||||
|
if(result.size != resultSize) {
|
||||||
|
std::cout << "[WARN] bad skill result size for " << skill->skillType << " from " << (void*)handleSkillBuff << std::endl;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
results.push_back(result);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void attachSkillResults(std::vector<SkillResult> results, size_t resultSize, uint8_t* pivot) {
|
||||||
|
for(SkillResult& result : results) {
|
||||||
|
memcpy(pivot, result.payload, resultSize);
|
||||||
|
pivot += resultSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Abilities::useNanoSkill(CNSocket* sock, sNano& nano, std::vector<ICombatant*> affected) {
|
||||||
|
if(SkillTable.count(nano.iSkillID) == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
SkillData* skill = &SkillTable[nano.iSkillID];
|
||||||
|
Player* plr = PlayerManager::getPlayer(sock);
|
||||||
|
|
||||||
|
std::vector<SkillResult> results = handleSkill(skill, Nanos::getNanoBoost(plr), plr, affected);
|
||||||
|
size_t resultSize = results.back().size; // guaranteed to be the same for every item
|
||||||
|
|
||||||
|
if (!validOutVarPacket(sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC), results.size(), resultSize)) {
|
||||||
|
std::cout << "[WARN] bad sP_FE2CL_NANO_SKILL_USE_SUCC packet size\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize response struct
|
||||||
|
size_t resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC) + results.size() * resultSize;
|
||||||
|
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||||
|
memset(respbuf, 0, resplen);
|
||||||
|
|
||||||
|
sP_FE2CL_NANO_SKILL_USE_SUCC* pkt = (sP_FE2CL_NANO_SKILL_USE_SUCC*)respbuf;
|
||||||
|
pkt->iPC_ID = plr->iID;
|
||||||
|
pkt->iNanoID = nano.iID;
|
||||||
|
pkt->iSkillID = nano.iSkillID;
|
||||||
|
pkt->iNanoStamina = nano.iStamina;
|
||||||
|
pkt->bNanoDeactive = nano.iStamina <= 0;
|
||||||
|
pkt->eST = skill->skillType;
|
||||||
|
pkt->iTargetCnt = (int32_t)results.size();
|
||||||
|
|
||||||
|
attachSkillResults(results, resultSize, (uint8_t*)(pkt + 1));
|
||||||
|
sock->sendPacket(pkt, P_FE2CL_NANO_SKILL_USE_SUCC, resplen);
|
||||||
|
PlayerManager::sendToViewable(sock, pkt, P_FE2CL_NANO_SKILL_USE_SUCC, resplen);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Abilities::useNPCSkill(EntityRef npc, int skillID, std::vector<ICombatant*> affected) {
|
||||||
|
if(SkillTable.count(skillID) == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Entity* entity = npc.getEntity();
|
||||||
|
ICombatant* src = nullptr;
|
||||||
|
if(npc.kind == EntityKind::COMBAT_NPC || npc.kind == EntityKind::MOB)
|
||||||
|
src = dynamic_cast<ICombatant*>(entity);
|
||||||
|
|
||||||
|
SkillData* skill = &SkillTable[skillID];
|
||||||
|
|
||||||
|
std::vector<SkillResult> results = handleSkill(skill, 0, src, affected);
|
||||||
|
size_t resultSize = results.back().size; // guaranteed to be the same for every item
|
||||||
|
|
||||||
|
if (!validOutVarPacket(sizeof(sP_FE2CL_NPC_SKILL_HIT), results.size(), resultSize)) {
|
||||||
|
std::cout << "[WARN] bad sP_FE2CL_NPC_SKILL_HIT packet size\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize response struct
|
||||||
|
size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + results.size() * resultSize;
|
||||||
|
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||||
|
memset(respbuf, 0, resplen);
|
||||||
|
|
||||||
|
sP_FE2CL_NPC_SKILL_HIT* pkt = (sP_FE2CL_NPC_SKILL_HIT*)respbuf;
|
||||||
|
pkt->iNPC_ID = npc.id;
|
||||||
|
pkt->iSkillID = skillID;
|
||||||
|
pkt->eST = skill->skillType;
|
||||||
|
pkt->iTargetCnt = (int32_t)results.size();
|
||||||
|
|
||||||
|
attachSkillResults(results, resultSize, (uint8_t*)(pkt + 1));
|
||||||
|
NPCManager::sendToViewable(entity, pkt, P_FE2CL_NPC_SKILL_HIT, resplen);
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
std::vector<EntityRef> Abilities::matchTargets(SkillData* skill, int count, int32_t *ids) {
|
std::vector<EntityRef> Abilities::matchTargets(SkillData* skill, int count, int32_t *ids) {
|
||||||
|
|
||||||
@ -35,16 +278,65 @@ std::vector<EntityRef> Abilities::matchTargets(SkillData* skill, int count, int3
|
|||||||
return targets;
|
return targets;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Abilities::applyAbility(SkillData* skill, EntityRef src, std::vector<EntityRef> targets) {
|
/* ripped from client (enums emplaced) */
|
||||||
for (EntityRef target : targets) {
|
int Abilities::getCSTBFromST(int eSkillType) {
|
||||||
Entity* entity = target.getEntity();
|
int result = 0;
|
||||||
if (entity->kind != PLAYER && entity->kind != COMBAT_NPC && entity->kind != MOB)
|
switch (eSkillType)
|
||||||
continue; // not a combatant
|
{
|
||||||
|
case EST_RUN:
|
||||||
|
result = ECSB_UP_MOVE_SPEED;
|
||||||
|
break;
|
||||||
|
case EST_JUMP:
|
||||||
|
result = ECSB_UP_JUMP_HEIGHT;
|
||||||
|
break;
|
||||||
|
case EST_STEALTH:
|
||||||
|
result = ECSB_UP_STEALTH;
|
||||||
|
break;
|
||||||
|
case EST_PHOENIX:
|
||||||
|
result = ECSB_PHOENIX;
|
||||||
|
break;
|
||||||
|
case EST_PROTECTBATTERY:
|
||||||
|
result = ECSB_PROTECT_BATTERY;
|
||||||
|
break;
|
||||||
|
case EST_PROTECTINFECTION:
|
||||||
|
result = ECSB_PROTECT_INFECTION;
|
||||||
|
break;
|
||||||
|
case EST_SNARE:
|
||||||
|
result = ECSB_DN_MOVE_SPEED;
|
||||||
|
break;
|
||||||
|
case EST_SLEEP:
|
||||||
|
result = ECSB_MEZ;
|
||||||
|
break;
|
||||||
|
case EST_MINIMAPENEMY:
|
||||||
|
result = ECSB_MINIMAP_ENEMY;
|
||||||
|
break;
|
||||||
|
case EST_MINIMAPTRESURE:
|
||||||
|
result = ECSB_MINIMAP_TRESURE;
|
||||||
|
break;
|
||||||
|
case EST_REWARDBLOB:
|
||||||
|
result = ECSB_REWARD_BLOB;
|
||||||
|
break;
|
||||||
|
case EST_REWARDCASH:
|
||||||
|
result = ECSB_REWARD_CASH;
|
||||||
|
break;
|
||||||
|
case EST_INFECTIONDAMAGE:
|
||||||
|
result = ECSB_INFECTION;
|
||||||
|
break;
|
||||||
|
case EST_FREEDOM:
|
||||||
|
result = ECSB_FREEDOM;
|
||||||
|
break;
|
||||||
|
case EST_BOUNDINGBALL:
|
||||||
|
result = ECSB_BOUNDINGBALL;
|
||||||
|
break;
|
||||||
|
case EST_INVULNERABLE:
|
||||||
|
result = ECSB_INVULNERABLE;
|
||||||
|
break;
|
||||||
|
case EST_BUFFHEAL:
|
||||||
|
result = ECSB_HEAL;
|
||||||
|
break;
|
||||||
|
case EST_NANOSTIMPAK:
|
||||||
|
result = ECSB_STIMPAKSLOT1;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
return result;
|
||||||
|
|
||||||
void Abilities::init() {
|
|
||||||
//REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_EMAIL_UPDATE_CHECK, emailUpdateCheck);
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/Core.hpp"
|
||||||
|
|
||||||
#include "Entities.hpp"
|
#include "Entities.hpp"
|
||||||
|
#include "Player.hpp"
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
constexpr size_t MAX_SKILLRESULT_SIZE = sizeof(sSkillResult_BatteryDrain);
|
||||||
|
|
||||||
enum class SkillEffectTarget {
|
enum class SkillEffectTarget {
|
||||||
POINT = 1,
|
POINT = 1,
|
||||||
SELF = 2,
|
SELF = 2,
|
||||||
@ -25,8 +30,20 @@ enum class SkillDrainType {
|
|||||||
PASSIVE = 2
|
PASSIVE = 2
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct SkillResult {
|
||||||
|
size_t size;
|
||||||
|
uint8_t payload[MAX_SKILLRESULT_SIZE];
|
||||||
|
SkillResult(size_t len, void* dat) {
|
||||||
|
size = len;
|
||||||
|
memcpy(payload, dat, len);
|
||||||
|
}
|
||||||
|
SkillResult() {
|
||||||
|
size = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
struct SkillData {
|
struct SkillData {
|
||||||
int skillType;
|
int skillType; // eST
|
||||||
SkillEffectTarget effectTarget;
|
SkillEffectTarget effectTarget;
|
||||||
int effectType; // always 1?
|
int effectType; // always 1?
|
||||||
SkillTargetType targetType;
|
SkillTargetType targetType;
|
||||||
@ -43,8 +60,9 @@ struct SkillData {
|
|||||||
namespace Abilities {
|
namespace Abilities {
|
||||||
extern std::map<int32_t, SkillData> SkillTable;
|
extern std::map<int32_t, SkillData> SkillTable;
|
||||||
|
|
||||||
std::vector<EntityRef> matchTargets(SkillData*, int, int32_t*);
|
void useNanoSkill(CNSocket*, sNano&, std::vector<ICombatant*>);
|
||||||
void applyAbility(SkillData*, EntityRef, std::vector<EntityRef>);
|
void useNPCSkill(EntityRef, int skillID, std::vector<ICombatant*>);
|
||||||
|
|
||||||
void init();
|
std::vector<EntityRef> matchTargets(SkillData*, int, int32_t*);
|
||||||
|
int getCSTBFromST(int eSkillType);
|
||||||
}
|
}
|
||||||
|
151
src/Buffs.cpp
Normal file
151
src/Buffs.cpp
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
#include "Buffs.hpp"
|
||||||
|
|
||||||
|
#include "PlayerManager.hpp"
|
||||||
|
#include "NPCManager.hpp"
|
||||||
|
|
||||||
|
using namespace Buffs;
|
||||||
|
|
||||||
|
void Buff::tick(time_t currTime) {
|
||||||
|
auto it = stacks.begin();
|
||||||
|
while(it != stacks.end()) {
|
||||||
|
BuffStack& stack = *it;
|
||||||
|
//if(onTick) onTick(self, this, currTime);
|
||||||
|
|
||||||
|
if(stack.durationTicks == 0) {
|
||||||
|
BuffStack deadStack = stack;
|
||||||
|
it = stacks.erase(it);
|
||||||
|
if(onUpdate) onUpdate(self, this, ETBU_DEL, &deadStack);
|
||||||
|
} else {
|
||||||
|
if(stack.durationTicks > 0) stack.durationTicks--;
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Buff::combatTick(time_t currTime) {
|
||||||
|
if(onCombatTick) onCombatTick(self, this, currTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Buff::clear() {
|
||||||
|
while(!stacks.empty()) {
|
||||||
|
BuffStack stack = stacks.back();
|
||||||
|
stacks.pop_back();
|
||||||
|
if(onUpdate) onUpdate(self, this, ETBU_DEL, &stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Buff::clear(BuffClass buffClass) {
|
||||||
|
auto it = stacks.begin();
|
||||||
|
while(it != stacks.end()) {
|
||||||
|
BuffStack& stack = *it;
|
||||||
|
if(stack.buffStackClass == buffClass) {
|
||||||
|
BuffStack deadStack = stack;
|
||||||
|
it = stacks.erase(it);
|
||||||
|
if(onUpdate) onUpdate(self, this, ETBU_DEL, &deadStack);
|
||||||
|
} else it++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Buff::addStack(BuffStack* stack) {
|
||||||
|
stacks.push_back(*stack);
|
||||||
|
if(onUpdate) onUpdate(self, this, ETBU_ADD, &stacks.back());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Buff::hasClass(BuffClass buffClass) {
|
||||||
|
for(BuffStack& stack : stacks) {
|
||||||
|
if(stack.buffStackClass == buffClass)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BuffClass Buff::maxClass() {
|
||||||
|
BuffClass buffClass = BuffClass::NONE;
|
||||||
|
for(BuffStack& stack : stacks) {
|
||||||
|
if(stack.buffStackClass > buffClass)
|
||||||
|
buffClass = stack.buffStackClass;
|
||||||
|
}
|
||||||
|
return buffClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Buff::getValue(BuffValueSelector selector) {
|
||||||
|
if(isStale()) return 0;
|
||||||
|
|
||||||
|
int value = selector == BuffValueSelector::NET_TOTAL ? 0 : stacks.front().value;
|
||||||
|
for(BuffStack& stack : stacks) {
|
||||||
|
switch(selector)
|
||||||
|
{
|
||||||
|
case BuffValueSelector::NET_TOTAL:
|
||||||
|
value += stack.value;
|
||||||
|
break;
|
||||||
|
case BuffValueSelector::MIN_VALUE:
|
||||||
|
if(stack.value < value) value = stack.value;
|
||||||
|
break;
|
||||||
|
case BuffValueSelector::MAX_VALUE:
|
||||||
|
if(stack.value > value) value = stack.value;
|
||||||
|
break;
|
||||||
|
case BuffValueSelector::MIN_MAGNITUDE:
|
||||||
|
if(abs(stack.value) < abs(value)) value = stack.value;
|
||||||
|
break;
|
||||||
|
case BuffValueSelector::MAX_MAGNITUDE:
|
||||||
|
default:
|
||||||
|
if(abs(stack.value) > abs(value)) value = stack.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Buff::isStale() {
|
||||||
|
return stacks.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This will practically never do anything important, but it's here just in case */
|
||||||
|
void Buff::updateCallbacks(BuffCallback<int, BuffStack*> fOnUpdate, BuffCallback<time_t> fOnCombatTick) {
|
||||||
|
if(!onUpdate) onUpdate = fOnUpdate;
|
||||||
|
if(!onCombatTick) onCombatTick = fOnCombatTick;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma region Handlers
|
||||||
|
void Buffs::timeBuffUpdate(EntityRef self, Buff* buff, int status, BuffStack* stack) {
|
||||||
|
|
||||||
|
if(self.kind != EntityKind::PLAYER)
|
||||||
|
return; // not implemented
|
||||||
|
|
||||||
|
Player* plr = (Player*)self.getEntity();
|
||||||
|
if(plr == nullptr)
|
||||||
|
return; // sanity check
|
||||||
|
|
||||||
|
if(status == ETBU_DEL && !buff->isStale())
|
||||||
|
return; // no premature effect deletion
|
||||||
|
|
||||||
|
int cbf = plr->getCompositeCondition();
|
||||||
|
sTimeBuff payload{};
|
||||||
|
if(status == ETBU_ADD) {
|
||||||
|
payload.iValue = buff->getValue(BuffValueSelector::MAX_MAGNITUDE);
|
||||||
|
// we need to explicitly add the ECSB for this buff,
|
||||||
|
// in case this is the first stack in and the entry
|
||||||
|
// in the buff map doesn't yet exist
|
||||||
|
if(buff->id > 0) cbf |= CSB_FROM_ECSB(buff->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt);
|
||||||
|
pkt.eCSTB = buff->id; // eCharStatusTimeBuffID
|
||||||
|
pkt.eTBU = status; // eTimeBuffUpdate
|
||||||
|
pkt.eTBT = (int)stack->buffStackClass;
|
||||||
|
pkt.iConditionBitFlag = cbf;
|
||||||
|
pkt.TimeBuff = payload;
|
||||||
|
self.sock->sendPacket((void*)&pkt, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Buffs::timeBuffTimeout(EntityRef self) {
|
||||||
|
if(self.kind != EntityKind::PLAYER && self.kind != EntityKind::COMBAT_NPC && self.kind != EntityKind::MOB)
|
||||||
|
return; // not a combatant
|
||||||
|
Entity* entity = self.getEntity();
|
||||||
|
ICombatant* combatant = dynamic_cast<ICombatant*>(entity);
|
||||||
|
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt); // send a buff timeout to other players
|
||||||
|
pkt.eCT = combatant->getCharType();
|
||||||
|
pkt.iID = combatant->getID();
|
||||||
|
pkt.iConditionBitFlag = combatant->getCompositeCondition();
|
||||||
|
NPCManager::sendToViewable(entity, &pkt, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
|
||||||
|
}
|
||||||
|
#pragma endregion
|
90
src/Buffs.hpp
Normal file
90
src/Buffs.hpp
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/Core.hpp"
|
||||||
|
|
||||||
|
#include "EntityRef.hpp"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
/* forward declaration(s) */
|
||||||
|
class Buff;
|
||||||
|
template<class... Types>
|
||||||
|
using BuffCallback = std::function<void(EntityRef, Buff*, Types...)>;
|
||||||
|
|
||||||
|
#define CSB_FROM_ECSB(x) (1 << (x - 1))
|
||||||
|
|
||||||
|
enum class BuffClass {
|
||||||
|
NONE = ETBT_NONE,
|
||||||
|
NANO = ETBT_NANO,
|
||||||
|
GROUP_NANO = ETBT_GROUPNANO,
|
||||||
|
EGG = ETBT_SHINY,
|
||||||
|
ENVIRONMENT = ETBT_LANDEFFECT,
|
||||||
|
ITEM = ETBT_ITEM,
|
||||||
|
CASH_ITEM = ETBT_CASHITEM
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class BuffValueSelector {
|
||||||
|
MAX_VALUE,
|
||||||
|
MIN_VALUE,
|
||||||
|
MAX_MAGNITUDE,
|
||||||
|
MIN_MAGNITUDE,
|
||||||
|
NET_TOTAL
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BuffStack {
|
||||||
|
int durationTicks;
|
||||||
|
int value;
|
||||||
|
EntityRef source;
|
||||||
|
BuffClass buffStackClass;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Buff {
|
||||||
|
private:
|
||||||
|
EntityRef self;
|
||||||
|
std::vector<BuffStack> stacks;
|
||||||
|
|
||||||
|
public:
|
||||||
|
int id;
|
||||||
|
/* called just after a stack is added or removed */
|
||||||
|
BuffCallback<int, BuffStack*> onUpdate;
|
||||||
|
/* called when the buff is combat-ticked */
|
||||||
|
BuffCallback<time_t> onCombatTick;
|
||||||
|
|
||||||
|
void tick(time_t);
|
||||||
|
void combatTick(time_t);
|
||||||
|
void clear();
|
||||||
|
void clear(BuffClass buffClass);
|
||||||
|
void addStack(BuffStack* stack);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sometimes we need to determine if a buff
|
||||||
|
* is covered by a certain class, ex: nano
|
||||||
|
* vs. coco egg in the case of infection protection
|
||||||
|
*/
|
||||||
|
bool hasClass(BuffClass buffClass);
|
||||||
|
BuffClass maxClass();
|
||||||
|
|
||||||
|
int getValue(BuffValueSelector selector);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* In general, a Buff object won't exist
|
||||||
|
* unless it has stacks. However, when
|
||||||
|
* popping stacks during iteration (onExpire),
|
||||||
|
* stacks will be empty for a brief moment
|
||||||
|
* when the last stack is popped.
|
||||||
|
*/
|
||||||
|
bool isStale();
|
||||||
|
|
||||||
|
void updateCallbacks(BuffCallback<int, BuffStack*> fOnUpdate, BuffCallback<time_t> fonTick);
|
||||||
|
|
||||||
|
Buff(int iid, EntityRef pSelf, BuffCallback<int, BuffStack*> fOnUpdate, BuffCallback<time_t> fOnCombatTick, BuffStack* firstStack)
|
||||||
|
: self(pSelf), id(iid), onUpdate(fOnUpdate), onCombatTick(fOnCombatTick) {
|
||||||
|
addStack(firstStack);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace Buffs {
|
||||||
|
void timeBuffUpdate(EntityRef self, Buff* buff, int status, BuffStack* stack);
|
||||||
|
void timeBuffTimeout(EntityRef self);
|
||||||
|
}
|
331
src/Combat.cpp
331
src/Combat.cpp
@ -8,21 +8,84 @@
|
|||||||
#include "NPCManager.hpp"
|
#include "NPCManager.hpp"
|
||||||
#include "Nanos.hpp"
|
#include "Nanos.hpp"
|
||||||
#include "Abilities.hpp"
|
#include "Abilities.hpp"
|
||||||
|
#include "Buffs.hpp"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
using namespace Combat;
|
using namespace Combat;
|
||||||
|
|
||||||
/// Player Id -> Bullet Id -> Bullet
|
/// Player Id -> Bullet Id -> Bullet
|
||||||
std::map<int32_t, std::map<int8_t, Bullet>> Combat::Bullets;
|
std::map<int32_t, std::map<int8_t, Bullet>> Combat::Bullets;
|
||||||
|
|
||||||
int Player::takeDamage(EntityRef src, int amt) {
|
#pragma region Player
|
||||||
HP -= amt;
|
bool Player::addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) {
|
||||||
return amt;
|
EntityRef self = PlayerManager::getSockFromID(iID);
|
||||||
|
|
||||||
|
if(!hasBuff(buffId)) {
|
||||||
|
buffs[buffId] = new Buff(buffId, self, onUpdate, onTick, stack);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffs[buffId]->updateCallbacks(onUpdate, onTick);
|
||||||
|
buffs[buffId]->addStack(stack);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::heal(EntityRef src, int amt) {
|
Buff* Player::getBuff(int buffId) {
|
||||||
// stubbed
|
if(hasBuff(buffId)) {
|
||||||
|
return buffs[buffId];
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Player::removeBuff(int buffId) {
|
||||||
|
if(hasBuff(buffId)) {
|
||||||
|
buffs[buffId]->clear();
|
||||||
|
delete buffs[buffId];
|
||||||
|
buffs.erase(buffId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Player::removeBuff(int buffId, int buffClass) {
|
||||||
|
if(hasBuff(buffId)) {
|
||||||
|
buffs[buffId]->clear((BuffClass)buffClass);
|
||||||
|
if(buffs[buffId]->isStale()) {
|
||||||
|
delete buffs[buffId];
|
||||||
|
buffs.erase(buffId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Player::hasBuff(int buffId) {
|
||||||
|
auto buff = buffs.find(buffId);
|
||||||
|
return buff != buffs.end() && !buff->second->isStale();
|
||||||
|
}
|
||||||
|
|
||||||
|
int Player::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 Player::takeDamage(EntityRef src, int amt) {
|
||||||
|
int dmg = amt;
|
||||||
|
if(HP - dmg < 0) dmg = HP;
|
||||||
|
HP -= dmg;
|
||||||
|
|
||||||
|
return dmg;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Player::heal(EntityRef src, int amt) {
|
||||||
|
int heal = amt;
|
||||||
|
if(HP + heal > getMaxHP()) heal = getMaxHP() - HP;
|
||||||
|
HP += heal;
|
||||||
|
|
||||||
|
return heal;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Player::isAlive() {
|
bool Player::isAlive() {
|
||||||
@ -33,25 +96,107 @@ int Player::getCurrentHP() {
|
|||||||
return HP;
|
return HP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int Player::getMaxHP() {
|
||||||
|
return PC_MAXHEALTH(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Player::getLevel() {
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<EntityRef> Player::getGroupMembers() {
|
||||||
|
std::vector<EntityRef> members;
|
||||||
|
if(group != nullptr)
|
||||||
|
members = group->members;
|
||||||
|
else
|
||||||
|
members.push_back(PlayerManager::getSockFromID(iID));
|
||||||
|
return members;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t Player::getCharType() {
|
||||||
|
return 1; // eCharType (eCT_PC)
|
||||||
|
}
|
||||||
|
|
||||||
int32_t Player::getID() {
|
int32_t Player::getID() {
|
||||||
return iID;
|
return iID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EntityRef Player::getRef() {
|
||||||
|
return EntityRef(PlayerManager::getSockFromID(iID));
|
||||||
|
}
|
||||||
|
|
||||||
void Player::step(time_t currTime) {
|
void Player::step(time_t currTime) {
|
||||||
// no-op
|
CNSocket* sock = getRef().sock;
|
||||||
|
|
||||||
|
// nanos
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
if (activeNano != 0 && equippedNanos[i] == activeNano) { // tick active nano
|
||||||
|
sNano& nano = Nanos[activeNano];
|
||||||
|
int drainRate = 0;
|
||||||
|
|
||||||
|
if (Abilities::SkillTable.find(nano.iSkillID) != Abilities::SkillTable.end()) {
|
||||||
|
// nano has skill data
|
||||||
|
SkillData* skill = &Abilities::SkillTable[nano.iSkillID];
|
||||||
|
int boost = Nanos::getNanoBoost(this);
|
||||||
|
if (skill->drainType == SkillDrainType::PASSIVE)
|
||||||
|
drainRate = skill->batteryUse[boost * 3];
|
||||||
|
}
|
||||||
|
|
||||||
|
nano.iStamina -= 1 + drainRate / 5;
|
||||||
|
if (nano.iStamina <= 0)
|
||||||
|
Nanos::summonNano(sock, -1, true); // unsummon nano silently
|
||||||
|
|
||||||
|
} else if (Nanos[equippedNanos[i]].iStamina < 150) { // tick resting nano
|
||||||
|
sNano& nano = Nanos[equippedNanos[i]];
|
||||||
|
if (nano.iStamina < 150)
|
||||||
|
nano.iStamina += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// buffs
|
||||||
|
for(auto buffEntry : buffs) {
|
||||||
|
buffEntry.second->combatTick(currTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma endregion
|
||||||
|
|
||||||
|
#pragma region CombatNPC
|
||||||
|
bool CombatNPC::addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) { /* stubbed */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Buff* CombatNPC::getBuff(int buffId) { /* stubbed */
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CombatNPC::removeBuff(int buffId) { /* stubbed */ }
|
||||||
|
|
||||||
|
void CombatNPC::removeBuff(int buffId, int buffClass) { /* stubbed */ }
|
||||||
|
|
||||||
|
bool CombatNPC::hasBuff(int buffId) { /* stubbed */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CombatNPC::getCompositeCondition() { /* stubbed */
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int CombatNPC::takeDamage(EntityRef src, int amt) {
|
int CombatNPC::takeDamage(EntityRef src, int amt) {
|
||||||
|
int dmg = amt;
|
||||||
|
if(hp - dmg < 0) dmg = hp;
|
||||||
|
hp -= dmg;
|
||||||
|
|
||||||
hp -= amt;
|
if(hp <= 0) transition(AIState::DEAD, src);
|
||||||
if (hp <= 0)
|
|
||||||
transition(AIState::DEAD, src);
|
|
||||||
|
|
||||||
return amt;
|
return dmg;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CombatNPC::heal(EntityRef src, int amt) {
|
int CombatNPC::heal(EntityRef src, int amt) {
|
||||||
// stubbed
|
int heal = amt;
|
||||||
|
if(hp + heal > getMaxHP()) heal = getMaxHP() - hp;
|
||||||
|
hp += heal;
|
||||||
|
|
||||||
|
return heal;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CombatNPC::isAlive() {
|
bool CombatNPC::isAlive() {
|
||||||
@ -62,10 +207,37 @@ int CombatNPC::getCurrentHP() {
|
|||||||
return hp;
|
return hp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int CombatNPC::getMaxHP() {
|
||||||
|
return maxHealth;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CombatNPC::getLevel() {
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<EntityRef> CombatNPC::getGroupMembers() {
|
||||||
|
std::vector<EntityRef> members;
|
||||||
|
if(group != nullptr)
|
||||||
|
members = group->members;
|
||||||
|
else
|
||||||
|
members.push_back(id);
|
||||||
|
return members;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t CombatNPC::getCharType() {
|
||||||
|
if(kind == EntityKind::MOB)
|
||||||
|
return 4; // eCharType (eCT_MOB)
|
||||||
|
return 2; // eCharType (eCT_NPC)
|
||||||
|
}
|
||||||
|
|
||||||
int32_t CombatNPC::getID() {
|
int32_t CombatNPC::getID() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EntityRef CombatNPC::getRef() {
|
||||||
|
return EntityRef(id);
|
||||||
|
}
|
||||||
|
|
||||||
void CombatNPC::step(time_t currTime) {
|
void CombatNPC::step(time_t currTime) {
|
||||||
|
|
||||||
if(stateHandlers.find(state) != stateHandlers.end())
|
if(stateHandlers.find(state) != stateHandlers.end())
|
||||||
@ -91,6 +263,7 @@ void CombatNPC::transition(AIState newState, EntityRef src) {
|
|||||||
event.handler(src, this);
|
event.handler(src, this);
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
#pragma endregion
|
||||||
|
|
||||||
static std::pair<int,int> getDamage(int attackPower, int defensePower, bool shouldCrit,
|
static std::pair<int,int> getDamage(int attackPower, int defensePower, bool shouldCrit,
|
||||||
bool batteryBoost, int attackerStyle,
|
bool batteryBoost, int attackerStyle,
|
||||||
@ -299,40 +472,27 @@ static void combatEnd(CNSocket *sock, CNPacketData *data) {
|
|||||||
plr->healCooldown = 4000;
|
plr->healCooldown = 4000;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dotDamageOnOff(CNSocket *sock, CNPacketData *data) {
|
static void dealGooDamage(CNSocket *sock) {
|
||||||
sP_CL2FE_DOT_DAMAGE_ONOFF *pkt = (sP_CL2FE_DOT_DAMAGE_ONOFF*)data->buf;
|
|
||||||
Player *plr = PlayerManager::getPlayer(sock);
|
Player *plr = PlayerManager::getPlayer(sock);
|
||||||
|
if(plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE)
|
||||||
|
return; // ignore completely
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void dealGooDamage(CNSocket *sock, int amount) {
|
|
||||||
size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_DotDamage);
|
size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_DotDamage);
|
||||||
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
|
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
|
||||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||||
Player *plr = PlayerManager::getPlayer(sock);
|
|
||||||
|
|
||||||
memset(respbuf, 0, resplen);
|
memset(respbuf, 0, resplen);
|
||||||
|
|
||||||
sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK *pkt = (sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK*)respbuf;
|
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));
|
sSkillResult_DotDamage *dmg = (sSkillResult_DotDamage*)(respbuf + sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK));
|
||||||
|
|
||||||
if (plr->iConditionBitFlag & CSB_BIT_PROTECT_INFECTION) {
|
int amount = PC_MAXHEALTH(plr->level) * 3 / 20;
|
||||||
|
Buff* protectionBuff = plr->getBuff(ECSB_PROTECT_INFECTION);
|
||||||
|
if (protectionBuff != nullptr) {
|
||||||
amount = -2; // -2 is the magic number for "Protected" to appear as the damage number
|
amount = -2; // -2 is the magic number for "Protected" to appear as the damage number
|
||||||
dmg->bProtected = 1;
|
dmg->bProtected = 1;
|
||||||
|
|
||||||
// eggs allow protection without nanos
|
// eggs allow protection without nanos
|
||||||
if (plr->activeNano != -1 && (plr->iSelfConditionBitFlag & CSB_BIT_PROTECT_INFECTION))
|
if (protectionBuff->maxClass() <= BuffClass::NANO && plr->activeNano != -1)
|
||||||
plr->Nanos[plr->activeNano].iStamina -= 3;
|
plr->Nanos[plr->activeNano].iStamina -= 3;
|
||||||
} else {
|
} else {
|
||||||
plr->HP -= amount;
|
plr->HP -= amount;
|
||||||
@ -356,12 +516,39 @@ static void dealGooDamage(CNSocket *sock, int amount) {
|
|||||||
dmg->iID = plr->iID;
|
dmg->iID = plr->iID;
|
||||||
dmg->iDamage = amount;
|
dmg->iDamage = amount;
|
||||||
dmg->iHP = plr->HP;
|
dmg->iHP = plr->HP;
|
||||||
dmg->iConditionBitFlag = plr->iConditionBitFlag;
|
dmg->iConditionBitFlag = plr->getCompositeCondition();
|
||||||
|
|
||||||
sock->sendPacket((void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen);
|
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);
|
PlayerManager::sendToViewable(sock, (void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void dotDamageOnOff(CNSocket *sock, CNPacketData *data) {
|
||||||
|
sP_CL2FE_DOT_DAMAGE_ONOFF *pkt = (sP_CL2FE_DOT_DAMAGE_ONOFF*)data->buf;
|
||||||
|
Player *plr = PlayerManager::getPlayer(sock);
|
||||||
|
|
||||||
|
// infection debuff toggles as the client asks it to,
|
||||||
|
// so we add and remove a permanent debuff
|
||||||
|
if (pkt->iFlag && !plr->hasBuff(ECSB_INFECTION)) {
|
||||||
|
BuffStack infection = {
|
||||||
|
-1, // infinite
|
||||||
|
0, // no value
|
||||||
|
sock, // self-inflicted
|
||||||
|
BuffClass::ENVIRONMENT
|
||||||
|
};
|
||||||
|
plr->addBuff(ECSB_INFECTION,
|
||||||
|
[](EntityRef self, Buff* buff, int status, BuffStack* stack) {
|
||||||
|
Buffs::timeBuffUpdate(self, buff, status, stack);
|
||||||
|
},
|
||||||
|
[](EntityRef self, Buff* buff, time_t currTime) {
|
||||||
|
if(self.kind == EntityKind::PLAYER)
|
||||||
|
dealGooDamage(self.sock);
|
||||||
|
},
|
||||||
|
&infection);
|
||||||
|
} else if(!pkt->iFlag && plr->hasBuff(ECSB_INFECTION)) {
|
||||||
|
plr->removeBuff(ECSB_INFECTION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void pcAttackChars(CNSocket *sock, CNPacketData *data) {
|
static void pcAttackChars(CNSocket *sock, CNPacketData *data) {
|
||||||
sP_CL2FE_REQ_PC_ATTACK_CHARs* pkt = (sP_CL2FE_REQ_PC_ATTACK_CHARs*)data->buf;
|
sP_CL2FE_REQ_PC_ATTACK_CHARs* pkt = (sP_CL2FE_REQ_PC_ATTACK_CHARs*)data->buf;
|
||||||
Player *plr = PlayerManager::getPlayer(sock);
|
Player *plr = PlayerManager::getPlayer(sock);
|
||||||
@ -649,6 +836,7 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) {
|
|||||||
|
|
||||||
static void playerTick(CNServer *serv, time_t currTime) {
|
static void playerTick(CNServer *serv, time_t currTime) {
|
||||||
static time_t lastHealTime = 0;
|
static time_t lastHealTime = 0;
|
||||||
|
static time_t lastCombatTIme = 0;
|
||||||
|
|
||||||
for (auto& pair : PlayerManager::players) {
|
for (auto& pair : PlayerManager::players) {
|
||||||
CNSocket *sock = pair.first;
|
CNSocket *sock = pair.first;
|
||||||
@ -657,17 +845,12 @@ static void playerTick(CNServer *serv, time_t currTime) {
|
|||||||
|
|
||||||
// group ticks
|
// group ticks
|
||||||
if (plr->group != nullptr)
|
if (plr->group != nullptr)
|
||||||
Groups::groupTickInfo(plr);
|
Groups::groupTickInfo(sock);
|
||||||
|
|
||||||
// do not tick dead players
|
// do not tick dead players
|
||||||
if (plr->HP <= 0)
|
if (plr->HP <= 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
// heal
|
// heal
|
||||||
if (currTime - lastHealTime >= 4000 && !plr->inCombat && plr->HP < PC_MAXHEALTH(plr->level)) {
|
if (currTime - lastHealTime >= 4000 && !plr->inCombat && plr->HP < PC_MAXHEALTH(plr->level)) {
|
||||||
if (currTime - lastHealTime - plr->healCooldown >= 4000) {
|
if (currTime - lastHealTime - plr->healCooldown >= 4000) {
|
||||||
@ -679,44 +862,21 @@ static void playerTick(CNServer *serv, time_t currTime) {
|
|||||||
plr->healCooldown -= 4000;
|
plr->healCooldown -= 4000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// combat tick
|
||||||
|
if(currTime - lastCombatTIme >= 2000) {
|
||||||
|
plr->step(currTime);
|
||||||
|
transmit = true;
|
||||||
|
}
|
||||||
|
|
||||||
// nanos
|
// nanos
|
||||||
for (int i = 0; i < 3; i++) {
|
if (plr->activeNano != 0) { // tick active nano
|
||||||
if (plr->activeNano != 0 && plr->equippedNanos[i] == plr->activeNano) { // tick active nano
|
sNano* nano = plr->getActiveNano();
|
||||||
sNano& nano = plr->Nanos[plr->activeNano];
|
if (Abilities::SkillTable.find(nano->iSkillID) != Abilities::SkillTable.end()) {
|
||||||
int drainRate = 0;
|
// nano has skill data
|
||||||
|
SkillData* skill = &Abilities::SkillTable[nano->iSkillID];
|
||||||
if (Abilities::SkillTable.find(nano.iSkillID) != Abilities::SkillTable.end()) {
|
if (skill->drainType == SkillDrainType::PASSIVE)
|
||||||
// nano has skill data
|
Nanos::applyNanoBuff(skill, plr);
|
||||||
SkillData* skill = &Abilities::SkillTable[nano.iSkillID];
|
// ^ composite condition calculation is separate from combat for responsiveness
|
||||||
int boost = Nanos::getNanoBoost(plr);
|
|
||||||
drainRate = skill->batteryUse[boost * 3];
|
|
||||||
|
|
||||||
if (skill->drainType == SkillDrainType::PASSIVE) {
|
|
||||||
// passive buff
|
|
||||||
std::vector<EntityRef> targets;
|
|
||||||
if (skill->targetType == SkillTargetType::GROUP && plr->group != nullptr)
|
|
||||||
targets = plr->group->members; // group
|
|
||||||
else if(skill->targetType == SkillTargetType::SELF)
|
|
||||||
targets.push_back(sock); // self
|
|
||||||
|
|
||||||
std::cout << "[SKILL] id " << nano.iSkillID << ", type " << skill->skillType << ", target " << (int)skill->targetType << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nano.iStamina -= 1 + drainRate / 5;
|
|
||||||
|
|
||||||
if (nano.iStamina <= 0)
|
|
||||||
Nanos::summonNano(sock, -1, true); // unsummon nano silently
|
|
||||||
|
|
||||||
transmit = true;
|
|
||||||
} else if (plr->Nanos[plr->equippedNanos[i]].iStamina < 150) { // tick resting nano
|
|
||||||
sNano& nano = plr->Nanos[plr->equippedNanos[i]];
|
|
||||||
nano.iStamina += 1;
|
|
||||||
|
|
||||||
if (nano.iStamina > 150)
|
|
||||||
nano.iStamina = 150;
|
|
||||||
|
|
||||||
transmit = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -732,6 +892,19 @@ static void playerTick(CNServer *serv, time_t currTime) {
|
|||||||
PlayerManager::sendToViewable(sock, (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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// process buffsets
|
||||||
|
auto it = plr->buffs.begin();
|
||||||
|
while(it != plr->buffs.end()) {
|
||||||
|
Buff* buff = (*it).second;
|
||||||
|
buff->tick(currTime);
|
||||||
|
if(buff->isStale()) {
|
||||||
|
// garbage collect
|
||||||
|
it = plr->buffs.erase(it);
|
||||||
|
delete buff;
|
||||||
|
}
|
||||||
|
else it++;
|
||||||
|
}
|
||||||
|
|
||||||
if (transmit) {
|
if (transmit) {
|
||||||
INITSTRUCT(sP_FE2CL_REP_PC_TICK, pkt);
|
INITSTRUCT(sP_FE2CL_REP_PC_TICK, pkt);
|
||||||
|
|
||||||
@ -746,13 +919,15 @@ static void playerTick(CNServer *serv, time_t currTime) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if this was a heal tick, update the counter outside of the loop
|
// if this was a heal/combat tick, update the counters outside of the loop
|
||||||
if (currTime - lastHealTime >= 4000)
|
if (currTime - lastHealTime >= 4000)
|
||||||
lastHealTime = currTime;
|
lastHealTime = currTime;
|
||||||
|
if(currTime - lastCombatTIme >= 2000)
|
||||||
|
lastCombatTIme = currTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Combat::init() {
|
void Combat::init() {
|
||||||
REGISTER_SHARD_TIMER(playerTick, 2000);
|
REGISTER_SHARD_TIMER(playerTick, MS_PER_PLAYER_TICK);
|
||||||
|
|
||||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ATTACK_NPCs, pcAttackNpcs);
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ATTACK_NPCs, pcAttackNpcs);
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
#include "Missions.hpp"
|
#include "Missions.hpp"
|
||||||
#include "Eggs.hpp"
|
#include "Eggs.hpp"
|
||||||
#include "Items.hpp"
|
#include "Items.hpp"
|
||||||
|
#include "Abilities.hpp"
|
||||||
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
@ -501,9 +502,12 @@ static void buffCommand(std::string full, std::vector<std::string>& args, CNSock
|
|||||||
if (*tmp)
|
if (*tmp)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (Eggs::eggBuffPlayer(sock, skillId, 0, duration)<0)
|
if (Abilities::SkillTable.count(skillId) == 0) {
|
||||||
Chat::sendServerMessage(sock, "/buff: unknown skill Id");
|
Chat::sendServerMessage(sock, "/buff: unknown skill Id");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Eggs::eggBuffPlayer(sock, skillId, 0, duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void eggCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
static void eggCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||||
|
143
src/Eggs.cpp
143
src/Eggs.cpp
@ -13,118 +13,52 @@
|
|||||||
|
|
||||||
using namespace Eggs;
|
using namespace Eggs;
|
||||||
|
|
||||||
/// sock, CBFlag -> until
|
|
||||||
std::map<std::pair<CNSocket*, int32_t>, time_t> Eggs::EggBuffs;
|
|
||||||
std::unordered_map<int, EggType> Eggs::EggTypes;
|
std::unordered_map<int, EggType> Eggs::EggTypes;
|
||||||
|
|
||||||
int Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) {
|
void Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) {
|
||||||
Player* plr = PlayerManager::getPlayer(sock);
|
Player* plr = PlayerManager::getPlayer(sock);
|
||||||
|
|
||||||
// TODO ABILITIES
|
// eggId might be 0 if the buff is made by the /buff command
|
||||||
//int bitFlag = plr->group->conditionBitFlag;
|
EntityRef src = eggId == 0 ? sock : EntityRef(eggId);
|
||||||
int CBFlag = 0;// Abilities::applyBuff(sock, skillId, 1, 3, bitFlag);
|
|
||||||
|
if(Abilities::SkillTable.count(skillId) == 0) {
|
||||||
size_t resplen;
|
std::cout << "[WARN] egg " << eggId << " has skill ID " << skillId << " which doesn't exist" << std::endl;
|
||||||
|
return;
|
||||||
if (skillId == 183) {
|
|
||||||
resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + sizeof(sSkillResult_Damage);
|
|
||||||
} else if (skillId == 150) {
|
|
||||||
resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + sizeof(sSkillResult_Heal_HP);
|
|
||||||
} else {
|
|
||||||
resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + sizeof(sSkillResult_Buff);
|
|
||||||
}
|
|
||||||
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
|
|
||||||
// we know it's only one trailing struct, so we can skip full validation
|
|
||||||
|
|
||||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
|
||||||
auto skillUse = (sP_FE2CL_NPC_SKILL_HIT*)respbuf;
|
|
||||||
|
|
||||||
if (skillId == 183) { // damage egg
|
|
||||||
auto skill = (sSkillResult_Damage*)(respbuf + sizeof(sP_FE2CL_NPC_SKILL_HIT));
|
|
||||||
memset(respbuf, 0, resplen);
|
|
||||||
skill->eCT = 1;
|
|
||||||
skill->iID = plr->iID;
|
|
||||||
skill->iDamage = PC_MAXHEALTH(plr->level) * Abilities::SkillTable[skillId].values[0][0] / 1000;
|
|
||||||
plr->HP -= skill->iDamage;
|
|
||||||
if (plr->HP < 0)
|
|
||||||
plr->HP = 0;
|
|
||||||
skill->iHP = plr->HP;
|
|
||||||
} else if (skillId == 150) { // heal egg
|
|
||||||
auto skill = (sSkillResult_Heal_HP*)(respbuf + sizeof(sP_FE2CL_NPC_SKILL_HIT));
|
|
||||||
memset(respbuf, 0, resplen);
|
|
||||||
skill->eCT = 1;
|
|
||||||
skill->iID = plr->iID;
|
|
||||||
skill->iHealHP = PC_MAXHEALTH(plr->level) * Abilities::SkillTable[skillId].values[0][0] / 1000;
|
|
||||||
plr->HP += skill->iHealHP;
|
|
||||||
if (plr->HP > PC_MAXHEALTH(plr->level))
|
|
||||||
plr->HP = PC_MAXHEALTH(plr->level);
|
|
||||||
skill->iHP = plr->HP;
|
|
||||||
} else { // regular buff egg
|
|
||||||
auto skill = (sSkillResult_Buff*)(respbuf + sizeof(sP_FE2CL_NPC_SKILL_HIT));
|
|
||||||
memset(respbuf, 0, resplen);
|
|
||||||
skill->eCT = 1;
|
|
||||||
skill->iID = plr->iID;
|
|
||||||
skill->iConditionBitFlag = plr->iConditionBitFlag;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
skillUse->iNPC_ID = eggId;
|
SkillData* skill = &Abilities::SkillTable[skillId];
|
||||||
skillUse->iSkillID = skillId;
|
if(skill->drainType == SkillDrainType::PASSIVE) {
|
||||||
skillUse->eST = Abilities::SkillTable[skillId].skillType;
|
// apply buff
|
||||||
skillUse->iTargetCnt = 1;
|
if(skill->targetType != SkillTargetType::SELF) {
|
||||||
|
std::cout << "[WARN] weird skill type for egg " << eggId << " with skill " << skillId << ", should be " << (int)skill->targetType << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
sock->sendPacket((void*)&respbuf, P_FE2CL_NPC_SKILL_HIT, resplen);
|
int timeBuffId = Abilities::getCSTBFromST(skill->skillType);
|
||||||
PlayerManager::sendToViewable(sock, (void*)&respbuf, P_FE2CL_NPC_SKILL_HIT, resplen);
|
int value = skill->values[0][0];
|
||||||
|
BuffStack eggBuff = {
|
||||||
|
duration * 1000 / MS_PER_PLAYER_TICK,
|
||||||
|
value,
|
||||||
|
src,
|
||||||
|
BuffClass::EGG
|
||||||
|
};
|
||||||
|
plr->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) {
|
||||||
|
// no-op
|
||||||
|
},
|
||||||
|
&eggBuff);
|
||||||
|
}
|
||||||
|
|
||||||
if (CBFlag == 0)
|
// use skill
|
||||||
return -1;
|
std::vector<ICombatant*> targets;
|
||||||
|
targets.push_back(dynamic_cast<ICombatant*>(plr));
|
||||||
std::pair<CNSocket*, int32_t> key = std::make_pair(sock, CBFlag);
|
Abilities::useNPCSkill(src, skillId, targets);
|
||||||
|
|
||||||
// save the buff serverside;
|
|
||||||
// if you get the same buff again, new duration will override the previous one
|
|
||||||
time_t until = getTime() + (time_t)duration * 1000;
|
|
||||||
EggBuffs[key] = until;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void eggStep(CNServer* serv, time_t currTime) {
|
static void eggStep(CNServer* serv, time_t currTime) {
|
||||||
// tick buffs
|
|
||||||
time_t timeStamp = currTime;
|
|
||||||
auto it = EggBuffs.begin();
|
|
||||||
while (it != EggBuffs.end()) {
|
|
||||||
// check remaining time
|
|
||||||
if (it->second > timeStamp) {
|
|
||||||
it++;
|
|
||||||
} else { // if time reached 0
|
|
||||||
CNSocket* sock = it->first.first;
|
|
||||||
int32_t CBFlag = it->first.second;
|
|
||||||
Player* plr = PlayerManager::getPlayer(sock);
|
|
||||||
|
|
||||||
//int groupFlags = plr->group->conditionBitFlag;
|
|
||||||
// TODO ABILITIES
|
|
||||||
//for (auto& pwr : Abilities::Powers) {
|
|
||||||
// if (pwr.bitFlag == CBFlag) { // pick the power with the right flag and unbuff
|
|
||||||
// INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, resp);
|
|
||||||
// resp.eCSTB = pwr.timeBuffID;
|
|
||||||
// resp.eTBU = 2;
|
|
||||||
// resp.eTBT = 3; // for egg buffs
|
|
||||||
// plr->iConditionBitFlag &= ~CBFlag;
|
|
||||||
// resp.iConditionBitFlag = plr->iConditionBitFlag |= groupFlags | plr->iSelfConditionBitFlag;
|
|
||||||
// sock->sendPacket(resp, P_FE2CL_PC_BUFF_UPDATE);
|
|
||||||
|
|
||||||
// INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, resp2); // send a buff timeout to other players
|
|
||||||
// resp2.eCT = 1;
|
|
||||||
// resp2.iID = plr->iID;
|
|
||||||
// resp2.iConditionBitFlag = plr->iConditionBitFlag;
|
|
||||||
// PlayerManager::sendToViewable(sock, resp2, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT);
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
// remove buff from the map
|
|
||||||
it = EggBuffs.erase(it);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check dead eggs and eggs in inactive chunks
|
// check dead eggs and eggs in inactive chunks
|
||||||
for (auto npc : NPCManager::NPCs) {
|
for (auto npc : NPCManager::NPCs) {
|
||||||
if (npc.second->kind != EntityKind::EGG)
|
if (npc.second->kind != EntityKind::EGG)
|
||||||
@ -134,7 +68,7 @@ static void eggStep(CNServer* serv, time_t currTime) {
|
|||||||
if (!egg->dead || !Chunking::inPopulatedChunks(&egg->viewableChunks))
|
if (!egg->dead || !Chunking::inPopulatedChunks(&egg->viewableChunks))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (egg->deadUntil <= timeStamp) {
|
if (egg->deadUntil <= currTime) {
|
||||||
// respawn it
|
// respawn it
|
||||||
egg->dead = false;
|
egg->dead = false;
|
||||||
egg->deadUntil = 0;
|
egg->deadUntil = 0;
|
||||||
@ -191,16 +125,13 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) {
|
|||||||
|
|
||||||
EggType* type = &EggTypes[typeId];
|
EggType* type = &EggTypes[typeId];
|
||||||
|
|
||||||
// buff the player
|
|
||||||
if (type->effectId != 0)
|
|
||||||
eggBuffPlayer(sock, type->effectId, eggRef.id, type->duration);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* SHINY_PICKUP_SUCC is only causing a GUI effect in the client
|
* SHINY_PICKUP_SUCC is only causing a GUI effect in the client
|
||||||
* (buff icon pops up in the bottom of the screen)
|
* (buff icon pops up in the bottom of the screen)
|
||||||
* so we don't send it for non-effect
|
* so we don't send it for non-effect
|
||||||
*/
|
*/
|
||||||
if (type->effectId != 0) {
|
if (type->effectId != 0) {
|
||||||
|
eggBuffPlayer(sock, type->effectId, eggRef.id, type->duration);
|
||||||
INITSTRUCT(sP_FE2CL_REP_SHINY_PICKUP_SUCC, resp);
|
INITSTRUCT(sP_FE2CL_REP_SHINY_PICKUP_SUCC, resp);
|
||||||
resp.iSkillID = type->effectId;
|
resp.iSkillID = type->effectId;
|
||||||
|
|
||||||
|
@ -10,12 +10,10 @@ struct EggType {
|
|||||||
};
|
};
|
||||||
|
|
||||||
namespace Eggs {
|
namespace Eggs {
|
||||||
extern std::map<std::pair<CNSocket*, int32_t>, time_t> EggBuffs;
|
|
||||||
extern std::unordered_map<int, EggType> EggTypes;
|
extern std::unordered_map<int, EggType> EggTypes;
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
|
|
||||||
/// returns -1 on fail
|
void eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration);
|
||||||
int eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration);
|
|
||||||
void npcDataToEggData(int x, int y, int z, sNPCAppearanceData* npc, sShinyAppearanceData* egg);
|
void npcDataToEggData(int x, int y, int z, sNPCAppearanceData* npc, sShinyAppearanceData* egg);
|
||||||
}
|
}
|
||||||
|
@ -83,6 +83,10 @@ void Egg::enterIntoViewOf(CNSocket *sock) {
|
|||||||
sock->sendPacket(pkt, P_FE2CL_SHINY_ENTER);
|
sock->sendPacket(pkt, P_FE2CL_SHINY_ENTER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sNano* Player::getActiveNano() {
|
||||||
|
return &Nanos[activeNano];
|
||||||
|
}
|
||||||
|
|
||||||
sPCAppearanceData Player::getAppearanceData() {
|
sPCAppearanceData Player::getAppearanceData() {
|
||||||
sPCAppearanceData data = {};
|
sPCAppearanceData data = {};
|
||||||
data.iID = iID;
|
data.iID = iID;
|
||||||
|
@ -3,18 +3,16 @@
|
|||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
#include "Chunking.hpp"
|
#include "Chunking.hpp"
|
||||||
|
|
||||||
|
#include "EntityRef.hpp"
|
||||||
|
#include "Buffs.hpp"
|
||||||
|
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
enum EntityKind {
|
/* forward declaration(s) */
|
||||||
INVALID,
|
class Chunk;
|
||||||
PLAYER,
|
struct Group;
|
||||||
SIMPLE_NPC,
|
|
||||||
COMBAT_NPC,
|
|
||||||
MOB,
|
|
||||||
EGG,
|
|
||||||
BUS
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class AIState {
|
enum class AIState {
|
||||||
INACTIVE,
|
INACTIVE,
|
||||||
@ -24,9 +22,6 @@ enum class AIState {
|
|||||||
DEAD
|
DEAD
|
||||||
};
|
};
|
||||||
|
|
||||||
class Chunk;
|
|
||||||
struct Group;
|
|
||||||
|
|
||||||
struct Entity {
|
struct Entity {
|
||||||
EntityKind kind = EntityKind::INVALID;
|
EntityKind kind = EntityKind::INVALID;
|
||||||
int x = 0, y = 0, z = 0;
|
int x = 0, y = 0, z = 0;
|
||||||
@ -44,42 +39,6 @@ struct Entity {
|
|||||||
virtual void disappearFromViewOf(CNSocket *sock) = 0;
|
virtual void disappearFromViewOf(CNSocket *sock) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct EntityRef {
|
|
||||||
EntityKind kind;
|
|
||||||
union {
|
|
||||||
CNSocket *sock;
|
|
||||||
int32_t id;
|
|
||||||
};
|
|
||||||
|
|
||||||
EntityRef(CNSocket *s);
|
|
||||||
EntityRef(int32_t i);
|
|
||||||
|
|
||||||
bool isValid() const;
|
|
||||||
Entity *getEntity() const;
|
|
||||||
|
|
||||||
bool operator==(const EntityRef& other) const {
|
|
||||||
if (kind != other.kind)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (kind == EntityKind::PLAYER)
|
|
||||||
return sock == other.sock;
|
|
||||||
|
|
||||||
return id == other.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
// arbitrary ordering
|
|
||||||
bool operator<(const EntityRef& other) const {
|
|
||||||
if (kind == other.kind) {
|
|
||||||
if (kind == EntityKind::PLAYER)
|
|
||||||
return sock < other.sock;
|
|
||||||
else
|
|
||||||
return id < other.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return kind < other.kind;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Interfaces
|
* Interfaces
|
||||||
*/
|
*/
|
||||||
@ -88,11 +47,22 @@ public:
|
|||||||
ICombatant() {}
|
ICombatant() {}
|
||||||
virtual ~ICombatant() {}
|
virtual ~ICombatant() {}
|
||||||
|
|
||||||
|
virtual bool addBuff(int, BuffCallback<int, BuffStack*>, BuffCallback<time_t>, BuffStack*) = 0;
|
||||||
|
virtual Buff* getBuff(int) = 0;
|
||||||
|
virtual void removeBuff(int) = 0;
|
||||||
|
virtual void removeBuff(int, int) = 0;
|
||||||
|
virtual bool hasBuff(int) = 0;
|
||||||
|
virtual int getCompositeCondition() = 0;
|
||||||
virtual int takeDamage(EntityRef, int) = 0;
|
virtual int takeDamage(EntityRef, int) = 0;
|
||||||
virtual void heal(EntityRef, int) = 0;
|
virtual int heal(EntityRef, int) = 0;
|
||||||
virtual bool isAlive() = 0;
|
virtual bool isAlive() = 0;
|
||||||
virtual int getCurrentHP() = 0;
|
virtual int getCurrentHP() = 0;
|
||||||
|
virtual int getMaxHP() = 0;
|
||||||
|
virtual int getLevel() = 0;
|
||||||
|
virtual std::vector<EntityRef> getGroupMembers() = 0;
|
||||||
|
virtual int32_t getCharType() = 0;
|
||||||
virtual int32_t getID() = 0;
|
virtual int32_t getID() = 0;
|
||||||
|
virtual EntityRef getRef() = 0;
|
||||||
virtual void step(time_t currTime) = 0;
|
virtual void step(time_t currTime) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -109,6 +79,7 @@ public:
|
|||||||
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) {
|
||||||
|
kind = EntityKind::SIMPLE_NPC;
|
||||||
type = t;
|
type = t;
|
||||||
hp = 400;
|
hp = 400;
|
||||||
angle = _A;
|
angle = _A;
|
||||||
@ -143,17 +114,30 @@ struct CombatNPC : public BaseNPC, public ICombatant {
|
|||||||
spawnY = y;
|
spawnY = y;
|
||||||
spawnZ = z;
|
spawnZ = z;
|
||||||
|
|
||||||
|
kind = EntityKind::COMBAT_NPC;
|
||||||
|
|
||||||
stateHandlers[AIState::INACTIVE] = {};
|
stateHandlers[AIState::INACTIVE] = {};
|
||||||
transitionHandlers[AIState::INACTIVE] = {};
|
transitionHandlers[AIState::INACTIVE] = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
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 Buff* getBuff(int buffId) override;
|
||||||
|
virtual void removeBuff(int buffId) override;
|
||||||
|
virtual void removeBuff(int buffId, int buffClass) override;
|
||||||
|
virtual bool hasBuff(int buffId) override;
|
||||||
|
virtual int getCompositeCondition() override;
|
||||||
virtual int takeDamage(EntityRef src, int amt) override;
|
virtual int takeDamage(EntityRef src, int amt) override;
|
||||||
virtual void heal(EntityRef src, int amt) override;
|
virtual int heal(EntityRef src, int amt) override;
|
||||||
virtual bool isAlive() override;
|
virtual bool isAlive() override;
|
||||||
virtual int getCurrentHP() override;
|
virtual int getCurrentHP() override;
|
||||||
|
virtual int getMaxHP() override;
|
||||||
|
virtual int getLevel() override;
|
||||||
|
virtual std::vector<EntityRef> getGroupMembers() override;
|
||||||
|
virtual int32_t getCharType() override;
|
||||||
virtual int32_t getID() override;
|
virtual int32_t getID() override;
|
||||||
|
virtual EntityRef getRef() override;
|
||||||
virtual void step(time_t currTime) override;
|
virtual void step(time_t currTime) override;
|
||||||
|
|
||||||
virtual void transition(AIState newState, EntityRef src);
|
virtual void transition(AIState newState, EntityRef src);
|
||||||
|
56
src/EntityRef.hpp
Normal file
56
src/EntityRef.hpp
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/Core.hpp"
|
||||||
|
|
||||||
|
/* forward declaration(s) */
|
||||||
|
struct Entity;
|
||||||
|
|
||||||
|
enum EntityKind {
|
||||||
|
INVALID,
|
||||||
|
PLAYER,
|
||||||
|
SIMPLE_NPC,
|
||||||
|
COMBAT_NPC,
|
||||||
|
MOB,
|
||||||
|
EGG,
|
||||||
|
BUS
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EntityRef {
|
||||||
|
EntityKind kind;
|
||||||
|
union {
|
||||||
|
CNSocket *sock;
|
||||||
|
int32_t id;
|
||||||
|
};
|
||||||
|
|
||||||
|
EntityRef(CNSocket *s);
|
||||||
|
EntityRef(int32_t i);
|
||||||
|
|
||||||
|
bool isValid() const;
|
||||||
|
Entity *getEntity() const;
|
||||||
|
|
||||||
|
bool operator==(const EntityRef& other) const {
|
||||||
|
if (kind != other.kind)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (kind == EntityKind::PLAYER)
|
||||||
|
return sock == other.sock;
|
||||||
|
|
||||||
|
return id == other.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const EntityRef& other) const {
|
||||||
|
return !(*this == other);
|
||||||
|
}
|
||||||
|
|
||||||
|
// arbitrary ordering
|
||||||
|
bool operator<(const EntityRef& other) const {
|
||||||
|
if (kind == other.kind) {
|
||||||
|
if (kind == EntityKind::PLAYER)
|
||||||
|
return sock < other.sock;
|
||||||
|
else
|
||||||
|
return id < other.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return kind < other.kind;
|
||||||
|
}
|
||||||
|
};
|
355
src/Groups.cpp
355
src/Groups.cpp
@ -15,7 +15,54 @@
|
|||||||
|
|
||||||
using namespace Groups;
|
using namespace Groups;
|
||||||
|
|
||||||
void Groups::addToGroup(EntityRef member, Group* group) {
|
Group::Group(EntityRef leader) {
|
||||||
|
addToGroup(this, leader);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void attachGroupData(std::vector<EntityRef>& pcs, std::vector<EntityRef>& npcs, uint8_t* pivot) {
|
||||||
|
for(EntityRef pcRef : pcs) {
|
||||||
|
sPCGroupMemberInfo* info = (sPCGroupMemberInfo*)pivot;
|
||||||
|
|
||||||
|
Player* plr = PlayerManager::getPlayer(pcRef.sock);
|
||||||
|
info->iPC_ID = plr->iID;
|
||||||
|
info->iPCUID = plr->PCStyle.iPC_UID;
|
||||||
|
info->iNameCheck = plr->PCStyle.iNameCheck;
|
||||||
|
memcpy(info->szFirstName, plr->PCStyle.szFirstName, sizeof(plr->PCStyle.szFirstName));
|
||||||
|
memcpy(info->szLastName, plr->PCStyle.szLastName, sizeof(plr->PCStyle.szLastName));
|
||||||
|
info->iSpecialState = plr->iSpecialState;
|
||||||
|
info->iLv = plr->level;
|
||||||
|
info->iHP = plr->HP;
|
||||||
|
info->iMaxHP = PC_MAXHEALTH(plr->level);
|
||||||
|
// info->iMapType = 0;
|
||||||
|
// info->iMapNum = 0;
|
||||||
|
info->iX = plr->x;
|
||||||
|
info->iY = plr->y;
|
||||||
|
info->iZ = plr->z;
|
||||||
|
if(plr->activeNano > 0) {
|
||||||
|
info->Nano = *plr->getActiveNano();
|
||||||
|
info->bNano = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pivot = (uint8_t*)(info + 1);
|
||||||
|
}
|
||||||
|
for(EntityRef npcRef : npcs) {
|
||||||
|
sNPCGroupMemberInfo* info = (sNPCGroupMemberInfo*)pivot;
|
||||||
|
|
||||||
|
// probably should not assume that the combatant is an
|
||||||
|
// entity, but it works for now
|
||||||
|
BaseNPC* npc = (BaseNPC*)npcRef.getEntity();
|
||||||
|
info->iNPC_ID = npcRef.id;
|
||||||
|
info->iNPC_Type = npc->type;
|
||||||
|
info->iHP = npc->hp;
|
||||||
|
info->iX = npc->x;
|
||||||
|
info->iY = npc->y;
|
||||||
|
info->iZ = npc->z;
|
||||||
|
|
||||||
|
pivot = (uint8_t*)(info + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Groups::addToGroup(Group* group, EntityRef member) {
|
||||||
if (member.kind == EntityKind::PLAYER) {
|
if (member.kind == EntityKind::PLAYER) {
|
||||||
Player* plr = PlayerManager::getPlayer(member.sock);
|
Player* plr = PlayerManager::getPlayer(member.sock);
|
||||||
plr->group = group;
|
plr->group = group;
|
||||||
@ -29,37 +76,102 @@ void Groups::addToGroup(EntityRef member, Group* group) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
group->members.push_back(member);
|
group->members.push_back(member);
|
||||||
|
|
||||||
|
if(member.kind == EntityKind::PLAYER) {
|
||||||
|
std::vector<EntityRef> pcs = group->filter(EntityKind::PLAYER);
|
||||||
|
std::vector<EntityRef> npcs = group->filter(EntityKind::COMBAT_NPC);
|
||||||
|
size_t pcCount = pcs.size();
|
||||||
|
size_t npcCount = npcs.size();
|
||||||
|
|
||||||
|
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||||
|
memset(respbuf, 0, CN_PACKET_BUFFER_SIZE);
|
||||||
|
sP_FE2CL_PC_GROUP_JOIN* pkt = (sP_FE2CL_PC_GROUP_JOIN*)respbuf;
|
||||||
|
|
||||||
|
pkt->iID_NewMember = PlayerManager::getPlayer(member.sock)->iID;
|
||||||
|
pkt->iMemberPCCnt = (int32_t)pcCount;
|
||||||
|
pkt->iMemberNPCCnt = (int32_t)npcCount;
|
||||||
|
|
||||||
|
if(!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_JOIN), pcCount, sizeof(sPCGroupMemberInfo))
|
||||||
|
|| !validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_JOIN) + pcCount * sizeof(sPCGroupMemberInfo), npcCount, sizeof(sNPCGroupMemberInfo))) {
|
||||||
|
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_JOIN packet size" << std::endl;
|
||||||
|
} else {
|
||||||
|
uint8_t* pivot = (uint8_t*)(pkt + 1);
|
||||||
|
attachGroupData(pcs, npcs, pivot);
|
||||||
|
// PC_GROUP_JOIN_SUCC and PC_GROUP_JOIN carry identical payloads but have different IDs
|
||||||
|
// (and the client does care!) so we need to send one to the new member
|
||||||
|
// and the other to the rest
|
||||||
|
size_t resplen = sizeof(sP_FE2CL_PC_GROUP_JOIN) + pcCount * sizeof(sPCGroupMemberInfo) + npcCount * sizeof(sNPCGroupMemberInfo);
|
||||||
|
member.sock->sendPacket(respbuf, P_FE2CL_PC_GROUP_JOIN_SUCC, resplen);
|
||||||
|
sendToGroup(group, member, respbuf, P_FE2CL_PC_GROUP_JOIN, resplen);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Groups::removeFromGroup(EntityRef member, Group* group) {
|
bool Groups::removeFromGroup(Group* group, EntityRef member) {
|
||||||
if (member.kind == EntityKind::PLAYER) {
|
if (member.kind == EntityKind::PLAYER) {
|
||||||
Player* plr = PlayerManager::getPlayer(member.sock);
|
Player* plr = PlayerManager::getPlayer(member.sock);
|
||||||
plr->group = nullptr; // no dangling pointers here muahaahahah
|
plr->group = nullptr; // no dangling pointers here muahaahahah
|
||||||
|
|
||||||
|
INITSTRUCT(sP_FE2CL_PC_GROUP_LEAVE_SUCC, leavePkt);
|
||||||
|
member.sock->sendPacket(leavePkt, P_FE2CL_PC_GROUP_LEAVE_SUCC);
|
||||||
}
|
}
|
||||||
else if (member.kind == EntityKind::COMBAT_NPC) {
|
else if (member.kind == EntityKind::COMBAT_NPC) {
|
||||||
CombatNPC* npc = (CombatNPC*)member.getEntity();
|
CombatNPC* npc = (CombatNPC*)member.getEntity();
|
||||||
npc->group = nullptr;
|
npc->group = nullptr;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
std::cout << "[WARN] Adding a weird entity type to a group" << std::endl;
|
std::cout << "[WARN] Removing a weird entity type from a group" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto it = std::find(group->members.begin(), group->members.end(), member);
|
auto it = std::find(group->members.begin(), group->members.end(), member);
|
||||||
if (it == group->members.end()) {
|
if (it == group->members.end()) {
|
||||||
std::cout << "[WARN] Tried to remove a member that isn't in the group" << std::endl;
|
std::cout << "[WARN] Tried to remove a member that isn't in the group" << std::endl;
|
||||||
return;
|
} else {
|
||||||
|
group->members.erase(it);
|
||||||
}
|
}
|
||||||
|
|
||||||
group->members.erase(it);
|
if(member.kind == EntityKind::PLAYER) {
|
||||||
|
std::vector<EntityRef> pcs = group->filter(EntityKind::PLAYER);
|
||||||
|
std::vector<EntityRef> npcs = group->filter(EntityKind::COMBAT_NPC);
|
||||||
|
size_t pcCount = pcs.size();
|
||||||
|
size_t npcCount = npcs.size();
|
||||||
|
|
||||||
if (group->members.empty()) delete group; // cleanup memory
|
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||||
|
memset(respbuf, 0, CN_PACKET_BUFFER_SIZE);
|
||||||
|
sP_FE2CL_PC_GROUP_LEAVE* pkt = (sP_FE2CL_PC_GROUP_LEAVE*)respbuf;
|
||||||
|
|
||||||
|
pkt->iID_LeaveMember = PlayerManager::getPlayer(member.sock)->iID;
|
||||||
|
pkt->iMemberPCCnt = (int32_t)pcCount;
|
||||||
|
pkt->iMemberNPCCnt = (int32_t)npcCount;
|
||||||
|
|
||||||
|
if(!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_LEAVE), pcCount, sizeof(sPCGroupMemberInfo))
|
||||||
|
|| !validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_LEAVE) + pcCount * sizeof(sPCGroupMemberInfo), npcCount, sizeof(sNPCGroupMemberInfo))) {
|
||||||
|
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_LEAVE packet size" << std::endl;
|
||||||
|
} else {
|
||||||
|
uint8_t* pivot = (uint8_t*)(pkt + 1);
|
||||||
|
attachGroupData(pcs, npcs, pivot);
|
||||||
|
sendToGroup(group, respbuf, P_FE2CL_PC_GROUP_LEAVE,
|
||||||
|
sizeof(sP_FE2CL_PC_GROUP_LEAVE) + pcCount * sizeof(sPCGroupMemberInfo) + npcCount * sizeof(sNPCGroupMemberInfo));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (group->members.size() == 1) {
|
||||||
|
return removeFromGroup(group, group->members.back());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (group->members.empty()) {
|
||||||
|
delete group; // cleanup memory
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Groups::disbandGroup(Group* group) {
|
void Groups::disbandGroup(Group* group) {
|
||||||
// remove everyone from the group!!
|
// remove everyone from the group!!
|
||||||
std::vector<EntityRef> members = group->members;
|
bool done = false;
|
||||||
for (EntityRef member : members) {
|
while(!done) {
|
||||||
removeFromGroup(member, group);
|
EntityRef back = group->members.back();
|
||||||
|
done = removeFromGroup(group, back);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,7 +226,7 @@ static void joinGroup(CNSocket* sock, CNPacketData* data) {
|
|||||||
Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_From);
|
Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_From);
|
||||||
|
|
||||||
if (otherPlr == nullptr)
|
if (otherPlr == nullptr)
|
||||||
return;
|
return; // disconnect or something
|
||||||
|
|
||||||
int size = otherPlr->group == nullptr ? 1 : otherPlr->group->filter(EntityKind::PLAYER).size();
|
int size = otherPlr->group == nullptr ? 1 : otherPlr->group->filter(EntityKind::PLAYER).size();
|
||||||
|
|
||||||
@ -125,220 +237,69 @@ static void joinGroup(CNSocket* sock, CNPacketData* data) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_JOIN), size + 1, sizeof(sPCGroupMemberInfo))) {
|
|
||||||
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_JOIN packet size\n";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (otherPlr->group == nullptr) {
|
if (otherPlr->group == nullptr) {
|
||||||
// create group
|
// create group
|
||||||
otherPlr->group = new Group(); // spooky
|
EntityRef otherPlrRef = PlayerManager::getSockFromID(recv->iID_From);
|
||||||
addToGroup(PlayerManager::getSockFromID(recv->iID_From), otherPlr->group);
|
otherPlr->group = new Group(otherPlrRef);
|
||||||
}
|
}
|
||||||
addToGroup(sock, otherPlr->group);
|
addToGroup(otherPlr->group, sock);
|
||||||
auto players = otherPlr->group->filter(EntityKind::PLAYER);
|
|
||||||
|
|
||||||
size_t resplen = sizeof(sP_FE2CL_PC_GROUP_JOIN) + players.size() * sizeof(sPCGroupMemberInfo);
|
|
||||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
|
||||||
|
|
||||||
memset(respbuf, 0, resplen);
|
|
||||||
|
|
||||||
sP_FE2CL_PC_GROUP_JOIN *resp = (sP_FE2CL_PC_GROUP_JOIN*)respbuf;
|
|
||||||
sPCGroupMemberInfo *respdata = (sPCGroupMemberInfo*)(respbuf+sizeof(sP_FE2CL_PC_GROUP_JOIN));
|
|
||||||
|
|
||||||
resp->iID_NewMember = plr->iID;
|
|
||||||
resp->iMemberPCCnt = players.size();
|
|
||||||
|
|
||||||
//int bitFlag = otherPlr->group->conditionBitFlag;
|
|
||||||
for (int i = 0; i < players.size(); i++) {
|
|
||||||
|
|
||||||
Player* varPlr = PlayerManager::getPlayer(players[i].sock);
|
|
||||||
CNSocket* sockTo = players[i].sock;
|
|
||||||
|
|
||||||
if (varPlr == nullptr || sockTo == nullptr)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
respdata[i].iPC_ID = varPlr->iID;
|
|
||||||
respdata[i].iPCUID = varPlr->PCStyle.iPC_UID;
|
|
||||||
respdata[i].iNameCheck = varPlr->PCStyle.iNameCheck;
|
|
||||||
memcpy(respdata[i].szFirstName, varPlr->PCStyle.szFirstName, sizeof(varPlr->PCStyle.szFirstName));
|
|
||||||
memcpy(respdata[i].szLastName, varPlr->PCStyle.szLastName, sizeof(varPlr->PCStyle.szLastName));
|
|
||||||
respdata[i].iSpecialState = varPlr->iSpecialState;
|
|
||||||
respdata[i].iLv = varPlr->level;
|
|
||||||
respdata[i].iHP = varPlr->HP;
|
|
||||||
respdata[i].iMaxHP = PC_MAXHEALTH(varPlr->level);
|
|
||||||
//respdata[i].iMapType = 0;
|
|
||||||
//respdata[i].iMapNum = 0;
|
|
||||||
respdata[i].iX = varPlr->x;
|
|
||||||
respdata[i].iY = varPlr->y;
|
|
||||||
respdata[i].iZ = varPlr->z;
|
|
||||||
// client doesnt read nano data here
|
|
||||||
|
|
||||||
if (varPlr != plr) { // apply the new member's buffs to the group and the group's buffs to the new member
|
|
||||||
// TODO ABILITIES
|
|
||||||
/*if (Abilities::SkillTable[varPlr->Nanos[varPlr->activeNano].iSkillID].targetType == 3)
|
|
||||||
Abilities::applyBuff(sock, varPlr->Nanos[varPlr->activeNano].iSkillID, 1, 1, bitFlag);
|
|
||||||
if (Abilities::SkillTable[plr->Nanos[plr->activeNano].iSkillID].targetType == 3)
|
|
||||||
Abilities::applyBuff(sockTo, plr->Nanos[plr->activeNano].iSkillID, 1, 1, bitFlag);*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Groups::sendToGroup(otherPlr->group, (void*)&respbuf, P_FE2CL_PC_GROUP_JOIN, resplen);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void leaveGroup(CNSocket* sock, CNPacketData* data) {
|
static void leaveGroup(CNSocket* sock, CNPacketData* data) {
|
||||||
Player* plr = PlayerManager::getPlayer(sock);
|
Player* plr = PlayerManager::getPlayer(sock);
|
||||||
groupKick(plr);
|
groupKick(plr->group, sock);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Groups::sendToGroup(Group* group, void* buf, uint32_t type, size_t size) {
|
void Groups::sendToGroup(Group* group, void* buf, uint32_t type, size_t size) {
|
||||||
auto players = group->filter(EntityKind::PLAYER);
|
auto players = group->filter(EntityKind::PLAYER);
|
||||||
for (int i = 0; i < players.size(); i++) {
|
for (EntityRef ref : players) {
|
||||||
CNSocket* sock = players[i].sock;
|
ref.sock->sendPacket(buf, type, size);
|
||||||
sock->sendPacket(buf, type, size);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Groups::groupTickInfo(Player* plr) {
|
void Groups::sendToGroup(Group* group, EntityRef excluded, void* buf, uint32_t type, size_t size) {
|
||||||
|
auto players = group->filter(EntityKind::PLAYER);
|
||||||
auto players = plr->group->filter(EntityKind::PLAYER);
|
for (EntityRef ref : players) {
|
||||||
|
if(ref != excluded) ref.sock->sendPacket(buf, type, size);
|
||||||
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO), players.size(), sizeof(sPCGroupMemberInfo))) {
|
|
||||||
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_JOIN packet size\n";
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Groups::groupTickInfo(CNSocket* sock) {
|
||||||
|
Player* plr = PlayerManager::getPlayer(sock);
|
||||||
|
Group* group = plr->group;
|
||||||
|
std::vector<EntityRef> pcs = group->filter(EntityKind::PLAYER);
|
||||||
|
std::vector<EntityRef> npcs = group->filter(EntityKind::COMBAT_NPC);
|
||||||
|
size_t pcCount = pcs.size();
|
||||||
|
size_t npcCount = npcs.size();
|
||||||
|
|
||||||
size_t resplen = sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO) + players.size() * sizeof(sPCGroupMemberInfo);
|
|
||||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||||
|
memset(respbuf, 0, CN_PACKET_BUFFER_SIZE);
|
||||||
|
sP_FE2CL_PC_GROUP_MEMBER_INFO* pkt = (sP_FE2CL_PC_GROUP_MEMBER_INFO*)respbuf;
|
||||||
|
|
||||||
memset(respbuf, 0, resplen);
|
pkt->iID = plr->iID;
|
||||||
|
pkt->iMemberPCCnt = (int32_t)pcCount;
|
||||||
|
pkt->iMemberNPCCnt = (int32_t)npcCount;
|
||||||
|
|
||||||
sP_FE2CL_PC_GROUP_MEMBER_INFO *resp = (sP_FE2CL_PC_GROUP_MEMBER_INFO*)respbuf;
|
if(!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO), pcCount, sizeof(sPCGroupMemberInfo))
|
||||||
sPCGroupMemberInfo *respdata = (sPCGroupMemberInfo*)(respbuf+sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO));
|
|| !validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO) + pcCount * sizeof(sPCGroupMemberInfo), npcCount, sizeof(sNPCGroupMemberInfo))) {
|
||||||
|
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_MEMBER_INFO packet size" << std::endl;
|
||||||
resp->iID = plr->iID;
|
} else {
|
||||||
resp->iMemberPCCnt = players.size();
|
uint8_t* pivot = (uint8_t*)(pkt + 1);
|
||||||
|
attachGroupData(pcs, npcs, pivot);
|
||||||
for (int i = 0; i < players.size(); i++) {
|
sock->sendPacket(respbuf, P_FE2CL_PC_GROUP_MEMBER_INFO,
|
||||||
EntityRef member = players[i];
|
sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO) + pcCount * sizeof(sPCGroupMemberInfo) + npcCount * sizeof(sNPCGroupMemberInfo));
|
||||||
Player* varPlr = PlayerManager::getPlayer(member.sock);
|
|
||||||
|
|
||||||
if (varPlr == nullptr)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
respdata[i].iPC_ID = varPlr->iID;
|
|
||||||
respdata[i].iPCUID = varPlr->PCStyle.iPC_UID;
|
|
||||||
respdata[i].iNameCheck = varPlr->PCStyle.iNameCheck;
|
|
||||||
memcpy(respdata[i].szFirstName, varPlr->PCStyle.szFirstName, sizeof(varPlr->PCStyle.szFirstName));
|
|
||||||
memcpy(respdata[i].szLastName, varPlr->PCStyle.szLastName, sizeof(varPlr->PCStyle.szLastName));
|
|
||||||
respdata[i].iSpecialState = varPlr->iSpecialState;
|
|
||||||
respdata[i].iLv = varPlr->level;
|
|
||||||
respdata[i].iHP = varPlr->HP;
|
|
||||||
respdata[i].iMaxHP = PC_MAXHEALTH(varPlr->level);
|
|
||||||
//respdata[i].iMapType = 0;
|
|
||||||
//respdata[i].iMapNum = 0;
|
|
||||||
respdata[i].iX = varPlr->x;
|
|
||||||
respdata[i].iY = varPlr->y;
|
|
||||||
respdata[i].iZ = varPlr->z;
|
|
||||||
if (varPlr->activeNano > 0) {
|
|
||||||
respdata[i].bNano = 1;
|
|
||||||
respdata[i].Nano = varPlr->Nanos[varPlr->activeNano];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sendToGroup(plr->group, (void*)&respbuf, P_FE2CL_PC_GROUP_MEMBER_INFO, resplen);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void groupUnbuff(Player* plr) {
|
|
||||||
Group* group = plr->group;
|
|
||||||
for (int i = 0; i < group->members.size(); i++) {
|
|
||||||
for (int n = 0; n < group->members.size(); n++) {
|
|
||||||
if (i == n)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
EntityRef other = group->members[n];
|
|
||||||
|
|
||||||
// TODO ABILITIES
|
|
||||||
//Abilities::applyBuff(sock, otherPlr->Nanos[otherPlr->activeNano].iSkillID, 2, 1, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Groups::groupKick(Player* plr) {
|
void Groups::groupKick(Group* group, EntityRef ref) {
|
||||||
Group* group = plr->group;
|
|
||||||
|
|
||||||
// if you are the group leader, destroy your own group and kick everybody
|
// if you are the group leader, destroy your own group and kick everybody
|
||||||
if (plr->group->members[0] == PlayerManager::getSockFromID(plr->iID)) {
|
if (group->members[0] == ref) {
|
||||||
groupUnbuff(plr);
|
disbandGroup(group);
|
||||||
INITSTRUCT(sP_FE2CL_PC_GROUP_LEAVE_SUCC, resp1);
|
|
||||||
sendToGroup(plr->group, (void*)&resp1, P_FE2CL_PC_GROUP_LEAVE_SUCC, sizeof(sP_FE2CL_PC_GROUP_LEAVE_SUCC));
|
|
||||||
disbandGroup(plr->group);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto players = group->filter(EntityKind::PLAYER);
|
removeFromGroup(group, ref);
|
||||||
|
|
||||||
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_LEAVE), players.size() - 1, sizeof(sPCGroupMemberInfo))) {
|
|
||||||
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_LEAVE packet size\n";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t resplen = sizeof(sP_FE2CL_PC_GROUP_LEAVE) + (players.size() - 1) * sizeof(sPCGroupMemberInfo);
|
|
||||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
|
||||||
|
|
||||||
memset(respbuf, 0, resplen);
|
|
||||||
|
|
||||||
sP_FE2CL_PC_GROUP_LEAVE *resp = (sP_FE2CL_PC_GROUP_LEAVE*)respbuf;
|
|
||||||
sPCGroupMemberInfo *respdata = (sPCGroupMemberInfo*)(respbuf+sizeof(sP_FE2CL_PC_GROUP_LEAVE));
|
|
||||||
|
|
||||||
resp->iID_LeaveMember = plr->iID;
|
|
||||||
resp->iMemberPCCnt = players.size() - 1;
|
|
||||||
|
|
||||||
int bitFlag = 0; // TODO ABILITIES getGroupFlags(otherPlr) & ~plr->iGroupConditionBitFlag;
|
|
||||||
|
|
||||||
CNSocket* sock = PlayerManager::getSockFromID(plr->iID);
|
|
||||||
|
|
||||||
if (sock == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
removeFromGroup(sock, group);
|
|
||||||
|
|
||||||
players = group->filter(EntityKind::PLAYER);
|
|
||||||
for (int i = 0; i < players.size(); i++) {
|
|
||||||
CNSocket* sockTo = players[i].sock;
|
|
||||||
Player* varPlr = PlayerManager::getPlayer(sock);
|
|
||||||
|
|
||||||
if (varPlr == nullptr || sockTo == nullptr)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
respdata[i].iPC_ID = varPlr->iID;
|
|
||||||
respdata[i].iPCUID = varPlr->PCStyle.iPC_UID;
|
|
||||||
respdata[i].iNameCheck = varPlr->PCStyle.iNameCheck;
|
|
||||||
memcpy(respdata[i].szFirstName, varPlr->PCStyle.szFirstName, sizeof(varPlr->PCStyle.szFirstName));
|
|
||||||
memcpy(respdata[i].szLastName, varPlr->PCStyle.szLastName, sizeof(varPlr->PCStyle.szLastName));
|
|
||||||
respdata[i].iSpecialState = varPlr->iSpecialState;
|
|
||||||
respdata[i].iLv = varPlr->level;
|
|
||||||
respdata[i].iHP = varPlr->HP;
|
|
||||||
respdata[i].iMaxHP = PC_MAXHEALTH(varPlr->level);
|
|
||||||
// respdata[i]].iMapType = 0;
|
|
||||||
// respdata[i]].iMapNum = 0;
|
|
||||||
respdata[i].iX = varPlr->x;
|
|
||||||
respdata[i].iY = varPlr->y;
|
|
||||||
respdata[i].iZ = varPlr->z;
|
|
||||||
// client doesnt read nano data here
|
|
||||||
|
|
||||||
// remove the leaving member's buffs from the group and remove the group buffs from the leaving member.
|
|
||||||
// TODO ABILITIES
|
|
||||||
/*if (Abilities::SkillTable[varPlr->Nanos[varPlr->activeNano].iSkillID].targetType == 3)
|
|
||||||
Abilities::applyBuff(sock, varPlr->Nanos[varPlr->activeNano].iSkillID, 2, 1, 0);
|
|
||||||
if (Abilities::SkillTable[plr->Nanos[varPlr->activeNano].iSkillID].targetType == 3)
|
|
||||||
Abilities::applyBuff(sockTo, plr->Nanos[plr->activeNano].iSkillID, 2, 1, bitFlag);*/
|
|
||||||
}
|
|
||||||
|
|
||||||
sendToGroup(group, (void*)&respbuf, P_FE2CL_PC_GROUP_LEAVE, resplen);
|
|
||||||
|
|
||||||
INITSTRUCT(sP_FE2CL_PC_GROUP_LEAVE_SUCC, resp1);
|
|
||||||
sock->sendPacket((void*)&resp1, P_FE2CL_PC_GROUP_LEAVE_SUCC, sizeof(sP_FE2CL_PC_GROUP_LEAVE_SUCC));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Groups::init() {
|
void Groups::init() {
|
||||||
|
@ -19,16 +19,19 @@ struct Group {
|
|||||||
});
|
});
|
||||||
return filtered;
|
return filtered;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Group(EntityRef leader);
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace Groups {
|
namespace Groups {
|
||||||
void init();
|
void init();
|
||||||
|
|
||||||
void sendToGroup(Group* group, void* buf, uint32_t type, size_t size);
|
void sendToGroup(Group* group, void* buf, uint32_t type, size_t size);
|
||||||
void groupTickInfo(Player* plr);
|
void sendToGroup(Group* group, EntityRef excluded, void* buf, uint32_t type, size_t size);
|
||||||
void groupKick(Player* plr);
|
void groupTickInfo(CNSocket* sock);
|
||||||
|
|
||||||
void addToGroup(EntityRef member, Group* group);
|
void groupKick(Group* group, EntityRef ref);
|
||||||
void removeFromGroup(EntityRef member, Group* group);
|
void addToGroup(Group* group, EntityRef member);
|
||||||
|
bool removeFromGroup(Group* group, EntityRef member); // true iff group deleted
|
||||||
void disbandGroup(Group* group);
|
void disbandGroup(Group* group);
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
#include "Eggs.hpp"
|
#include "Eggs.hpp"
|
||||||
#include "MobAI.hpp"
|
#include "MobAI.hpp"
|
||||||
#include "Missions.hpp"
|
#include "Missions.hpp"
|
||||||
|
#include "Buffs.hpp"
|
||||||
|
|
||||||
#include <string.h> // for memset()
|
#include <string.h> // for memset()
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
@ -484,27 +485,31 @@ static void itemUseHandler(CNSocket* sock, CNPacketData* data) {
|
|||||||
resp->eST = EST_NANOSTIMPAK;
|
resp->eST = EST_NANOSTIMPAK;
|
||||||
resp->iSkillID = 144;
|
resp->iSkillID = 144;
|
||||||
|
|
||||||
int value1 = CSB_BIT_STIMPAKSLOT1 << request->iNanoSlot;
|
int eCSB = ECSB_STIMPAKSLOT1 + request->iNanoSlot;
|
||||||
int value2 = ECSB_STIMPAKSLOT1 + request->iNanoSlot;
|
|
||||||
|
|
||||||
respdata->eCT = 1;
|
respdata->eCT = 1;
|
||||||
respdata->iID = player->iID;
|
respdata->iID = player->iID;
|
||||||
respdata->iConditionBitFlag = value1;
|
respdata->iConditionBitFlag = CSB_FROM_ECSB(eCSB);
|
||||||
|
|
||||||
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt);
|
int durationMilliseconds = Abilities::SkillTable[144].durationTime[0] * 100;
|
||||||
pkt.eCSTB = value2; // eCharStatusTimeBuffID
|
BuffStack gumballBuff = {
|
||||||
pkt.eTBU = 1; // eTimeBuffUpdate
|
durationMilliseconds / MS_PER_PLAYER_TICK,
|
||||||
pkt.eTBT = 1; // eTimeBuffType 1 means nano
|
0,
|
||||||
pkt.iConditionBitFlag = player->iConditionBitFlag |= value1;
|
sock,
|
||||||
sock->sendPacket(pkt, P_FE2CL_PC_BUFF_UPDATE);
|
BuffClass::CASH_ITEM // or BuffClass::ITEM?
|
||||||
|
};
|
||||||
|
player->addBuff(eCSB,
|
||||||
|
[](EntityRef self, Buff* buff, int status, BuffStack* stack) {
|
||||||
|
Buffs::timeBuffUpdate(self, buff, status, stack);
|
||||||
|
},
|
||||||
|
[](EntityRef self, Buff* buff, time_t currTime) {
|
||||||
|
// no-op
|
||||||
|
},
|
||||||
|
&gumballBuff);
|
||||||
|
|
||||||
sock->sendPacket((void*)&respbuf, P_FE2CL_REP_PC_ITEM_USE_SUCC, resplen);
|
sock->sendPacket((void*)&respbuf, P_FE2CL_REP_PC_ITEM_USE_SUCC, resplen);
|
||||||
// update inventory serverside
|
// update inventory serverside
|
||||||
player->Inven[resp->iSlotNum] = resp->RemainItem;
|
player->Inven[resp->iSlotNum] = resp->RemainItem;
|
||||||
|
|
||||||
std::pair<CNSocket*, int32_t> key = std::make_pair(sock, value1);
|
|
||||||
time_t until = getTime() + (time_t)Abilities::SkillTable[144].durationTime[0] * 100;
|
|
||||||
Eggs::EggBuffs[key] = until;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void itemBankOpenHandler(CNSocket* sock, CNPacketData* data) {
|
static void itemBankOpenHandler(CNSocket* sock, CNPacketData* data) {
|
||||||
@ -757,7 +762,7 @@ static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRo
|
|||||||
if (rolled.taros % miscDropChance.taroDropChanceTotal < miscDropChance.taroDropChance) {
|
if (rolled.taros % miscDropChance.taroDropChanceTotal < miscDropChance.taroDropChance) {
|
||||||
plr->money += miscDropType.taroAmount;
|
plr->money += miscDropType.taroAmount;
|
||||||
// money nano boost
|
// money nano boost
|
||||||
if (plr->iConditionBitFlag & CSB_BIT_REWARD_CASH) {
|
if (plr->hasBuff(ECSB_REWARD_CASH)) {
|
||||||
int boost = 0;
|
int boost = 0;
|
||||||
if (Nanos::getNanoBoost(plr)) // for gumballs
|
if (Nanos::getNanoBoost(plr)) // for gumballs
|
||||||
boost = 1;
|
boost = 1;
|
||||||
@ -772,7 +777,7 @@ static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRo
|
|||||||
if (levelDifference > 0)
|
if (levelDifference > 0)
|
||||||
fm = levelDifference < 10 ? fm - (levelDifference * fm / 10) : 0;
|
fm = levelDifference < 10 ? fm - (levelDifference * fm / 10) : 0;
|
||||||
// scavenger nano boost
|
// scavenger nano boost
|
||||||
if (plr->iConditionBitFlag & CSB_BIT_REWARD_BLOB) {
|
if (plr->hasBuff(ECSB_REWARD_BLOB)) {
|
||||||
int boost = 0;
|
int boost = 0;
|
||||||
if (Nanos::getNanoBoost(plr)) // for gumballs
|
if (Nanos::getNanoBoost(plr)) // for gumballs
|
||||||
boost = 1;
|
boost = 1;
|
||||||
|
@ -163,14 +163,14 @@ static int giveMissionReward(CNSocket *sock, int task, int choice=0) {
|
|||||||
|
|
||||||
// update player
|
// update player
|
||||||
plr->money += reward->money;
|
plr->money += reward->money;
|
||||||
if (plr->iConditionBitFlag & CSB_BIT_REWARD_CASH) { // nano boost for taros
|
if (plr->hasBuff(ECSB_REWARD_CASH)) { // nano boost for taros
|
||||||
int boost = 0;
|
int boost = 0;
|
||||||
if (Nanos::getNanoBoost(plr)) // for gumballs
|
if (Nanos::getNanoBoost(plr)) // for gumballs
|
||||||
boost = 1;
|
boost = 1;
|
||||||
plr->money += reward->money * (5 + boost) / 25;
|
plr->money += reward->money * (5 + boost) / 25;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (plr->iConditionBitFlag & CSB_BIT_REWARD_BLOB) { // nano boost for fm
|
if (plr->hasBuff(ECSB_REWARD_BLOB)) { // nano boost for fm
|
||||||
int boost = 0;
|
int boost = 0;
|
||||||
if (Nanos::getNanoBoost(plr)) // for gumballs
|
if (Nanos::getNanoBoost(plr)) // for gumballs
|
||||||
boost = 1;
|
boost = 1;
|
||||||
|
@ -187,7 +187,7 @@ bool MobAI::aggroCheck(Mob *mob, time_t currTime) {
|
|||||||
|
|
||||||
int mobRange = mob->sightRange;
|
int mobRange = mob->sightRange;
|
||||||
|
|
||||||
if (plr->iConditionBitFlag & CSB_BIT_UP_STEALTH
|
if (plr->hasBuff(ECSB_UP_STEALTH)
|
||||||
|| Racing::EPRaces.find(s) != Racing::EPRaces.end())
|
|| Racing::EPRaces.find(s) != Racing::EPRaces.end())
|
||||||
mobRange /= 3;
|
mobRange /= 3;
|
||||||
|
|
||||||
@ -315,7 +315,7 @@ static void dealCorruption(Mob *mob, std::vector<int> targetData, int skillID, i
|
|||||||
plr->HP -= respdata[i].iDamage;
|
plr->HP -= respdata[i].iDamage;
|
||||||
|
|
||||||
respdata[i].iHP = plr->HP;
|
respdata[i].iHP = plr->HP;
|
||||||
respdata[i].iConditionBitFlag = plr->iConditionBitFlag;
|
respdata[i].iConditionBitFlag = plr->getCompositeCondition();
|
||||||
|
|
||||||
if (plr->HP <= 0) {
|
if (plr->HP <= 0) {
|
||||||
if (!MobAI::aggroCheck(mob, getTime()))
|
if (!MobAI::aggroCheck(mob, getTime()))
|
||||||
|
@ -81,7 +81,7 @@ void NPCManager::updateNPCPosition(int32_t id, int X, int Y, int Z, uint64_t I,
|
|||||||
Chunking::updateEntityChunk({id}, oldChunk, newChunk);
|
Chunking::updateEntityChunk({id}, oldChunk, newChunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NPCManager::sendToViewable(BaseNPC *npc, void *buf, uint32_t type, size_t size) {
|
void NPCManager::sendToViewable(Entity *npc, void *buf, uint32_t type, size_t size) {
|
||||||
for (auto it = npc->viewableChunks.begin(); it != npc->viewableChunks.end(); it++) {
|
for (auto it = npc->viewableChunks.begin(); it != npc->viewableChunks.end(); it++) {
|
||||||
Chunk* chunk = *it;
|
Chunk* chunk = *it;
|
||||||
for (const EntityRef& ref : chunk->entities) {
|
for (const EntityRef& ref : chunk->entities) {
|
||||||
|
@ -42,7 +42,7 @@ namespace NPCManager {
|
|||||||
void destroyNPC(int32_t);
|
void destroyNPC(int32_t);
|
||||||
void updateNPCPosition(int32_t, int X, int Y, int Z, uint64_t I, int angle);
|
void updateNPCPosition(int32_t, int X, int Y, int Z, uint64_t I, int angle);
|
||||||
|
|
||||||
void sendToViewable(BaseNPC* npc, void* buf, uint32_t type, size_t size);
|
void sendToViewable(Entity* npc, void* buf, uint32_t type, size_t size);
|
||||||
|
|
||||||
BaseNPC *summonNPC(int x, int y, int z, uint64_t instance, int type, bool respawn=false, bool baseInstance=false);
|
BaseNPC *summonNPC(int x, int y, int z, uint64_t instance, int type, bool respawn=false, bool baseInstance=false);
|
||||||
|
|
||||||
|
@ -69,6 +69,52 @@ void Nanos::addNano(CNSocket* sock, int16_t nanoID, int16_t slot, bool spendfm)
|
|||||||
PlayerManager::sendToViewable(sock, resp2, P_FE2CL_REP_PC_CHANGE_LEVEL);
|
PlayerManager::sendToViewable(sock, resp2, P_FE2CL_REP_PC_CHANGE_LEVEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<ICombatant*> Nanos::applyNanoBuff(SkillData* skill, Player* plr) {
|
||||||
|
assert(skill->drainType == SkillDrainType::PASSIVE);
|
||||||
|
|
||||||
|
EntityRef self = PlayerManager::getSockFromID(plr->iID);
|
||||||
|
std::vector<ICombatant*> affected;
|
||||||
|
std::vector<EntityRef> targets;
|
||||||
|
if (skill->targetType == SkillTargetType::GROUP) {
|
||||||
|
targets = plr->getGroupMembers(); // group
|
||||||
|
}
|
||||||
|
else if(skill->targetType == SkillTargetType::SELF) {
|
||||||
|
targets.push_back(self); // self
|
||||||
|
} else {
|
||||||
|
std::cout << "[WARN] Passive skill with type " << skill->skillType << " has target type MOB" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
int timeBuffId = Abilities::getCSTBFromST(skill->skillType);
|
||||||
|
int boost = Nanos::getNanoBoost(plr) ? 3 : 0;
|
||||||
|
int value = skill->values[0][boost];
|
||||||
|
|
||||||
|
BuffStack passiveBuff = {
|
||||||
|
1, // passive nano buffs refreshed every tick
|
||||||
|
value,
|
||||||
|
self,
|
||||||
|
BuffClass::NONE, // overwritten per target
|
||||||
|
};
|
||||||
|
|
||||||
|
for (EntityRef target : targets) {
|
||||||
|
Entity* entity = target.getEntity();
|
||||||
|
if (entity->kind != PLAYER && entity->kind != COMBAT_NPC && entity->kind != MOB)
|
||||||
|
continue; // not a combatant
|
||||||
|
|
||||||
|
passiveBuff.buffStackClass = target == self ? BuffClass::NANO : BuffClass::GROUP_NANO;
|
||||||
|
ICombatant* combatant = dynamic_cast<ICombatant*>(entity);
|
||||||
|
if(combatant->addBuff(timeBuffId,
|
||||||
|
[](EntityRef self, Buff* buff, int status, BuffStack* stack) {
|
||||||
|
Buffs::timeBuffUpdate(self, buff, status, stack);
|
||||||
|
},
|
||||||
|
[](EntityRef self, Buff* buff, time_t currTime) {
|
||||||
|
// no-op
|
||||||
|
},
|
||||||
|
&passiveBuff)) affected.push_back(combatant);
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected;
|
||||||
|
}
|
||||||
|
|
||||||
void Nanos::summonNano(CNSocket *sock, int slot, bool silent) {
|
void Nanos::summonNano(CNSocket *sock, int slot, bool silent) {
|
||||||
INITSTRUCT(sP_FE2CL_REP_NANO_ACTIVE_SUCC, resp);
|
INITSTRUCT(sP_FE2CL_REP_NANO_ACTIVE_SUCC, resp);
|
||||||
resp.iActiveNanoSlotNum = slot;
|
resp.iActiveNanoSlotNum = slot;
|
||||||
@ -86,10 +132,16 @@ void Nanos::summonNano(CNSocket *sock, int slot, bool silent) {
|
|||||||
return; // sanity check
|
return; // sanity check
|
||||||
|
|
||||||
plr->activeNano = nanoID;
|
plr->activeNano = nanoID;
|
||||||
|
sNano& nano = plr->Nanos[nanoID];
|
||||||
|
|
||||||
int16_t skillID = plr->Nanos[nanoID].iSkillID;
|
SkillData* skill = Abilities::SkillTable.count(nano.iSkillID) > 0
|
||||||
if (Abilities::SkillTable.count(skillID) > 0 && Abilities::SkillTable[skillID].drainType == SkillDrainType::PASSIVE)
|
? &Abilities::SkillTable[nano.iSkillID] : nullptr;
|
||||||
resp.eCSTB___Add = 1; // passive buff effect
|
if (slot != -1 && skill != nullptr && skill->drainType == SkillDrainType::PASSIVE) {
|
||||||
|
// passive buff effect
|
||||||
|
resp.eCSTB___Add = 1;
|
||||||
|
std::vector<ICombatant*> affectedCombatants = applyNanoBuff(skill, plr);
|
||||||
|
if(!affectedCombatants.empty()) Abilities::useNanoSkill(sock, nano, affectedCombatants);
|
||||||
|
}
|
||||||
|
|
||||||
if (!silent) // silent nano death but only for the summoning player
|
if (!silent) // silent nano death but only for the summoning player
|
||||||
sock->sendPacket(resp, P_FE2CL_REP_NANO_ACTIVE_SUCC);
|
sock->sendPacket(resp, P_FE2CL_REP_NANO_ACTIVE_SUCC);
|
||||||
@ -97,7 +149,7 @@ void Nanos::summonNano(CNSocket *sock, int slot, bool silent) {
|
|||||||
// Send to other players, these players can't handle silent nano deaths so this packet needs to be sent.
|
// Send to other players, these players can't handle silent nano deaths so this packet needs to be sent.
|
||||||
INITSTRUCT(sP_FE2CL_NANO_ACTIVE, pkt1);
|
INITSTRUCT(sP_FE2CL_NANO_ACTIVE, pkt1);
|
||||||
pkt1.iPC_ID = plr->iID;
|
pkt1.iPC_ID = plr->iID;
|
||||||
pkt1.Nano = plr->Nanos[nanoID];
|
pkt1.Nano = nano;
|
||||||
PlayerManager::sendToViewable(sock, pkt1, P_FE2CL_NANO_ACTIVE);
|
PlayerManager::sendToViewable(sock, pkt1, P_FE2CL_NANO_ACTIVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,7 +236,7 @@ int Nanos::nanoStyle(int nanoID) {
|
|||||||
bool Nanos::getNanoBoost(Player* plr) {
|
bool Nanos::getNanoBoost(Player* plr) {
|
||||||
for (int i = 0; i < 3; i++)
|
for (int i = 0; i < 3; i++)
|
||||||
if (plr->equippedNanos[i] == plr->activeNano)
|
if (plr->equippedNanos[i] == plr->activeNano)
|
||||||
if (plr->iConditionBitFlag & (CSB_BIT_STIMPAKSLOT1 << i))
|
if (plr->hasBuff(ECSB_STIMPAKSLOT1 + i))
|
||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -208,18 +260,6 @@ static void nanoEquipHandler(CNSocket* sock, CNPacketData* data) {
|
|||||||
// Update player
|
// Update player
|
||||||
plr->equippedNanos[nano->iNanoSlotNum] = nano->iNanoID;
|
plr->equippedNanos[nano->iNanoSlotNum] = nano->iNanoID;
|
||||||
|
|
||||||
// Unbuff gumballs
|
|
||||||
int value1 = CSB_BIT_STIMPAKSLOT1 << nano->iNanoSlotNum;
|
|
||||||
if (plr->iConditionBitFlag & value1) {
|
|
||||||
int value2 = ECSB_STIMPAKSLOT1 + nano->iNanoSlotNum;
|
|
||||||
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt);
|
|
||||||
pkt.eCSTB = value2; // eCharStatusTimeBuffID
|
|
||||||
pkt.eTBU = 2; // eTimeBuffUpdate
|
|
||||||
pkt.eTBT = 1; // eTimeBuffType 1 means nano
|
|
||||||
pkt.iConditionBitFlag = plr->iConditionBitFlag &= ~value1;
|
|
||||||
sock->sendPacket(pkt, P_FE2CL_PC_BUFF_UPDATE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// unsummon nano if replaced
|
// unsummon nano if replaced
|
||||||
if (plr->activeNano == plr->equippedNanos[nano->iNanoSlotNum])
|
if (plr->activeNano == plr->equippedNanos[nano->iNanoSlotNum])
|
||||||
summonNano(sock, -1);
|
summonNano(sock, -1);
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
|
|
||||||
#include "Player.hpp"
|
#include "Player.hpp"
|
||||||
|
#include "Abilities.hpp"
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
@ -25,4 +26,5 @@ namespace Nanos {
|
|||||||
void summonNano(CNSocket* sock, int slot, bool silent = false);
|
void summonNano(CNSocket* sock, int slot, bool silent = false);
|
||||||
int nanoStyle(int nanoID);
|
int nanoStyle(int nanoID);
|
||||||
bool getNanoBoost(Player* plr);
|
bool getNanoBoost(Player* plr);
|
||||||
|
std::vector<ICombatant*> applyNanoBuff(SkillData* skill, Player* plr);
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,10 @@
|
|||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
/* forward declaration(s) */
|
||||||
|
class Buff;
|
||||||
|
struct BuffStack;
|
||||||
|
|
||||||
#define ACTIVE_MISSION_COUNT 6
|
#define ACTIVE_MISSION_COUNT 6
|
||||||
|
|
||||||
#define PC_MAXHEALTH(level) (925 + 75 * (level))
|
#define PC_MAXHEALTH(level) (925 + 75 * (level))
|
||||||
@ -32,9 +36,8 @@ struct Player : public Entity, public ICombatant {
|
|||||||
int8_t iPCState = 0;
|
int8_t iPCState = 0;
|
||||||
int32_t iWarpLocationFlag = 0;
|
int32_t iWarpLocationFlag = 0;
|
||||||
int64_t aSkywayLocationFlag[2] = {};
|
int64_t aSkywayLocationFlag[2] = {};
|
||||||
int32_t iConditionBitFlag = 0;
|
|
||||||
int32_t iSelfConditionBitFlag = 0;
|
|
||||||
int8_t iSpecialState = 0;
|
int8_t iSpecialState = 0;
|
||||||
|
std::unordered_map<int, Buff*> buffs = {};
|
||||||
|
|
||||||
int angle = 0;
|
int angle = 0;
|
||||||
int lastX = 0, lastY = 0, lastZ = 0, lastAngle = 0;
|
int lastX = 0, lastY = 0, lastZ = 0, lastAngle = 0;
|
||||||
@ -86,13 +89,25 @@ struct Player : public Entity, public ICombatant {
|
|||||||
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 bool addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) override;
|
||||||
|
virtual Buff* getBuff(int buffId) override;
|
||||||
|
virtual void removeBuff(int buffId) override;
|
||||||
|
virtual void removeBuff(int buffId, int buffClass) override;
|
||||||
|
virtual bool hasBuff(int buffId) override;
|
||||||
|
virtual int getCompositeCondition() override;
|
||||||
virtual int takeDamage(EntityRef src, int amt) override;
|
virtual int takeDamage(EntityRef src, int amt) override;
|
||||||
virtual void heal(EntityRef src, int amt) override;
|
virtual int heal(EntityRef src, int amt) override;
|
||||||
virtual bool isAlive() override;
|
virtual bool isAlive() override;
|
||||||
virtual int getCurrentHP() override;
|
virtual int getCurrentHP() override;
|
||||||
|
virtual int getMaxHP() override;
|
||||||
|
virtual int getLevel() override;
|
||||||
|
virtual std::vector<EntityRef> getGroupMembers() override;
|
||||||
|
virtual int32_t getCharType() override;
|
||||||
virtual int32_t getID() override;
|
virtual int32_t getID() override;
|
||||||
|
virtual EntityRef getRef() override;
|
||||||
|
|
||||||
virtual void step(time_t currTime) override;
|
virtual void step(time_t currTime) override;
|
||||||
|
|
||||||
|
sNano* getActiveNano();
|
||||||
sPCAppearanceData getAppearanceData();
|
sPCAppearanceData getAppearanceData();
|
||||||
};
|
};
|
||||||
|
@ -36,8 +36,13 @@ void PlayerManager::removePlayer(CNSocket* key) {
|
|||||||
Player* plr = getPlayer(key);
|
Player* plr = getPlayer(key);
|
||||||
uint64_t fromInstance = plr->instanceID;
|
uint64_t fromInstance = plr->instanceID;
|
||||||
|
|
||||||
|
// free buff memory
|
||||||
|
for(auto buffEntry : plr->buffs)
|
||||||
|
delete buffEntry.second;
|
||||||
|
|
||||||
|
// leave group
|
||||||
if(plr->group != nullptr)
|
if(plr->group != nullptr)
|
||||||
Groups::groupKick(plr);
|
Groups::groupKick(plr->group, key);
|
||||||
|
|
||||||
// remove player's bullets
|
// remove player's bullets
|
||||||
Combat::Bullets.erase(plr->iID);
|
Combat::Bullets.erase(plr->iID);
|
||||||
@ -61,16 +66,6 @@ void PlayerManager::removePlayer(CNSocket* key) {
|
|||||||
// if the player was in a lair, clean it up
|
// if the player was in a lair, clean it up
|
||||||
Chunking::destroyInstanceIfEmpty(fromInstance);
|
Chunking::destroyInstanceIfEmpty(fromInstance);
|
||||||
|
|
||||||
// remove player's buffs from the server
|
|
||||||
auto it = Eggs::EggBuffs.begin();
|
|
||||||
while (it != Eggs::EggBuffs.end()) {
|
|
||||||
if (it->first.first == key) {
|
|
||||||
it = Eggs::EggBuffs.erase(it);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
it++;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cout << players.size() << " players" << std::endl;
|
std::cout << players.size() << " players" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -403,7 +398,7 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) {
|
|||||||
|
|
||||||
switch ((ePCRegenType)reviveData->iRegenType) {
|
switch ((ePCRegenType)reviveData->iRegenType) {
|
||||||
case ePCRegenType::HereByPhoenix: // nano revive
|
case ePCRegenType::HereByPhoenix: // nano revive
|
||||||
if (!(plr->iConditionBitFlag & CSB_BIT_PHOENIX))
|
if (!(plr->hasBuff(ECSB_PHOENIX)))
|
||||||
return; // sanity check
|
return; // sanity check
|
||||||
plr->Nanos[plr->activeNano].iStamina = 0;
|
plr->Nanos[plr->activeNano].iStamina = 0;
|
||||||
// TODO ABILITIES
|
// TODO ABILITIES
|
||||||
@ -470,10 +465,8 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) {
|
|||||||
resp2.PCRegenDataForOtherPC.iAngle = plr->angle;
|
resp2.PCRegenDataForOtherPC.iAngle = plr->angle;
|
||||||
|
|
||||||
if (plr->group != nullptr) {
|
if (plr->group != nullptr) {
|
||||||
// TODO ABILITIES
|
|
||||||
//int bitFlag = plr->group->conditionBitFlag;
|
resp2.PCRegenDataForOtherPC.iConditionBitFlag = plr->getCompositeCondition();
|
||||||
//resp2.PCRegenDataForOtherPC.iConditionBitFlag = plr->iConditionBitFlag = plr->iSelfConditionBitFlag | bitFlag;
|
|
||||||
|
|
||||||
resp2.PCRegenDataForOtherPC.iPCState = plr->iPCState;
|
resp2.PCRegenDataForOtherPC.iPCState = plr->iPCState;
|
||||||
resp2.PCRegenDataForOtherPC.iSpecialState = plr->iSpecialState;
|
resp2.PCRegenDataForOtherPC.iSpecialState = plr->iSpecialState;
|
||||||
resp2.PCRegenDataForOtherPC.Nano = plr->Nanos[plr->activeNano];
|
resp2.PCRegenDataForOtherPC.Nano = plr->Nanos[plr->activeNano];
|
||||||
|
@ -93,7 +93,7 @@ inline constexpr bool isOutboundPacketID(uint32_t id) {
|
|||||||
|
|
||||||
// overflow-safe validation of variable-length packets
|
// overflow-safe validation of variable-length packets
|
||||||
// for outbound packets
|
// for outbound packets
|
||||||
inline constexpr bool validOutVarPacket(size_t base, int32_t npayloads, size_t plsize) {
|
inline constexpr bool validOutVarPacket(size_t base, size_t npayloads, size_t plsize) {
|
||||||
// check for multiplication overflow
|
// check for multiplication overflow
|
||||||
if (npayloads > 0 && (CN_PACKET_BUFFER_SIZE - 8) / (size_t)npayloads < plsize)
|
if (npayloads > 0 && (CN_PACKET_BUFFER_SIZE - 8) / (size_t)npayloads < plsize)
|
||||||
return false;
|
return false;
|
||||||
@ -110,7 +110,7 @@ inline constexpr bool validOutVarPacket(size_t base, int32_t npayloads, size_t p
|
|||||||
}
|
}
|
||||||
|
|
||||||
// for inbound packets
|
// for inbound packets
|
||||||
inline constexpr bool validInVarPacket(size_t base, int32_t npayloads, size_t plsize, size_t datasize) {
|
inline constexpr bool validInVarPacket(size_t base, size_t npayloads, size_t plsize, size_t datasize) {
|
||||||
// check for multiplication overflow
|
// check for multiplication overflow
|
||||||
if (npayloads > 0 && (CN_PACKET_BUFFER_SIZE - 8) / (size_t)npayloads < plsize)
|
if (npayloads > 0 && (CN_PACKET_BUFFER_SIZE - 8) / (size_t)npayloads < plsize)
|
||||||
return false;
|
return false;
|
||||||
|
@ -118,6 +118,27 @@ enum {
|
|||||||
ECSTB__END = 26,
|
ECSTB__END = 26,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
ETBU_NONE = 0,
|
||||||
|
ETBU_ADD = 1,
|
||||||
|
ETBU_DEL = 2,
|
||||||
|
ETBU_CHANGE = 3,
|
||||||
|
ETBU__END = 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
ETBT_NONE = 0,
|
||||||
|
ETBT_NANO = 1,
|
||||||
|
ETBT_GROUPNANO = 2,
|
||||||
|
ETBT_SHINY = 3,
|
||||||
|
ETBT_LANDEFFECT = 4,
|
||||||
|
ETBT_ITEM = 5,
|
||||||
|
ETBT_CASHITEM = 6,
|
||||||
|
ETBT__END = 7,
|
||||||
|
ETBT_SKILL = 1,
|
||||||
|
ETBT_GROUPSKILL = 2
|
||||||
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
SUCC = 1,
|
SUCC = 1,
|
||||||
FAIL = 0,
|
FAIL = 0,
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#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
|
||||||
|
|
||||||
class CNShardServer : public CNServer {
|
class CNShardServer : public CNServer {
|
||||||
private:
|
private:
|
||||||
|
Loading…
Reference in New Issue
Block a user