mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2024-11-24 22:11:04 +00:00
Compare commits
90 Commits
b1613639f5
...
275f2183bf
Author | SHA1 | Date | |
---|---|---|---|
|
275f2183bf | ||
|
ae9ee6094d | ||
|
6d5cea0d9a | ||
|
fdd7d53aed | ||
|
57f8f3e0e1 | ||
|
a3008cab3c | ||
|
02fd17c0e7 | ||
|
da33c5c30f | ||
|
478ae3d996 | ||
|
9affe76587 | ||
|
33fde8af69 | ||
|
1a7bb826d8 | ||
|
da9fb499de | ||
|
2bc2235179 | ||
|
618126f963 | ||
|
b16ffe4f19 | ||
|
6edc01c1f1 | ||
|
c0e566050b | ||
|
7a59248ace | ||
|
6ee55d7406 | ||
|
41622ad8aa | ||
|
0f09808bc4 | ||
|
b6171ebdc1 | ||
|
5671adbd74 | ||
|
550b26db39 | ||
|
68be9cc381 | ||
|
ef33a182d1 | ||
|
9bd8eabed7 | ||
|
2249b5381b | ||
|
9b7656117d | ||
|
9394825d41 | ||
|
6e0d55c4b7 | ||
|
33396f8d28 | ||
|
cd908666af | ||
|
74588f2c77 | ||
|
829f75112c | ||
|
343668fbcd | ||
|
2e572169c0 | ||
|
b3e28ddea3 | ||
|
1670dfd830 | ||
|
e768ebcabe | ||
|
9bc7e8de62 | ||
|
f249599ab5 | ||
|
2901f5f285 | ||
|
36f329c302 | ||
|
eb7daf8eaa | ||
|
0c5a9400ce | ||
|
f150595f70 | ||
|
c6528eb2ac | ||
|
d631ca1aa1 | ||
|
a94fb0ed6d | ||
|
d48aa21135 | ||
|
c60c4dac38 | ||
|
215da0130d | ||
|
0e8a4742eb | ||
|
85bb4d163e | ||
|
09b74a5711 | ||
|
90819bea8e | ||
|
3cc5c09a91 | ||
|
536d5fbcfa | ||
|
4ec3a3acb7 | ||
|
89ed0b99a3 | ||
|
db73b85bc8 | ||
|
6cab203401 | ||
|
92846e0eac | ||
|
5963ea06be | ||
|
c28970f2e1 | ||
|
8be853c2dc | ||
|
a811e73fed | ||
|
a58971c270 | ||
|
07429a0e51 | ||
|
7c9038cf10 | ||
|
c1e391d86a | ||
|
e23af08838 | ||
|
8a26ae2f01 | ||
|
d17694e12e | ||
|
4b612f35d2 | ||
|
d9e0a4a281 | ||
|
dd9891f668 | ||
|
962141e54f | ||
bea41132b4 | |||
f305d7252d | |||
ff62129eec | |||
55f3ab8bad | |||
|
a732cde117 | ||
|
703eaff2b4 | ||
|
f4f5f2e0bd | ||
|
1858938280 | ||
|
0af8f7e91d | ||
|
168a85e8ff |
2
.gitignore
vendored
2
.gitignore
vendored
@ -16,3 +16,5 @@ build/
|
|||||||
version.h
|
version.h
|
||||||
infer-out
|
infer-out
|
||||||
gmon.out
|
gmon.out
|
||||||
|
*.bak
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
2
Makefile
2
Makefile
@ -50,6 +50,7 @@ CXXSRC=\
|
|||||||
src/db/email.cpp\
|
src/db/email.cpp\
|
||||||
src/sandbox/seccomp.cpp\
|
src/sandbox/seccomp.cpp\
|
||||||
src/sandbox/openbsd.cpp\
|
src/sandbox/openbsd.cpp\
|
||||||
|
src/Buffs.cpp\
|
||||||
src/Chat.cpp\
|
src/Chat.cpp\
|
||||||
src/CustomCommands.cpp\
|
src/CustomCommands.cpp\
|
||||||
src/Entities.cpp\
|
src/Entities.cpp\
|
||||||
@ -96,6 +97,7 @@ CXXHDR=\
|
|||||||
vendor/JSON.hpp\
|
vendor/JSON.hpp\
|
||||||
vendor/INIReader.hpp\
|
vendor/INIReader.hpp\
|
||||||
vendor/JSON.hpp\
|
vendor/JSON.hpp\
|
||||||
|
src/Buffs.hpp\
|
||||||
src/Chat.hpp\
|
src/Chat.hpp\
|
||||||
src/CustomCommands.hpp\
|
src/CustomCommands.hpp\
|
||||||
src/Entities.hpp\
|
src/Entities.hpp\
|
||||||
|
1196
src/Abilities.cpp
1196
src/Abilities.cpp
File diff suppressed because it is too large
Load Diff
@ -1,64 +1,111 @@
|
|||||||
#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;
|
#include <assert.h>
|
||||||
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, // dnd
|
||||||
handler(sock, targetData, nanoID, skillID, duration, amount, skillType, bitFlag, timeBuffID);
|
SLEEP = 4, // dnd
|
||||||
}
|
SNARE = 5, // dnd
|
||||||
|
HEAL_STAMINA = 6,
|
||||||
|
STAMINA_SELF = 7,
|
||||||
|
STUN = 8, // dnd
|
||||||
|
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);
|
assert(len <= MAX_SKILLRESULT_SIZE);
|
||||||
|
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;
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
197
src/Buffs.cpp
Normal file
197
src/Buffs.cpp
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
EntityRef Buff::getLastSource() {
|
||||||
|
if(stacks.empty())
|
||||||
|
return self;
|
||||||
|
return stacks.back().source;
|
||||||
|
}
|
||||||
|
|
||||||
|
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::timeBuffTick(EntityRef self, Buff* buff) {
|
||||||
|
if(self.kind != EntityKind::COMBAT_NPC && self.kind != EntityKind::MOB)
|
||||||
|
return; // not implemented
|
||||||
|
Entity* entity = self.getEntity();
|
||||||
|
ICombatant* combatant = dynamic_cast<ICombatant*>(entity);
|
||||||
|
|
||||||
|
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK, pkt);
|
||||||
|
pkt.eCT = combatant->getCharType();
|
||||||
|
pkt.iID = combatant->getID();
|
||||||
|
pkt.iTB_ID = buff->id;
|
||||||
|
NPCManager::sendToViewable(entity, &pkt, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK));
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
int32_t eCharType = combatant->getCharType();
|
||||||
|
pkt.eCT = eCharType == 4 ? 2 : eCharType; // convention not followed by client here
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Buffs::tickDrain(EntityRef self, Buff* buff) {
|
||||||
|
if(self.kind != EntityKind::COMBAT_NPC && self.kind != EntityKind::MOB)
|
||||||
|
return; // not implemented
|
||||||
|
Entity* entity = self.getEntity();
|
||||||
|
ICombatant* combatant = dynamic_cast<ICombatant*>(entity);
|
||||||
|
int damage = combatant->takeDamage(buff->getLastSource(), combatant->getMaxHP() / 100);
|
||||||
|
|
||||||
|
size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_Damage);
|
||||||
|
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
|
||||||
|
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||||
|
memset(respbuf, 0, resplen);
|
||||||
|
|
||||||
|
sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK *pkt = (sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK*)respbuf;
|
||||||
|
pkt->iID = self.id;
|
||||||
|
pkt->eCT = combatant->getCharType();
|
||||||
|
pkt->iTB_ID = ECSB_BOUNDINGBALL;
|
||||||
|
|
||||||
|
sSkillResult_Damage *drain = (sSkillResult_Damage*)(respbuf + sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK));
|
||||||
|
drain->iDamage = damage;
|
||||||
|
drain->iHP = combatant->getCurrentHP();
|
||||||
|
drain->eCT = pkt->eCT;
|
||||||
|
drain->iID = pkt->iID;
|
||||||
|
|
||||||
|
NPCManager::sendToViewable(self.getEntity(), (void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen);
|
||||||
|
}
|
||||||
|
#pragma endregion
|
93
src/Buffs.hpp
Normal file
93
src/Buffs.hpp
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
#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);
|
||||||
|
EntityRef getLastSource();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 timeBuffTick(EntityRef self, Buff* buff);
|
||||||
|
void timeBuffTimeout(EntityRef self);
|
||||||
|
void tickDrain(EntityRef self, Buff* buff);
|
||||||
|
}
|
@ -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),
|
||||||
|
22
src/Chat.cpp
22
src/Chat.cpp
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
696
src/Combat.cpp
696
src/Combat.cpp
@ -1,22 +1,328 @@
|
|||||||
#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) {
|
||||||
|
if(!isAlive())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(!hasBuff(buffId)) {
|
||||||
|
buffs[buffId] = new Buff(buffId, getRef(), 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, BuffClass buffClass) {
|
||||||
|
if(hasBuff(buffId)) {
|
||||||
|
buffs[buffId]->clear(buffClass);
|
||||||
|
// buff might not be stale since another buff class might remain
|
||||||
|
if(buffs[buffId]->isStale()) {
|
||||||
|
delete buffs[buffId];
|
||||||
|
buffs.erase(buffId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Player::clearBuffs(bool force) {
|
||||||
|
auto it = buffs.begin();
|
||||||
|
while(it != buffs.end()) {
|
||||||
|
Buff* buff = (*it).second;
|
||||||
|
if(!force) buff->clear();
|
||||||
|
delete buff;
|
||||||
|
it = buffs.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
if(!isAlive())
|
||||||
|
break; // unsafe to keep ticking if we're dead
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma endregion
|
||||||
|
|
||||||
|
#pragma region CombatNPC
|
||||||
|
bool CombatNPC::addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) { /* stubbed */
|
||||||
|
if(!isAlive())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(!hasBuff(buffId)) {
|
||||||
|
buffs[buffId] = new Buff(buffId, getRef(), onUpdate, onTick, stack);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffs[buffId]->updateCallbacks(onUpdate, onTick);
|
||||||
|
buffs[buffId]->addStack(stack);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Buff* CombatNPC::getBuff(int buffId) { /* stubbed */
|
||||||
|
if(hasBuff(buffId)) {
|
||||||
|
return buffs[buffId];
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CombatNPC::removeBuff(int buffId) {
|
||||||
|
if(hasBuff(buffId)) {
|
||||||
|
buffs[buffId]->clear();
|
||||||
|
delete buffs[buffId];
|
||||||
|
buffs.erase(buffId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CombatNPC::removeBuff(int buffId, BuffClass buffClass) {
|
||||||
|
if(hasBuff(buffId)) {
|
||||||
|
buffs[buffId]->clear(buffClass);
|
||||||
|
// buff might not be stale since another buff class might remain
|
||||||
|
if(buffs[buffId]->isStale()) {
|
||||||
|
delete buffs[buffId];
|
||||||
|
buffs.erase(buffId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CombatNPC::clearBuffs(bool force) {
|
||||||
|
auto it = buffs.begin();
|
||||||
|
while(it != buffs.end()) {
|
||||||
|
Buff* buff = (*it).second;
|
||||||
|
if(!force) buff->clear();
|
||||||
|
delete buff;
|
||||||
|
it = buffs.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CombatNPC::hasBuff(int buffId) {
|
||||||
|
auto buff = buffs.find(buffId);
|
||||||
|
return buff != buffs.end() && !buff->second->isStale();
|
||||||
|
}
|
||||||
|
|
||||||
|
int CombatNPC::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 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 +420,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 +443,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 +474,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,50 +486,9 @@ 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -233,96 +498,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 +530,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 +574,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 +616,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 +640,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 +664,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)
|
} else { // eCT == 4; attack mob
|
||||||
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 (NPCManager::NPCs.find(pktdata[i].iID) == NPCManager::NPCs.end()) {
|
||||||
|
// not sure how to best handle this
|
||||||
|
std::cout << "[WARN] pcAttackChars: NPC ID not found" << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseNPC* npc = NPCManager::NPCs[pktdata[i].iID];
|
||||||
|
if (npc->kind != EntityKind::MOB) {
|
||||||
|
std::cout << "[WARN] pcAttackChars: NPC is not a mob" << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Mob* mob = (Mob*)npc;
|
||||||
|
target = mob;
|
||||||
|
int difficulty = (int)mob->data["m_iNpcLevel"];
|
||||||
|
damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, (plr->batteryW > 6 + difficulty),
|
||||||
|
Nanos::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty);
|
||||||
|
}
|
||||||
|
|
||||||
if (plr->batteryW >= 6 + plr->level)
|
if (plr->batteryW >= 6 + plr->level)
|
||||||
plr->batteryW -= 6 + plr->level;
|
plr->batteryW -= 6 + plr->level;
|
||||||
else
|
else
|
||||||
plr->batteryW = 0;
|
plr->batteryW = 0;
|
||||||
|
|
||||||
target->HP -= damage.first;
|
damage.first = target->takeDamage(sock, damage.first);
|
||||||
|
|
||||||
respdata[i].eCT = pktdata[i*2+1];
|
respdata[i].eCT = pktdata[i].eCT;
|
||||||
respdata[i].iID = target->iID;
|
respdata[i].iID = target->getID();
|
||||||
respdata[i].iDamage = damage.first;
|
respdata[i].iDamage = damage.first;
|
||||||
respdata[i].iHP = target->HP;
|
respdata[i].iHP = target->getCurrentHP();
|
||||||
respdata[i].iHitFlag = damage.second; // hitscan, not a rocket or a grenade
|
respdata[i].iHitFlag = damage.second; // hitscan, not a rocket or a grenade
|
||||||
} else { // eCT == 4; attack mob
|
|
||||||
if (NPCManager::NPCs.find(pktdata[i*2]) == NPCManager::NPCs.end()) {
|
|
||||||
// not sure how to best handle this
|
|
||||||
std::cout << "[WARN] pcAttackChars: NPC ID not found" << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
BaseNPC* npc = NPCManager::NPCs[pktdata[i * 2]];
|
|
||||||
if (npc->type != EntityType::MOB) {
|
|
||||||
std::cout << "[WARN] pcAttackChars: NPC is not a mob" << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Mob* mob = (Mob*)npc;
|
|
||||||
|
|
||||||
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"];
|
|
||||||
|
|
||||||
damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, (plr->batteryW > 6 + 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sock->sendPacket((void*)respbuf, P_FE2CL_PC_ATTACK_CHARs_SUCC, resplen);
|
sock->sendPacket((void*)respbuf, P_FE2CL_PC_ATTACK_CHARs_SUCC, resplen);
|
||||||
@ -653,21 +834,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 +862,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 +875,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 +894,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 +902,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 +920,24 @@ 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);
|
||||||
|
|
||||||
if (plr->Nanos[plr->activeNano].iStamina <= 0)
|
|
||||||
Nanos::summonNano(sock, -1, true); // unsummon nano silently
|
|
||||||
|
|
||||||
transmit = true;
|
transmit = true;
|
||||||
} else if (plr->Nanos[plr->equippedNanos[i]].iStamina < 150) { // regain stamina
|
}
|
||||||
sNano& nano = plr->Nanos[plr->equippedNanos[i]];
|
|
||||||
nano.iStamina += 1;
|
|
||||||
|
|
||||||
if (nano.iStamina > 150)
|
// nanos
|
||||||
nano.iStamina = 150;
|
if (plr->activeNano != 0) { // tick active nano
|
||||||
|
sNano* nano = plr->getActiveNano();
|
||||||
transmit = true;
|
if (Abilities::SkillTable.find(nano->iSkillID) != Abilities::SkillTable.end()) {
|
||||||
|
// nano has skill data
|
||||||
|
SkillData* skill = &Abilities::SkillTable[nano->iSkillID];
|
||||||
|
if (skill->drainType == SkillDrainType::PASSIVE) {
|
||||||
|
ICombatant* src = dynamic_cast<ICombatant*>(plr);
|
||||||
|
int32_t targets[] = { plr->iID };
|
||||||
|
std::vector<ICombatant*> affectedCombatants = Abilities::matchTargets(src, skill, 1, targets);
|
||||||
|
Abilities::useNanoSkill(sock, skill, *nano, affectedCombatants);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -789,6 +953,20 @@ 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->combatTick() gets called in Player::step
|
||||||
|
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 +981,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);
|
||||||
|
|
||||||
|
@ -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);
|
|
||||||
}
|
}
|
||||||
|
@ -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,14 @@ 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] EntityType: " + std::to_string((int)npc->kind));
|
||||||
Chat::sendServerMessage(sock, "[WHOIS] EntityType: " + std::to_string((int)npc->type));
|
|
||||||
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 +697,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 +708,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 +980,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 +1007,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 +1022,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 +1061,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 +1079,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 +1089,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 +1101,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 +1129,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 +1148,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 +1175,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 +1187,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");
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace CustomCommands {
|
namespace CustomCommands {
|
||||||
void init();
|
void init();
|
||||||
|
|
||||||
|
162
src/Eggs.cpp
162
src/Eggs.cpp
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
|
@ -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,38 @@ 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 = 0;
|
||||||
|
data.iHP = hp;
|
||||||
|
data.iNPCType = type;
|
||||||
|
data.iNPC_ID = id;
|
||||||
|
data.iX = x;
|
||||||
|
data.iY = y;
|
||||||
|
data.iZ = z;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
sNPCAppearanceData CombatNPC::getAppearanceData() {
|
||||||
|
sNPCAppearanceData data = BaseNPC::getAppearanceData();
|
||||||
|
data.iConditionBitFlag = getCompositeCondition();
|
||||||
|
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 +70,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 +80,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 +122,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
174
src/Entities.hpp
174
src/Entities.hpp
@ -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,39 @@ 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, BuffClass) = 0;
|
||||||
Entity *getEntity() const;
|
virtual void clearBuffs(bool) = 0;
|
||||||
|
virtual bool hasBuff(int) = 0;
|
||||||
bool operator==(const EntityRef& other) const {
|
virtual int getCompositeCondition() = 0;
|
||||||
if (type != other.type)
|
virtual int takeDamage(EntityRef, int) = 0;
|
||||||
return false;
|
virtual int heal(EntityRef, int) = 0;
|
||||||
|
virtual bool isAlive() = 0;
|
||||||
if (type == EntityType::PLAYER)
|
virtual int getCurrentHP() = 0;
|
||||||
return sock == other.sock;
|
virtual int getMaxHP() = 0;
|
||||||
|
virtual int getLevel() = 0;
|
||||||
return id == other.id;
|
virtual std::vector<EntityRef> getGroupMembers() = 0;
|
||||||
}
|
virtual int32_t getCharType() = 0;
|
||||||
|
virtual int32_t getID() = 0;
|
||||||
// arbitrary ordering
|
virtual EntityRef getRef() = 0;
|
||||||
bool operator<(const EntityRef& other) const {
|
virtual void step(time_t currTime) = 0;
|
||||||
if (type == other.type) {
|
|
||||||
if (type == EntityType::PLAYER)
|
|
||||||
return sock < other.sock;
|
|
||||||
else
|
|
||||||
return id < other.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return type < other.type;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -74,75 +69,104 @@ struct EntityRef {
|
|||||||
*/
|
*/
|
||||||
class BaseNPC : public Entity {
|
class BaseNPC : public Entity {
|
||||||
public:
|
public:
|
||||||
sNPCAppearanceData appearanceData = {};
|
int id;
|
||||||
|
int type;
|
||||||
|
int hp;
|
||||||
|
int angle;
|
||||||
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;
|
id = _id;
|
||||||
appearanceData.iAngle = angle;
|
|
||||||
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;
|
||||||
|
|
||||||
|
virtual 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
|
std::unordered_map<int, Buff*> buffs = {};
|
||||||
CombatNPC(int x, int y, int z, int angle, uint64_t iID, int t, int id, int maxHP) :
|
|
||||||
BaseNPC(x, y, z, angle, iID, t, id),
|
|
||||||
maxHealth(maxHP) {}
|
|
||||||
|
|
||||||
virtual void stepAI(time_t currTime) {
|
CombatNPC(int x, int y, int z, int angle, uint64_t iID, int t, int id, int maxHP)
|
||||||
if (_stepAI != nullptr)
|
: BaseNPC(angle, iID, t, id), maxHealth(maxHP) {
|
||||||
_stepAI(this, currTime);
|
spawnX = x;
|
||||||
|
spawnY = y;
|
||||||
|
spawnZ = z;
|
||||||
|
|
||||||
|
kind = EntityKind::COMBAT_NPC;
|
||||||
|
|
||||||
|
stateHandlers[AIState::INACTIVE] = {};
|
||||||
|
transitionHandlers[AIState::INACTIVE] = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual bool isAlive() override { return appearanceData.iHP > 0; }
|
virtual sNPCAppearanceData getAppearanceData() override;
|
||||||
|
|
||||||
|
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, BuffClass buffClass) override;
|
||||||
|
virtual void clearBuffs(bool force) 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
56
src/EntityRef.hpp
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/Core.hpp"
|
||||||
|
|
||||||
|
/* forward declaration(s) */
|
||||||
|
struct Entity;
|
||||||
|
|
||||||
|
enum EntityKind {
|
||||||
|
INVALID,
|
||||||
|
PLAYER,
|
||||||
|
SIMPLE_NPC,
|
||||||
|
COMBAT_NPC,
|
||||||
|
MOB,
|
||||||
|
EGG,
|
||||||
|
BUS
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EntityRef {
|
||||||
|
EntityKind kind;
|
||||||
|
union {
|
||||||
|
CNSocket *sock;
|
||||||
|
int32_t id;
|
||||||
|
};
|
||||||
|
|
||||||
|
EntityRef(CNSocket *s);
|
||||||
|
EntityRef(int32_t i);
|
||||||
|
|
||||||
|
bool isValid() const;
|
||||||
|
Entity *getEntity() const;
|
||||||
|
|
||||||
|
bool operator==(const EntityRef& other) const {
|
||||||
|
if (kind != other.kind)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (kind == EntityKind::PLAYER)
|
||||||
|
return sock == other.sock;
|
||||||
|
|
||||||
|
return id == other.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const EntityRef& other) const {
|
||||||
|
return !(*this == other);
|
||||||
|
}
|
||||||
|
|
||||||
|
// arbitrary ordering
|
||||||
|
bool operator<(const EntityRef& other) const {
|
||||||
|
if (kind == other.kind) {
|
||||||
|
if (kind == EntityKind::PLAYER)
|
||||||
|
return sock < other.sock;
|
||||||
|
else
|
||||||
|
return id < other.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return kind < other.kind;
|
||||||
|
}
|
||||||
|
};
|
443
src/Groups.cpp
443
src/Groups.cpp
@ -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() {
|
||||||
|
@ -1,17 +1,37 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Player.hpp"
|
#include "EntityRef.hpp"
|
||||||
#include "core/Core.hpp"
|
|
||||||
#include "servers/CNShardServer.hpp"
|
|
||||||
|
|
||||||
#include <map>
|
#include <vector>
|
||||||
#include <list>
|
#include <assert.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
EntityRef getLeader() {
|
||||||
|
assert(members.size() > 0);
|
||||||
|
return members[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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) {
|
||||||
|
@ -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;
|
||||||
|
635
src/MobAI.cpp
635
src/MobAI.cpp
@ -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 (hasBuff(ECSB_MEZ)) {
|
||||||
|
removeBuff(ECSB_MEZ);
|
||||||
|
|
||||||
|
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1);
|
||||||
|
pkt1.eCT = 2;
|
||||||
|
pkt1.iID = id;
|
||||||
|
pkt1.iConditionBitFlag = getCompositeCondition();
|
||||||
|
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,43 @@ 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->clearBuffs(false);
|
||||||
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->getCompositeCondition();
|
||||||
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 +140,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 +175,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 +186,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 +195,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 +216,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 +244,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 +282,29 @@ 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};
|
SkillData skill = Abilities::SkillTable[skillID];
|
||||||
for (auto& pwr : Nanos::NanoPowers)
|
skill.durationTime[0] = 0;
|
||||||
if (pwr.skillType == EST_DAMAGE)
|
skill.values[0][0] = 200; // have to set
|
||||||
pwr.handle(sock, targetData2, plr->activeNano, skillID, 0, 200);
|
skill.values[0][1] = 200; // all of these
|
||||||
|
skill.values[0][2] = 200; // because the player might
|
||||||
|
skill.values[0][3] = 200; // have a boost
|
||||||
|
Abilities::useNanoSkill(sock, &skill, *plr->getActiveNano(), { mob });
|
||||||
} 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 +316,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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -282,12 +328,6 @@ static void dealCorruption(Mob *mob, std::vector<int> targetData, int skillID, i
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void useAbilities(Mob *mob, time_t currTime) {
|
static void useAbilities(Mob *mob, time_t currTime) {
|
||||||
/*
|
|
||||||
* targetData approach
|
|
||||||
* first integer is the count
|
|
||||||
* second to fifth integers are IDs, these can be either player iID or mob's iID
|
|
||||||
* whether the skill targets players or mobs is determined by the skill packet being fired
|
|
||||||
*/
|
|
||||||
Player *plr = PlayerManager::getPlayer(mob->target);
|
Player *plr = PlayerManager::getPlayer(mob->target);
|
||||||
|
|
||||||
if (mob->skillStyle >= 0) { // corruption hit
|
if (mob->skillStyle >= 0) { // corruption hit
|
||||||
@ -302,35 +342,32 @@ static void useAbilities(Mob *mob, time_t currTime) {
|
|||||||
|
|
||||||
if (mob->skillStyle == -2) { // eruption hit
|
if (mob->skillStyle == -2) { // eruption hit
|
||||||
int skillID = (int)mob->data["m_iMegaType"];
|
int skillID = (int)mob->data["m_iMegaType"];
|
||||||
std::vector<int> targetData = {0, 0, 0, 0, 0};
|
std::vector<ICombatant*> targets{};
|
||||||
|
|
||||||
// find the players within range of eruption
|
// find the players within range of eruption
|
||||||
for (auto it = mob->viewableChunks.begin(); it != mob->viewableChunks.end(); it++) {
|
for (auto it = mob->viewableChunks.begin(); it != mob->viewableChunks.end(); it++) {
|
||||||
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;
|
||||||
Player *plr = PlayerManager::getPlayer(s);
|
Player *plr = PlayerManager::getPlayer(s);
|
||||||
|
|
||||||
if (plr->HP <= 0)
|
if (!plr->isAlive())
|
||||||
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;
|
targets.push_back(plr);
|
||||||
targetData[targetData[0]] = plr->iID;
|
if (targets.size() > 3) // make sure not to have more than 4
|
||||||
if (targetData[0] > 3) // make sure not to have more than 4
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& pwr : Combat::MobPowers)
|
Abilities::useNPCSkill(mob->id, skillID, targets);
|
||||||
if (pwr.skillType == Nanos::SkillTable[skillID].skillType)
|
|
||||||
pwr.handle(mob, targetData, skillID, Nanos::SkillTable[skillID].durationTime[0], Nanos::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 +385,11 @@ 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};
|
SkillData* skill = &Abilities::SkillTable[skillID];
|
||||||
for (auto& pwr : Combat::MobPowers)
|
int debuffID = Abilities::getCSTBFromST(skill->skillType);
|
||||||
if (pwr.skillType == Nanos::SkillTable[skillID].skillType) {
|
if(plr->hasBuff(debuffID))
|
||||||
if (pwr.bitFlag != 0 && (plr->iConditionBitFlag & pwr.bitFlag))
|
|
||||||
return; // prevent debuffing a player twice
|
return; // prevent debuffing a player twice
|
||||||
pwr.handle(mob, targetData, skillID, Nanos::SkillTable[skillID].durationTime[0], Nanos::SkillTable[skillID].powerIntensity[0]);
|
Abilities::useNPCSkill(mob->getRef(), skillID, { plr });
|
||||||
}
|
|
||||||
mob->nextAttack = currTime + (int)mob->data["m_iDelayTime"] * 100;
|
mob->nextAttack = currTime + (int)mob->data["m_iDelayTime"] * 100;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -362,7 +397,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 +416,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,95 +430,58 @@ static void useAbilities(Mob *mob, time_t currTime) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MobAI::enterCombat(CNSocket *sock, Mob *mob) {
|
void MobAI::incNextMovement(Mob* mob, time_t currTime) {
|
||||||
mob->target = sock;
|
if (currTime == 0)
|
||||||
mob->state = MobState::COMBAT;
|
currTime = getTime();
|
||||||
mob->nextMovement = getTime();
|
|
||||||
mob->nextAttack = 0;
|
|
||||||
|
|
||||||
mob->roamX = mob->x;
|
int delay = (int)mob->data["m_iDelayTime"] * 1000;
|
||||||
mob->roamY = mob->y;
|
mob->nextMovement = currTime + delay / 2 + Rand::rand(delay / 2);
|
||||||
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) {
|
void MobAI::deadStep(CombatNPC* npc, time_t currTime) {
|
||||||
size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_Damage);
|
Mob* self = (Mob*)npc;
|
||||||
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
|
|
||||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
|
||||||
|
|
||||||
memset(respbuf, 0, resplen);
|
|
||||||
|
|
||||||
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));
|
|
||||||
|
|
||||||
pkt->iID = mob->appearanceData.iNPC_ID;
|
|
||||||
pkt->eCT = 4; // mob
|
|
||||||
pkt->iTB_ID = ECSB_BOUNDINGBALL;
|
|
||||||
|
|
||||||
drain->eCT = 4;
|
|
||||||
drain->iID = mob->appearanceData.iNPC_ID;
|
|
||||||
drain->iDamage = amount;
|
|
||||||
drain->iHP = mob->appearanceData.iHP -= amount;
|
|
||||||
|
|
||||||
NPCManager::sendToViewable(mob, (void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen);
|
|
||||||
|
|
||||||
if (mob->appearanceData.iHP <= 0)
|
|
||||||
Combat::killMob(mob->target, mob);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void deadStep(Mob *mob, time_t currTime) {
|
|
||||||
// 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 +489,147 @@ 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
|
// tick buffs
|
||||||
if (mob->skillStyle < 0 && (mob->lastDrainTime == 0 || currTime - mob->lastDrainTime >= 1000)
|
auto it = npc->buffs.begin();
|
||||||
&& mob->appearanceData.iConditionBitFlag & CSB_BIT_BOUNDINGBALL) {
|
while(it != npc->buffs.end()) {
|
||||||
drainMobHP(mob, mob->maxHealth / 20); // lose 5% every second
|
Buff* buff = (*it).second;
|
||||||
mob->lastDrainTime = currTime;
|
buff->combatTick(currTime);
|
||||||
}
|
|
||||||
|
|
||||||
// if drain killed the mob, return early
|
// if mob state changed, end the step
|
||||||
if (mob->appearanceData.iHP <= 0)
|
if(self->state != AIState::COMBAT)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// unbuffing
|
buff->tick(currTime);
|
||||||
std::unordered_map<int32_t, time_t>::iterator it = mob->unbuffTimes.begin();
|
if(buff->isStale()) {
|
||||||
while (it != mob->unbuffTimes.end()) {
|
// garbage collect
|
||||||
|
it = npc->buffs.erase(it);
|
||||||
if (currTime >= it->second) {
|
delete buff;
|
||||||
mob->appearanceData.iConditionBitFlag &= ~it->first;
|
|
||||||
|
|
||||||
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));
|
|
||||||
|
|
||||||
it = mob->unbuffTimes.erase(it);
|
|
||||||
} else {
|
|
||||||
it++;
|
|
||||||
}
|
}
|
||||||
|
else 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->hasBuff(ECSB_STUN) || self->hasBuff(ECSB_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;
|
int speed = self->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->hasBuff(ECSB_DN_MOVE_SPEED))
|
||||||
speed /= 2;
|
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, 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 < 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 = 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 +637,208 @@ 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->hasBuff(ECSB_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;
|
void MobAI::onRoamStart(CombatNPC* npc, EntityRef src) {
|
||||||
|
Mob* self = (Mob*)npc;
|
||||||
|
|
||||||
|
self->hp = self->maxHealth;
|
||||||
|
self->killedTime = 0;
|
||||||
|
self->nextAttack = 0;
|
||||||
|
|
||||||
// cast a return home heal spell, this is the right way(tm)
|
// cast a return home heal spell, this is the right way(tm)
|
||||||
std::vector<int> targetData = {1, 0, 0, 0, 0};
|
Abilities::useNPCSkill(npc->getRef(), 110, { npc });
|
||||||
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
|
// clear outlying debuffs
|
||||||
clearDebuff(mob);
|
clearDebuff(self);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MobAI::step(CombatNPC *npc, time_t currTime) {
|
void MobAI::onCombatStart(CombatNPC* npc, EntityRef src) {
|
||||||
assert(npc->type == EntityType::MOB);
|
Mob* self = (Mob*)npc;
|
||||||
auto mob = (Mob*)npc;
|
|
||||||
|
|
||||||
if (mob->playersInView < 0)
|
assert(src.kind == EntityKind::PLAYER);
|
||||||
std::cout << "[WARN] Weird playerview value " << mob->playersInView << std::endl;
|
self->target = src.sock;
|
||||||
|
self->nextMovement = getTime();
|
||||||
|
self->nextAttack = 0;
|
||||||
|
|
||||||
// skip mob movement and combat if disabled or not in view
|
self->roamX = self->x;
|
||||||
if ((!simulateMobs || mob->playersInView == 0) && mob->state != MobState::DEAD
|
self->roamY = self->y;
|
||||||
&& mob->state != MobState::RETREAT)
|
self->roamZ = self->z;
|
||||||
|
|
||||||
|
int skillID = (int)self->data["m_iPassiveBuff"];
|
||||||
|
if(skillID != 0) // cast passive
|
||||||
|
Abilities::useNPCSkill(npc->getRef(), skillID, { npc });
|
||||||
|
}
|
||||||
|
|
||||||
|
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->skillStyle = -1;
|
||||||
|
self->clearBuffs(true);
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,26 @@
|
|||||||
#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
|
|
||||||
MobState state = MobState::INACTIVE;
|
|
||||||
|
|
||||||
std::unordered_map<int32_t,time_t> unbuffTimes = {};
|
|
||||||
|
|
||||||
// dead
|
// dead
|
||||||
time_t killedTime = 0;
|
time_t killedTime = 0;
|
||||||
@ -47,16 +47,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 +62,28 @@ 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;
|
|
||||||
|
|
||||||
// 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 +94,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 +111,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);
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Chunking.hpp"
|
|
||||||
#include "Entities.hpp"
|
|
@ -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
|
||||||
@ -373,5 +376,5 @@ void NPCManager::init() {
|
|||||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_NPC_UNSUMMON, npcUnsummonHandler);
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_NPC_UNSUMMON, npcUnsummonHandler);
|
||||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_BARKER, npcBarkHandler);
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_BARKER, npcBarkHandler);
|
||||||
|
|
||||||
REGISTER_SHARD_TIMER(step, 200);
|
REGISTER_SHARD_TIMER(step, MS_PER_COMBAT_TICK);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
|
||||||
@ -82,40 +82,21 @@ 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))
|
ICombatant* src = dynamic_cast<ICombatant*>(plr);
|
||||||
boost = 1;
|
int32_t targets[] = { plr->iID };
|
||||||
|
std::vector<ICombatant*> affectedCombatants = Abilities::matchTargets(src, skill, 1, targets);
|
||||||
for (auto& pwr : NanoPowers) {
|
Abilities::useNanoSkill(sock, skill, nano, affectedCombatants);
|
||||||
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 +105,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 +192,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 +216,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,28 +258,26 @@ 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));
|
||||||
|
Abilities::useNanoSkill(sock, skillData, nano, targetData);
|
||||||
|
|
||||||
int boost = 0;
|
if (plr->Nanos[plr->activeNano].iStamina <= 0)
|
||||||
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)
|
|
||||||
summonNano(sock, -1);
|
summonNano(sock, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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,31 @@ 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, BuffClass buffClass) override;
|
||||||
|
virtual void clearBuffs(bool force) 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();
|
||||||
};
|
};
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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];
|
||||||
Nanos::SkillTable[skills["m_iSkillNumber"]] = skillData;
|
skillData.values[1][i] = skill["m_iValueB"][i];
|
||||||
|
skillData.values[2][i] = skill["m_iValueC"][i];
|
||||||
}
|
}
|
||||||
|
|
||||||
std::cout << "[INFO] Loaded " << Nanos::SkillTable.size() << " nano skills" << std::endl;
|
Abilities::SkillTable[skill["m_iSkillNumber"]] = skillData;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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"
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Items.hpp"
|
|
||||||
|
|
||||||
namespace Trading {
|
namespace Trading {
|
||||||
void init();
|
void init();
|
||||||
}
|
}
|
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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; \
|
||||||
|
@ -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,
|
||||||
|
@ -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 {
|
||||||
|
@ -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) {
|
||||||
|
@ -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>
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -3,9 +3,12 @@
|
|||||||
#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
|
||||||
|
#define MS_PER_COMBAT_TICK 200
|
||||||
|
|
||||||
class CNShardServer : public CNServer {
|
class CNShardServer : public CNServer {
|
||||||
private:
|
private:
|
||||||
|
@ -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>
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
@ -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
2
vendor/JSON.hpp
vendored
@ -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_
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user