This commit is contained in:
Gent Semaj 2023-07-12 21:43:15 +00:00 committed by GitHub
commit 0b325f6f9f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
66 changed files with 2579 additions and 2373 deletions

2
.gitignore vendored
View File

@ -16,3 +16,5 @@ build/
version.h version.h
infer-out infer-out
gmon.out gmon.out
*.bak

View File

@ -3,7 +3,8 @@ project(OpenFusion)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
execute_process(COMMAND git describe --tags OUTPUT_VARIABLE GIT_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process(COMMAND git describe --tags OUTPUT_VARIABLE GIT_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
# OpenFusion supports multiple packet/struct versions # OpenFusion supports multiple packet/struct versions
# 104 is the default version to build which can be changed # 104 is the default version to build which can be changed

View File

@ -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\

File diff suppressed because it is too large Load Diff

View File

@ -1,64 +1,109 @@
#pragma once #pragma once
#include "core/Core.hpp" #include "core/Core.hpp"
#include "Combat.hpp"
typedef void (*PowerHandler)(CNSocket*, std::vector<int>, int16_t, int16_t, int16_t, int16_t, int16_t, int32_t, int16_t); #include "Entities.hpp"
#include "Player.hpp"
struct NanoPower { #include <map>
int16_t skillType; #include <vector>
int32_t bitFlag;
int16_t timeBuffID;
PowerHandler handler;
NanoPower(int16_t s, int32_t b, int16_t t, PowerHandler h) : skillType(s), bitFlag(b), timeBuffID(t), handler(h) {} constexpr size_t MAX_SKILLRESULT_SIZE = sizeof(sSkillResult_BatteryDrain);
void handle(CNSocket *sock, std::vector<int> targetData, int16_t nanoID, int16_t skillID, int16_t duration, int16_t amount) { enum class SkillType {
if (handler == nullptr) DAMAGE = 1,
return; HEAL_HP = 2,
KNOCKDOWN = 3,
handler(sock, targetData, nanoID, skillID, duration, amount, skillType, bitFlag, timeBuffID); SLEEP = 4,
} SNARE = 5,
HEAL_STAMINA = 6,
STAMINA_SELF = 7,
STUN = 8,
WEAPONSLOW = 9,
JUMP = 10,
RUN = 11,
STEALTH = 12,
SWIM = 13,
MINIMAPENEMY = 14,
MINIMAPTRESURE = 15,
PHOENIX = 16,
PROTECTBATTERY = 17,
PROTECTINFECTION = 18,
REWARDBLOB = 19,
REWARDCASH = 20,
BATTERYDRAIN = 21,
CORRUPTIONATTACK = 22,
INFECTIONDAMAGE = 23,
KNOCKBACK = 24,
FREEDOM = 25,
PHOENIX_GROUP = 26,
RECALL = 27,
RECALL_GROUP = 28,
RETROROCKET_SELF = 29,
BLOODSUCKING = 30,
BOUNDINGBALL = 31,
INVULNERABLE = 32,
NANOSTIMPAK = 33,
RETURNHOMEHEAL = 34,
BUFFHEAL = 35,
EXTRABANK = 36,
CORRUPTIONATTACKWIN = 38,
CORRUPTIONATTACKLOSE = 39,
}; };
typedef void (*MobPowerHandler)(Mob*, std::vector<int>, int16_t, int16_t, int16_t, int16_t, int32_t, int16_t); enum class SkillEffectTarget {
POINT = 1,
SELF = 2,
CONE = 3,
WEAPON = 4,
AREA_SELF = 5,
AREA_TARGET = 6
};
struct MobPower { enum class SkillTargetType {
int16_t skillType; MOBS = 1,
int32_t bitFlag; PLAYERS = 2,
int16_t timeBuffID; GROUP = 3
MobPowerHandler handler; };
MobPower(int16_t s, int32_t b, int16_t t, MobPowerHandler h) : skillType(s), bitFlag(b), timeBuffID(t), handler(h) {} enum class SkillDrainType {
ACTIVE = 1,
PASSIVE = 2
};
void handle(Mob *mob, std::vector<int> targetData, int16_t skillID, int16_t duration, int16_t amount) { struct SkillResult {
if (handler == nullptr) size_t size;
return; uint8_t payload[MAX_SKILLRESULT_SIZE];
SkillResult(size_t len, void* dat) {
handler(mob, targetData, skillID, duration, amount, skillType, bitFlag, timeBuffID); size = len;
memcpy(payload, dat, len);
}
SkillResult() {
size = 0;
} }
}; };
struct SkillData { struct SkillData {
int skillType; SkillType skillType; // eST
int targetType; SkillEffectTarget effectTarget;
int drainType; int effectType; // always 1?
SkillTargetType targetType;
SkillDrainType drainType;
int effectArea; int effectArea;
int batteryUse[4]; int batteryUse[4];
int durationTime[4]; int durationTime[4];
int powerIntensity[4];
int valueTypes[3];
int values[3][4];
}; };
namespace Nanos { namespace Abilities {
extern std::vector<NanoPower> NanoPowers;
extern std::map<int32_t, SkillData> SkillTable; extern std::map<int32_t, SkillData> SkillTable;
void nanoUnbuff(CNSocket* sock, std::vector<int> targetData, int32_t bitFlag, int16_t timeBuffID, int16_t amount, bool groupPower); void useNanoSkill(CNSocket*, SkillData*, sNano&, std::vector<ICombatant*>);
int applyBuff(CNSocket* sock, int skillID, int eTBU, int eTBT, int32_t groupFlags); void useNPCSkill(EntityRef, int skillID, std::vector<ICombatant*>);
std::vector<int> findTargets(Player* plr, int skillID, CNPacketData* data = nullptr); std::vector<ICombatant*> matchTargets(ICombatant*, SkillData*, int, int32_t*);
} int getCSTBFromST(SkillType skillType);
namespace Combat {
extern std::vector<MobPower> MobPowers;
} }

View File

@ -1,15 +1,10 @@
#include "servers/CNShardServer.hpp"
#include "Buddies.hpp" #include "Buddies.hpp"
#include "PlayerManager.hpp"
#include "Buddies.hpp"
#include "db/Database.hpp"
#include "Items.hpp"
#include "db/Database.hpp"
#include <iostream> #include "db/Database.hpp"
#include <chrono> #include "servers/CNShardServer.hpp"
#include <algorithm>
#include <thread> #include "Player.hpp"
#include "PlayerManager.hpp"
using namespace Buddies; using namespace Buddies;

View File

@ -1,7 +1,5 @@
#pragma once #pragma once
#include "Player.hpp"
#include "core/Core.hpp"
#include "core/Core.hpp" #include "core/Core.hpp"
namespace Buddies { namespace Buddies {

151
src/Buffs.cpp Normal file
View 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
View 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);
}

View File

@ -1,10 +1,13 @@
#include "BuiltinCommands.hpp" #include "BuiltinCommands.hpp"
#include "servers/CNShardServer.hpp"
#include "Player.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "Chat.hpp"
#include "Items.hpp" #include "Items.hpp"
#include "Missions.hpp" #include "Missions.hpp"
#include "Chat.hpp"
#include "Nanos.hpp" #include "Nanos.hpp"
#include "Rand.hpp"
// helper function, not a packet handler // helper function, not a packet handler
void BuiltinCommands::setSpecialState(CNSocket* sock, CNPacketData* data) { void BuiltinCommands::setSpecialState(CNSocket* sock, CNPacketData* data) {
@ -247,17 +250,17 @@ static void teleportPlayer(CNSocket *sock, CNPacketData *data) {
uint64_t instance = plr->instanceID; uint64_t instance = plr->instanceID;
const int unstickRange = 400; const int unstickRange = 400;
switch (req->eTeleportType) { switch ((eCN_GM_TeleportType)req->eTeleportType) {
case eCN_GM_TeleportMapType__MyLocation: case eCN_GM_TeleportType::MyLocation:
PlayerManager::sendPlayerTo(targetSock, plr->x, plr->y, plr->z, instance); PlayerManager::sendPlayerTo(targetSock, plr->x, plr->y, plr->z, instance);
break; break;
case eCN_GM_TeleportMapType__MapXYZ: case eCN_GM_TeleportType::MapXYZ:
instance = req->iToMap; instance = req->iToMap;
// fallthrough // fallthrough
case eCN_GM_TeleportMapType__XYZ: case eCN_GM_TeleportType::XYZ:
PlayerManager::sendPlayerTo(targetSock, req->iToX, req->iToY, req->iToZ, instance); PlayerManager::sendPlayerTo(targetSock, req->iToX, req->iToY, req->iToZ, instance);
break; break;
case eCN_GM_TeleportMapType__SomeoneLocation: case eCN_GM_TeleportType::SomeoneLocation:
// player to teleport to // player to teleport to
goalSock = PlayerManager::getSockFromAny(req->eGoalPCSearchBy, req->iGoalPC_ID, req->iGoalPC_UID, goalSock = PlayerManager::getSockFromAny(req->eGoalPCSearchBy, req->iGoalPC_ID, req->iGoalPC_UID,
AUTOU16TOU8(req->szGoalPC_FirstName), AUTOU16TOU8(req->szGoalPC_LastName)); AUTOU16TOU8(req->szGoalPC_FirstName), AUTOU16TOU8(req->szGoalPC_LastName));
@ -269,7 +272,7 @@ static void teleportPlayer(CNSocket *sock, CNPacketData *data) {
PlayerManager::sendPlayerTo(targetSock, goalPlr->x, goalPlr->y, goalPlr->z, goalPlr->instanceID); PlayerManager::sendPlayerTo(targetSock, goalPlr->x, goalPlr->y, goalPlr->z, goalPlr->instanceID);
break; break;
case eCN_GM_TeleportMapType__Unstick: case eCN_GM_TeleportType::Unstick:
targetPlr = PlayerManager::getPlayer(targetSock); targetPlr = PlayerManager::getPlayer(targetSock);
PlayerManager::sendPlayerTo(targetSock, targetPlr->x - unstickRange/2 + Rand::rand(unstickRange), PlayerManager::sendPlayerTo(targetSock, targetPlr->x - unstickRange/2 + Rand::rand(unstickRange),

View File

@ -1,6 +1,9 @@
#include "Chat.hpp" #include "Chat.hpp"
#include "servers/CNShardServer.hpp"
#include "Player.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "Groups.hpp"
#include "CustomCommands.hpp" #include "CustomCommands.hpp"
#include <assert.h> #include <assert.h>
@ -225,10 +228,6 @@ static void tradeChatHandler(CNSocket* sock, CNPacketData* data) {
static void groupChatHandler(CNSocket* sock, CNPacketData* data) { static void groupChatHandler(CNSocket* sock, CNPacketData* data) {
sP_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE* chat = (sP_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE*)data->buf; sP_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE* chat = (sP_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE*)data->buf;
Player* plr = PlayerManager::getPlayer(sock); Player* plr = PlayerManager::getPlayer(sock);
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
if (otherPlr == nullptr)
return;
std::string fullChat = sanitizeText(AUTOU16TOU8(chat->szFreeChat)); std::string fullChat = sanitizeText(AUTOU16TOU8(chat->szFreeChat));
@ -251,16 +250,15 @@ static void groupChatHandler(CNSocket* sock, CNPacketData* data) {
resp.iSendPCID = plr->iID; resp.iSendPCID = plr->iID;
resp.iEmoteCode = chat->iEmoteCode; resp.iEmoteCode = chat->iEmoteCode;
Groups::sendToGroup(otherPlr, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC)); if (plr->group == nullptr)
sock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC));
else
Groups::sendToGroup(plr->group, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC));
} }
static void groupMenuChatHandler(CNSocket* sock, CNPacketData* data) { static void groupMenuChatHandler(CNSocket* sock, CNPacketData* data) {
sP_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE* chat = (sP_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE*)data->buf; sP_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE* chat = (sP_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE*)data->buf;
Player* plr = PlayerManager::getPlayer(sock); Player* plr = PlayerManager::getPlayer(sock);
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
if (otherPlr == nullptr)
return;
std::string fullChat = sanitizeText(AUTOU16TOU8(chat->szFreeChat)); std::string fullChat = sanitizeText(AUTOU16TOU8(chat->szFreeChat));
std::string logLine = "[GroupMenuChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat; std::string logLine = "[GroupMenuChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat;
@ -275,7 +273,9 @@ static void groupMenuChatHandler(CNSocket* sock, CNPacketData* data) {
resp.iSendPCID = plr->iID; resp.iSendPCID = plr->iID;
resp.iEmoteCode = chat->iEmoteCode; resp.iEmoteCode = chat->iEmoteCode;
Groups::sendToGroup(otherPlr, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC)); if (plr->group == nullptr)
sock->sendPacket((void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC));
Groups::sendToGroup(plr->group, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC));
} }
// we only allow plain ascii, at least for now // we only allow plain ascii, at least for now

View File

@ -2,7 +2,10 @@
#define CMD_PREFIX '/' #define CMD_PREFIX '/'
#include "servers/CNShardServer.hpp" #include "core/Core.hpp"
#include <string>
#include <vector>
namespace Chat { namespace Chat {
extern std::vector<std::string> dump; extern std::vector<std::string> dump;

View File

@ -1,9 +1,9 @@
#include "Chunking.hpp" #include "Chunking.hpp"
#include "PlayerManager.hpp"
#include "MobAI.hpp"
#include "NPCManager.hpp" #include "NPCManager.hpp"
#include "settings.hpp"
#include "Combat.hpp" #include <assert.h>
#include "Eggs.hpp"
using namespace Chunking; using namespace Chunking;
@ -26,7 +26,7 @@ static void newChunk(ChunkPos pos) {
// add the chunk to the cache of all players and NPCs in the surrounding chunks // add the chunk to the cache of all players and NPCs in the surrounding chunks
std::set<Chunk*> surroundings = getViewableChunks(pos); std::set<Chunk*> surroundings = getViewableChunks(pos);
for (Chunk* c : surroundings) for (Chunk* c : surroundings)
for (const EntityRef& ref : c->entities) for (const EntityRef ref : c->entities)
ref.getEntity()->viewableChunks.insert(chunk); ref.getEntity()->viewableChunks.insert(chunk);
} }
@ -41,24 +41,24 @@ static void deleteChunk(ChunkPos pos) {
// remove the chunk from the cache of all players and NPCs in the surrounding chunks // remove the chunk from the cache of all players and NPCs in the surrounding chunks
std::set<Chunk*> surroundings = getViewableChunks(pos); std::set<Chunk*> surroundings = getViewableChunks(pos);
for(Chunk* c : surroundings) for(Chunk* c : surroundings)
for (const EntityRef& ref : c->entities) for (const EntityRef ref : c->entities)
ref.getEntity()->viewableChunks.erase(chunk); ref.getEntity()->viewableChunks.erase(chunk);
chunks.erase(pos); // remove from map chunks.erase(pos); // remove from map
delete chunk; // free from memory delete chunk; // free from memory
} }
void Chunking::trackEntity(ChunkPos chunkPos, const EntityRef& ref) { void Chunking::trackEntity(ChunkPos chunkPos, const EntityRef ref) {
if (!chunkExists(chunkPos)) if (!chunkExists(chunkPos))
return; // shouldn't happen return; // shouldn't happen
chunks[chunkPos]->entities.insert(ref); chunks[chunkPos]->entities.insert(ref);
if (ref.type == EntityType::PLAYER) if (ref.kind == EntityKind::PLAYER)
chunks[chunkPos]->nplayers++; chunks[chunkPos]->nplayers++;
} }
void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef& ref) { void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef ref) {
if (!chunkExists(chunkPos)) if (!chunkExists(chunkPos))
return; // do nothing if chunk doesn't even exist return; // do nothing if chunk doesn't even exist
@ -66,7 +66,7 @@ void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef& ref) {
chunk->entities.erase(ref); // gone chunk->entities.erase(ref); // gone
if (ref.type == EntityType::PLAYER) if (ref.kind == EntityKind::PLAYER)
chunks[chunkPos]->nplayers--; chunks[chunkPos]->nplayers--;
assert(chunks[chunkPos]->nplayers >= 0); assert(chunks[chunkPos]->nplayers >= 0);
@ -75,13 +75,13 @@ void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef& ref) {
deleteChunk(chunkPos); deleteChunk(chunkPos);
} }
void Chunking::addEntityToChunks(std::set<Chunk*> chnks, const EntityRef& ref) { void Chunking::addEntityToChunks(std::set<Chunk*> chnks, const EntityRef ref) {
Entity *ent = ref.getEntity(); Entity *ent = ref.getEntity();
bool alive = ent->isAlive(); bool alive = ent->isExtant();
// TODO: maybe optimize this, potentially using AROUND packets? // TODO: maybe optimize this, potentially using AROUND packets?
for (Chunk *chunk : chnks) { for (Chunk *chunk : chnks) {
for (const EntityRef& otherRef : chunk->entities) { for (const EntityRef otherRef : chunk->entities) {
// skip oneself // skip oneself
if (ref == otherRef) if (ref == otherRef)
continue; continue;
@ -89,31 +89,31 @@ void Chunking::addEntityToChunks(std::set<Chunk*> chnks, const EntityRef& ref) {
Entity *other = otherRef.getEntity(); Entity *other = otherRef.getEntity();
// notify all visible players of the existence of this Entity // notify all visible players of the existence of this Entity
if (alive && otherRef.type == EntityType::PLAYER) { if (alive && otherRef.kind == EntityKind::PLAYER) {
ent->enterIntoViewOf(otherRef.sock); ent->enterIntoViewOf(otherRef.sock);
} }
// notify this *player* of the existence of all visible Entities // notify this *player* of the existence of all visible Entities
if (ref.type == EntityType::PLAYER && other->isAlive()) { if (ref.kind == EntityKind::PLAYER && other->isExtant()) {
other->enterIntoViewOf(ref.sock); other->enterIntoViewOf(ref.sock);
} }
// for mobs, increment playersInView // for mobs, increment playersInView
if (ref.type == EntityType::MOB && otherRef.type == EntityType::PLAYER) if (ref.kind == EntityKind::MOB && otherRef.kind == EntityKind::PLAYER)
((Mob*)ent)->playersInView++; ((Mob*)ent)->playersInView++;
if (otherRef.type == EntityType::MOB && ref.type == EntityType::PLAYER) if (otherRef.kind == EntityKind::MOB && ref.kind == EntityKind::PLAYER)
((Mob*)other)->playersInView++; ((Mob*)other)->playersInView++;
} }
} }
} }
void Chunking::removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef& ref) { void Chunking::removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef ref) {
Entity *ent = ref.getEntity(); Entity *ent = ref.getEntity();
bool alive = ent->isAlive(); bool alive = ent->isExtant();
// TODO: same as above // TODO: same as above
for (Chunk *chunk : chnks) { for (Chunk *chunk : chnks) {
for (const EntityRef& otherRef : chunk->entities) { for (const EntityRef otherRef : chunk->entities) {
// skip oneself // skip oneself
if (ref == otherRef) if (ref == otherRef)
continue; continue;
@ -121,19 +121,19 @@ void Chunking::removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef& r
Entity *other = otherRef.getEntity(); Entity *other = otherRef.getEntity();
// notify all visible players of the departure of this Entity // notify all visible players of the departure of this Entity
if (alive && otherRef.type == EntityType::PLAYER) { if (alive && otherRef.kind == EntityKind::PLAYER) {
ent->disappearFromViewOf(otherRef.sock); ent->disappearFromViewOf(otherRef.sock);
} }
// notify this *player* of the departure of all visible Entities // notify this *player* of the departure of all visible Entities
if (ref.type == EntityType::PLAYER && other->isAlive()) { if (ref.kind == EntityKind::PLAYER && other->isExtant()) {
other->disappearFromViewOf(ref.sock); other->disappearFromViewOf(ref.sock);
} }
// for mobs, decrement playersInView // for mobs, decrement playersInView
if (ref.type == EntityType::MOB && otherRef.type == EntityType::PLAYER) if (ref.kind == EntityKind::MOB && otherRef.kind == EntityKind::PLAYER)
((Mob*)ent)->playersInView--; ((Mob*)ent)->playersInView--;
if (otherRef.type == EntityType::MOB && ref.type == EntityType::PLAYER) if (otherRef.kind == EntityKind::MOB && ref.kind == EntityKind::PLAYER)
((Mob*)other)->playersInView--; ((Mob*)other)->playersInView--;
} }
} }
@ -154,8 +154,8 @@ static void emptyChunk(ChunkPos chunkPos) {
// unspawn all of the mobs/npcs // unspawn all of the mobs/npcs
std::set refs(chunk->entities); std::set refs(chunk->entities);
for (const EntityRef& ref : refs) { for (const EntityRef ref : refs) {
if (ref.type == EntityType::PLAYER) if (ref.kind == EntityKind::PLAYER)
assert(0); assert(0);
// every call of this will check if the chunk is empty and delete it if so // every call of this will check if the chunk is empty and delete it if so
@ -163,7 +163,7 @@ static void emptyChunk(ChunkPos chunkPos) {
} }
} }
void Chunking::updateEntityChunk(const EntityRef& ref, ChunkPos from, ChunkPos to) { void Chunking::updateEntityChunk(const EntityRef ref, ChunkPos from, ChunkPos to) {
Entity* ent = ref.getEntity(); Entity* ent = ref.getEntity();
// move to other chunk's player set // move to other chunk's player set
@ -267,54 +267,53 @@ void Chunking::createInstance(uint64_t instanceID) {
std::cout << "Creating instance " << instanceID << std::endl; std::cout << "Creating instance " << instanceID << std::endl;
for (ChunkPos &coords : templateChunks) { for (ChunkPos &coords : templateChunks) {
for (const EntityRef& ref : chunks[coords]->entities) { for (const EntityRef ref : chunks[coords]->entities) {
if (ref.type == EntityType::PLAYER) if (ref.kind == EntityKind::PLAYER)
continue; continue;
int npcID = ref.id; int npcID = ref.id;
BaseNPC* baseNPC = (BaseNPC*)ref.getEntity(); BaseNPC* baseNPC = (BaseNPC*)ref.getEntity();
// make a copy of each NPC in the template chunks and put them in the new instance // make a copy of each NPC in the template chunks and put them in the new instance
if (baseNPC->type == EntityType::MOB) { if (baseNPC->kind == EntityKind::MOB) {
if (((Mob*)baseNPC)->groupLeader != 0 && ((Mob*)baseNPC)->groupLeader != npcID) if (((Mob*)baseNPC)->groupLeader != 0 && ((Mob*)baseNPC)->groupLeader != npcID)
continue; // follower; don't copy individually continue; // follower; don't copy individually
Mob* newMob = new Mob(baseNPC->x, baseNPC->y, baseNPC->z, baseNPC->appearanceData.iAngle, Mob* newMob = new Mob(baseNPC->x, baseNPC->y, baseNPC->z, baseNPC->angle,
instanceID, baseNPC->appearanceData.iNPCType, NPCManager::NPCData[baseNPC->appearanceData.iNPCType], NPCManager::nextId--); instanceID, baseNPC->type, NPCManager::NPCData[baseNPC->type], NPCManager::nextId--);
NPCManager::NPCs[newMob->appearanceData.iNPC_ID] = newMob; NPCManager::NPCs[newMob->id] = newMob;
// if in a group, copy over group members as well // if in a group, copy over group members as well
if (((Mob*)baseNPC)->groupLeader != 0) { if (((Mob*)baseNPC)->groupLeader != 0) {
newMob->groupLeader = newMob->appearanceData.iNPC_ID; // set leader ID for new leader newMob->groupLeader = newMob->id; // set leader ID for new leader
Mob* mobData = (Mob*)baseNPC; Mob* mobData = (Mob*)baseNPC;
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
if (mobData->groupMember[i] != 0) { if (mobData->groupMember[i] != 0) {
int followerID = NPCManager::nextId--; // id for follower int followerID = NPCManager::nextId--; // id for follower
BaseNPC* baseFollower = NPCManager::NPCs[mobData->groupMember[i]]; // follower from template BaseNPC* baseFollower = NPCManager::NPCs[mobData->groupMember[i]]; // follower from template
// new follower instance // new follower instance
Mob* newMobFollower = new Mob(baseFollower->x, baseFollower->y, baseFollower->z, baseFollower->appearanceData.iAngle, Mob* newMobFollower = new Mob(baseFollower->x, baseFollower->y, baseFollower->z, baseFollower->angle,
instanceID, baseFollower->appearanceData.iNPCType, NPCManager::NPCData[baseFollower->appearanceData.iNPCType], followerID); instanceID, baseFollower->type, NPCManager::NPCData[baseFollower->type], followerID);
// add follower to NPC maps // add follower to NPC maps
NPCManager::NPCs[followerID] = newMobFollower; NPCManager::NPCs[followerID] = newMobFollower;
// set follower-specific properties // set follower-specific properties
newMobFollower->groupLeader = newMob->appearanceData.iNPC_ID; newMobFollower->groupLeader = newMob->id;
newMobFollower->offsetX = ((Mob*)baseFollower)->offsetX; newMobFollower->offsetX = ((Mob*)baseFollower)->offsetX;
newMobFollower->offsetY = ((Mob*)baseFollower)->offsetY; newMobFollower->offsetY = ((Mob*)baseFollower)->offsetY;
// add follower copy to leader copy // add follower copy to leader copy
newMob->groupMember[i] = followerID; newMob->groupMember[i] = followerID;
NPCManager::updateNPCPosition(followerID, baseFollower->x, baseFollower->y, baseFollower->z, NPCManager::updateNPCPosition(followerID, baseFollower->x, baseFollower->y, baseFollower->z,
instanceID, baseFollower->appearanceData.iAngle); instanceID, baseFollower->angle);
} }
} }
} }
NPCManager::updateNPCPosition(newMob->appearanceData.iNPC_ID, baseNPC->x, baseNPC->y, baseNPC->z, NPCManager::updateNPCPosition(newMob->id, baseNPC->x, baseNPC->y, baseNPC->z,
instanceID, baseNPC->appearanceData.iAngle); instanceID, baseNPC->angle);
} else { } else {
BaseNPC* newNPC = new BaseNPC(baseNPC->x, baseNPC->y, baseNPC->z, baseNPC->appearanceData.iAngle, BaseNPC* newNPC = new BaseNPC(baseNPC->angle, instanceID, baseNPC->type, NPCManager::nextId--);
instanceID, baseNPC->appearanceData.iNPCType, NPCManager::nextId--); NPCManager::NPCs[newNPC->id] = newNPC;
NPCManager::NPCs[newNPC->appearanceData.iNPC_ID] = newNPC; NPCManager::updateNPCPosition(newNPC->id, baseNPC->x, baseNPC->y, baseNPC->z,
NPCManager::updateNPCPosition(newNPC->appearanceData.iNPC_ID, baseNPC->x, baseNPC->y, baseNPC->z, instanceID, baseNPC->angle);
instanceID, baseNPC->appearanceData.iAngle);
} }
} }
} }

View File

@ -1,14 +1,10 @@
#pragma once #pragma once
#include "core/Core.hpp" #include "EntityRef.hpp"
#include <utility>
#include <set> #include <set>
#include <map> #include <map>
#include <tuple> #include <vector>
#include <algorithm>
struct EntityRef;
class Chunk { class Chunk {
public: public:
@ -36,13 +32,13 @@ namespace Chunking {
extern const ChunkPos INVALID_CHUNK; extern const ChunkPos INVALID_CHUNK;
void updateEntityChunk(const EntityRef& ref, ChunkPos from, ChunkPos to); void updateEntityChunk(const EntityRef ref, ChunkPos from, ChunkPos to);
void trackEntity(ChunkPos chunkPos, const EntityRef& ref); void trackEntity(ChunkPos chunkPos, const EntityRef ref);
void untrackEntity(ChunkPos chunkPos, const EntityRef& ref); void untrackEntity(ChunkPos chunkPos, const EntityRef ref);
void addEntityToChunks(std::set<Chunk*> chnks, const EntityRef& ref); void addEntityToChunks(std::set<Chunk*> chnks, const EntityRef ref);
void removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef& ref); void removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef ref);
bool chunkExists(ChunkPos chunk); bool chunkExists(ChunkPos chunk);
ChunkPos chunkPosAt(int posX, int posY, uint64_t instanceID); ChunkPos chunkPosAt(int posX, int posY, uint64_t instanceID);

View File

@ -1,22 +1,270 @@
#include "Combat.hpp" #include "Combat.hpp"
#include "PlayerManager.hpp"
#include "Nanos.hpp" #include "servers/CNShardServer.hpp"
#include "NPCManager.hpp"
#include "Items.hpp"
#include "Missions.hpp"
#include "Groups.hpp"
#include "Transport.hpp"
#include "Racing.hpp"
#include "Abilities.hpp"
#include "Rand.hpp" #include "Rand.hpp"
#include "Player.hpp"
#include "PlayerManager.hpp"
#include "NPCManager.hpp"
#include "Nanos.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;
#pragma region Player
bool Player::addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) {
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;
}
Buff* Player::getBuff(int buffId) {
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() {
return HP > 0;
}
int Player::getCurrentHP() {
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() {
return iID;
}
EntityRef Player::getRef() {
return EntityRef(PlayerManager::getSockFromID(iID));
}
void Player::step(time_t currTime) {
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 dmg = amt;
if(hp - dmg < 0) dmg = hp;
hp -= dmg;
if(hp <= 0) transition(AIState::DEAD, src);
return dmg;
}
int CombatNPC::heal(EntityRef src, int amt) {
int heal = amt;
if(hp + heal > getMaxHP()) heal = getMaxHP() - hp;
hp += heal;
return heal;
}
bool CombatNPC::isAlive() {
return hp > 0;
}
int CombatNPC::getCurrentHP() {
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() {
return id;
}
EntityRef CombatNPC::getRef() {
return EntityRef(id);
}
void CombatNPC::step(time_t currTime) {
if(stateHandlers.find(state) != stateHandlers.end())
stateHandlers[state](this, currTime);
else {
std::cout << "[WARN] State " << (int)state << " has no handler; going inactive" << std::endl;
transition(AIState::INACTIVE, id);
}
}
void CombatNPC::transition(AIState newState, EntityRef src) {
state = newState;
if (transitionHandlers.find(newState) != transitionHandlers.end())
transitionHandlers[newState](this, src);
else {
std::cout << "[WARN] Transition to " << (int)state << " has no handler; going inactive" << std::endl;
transition(AIState::INACTIVE, id);
}
/* TODO: fire any triggered events
for (NPCEvent& event : NPCManager::NPCEvents)
if (event.trigger == ON_KILLED && event.npcType == type)
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,
int defenderStyle, int difficulty) { int defenderStyle, int difficulty) {
@ -114,7 +362,7 @@ static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) {
BaseNPC* npc = NPCManager::NPCs[targets[i]]; BaseNPC* npc = NPCManager::NPCs[targets[i]];
if (npc->type != EntityType::MOB) { if (npc->kind != EntityKind::MOB) {
std::cout << "[WARN] pcAttackNpcs: NPC is not a mob" << std::endl; std::cout << "[WARN] pcAttackNpcs: NPC is not a mob" << std::endl;
return; return;
} }
@ -137,11 +385,11 @@ static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) {
else else
plr->batteryW = 0; plr->batteryW = 0;
damage.first = hitMob(sock, mob, damage.first); damage.first = mob->takeDamage(sock, damage.first);
respdata[i].iID = mob->appearanceData.iNPC_ID; respdata[i].iID = mob->id;
respdata[i].iDamage = damage.first; respdata[i].iDamage = damage.first;
respdata[i].iHP = mob->appearanceData.iHP; respdata[i].iHP = mob->hp;
respdata[i].iHitFlag = damage.second; // hitscan, not a rocket or a grenade respdata[i].iHitFlag = damage.second; // hitscan, not a rocket or a grenade
} }
@ -168,7 +416,7 @@ void Combat::npcAttackPc(Mob *mob, time_t currTime) {
if (!(plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE)) if (!(plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE))
plr->HP -= damage.first; plr->HP -= damage.first;
pkt->iNPC_ID = mob->appearanceData.iNPC_ID; pkt->iNPC_ID = mob->id;
pkt->iPCCnt = 1; pkt->iPCCnt = 1;
atk->iID = plr->iID; atk->iID = plr->iID;
@ -180,52 +428,11 @@ void Combat::npcAttackPc(Mob *mob, time_t currTime) {
PlayerManager::sendToViewable(mob->target, respbuf, P_FE2CL_NPC_ATTACK_PCs); PlayerManager::sendToViewable(mob->target, respbuf, P_FE2CL_NPC_ATTACK_PCs);
if (plr->HP <= 0) { if (plr->HP <= 0) {
mob->target = nullptr; if (!MobAI::aggroCheck(mob, getTime()))
mob->state = MobState::RETREAT; mob->transition(AIState::RETREAT, mob->target);
if (!MobAI::aggroCheck(mob, currTime)) {
MobAI::clearDebuff(mob);
if (mob->groupLeader != 0)
MobAI::groupRetreat(mob);
}
} }
} }
int Combat::hitMob(CNSocket *sock, Mob *mob, int damage) {
// cannot kill mobs multiple times; cannot harm retreating mobs
if (mob->state != MobState::ROAMING && mob->state != MobState::COMBAT) {
return 0; // no damage
}
if (mob->skillStyle >= 0)
return 0; // don't hurt a mob casting corruption
if (mob->state == MobState::ROAMING) {
assert(mob->target == nullptr);
MobAI::enterCombat(sock, mob);
if (mob->groupLeader != 0)
MobAI::followToCombat(mob);
}
mob->appearanceData.iHP -= damage;
// wake up sleeping monster
if (mob->appearanceData.iConditionBitFlag & CSB_BIT_MEZ) {
mob->appearanceData.iConditionBitFlag &= ~CSB_BIT_MEZ;
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1);
pkt1.eCT = 2;
pkt1.iID = mob->appearanceData.iNPC_ID;
pkt1.iConditionBitFlag = mob->appearanceData.iConditionBitFlag;
NPCManager::sendToViewable(mob, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
}
if (mob->appearanceData.iHP <= 0)
killMob(mob->target, mob);
return damage;
}
/* /*
* When a group of players is doing missions together, we want them to all get * When a group of players is doing missions together, we want them to all get
* quest items at the same time, but we don't want the odds of quest item * quest items at the same time, but we don't want the odds of quest item
@ -233,96 +440,16 @@ int Combat::hitMob(CNSocket *sock, Mob *mob, int damage) {
* single RNG roll per mission task, and every group member shares that same * single RNG roll per mission task, and every group member shares that same
* set of rolls. * set of rolls.
*/ */
static void genQItemRolls(Player *leader, std::map<int, int>& rolls) { void Combat::genQItemRolls(std::vector<Player*> players, std::map<int, int>& rolls) {
for (int i = 0; i < leader->groupCnt; i++) { for (int i = 0; i < players.size(); i++) {
if (leader->groupIDs[i] == 0)
continue;
CNSocket *otherSock = PlayerManager::getSockFromID(leader->groupIDs[i]);
if (otherSock == nullptr)
continue;
Player *member = PlayerManager::getPlayer(otherSock);
Player* member = players[i];
for (int j = 0; j < ACTIVE_MISSION_COUNT; j++) for (int j = 0; j < ACTIVE_MISSION_COUNT; j++)
if (member->tasks[j] != 0) if (member->tasks[j] != 0)
rolls[member->tasks[j]] = Rand::rand(); rolls[member->tasks[j]] = Rand::rand();
} }
} }
void Combat::killMob(CNSocket *sock, Mob *mob) {
mob->state = MobState::DEAD;
mob->target = nullptr;
mob->appearanceData.iConditionBitFlag = 0;
mob->skillStyle = -1;
mob->unbuffTimes.clear();
mob->killedTime = getTime(); // XXX: maybe introduce a shard-global time for each step?
// check for the edge case where hitting the mob did not aggro it
if (sock != nullptr) {
Player* plr = PlayerManager::getPlayer(sock);
Items::DropRoll rolled;
Items::DropRoll eventRolled;
std::map<int, int> qitemRolls;
Player *leader = PlayerManager::getPlayerFromID(plr->iIDGroup);
assert(leader != nullptr); // should never happen
genQItemRolls(leader, qitemRolls);
if (plr->groupCnt == 1 && plr->iIDGroup == plr->iID) {
Items::giveMobDrop(sock, mob, rolled, eventRolled);
Missions::mobKilled(sock, mob->appearanceData.iNPCType, qitemRolls);
} else {
for (int i = 0; i < leader->groupCnt; i++) {
CNSocket* sockTo = PlayerManager::getSockFromID(leader->groupIDs[i]);
if (sockTo == nullptr)
continue;
Player *otherPlr = PlayerManager::getPlayer(sockTo);
// only contribute to group members' kills if they're close enough
int dist = std::hypot(plr->x - otherPlr->x + 1, plr->y - otherPlr->y + 1);
if (dist > 5000)
continue;
Items::giveMobDrop(sockTo, mob, rolled, eventRolled);
Missions::mobKilled(sockTo, mob->appearanceData.iNPCType, qitemRolls);
}
}
}
// delay the despawn animation
mob->despawned = false;
// fire any triggered events
for (NPCEvent& event : NPCManager::NPCEvents)
if (event.trigger == ON_KILLED && event.npcType == mob->appearanceData.iNPCType)
event.handler(sock, mob);
auto it = Transport::NPCQueues.find(mob->appearanceData.iNPC_ID);
if (it == Transport::NPCQueues.end() || it->second.empty())
return;
// rewind or empty the movement queue
if (mob->staticPath) {
/*
* This is inelegant, but we wind forward in the path until we find the point that
* corresponds with the Mob's spawn point.
*
* IMPORTANT: The check in TableData::loadPaths() must pass or else this will loop forever.
*/
auto& queue = it->second;
for (auto point = queue.front(); point.x != mob->spawnX || point.y != mob->spawnY; point = queue.front()) {
queue.pop();
queue.push(point);
}
} else {
Transport::NPCQueues.erase(mob->appearanceData.iNPC_ID);
}
}
static void combatBegin(CNSocket *sock, CNPacketData *data) { static void combatBegin(CNSocket *sock, CNPacketData *data) {
Player *plr = PlayerManager::getPlayer(sock); Player *plr = PlayerManager::getPlayer(sock);
@ -345,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;
@ -402,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);
@ -417,12 +558,12 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) {
return; return;
// Unlike the attack mob packet, attacking players packet has an 8-byte trail (Instead of 4 bytes). // Unlike the attack mob packet, attacking players packet has an 8-byte trail (Instead of 4 bytes).
if (!validInVarPacket(sizeof(sP_CL2FE_REQ_PC_ATTACK_CHARs), pkt->iTargetCnt, sizeof(int32_t) * 2, data->size)) { if (!validInVarPacket(sizeof(sP_CL2FE_REQ_PC_ATTACK_CHARs), pkt->iTargetCnt, sizeof(sGM_PVPTarget), data->size)) {
std::cout << "[WARN] bad sP_CL2FE_REQ_PC_ATTACK_CHARs packet size\n"; std::cout << "[WARN] bad sP_CL2FE_REQ_PC_ATTACK_CHARs packet size\n";
return; return;
} }
int32_t *pktdata = (int32_t*)((uint8_t*)data->buf + sizeof(sP_CL2FE_REQ_PC_ATTACK_CHARs)); sGM_PVPTarget* pktdata = (sGM_PVPTarget*)((uint8_t*)data->buf + sizeof(sP_CL2FE_REQ_PC_ATTACK_CHARs));
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_ATTACK_CHARs_SUCC), pkt->iTargetCnt, sizeof(sAttackResult))) { if (!validOutVarPacket(sizeof(sP_FE2CL_PC_ATTACK_CHARs_SUCC), pkt->iTargetCnt, sizeof(sAttackResult))) {
std::cout << "[WARN] bad sP_FE2CL_PC_ATTACK_CHARs_SUCC packet size\n"; std::cout << "[WARN] bad sP_FE2CL_PC_ATTACK_CHARs_SUCC packet size\n";
@ -441,11 +582,19 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) {
resp->iTargetCnt = pkt->iTargetCnt; resp->iTargetCnt = pkt->iTargetCnt;
for (int i = 0; i < pkt->iTargetCnt; i++) { for (int i = 0; i < pkt->iTargetCnt; i++) {
if (pktdata[i*2+1] == 1) { // eCT == 1; attack player
Player *target = nullptr; ICombatant* target = nullptr;
std::pair<int, int> damage;
if (pkt->iTargetCnt > 1)
damage.first = plr->groupDamage;
else
damage.first = plr->pointDamage;
if (pktdata[i].eCT == 1) { // eCT == 1; attack player
for (auto& pair : PlayerManager::players) { for (auto& pair : PlayerManager::players) {
if (pair.second->iID == pktdata[i*2]) { if (pair.second->iID == pktdata[i].iID) {
target = pair.second; target = pair.second;
break; break;
} }
@ -457,67 +606,41 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) {
return; return;
} }
std::pair<int,int> damage; damage = getDamage(damage.first, ((Player*)target)->defense, true, (plr->batteryW > 6 + plr->level), -1, -1, 0);
if (pkt->iTargetCnt > 1)
damage.first = plr->groupDamage;
else
damage.first = plr->pointDamage;
damage = getDamage(damage.first, target->defense, true, (plr->batteryW > 6 + plr->level), -1, -1, 0);
if (plr->batteryW >= 6 + plr->level)
plr->batteryW -= 6 + plr->level;
else
plr->batteryW = 0;
target->HP -= damage.first;
respdata[i].eCT = pktdata[i*2+1];
respdata[i].iID = target->iID;
respdata[i].iDamage = damage.first;
respdata[i].iHP = target->HP;
respdata[i].iHitFlag = damage.second; // hitscan, not a rocket or a grenade
} else { // eCT == 4; attack mob } else { // eCT == 4; attack mob
if (NPCManager::NPCs.find(pktdata[i*2]) == NPCManager::NPCs.end()) {
if (NPCManager::NPCs.find(pktdata[i].iID) == NPCManager::NPCs.end()) {
// not sure how to best handle this // not sure how to best handle this
std::cout << "[WARN] pcAttackChars: NPC ID not found" << std::endl; std::cout << "[WARN] pcAttackChars: NPC ID not found" << std::endl;
return; return;
} }
BaseNPC* npc = NPCManager::NPCs[pktdata[i * 2]]; BaseNPC* npc = NPCManager::NPCs[pktdata[i].iID];
if (npc->type != EntityType::MOB) { if (npc->kind != EntityKind::MOB) {
std::cout << "[WARN] pcAttackChars: NPC is not a mob" << std::endl; std::cout << "[WARN] pcAttackChars: NPC is not a mob" << std::endl;
return; return;
} }
Mob* mob = (Mob*)npc; Mob* mob = (Mob*)npc;
target = mob;
std::pair<int,int> damage;
if (pkt->iTargetCnt > 1)
damage.first = plr->groupDamage;
else
damage.first = plr->pointDamage;
int difficulty = (int)mob->data["m_iNpcLevel"]; int difficulty = (int)mob->data["m_iNpcLevel"];
damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, (plr->batteryW > 6 + difficulty), damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, (plr->batteryW > 6 + difficulty),
Nanos::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty); Nanos::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty);
if (plr->batteryW >= 6 + difficulty)
plr->batteryW -= 6 + difficulty;
else
plr->batteryW = 0;
damage.first = hitMob(sock, mob, damage.first);
respdata[i].eCT = pktdata[i*2+1];
respdata[i].iID = mob->appearanceData.iNPC_ID;
respdata[i].iDamage = damage.first;
respdata[i].iHP = mob->appearanceData.iHP;
respdata[i].iHitFlag = damage.second; // hitscan, not a rocket or a grenade
} }
if (plr->batteryW >= 6 + plr->level)
plr->batteryW -= 6 + plr->level;
else
plr->batteryW = 0;
damage.first = target->takeDamage(sock, damage.first);
respdata[i].eCT = pktdata[i].eCT;
respdata[i].iID = target->getID();
respdata[i].iDamage = damage.first;
respdata[i].iHP = target->getCurrentHP();
respdata[i].iHitFlag = damage.second; // hitscan, not a rocket or a grenade
} }
sock->sendPacket((void*)respbuf, P_FE2CL_PC_ATTACK_CHARs_SUCC, resplen); sock->sendPacket((void*)respbuf, P_FE2CL_PC_ATTACK_CHARs_SUCC, resplen);
@ -653,21 +776,6 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) {
return; return;
} }
// rapid fire anti-cheat
time_t currTime = getTime();
if (currTime - plr->lastShot < plr->fireRate * 80)
plr->suspicionRating += plr->fireRate * 100 + plr->lastShot - currTime; // gain suspicion for rapid firing
else if (currTime - plr->lastShot < plr->fireRate * 180 && plr->suspicionRating > 0)
plr->suspicionRating += plr->fireRate * 100 + plr->lastShot - currTime; // lose suspicion for delayed firing
plr->lastShot = currTime;
if (plr->suspicionRating > 10000) { // kill the socket when the player is too suspicious
sock->kill();
CNShardServer::_killConnection(sock);
return;
}
/* /*
* initialize response struct * initialize response struct
* rocket style hit doesn't work properly, so we're always sending this one * rocket style hit doesn't work properly, so we're always sending this one
@ -696,7 +804,7 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) {
} }
BaseNPC* npc = NPCManager::NPCs[pktdata[i]]; BaseNPC* npc = NPCManager::NPCs[pktdata[i]];
if (npc->type != EntityType::MOB) { if (npc->kind != EntityKind::MOB) {
std::cout << "[WARN] projectileHit: NPC is not a mob" << std::endl; std::cout << "[WARN] projectileHit: NPC is not a mob" << std::endl;
return; return;
} }
@ -709,11 +817,11 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) {
int difficulty = (int)mob->data["m_iNpcLevel"]; int difficulty = (int)mob->data["m_iNpcLevel"];
damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, bullet->weaponBoost, Nanos::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty); damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, bullet->weaponBoost, Nanos::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty);
damage.first = hitMob(sock, mob, damage.first); damage.first = mob->takeDamage(sock, damage.first);
respdata[i].iID = mob->appearanceData.iNPC_ID; respdata[i].iID = mob->id;
respdata[i].iDamage = damage.first; respdata[i].iDamage = damage.first;
respdata[i].iHP = mob->appearanceData.iHP; respdata[i].iHP = mob->hp;
respdata[i].iHitFlag = damage.second; respdata[i].iHitFlag = damage.second;
} }
@ -728,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;
@ -735,18 +844,13 @@ static void playerTick(CNServer *serv, time_t currTime) {
bool transmit = false; bool transmit = false;
// group ticks // group ticks
if (plr->groupCnt > 1) 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) {
@ -758,22 +862,21 @@ static void playerTick(CNServer *serv, time_t currTime) {
plr->healCooldown -= 4000; plr->healCooldown -= 4000;
} }
for (int i = 0; i < 3; i++) { // combat tick
if (plr->activeNano != 0 && plr->equippedNanos[i] == plr->activeNano) { // spend stamina if(currTime - lastCombatTIme >= 2000) {
plr->Nanos[plr->activeNano].iStamina -= 1 + plr->nanoDrainRate / 5; plr->step(currTime);
transmit = true;
}
if (plr->Nanos[plr->activeNano].iStamina <= 0) // nanos
Nanos::summonNano(sock, -1, true); // unsummon nano silently if (plr->activeNano != 0) { // tick active nano
sNano* nano = plr->getActiveNano();
transmit = true; if (Abilities::SkillTable.find(nano->iSkillID) != Abilities::SkillTable.end()) {
} else if (plr->Nanos[plr->equippedNanos[i]].iStamina < 150) { // regain stamina // nano has skill data
sNano& nano = plr->Nanos[plr->equippedNanos[i]]; SkillData* skill = &Abilities::SkillTable[nano->iSkillID];
nano.iStamina += 1; if (skill->drainType == SkillDrainType::PASSIVE)
Nanos::applyNanoBuff(skill, plr);
if (nano.iStamina > 150) // ^ composite condition calculation is separate from combat for responsiveness
nano.iStamina = 150;
transmit = true;
} }
} }
@ -789,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);
@ -803,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);

View File

@ -1,15 +1,10 @@
#pragma once #pragma once
#include "core/Core.hpp" #include "Player.hpp"
#include "servers/CNShardServer.hpp"
#include "NPC.hpp"
#include "MobAI.hpp" #include "MobAI.hpp"
#include "JSON.hpp"
#include <map> #include <map>
#include <unordered_map> #include <vector>
#include <queue>
struct Bullet { struct Bullet {
int pointDamage; int pointDamage;
@ -24,6 +19,5 @@ namespace Combat {
void init(); void init();
void npcAttackPc(Mob *mob, time_t currTime); void npcAttackPc(Mob *mob, time_t currTime);
int hitMob(CNSocket *sock, Mob *mob, int damage); void genQItemRolls(std::vector<Player*> players, std::map<int, int>& rolls);
void killMob(CNSocket *sock, Mob *mob);
} }

View File

@ -1,18 +1,19 @@
#include "CustomCommands.hpp" #include "CustomCommands.hpp"
#include "Chat.hpp"
#include "db/Database.hpp"
#include "Player.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "Chat.hpp"
#include "TableData.hpp" #include "TableData.hpp"
#include "NPCManager.hpp" #include "NPCManager.hpp"
#include "Eggs.hpp"
#include "MobAI.hpp" #include "MobAI.hpp"
#include "Items.hpp"
#include "db/Database.hpp"
#include "Transport.hpp"
#include "Missions.hpp" #include "Missions.hpp"
#include "Eggs.hpp"
#include "Items.hpp"
#include "Abilities.hpp"
#include <sstream> #include <sstream>
#include <iterator>
#include <math.h>
#include <limits.h> #include <limits.h>
typedef void (*CommandHandler)(std::string fullString, std::vector<std::string>& args, CNSocket* sock); typedef void (*CommandHandler)(std::string fullString, std::vector<std::string>& args, CNSocket* sock);
@ -243,20 +244,20 @@ static void summonWCommand(std::string full, std::vector<std::string>& args, CNS
BaseNPC *npc = NPCManager::summonNPC(plr->x, plr->y, plr->z, plr->instanceID, type, true); BaseNPC *npc = NPCManager::summonNPC(plr->x, plr->y, plr->z, plr->instanceID, type, true);
// update angle // update angle
npc->appearanceData.iAngle = (plr->angle + 180) % 360; npc->angle = (plr->angle + 180) % 360;
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, plr->x, plr->y, plr->z, plr->instanceID, npc->appearanceData.iAngle); NPCManager::updateNPCPosition(npc->id, plr->x, plr->y, plr->z, plr->instanceID, npc->angle);
// if we're in a lair, we need to spawn the NPC in both the private instance and the template // if we're in a lair, we need to spawn the NPC in both the private instance and the template
if (PLAYERID(plr->instanceID) != 0) { if (PLAYERID(plr->instanceID) != 0) {
npc = NPCManager::summonNPC(plr->x, plr->y, plr->z, plr->instanceID, type, true, true); npc = NPCManager::summonNPC(plr->x, plr->y, plr->z, plr->instanceID, type, true, true);
npc->appearanceData.iAngle = (plr->angle + 180) % 360; npc->angle = (plr->angle + 180) % 360;
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, plr->x, plr->y, plr->z, npc->instanceID, npc->appearanceData.iAngle); NPCManager::updateNPCPosition(npc->id, plr->x, plr->y, plr->z, npc->instanceID, npc->angle);
} }
Chat::sendServerMessage(sock, "/summonW: placed mob with type: " + std::to_string(type) + Chat::sendServerMessage(sock, "/summonW: placed mob with type: " + std::to_string(type) +
", id: " + std::to_string(npc->appearanceData.iNPC_ID)); ", id: " + std::to_string(npc->id));
TableData::RunningMobs[npc->appearanceData.iNPC_ID] = npc; // only record the one in the template TableData::RunningMobs[npc->id] = npc; // only record the one in the template
} }
static void unsummonWCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) { static void unsummonWCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
@ -269,24 +270,24 @@ static void unsummonWCommand(std::string full, std::vector<std::string>& args, C
return; return;
} }
if (TableData::RunningEggs.find(npc->appearanceData.iNPC_ID) != TableData::RunningEggs.end()) { if (TableData::RunningEggs.find(npc->id) != TableData::RunningEggs.end()) {
Chat::sendServerMessage(sock, "/unsummonW: removed egg with type: " + std::to_string(npc->appearanceData.iNPCType) + Chat::sendServerMessage(sock, "/unsummonW: removed egg with type: " + std::to_string(npc->type) +
", id: " + std::to_string(npc->appearanceData.iNPC_ID)); ", id: " + std::to_string(npc->id));
TableData::RunningEggs.erase(npc->appearanceData.iNPC_ID); TableData::RunningEggs.erase(npc->id);
NPCManager::destroyNPC(npc->appearanceData.iNPC_ID); NPCManager::destroyNPC(npc->id);
return; return;
} }
if (TableData::RunningMobs.find(npc->appearanceData.iNPC_ID) == TableData::RunningMobs.end() if (TableData::RunningMobs.find(npc->id) == TableData::RunningMobs.end()
&& TableData::RunningGroups.find(npc->appearanceData.iNPC_ID) == TableData::RunningGroups.end()) { && TableData::RunningGroups.find(npc->id) == TableData::RunningGroups.end()) {
Chat::sendServerMessage(sock, "/unsummonW: Closest NPC is not a gruntwork mob."); Chat::sendServerMessage(sock, "/unsummonW: Closest NPC is not a gruntwork mob.");
return; return;
} }
if (NPCManager::NPCs.find(npc->appearanceData.iNPC_ID) != NPCManager::NPCs.end() && NPCManager::NPCs[npc->appearanceData.iNPC_ID]->type == EntityType::MOB) { if (NPCManager::NPCs.find(npc->id) != NPCManager::NPCs.end() && NPCManager::NPCs[npc->id]->kind == EntityKind::MOB) {
int leadId = ((Mob*)npc)->groupLeader; int leadId = ((Mob*)npc)->groupLeader;
if (leadId != 0) { if (leadId != 0) {
if (NPCManager::NPCs.find(leadId) == NPCManager::NPCs.end() || NPCManager::NPCs[leadId]->type != EntityType::MOB) { if (NPCManager::NPCs.find(leadId) == NPCManager::NPCs.end() || NPCManager::NPCs[leadId]->kind != EntityKind::MOB) {
std::cout << "[WARN] unsummonW: leader not found!" << std::endl; std::cout << "[WARN] unsummonW: leader not found!" << std::endl;
} }
Mob* leadNpc = (Mob*)NPCManager::NPCs[leadId]; Mob* leadNpc = (Mob*)NPCManager::NPCs[leadId];
@ -294,7 +295,7 @@ static void unsummonWCommand(std::string full, std::vector<std::string>& args, C
if (leadNpc->groupMember[i] == 0) if (leadNpc->groupMember[i] == 0)
break; break;
if (NPCManager::NPCs.find(leadNpc->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadNpc->groupMember[i]]->type != EntityType::MOB) { if (NPCManager::NPCs.find(leadNpc->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadNpc->groupMember[i]]->kind != EntityKind::MOB) {
std::cout << "[WARN] unsommonW: leader can't find a group member!" << std::endl; std::cout << "[WARN] unsommonW: leader can't find a group member!" << std::endl;
continue; continue;
} }
@ -308,12 +309,12 @@ static void unsummonWCommand(std::string full, std::vector<std::string>& args, C
} }
} }
Chat::sendServerMessage(sock, "/unsummonW: removed mob with type: " + std::to_string(npc->appearanceData.iNPCType) + Chat::sendServerMessage(sock, "/unsummonW: removed mob with type: " + std::to_string(npc->type) +
", id: " + std::to_string(npc->appearanceData.iNPC_ID)); ", id: " + std::to_string(npc->id));
TableData::RunningMobs.erase(npc->appearanceData.iNPC_ID); TableData::RunningMobs.erase(npc->id);
NPCManager::destroyNPC(npc->appearanceData.iNPC_ID); NPCManager::destroyNPC(npc->id);
} }
static void toggleAiCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) { static void toggleAiCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
@ -324,11 +325,11 @@ static void toggleAiCommand(std::string full, std::vector<std::string>& args, CN
// return all mobs to their spawn points // return all mobs to their spawn points
for (auto& pair : NPCManager::NPCs) { for (auto& pair : NPCManager::NPCs) {
if (pair.second->type != EntityType::MOB) if (pair.second->kind != EntityKind::MOB)
continue; continue;
Mob* mob = (Mob*)pair.second; Mob* mob = (Mob*)pair.second;
mob->state = MobState::RETREAT; mob->state = AIState::RETREAT;
mob->target = nullptr; mob->target = nullptr;
mob->nextMovement = getTime(); mob->nextMovement = getTime();
@ -356,24 +357,24 @@ static void npcRotateCommand(std::string full, std::vector<std::string>& args, C
} }
int angle = (plr->angle + 180) % 360; int angle = (plr->angle + 180) % 360;
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->x, npc->y, npc->z, npc->instanceID, angle); NPCManager::updateNPCPosition(npc->id, npc->x, npc->y, npc->z, npc->instanceID, angle);
// if it's a gruntwork NPC, rotate in-place // if it's a gruntwork NPC, rotate in-place
if (TableData::RunningMobs.find(npc->appearanceData.iNPC_ID) != TableData::RunningMobs.end()) { if (TableData::RunningMobs.find(npc->id) != TableData::RunningMobs.end()) {
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->x, npc->y, npc->z, npc->instanceID, angle); NPCManager::updateNPCPosition(npc->id, npc->x, npc->y, npc->z, npc->instanceID, angle);
Chat::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) + " for gruntwork NPC " Chat::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) + " for gruntwork NPC "
+ std::to_string(npc->appearanceData.iNPC_ID)); + std::to_string(npc->id));
} else { } else {
TableData::RunningNPCRotations[npc->appearanceData.iNPC_ID] = angle; TableData::RunningNPCRotations[npc->id] = angle;
Chat::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) + " for NPC " Chat::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) + " for NPC "
+ std::to_string(npc->appearanceData.iNPC_ID)); + std::to_string(npc->id));
} }
// update rotation clientside // update rotation clientside
INITSTRUCT(sP_FE2CL_NPC_ENTER, pkt); INITSTRUCT(sP_FE2CL_NPC_ENTER, pkt);
pkt.NPCAppearanceData = npc->appearanceData; pkt.NPCAppearanceData = npc->getAppearanceData();
sock->sendPacket(pkt, P_FE2CL_NPC_ENTER); sock->sendPacket(pkt, P_FE2CL_NPC_ENTER);
} }
@ -443,9 +444,9 @@ static void npcInstanceCommand(std::string full, std::vector<std::string>& args,
return; return;
} }
Chat::sendServerMessage(sock, "[NPCI] Moving NPC with ID " + std::to_string(npc->appearanceData.iNPC_ID) + " to instance " + std::to_string(instance)); Chat::sendServerMessage(sock, "[NPCI] Moving NPC with ID " + std::to_string(npc->id) + " to instance " + std::to_string(instance));
TableData::RunningNPCMapNumbers[npc->appearanceData.iNPC_ID] = instance; TableData::RunningNPCMapNumbers[npc->id] = instance;
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->x, npc->y, npc->z, instance, npc->appearanceData.iAngle); NPCManager::updateNPCPosition(npc->id, npc->x, npc->y, npc->z, instance, npc->angle);
} }
static void minfoCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) { static void minfoCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
@ -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) {
@ -532,7 +536,7 @@ static void eggCommand(std::string full, std::vector<std::string>& args, CNSocke
int addX = 0; //-500.0f * sin(plr->angle / 180.0f * M_PI); int addX = 0; //-500.0f * sin(plr->angle / 180.0f * M_PI);
int addY = 0; //-500.0f * cos(plr->angle / 180.0f * M_PI); int addY = 0; //-500.0f * cos(plr->angle / 180.0f * M_PI);
Egg* egg = new Egg(plr->x + addX, plr->y + addY, plr->z, plr->instanceID, eggType, id, false); // change last arg to true after gruntwork Egg* egg = new Egg(plr->instanceID, eggType, id, false); // change last arg to true after gruntwork
NPCManager::NPCs[id] = egg; NPCManager::NPCs[id] = egg;
NPCManager::updateNPCPosition(id, plr->x + addX, plr->y + addY, plr->z, plr->instanceID, plr->angle); NPCManager::updateNPCPosition(id, plr->x + addX, plr->y + addY, plr->z, plr->instanceID, plr->angle);
@ -609,40 +613,40 @@ static void summonGroupCommand(std::string full, std::vector<std::string>& args,
} }
BaseNPC *npc = NPCManager::summonNPC(x, y, z, plr->instanceID, type, wCommand); BaseNPC *npc = NPCManager::summonNPC(x, y, z, plr->instanceID, type, wCommand);
if (team == 2 && i > 0 && npc->type == EntityType::MOB) { if (team == 2 && i > 0 && npc->kind == EntityKind::MOB) {
leadNpc->groupMember[i-1] = npc->appearanceData.iNPC_ID; leadNpc->groupMember[i-1] = npc->id;
Mob* mob = (Mob*)NPCManager::NPCs[npc->appearanceData.iNPC_ID]; Mob* mob = (Mob*)NPCManager::NPCs[npc->id];
mob->groupLeader = leadNpc->appearanceData.iNPC_ID; mob->groupLeader = leadNpc->id;
mob->offsetX = x - plr->x; mob->offsetX = x - plr->x;
mob->offsetY = y - plr->y; mob->offsetY = y - plr->y;
} }
npc->appearanceData.iAngle = (plr->angle + 180) % 360; npc->angle = (plr->angle + 180) % 360;
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, x, y, z, plr->instanceID, npc->appearanceData.iAngle); NPCManager::updateNPCPosition(npc->id, x, y, z, plr->instanceID, npc->angle);
// if we're in a lair, we need to spawn the NPC in both the private instance and the template // if we're in a lair, we need to spawn the NPC in both the private instance and the template
if (PLAYERID(plr->instanceID) != 0) { if (PLAYERID(plr->instanceID) != 0) {
npc = NPCManager::summonNPC(plr->x, plr->y, plr->z, plr->instanceID, type, wCommand, true); npc = NPCManager::summonNPC(plr->x, plr->y, plr->z, plr->instanceID, type, wCommand, true);
if (team == 2 && i > 0 && npc->type == EntityType::MOB) { if (team == 2 && i > 0 && npc->kind == EntityKind::MOB) {
leadNpc->groupMember[i-1] = npc->appearanceData.iNPC_ID; leadNpc->groupMember[i-1] = npc->id;
Mob* mob = (Mob*)NPCManager::NPCs[npc->appearanceData.iNPC_ID]; Mob* mob = (Mob*)NPCManager::NPCs[npc->id];
mob->groupLeader = leadNpc->appearanceData.iNPC_ID; mob->groupLeader = leadNpc->id;
mob->offsetX = x - plr->x; mob->offsetX = x - plr->x;
mob->offsetY = y - plr->y; mob->offsetY = y - plr->y;
} }
npc->appearanceData.iAngle = (plr->angle + 180) % 360; npc->angle = (plr->angle + 180) % 360;
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, x, y, z, plr->instanceID, npc->appearanceData.iAngle); NPCManager::updateNPCPosition(npc->id, x, y, z, plr->instanceID, npc->angle);
} }
Chat::sendServerMessage(sock, "/summonGroup(W): placed mob with type: " + std::to_string(type) + Chat::sendServerMessage(sock, "/summonGroup(W): placed mob with type: " + std::to_string(type) +
", id: " + std::to_string(npc->appearanceData.iNPC_ID)); ", id: " + std::to_string(npc->id));
if (i == 0 && team == 2 && npc->type == EntityType::MOB) { if (i == 0 && team == 2 && npc->kind == EntityKind::MOB) {
type = type2; type = type2;
leadNpc = (Mob*)NPCManager::NPCs[npc->appearanceData.iNPC_ID]; leadNpc = (Mob*)NPCManager::NPCs[npc->id];
leadNpc->groupLeader = leadNpc->appearanceData.iNPC_ID; leadNpc->groupLeader = leadNpc->id;
} }
} }
@ -654,7 +658,7 @@ static void summonGroupCommand(std::string full, std::vector<std::string>& args,
return; return;
} }
TableData::RunningGroups[leadNpc->appearanceData.iNPC_ID] = leadNpc; // only record the leader TableData::RunningGroups[leadNpc->id] = leadNpc; // only record the leader
} }
static void flushCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) { static void flushCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
@ -671,15 +675,15 @@ static void whoisCommand(std::string full, std::vector<std::string>& args, CNSoc
return; return;
} }
Chat::sendServerMessage(sock, "[WHOIS] ID: " + std::to_string(npc->appearanceData.iNPC_ID)); Chat::sendServerMessage(sock, "[WHOIS] ID: " + std::to_string(npc->id));
Chat::sendServerMessage(sock, "[WHOIS] Type: " + std::to_string(npc->appearanceData.iNPCType)); Chat::sendServerMessage(sock, "[WHOIS] Type: " + std::to_string(npc->type));
Chat::sendServerMessage(sock, "[WHOIS] HP: " + std::to_string(npc->appearanceData.iHP)); Chat::sendServerMessage(sock, "[WHOIS] HP: " + std::to_string(npc->hp));
Chat::sendServerMessage(sock, "[WHOIS] CBF: " + std::to_string(npc->appearanceData.iConditionBitFlag)); Chat::sendServerMessage(sock, "[WHOIS] CBF: " + std::to_string(npc->cbf));
Chat::sendServerMessage(sock, "[WHOIS] EntityType: " + std::to_string((int)npc->type)); Chat::sendServerMessage(sock, "[WHOIS] EntityType: " + std::to_string((int)npc->kind));
Chat::sendServerMessage(sock, "[WHOIS] X: " + std::to_string(npc->x)); Chat::sendServerMessage(sock, "[WHOIS] X: " + std::to_string(npc->x));
Chat::sendServerMessage(sock, "[WHOIS] Y: " + std::to_string(npc->y)); Chat::sendServerMessage(sock, "[WHOIS] Y: " + std::to_string(npc->y));
Chat::sendServerMessage(sock, "[WHOIS] Z: " + std::to_string(npc->z)); Chat::sendServerMessage(sock, "[WHOIS] Z: " + std::to_string(npc->z));
Chat::sendServerMessage(sock, "[WHOIS] Angle: " + std::to_string(npc->appearanceData.iAngle)); Chat::sendServerMessage(sock, "[WHOIS] Angle: " + std::to_string(npc->angle));
std::string chunkPosition = std::to_string(std::get<0>(npc->chunkPos)) + ", " + std::to_string(std::get<1>(npc->chunkPos)) + ", " + std::to_string(std::get<2>(npc->chunkPos)); std::string chunkPosition = std::to_string(std::get<0>(npc->chunkPos)) + ", " + std::to_string(std::get<1>(npc->chunkPos)) + ", " + std::to_string(std::get<2>(npc->chunkPos));
Chat::sendServerMessage(sock, "[WHOIS] Chunk: {" + chunkPosition + "}"); Chat::sendServerMessage(sock, "[WHOIS] Chunk: {" + chunkPosition + "}");
Chat::sendServerMessage(sock, "[WHOIS] MapNum: " + std::to_string(MAPNUM(npc->instanceID))); Chat::sendServerMessage(sock, "[WHOIS] MapNum: " + std::to_string(MAPNUM(npc->instanceID)));
@ -694,7 +698,7 @@ static void lairUnlockCommand(std::string full, std::vector<std::string>& args,
int lastDist = INT_MAX; int lastDist = INT_MAX;
for (Chunk *chnk : Chunking::getViewableChunks(plr->chunkPos)) { for (Chunk *chnk : Chunking::getViewableChunks(plr->chunkPos)) {
for (const EntityRef& ref : chnk->entities) { for (const EntityRef& ref : chnk->entities) {
if (ref.type == EntityType::PLAYER) if (ref.kind == EntityKind::PLAYER)
continue; continue;
BaseNPC* npc = (BaseNPC*)ref.getEntity(); BaseNPC* npc = (BaseNPC*)ref.getEntity();
@ -705,7 +709,7 @@ static void lairUnlockCommand(std::string full, std::vector<std::string>& args,
continue; continue;
for (auto it = NPCManager::Warps.begin(); it != NPCManager::Warps.end(); it++) { for (auto it = NPCManager::Warps.begin(); it != NPCManager::Warps.end(); it++) {
if (it->second.npcID == npc->appearanceData.iNPCType) { if (it->second.npcID == npc->type) {
taskID = it->second.limitTaskID; taskID = it->second.limitTaskID;
missionID = Missions::Tasks[taskID]->task["m_iHMissionID"]; missionID = Missions::Tasks[taskID]->task["m_iHMissionID"];
lastDist = dist; lastDist = dist;
@ -977,11 +981,17 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
// add first point at NPC's current location // add first point at NPC's current location
std::vector<BaseNPC*> pathPoints; std::vector<BaseNPC*> pathPoints;
BaseNPC* marker = new BaseNPC(npc->x, npc->y, npc->z, 0, plr->instanceID, 1386, NPCManager::nextId--); BaseNPC* marker = new BaseNPC(0, plr->instanceID, 1386, NPCManager::nextId--);
// assign coords manually, since we aren't actually adding markers to the world
marker->x = npc->x;
marker->y = npc->y;
marker->z = npc->z;
pathPoints.push_back(marker); pathPoints.push_back(marker);
// map from player // map from player
TableData::RunningNPCPaths[plr->iID] = std::make_pair(npc, pathPoints); TableData::RunningNPCPaths[plr->iID] = std::make_pair(npc, pathPoints);
Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->appearanceData.iNPC_ID) + " is now following you"); Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->id) + " is now following you");
updatePathMarkers(sock); updatePathMarkers(sock);
return; return;
} }
@ -998,7 +1008,12 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
// /path kf // /path kf
if (args[1] == "kf") { if (args[1] == "kf") {
BaseNPC* marker = new BaseNPC(npc->x, npc->y, npc->z, 0, plr->instanceID, 1386, NPCManager::nextId--); BaseNPC* marker = new BaseNPC(0, plr->instanceID, 1386, NPCManager::nextId--);
marker->x = npc->x;
marker->y = npc->y;
marker->z = npc->z;
entry->second.push_back(marker); entry->second.push_back(marker);
Chat::sendServerMessage(sock, "[PATH] Added keyframe"); Chat::sendServerMessage(sock, "[PATH] Added keyframe");
updatePathMarkers(sock); updatePathMarkers(sock);
@ -1008,8 +1023,8 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
// /path here // /path here
if (args[1] == "here") { if (args[1] == "here") {
// bring the NPC to where the player is standing // bring the NPC to where the player is standing
Transport::NPCQueues.erase(npc->appearanceData.iNPC_ID); // delete transport queue Transport::NPCQueues.erase(npc->id); // delete transport queue
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, plr->x, plr->y, plr->z, npc->instanceID, 0); NPCManager::updateNPCPosition(npc->id, plr->x, plr->y, plr->z, npc->instanceID, 0);
npc->disappearFromViewOf(sock); npc->disappearFromViewOf(sock);
npc->enterIntoViewOf(sock); npc->enterIntoViewOf(sock);
Chat::sendServerMessage(sock, "[PATH] Come here"); Chat::sendServerMessage(sock, "[PATH] Come here");
@ -1047,9 +1062,9 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
speed = speedArg; speed = speedArg;
} }
// return NPC to home // return NPC to home
Transport::NPCQueues.erase(npc->appearanceData.iNPC_ID); // delete transport queue Transport::NPCQueues.erase(npc->id); // delete transport queue
BaseNPC* home = entry->second[0]; BaseNPC* home = entry->second[0];
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, home->x, home->y, home->z, npc->instanceID, 0); NPCManager::updateNPCPosition(npc->id, home->x, home->y, home->z, npc->instanceID, 0);
npc->disappearFromViewOf(sock); npc->disappearFromViewOf(sock);
npc->enterIntoViewOf(sock); npc->enterIntoViewOf(sock);
@ -1065,7 +1080,7 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
Transport::lerp(&keyframes, from, to, speed); // lerp from A to B Transport::lerp(&keyframes, from, to, speed); // lerp from A to B
from = to; // update point A from = to; // update point A
} }
Transport::NPCQueues[npc->appearanceData.iNPC_ID] = keyframes; Transport::NPCQueues[npc->id] = keyframes;
entry->second.pop_back(); // remove temp end point entry->second.pop_back(); // remove temp end point
Chat::sendServerMessage(sock, "[PATH] Testing NPC path"); Chat::sendServerMessage(sock, "[PATH] Testing NPC path");
@ -1075,9 +1090,9 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
// /path cancel // /path cancel
if (args[1] == "cancel") { if (args[1] == "cancel") {
// return NPC to home // return NPC to home
Transport::NPCQueues.erase(npc->appearanceData.iNPC_ID); // delete transport queue Transport::NPCQueues.erase(npc->id); // delete transport queue
BaseNPC* home = entry->second[0]; BaseNPC* home = entry->second[0];
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, home->x, home->y, home->z, npc->instanceID, 0); NPCManager::updateNPCPosition(npc->id, home->x, home->y, home->z, npc->instanceID, 0);
npc->disappearFromViewOf(sock); npc->disappearFromViewOf(sock);
npc->enterIntoViewOf(sock); npc->enterIntoViewOf(sock);
// deallocate markers // deallocate markers
@ -1087,7 +1102,7 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
} }
// unmap // unmap
TableData::RunningNPCPaths.erase(plr->iID); TableData::RunningNPCPaths.erase(plr->iID);
Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->appearanceData.iNPC_ID) + " is no longer following you"); Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->id) + " is no longer following you");
return; return;
} }
@ -1115,9 +1130,9 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
} }
// return NPC to home and set path to repeat // return NPC to home and set path to repeat
Transport::NPCQueues.erase(npc->appearanceData.iNPC_ID); // delete transport queue Transport::NPCQueues.erase(npc->id); // delete transport queue
BaseNPC* home = entry->second[0]; BaseNPC* home = entry->second[0];
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, home->x, home->y, home->z, npc->instanceID, 0); NPCManager::updateNPCPosition(npc->id, home->x, home->y, home->z, npc->instanceID, 0);
npc->disappearFromViewOf(sock); npc->disappearFromViewOf(sock);
npc->enterIntoViewOf(sock); npc->enterIntoViewOf(sock);
npc->loopingPath = true; npc->loopingPath = true;
@ -1134,7 +1149,7 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
Transport::lerp(&keyframes, from, to, speed); // lerp from A to B Transport::lerp(&keyframes, from, to, speed); // lerp from A to B
from = to; // update point A from = to; // update point A
} }
Transport::NPCQueues[npc->appearanceData.iNPC_ID] = keyframes; Transport::NPCQueues[npc->id] = keyframes;
entry->second.pop_back(); // remove temp end point entry->second.pop_back(); // remove temp end point
// save to gruntwork // save to gruntwork
@ -1161,7 +1176,7 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
finishedPath.isLoop = true; finishedPath.isLoop = true;
finishedPath.speed = speed; finishedPath.speed = speed;
finishedPath.points = finalPoints; finishedPath.points = finalPoints;
finishedPath.targetIDs.push_back(npc->appearanceData.iNPC_ID); finishedPath.targetIDs.push_back(npc->id);
TableData::FinishedNPCPaths.push_back(finishedPath); TableData::FinishedNPCPaths.push_back(finishedPath);
@ -1173,7 +1188,7 @@ static void pathCommand(std::string full, std::vector<std::string>& args, CNSock
// unmap // unmap
TableData::RunningNPCPaths.erase(plr->iID); TableData::RunningNPCPaths.erase(plr->iID);
Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->appearanceData.iNPC_ID) + " is no longer following you"); Chat::sendServerMessage(sock, "[PATH] NPC " + std::to_string(npc->id) + " is no longer following you");
TableData::flush(); TableData::flush();
Chat::sendServerMessage(sock, "[PATH] Path saved to gruntwork"); Chat::sendServerMessage(sock, "[PATH] Path saved to gruntwork");

View File

@ -2,6 +2,8 @@
#include "core/Core.hpp" #include "core/Core.hpp"
#include <string>
namespace CustomCommands { namespace CustomCommands {
void init(); void init();

View File

@ -1,141 +1,78 @@
#include "core/Core.hpp"
#include "Eggs.hpp" #include "Eggs.hpp"
#include "servers/CNShardServer.hpp"
#include "Player.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "Items.hpp"
#include "Nanos.hpp"
#include "Abilities.hpp" #include "Abilities.hpp"
#include "Groups.hpp" #include "NPCManager.hpp"
#include "Entities.hpp"
#include "Items.hpp"
#include <assert.h> #include <assert.h>
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);
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
int bitFlag = Groups::getGroupFlags(otherPlr); // eggId might be 0 if the buff is made by the /buff command
int CBFlag = Nanos::applyBuff(sock, skillId, 1, 3, bitFlag); EntityRef src = eggId == 0 ? sock : EntityRef(eggId);
size_t resplen; if(Abilities::SkillTable.count(skillId) == 0) {
std::cout << "[WARN] egg " << eggId << " has skill ID " << skillId << " which doesn't exist" << std::endl;
if (skillId == 183) { return;
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) * Nanos::SkillTable[skillId].powerIntensity[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) * Nanos::SkillTable[skillId].powerIntensity[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 = Nanos::SkillTable[skillId].skillType; // apply buff
skillUse->iTargetCnt = 1; if(skill->targetType != SkillTargetType::PLAYERS) {
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);
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
int groupFlags = Groups::getGroupFlags(otherPlr);
for (auto& pwr : Nanos::NanoPowers) {
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->type != EntityType::EGG) if (npc.second->kind != EntityKind::EGG)
continue; continue;
auto egg = (Egg*)npc.second; auto egg = (Egg*)npc.second;
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;
egg->appearanceData.iHP = 400; egg->hp = 400;
Chunking::addEntityToChunks(Chunking::getViewableChunks(egg->chunkPos), {npc.first}); Chunking::addEntityToChunks(Chunking::getViewableChunks(egg->chunkPos), {npc.first});
} }
@ -163,7 +100,7 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) {
return; return;
} }
auto egg = (Egg*)eggRef.getEntity(); auto egg = (Egg*)eggRef.getEntity();
if (egg->type != EntityType::EGG) { if (egg->kind != EntityKind::EGG) {
std::cout << "[WARN] Player tried to open something other than an?!" << std::endl; std::cout << "[WARN] Player tried to open something other than an?!" << std::endl;
return; return;
} }
@ -180,7 +117,7 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) {
} }
*/ */
int typeId = egg->appearanceData.iNPCType; int typeId = egg->type;
if (EggTypes.find(typeId) == EggTypes.end()) { if (EggTypes.find(typeId) == EggTypes.end()) {
std::cout << "[WARN] Egg Type " << typeId << " not found!" << std::endl; std::cout << "[WARN] Egg Type " << typeId << " not found!" << std::endl;
return; return;
@ -188,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;
@ -255,7 +189,7 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) {
Chunking::removeEntityFromChunks(Chunking::getViewableChunks(egg->chunkPos), eggRef); Chunking::removeEntityFromChunks(Chunking::getViewableChunks(egg->chunkPos), eggRef);
egg->dead = true; egg->dead = true;
egg->deadUntil = getTime() + (time_t)type->regen * 1000; egg->deadUntil = getTime() + (time_t)type->regen * 1000;
egg->appearanceData.iHP = 0; egg->hp = 0;
} }
} }

View File

@ -1,7 +1,6 @@
#pragma once #pragma once
#include "core/Core.hpp" #include "core/Core.hpp"
#include "Entities.hpp"
struct EggType { struct EggType {
int dropCrateId; int dropCrateId;
@ -11,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);
} }

View File

@ -1,9 +1,9 @@
#include "Email.hpp" #include "Email.hpp"
#include "core/Core.hpp" #include "core/Core.hpp"
#include "db/Database.hpp"
#include "servers/CNShardServer.hpp" #include "servers/CNShardServer.hpp"
#include "db/Database.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "Items.hpp" #include "Items.hpp"
#include "Chat.hpp" #include "Chat.hpp"

View File

@ -1,18 +1,15 @@
#include "core/Core.hpp"
#include "Entities.hpp" #include "Entities.hpp"
#include "Chunking.hpp"
#include "PlayerManager.hpp"
#include "NPCManager.hpp"
#include "Eggs.hpp"
#include "MobAI.hpp"
#include <type_traits> #include "NPCManager.hpp"
#include "PlayerManager.hpp"
#include <assert.h>
static_assert(std::is_standard_layout<EntityRef>::value); static_assert(std::is_standard_layout<EntityRef>::value);
static_assert(std::is_trivially_copyable<EntityRef>::value); static_assert(std::is_trivially_copyable<EntityRef>::value);
EntityRef::EntityRef(CNSocket *s) { EntityRef::EntityRef(CNSocket *s) {
type = EntityType::PLAYER; kind = EntityKind::PLAYER;
sock = s; sock = s;
} }
@ -20,11 +17,11 @@ EntityRef::EntityRef(int32_t i) {
id = i; id = i;
assert(NPCManager::NPCs.find(id) != NPCManager::NPCs.end()); assert(NPCManager::NPCs.find(id) != NPCManager::NPCs.end());
type = NPCManager::NPCs[id]->type; kind = NPCManager::NPCs[id]->kind;
} }
bool EntityRef::isValid() const { bool EntityRef::isValid() const {
if (type == EntityType::PLAYER) if (kind == EntityKind::PLAYER)
return PlayerManager::players.find(sock) != PlayerManager::players.end(); return PlayerManager::players.find(sock) != PlayerManager::players.end();
return NPCManager::NPCs.find(id) != NPCManager::NPCs.end(); return NPCManager::NPCs.find(id) != NPCManager::NPCs.end();
@ -33,21 +30,32 @@ bool EntityRef::isValid() const {
Entity *EntityRef::getEntity() const { Entity *EntityRef::getEntity() const {
assert(isValid()); assert(isValid());
if (type == EntityType::PLAYER) if (kind == EntityKind::PLAYER)
return PlayerManager::getPlayer(sock); return PlayerManager::getPlayer(sock);
return NPCManager::NPCs[id]; return NPCManager::NPCs[id];
} }
sNPCAppearanceData BaseNPC::getAppearanceData() {
sNPCAppearanceData data = {};
data.iAngle = angle;
data.iBarkerType = 0; // unused?
data.iConditionBitFlag = cbf;
data.iHP = hp;
data.iNPCType = type;
data.iNPC_ID = id;
data.iX = x;
data.iY = y;
data.iZ = z;
return data;
}
/* /*
* Entity coming into view. * Entity coming into view.
*/ */
void BaseNPC::enterIntoViewOf(CNSocket *sock) { void BaseNPC::enterIntoViewOf(CNSocket *sock) {
INITSTRUCT(sP_FE2CL_NPC_ENTER, pkt); INITSTRUCT(sP_FE2CL_NPC_ENTER, pkt);
pkt.NPCAppearanceData = appearanceData; pkt.NPCAppearanceData = getAppearanceData();
pkt.NPCAppearanceData.iX = x;
pkt.NPCAppearanceData.iY = y;
pkt.NPCAppearanceData.iZ = z;
sock->sendPacket(pkt, P_FE2CL_NPC_ENTER); sock->sendPacket(pkt, P_FE2CL_NPC_ENTER);
} }
@ -56,7 +64,7 @@ void Bus::enterIntoViewOf(CNSocket *sock) {
// TODO: Potentially decouple this from BaseNPC? // TODO: Potentially decouple this from BaseNPC?
pkt.AppearanceData = { pkt.AppearanceData = {
3, appearanceData.iNPC_ID, appearanceData.iNPCType, 3, id, type,
x, y, z x, y, z
}; };
@ -66,28 +74,40 @@ void Bus::enterIntoViewOf(CNSocket *sock) {
void Egg::enterIntoViewOf(CNSocket *sock) { void Egg::enterIntoViewOf(CNSocket *sock) {
INITSTRUCT(sP_FE2CL_SHINY_ENTER, pkt); INITSTRUCT(sP_FE2CL_SHINY_ENTER, pkt);
Eggs::npcDataToEggData(x, y, z, &appearanceData, &pkt.ShinyAppearanceData); // TODO: Potentially decouple this from BaseNPC?
pkt.ShinyAppearanceData = {
id, type, 0, // client doesn't care about map num
x, y, z
};
sock->sendPacket(pkt, P_FE2CL_SHINY_ENTER); sock->sendPacket(pkt, P_FE2CL_SHINY_ENTER);
} }
sNano* Player::getActiveNano() {
return &Nanos[activeNano];
}
sPCAppearanceData Player::getAppearanceData() {
sPCAppearanceData data = {};
data.iID = iID;
data.iHP = HP;
data.iLv = level;
data.iX = x;
data.iY = y;
data.iZ = z;
data.iAngle = angle;
data.PCStyle = PCStyle;
data.Nano = Nanos[activeNano];
data.iPCState = iPCState;
data.iSpecialState = iSpecialState;
memcpy(data.ItemEquip, Equip, sizeof(sItemBase) * AEQUIP_COUNT);
return data;
}
// TODO: this is less effiecient than it was, because of memset() // TODO: this is less effiecient than it was, because of memset()
void Player::enterIntoViewOf(CNSocket *sock) { void Player::enterIntoViewOf(CNSocket *sock) {
INITSTRUCT(sP_FE2CL_PC_NEW, pkt); INITSTRUCT(sP_FE2CL_PC_NEW, pkt);
pkt.PCAppearanceData = getAppearanceData();
pkt.PCAppearanceData.iID = iID;
pkt.PCAppearanceData.iHP = HP;
pkt.PCAppearanceData.iLv = level;
pkt.PCAppearanceData.iX = x;
pkt.PCAppearanceData.iY = y;
pkt.PCAppearanceData.iZ = z;
pkt.PCAppearanceData.iAngle = angle;
pkt.PCAppearanceData.PCStyle = PCStyle;
pkt.PCAppearanceData.Nano = Nanos[activeNano];
pkt.PCAppearanceData.iPCState = iPCState;
pkt.PCAppearanceData.iSpecialState = iSpecialState;
memcpy(pkt.PCAppearanceData.ItemEquip, Equip, sizeof(sItemBase) * AEQUIP_COUNT);
sock->sendPacket(pkt, P_FE2CL_PC_NEW); sock->sendPacket(pkt, P_FE2CL_PC_NEW);
} }
@ -96,20 +116,20 @@ void Player::enterIntoViewOf(CNSocket *sock) {
*/ */
void BaseNPC::disappearFromViewOf(CNSocket *sock) { void BaseNPC::disappearFromViewOf(CNSocket *sock) {
INITSTRUCT(sP_FE2CL_NPC_EXIT, pkt); INITSTRUCT(sP_FE2CL_NPC_EXIT, pkt);
pkt.iNPC_ID = appearanceData.iNPC_ID; pkt.iNPC_ID = id;
sock->sendPacket(pkt, P_FE2CL_NPC_EXIT); sock->sendPacket(pkt, P_FE2CL_NPC_EXIT);
} }
void Bus::disappearFromViewOf(CNSocket *sock) { void Bus::disappearFromViewOf(CNSocket *sock) {
INITSTRUCT(sP_FE2CL_TRANSPORTATION_EXIT, pkt); INITSTRUCT(sP_FE2CL_TRANSPORTATION_EXIT, pkt);
pkt.eTT = 3; pkt.eTT = 3;
pkt.iT_ID = appearanceData.iNPC_ID; pkt.iT_ID = id;
sock->sendPacket(pkt, P_FE2CL_TRANSPORTATION_EXIT); sock->sendPacket(pkt, P_FE2CL_TRANSPORTATION_EXIT);
} }
void Egg::disappearFromViewOf(CNSocket *sock) { void Egg::disappearFromViewOf(CNSocket *sock) {
INITSTRUCT(sP_FE2CL_SHINY_EXIT, pkt); INITSTRUCT(sP_FE2CL_SHINY_EXIT, pkt);
pkt.iShinyID = appearanceData.iNPC_ID; pkt.iShinyID = id;
sock->sendPacket(pkt, P_FE2CL_SHINY_EXIT); sock->sendPacket(pkt, P_FE2CL_SHINY_EXIT);
} }

View File

@ -1,23 +1,26 @@
#pragma once #pragma once
#include "core/Core.hpp" #include "core/Core.hpp"
#include "EntityRef.hpp"
#include "Buffs.hpp"
#include "Chunking.hpp" #include "Chunking.hpp"
#include "Groups.hpp"
#include <stdint.h>
#include <set> #include <set>
#include <map>
#include <functional>
enum class EntityType : uint8_t { enum class AIState {
INVALID, INACTIVE,
PLAYER, ROAMING,
SIMPLE_NPC, COMBAT,
COMBAT_NPC, RETREAT,
MOB, DEAD
EGG,
BUS
}; };
struct Entity { struct Entity {
EntityType type = EntityType::INVALID; EntityKind kind = EntityKind::INVALID;
int x = 0, y = 0, z = 0; int x = 0, y = 0, z = 0;
uint64_t instanceID = 0; uint64_t instanceID = 0;
ChunkPos chunkPos = {}; ChunkPos chunkPos = {};
@ -26,47 +29,38 @@ struct Entity {
// destructor must be virtual, apparently // destructor must be virtual, apparently
virtual ~Entity() {} virtual ~Entity() {}
virtual bool isAlive() { return true; } virtual bool isExtant() { return true; }
// stubs // stubs
virtual void enterIntoViewOf(CNSocket *sock) = 0; virtual void enterIntoViewOf(CNSocket *sock) = 0;
virtual void disappearFromViewOf(CNSocket *sock) = 0; virtual void disappearFromViewOf(CNSocket *sock) = 0;
}; };
struct EntityRef { /*
EntityType type; * Interfaces
union { */
CNSocket *sock; class ICombatant {
int32_t id; public:
}; ICombatant() {}
virtual ~ICombatant() {}
EntityRef(CNSocket *s); virtual bool addBuff(int, BuffCallback<int, BuffStack*>, BuffCallback<time_t>, BuffStack*) = 0;
EntityRef(int32_t i); virtual Buff* getBuff(int) = 0;
virtual void removeBuff(int) = 0;
bool isValid() const; virtual void removeBuff(int, int) = 0;
Entity *getEntity() const; virtual bool hasBuff(int) = 0;
virtual int getCompositeCondition() = 0;
bool operator==(const EntityRef& other) const { virtual int takeDamage(EntityRef, int) = 0;
if (type != other.type) virtual int heal(EntityRef, int) = 0;
return false; virtual bool isAlive() = 0;
virtual int getCurrentHP() = 0;
if (type == EntityType::PLAYER) virtual int getMaxHP() = 0;
return sock == other.sock; virtual int getLevel() = 0;
virtual std::vector<EntityRef> getGroupMembers() = 0;
return id == other.id; virtual int32_t getCharType() = 0;
} virtual int32_t getID() = 0;
virtual EntityRef getRef() = 0;
// arbitrary ordering virtual void step(time_t currTime) = 0;
bool operator<(const EntityRef& other) const {
if (type == other.type) {
if (type == EntityType::PLAYER)
return sock < other.sock;
else
return id < other.id;
}
return type < other.type;
}
}; };
/* /*
@ -74,75 +68,101 @@ struct EntityRef {
*/ */
class BaseNPC : public Entity { class BaseNPC : public Entity {
public: public:
sNPCAppearanceData appearanceData = {}; int id;
int type;
int hp;
int angle;
int cbf;
bool loopingPath = false; bool loopingPath = false;
BaseNPC(int _X, int _Y, int _Z, int angle, uint64_t iID, int t, int id) { // XXX BaseNPC(int _A, uint64_t iID, int t, int _id) {
x = _X; kind = EntityKind::SIMPLE_NPC;
y = _Y; type = t;
z = _Z; hp = 400;
appearanceData.iNPCType = t; angle = _A;
appearanceData.iHP = 400; cbf = 0;
appearanceData.iAngle = angle; id = _id;
appearanceData.iConditionBitFlag = 0;
appearanceData.iBarkerType = 0;
appearanceData.iNPC_ID = id;
instanceID = iID; instanceID = iID;
}; };
virtual void enterIntoViewOf(CNSocket *sock) override; virtual void enterIntoViewOf(CNSocket *sock) override;
virtual void disappearFromViewOf(CNSocket *sock) override; virtual void disappearFromViewOf(CNSocket *sock) override;
sNPCAppearanceData getAppearanceData();
}; };
struct CombatNPC : public BaseNPC { struct CombatNPC : public BaseNPC, public ICombatant {
int maxHealth = 0; int maxHealth = 0;
int spawnX = 0; int spawnX = 0;
int spawnY = 0; int spawnY = 0;
int spawnZ = 0; int spawnZ = 0;
int level = 0; int level = 0;
int speed = 300; int speed = 300;
AIState state = AIState::INACTIVE;
Group* group = nullptr;
int playersInView = 0; // for optimizing away AI in empty chunks
void (*_stepAI)(CombatNPC*, time_t) = nullptr; std::map<AIState, void (*)(CombatNPC*, time_t)> stateHandlers;
std::map<AIState, void (*)(CombatNPC*, EntityRef)> transitionHandlers;
// XXX CombatNPC(int x, int y, int z, int angle, uint64_t iID, int t, int id, int maxHP)
CombatNPC(int x, int y, int z, int angle, uint64_t iID, int t, int id, int maxHP) : : BaseNPC(angle, iID, t, id), maxHealth(maxHP) {
BaseNPC(x, y, z, angle, iID, t, id), spawnX = x;
maxHealth(maxHP) {} spawnY = y;
spawnZ = z;
virtual void stepAI(time_t currTime) { kind = EntityKind::COMBAT_NPC;
if (_stepAI != nullptr)
_stepAI(this, currTime); stateHandlers[AIState::INACTIVE] = {};
transitionHandlers[AIState::INACTIVE] = {};
} }
virtual bool isAlive() override { return appearanceData.iHP > 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 heal(EntityRef src, int amt) override;
virtual bool isAlive() 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 EntityRef getRef() override;
virtual void step(time_t currTime) override;
virtual void transition(AIState newState, EntityRef src);
}; };
// Mob is in MobAI.hpp, Player is in Player.hpp // Mob is in MobAI.hpp, Player is in Player.hpp
// TODO: decouple from BaseNPC
struct Egg : public BaseNPC { struct Egg : public BaseNPC {
bool summoned = false; bool summoned = false;
bool dead = false; bool dead = false;
time_t deadUntil; time_t deadUntil;
Egg(int x, int y, int z, uint64_t iID, int t, int32_t id, bool summon) Egg(uint64_t iID, int t, int32_t id, bool summon)
: BaseNPC(x, y, z, 0, iID, t, id) { : BaseNPC(0, iID, t, id) {
summoned = summon; summoned = summon;
type = EntityType::EGG; kind = EntityKind::EGG;
} }
virtual bool isAlive() override { return !dead; } virtual bool isExtant() override { return !dead; }
virtual void enterIntoViewOf(CNSocket *sock) override; virtual void enterIntoViewOf(CNSocket *sock) override;
virtual void disappearFromViewOf(CNSocket *sock) override; virtual void disappearFromViewOf(CNSocket *sock) override;
}; };
// TODO: decouple from BaseNPC
struct Bus : public BaseNPC { struct Bus : public BaseNPC {
Bus(int x, int y, int z, int angle, uint64_t iID, int t, int id) : Bus(int angle, uint64_t iID, int t, int id) :
BaseNPC(x, y, z, angle, iID, t, id) { BaseNPC(angle, iID, t, id) {
type = EntityType::BUS; kind = EntityKind::BUS;
loopingPath = true; loopingPath = true;
} }

56
src/EntityRef.hpp Normal file
View 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;
}
};

View File

@ -1,13 +1,10 @@
#include "servers/CNShardServer.hpp"
#include "PlayerManager.hpp"
#include "Groups.hpp" #include "Groups.hpp"
#include "Nanos.hpp"
#include "Abilities.hpp"
#include <iostream> #include "servers/CNShardServer.hpp"
#include <chrono>
#include <algorithm> #include "Player.hpp"
#include <thread> #include "PlayerManager.hpp"
#include "Entities.hpp"
/* /*
* NOTE: Variadic response packets that list group members are technically * NOTE: Variadic response packets that list group members are technically
@ -19,22 +16,177 @@
using namespace Groups; using namespace Groups;
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) {
Player* plr = PlayerManager::getPlayer(member.sock);
plr->group = group;
}
else if (member.kind == EntityKind::COMBAT_NPC) {
CombatNPC* npc = (CombatNPC*)member.getEntity();
npc->group = group;
}
else {
std::cout << "[WARN] Adding a weird entity type to a group" << std::endl;
}
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);
}
}
}
bool Groups::removeFromGroup(Group* group, EntityRef member) {
if (member.kind == EntityKind::PLAYER) {
Player* plr = PlayerManager::getPlayer(member.sock);
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) {
CombatNPC* npc = (CombatNPC*)member.getEntity();
npc->group = nullptr;
}
else {
std::cout << "[WARN] Removing a weird entity type from a group" << std::endl;
}
auto it = std::find(group->members.begin(), group->members.end(), member);
if (it == group->members.end()) {
std::cout << "[WARN] Tried to remove a member that isn't in the group" << std::endl;
} else {
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();
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) {
// remove everyone from the group!!
bool done = false;
while(!done) {
EntityRef back = group->members.back();
done = removeFromGroup(group, back);
}
}
static void requestGroup(CNSocket* sock, CNPacketData* data) { static void requestGroup(CNSocket* sock, CNPacketData* data) {
sP_CL2FE_REQ_PC_GROUP_INVITE* recv = (sP_CL2FE_REQ_PC_GROUP_INVITE*)data->buf; sP_CL2FE_REQ_PC_GROUP_INVITE* recv = (sP_CL2FE_REQ_PC_GROUP_INVITE*)data->buf;
Player* plr = PlayerManager::getPlayer(sock); Player* plr = PlayerManager::getPlayer(sock);
Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_To); Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_To);
if (otherPlr == nullptr)
return;
otherPlr = PlayerManager::getPlayerFromID(otherPlr->iIDGroup);
if (otherPlr == nullptr) if (otherPlr == nullptr)
return; return;
// fail if the group is full or the other player is already in a group // fail if the group is full or the other player is already in a group
if (plr->groupCnt >= 4 || otherPlr->iIDGroup != otherPlr->iID || otherPlr->groupCnt > 1) { if ((plr->group != nullptr && plr->group->filter(EntityKind::PLAYER).size() >= 4) || otherPlr->group != nullptr) {
INITSTRUCT(sP_FE2CL_PC_GROUP_INVITE_FAIL, resp); INITSTRUCT(sP_FE2CL_PC_GROUP_INVITE_FAIL, resp);
sock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_INVITE_FAIL, sizeof(sP_FE2CL_PC_GROUP_INVITE_FAIL)); sock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_INVITE_FAIL, sizeof(sP_FE2CL_PC_GROUP_INVITE_FAIL));
return; return;
@ -75,255 +227,80 @@ 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
otherPlr = PlayerManager::getPlayerFromID(otherPlr->iIDGroup); int size = otherPlr->group == nullptr ? 1 : otherPlr->group->filter(EntityKind::PLAYER).size();
if (otherPlr == nullptr)
return;
// fail if the group is full or the other player is already in a group // fail if the group is full or the other player is already in a group
if (plr->groupCnt > 1 || plr->iIDGroup != plr->iID || otherPlr->groupCnt >= 4) { if (plr->group != nullptr || size + 1 > 4) {
INITSTRUCT(sP_FE2CL_PC_GROUP_JOIN_FAIL, resp); INITSTRUCT(sP_FE2CL_PC_GROUP_JOIN_FAIL, resp);
sock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_JOIN_FAIL, sizeof(sP_FE2CL_PC_GROUP_JOIN_FAIL)); sock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_JOIN_FAIL, sizeof(sP_FE2CL_PC_GROUP_JOIN_FAIL));
return; return;
} }
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_JOIN), otherPlr->groupCnt + 1, sizeof(sPCGroupMemberInfo))) { if (otherPlr->group == nullptr) {
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_JOIN packet size\n"; // create group
return; EntityRef otherPlrRef = PlayerManager::getSockFromID(recv->iID_From);
otherPlr->group = new Group(otherPlrRef);
} }
addToGroup(otherPlr->group, sock);
plr->iIDGroup = otherPlr->iID;
otherPlr->groupCnt += 1;
otherPlr->groupIDs[otherPlr->groupCnt-1] = plr->iID;
size_t resplen = sizeof(sP_FE2CL_PC_GROUP_JOIN) + otherPlr->groupCnt * 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 = otherPlr->groupCnt;
int bitFlag = getGroupFlags(otherPlr);
for (int i = 0; i < otherPlr->groupCnt; i++) {
Player* varPlr = PlayerManager::getPlayerFromID(otherPlr->groupIDs[i]);
CNSocket* sockTo = PlayerManager::getSockFromID(otherPlr->groupIDs[i]);
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
if (Nanos::SkillTable[varPlr->Nanos[varPlr->activeNano].iSkillID].targetType == 3)
Nanos::applyBuff(sock, varPlr->Nanos[varPlr->activeNano].iSkillID, 1, 1, bitFlag);
if (Nanos::SkillTable[plr->Nanos[plr->activeNano].iSkillID].targetType == 3)
Nanos::applyBuff(sockTo, plr->Nanos[plr->activeNano].iSkillID, 1, 1, bitFlag);
}
}
sendToGroup(otherPlr, (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);
groupKickPlayer(plr); groupKick(plr->group, sock);
} }
void Groups::sendToGroup(Player* plr, void* buf, uint32_t type, size_t size) { void Groups::sendToGroup(Group* group, void* buf, uint32_t type, size_t size) {
for (int i = 0; i < plr->groupCnt; i++) { auto players = group->filter(EntityKind::PLAYER);
CNSocket* sock = PlayerManager::getSockFromID(plr->groupIDs[i]); for (EntityRef ref : players) {
ref.sock->sendPacket(buf, type, size);
if (sock == nullptr)
continue;
if (type == P_FE2CL_PC_GROUP_LEAVE_SUCC) {
Player* leavingPlr = PlayerManager::getPlayer(sock);
leavingPlr->iIDGroup = leavingPlr->iID;
}
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) {
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO), plr->groupCnt, sizeof(sPCGroupMemberInfo))) { auto players = group->filter(EntityKind::PLAYER);
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_JOIN packet size\n"; for (EntityRef ref : players) {
return; if(ref != excluded) ref.sock->sendPacket(buf, type, size);
} }
}
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) + plr->groupCnt * 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 = plr->groupCnt; uint8_t* pivot = (uint8_t*)(pkt + 1);
attachGroupData(pcs, npcs, pivot);
for (int i = 0; i < plr->groupCnt; i++) { sock->sendPacket(respbuf, P_FE2CL_PC_GROUP_MEMBER_INFO,
Player* varPlr = PlayerManager::getPlayerFromID(plr->groupIDs[i]); sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO) + pcCount * sizeof(sPCGroupMemberInfo) + npcCount * sizeof(sNPCGroupMemberInfo));
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, (void*)&respbuf, P_FE2CL_PC_GROUP_MEMBER_INFO, resplen);
}
static void groupUnbuff(Player* plr) {
for (int i = 0; i < plr->groupCnt; i++) {
for (int n = 0; n < plr->groupCnt; n++) {
if (i == n)
continue;
Player* otherPlr = PlayerManager::getPlayerFromID(plr->groupIDs[i]);
CNSocket* sock = PlayerManager::getSockFromID(plr->groupIDs[n]);
Nanos::applyBuff(sock, otherPlr->Nanos[otherPlr->activeNano].iSkillID, 2, 1, 0);
}
} }
} }
void Groups::groupKickPlayer(Player* plr) { void Groups::groupKick(Group* group, EntityRef ref) {
// 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->iID == plr->iIDGroup) { if (group->members[0] == ref) {
groupUnbuff(plr); disbandGroup(group);
INITSTRUCT(sP_FE2CL_PC_GROUP_LEAVE_SUCC, resp1);
sendToGroup(plr, (void*)&resp1, P_FE2CL_PC_GROUP_LEAVE_SUCC, sizeof(sP_FE2CL_PC_GROUP_LEAVE_SUCC));
plr->groupCnt = 1;
return; return;
} }
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup); removeFromGroup(group, ref);
if (otherPlr == nullptr)
return;
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_LEAVE), otherPlr->groupCnt - 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) + (otherPlr->groupCnt - 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 = otherPlr->groupCnt - 1;
int bitFlag = getGroupFlags(otherPlr) & ~plr->iGroupConditionBitFlag;
int moveDown = 0;
CNSocket* sock = PlayerManager::getSockFromID(plr->iID);
if (sock == nullptr)
return;
for (int i = 0; i < otherPlr->groupCnt; i++) {
Player* varPlr = PlayerManager::getPlayerFromID(otherPlr->groupIDs[i]);
CNSocket* sockTo = PlayerManager::getSockFromID(otherPlr->groupIDs[i]);
if (varPlr == nullptr || sockTo == nullptr)
continue;
if (moveDown == 1)
otherPlr->groupIDs[i-1] = otherPlr->groupIDs[i];
respdata[i-moveDown].iPC_ID = varPlr->iID;
respdata[i-moveDown].iPCUID = varPlr->PCStyle.iPC_UID;
respdata[i-moveDown].iNameCheck = varPlr->PCStyle.iNameCheck;
memcpy(respdata[i-moveDown].szFirstName, varPlr->PCStyle.szFirstName, sizeof(varPlr->PCStyle.szFirstName));
memcpy(respdata[i-moveDown].szLastName, varPlr->PCStyle.szLastName, sizeof(varPlr->PCStyle.szLastName));
respdata[i-moveDown].iSpecialState = varPlr->iSpecialState;
respdata[i-moveDown].iLv = varPlr->level;
respdata[i-moveDown].iHP = varPlr->HP;
respdata[i-moveDown].iMaxHP = PC_MAXHEALTH(varPlr->level);
// respdata[i-moveDown]].iMapType = 0;
// respdata[i-moveDown]].iMapNum = 0;
respdata[i-moveDown].iX = varPlr->x;
respdata[i-moveDown].iY = varPlr->y;
respdata[i-moveDown].iZ = varPlr->z;
// client doesnt read nano data here
if (varPlr == plr) {
moveDown = 1;
otherPlr->groupIDs[i] = 0;
} else { // remove the leaving member's buffs from the group and remove the group buffs from the leaving member.
if (Nanos::SkillTable[varPlr->Nanos[varPlr->activeNano].iSkillID].targetType == 3)
Nanos::applyBuff(sock, varPlr->Nanos[varPlr->activeNano].iSkillID, 2, 1, 0);
if (Nanos::SkillTable[plr->Nanos[varPlr->activeNano].iSkillID].targetType == 3)
Nanos::applyBuff(sockTo, plr->Nanos[plr->activeNano].iSkillID, 2, 1, bitFlag);
}
}
plr->iIDGroup = plr->iID;
otherPlr->groupCnt -= 1;
sendToGroup(otherPlr, (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));
}
int Groups::getGroupFlags(Player* plr) {
int bitFlag = 0;
for (int i = 0; i < plr->groupCnt; i++) {
Player* otherPlr = PlayerManager::getPlayerFromID(plr->groupIDs[i]);
if (otherPlr == nullptr)
continue;
bitFlag |= otherPlr->iGroupConditionBitFlag;
}
return bitFlag;
} }
void Groups::init() { void Groups::init() {

View File

@ -1,17 +1,32 @@
#pragma once #pragma once
#include "Player.hpp" #include "EntityRef.hpp"
#include "core/Core.hpp"
#include "servers/CNShardServer.hpp"
#include <map> #include <vector>
#include <list>
struct Group {
std::vector<EntityRef> members;
std::vector<EntityRef> filter(EntityKind kind) {
std::vector<EntityRef> filtered;
std::copy_if(members.begin(), members.end(), std::back_inserter(filtered), [kind](EntityRef e) {
return e.kind == kind;
});
return filtered;
}
Group(EntityRef leader);
};
namespace Groups { namespace Groups {
void init(); void init();
void sendToGroup(Player* plr, 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 groupKickPlayer(Player* plr); void groupTickInfo(CNSocket* sock);
int getGroupFlags(Player* plr);
void groupKick(Group* group, EntityRef ref);
void addToGroup(Group* group, EntityRef member);
bool removeFromGroup(Group* group, EntityRef member); // true iff group deleted
void disbandGroup(Group* group);
} }

View File

@ -1,16 +1,19 @@
#include "servers/CNShardServer.hpp"
#include "Items.hpp" #include "Items.hpp"
#include "servers/CNShardServer.hpp"
#include "Player.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "Nanos.hpp" #include "Nanos.hpp"
#include "NPCManager.hpp"
#include "Player.hpp"
#include "Abilities.hpp" #include "Abilities.hpp"
#include "Missions.hpp"
#include "Eggs.hpp" #include "Eggs.hpp"
#include "Rand.hpp" #include "MobAI.hpp"
#include "Missions.hpp"
#include "Buffs.hpp"
#include <string.h> // for memset() #include <string.h> // for memset()
#include <assert.h> #include <assert.h>
#include <numeric>
using namespace Items; using namespace Items;
@ -479,30 +482,34 @@ static void itemUseHandler(CNSocket* sock, CNPacketData* data) {
resp->iSlotNum = request->iSlotNum; resp->iSlotNum = request->iSlotNum;
resp->RemainItem = gumball; resp->RemainItem = gumball;
resp->iTargetCnt = 1; resp->iTargetCnt = 1;
resp->eST = EST_NANOSTIMPAK; resp->eST = (int32_t)SkillType::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)Nanos::SkillTable[144].durationTime[0] * 100;
Eggs::EggBuffs[key] = until;
} }
static void itemBankOpenHandler(CNSocket* sock, CNPacketData* data) { static void itemBankOpenHandler(CNSocket* sock, CNPacketData* data) {
@ -755,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;
@ -770,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;
@ -822,12 +829,12 @@ static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRo
void Items::giveMobDrop(CNSocket *sock, Mob* mob, const DropRoll& rolled, const DropRoll& eventRolled) { void Items::giveMobDrop(CNSocket *sock, Mob* mob, const DropRoll& rolled, const DropRoll& eventRolled) {
// sanity check // sanity check
if (Items::MobToDropMap.find(mob->appearanceData.iNPCType) == Items::MobToDropMap.end()) { if (Items::MobToDropMap.find(mob->type) == Items::MobToDropMap.end()) {
std::cout << "[WARN] Mob ID " << mob->appearanceData.iNPCType << " has no drops assigned" << std::endl; std::cout << "[WARN] Mob ID " << mob->type << " has no drops assigned" << std::endl;
return; return;
} }
// find mob drop id // find mob drop id
int mobDropId = Items::MobToDropMap[mob->appearanceData.iNPCType]; int mobDropId = Items::MobToDropMap[mob->type];
giveSingleDrop(sock, mob, mobDropId, rolled); giveSingleDrop(sock, mob, mobDropId, rolled);

View File

@ -1,9 +1,13 @@
#pragma once #pragma once
#include "servers/CNShardServer.hpp" #include "core/Core.hpp"
#include "Player.hpp" #include "Player.hpp"
#include "MobAI.hpp"
#include "Rand.hpp" #include "Rand.hpp"
#include "MobAI.hpp"
#include <vector>
#include <map>
struct CrocPotEntry { struct CrocPotEntry {
int multStats, multLooks; int multStats, multLooks;

View File

@ -1,11 +1,10 @@
#include "servers/CNShardServer.hpp"
#include "Missions.hpp" #include "Missions.hpp"
#include "PlayerManager.hpp"
#include "Nanos.hpp"
#include "Items.hpp"
#include "Transport.hpp"
#include "string.h" #include "servers/CNShardServer.hpp"
#include "PlayerManager.hpp"
#include "Items.hpp"
#include "Nanos.hpp"
using namespace Missions; using namespace Missions;
@ -164,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;
@ -367,15 +366,15 @@ static void taskStart(CNSocket* sock, CNPacketData* data) {
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_START_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_START_SUCC)); sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_START_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_START_SUCC));
// if escort task, assign matching paths to all nearby NPCs // if escort task, assign matching paths to all nearby NPCs
if (task["m_iHTaskType"] == 6) { if (task["m_iHTaskType"] == (int)eTaskTypeProperty::EscortDefence) {
for (ChunkPos& chunkPos : Chunking::getChunksInMap(plr->instanceID)) { // check all NPCs in the instance for (ChunkPos& chunkPos : Chunking::getChunksInMap(plr->instanceID)) { // check all NPCs in the instance
Chunk* chunk = Chunking::chunks[chunkPos]; Chunk* chunk = Chunking::chunks[chunkPos];
for (EntityRef ref : chunk->entities) { for (EntityRef ref : chunk->entities) {
if (ref.type != EntityType::PLAYER) { if (ref.kind != EntityKind::PLAYER) {
BaseNPC* npc = (BaseNPC*)ref.getEntity(); BaseNPC* npc = (BaseNPC*)ref.getEntity();
NPCPath* path = Transport::findApplicablePath(npc->appearanceData.iNPC_ID, npc->appearanceData.iNPCType, missionData->iTaskNum); NPCPath* path = Transport::findApplicablePath(npc->id, npc->type, missionData->iTaskNum);
if (path != nullptr) { if (path != nullptr) {
Transport::constructPathNPC(npc->appearanceData.iNPC_ID, path); Transport::constructPathNPC(npc->id, path);
return; return;
} }
} }
@ -399,7 +398,7 @@ static void taskEnd(CNSocket* sock, CNPacketData* data) {
* once we comb over mission logic more throughly * once we comb over mission logic more throughly
*/ */
bool mobsAreKilled = false; bool mobsAreKilled = false;
if (task->task["m_iHTaskType"] == 5) { if (task->task["m_iHTaskType"] == (int)eTaskTypeProperty::Defeat) {
mobsAreKilled = true; mobsAreKilled = true;
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) { for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
if (plr->tasks[i] == missionData->iTaskNum) { if (plr->tasks[i] == missionData->iTaskNum) {

View File

@ -1,9 +1,11 @@
#pragma once #pragma once
#include "servers/CNShardServer.hpp" #include "core/Core.hpp"
#include "JSON.hpp"
#include "Player.hpp" #include "Player.hpp"
#include "JSON.hpp" #include <map>
struct Reward { struct Reward {
int32_t id; int32_t id;

View File

@ -1,10 +1,15 @@
#include "MobAI.hpp" #include "MobAI.hpp"
#include "Player.hpp"
#include "Chunking.hpp"
#include "NPCManager.hpp"
#include "Entities.hpp"
#include "PlayerManager.hpp"
#include "Racing.hpp" #include "Racing.hpp"
#include "Transport.hpp"
#include "Nanos.hpp" #include "Nanos.hpp"
#include "Combat.hpp"
#include "Abilities.hpp" #include "Abilities.hpp"
#include "Combat.hpp"
#include "Items.hpp"
#include "Missions.hpp"
#include "Rand.hpp" #include "Rand.hpp"
#include <cmath> #include <cmath>
@ -14,7 +19,51 @@ using namespace MobAI;
bool MobAI::simulateMobs = settings::SIMULATEMOBS; bool MobAI::simulateMobs = settings::SIMULATEMOBS;
static void roamingStep(Mob *mob, time_t currTime); void Mob::step(time_t currTime) {
if (playersInView < 0)
std::cout << "[WARN] Weird playerview value " << playersInView << std::endl;
// skip movement and combat if disabled or not in view
if ((!MobAI::simulateMobs || playersInView == 0) && state != AIState::DEAD
&& state != AIState::RETREAT)
return;
// call superclass step
CombatNPC::step(currTime);
}
int Mob::takeDamage(EntityRef src, int amt) {
// cannot kill mobs multiple times; cannot harm retreating mobs
if (state != AIState::ROAMING && state != AIState::COMBAT) {
return 0; // no damage
}
if (skillStyle >= 0)
return 0; // don't hurt a mob casting corruption
if (state == AIState::ROAMING) {
assert(target == nullptr && src.kind == EntityKind::PLAYER); // TODO: players only for now
transition(AIState::COMBAT, src);
if (groupLeader != 0)
MobAI::followToCombat(this);
}
// wake up sleeping monster
if (cbf & CSB_BIT_MEZ) {
cbf &= ~CSB_BIT_MEZ;
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1);
pkt1.eCT = 2;
pkt1.iID = id;
pkt1.iConditionBitFlag = cbf;
NPCManager::sendToViewable(this, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
}
// call superclass takeDamage
return CombatNPC::takeDamage(src, amt);
}
/* /*
* Dynamic lerp; distinct from Transport::lerp(). This one doesn't care about height and * Dynamic lerp; distinct from Transport::lerp(). This one doesn't care about height and
@ -47,44 +96,44 @@ static std::pair<int,int> lerp(int x1, int y1, int x2, int y2, int speed) {
void MobAI::clearDebuff(Mob *mob) { void MobAI::clearDebuff(Mob *mob) {
mob->skillStyle = -1; mob->skillStyle = -1;
mob->appearanceData.iConditionBitFlag = 0; mob->cbf = 0;
mob->unbuffTimes.clear(); mob->unbuffTimes.clear();
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1); INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1);
pkt1.eCT = 2; pkt1.eCT = 2;
pkt1.iID = mob->appearanceData.iNPC_ID; pkt1.iID = mob->id;
pkt1.iConditionBitFlag = mob->appearanceData.iConditionBitFlag; pkt1.iConditionBitFlag = mob->cbf;
NPCManager::sendToViewable(mob, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT)); NPCManager::sendToViewable(mob, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
} }
void MobAI::followToCombat(Mob *mob) { void MobAI::followToCombat(Mob *mob) {
if (NPCManager::NPCs.find(mob->groupLeader) != NPCManager::NPCs.end() && NPCManager::NPCs[mob->groupLeader]->type == EntityType::MOB) { if (NPCManager::NPCs.find(mob->groupLeader) != NPCManager::NPCs.end() && NPCManager::NPCs[mob->groupLeader]->kind == EntityKind::MOB) {
Mob* leadMob = (Mob*)NPCManager::NPCs[mob->groupLeader]; Mob* leadMob = (Mob*)NPCManager::NPCs[mob->groupLeader];
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
if (leadMob->groupMember[i] == 0) if (leadMob->groupMember[i] == 0)
break; break;
if (NPCManager::NPCs.find(leadMob->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadMob->groupMember[i]]->type != EntityType::MOB) { if (NPCManager::NPCs.find(leadMob->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadMob->groupMember[i]]->kind != EntityKind::MOB) {
std::cout << "[WARN] roamingStep: leader can't find a group member!" << std::endl; std::cout << "[WARN] roamingStep: leader can't find a group member!" << std::endl;
continue; continue;
} }
Mob* followerMob = (Mob*)NPCManager::NPCs[leadMob->groupMember[i]]; Mob* followerMob = (Mob*)NPCManager::NPCs[leadMob->groupMember[i]];
if (followerMob->state != MobState::ROAMING) // only roaming mobs should transition to combat if (followerMob->state != AIState::ROAMING) // only roaming mobs should transition to combat
continue; continue;
enterCombat(mob->target, followerMob); followerMob->transition(AIState::COMBAT, mob->target);
} }
if (leadMob->state != MobState::ROAMING) if (leadMob->state != AIState::ROAMING)
return; return;
enterCombat(mob->target, leadMob); leadMob->transition(AIState::COMBAT, mob->target);
} }
} }
void MobAI::groupRetreat(Mob *mob) { void MobAI::groupRetreat(Mob *mob) {
if (NPCManager::NPCs.find(mob->groupLeader) == NPCManager::NPCs.end() || NPCManager::NPCs[mob->groupLeader]->type != EntityType::MOB) if (NPCManager::NPCs.find(mob->groupLeader) == NPCManager::NPCs.end() || NPCManager::NPCs[mob->groupLeader]->kind != EntityKind::MOB)
return; return;
Mob* leadMob = (Mob*)NPCManager::NPCs[mob->groupLeader]; Mob* leadMob = (Mob*)NPCManager::NPCs[mob->groupLeader];
@ -92,25 +141,25 @@ void MobAI::groupRetreat(Mob *mob) {
if (leadMob->groupMember[i] == 0) if (leadMob->groupMember[i] == 0)
break; break;
if (NPCManager::NPCs.find(leadMob->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadMob->groupMember[i]]->type != EntityType::MOB) { if (NPCManager::NPCs.find(leadMob->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadMob->groupMember[i]]->kind != EntityKind::MOB) {
std::cout << "[WARN] roamingStep: leader can't find a group member!" << std::endl; std::cout << "[WARN] roamingStep: leader can't find a group member!" << std::endl;
continue; continue;
} }
Mob* followerMob = (Mob*)NPCManager::NPCs[leadMob->groupMember[i]]; Mob* followerMob = (Mob*)NPCManager::NPCs[leadMob->groupMember[i]];
if (followerMob->state != MobState::COMBAT) if (followerMob->state != AIState::COMBAT)
continue; continue;
followerMob->target = nullptr; followerMob->target = nullptr;
followerMob->state = MobState::RETREAT; followerMob->state = AIState::RETREAT;
clearDebuff(followerMob); clearDebuff(followerMob);
} }
if (leadMob->state != MobState::COMBAT) if (leadMob->state != AIState::COMBAT)
return; return;
leadMob->target = nullptr; leadMob->target = nullptr;
leadMob->state = MobState::RETREAT; leadMob->state = AIState::RETREAT;
clearDebuff(leadMob); clearDebuff(leadMob);
} }
@ -127,7 +176,7 @@ bool MobAI::aggroCheck(Mob *mob, time_t currTime) {
Chunk* chunk = *it; Chunk* chunk = *it;
for (const EntityRef& ref : chunk->entities) { for (const EntityRef& ref : chunk->entities) {
// TODO: support targetting other CombatNPCs // TODO: support targetting other CombatNPCs
if (ref.type != EntityType::PLAYER) if (ref.kind != EntityKind::PLAYER)
continue; continue;
CNSocket *s = ref.sock; CNSocket *s = ref.sock;
@ -138,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;
@ -147,7 +196,7 @@ bool MobAI::aggroCheck(Mob *mob, time_t currTime) {
if (levelDifference > -10) if (levelDifference > -10)
mobRange = levelDifference < 10 ? mobRange - (levelDifference * mobRange / 15) : mobRange / 3; mobRange = levelDifference < 10 ? mobRange - (levelDifference * mobRange / 15) : mobRange / 3;
if (mob->state != MobState::ROAMING && plr->inCombat) // freshly out of aggro mobs if (mob->state != AIState::ROAMING && plr->inCombat) // freshly out of aggro mobs
mobRange = mob->sightRange * 2; // should not be impacted by the above mobRange = mob->sightRange * 2; // should not be impacted by the above
if (plr->iSpecialState & (CN_SPECIAL_STATE_FLAG__INVISIBLE|CN_SPECIAL_STATE_FLAG__INVULNERABLE)) if (plr->iSpecialState & (CN_SPECIAL_STATE_FLAG__INVISIBLE|CN_SPECIAL_STATE_FLAG__INVULNERABLE))
@ -168,7 +217,7 @@ bool MobAI::aggroCheck(Mob *mob, time_t currTime) {
if (closest != nullptr) { if (closest != nullptr) {
// found closest player. engage. // found closest player. engage.
enterCombat(closest, mob); mob->transition(AIState::COMBAT, closest);
if (mob->groupLeader != 0) if (mob->groupLeader != 0)
followToCombat(mob); followToCombat(mob);
@ -196,7 +245,7 @@ static void dealCorruption(Mob *mob, std::vector<int> targetData, int skillID, i
sP_FE2CL_NPC_SKILL_CORRUPTION_HIT *resp = (sP_FE2CL_NPC_SKILL_CORRUPTION_HIT*)respbuf; sP_FE2CL_NPC_SKILL_CORRUPTION_HIT *resp = (sP_FE2CL_NPC_SKILL_CORRUPTION_HIT*)respbuf;
sCAttackResult *respdata = (sCAttackResult*)(respbuf+sizeof(sP_FE2CL_NPC_SKILL_CORRUPTION_HIT)); sCAttackResult *respdata = (sCAttackResult*)(respbuf+sizeof(sP_FE2CL_NPC_SKILL_CORRUPTION_HIT));
resp->iNPC_ID = mob->appearanceData.iNPC_ID; resp->iNPC_ID = mob->id;
resp->iSkillID = skillID; resp->iSkillID = skillID;
resp->iStyle = style; resp->iStyle = style;
resp->iValue1 = plr->x; resp->iValue1 = plr->x;
@ -234,26 +283,27 @@ static void dealCorruption(Mob *mob, std::vector<int> targetData, int skillID, i
int style2 = Nanos::nanoStyle(plr->activeNano); int style2 = Nanos::nanoStyle(plr->activeNano);
if (style2 == -1) { // no nano if (style2 == -1) { // no nano
respdata[i].iHitFlag = 8; respdata[i].iHitFlag = HF_BIT_STYLE_TIE;
respdata[i].iDamage = Nanos::SkillTable[skillID].powerIntensity[0] * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500; respdata[i].iDamage = Abilities::SkillTable[skillID].values[0][0] * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500;
} else if (style == style2) { } else if (style == style2) {
respdata[i].iHitFlag = 8; // tie respdata[i].iHitFlag = HF_BIT_STYLE_TIE;
respdata[i].iDamage = 0; respdata[i].iDamage = 0;
respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina; respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina;
} else if (style - style2 == 1 || style2 - style == 2) { } else if (style - style2 == 1 || style2 - style == 2) {
respdata[i].iHitFlag = 4; // win respdata[i].iHitFlag = HF_BIT_STYLE_WIN;
respdata[i].iDamage = 0; respdata[i].iDamage = 0;
respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina += 45; respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina += 45;
if (plr->Nanos[plr->activeNano].iStamina > 150) if (plr->Nanos[plr->activeNano].iStamina > 150)
respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina = 150; respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina = 150;
// fire damage power disguised as a corruption attack back at the enemy // fire damage power disguised as a corruption attack back at the enemy
std::vector<int> targetData2 = {1, mob->appearanceData.iNPC_ID, 0, 0, 0}; // TODO ABILITIES
for (auto& pwr : Nanos::NanoPowers) /*std::vector<int> targetData2 = {1, mob->id, 0, 0, 0};
for (auto& pwr : Abilities::Powers)
if (pwr.skillType == EST_DAMAGE) if (pwr.skillType == EST_DAMAGE)
pwr.handle(sock, targetData2, plr->activeNano, skillID, 0, 200); pwr.handle(sock, targetData2, plr->activeNano, skillID, 0, 200);*/
} else { } else {
respdata[i].iHitFlag = 16; // lose respdata[i].iHitFlag = HF_BIT_STYLE_LOSE;
respdata[i].iDamage = Nanos::SkillTable[skillID].powerIntensity[0] * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500; respdata[i].iDamage = Abilities::SkillTable[skillID].values[0][0] * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500;
respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina -= 90; respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina -= 90;
if (plr->Nanos[plr->activeNano].iStamina < 0) { if (plr->Nanos[plr->activeNano].iStamina < 0) {
respdata[i].bNanoDeactive = 1; respdata[i].bNanoDeactive = 1;
@ -265,16 +315,11 @@ 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) {
mob->target = nullptr; if (!MobAI::aggroCheck(mob, getTime()))
mob->state = MobState::RETREAT; mob->transition(AIState::RETREAT, mob->target);
if (!aggroCheck(mob, getTime())) {
clearDebuff(mob);
if (mob->groupLeader != 0)
groupRetreat(mob);
}
} }
} }
@ -309,7 +354,7 @@ static void useAbilities(Mob *mob, time_t currTime) {
Chunk* chunk = *it; Chunk* chunk = *it;
for (const EntityRef& ref : chunk->entities) { for (const EntityRef& ref : chunk->entities) {
// TODO: see aggroCheck() // TODO: see aggroCheck()
if (ref.type != EntityType::PLAYER) if (ref.kind != EntityKind::PLAYER)
continue; continue;
CNSocket *s= ref.sock; CNSocket *s= ref.sock;
@ -319,7 +364,7 @@ static void useAbilities(Mob *mob, time_t currTime) {
continue; continue;
int distance = hypot(mob->hitX - plr->x, mob->hitY - plr->y); int distance = hypot(mob->hitX - plr->x, mob->hitY - plr->y);
if (distance < Nanos::SkillTable[skillID].effectArea) { if (distance < Abilities::SkillTable[skillID].effectArea) {
targetData[0] += 1; targetData[0] += 1;
targetData[targetData[0]] = plr->iID; targetData[targetData[0]] = plr->iID;
if (targetData[0] > 3) // make sure not to have more than 4 if (targetData[0] > 3) // make sure not to have more than 4
@ -328,9 +373,10 @@ static void useAbilities(Mob *mob, time_t currTime) {
} }
} }
for (auto& pwr : Combat::MobPowers) // TODO ABILITIES
if (pwr.skillType == Nanos::SkillTable[skillID].skillType) /*for (auto& pwr : Abilities::Powers)
pwr.handle(mob, targetData, skillID, Nanos::SkillTable[skillID].durationTime[0], Nanos::SkillTable[skillID].powerIntensity[0]); if (pwr.skillType == Abilities::SkillTable[skillID].skillType)
pwr.handle(mob->id, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]);*/
mob->skillStyle = -3; // eruption cooldown mob->skillStyle = -3; // eruption cooldown
mob->nextAttack = currTime + 1000; mob->nextAttack = currTime + 1000;
return; return;
@ -348,13 +394,14 @@ static void useAbilities(Mob *mob, time_t currTime) {
if (random < prob1) { // active skill hit if (random < prob1) { // active skill hit
int skillID = (int)mob->data["m_iActiveSkill1"]; int skillID = (int)mob->data["m_iActiveSkill1"];
std::vector<int> targetData = {1, plr->iID, 0, 0, 0}; // TODO ABILITIES
for (auto& pwr : Combat::MobPowers) //std::vector<int> targetData = {1, plr->iID, 0, 0, 0};
if (pwr.skillType == Nanos::SkillTable[skillID].skillType) { //for (auto& pwr : Abilities::Powers)
if (pwr.bitFlag != 0 && (plr->iConditionBitFlag & pwr.bitFlag)) // if (pwr.skillType == Abilities::SkillTable[skillID].skillType) {
return; // prevent debuffing a player twice // if (pwr.bitFlag != 0 && (plr->iConditionBitFlag & pwr.bitFlag))
pwr.handle(mob, targetData, skillID, Nanos::SkillTable[skillID].durationTime[0], Nanos::SkillTable[skillID].powerIntensity[0]); // return; // prevent debuffing a player twice
} // pwr.handle(mob->id, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]);
// }
mob->nextAttack = currTime + (int)mob->data["m_iDelayTime"] * 100; mob->nextAttack = currTime + (int)mob->data["m_iDelayTime"] * 100;
return; return;
} }
@ -362,7 +409,7 @@ static void useAbilities(Mob *mob, time_t currTime) {
if (random < prob1 + prob2) { // corruption windup if (random < prob1 + prob2) { // corruption windup
int skillID = (int)mob->data["m_iCorruptionType"]; int skillID = (int)mob->data["m_iCorruptionType"];
INITSTRUCT(sP_FE2CL_NPC_SKILL_CORRUPTION_READY, pkt); INITSTRUCT(sP_FE2CL_NPC_SKILL_CORRUPTION_READY, pkt);
pkt.iNPC_ID = mob->appearanceData.iNPC_ID; pkt.iNPC_ID = mob->id;
pkt.iSkillID = skillID; pkt.iSkillID = skillID;
pkt.iValue1 = plr->x; pkt.iValue1 = plr->x;
pkt.iValue2 = plr->y; pkt.iValue2 = plr->y;
@ -381,7 +428,7 @@ static void useAbilities(Mob *mob, time_t currTime) {
if (random < prob1 + prob2 + prob3) { // eruption windup if (random < prob1 + prob2 + prob3) { // eruption windup
int skillID = (int)mob->data["m_iMegaType"]; int skillID = (int)mob->data["m_iMegaType"];
INITSTRUCT(sP_FE2CL_NPC_SKILL_READY, pkt); INITSTRUCT(sP_FE2CL_NPC_SKILL_READY, pkt);
pkt.iNPC_ID = mob->appearanceData.iNPC_ID; pkt.iNPC_ID = mob->id;
pkt.iSkillID = skillID; pkt.iSkillID = skillID;
pkt.iValue1 = mob->hitX = plr->x; pkt.iValue1 = mob->hitX = plr->x;
pkt.iValue2 = mob->hitY = plr->y; pkt.iValue2 = mob->hitY = plr->y;
@ -395,27 +442,6 @@ static void useAbilities(Mob *mob, time_t currTime) {
return; return;
} }
void MobAI::enterCombat(CNSocket *sock, Mob *mob) {
mob->target = sock;
mob->state = MobState::COMBAT;
mob->nextMovement = getTime();
mob->nextAttack = 0;
mob->roamX = mob->x;
mob->roamY = mob->y;
mob->roamZ = mob->z;
int skillID = (int)mob->data["m_iPassiveBuff"]; // cast passive
std::vector<int> targetData = {1, mob->appearanceData.iNPC_ID, 0, 0, 0};
for (auto& pwr : Combat::MobPowers)
if (pwr.skillType == Nanos::SkillTable[skillID].skillType)
pwr.handle(mob, targetData, skillID, Nanos::SkillTable[skillID].durationTime[0], Nanos::SkillTable[skillID].powerIntensity[0]);
for (NPCEvent& event : NPCManager::NPCEvents) // trigger an ON_COMBAT
if (event.trigger == ON_COMBAT && event.npcType == mob->appearanceData.iNPCType)
event.handler(sock, mob);
}
static void drainMobHP(Mob *mob, int amount) { static void drainMobHP(Mob *mob, int amount) {
size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_Damage); size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_Damage);
assert(resplen < CN_PACKET_BUFFER_SIZE - 8); assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
@ -426,64 +452,73 @@ static void drainMobHP(Mob *mob, int amount) {
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_Damage *drain = (sSkillResult_Damage*)(respbuf + sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK)); sSkillResult_Damage *drain = (sSkillResult_Damage*)(respbuf + sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK));
pkt->iID = mob->appearanceData.iNPC_ID; pkt->iID = mob->id;
pkt->eCT = 4; // mob pkt->eCT = 4; // mob
pkt->iTB_ID = ECSB_BOUNDINGBALL; pkt->iTB_ID = ECSB_BOUNDINGBALL;
drain->eCT = 4; drain->eCT = 4;
drain->iID = mob->appearanceData.iNPC_ID; drain->iID = mob->id;
drain->iDamage = amount; drain->iDamage = amount;
drain->iHP = mob->appearanceData.iHP -= amount; drain->iHP = mob->hp -= amount;
NPCManager::sendToViewable(mob, (void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen); NPCManager::sendToViewable(mob, (void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen);
if (mob->appearanceData.iHP <= 0) if (mob->hp <= 0)
Combat::killMob(mob->target, mob); mob->transition(AIState::DEAD, mob->target);
} }
static void deadStep(Mob *mob, time_t currTime) { void MobAI::incNextMovement(Mob* mob, time_t currTime) {
if (currTime == 0)
currTime = getTime();
int delay = (int)mob->data["m_iDelayTime"] * 1000;
mob->nextMovement = currTime + delay / 2 + Rand::rand(delay / 2);
}
void MobAI::deadStep(CombatNPC* npc, time_t currTime) {
Mob* self = (Mob*)npc;
// despawn the mob after a short delay // despawn the mob after a short delay
if (mob->killedTime != 0 && !mob->despawned && currTime - mob->killedTime > 2000) { if (self->killedTime != 0 && !self->despawned && currTime - self->killedTime > 2000) {
mob->despawned = true; self->despawned = true;
INITSTRUCT(sP_FE2CL_NPC_EXIT, pkt); INITSTRUCT(sP_FE2CL_NPC_EXIT, pkt);
pkt.iNPC_ID = mob->appearanceData.iNPC_ID; pkt.iNPC_ID = self->id;
NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_EXIT, sizeof(sP_FE2CL_NPC_EXIT)); NPCManager::sendToViewable(self, &pkt, P_FE2CL_NPC_EXIT, sizeof(sP_FE2CL_NPC_EXIT));
// if it was summoned, mark it for removal // if it was summoned, mark it for removal
if (mob->summoned) { if (self->summoned) {
std::cout << "[INFO] Queueing killed summoned mob for removal" << std::endl; std::cout << "[INFO] Queueing killed summoned mob for removal" << std::endl;
NPCManager::queueNPCRemoval(mob->appearanceData.iNPC_ID); NPCManager::queueNPCRemoval(self->id);
return; return;
} }
// pre-set spawn coordinates if not marked for removal // pre-set spawn coordinates if not marked for removal
mob->x = mob->spawnX; self->x = self->spawnX;
mob->y = mob->spawnY; self->y = self->spawnY;
mob->z = mob->spawnZ; self->z = self->spawnZ;
} }
// to guide their groupmates, group leaders still need to move despite being dead // to guide their groupmates, group leaders still need to move despite being dead
if (mob->groupLeader == mob->appearanceData.iNPC_ID) if (self->groupLeader == self->id)
roamingStep(mob, currTime); roamingStep(self, currTime);
if (mob->killedTime != 0 && currTime - mob->killedTime < mob->regenTime * 100) if (self->killedTime != 0 && currTime - self->killedTime < self->regenTime * 100)
return; return;
std::cout << "respawning mob " << mob->appearanceData.iNPC_ID << " with HP = " << mob->maxHealth << std::endl; std::cout << "respawning mob " << self->id << " with HP = " << self->maxHealth << std::endl;
mob->appearanceData.iHP = mob->maxHealth; self->transition(AIState::ROAMING, self->id);
mob->state = MobState::ROAMING;
// if mob is a group leader/follower, spawn where the group is. // if mob is a group leader/follower, spawn where the group is.
if (mob->groupLeader != 0) { if (self->groupLeader != 0) {
if (NPCManager::NPCs.find(mob->groupLeader) != NPCManager::NPCs.end() && NPCManager::NPCs[mob->groupLeader]->type == EntityType::MOB) { if (NPCManager::NPCs.find(self->groupLeader) != NPCManager::NPCs.end() && NPCManager::NPCs[self->groupLeader]->kind == EntityKind::MOB) {
Mob* leaderMob = (Mob*)NPCManager::NPCs[mob->groupLeader]; Mob* leaderMob = (Mob*)NPCManager::NPCs[self->groupLeader];
mob->x = leaderMob->x + mob->offsetX; self->x = leaderMob->x + self->offsetX;
mob->y = leaderMob->y + mob->offsetY; self->y = leaderMob->y + self->offsetY;
mob->z = leaderMob->z; self->z = leaderMob->z;
} else { } else {
std::cout << "[WARN] deadStep: mob cannot find it's leader!" << std::endl; std::cout << "[WARN] deadStep: mob cannot find it's leader!" << std::endl;
} }
@ -491,180 +526,157 @@ static void deadStep(Mob *mob, time_t currTime) {
INITSTRUCT(sP_FE2CL_NPC_NEW, pkt); INITSTRUCT(sP_FE2CL_NPC_NEW, pkt);
pkt.NPCAppearanceData = mob->appearanceData; pkt.NPCAppearanceData = self->getAppearanceData();
pkt.NPCAppearanceData.iX = mob->x;
pkt.NPCAppearanceData.iY = mob->y;
pkt.NPCAppearanceData.iZ = mob->z;
// notify all nearby players // notify all nearby players
NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_NEW, sizeof(sP_FE2CL_NPC_NEW)); NPCManager::sendToViewable(self, &pkt, P_FE2CL_NPC_NEW, sizeof(sP_FE2CL_NPC_NEW));
} }
static void combatStep(Mob *mob, time_t currTime) { void MobAI::combatStep(CombatNPC* npc, time_t currTime) {
assert(mob->target != nullptr); Mob* self = (Mob*)npc;
assert(self->target != nullptr);
// lose aggro if the player lost connection // lose aggro if the player lost connection
if (PlayerManager::players.find(mob->target) == PlayerManager::players.end()) { if (PlayerManager::players.find(self->target) == PlayerManager::players.end()) {
mob->target = nullptr; if (!MobAI::aggroCheck(self, getTime()))
mob->state = MobState::RETREAT; self->transition(AIState::RETREAT, self->target);
if (!aggroCheck(mob, currTime)) {
clearDebuff(mob);
if (mob->groupLeader != 0)
groupRetreat(mob);
}
return; return;
} }
Player *plr = PlayerManager::getPlayer(mob->target); Player *plr = PlayerManager::getPlayer(self->target);
// lose aggro if the player became invulnerable or died // lose aggro if the player became invulnerable or died
if (plr->HP <= 0 if (plr->HP <= 0
|| (plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE)) { || (plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE)) {
mob->target = nullptr; if (!MobAI::aggroCheck(self, getTime()))
mob->state = MobState::RETREAT; self->transition(AIState::RETREAT, self->target);
if (!aggroCheck(mob, currTime)) {
clearDebuff(mob);
if (mob->groupLeader != 0)
groupRetreat(mob);
}
return; return;
} }
// drain // drain
if (mob->skillStyle < 0 && (mob->lastDrainTime == 0 || currTime - mob->lastDrainTime >= 1000) if (self->skillStyle < 0 && (self->lastDrainTime == 0 || currTime - self->lastDrainTime >= 1000)
&& mob->appearanceData.iConditionBitFlag & CSB_BIT_BOUNDINGBALL) { && self->cbf & CSB_BIT_BOUNDINGBALL) {
drainMobHP(mob, mob->maxHealth / 20); // lose 5% every second drainMobHP(self, self->maxHealth / 20); // lose 5% every second
mob->lastDrainTime = currTime; self->lastDrainTime = currTime;
} }
// if drain killed the mob, return early // if drain killed the mob, return early
if (mob->appearanceData.iHP <= 0) if (self->hp <= 0)
return; return;
// unbuffing // unbuffing
std::unordered_map<int32_t, time_t>::iterator it = mob->unbuffTimes.begin(); std::unordered_map<int32_t, time_t>::iterator it = self->unbuffTimes.begin();
while (it != mob->unbuffTimes.end()) { while (it != self->unbuffTimes.end()) {
if (currTime >= it->second) { if (currTime >= it->second) {
mob->appearanceData.iConditionBitFlag &= ~it->first; self->cbf &= ~it->first;
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1); INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1);
pkt1.eCT = 2; pkt1.eCT = 2;
pkt1.iID = mob->appearanceData.iNPC_ID; pkt1.iID = self->id;
pkt1.iConditionBitFlag = mob->appearanceData.iConditionBitFlag; pkt1.iConditionBitFlag = self->cbf;
NPCManager::sendToViewable(mob, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT)); NPCManager::sendToViewable(self, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
it = mob->unbuffTimes.erase(it); it = self->unbuffTimes.erase(it);
} else { } else {
it++; it++;
} }
} }
// skip attack if stunned or asleep // skip attack if stunned or asleep
if (mob->appearanceData.iConditionBitFlag & (CSB_BIT_STUN|CSB_BIT_MEZ)) { if (self->cbf & (CSB_BIT_STUN|CSB_BIT_MEZ)) {
mob->skillStyle = -1; // in this case we also reset the any outlying abilities the mob might be winding up. self->skillStyle = -1; // in this case we also reset the any outlying abilities the mob might be winding up.
return; return;
} }
int distance = hypot(plr->x - mob->x, plr->y - mob->y); int distance = hypot(plr->x - self->x, plr->y - self->y);
int mobRange = (int)mob->data["m_iAtkRange"] + (int)mob->data["m_iRadius"]; int mobRange = (int)self->data["m_iAtkRange"] + (int)self->data["m_iRadius"];
if (currTime >= mob->nextAttack) { if (currTime >= self->nextAttack) {
if (mob->skillStyle != -1 || distance <= mobRange || Rand::rand(20) == 0) // while not in attack range, 1 / 20 chance. if (self->skillStyle != -1 || distance <= mobRange || Rand::rand(20) == 0) // while not in attack range, 1 / 20 chance.
useAbilities(mob, currTime); useAbilities(self, currTime);
if (mob->target == nullptr) if (self->target == nullptr)
return; return;
} }
int distanceToTravel = INT_MAX; int distanceToTravel = INT_MAX;
int speed = mob->speed;
// movement logic: move when out of range but don't move while casting a skill // movement logic: move when out of range but don't move while casting a skill
if (distance > mobRange && mob->skillStyle == -1) { if (distance > mobRange && self->skillStyle == -1) {
if (mob->nextMovement != 0 && currTime < mob->nextMovement) if (self->nextMovement != 0 && currTime < self->nextMovement)
return; return;
mob->nextMovement = currTime + 400; self->nextMovement = currTime + 400;
if (currTime >= mob->nextAttack) if (currTime >= self->nextAttack)
mob->nextAttack = 0; self->nextAttack = 0;
// halve movement speed if snared // halve movement speed if snared
if (mob->appearanceData.iConditionBitFlag & CSB_BIT_DN_MOVE_SPEED) if (self->cbf & CSB_BIT_DN_MOVE_SPEED)
speed /= 2; self->speed /= 2;
int targetX = plr->x; int targetX = plr->x;
int targetY = plr->y; int targetY = plr->y;
if (mob->groupLeader != 0) { if (self->groupLeader != 0) {
targetX += mob->offsetX*distance/(mob->idleRange + 1); targetX += self->offsetX*distance/(self->idleRange + 1);
targetY += mob->offsetY*distance/(mob->idleRange + 1); targetY += self->offsetY*distance/(self->idleRange + 1);
} }
distanceToTravel = std::min(distance-mobRange+1, speed*2/5); distanceToTravel = std::min(distance-mobRange+1, self->speed*2/5);
auto targ = lerp(mob->x, mob->y, targetX, targetY, distanceToTravel); auto targ = lerp(self->x, self->y, targetX, targetY, distanceToTravel);
if (distanceToTravel < speed*2/5 && currTime >= mob->nextAttack) if (distanceToTravel < self->speed*2/5 && currTime >= self->nextAttack)
mob->nextAttack = 0; self->nextAttack = 0;
NPCManager::updateNPCPosition(mob->appearanceData.iNPC_ID, targ.first, targ.second, mob->z, mob->instanceID, mob->appearanceData.iAngle); NPCManager::updateNPCPosition(self->id, targ.first, targ.second, self->z, self->instanceID, self->angle);
INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt); INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt);
pkt.iNPC_ID = mob->appearanceData.iNPC_ID; pkt.iNPC_ID = self->id;
pkt.iSpeed = speed; pkt.iSpeed = self->speed;
pkt.iToX = mob->x = targ.first; pkt.iToX = self->x = targ.first;
pkt.iToY = mob->y = targ.second; pkt.iToY = self->y = targ.second;
pkt.iToZ = plr->z; pkt.iToZ = plr->z;
pkt.iMoveStyle = 1; pkt.iMoveStyle = 1;
// notify all nearby players // notify all nearby players
NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE)); NPCManager::sendToViewable(self, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE));
} }
/* attack logic /* attack logic
* 2/5 represents 400 ms which is the time interval mobs use per movement logic step * 2/5 represents 400 ms which is the time interval mobs use per movement logic step
* if the mob is one move interval away, we should just start attacking anyways. * if the mob is one move interval away, we should just start attacking anyways.
*/ */
if (distance <= mobRange || distanceToTravel < speed*2/5) { if (distance <= mobRange || distanceToTravel < self->speed*2/5) {
if (mob->nextAttack == 0 || currTime >= mob->nextAttack) { if (self->nextAttack == 0 || currTime >= self->nextAttack) {
mob->nextAttack = currTime + (int)mob->data["m_iDelayTime"] * 100; self->nextAttack = currTime + (int)self->data["m_iDelayTime"] * 100;
Combat::npcAttackPc(mob, currTime); Combat::npcAttackPc(self, currTime);
} }
} }
// retreat if the player leaves combat range // retreat if the player leaves combat range
int xyDistance = hypot(plr->x - mob->roamX, plr->y - mob->roamY); int xyDistance = hypot(plr->x - self->roamX, plr->y - self->roamY);
distance = hypot(xyDistance, plr->z - mob->roamZ); distance = hypot(xyDistance, plr->z - self->roamZ);
if (distance >= mob->data["m_iCombatRange"]) { if (distance >= self->data["m_iCombatRange"]) {
mob->target = nullptr; self->transition(AIState::RETREAT, self->target);
mob->state = MobState::RETREAT;
clearDebuff(mob);
if (mob->groupLeader != 0)
groupRetreat(mob);
} }
} }
void MobAI::incNextMovement(Mob *mob, time_t currTime) { void MobAI::roamingStep(CombatNPC* npc, time_t currTime) {
if (currTime == 0) Mob* self = (Mob*)npc;
currTime = getTime();
int delay = (int)mob->data["m_iDelayTime"] * 1000;
mob->nextMovement = currTime + delay/2 + Rand::rand(delay/2);
}
static void roamingStep(Mob *mob, time_t currTime) {
/* /*
* We reuse nextAttack to avoid scanning for players all the time, but to still * We reuse nextAttack to avoid scanning for players all the time, but to still
* do so more often than if we waited for nextMovement (which is way too slow). * do so more often than if we waited for nextMovement (which is way too slow).
* In the case of group leaders, this step will be called by dead mobs, so disable attack. * In the case of group leaders, this step will be called by dead mobs, so disable attack.
*/ */
if (mob->state != MobState::DEAD && (mob->nextAttack == 0 || currTime >= mob->nextAttack)) { if (self->state != AIState::DEAD && (self->nextAttack == 0 || currTime >= self->nextAttack)) {
mob->nextAttack = currTime + 500; self->nextAttack = currTime + 500;
if (aggroCheck(mob, currTime)) if (aggroCheck(self, currTime))
return; return;
} }
// no random roaming if the mob already has a set path // no random roaming if the mob already has a set path
if (mob->staticPath) if (self->staticPath)
return; return;
if (mob->groupLeader != 0 && mob->groupLeader != mob->appearanceData.iNPC_ID) // don't roam by yourself without group leader if (self->groupLeader != 0 && self->groupLeader != self->id) // don't roam by yourself without group leader
return; return;
/* /*
@ -672,142 +684,216 @@ static void roamingStep(Mob *mob, time_t currTime) {
* Transport::stepNPCPathing() (which ticks at a higher frequency than nextMovement), * Transport::stepNPCPathing() (which ticks at a higher frequency than nextMovement),
* so we don't have to check if there's already entries in the queue since we know there won't be. * so we don't have to check if there's already entries in the queue since we know there won't be.
*/ */
if (mob->nextMovement != 0 && currTime < mob->nextMovement) if (self->nextMovement != 0 && currTime < self->nextMovement)
return; return;
incNextMovement(mob, currTime); incNextMovement(self, currTime);
int xStart = mob->spawnX - mob->idleRange/2; int xStart = self->spawnX - self->idleRange/2;
int yStart = mob->spawnY - mob->idleRange/2; int yStart = self->spawnY - self->idleRange/2;
int speed = mob->speed;
// some mobs don't move (and we mustn't divide/modulus by zero) // some mobs don't move (and we mustn't divide/modulus by zero)
if (mob->idleRange == 0 || speed == 0) if (self->idleRange == 0 || self->speed == 0)
return; return;
int farX, farY, distance; int farX, farY, distance;
int minDistance = mob->idleRange / 2; int minDistance = self->idleRange / 2;
// pick a random destination // pick a random destination
farX = xStart + Rand::rand(mob->idleRange); farX = xStart + Rand::rand(self->idleRange);
farY = yStart + Rand::rand(mob->idleRange); farY = yStart + Rand::rand(self->idleRange);
distance = std::abs(std::max(farX - mob->x, farY - mob->y)); distance = std::abs(std::max(farX - self->x, farY - self->y));
if (distance == 0) if (distance == 0)
distance += 1; // hack to avoid FPE distance += 1; // hack to avoid FPE
// if it's too short a walk, go further in that direction // if it's too short a walk, go further in that direction
farX = mob->x + (farX - mob->x) * minDistance / distance; farX = self->x + (farX - self->x) * minDistance / distance;
farY = mob->y + (farY - mob->y) * minDistance / distance; farY = self->y + (farY - self->y) * minDistance / distance;
// but don't got out of bounds // but don't got out of bounds
farX = std::clamp(farX, xStart, xStart + mob->idleRange); farX = std::clamp(farX, xStart, xStart + self->idleRange);
farY = std::clamp(farY, yStart, yStart + mob->idleRange); farY = std::clamp(farY, yStart, yStart + self->idleRange);
// halve movement speed if snared // halve movement speed if snared
if (mob->appearanceData.iConditionBitFlag & CSB_BIT_DN_MOVE_SPEED) if (self->cbf & CSB_BIT_DN_MOVE_SPEED)
speed /= 2; self->speed /= 2;
std::queue<Vec3> queue; std::queue<Vec3> queue;
Vec3 from = { mob->x, mob->y, mob->z }; Vec3 from = { self->x, self->y, self->z };
Vec3 to = { farX, farY, mob->z }; Vec3 to = { farX, farY, self->z };
// add a route to the queue; to be processed in Transport::stepNPCPathing() // add a route to the queue; to be processed in Transport::stepNPCPathing()
Transport::lerp(&queue, from, to, speed); Transport::lerp(&queue, from, to, self->speed);
Transport::NPCQueues[mob->appearanceData.iNPC_ID] = queue; Transport::NPCQueues[self->id] = queue;
if (mob->groupLeader != 0 && mob->groupLeader == mob->appearanceData.iNPC_ID) { if (self->groupLeader != 0 && self->groupLeader == self->id) {
// make followers follow this npc. // make followers follow this npc.
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
if (mob->groupMember[i] == 0) if (self->groupMember[i] == 0)
break; break;
if (NPCManager::NPCs.find(mob->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[mob->groupMember[i]]->type != EntityType::MOB) { if (NPCManager::NPCs.find(self->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[self->groupMember[i]]->kind != EntityKind::MOB) {
std::cout << "[WARN] roamingStep: leader can't find a group member!" << std::endl; std::cout << "[WARN] roamingStep: leader can't find a group member!" << std::endl;
continue; continue;
} }
std::queue<Vec3> queue2; std::queue<Vec3> queue2;
Mob* followerMob = (Mob*)NPCManager::NPCs[mob->groupMember[i]]; Mob* followerMob = (Mob*)NPCManager::NPCs[self->groupMember[i]];
from = { followerMob->x, followerMob->y, followerMob->z }; from = { followerMob->x, followerMob->y, followerMob->z };
to = { farX + followerMob->offsetX, farY + followerMob->offsetY, followerMob->z }; to = { farX + followerMob->offsetX, farY + followerMob->offsetY, followerMob->z };
Transport::lerp(&queue2, from, to, speed); Transport::lerp(&queue2, from, to, self->speed);
Transport::NPCQueues[followerMob->appearanceData.iNPC_ID] = queue2; Transport::NPCQueues[followerMob->id] = queue2;
} }
} }
} }
static void retreatStep(Mob *mob, time_t currTime) { void MobAI::retreatStep(CombatNPC* npc, time_t currTime) {
if (mob->nextMovement != 0 && currTime < mob->nextMovement) Mob* self = (Mob*)npc;
if (self->nextMovement != 0 && currTime < self->nextMovement)
return; return;
mob->nextMovement = currTime + 400; self->nextMovement = currTime + 400;
// distance between spawn point and current location // distance between spawn point and current location
int distance = hypot(mob->x - mob->roamX, mob->y - mob->roamY); int distance = hypot(self->x - self->roamX, self->y - self->roamY);
//if (distance > mob->data["m_iIdleRange"]) { //if (distance > mob->data["m_iIdleRange"]) {
if (distance > 10) { if (distance > 10) {
INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt); INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt);
auto targ = lerp(mob->x, mob->y, mob->roamX, mob->roamY, (int)mob->speed*4/5); auto targ = lerp(self->x, self->y, self->roamX, self->roamY, (int)self->speed*4/5);
pkt.iNPC_ID = mob->appearanceData.iNPC_ID; pkt.iNPC_ID = self->id;
pkt.iSpeed = (int)mob->speed * 2; pkt.iSpeed = (int)self->speed * 2;
pkt.iToX = mob->x = targ.first; pkt.iToX = self->x = targ.first;
pkt.iToY = mob->y = targ.second; pkt.iToY = self->y = targ.second;
pkt.iToZ = mob->z = mob->spawnZ; pkt.iToZ = self->z = self->spawnZ;
pkt.iMoveStyle = 1; pkt.iMoveStyle = 1;
// notify all nearby players // notify all nearby players
NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE)); NPCManager::sendToViewable(self, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE));
} }
// if we got there // if we got there
//if (distance <= mob->data["m_iIdleRange"]) { //if (distance <= mob->data["m_iIdleRange"]) {
if (distance <= 10) { // retreat back to the spawn point if (distance <= 10) { // retreat back to the spawn point
mob->state = MobState::ROAMING; self->transition(AIState::ROAMING, self->id);
mob->appearanceData.iHP = mob->maxHealth;
mob->killedTime = 0;
mob->nextAttack = 0;
mob->appearanceData.iConditionBitFlag = 0;
// cast a return home heal spell, this is the right way(tm)
std::vector<int> targetData = {1, 0, 0, 0, 0};
for (auto& pwr : Combat::MobPowers)
if (pwr.skillType == Nanos::SkillTable[110].skillType)
pwr.handle(mob, targetData, 110, Nanos::SkillTable[110].durationTime[0], Nanos::SkillTable[110].powerIntensity[0]);
// clear outlying debuffs
clearDebuff(mob);
} }
} }
void MobAI::step(CombatNPC *npc, time_t currTime) { void MobAI::onRoamStart(CombatNPC* npc, EntityRef src) {
assert(npc->type == EntityType::MOB); Mob* self = (Mob*)npc;
auto mob = (Mob*)npc;
if (mob->playersInView < 0) self->hp = self->maxHealth;
std::cout << "[WARN] Weird playerview value " << mob->playersInView << std::endl; self->killedTime = 0;
self->nextAttack = 0;
self->cbf = 0;
// skip mob movement and combat if disabled or not in view // cast a return home heal spell, this is the right way(tm)
if ((!simulateMobs || mob->playersInView == 0) && mob->state != MobState::DEAD // TODO ABILITIES
&& mob->state != MobState::RETREAT) /*std::vector<int> targetData = { 1, 0, 0, 0, 0 };
for (auto& pwr : Abilities::Powers)
if (pwr.skillType == Abilities::SkillTable[110].skillType)
pwr.handle(self->id, targetData, 110, Abilities::SkillTable[110].durationTime[0], Abilities::SkillTable[110].powerIntensity[0]);*/
// clear outlying debuffs
clearDebuff(self);
}
void MobAI::onCombatStart(CombatNPC* npc, EntityRef src) {
Mob* self = (Mob*)npc;
assert(src.kind == EntityKind::PLAYER);
self->target = src.sock;
self->nextMovement = getTime();
self->nextAttack = 0;
self->roamX = self->x;
self->roamY = self->y;
self->roamZ = self->z;
int skillID = (int)self->data["m_iPassiveBuff"]; // cast passive
// TODO ABILITIES
/*std::vector<int> targetData = { 1, self->id, 0, 0, 0 };
for (auto& pwr : Abilities::Powers)
if (pwr.skillType == Abilities::SkillTable[skillID].skillType)
pwr.handle(self->id, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]);*/
}
void MobAI::onRetreat(CombatNPC* npc, EntityRef src) {
Mob* self = (Mob*)npc;
self->target = nullptr;
MobAI::clearDebuff(self);
if (self->groupLeader != 0)
MobAI::groupRetreat(self);
}
void MobAI::onDeath(CombatNPC* npc, EntityRef src) {
Mob* self = (Mob*)npc;
self->target = nullptr;
self->cbf = 0;
self->skillStyle = -1;
self->unbuffTimes.clear();
self->killedTime = getTime(); // XXX: maybe introduce a shard-global time for each step?
// check for the edge case where hitting the mob did not aggro it
if (src.kind == EntityKind::PLAYER && src.isValid()) {
Player* plr = PlayerManager::getPlayer(src.sock);
Items::DropRoll rolled;
Items::DropRoll eventRolled;
std::map<int, int> qitemRolls;
std::vector<Player*> playerRefs;
if (plr->group == nullptr) {
playerRefs.push_back(plr);
Combat::genQItemRolls(playerRefs, qitemRolls);
Items::giveMobDrop(src.sock, self, rolled, eventRolled);
Missions::mobKilled(src.sock, self->type, qitemRolls);
}
else {
auto players = plr->group->filter(EntityKind::PLAYER);
for (EntityRef pRef : players) playerRefs.push_back(PlayerManager::getPlayer(pRef.sock));
Combat::genQItemRolls(playerRefs, qitemRolls);
for (int i = 0; i < players.size(); i++) {
CNSocket* sockTo = players[i].sock;
Player* otherPlr = PlayerManager::getPlayer(sockTo);
// only contribute to group members' kills if they're close enough
int dist = std::hypot(plr->x - otherPlr->x + 1, plr->y - otherPlr->y + 1);
if (dist > 5000)
continue;
Items::giveMobDrop(sockTo, self, rolled, eventRolled);
Missions::mobKilled(sockTo, self->type, qitemRolls);
}
}
}
// delay the despawn animation
self->despawned = false;
auto it = Transport::NPCQueues.find(self->id);
if (it == Transport::NPCQueues.end() || it->second.empty())
return; return;
switch (mob->state) { // rewind or empty the movement queue
case MobState::INACTIVE: if (self->staticPath) {
// no-op /*
break; * This is inelegant, but we wind forward in the path until we find the point that
case MobState::ROAMING: * corresponds with the Mob's spawn point.
roamingStep(mob, currTime); *
break; * IMPORTANT: The check in TableData::loadPaths() must pass or else this will loop forever.
case MobState::COMBAT: */
combatStep(mob, currTime); auto& queue = it->second;
break; for (auto point = queue.front(); point.x != self->spawnX || point.y != self->spawnY; point = queue.front()) {
case MobState::RETREAT: queue.pop();
retreatStep(mob, currTime); queue.push(point);
break; }
case MobState::DEAD: }
deadStep(mob, currTime); else {
break; Transport::NPCQueues.erase(self->id);
} }
} }

View File

@ -1,25 +1,27 @@
#pragma once #pragma once
#include "core/Core.hpp" #include "core/Core.hpp"
#include "NPCManager.hpp" #include "JSON.hpp"
enum class MobState { #include "Entities.hpp"
INACTIVE,
ROAMING, #include <unordered_map>
COMBAT, #include <string>
RETREAT,
DEAD
};
namespace MobAI { namespace MobAI {
// needs to be declared before Mob's constructor void deadStep(CombatNPC* self, time_t currTime);
void step(CombatNPC*, time_t); void combatStep(CombatNPC* self, time_t currTime);
}; void roamingStep(CombatNPC* self, time_t currTime);
void retreatStep(CombatNPC* self, time_t currTime);
void onRoamStart(CombatNPC* self, EntityRef src);
void onCombatStart(CombatNPC* self, EntityRef src);
void onRetreat(CombatNPC* self, EntityRef src);
void onDeath(CombatNPC* self, EntityRef src);
}
struct Mob : public CombatNPC { struct Mob : public CombatNPC {
// general // general
MobState state = MobState::INACTIVE;
std::unordered_map<int32_t,time_t> unbuffTimes = {}; std::unordered_map<int32_t,time_t> unbuffTimes = {};
// dead // dead
@ -47,16 +49,13 @@ struct Mob : public CombatNPC {
int offsetX = 0, offsetY = 0; int offsetX = 0, offsetY = 0;
int groupMember[4] = {}; int groupMember[4] = {};
// for optimizing away AI in empty chunks
int playersInView = 0;
// temporary; until we're sure what's what // temporary; until we're sure what's what
nlohmann::json data = {}; nlohmann::json data = {};
Mob(int x, int y, int z, int angle, uint64_t iID, int t, nlohmann::json d, int32_t id) Mob(int x, int y, int z, int angle, uint64_t iID, int t, nlohmann::json d, int32_t id)
: CombatNPC(x, y, z, angle, iID, t, id, d["m_iHP"]), : CombatNPC(x, y, z, angle, iID, t, id, d["m_iHP"]),
sightRange(d["m_iSightRange"]) { sightRange(d["m_iSightRange"]) {
state = MobState::ROAMING; state = AIState::ROAMING;
data = d; data = d;
@ -65,20 +64,30 @@ struct Mob : public CombatNPC {
idleRange = (int)data["m_iIdleRange"]; idleRange = (int)data["m_iIdleRange"];
level = data["m_iNpcLevel"]; level = data["m_iNpcLevel"];
roamX = spawnX = x; roamX = x;
roamY = spawnY = y; roamY = y;
roamZ = spawnZ = z; roamZ = z;
offsetX = 0; offsetX = 0;
offsetY = 0; offsetY = 0;
appearanceData.iConditionBitFlag = 0; cbf = 0;
// NOTE: there appear to be discrepancies in the dump // NOTE: there appear to be discrepancies in the dump
appearanceData.iHP = maxHealth; hp = maxHealth;
type = EntityType::MOB; kind = EntityKind::MOB;
_stepAI = MobAI::step;
// AI
stateHandlers[AIState::DEAD] = MobAI::deadStep;
stateHandlers[AIState::COMBAT] = MobAI::combatStep;
stateHandlers[AIState::ROAMING] = MobAI::roamingStep;
stateHandlers[AIState::RETREAT] = MobAI::retreatStep;
transitionHandlers[AIState::DEAD] = MobAI::onDeath;
transitionHandlers[AIState::COMBAT] = MobAI::onCombatStart;
transitionHandlers[AIState::ROAMING] = MobAI::onRoamStart;
transitionHandlers[AIState::RETREAT] = MobAI::onRetreat;
} }
// constructor for /summon // constructor for /summon
@ -89,6 +98,9 @@ struct Mob : public CombatNPC {
~Mob() {} ~Mob() {}
virtual int takeDamage(EntityRef src, int amt) override;
virtual void step(time_t currTime) override;
auto operator[](std::string s) { auto operator[](std::string s) {
return data[s]; return data[s];
} }
@ -103,5 +115,4 @@ namespace MobAI {
void clearDebuff(Mob *mob); void clearDebuff(Mob *mob);
void followToCombat(Mob *mob); void followToCombat(Mob *mob);
void groupRetreat(Mob *mob); void groupRetreat(Mob *mob);
void enterCombat(CNSocket *sock, Mob *mob);
} }

View File

@ -1,4 +0,0 @@
#pragma once
#include "Chunking.hpp"
#include "Entities.hpp"

View File

@ -1,4 +1,8 @@
#include "NPCManager.hpp" #include "NPCManager.hpp"
#include "servers/CNShardServer.hpp"
#include "PlayerManager.hpp"
#include "Items.hpp" #include "Items.hpp"
#include "settings.hpp" #include "settings.hpp"
#include "Combat.hpp" #include "Combat.hpp"
@ -20,8 +24,6 @@
#include <assert.h> #include <assert.h>
#include <limits.h> #include <limits.h>
#include "JSON.hpp"
using namespace NPCManager; using namespace NPCManager;
std::unordered_map<int32_t, BaseNPC*> NPCManager::NPCs; std::unordered_map<int32_t, BaseNPC*> NPCManager::NPCs;
@ -67,7 +69,7 @@ void NPCManager::destroyNPC(int32_t id) {
void NPCManager::updateNPCPosition(int32_t id, int X, int Y, int Z, uint64_t I, int angle) { void NPCManager::updateNPCPosition(int32_t id, int X, int Y, int Z, uint64_t I, int angle) {
BaseNPC* npc = NPCs[id]; BaseNPC* npc = NPCs[id];
npc->appearanceData.iAngle = angle; npc->angle = angle;
ChunkPos oldChunk = npc->chunkPos; ChunkPos oldChunk = npc->chunkPos;
ChunkPos newChunk = Chunking::chunkPosAt(X, Y, I); ChunkPos newChunk = Chunking::chunkPosAt(X, Y, I);
npc->x = X; npc->x = X;
@ -79,11 +81,11 @@ 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) {
if (ref.type == EntityType::PLAYER) if (ref.kind == EntityKind::PLAYER)
ref.sock->sendPacket(buf, type, size); ref.sock->sendPacket(buf, type, size);
} }
} }
@ -122,7 +124,6 @@ static void npcUnsummonHandler(CNSocket* sock, CNPacketData* data) {
// type must already be checked and updateNPCPosition() must be called on the result // type must already be checked and updateNPCPosition() must be called on the result
BaseNPC *NPCManager::summonNPC(int x, int y, int z, uint64_t instance, int type, bool respawn, bool baseInstance) { BaseNPC *NPCManager::summonNPC(int x, int y, int z, uint64_t instance, int type, bool respawn, bool baseInstance) {
uint64_t inst = baseInstance ? MAPNUM(instance) : instance; uint64_t inst = baseInstance ? MAPNUM(instance) : instance;
#define EXTRA_HEIGHT 0
//assert(nextId < INT32_MAX); //assert(nextId < INT32_MAX);
int id = nextId--; int id = nextId--;
@ -130,12 +131,12 @@ BaseNPC *NPCManager::summonNPC(int x, int y, int z, uint64_t instance, int type,
BaseNPC *npc = nullptr; BaseNPC *npc = nullptr;
if (team == 2) { if (team == 2) {
npc = new Mob(x, y, z + EXTRA_HEIGHT, inst, type, NPCData[type], id); npc = new Mob(x, y, z, inst, type, NPCData[type], id);
// re-enable respawning, if desired // re-enable respawning, if desired
((Mob*)npc)->summoned = !respawn; ((Mob*)npc)->summoned = !respawn;
} else } else
npc = new BaseNPC(x, y, z + EXTRA_HEIGHT, 0, inst, type, id); npc = new BaseNPC(0, inst, type, id);
NPCs[id] = npc; NPCs[id] = npc;
@ -154,7 +155,7 @@ static void npcSummonHandler(CNSocket* sock, CNPacketData* data) {
for (int i = 0; i < req->iNPCCnt; i++) { for (int i = 0; i < req->iNPCCnt; i++) {
BaseNPC *npc = summonNPC(plr->x, plr->y, plr->z, plr->instanceID, req->iNPCType); BaseNPC *npc = summonNPC(plr->x, plr->y, plr->z, plr->instanceID, req->iNPCType);
updateNPCPosition(npc->appearanceData.iNPC_ID, plr->x, plr->y, plr->z, plr->instanceID, 0); updateNPCPosition(npc->id, plr->x, plr->y, plr->z, plr->instanceID, 0);
} }
} }
@ -183,9 +184,12 @@ static void handleWarp(CNSocket* sock, int32_t warpId) {
if (Warps[warpId].isInstance) { if (Warps[warpId].isInstance) {
uint64_t instanceID = Warps[warpId].instanceID; uint64_t instanceID = Warps[warpId].instanceID;
Player* leader = plr;
if (plr->group != nullptr) leader = PlayerManager::getPlayer(plr->group->filter(EntityKind::PLAYER)[0].sock);
// if warp requires you to be on a mission, it's gotta be a unique instance // if warp requires you to be on a mission, it's gotta be a unique instance
if (Warps[warpId].limitTaskID != 0 || instanceID == 14) { // 14 is a special case for the Time Lab if (Warps[warpId].limitTaskID != 0 || instanceID == 14) { // 14 is a special case for the Time Lab
instanceID += ((uint64_t)plr->iIDGroup << 32); // upper 32 bits are leader ID instanceID += ((uint64_t)leader->iID << 32); // upper 32 bits are leader ID
Chunking::createInstance(instanceID); Chunking::createInstance(instanceID);
// save Lair entrance coords as a pseudo-Resurrect 'Em // save Lair entrance coords as a pseudo-Resurrect 'Em
@ -195,14 +199,13 @@ static void handleWarp(CNSocket* sock, int32_t warpId) {
plr->recallInstance = instanceID; plr->recallInstance = instanceID;
} }
if (plr->iID == plr->iIDGroup && plr->groupCnt == 1) if (plr->group == nullptr)
PlayerManager::sendPlayerTo(sock, Warps[warpId].x, Warps[warpId].y, Warps[warpId].z, instanceID); PlayerManager::sendPlayerTo(sock, Warps[warpId].x, Warps[warpId].y, Warps[warpId].z, instanceID);
else { else {
Player* leaderPlr = PlayerManager::getPlayerFromID(plr->iIDGroup); auto players = plr->group->filter(EntityKind::PLAYER);
for (int i = 0; i < players.size(); i++) {
for (int i = 0; i < leaderPlr->groupCnt; i++) { CNSocket* sockTo = players[i].sock;
Player* otherPlr = PlayerManager::getPlayerFromID(leaderPlr->groupIDs[i]); Player* otherPlr = PlayerManager::getPlayer(sockTo);
CNSocket* sockTo = PlayerManager::getSockFromID(leaderPlr->groupIDs[i]);
if (otherPlr == nullptr || sockTo == nullptr) if (otherPlr == nullptr || sockTo == nullptr)
continue; continue;
@ -276,7 +279,7 @@ BaseNPC* NPCManager::getNearestNPC(std::set<Chunk*>* chunks, int X, int Y, int Z
for (auto c = chunks->begin(); c != chunks->end(); c++) { // haha get it for (auto c = chunks->begin(); c != chunks->end(); c++) { // haha get it
Chunk* chunk = *c; Chunk* chunk = *c;
for (auto ent = chunk->entities.begin(); ent != chunk->entities.end(); ent++) { for (auto ent = chunk->entities.begin(); ent != chunk->entities.end(); ent++) {
if (ent->type == EntityType::PLAYER) if (ent->kind == EntityKind::PLAYER)
continue; continue;
BaseNPC* npcTemp = (BaseNPC*)ent->getEntity(); BaseNPC* npcTemp = (BaseNPC*)ent->getEntity();
@ -301,20 +304,20 @@ static void lordFuseStageTwo(CNSocket *sock, BaseNPC *npc) {
std::cout << "Lord Fuse stage two" << std::endl; std::cout << "Lord Fuse stage two" << std::endl;
// Fuse doesn't move; spawnX, etc. is shorter to write than *appearanceData* // Fuse doesn't move
// Blastons, Heal // Blastons, Heal
Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->spawnX, oldbody->spawnY, oldbody->spawnZ, plr->instanceID, 2467); Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->x, oldbody->y, oldbody->z, plr->instanceID, 2467);
newbody->appearanceData.iAngle = oldbody->appearanceData.iAngle; newbody->angle = oldbody->angle;
NPCManager::updateNPCPosition(newbody->appearanceData.iNPC_ID, newbody->spawnX, newbody->spawnY, newbody->spawnZ, NPCManager::updateNPCPosition(newbody->id, newbody->x, newbody->y, newbody->z,
plr->instanceID, oldbody->appearanceData.iAngle); plr->instanceID, oldbody->angle);
// right arm, Adaptium, Stun // right arm, Adaptium, Stun
Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->spawnX - 600, oldbody->spawnY, oldbody->spawnZ, plr->instanceID, 2469); Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->x - 600, oldbody->y, oldbody->z, plr->instanceID, 2469);
arm->appearanceData.iAngle = oldbody->appearanceData.iAngle; arm->angle = oldbody->angle;
NPCManager::updateNPCPosition(arm->appearanceData.iNPC_ID, arm->spawnX, arm->spawnY, arm->spawnZ, NPCManager::updateNPCPosition(arm->id, arm->x, arm->y, arm->z,
plr->instanceID, oldbody->appearanceData.iAngle); plr->instanceID, oldbody->angle);
} }
// summon left arm and stage 3 body // summon left arm and stage 3 body
@ -325,18 +328,18 @@ static void lordFuseStageThree(CNSocket *sock, BaseNPC *npc) {
std::cout << "Lord Fuse stage three" << std::endl; std::cout << "Lord Fuse stage three" << std::endl;
// Cosmix, Damage Point // Cosmix, Damage Point
Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->spawnX, oldbody->spawnY, oldbody->spawnZ, plr->instanceID, 2468); Mob *newbody = (Mob*)NPCManager::summonNPC(oldbody->x, oldbody->y, oldbody->z, plr->instanceID, 2468);
newbody->appearanceData.iAngle = oldbody->appearanceData.iAngle; newbody->angle = oldbody->angle;
NPCManager::updateNPCPosition(newbody->appearanceData.iNPC_ID, newbody->spawnX, newbody->spawnY, newbody->spawnZ, NPCManager::updateNPCPosition(newbody->id, newbody->x, newbody->y, newbody->z,
plr->instanceID, oldbody->appearanceData.iAngle); plr->instanceID, oldbody->angle);
// Blastons, Heal // Blastons, Heal
Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->spawnX + 600, oldbody->spawnY, oldbody->spawnZ, plr->instanceID, 2470); Mob *arm = (Mob*)NPCManager::summonNPC(oldbody->x + 600, oldbody->y, oldbody->z, plr->instanceID, 2470);
arm->appearanceData.iAngle = oldbody->appearanceData.iAngle; arm->angle = oldbody->angle;
NPCManager::updateNPCPosition(arm->appearanceData.iNPC_ID, arm->spawnX, arm->spawnY, arm->spawnZ, NPCManager::updateNPCPosition(arm->id, arm->x, arm->y, arm->z,
plr->instanceID, oldbody->appearanceData.iAngle); plr->instanceID, oldbody->angle);
} }
std::vector<NPCEvent> NPCManager::NPCEvents = { std::vector<NPCEvent> NPCManager::NPCEvents = {
@ -352,11 +355,11 @@ void NPCManager::queueNPCRemoval(int32_t id) {
static void step(CNServer *serv, time_t currTime) { static void step(CNServer *serv, time_t currTime) {
for (auto& pair : NPCs) { for (auto& pair : NPCs) {
if (pair.second->type != EntityType::COMBAT_NPC && pair.second->type != EntityType::MOB) if (pair.second->kind != EntityKind::COMBAT_NPC && pair.second->kind != EntityKind::MOB)
continue; continue;
auto npc = (CombatNPC*)pair.second; auto npc = (CombatNPC*)pair.second;
npc->stepAI(currTime); npc->step(currTime);
} }
// deallocate all NPCs queued for removal // deallocate all NPCs queued for removal

View File

@ -1,15 +1,16 @@
#pragma once #pragma once
#include "core/Core.hpp" #include "core/Core.hpp"
#include "PlayerManager.hpp"
#include "NPC.hpp"
#include "Transport.hpp"
#include "JSON.hpp" #include "JSON.hpp"
#include "Transport.hpp"
#include "Chunking.hpp"
#include "Entities.hpp"
#include <map> #include <map>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include <set>
#define RESURRECT_HEIGHT 400 #define RESURRECT_HEIGHT 400
@ -29,8 +30,6 @@ struct NPCEvent {
: npcType(t), trigger(tr), handler(hndlr) {} : npcType(t), trigger(tr), handler(hndlr) {}
}; };
struct WarpLocation;
namespace NPCManager { namespace NPCManager {
extern std::unordered_map<int32_t, BaseNPC*> NPCs; extern std::unordered_map<int32_t, BaseNPC*> NPCs;
extern std::map<int32_t, WarpLocation> Warps; extern std::map<int32_t, WarpLocation> Warps;
@ -44,7 +43,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);

View File

@ -1,11 +1,11 @@
#include "servers/CNShardServer.hpp"
#include "Nanos.hpp" #include "Nanos.hpp"
#include "servers/CNShardServer.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "NPCManager.hpp"
#include "Combat.hpp"
#include "Missions.hpp" #include "Missions.hpp"
#include "Groups.hpp"
#include "Abilities.hpp" #include "Abilities.hpp"
#include "NPCManager.hpp"
#include <cmath> #include <cmath>
@ -69,6 +69,43 @@ 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);
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 passive skills, using just the player as a target is fine
// this is because the group skill type will ignore the count,
// and the other option is single-target
std::vector<ICombatant*> targets = Abilities::matchTargets(dynamic_cast<ICombatant*>(plr), skill, 1, &plr->iID);
std::vector<ICombatant*> affected;
for (ICombatant* target : targets) {
passiveBuff.buffStackClass = target == plr ? BuffClass::NANO : BuffClass::GROUP_NANO;
if(target->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(target);
}
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;
@ -82,40 +119,19 @@ void Nanos::summonNano(CNSocket *sock, int slot, bool silent) {
if (slot != -1 && plr->Nanos[nanoID].iSkillID == 0) if (slot != -1 && plr->Nanos[nanoID].iSkillID == 0)
return; // prevent powerless nanos from summoning return; // prevent powerless nanos from summoning
plr->nanoDrainRate = 0;
int16_t skillID = plr->Nanos[plr->activeNano].iSkillID;
// passive nano unbuffing
if (SkillTable[skillID].drainType == 2) {
std::vector<int> targetData = findTargets(plr, skillID);
for (auto& pwr : NanoPowers)
if (pwr.skillType == SkillTable[skillID].skillType)
nanoUnbuff(sock, targetData, pwr.bitFlag, pwr.timeBuffID, 0,(SkillTable[skillID].targetType == 3));
}
if (nanoID >= NANO_COUNT || nanoID < 0) if (nanoID >= NANO_COUNT || nanoID < 0)
return; // sanity check return; // sanity check
plr->activeNano = nanoID; plr->activeNano = nanoID;
skillID = plr->Nanos[nanoID].iSkillID; sNano& nano = plr->Nanos[nanoID];
// passive nano buffing SkillData* skill = Abilities::SkillTable.count(nano.iSkillID) > 0
if (SkillTable[skillID].drainType == 2) { ? &Abilities::SkillTable[nano.iSkillID] : nullptr;
std::vector<int> targetData = findTargets(plr, skillID); if (slot != -1 && skill != nullptr && skill->drainType == SkillDrainType::PASSIVE) {
// passive buff effect
int boost = 0; resp.eCSTB___Add = 1;
if (getNanoBoost(plr)) std::vector<ICombatant*> affectedCombatants = applyNanoBuff(skill, plr);
boost = 1; if(!affectedCombatants.empty()) Abilities::useNanoSkill(sock, skill, nano, affectedCombatants);
for (auto& pwr : NanoPowers) {
if (pwr.skillType == SkillTable[skillID].skillType) {
resp.eCSTB___Add = 1; // the part that makes nano go ZOOMAZOOM
plr->nanoDrainRate = SkillTable[skillID].batteryUse[boost*3];
pwr.handle(sock, targetData, nanoID, skillID, 0, SkillTable[skillID].powerIntensity[boost]);
}
}
} }
if (!silent) // silent nano death but only for the summoning player if (!silent) // silent nano death but only for the summoning player
@ -124,7 +140,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);
} }
@ -211,7 +227,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;
} }
@ -235,18 +251,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);
@ -289,26 +293,24 @@ static void nanoSummonHandler(CNSocket* sock, CNPacketData* data) {
static void nanoSkillUseHandler(CNSocket* sock, CNPacketData* data) { static void nanoSkillUseHandler(CNSocket* sock, CNPacketData* data) {
Player *plr = PlayerManager::getPlayer(sock); Player *plr = PlayerManager::getPlayer(sock);
int16_t nanoID = plr->activeNano; // validate request check
int16_t skillID = plr->Nanos[nanoID].iSkillID; sP_CL2FE_REQ_NANO_SKILL_USE* pkt = (sP_CL2FE_REQ_NANO_SKILL_USE*)data->buf;
if (!validInVarPacket(sizeof(sP_CL2FE_REQ_NANO_SKILL_USE), pkt->iTargetCnt, sizeof(int32_t), data->size)) {
std::cout << "[WARN] bad sP_CL2FE_REQ_NANO_SKILL_USE packet size" << std::endl;
return;
}
sNano& nano = plr->Nanos[plr->activeNano];
int16_t skillID = nano.iSkillID;
SkillData* skillData = &Abilities::SkillTable[skillID];
DEBUGLOG( DEBUGLOG(
std::cout << PlayerManager::getPlayerName(plr) << " requested to summon nano skill " << std::endl; std::cout << PlayerManager::getPlayerName(plr) << " requested to summon nano skill " << std::endl;
) )
std::vector<int> targetData = findTargets(plr, skillID, data); ICombatant* plrCombatant = dynamic_cast<ICombatant*>(plr);
std::vector<ICombatant*> targetData = Abilities::matchTargets(plrCombatant, skillData, pkt->iTargetCnt, (int32_t*)(pkt + 1));
int boost = 0; Abilities::useNanoSkill(sock, skillData, nano, targetData);
if (getNanoBoost(plr))
boost = 1;
plr->Nanos[plr->activeNano].iStamina -= SkillTable[skillID].batteryUse[boost*3];
if (plr->Nanos[plr->activeNano].iStamina < 0)
plr->Nanos[plr->activeNano].iStamina = 0;
for (auto& pwr : NanoPowers)
if (pwr.skillType == SkillTable[skillID].skillType)
pwr.handle(sock, targetData, nanoID, skillID, SkillTable[skillID].durationTime[boost], SkillTable[skillID].powerIntensity[boost]);
if (plr->Nanos[plr->activeNano].iStamina < 0) if (plr->Nanos[plr->activeNano].iStamina < 0)
summonNano(sock, -1); summonNano(sock, -1);

View File

@ -1,10 +1,11 @@
#pragma once #pragma once
#include <set> #include "core/Core.hpp"
#include <vector>
#include "Player.hpp" #include "Player.hpp"
#include "servers/CNShardServer.hpp" #include "Abilities.hpp"
#include <map>
struct NanoData { struct NanoData {
int style; int style;
@ -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);
} }

View File

@ -1,17 +1,21 @@
#pragma once #pragma once
#include <string>
#include <cstring>
#include "core/Core.hpp" #include "core/Core.hpp"
#include "Chunking.hpp"
#include "Entities.hpp" #include "Entities.hpp"
#include "Groups.hpp"
#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))
struct Player : public Entity { struct Player : public Entity, public ICombatant {
int accountId = 0; int accountId = 0;
int accountLevel = 0; // permission level (see CN_ACCOUNT_LEVEL enums) int accountLevel = 0; // permission level (see CN_ACCOUNT_LEVEL enums)
int32_t iID = 0; int32_t iID = 0;
@ -32,9 +36,8 @@ struct Player : public Entity {
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;
@ -49,7 +52,6 @@ struct Player : public Entity {
bool inCombat = false; bool inCombat = false;
bool onMonkey = false; bool onMonkey = false;
int nanoDrainRate = 0;
int healCooldown = 0; int healCooldown = 0;
int pointDamage = 0; int pointDamage = 0;
@ -65,10 +67,7 @@ struct Player : public Entity {
sTimeLimitItemDeleteInfo2CL toRemoveVehicle = {}; sTimeLimitItemDeleteInfo2CL toRemoveVehicle = {};
int32_t iIDGroup = 0; Group* group = nullptr;
int groupCnt = 0;
int32_t groupIDs[4] = {};
int32_t iGroupConditionBitFlag = 0;
bool notify = false; bool notify = false;
bool hidden = false; bool hidden = false;
@ -85,8 +84,30 @@ struct Player : public Entity {
time_t lastShot = 0; time_t lastShot = 0;
std::vector<sItemBase> buyback = {}; std::vector<sItemBase> buyback = {};
Player() { type = EntityType::PLAYER; } Player() { kind = EntityKind::PLAYER; }
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 heal(EntityRef src, int amt) override;
virtual bool isAlive() 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 EntityRef getRef() override;
virtual void step(time_t currTime) override;
sNano* getActiveNano();
sPCAppearanceData getAppearanceData();
}; };

View File

@ -1,25 +1,20 @@
#include "core/Core.hpp" #include "PlayerManager.hpp"
#include "db/Database.hpp"
#include "core/CNShared.hpp" #include "core/CNShared.hpp"
#include "servers/CNShardServer.hpp" #include "servers/CNShardServer.hpp"
#include "db/Database.hpp"
#include "PlayerManager.hpp"
#include "NPCManager.hpp" #include "NPCManager.hpp"
#include "Missions.hpp"
#include "Items.hpp"
#include "Nanos.hpp"
#include "Groups.hpp"
#include "Chat.hpp"
#include "Buddies.hpp"
#include "Combat.hpp" #include "Combat.hpp"
#include "Racing.hpp" #include "Racing.hpp"
#include "BuiltinCommands.hpp"
#include "Abilities.hpp"
#include "Eggs.hpp" #include "Eggs.hpp"
#include "Missions.hpp"
#include "settings.hpp" #include "Chat.hpp"
#include "Items.hpp"
#include "Buddies.hpp"
#include "BuiltinCommands.hpp"
#include <assert.h> #include <assert.h>
#include <algorithm> #include <algorithm>
#include <vector> #include <vector>
#include <cmath> #include <cmath>
@ -41,7 +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;
Groups::groupKickPlayer(plr); // free buff memory
for(auto buffEntry : plr->buffs)
delete buffEntry.second;
// leave group
if(plr->group != nullptr)
Groups::groupKick(plr->group, key);
// remove player's bullets // remove player's bullets
Combat::Bullets.erase(plr->iID); Combat::Bullets.erase(plr->iID);
@ -65,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;
} }
@ -232,8 +223,7 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
Database::getPlayer(plr, lm->playerId); Database::getPlayer(plr, lm->playerId);
} }
plr->groupCnt = 1; plr->group = nullptr;
plr->iIDGroup = plr->groupIDs[0] = plr->iID;
response.iID = plr->iID; response.iID = plr->iID;
response.uiSvrTime = getTime(); response.uiSvrTime = getTime();
@ -348,12 +338,20 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
delete lm; delete lm;
} }
void PlayerManager::sendToGroup(CNSocket* sock, void* buf, uint32_t type, size_t size) {
Player* plr = getPlayer(sock);
if (plr->group == nullptr)
return;
for(const EntityRef& ref : plr->group->filter(EntityKind::PLAYER))
ref.sock->sendPacket(buf, type, size);
}
void PlayerManager::sendToViewable(CNSocket* sock, void* buf, uint32_t type, size_t size) { void PlayerManager::sendToViewable(CNSocket* sock, void* buf, uint32_t type, size_t size) {
Player* plr = getPlayer(sock); Player* plr = getPlayer(sock);
for (auto it = plr->viewableChunks.begin(); it != plr->viewableChunks.end(); it++) { for (auto it = plr->viewableChunks.begin(); it != plr->viewableChunks.end(); it++) {
Chunk* chunk = *it; Chunk* chunk = *it;
for (const EntityRef& ref : chunk->entities) { for (const EntityRef& ref : chunk->entities) {
if (ref.type != EntityType::PLAYER || ref.sock == sock) if (ref.kind != EntityKind::PLAYER || ref.sock == sock)
continue; continue;
ref.sock->sendPacket(buf, type, size); ref.sock->sendPacket(buf, type, size);
@ -406,21 +404,22 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) {
int activeSlot = -1; int activeSlot = -1;
bool move = false; bool move = false;
if (reviveData->iRegenType == 3 && plr->iConditionBitFlag & CSB_BIT_PHOENIX) { switch ((ePCRegenType)reviveData->iRegenType) {
// nano revive case ePCRegenType::HereByPhoenix: // nano revive
if (!(plr->hasBuff(ECSB_PHOENIX)))
return; // sanity check
plr->Nanos[plr->activeNano].iStamina = 0; plr->Nanos[plr->activeNano].iStamina = 0;
// fallthrough
case ePCRegenType::HereByPhoenixGroup: // revived by group member's nano
plr->HP = PC_MAXHEALTH(plr->level) / 2; plr->HP = PC_MAXHEALTH(plr->level) / 2;
Nanos::applyBuff(sock, plr->Nanos[plr->activeNano].iSkillID, 2, 1, 0); break;
} else if (reviveData->iRegenType == 4) {
// revived by group member's nano default: // plain respawn
plr->HP = PC_MAXHEALTH(plr->level) / 2; plr->HP = PC_MAXHEALTH(plr->level) / 2;
} else if (reviveData->iRegenType == 5) { // fallthrough
// warp away case ePCRegenType::Unstick: // warp away
move = true; move = true;
} else { break;
// plain respawn
move = true;
plr->HP = PC_MAXHEALTH(plr->level) / 2;
} }
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
@ -472,11 +471,9 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) {
resp2.PCRegenDataForOtherPC.iHP = plr->HP; resp2.PCRegenDataForOtherPC.iHP = plr->HP;
resp2.PCRegenDataForOtherPC.iAngle = plr->angle; resp2.PCRegenDataForOtherPC.iAngle = plr->angle;
Player *otherPlr = getPlayerFromID(plr->iIDGroup); if (plr->group != nullptr) {
if (otherPlr != nullptr) {
int bitFlag = Groups::getGroupFlags(otherPlr);
resp2.PCRegenDataForOtherPC.iConditionBitFlag = plr->iConditionBitFlag = plr->iSelfConditionBitFlag | bitFlag;
resp2.PCRegenDataForOtherPC.iConditionBitFlag = plr->getCompositeCondition();
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];
@ -667,16 +664,16 @@ CNSocket *PlayerManager::getSockFromName(std::string firstname, std::string last
} }
CNSocket *PlayerManager::getSockFromAny(int by, int id, int uid, std::string firstname, std::string lastname) { CNSocket *PlayerManager::getSockFromAny(int by, int id, int uid, std::string firstname, std::string lastname) {
switch (by) { switch ((eCN_GM_TargetSearchBy)by) {
case eCN_GM_TargetSearchBy__PC_ID: case eCN_GM_TargetSearchBy::PC_ID:
assert(id != 0); assert(id != 0);
return getSockFromID(id); return getSockFromID(id);
case eCN_GM_TargetSearchBy__PC_UID: // account id; not player id case eCN_GM_TargetSearchBy::PC_UID: // account id; not player id
assert(uid != 0); assert(uid != 0);
for (auto& pair : players) for (auto& pair : players)
if (pair.second->accountId == uid) if (pair.second->accountId == uid)
return pair.first; return pair.first;
case eCN_GM_TargetSearchBy__PC_Name: case eCN_GM_TargetSearchBy::PC_Name:
assert(firstname != "" && lastname != ""); // XXX: remove this if we start messing around with edited names? assert(firstname != "" && lastname != ""); // XXX: remove this if we start messing around with edited names?
return getSockFromName(firstname, lastname); return getSockFromName(firstname, lastname);
} }

View File

@ -1,16 +1,15 @@
#pragma once #pragma once
#include "Player.hpp"
#include "core/Core.hpp" #include "core/Core.hpp"
#include "servers/CNShardServer.hpp"
#include "Chunking.hpp"
#include <utility> #include "Player.hpp"
#include "Chunking.hpp"
#include "Transport.hpp"
#include "Entities.hpp"
#include <map> #include <map>
#include <list> #include <list>
struct WarpLocation;
namespace PlayerManager { namespace PlayerManager {
extern std::map<CNSocket*, Player*> players; extern std::map<CNSocket*, Player*> players;
void init(); void init();
@ -34,6 +33,7 @@ namespace PlayerManager {
CNSocket *getSockFromAny(int by, int id, int uid, std::string firstname, std::string lastname); CNSocket *getSockFromAny(int by, int id, int uid, std::string firstname, std::string lastname);
WarpLocation *getRespawnPoint(Player *plr); WarpLocation *getRespawnPoint(Player *plr);
void sendToGroup(CNSocket *sock, void* buf, uint32_t type, size_t size);
void sendToViewable(CNSocket *sock, void* buf, uint32_t type, size_t size); void sendToViewable(CNSocket *sock, void* buf, uint32_t type, size_t size);
// TODO: unify this under the new Entity system // TODO: unify this under the new Entity system
@ -43,7 +43,7 @@ namespace PlayerManager {
for (auto it = plr->viewableChunks.begin(); it != plr->viewableChunks.end(); it++) { for (auto it = plr->viewableChunks.begin(); it != plr->viewableChunks.end(); it++) {
Chunk* chunk = *it; Chunk* chunk = *it;
for (const EntityRef& ref : chunk->entities) { for (const EntityRef& ref : chunk->entities) {
if (ref.type != EntityType::PLAYER || ref.sock == sock) if (ref.kind != EntityKind::PLAYER || ref.sock == sock)
continue; continue;
ref.sock->sendPacket(pkt, type); ref.sock->sendPacket(pkt, type);

View File

@ -1,4 +1,7 @@
#include "PlayerMovement.hpp" #include "PlayerMovement.hpp"
#include "servers/CNShardServer.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "TableData.hpp" #include "TableData.hpp"
#include "core/Core.hpp" #include "core/Core.hpp"
@ -33,7 +36,7 @@ static void movePlayer(CNSocket* sock, CNPacketData* data) {
// [gruntwork] check if player has a follower and move it // [gruntwork] check if player has a follower and move it
if (TableData::RunningNPCPaths.find(plr->iID) != TableData::RunningNPCPaths.end()) { if (TableData::RunningNPCPaths.find(plr->iID) != TableData::RunningNPCPaths.end()) {
BaseNPC* follower = TableData::RunningNPCPaths[plr->iID].first; BaseNPC* follower = TableData::RunningNPCPaths[plr->iID].first;
Transport::NPCQueues.erase(follower->appearanceData.iNPC_ID); // erase existing points Transport::NPCQueues.erase(follower->id); // erase existing points
std::queue<Vec3> queue; std::queue<Vec3> queue;
Vec3 from = { follower->x, follower->y, follower->z }; Vec3 from = { follower->x, follower->y, follower->z };
float drag = 0.95f; // this ensures that they don't bump into the player float drag = 0.95f; // this ensures that they don't bump into the player
@ -45,7 +48,7 @@ static void movePlayer(CNSocket* sock, CNPacketData* data) {
// add a route to the queue; to be processed in Transport::stepNPCPathing() // add a route to the queue; to be processed in Transport::stepNPCPathing()
Transport::lerp(&queue, from, to, NPC_DEFAULT_SPEED * 1.5); // little faster than typical Transport::lerp(&queue, from, to, NPC_DEFAULT_SPEED * 1.5); // little faster than typical
Transport::NPCQueues[follower->appearanceData.iNPC_ID] = queue; Transport::NPCQueues[follower->id] = queue;
} }
} }

View File

@ -1,10 +1,12 @@
#include "servers/CNShardServer.hpp"
#include "Racing.hpp" #include "Racing.hpp"
#include "db/Database.hpp"
#include "servers/CNShardServer.hpp"
#include "NPCManager.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "Missions.hpp" #include "Missions.hpp"
#include "Items.hpp" #include "Items.hpp"
#include "db/Database.hpp"
#include "NPCManager.hpp"
using namespace Racing; using namespace Racing;

View File

@ -1,8 +1,10 @@
#pragma once #pragma once
#include <set> #include "core/Core.hpp"
#include "servers/CNShardServer.hpp" #include <map>
#include <vector>
#include <set>
struct EPInfo { struct EPInfo {
int zoneX, zoneY, EPID, maxScore, maxTime; int zoneX, zoneY, EPID, maxScore, maxTime;

View File

@ -2,6 +2,7 @@
#include <random> #include <random>
#include <memory> #include <memory>
#include <vector>
namespace Rand { namespace Rand {
extern std::unique_ptr<std::mt19937> generator; extern std::unique_ptr<std::mt19937> generator;

View File

@ -1,18 +1,14 @@
#include "TableData.hpp" #include "TableData.hpp"
#include "NPCManager.hpp" #include "NPCManager.hpp"
#include "Transport.hpp"
#include "Items.hpp"
#include "settings.hpp"
#include "Missions.hpp" #include "Missions.hpp"
#include "Chunking.hpp" #include "Items.hpp"
#include "Nanos.hpp"
#include "Racing.hpp"
#include "Vendors.hpp" #include "Vendors.hpp"
#include "Racing.hpp"
#include "Nanos.hpp"
#include "Abilities.hpp" #include "Abilities.hpp"
#include "Eggs.hpp" #include "Eggs.hpp"
#include "JSON.hpp"
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
#include <cmath> #include <cmath>
@ -231,18 +227,34 @@ static void loadXDT(json& xdtData) {
// load nano powers // load nano powers
json skills = xdtData["m_pSkillTable"]["m_pSkillData"]; json skills = xdtData["m_pSkillTable"]["m_pSkillData"];
for (json::iterator _skills = skills.begin(); _skills != skills.end(); _skills++) { for (json::iterator _skill = skills.begin(); _skill != skills.end(); _skill++) {
auto skills = _skills.value(); auto skill = _skill.value();
SkillData skillData = { skills["m_iSkillType"], skills["m_iTargetType"], skills["m_iBatteryDrainType"], skills["m_iEffectArea"] }; SkillData skillData = {
skill["m_iSkillType"],
skill["m_iEffectTarget"],
skill["m_iEffectType"],
skill["m_iTargetType"],
skill["m_iBatteryDrainType"],
skill["m_iEffectArea"]
};
skillData.valueTypes[0] = skill["m_iValueA_Type"];
skillData.valueTypes[1] = skill["m_iValueB_Type"];
skillData.valueTypes[2] = skill["m_iValueC_Type"];
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
skillData.batteryUse[i] = skills["m_iBatteryDrainUse"][i]; skillData.batteryUse[i] = skill["m_iBatteryDrainUse"][i];
skillData.durationTime[i] = skills["m_iDurationTime"][i]; skillData.durationTime[i] = skill["m_iDurationTime"][i];
skillData.powerIntensity[i] = skills["m_iValueA"][i];
skillData.values[0][i] = skill["m_iValueA"][i];
skillData.values[1][i] = skill["m_iValueB"][i];
skillData.values[2][i] = skill["m_iValueC"][i];
} }
Nanos::SkillTable[skills["m_iSkillNumber"]] = skillData;
Abilities::SkillTable[skill["m_iSkillNumber"]] = skillData;
} }
std::cout << "[INFO] Loaded " << Nanos::SkillTable.size() << " nano skills" << std::endl; std::cout << "[INFO] Loaded " << Abilities::SkillTable.size() << " nano skills" << std::endl;
// load EP data // load EP data
json instances = xdtData["m_pInstanceTable"]["m_pInstanceData"]; json instances = xdtData["m_pInstanceTable"]["m_pInstanceData"];
@ -314,10 +326,10 @@ static void loadPaths(json& pathData, int32_t* nextId) {
if (passedDistance >= SLIDER_GAP_SIZE) { // space them out uniformaly if (passedDistance >= SLIDER_GAP_SIZE) { // space them out uniformaly
passedDistance -= SLIDER_GAP_SIZE; // step down passedDistance -= SLIDER_GAP_SIZE; // step down
// spawn a slider // spawn a slider
Bus* slider = new Bus(point.x, point.y, point.z, 0, INSTANCE_OVERWORLD, 1, (*nextId)--); Bus* slider = new Bus(0, INSTANCE_OVERWORLD, 1, (*nextId)--);
NPCManager::NPCs[slider->appearanceData.iNPC_ID] = slider; NPCManager::NPCs[slider->id] = slider;
NPCManager::updateNPCPosition(slider->appearanceData.iNPC_ID, slider->x, slider->y, slider->z, INSTANCE_OVERWORLD, 0); NPCManager::updateNPCPosition(slider->id, point.x, point.y, point.z, INSTANCE_OVERWORLD, 0);
Transport::NPCQueues[slider->appearanceData.iNPC_ID] = route; Transport::NPCQueues[slider->id] = route;
} }
// rotate // rotate
route.pop(); route.pop();
@ -659,7 +671,7 @@ static void loadEggs(json& eggData, int32_t* nextId) {
int id = (*nextId)--; int id = (*nextId)--;
uint64_t instanceID = egg.find("iMapNum") == egg.end() ? INSTANCE_OVERWORLD : (int)egg["iMapNum"]; uint64_t instanceID = egg.find("iMapNum") == egg.end() ? INSTANCE_OVERWORLD : (int)egg["iMapNum"];
Egg* addEgg = new Egg((int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, (int)egg["iType"], id, false); Egg* addEgg = new Egg(instanceID, (int)egg["iType"], id, false);
NPCManager::NPCs[id] = addEgg; NPCManager::NPCs[id] = addEgg;
eggCount++; eggCount++;
NPCManager::updateNPCPosition(id, (int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, 0); NPCManager::updateNPCPosition(id, (int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, 0);
@ -755,7 +767,7 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
if (NPCManager::NPCs.find(npcID) == NPCManager::NPCs.end()) if (NPCManager::NPCs.find(npcID) == NPCManager::NPCs.end())
continue; // NPC not found continue; // NPC not found
BaseNPC* npc = NPCManager::NPCs[npcID]; BaseNPC* npc = NPCManager::NPCs[npcID];
npc->appearanceData.iAngle = angle; npc->angle = angle;
RunningNPCRotations[npcID] = angle; RunningNPCRotations[npcID] = angle;
} }
@ -768,8 +780,8 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
if (NPCManager::NPCs.find(npcID) == NPCManager::NPCs.end()) if (NPCManager::NPCs.find(npcID) == NPCManager::NPCs.end())
continue; // NPC not found continue; // NPC not found
BaseNPC* npc = NPCManager::NPCs[npcID]; BaseNPC* npc = NPCManager::NPCs[npcID];
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->x, npc->y, NPCManager::updateNPCPosition(npc->id, npc->x, npc->y,
npc->z, instanceID, npc->appearanceData.iAngle); npc->z, instanceID, npc->angle);
RunningNPCMapNumbers[npcID] = instanceID; RunningNPCMapNumbers[npcID] = instanceID;
} }
@ -791,12 +803,12 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
// re-enable respawning // re-enable respawning
((Mob*)npc)->summoned = false; ((Mob*)npc)->summoned = false;
} else { } else {
npc = new BaseNPC(mob["iX"], mob["iY"], mob["iZ"], mob["iAngle"], instanceID, mob["iNPCType"], id); npc = new BaseNPC(mob["iAngle"], instanceID, mob["iNPCType"], id);
} }
NPCManager::NPCs[npc->appearanceData.iNPC_ID] = npc; NPCManager::NPCs[npc->id] = npc;
RunningMobs[npc->appearanceData.iNPC_ID] = npc; RunningMobs[npc->id] = npc;
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, mob["iX"], mob["iY"], mob["iZ"], instanceID, mob["iAngle"]); NPCManager::updateNPCPosition(npc->id, mob["iX"], mob["iY"], mob["iZ"], instanceID, mob["iAngle"]);
} }
// mob groups // mob groups
@ -840,7 +852,7 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
tmpFol->offsetX = follower.find("iOffsetX") == follower.end() ? 0 : (int)follower["iOffsetX"]; tmpFol->offsetX = follower.find("iOffsetX") == follower.end() ? 0 : (int)follower["iOffsetX"];
tmpFol->offsetY = follower.find("iOffsetY") == follower.end() ? 0 : (int)follower["iOffsetY"]; tmpFol->offsetY = follower.find("iOffsetY") == follower.end() ? 0 : (int)follower["iOffsetY"];
tmpFol->groupLeader = tmp->appearanceData.iNPC_ID; tmpFol->groupLeader = tmp->id;
tmp->groupMember[followerCount++] = *nextId; tmp->groupMember[followerCount++] = *nextId;
(*nextId)--; (*nextId)--;
@ -850,7 +862,7 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
std::cout << "[WARN] Mob group leader with ID " << *nextId << " has too many followers (" << followers.size() << ")\n"; std::cout << "[WARN] Mob group leader with ID " << *nextId << " has too many followers (" << followers.size() << ")\n";
} }
RunningGroups[tmp->appearanceData.iNPC_ID] = tmp; // store as running RunningGroups[tmp->id] = tmp; // store as running
} }
auto eggs = gruntwork["eggs"]; auto eggs = gruntwork["eggs"];
@ -859,7 +871,7 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
int id = (*nextId)--; int id = (*nextId)--;
uint64_t instanceID = egg.find("iMapNum") == egg.end() ? INSTANCE_OVERWORLD : (int)egg["iMapNum"]; uint64_t instanceID = egg.find("iMapNum") == egg.end() ? INSTANCE_OVERWORLD : (int)egg["iMapNum"];
Egg* addEgg = new Egg((int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, (int)egg["iType"], id, false); Egg* addEgg = new Egg(instanceID, (int)egg["iType"], id, false);
NPCManager::NPCs[id] = addEgg; NPCManager::NPCs[id] = addEgg;
NPCManager::updateNPCPosition(id, (int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, 0); NPCManager::updateNPCPosition(id, (int)egg["iX"], (int)egg["iY"], (int)egg["iZ"], instanceID, 0);
RunningEggs[id] = addEgg; RunningEggs[id] = addEgg;
@ -893,7 +905,7 @@ static void loadNPCs(json& npcData) {
if (npc["iX"] > 512000 && npc["iY"] < 256000) if (npc["iX"] > 512000 && npc["iY"] < 256000)
continue; continue;
#endif #endif
BaseNPC* tmp = new BaseNPC(npc["iX"], npc["iY"], npc["iZ"], npc["iAngle"], instanceID, type, npcID); BaseNPC* tmp = new BaseNPC(npc["iAngle"], instanceID, type, npcID);
NPCManager::NPCs[npcID] = tmp; NPCManager::NPCs[npcID] = tmp;
NPCManager::updateNPCPosition(npcID, npc["iX"], npc["iY"], npc["iZ"], instanceID, npc["iAngle"]); NPCManager::updateNPCPosition(npcID, npc["iX"], npc["iY"], npc["iZ"], instanceID, npc["iAngle"]);
@ -1003,7 +1015,7 @@ static void loadMobs(json& npcData, int32_t* nextId) {
tmpFol->offsetX = follower.find("iOffsetX") == follower.end() ? 0 : (int)follower["iOffsetX"]; tmpFol->offsetX = follower.find("iOffsetX") == follower.end() ? 0 : (int)follower["iOffsetX"];
tmpFol->offsetY = follower.find("iOffsetY") == follower.end() ? 0 : (int)follower["iOffsetY"]; tmpFol->offsetY = follower.find("iOffsetY") == follower.end() ? 0 : (int)follower["iOffsetY"];
tmpFol->groupLeader = tmp->appearanceData.iNPC_ID; tmpFol->groupLeader = tmp->id;
tmp->groupMember[followerCount++] = *nextId; tmp->groupMember[followerCount++] = *nextId;
(*nextId)--; (*nextId)--;
@ -1226,7 +1238,7 @@ void TableData::flush() {
continue; continue;
int x, y, z; int x, y, z;
if (npc->type == EntityType::MOB) { if (npc->kind == EntityKind::MOB) {
Mob *m = (Mob*)npc; Mob *m = (Mob*)npc;
x = m->spawnX; x = m->spawnX;
y = m->spawnY; y = m->spawnY;
@ -1238,13 +1250,13 @@ void TableData::flush() {
} }
// NOTE: this format deviates slightly from the one in mobs.json // NOTE: this format deviates slightly from the one in mobs.json
mob["iNPCType"] = (int)npc->appearanceData.iNPCType; mob["iNPCType"] = (int)npc->type;
mob["iX"] = x; mob["iX"] = x;
mob["iY"] = y; mob["iY"] = y;
mob["iZ"] = z; mob["iZ"] = z;
mob["iMapNum"] = MAPNUM(npc->instanceID); mob["iMapNum"] = MAPNUM(npc->instanceID);
// this is a bit imperfect, since this is a live angle, not a spawn angle so it'll change often, but eh // this is a bit imperfect, since this is a live angle, not a spawn angle so it'll change often, but eh
mob["iAngle"] = npc->appearanceData.iAngle; mob["iAngle"] = npc->angle;
// it's called mobs, but really it's everything // it's called mobs, but really it's everything
gruntwork["mobs"].push_back(mob); gruntwork["mobs"].push_back(mob);
@ -1259,19 +1271,19 @@ void TableData::flush() {
int x, y, z; int x, y, z;
std::vector<Mob*> followers; std::vector<Mob*> followers;
if (npc->type == EntityType::MOB) { if (npc->kind == EntityKind::MOB) {
Mob* m = (Mob*)npc; Mob* m = (Mob*)npc;
x = m->spawnX; x = m->spawnX;
y = m->spawnY; y = m->spawnY;
z = m->spawnZ; z = m->spawnZ;
if (m->groupLeader != m->appearanceData.iNPC_ID) { // make sure this is a leader if (m->groupLeader != m->id) { // make sure this is a leader
std::cout << "[WARN] Non-leader mob found in running groups; ignoring\n"; std::cout << "[WARN] Non-leader mob found in running groups; ignoring\n";
continue; continue;
} }
// add follower data to vector; go until OOB or until follower ID is 0 // add follower data to vector; go until OOB or until follower ID is 0
for (int i = 0; i < 4 && m->groupMember[i] > 0; i++) { for (int i = 0; i < 4 && m->groupMember[i] > 0; i++) {
if (NPCManager::NPCs.find(m->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[m->groupMember[i]]->type != EntityType::MOB) { if (NPCManager::NPCs.find(m->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[m->groupMember[i]]->kind != EntityKind::MOB) {
std::cout << "[WARN] Follower with ID " << m->groupMember[i] << " not found; skipping\n"; std::cout << "[WARN] Follower with ID " << m->groupMember[i] << " not found; skipping\n";
continue; continue;
} }
@ -1285,13 +1297,13 @@ void TableData::flush() {
} }
// NOTE: this format deviates slightly from the one in mobs.json // NOTE: this format deviates slightly from the one in mobs.json
mob["iNPCType"] = (int)npc->appearanceData.iNPCType; mob["iNPCType"] = (int)npc->type;
mob["iX"] = x; mob["iX"] = x;
mob["iY"] = y; mob["iY"] = y;
mob["iZ"] = z; mob["iZ"] = z;
mob["iMapNum"] = MAPNUM(npc->instanceID); mob["iMapNum"] = MAPNUM(npc->instanceID);
// this is a bit imperfect, since this is a live angle, not a spawn angle so it'll change often, but eh // this is a bit imperfect, since this is a live angle, not a spawn angle so it'll change often, but eh
mob["iAngle"] = npc->appearanceData.iAngle; mob["iAngle"] = npc->angle;
// followers // followers
while (followers.size() > 0) { while (followers.size() > 0) {
@ -1300,7 +1312,7 @@ void TableData::flush() {
// populate JSON entry // populate JSON entry
json fol; json fol;
fol["iNPCType"] = follower->appearanceData.iNPCType; fol["iNPCType"] = follower->type;
fol["iOffsetX"] = follower->offsetX; fol["iOffsetX"] = follower->offsetX;
fol["iOffsetY"] = follower->offsetY; fol["iOffsetY"] = follower->offsetY;
@ -1325,7 +1337,7 @@ void TableData::flush() {
int mapnum = MAPNUM(npc->instanceID); int mapnum = MAPNUM(npc->instanceID);
if (mapnum != 0) if (mapnum != 0)
egg["iMapNum"] = mapnum; egg["iMapNum"] = mapnum;
egg["iType"] = npc->appearanceData.iNPCType; egg["iType"] = npc->type;
gruntwork["eggs"].push_back(egg); gruntwork["eggs"].push_back(egg);
} }

View File

@ -1,8 +1,13 @@
#pragma once #pragma once
#include <map> #include "JSON.hpp"
#include "NPCManager.hpp" #include "Entities.hpp"
#include "Transport.hpp"
#include <map>
#include <unordered_map>
#include <vector>
// these are added to the NPC's static key to avoid collisions // these are added to the NPC's static key to avoid collisions
const int NPC_ID_OFFSET = 1; const int NPC_ID_OFFSET = 1;

View File

@ -1,4 +1,7 @@
#include "Trading.hpp" #include "Trading.hpp"
#include "servers/CNShardServer.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "db/Database.hpp" #include "db/Database.hpp"

View File

@ -1,7 +1,5 @@
#pragma once #pragma once
#include "Items.hpp"
namespace Trading { namespace Trading {
void init(); void init();
} }

View File

@ -1,9 +1,12 @@
#include "Transport.hpp"
#include "servers/CNShardServer.hpp" #include "servers/CNShardServer.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "Nanos.hpp" #include "Nanos.hpp"
#include "Transport.hpp"
#include "TableData.hpp" #include "TableData.hpp"
#include "Combat.hpp" #include "Entities.hpp"
#include "NPCManager.hpp"
#include "MobAI.hpp" #include "MobAI.hpp"
#include <unordered_map> #include <unordered_map>
@ -257,13 +260,13 @@ static void stepNPCPathing() {
} }
// skip if not simulating mobs // skip if not simulating mobs
if (npc->type == EntityType::MOB && !MobAI::simulateMobs) { if (npc->kind == EntityKind::MOB && !MobAI::simulateMobs) {
it++; it++;
continue; continue;
} }
// do not roam if not roaming // do not roam if not roaming
if (npc->type == EntityType::MOB && ((Mob*)npc)->state != MobState::ROAMING) { if (npc->kind == EntityKind::MOB && ((Mob*)npc)->state != AIState::ROAMING) {
it++; it++;
continue; continue;
} }
@ -276,15 +279,15 @@ static void stepNPCPathing() {
int distanceBetween = hypot(dXY, point.z - npc->z); // total distance int distanceBetween = hypot(dXY, point.z - npc->z); // total distance
// update NPC location to update viewables // update NPC location to update viewables
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, point.x, point.y, point.z, npc->instanceID, npc->appearanceData.iAngle); NPCManager::updateNPCPosition(npc->id, point.x, point.y, point.z, npc->instanceID, npc->angle);
// TODO: move walking logic into Entity stack // TODO: move walking logic into Entity stack
switch (npc->type) { switch (npc->kind) {
case EntityType::BUS: case EntityKind::BUS:
INITSTRUCT(sP_FE2CL_TRANSPORTATION_MOVE, busMove); INITSTRUCT(sP_FE2CL_TRANSPORTATION_MOVE, busMove);
busMove.eTT = 3; busMove.eTT = 3;
busMove.iT_ID = npc->appearanceData.iNPC_ID; busMove.iT_ID = npc->id;
busMove.iMoveStyle = 0; // ??? busMove.iMoveStyle = 0; // ???
busMove.iToX = point.x; busMove.iToX = point.x;
busMove.iToY = point.y; busMove.iToY = point.y;
@ -293,12 +296,12 @@ static void stepNPCPathing() {
NPCManager::sendToViewable(npc, &busMove, P_FE2CL_TRANSPORTATION_MOVE, sizeof(sP_FE2CL_TRANSPORTATION_MOVE)); NPCManager::sendToViewable(npc, &busMove, P_FE2CL_TRANSPORTATION_MOVE, sizeof(sP_FE2CL_TRANSPORTATION_MOVE));
break; break;
case EntityType::MOB: case EntityKind::MOB:
MobAI::incNextMovement((Mob*)npc); MobAI::incNextMovement((Mob*)npc);
/* fallthrough */ /* fallthrough */
default: default:
INITSTRUCT(sP_FE2CL_NPC_MOVE, move); INITSTRUCT(sP_FE2CL_NPC_MOVE, move);
move.iNPC_ID = npc->appearanceData.iNPC_ID; move.iNPC_ID = npc->id;
move.iMoveStyle = 0; // ??? move.iMoveStyle = 0; // ???
move.iToX = point.x; move.iToX = point.x;
move.iToY = point.y; move.iToY = point.y;
@ -385,7 +388,7 @@ NPCPath* Transport::findApplicablePath(int32_t id, int32_t type, int taskID) {
void Transport::constructPathNPC(int32_t id, NPCPath* path) { void Transport::constructPathNPC(int32_t id, NPCPath* path) {
BaseNPC* npc = NPCManager::NPCs[id]; BaseNPC* npc = NPCManager::NPCs[id];
if (npc->type == EntityType::MOB) if (npc->kind == EntityKind::MOB)
((Mob*)(npc))->staticPath = true; ((Mob*)(npc))->staticPath = true;
npc->loopingPath = path->isLoop; npc->loopingPath = path->isLoop;

View File

@ -1,8 +1,11 @@
#pragma once #pragma once
#include "servers/CNShardServer.hpp" #include "core/Core.hpp"
#include <unordered_map> #include <unordered_map>
#include <map>
#include <vector>
#include <queue>
const int SLIDER_SPEED = 1200; const int SLIDER_SPEED = 1200;
const int SLIDER_STOP_TICKS = 16; const int SLIDER_STOP_TICKS = 16;

View File

@ -1,4 +1,9 @@
#include "Vendors.hpp" #include "Vendors.hpp"
#include "servers/CNShardServer.hpp"
#include "PlayerManager.hpp"
#include "Items.hpp"
#include "Rand.hpp" #include "Rand.hpp"
// 7 days // 7 days

View File

@ -1,10 +1,9 @@
#pragma once #pragma once
#include "core/Core.hpp" #include "core/Core.hpp"
#include "servers/CNShardServer.hpp"
#include "Items.hpp" #include <vector>
#include "PlayerManager.hpp" #include <map>
struct VendorListing { struct VendorListing {
int sort, type, id; int sort, type, id;

View File

@ -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;

View File

@ -23,6 +23,7 @@
#include <string> #include <string>
#include <locale> #include <locale>
#include <codecvt> #include <codecvt>
#include <tuple>
// yes this is ugly, but this is needed to zero out the memory so we don't have random stackdata in our structs. // yes this is ugly, but this is needed to zero out the memory so we don't have random stackdata in our structs.
#define INITSTRUCT(T, x) T x; \ #define INITSTRUCT(T, x) T x; \

View File

@ -10,63 +10,44 @@ const float CN_EP_RANK_4 = 0.3f;
const float CN_EP_RANK_5 = 0.29f; const float CN_EP_RANK_5 = 0.29f;
// methods of finding players for GM commands // methods of finding players for GM commands
enum eCN_GM_TargetSearchBy { enum class eCN_GM_TargetSearchBy {
eCN_GM_TargetSearchBy__PC_ID, // player id PC_ID, // player id
eCN_GM_TargetSearchBy__PC_Name, // firstname, lastname PC_Name, // firstname, lastname
eCN_GM_TargetSearchBy__PC_UID // account id PC_UID // account id
}; };
enum eCN_GM_TeleportType { enum class eCN_GM_TeleportType {
eCN_GM_TeleportMapType__XYZ, XYZ,
eCN_GM_TeleportMapType__MapXYZ, MapXYZ,
eCN_GM_TeleportMapType__MyLocation, MyLocation,
eCN_GM_TeleportMapType__SomeoneLocation, SomeoneLocation,
eCN_GM_TeleportMapType__Unstick Unstick
}; };
// nano powers enum class eTaskTypeProperty {
None = -1,
Talk = 1,
GotoLocation = 2,
UseItems = 3,
Delivery = 4,
Defeat = 5,
EscortDefence = 6,
Max = 7
};
enum class ePCRegenType {
None,
Xcom,
Here,
HereByPhoenix,
HereByPhoenixGroup,
Unstick,
HereByPhoenixItem,
End
};
// nano power flags
enum { enum {
EST_NONE = 0,
EST_DAMAGE = 1,
EST_HEAL_HP = 2,
EST_KNOCKDOWN = 3,
EST_SLEEP = 4,
EST_SNARE = 5,
EST_HEAL_STAMINA = 6,
EST_STAMINA_SELF = 7,
EST_STUN = 8,
EST_WEAPONSLOW = 9,
EST_JUMP = 10,
EST_RUN = 11,
EST_STEALTH = 12,
EST_SWIM = 13,
EST_MINIMAPENEMY = 14,
EST_MINIMAPTRESURE = 15,
EST_PHOENIX = 16,
EST_PROTECTBATTERY = 17,
EST_PROTECTINFECTION = 18,
EST_REWARDBLOB = 19,
EST_REWARDCASH = 20,
EST_BATTERYDRAIN = 21,
EST_CORRUPTIONATTACK = 22,
EST_INFECTIONDAMAGE = 23,
EST_KNOCKBACK = 24,
EST_FREEDOM = 25,
EST_PHOENIX_GROUP = 26,
EST_RECALL = 27,
EST_RECALL_GROUP = 28,
EST_RETROROCKET_SELF = 29,
EST_BLOODSUCKING = 30,
EST_BOUNDINGBALL = 31,
EST_INVULNERABLE = 32,
EST_NANOSTIMPAK = 33,
EST_RETURNHOMEHEAL = 34,
EST_BUFFHEAL = 35,
EST_EXTRABANK = 36,
EST__END = 37,
EST_CORRUPTIONATTACKWIN = 38,
EST_CORRUPTIONATTACKLOSE = 39,
ECSB_NONE = 0, ECSB_NONE = 0,
ECSB_UP_MOVE_SPEED = 1, ECSB_UP_MOVE_SPEED = 1,
ECSB_UP_SWIM_SPEED = 2, ECSB_UP_SWIM_SPEED = 2,
@ -96,6 +77,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,

View File

@ -47,8 +47,8 @@ struct PacketDesc {
* really should. * really should.
*/ */
struct sGM_PVPTarget { struct sGM_PVPTarget {
uint32_t eCT;
uint32_t iID; uint32_t iID;
uint32_t eCT;
}; };
struct sSkillResult_Leech { struct sSkillResult_Leech {

View File

@ -1,13 +1,15 @@
#include "servers/CNLoginServer.hpp" #include "servers/CNLoginServer.hpp"
#include "core/CNShared.hpp" #include "core/CNShared.hpp"
#include "db/Database.hpp" #include "db/Database.hpp"
#include "PlayerManager.hpp"
#include "Items.hpp"
#include <regex>
#include "bcrypt/BCrypt.hpp" #include "bcrypt/BCrypt.hpp"
#include "PlayerManager.hpp"
#include "Items.hpp"
#include "settings.hpp" #include "settings.hpp"
#include <regex>
std::map<CNSocket*, CNLoginData> CNLoginServer::loginSessions; std::map<CNSocket*, CNLoginData> CNLoginServer::loginSessions;
CNLoginServer::CNLoginServer(uint16_t p) { CNLoginServer::CNLoginServer(uint16_t p) {

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "core/Core.hpp" #include "core/Core.hpp"
#include "Player.hpp" #include "Player.hpp"
#include <map> #include <map>

View File

@ -1,10 +1,12 @@
#include "servers/CNShardServer.hpp"
#include "core/Core.hpp" #include "core/Core.hpp"
#include "core/CNShared.hpp"
#include "db/Database.hpp" #include "db/Database.hpp"
#include "servers/Monitor.hpp" #include "servers/Monitor.hpp"
#include "servers/CNShardServer.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "MobAI.hpp" #include "MobAI.hpp"
#include "core/CNShared.hpp"
#include "settings.hpp" #include "settings.hpp"
#include "TableData.hpp" // for flush() #include "TableData.hpp" // for flush()

View File

@ -3,9 +3,11 @@
#include "core/Core.hpp" #include "core/Core.hpp"
#include <map> #include <map>
#include <list>
#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:

View File

@ -1,8 +1,10 @@
#include "servers/Monitor.hpp"
#include "servers/CNShardServer.hpp" #include "servers/CNShardServer.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "Chat.hpp" #include "Chat.hpp"
#include "Email.hpp" #include "Email.hpp"
#include "servers/Monitor.hpp"
#include "settings.hpp" #include "settings.hpp"
#include <cstdio> #include <cstdio>

View File

@ -2,9 +2,6 @@
#include "core/Core.hpp" #include "core/Core.hpp"
#include <list>
#include <mutex>
namespace Monitor { namespace Monitor {
SOCKET init(); SOCKET init();
bool acceptConnection(SOCKET, uint16_t); bool acceptConnection(SOCKET, uint16_t);

View File

@ -1,9 +1,10 @@
#include <iostream>
#include "settings.hpp" #include "settings.hpp"
#include "core/CNStructs.hpp" // so we get the ACADEMY definition
#include "INIReader.hpp" #include "INIReader.hpp"
// so we get the ACADEMY definition #include <iostream>
#include "core/CNStructs.hpp"
// defaults :) // defaults :)
int settings::VERBOSITY = 1; int settings::VERBOSITY = 1;

View File

@ -1,5 +1,7 @@
#pragma once #pragma once
#include <string>
namespace settings { namespace settings {
extern int VERBOSITY; extern int VERBOSITY;
extern bool SANDBOX; extern bool SANDBOX;

2
vendor/JSON.hpp vendored
View File

@ -27,6 +27,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
#pragma once
#ifndef INCLUDE_NLOHMANN_JSON_HPP_ #ifndef INCLUDE_NLOHMANN_JSON_HPP_
#define INCLUDE_NLOHMANN_JSON_HPP_ #define INCLUDE_NLOHMANN_JSON_HPP_