mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2024-11-22 13:30:06 +00:00
247 lines
8.3 KiB
C++
247 lines
8.3 KiB
C++
#include "Eggs.hpp"
|
|
|
|
#include "servers/CNShardServer.hpp"
|
|
|
|
#include "Player.hpp"
|
|
#include "PlayerManager.hpp"
|
|
#include "Abilities.hpp"
|
|
#include "NPCManager.hpp"
|
|
#include "Entities.hpp"
|
|
#include "Items.hpp"
|
|
|
|
#include <assert.h>
|
|
|
|
using namespace Eggs;
|
|
|
|
std::unordered_map<int, EggType> Eggs::EggTypes;
|
|
|
|
void Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) {
|
|
Player* plr = PlayerManager::getPlayer(sock);
|
|
|
|
// eggId might be 0 if the buff is made by the /buff command
|
|
EntityRef src = eggId == 0 ? sock : EntityRef(eggId);
|
|
|
|
if(Abilities::SkillTable.count(skillId) == 0) {
|
|
std::cout << "[WARN] egg " << eggId << " has skill ID " << skillId << " which doesn't exist" << std::endl;
|
|
return;
|
|
}
|
|
|
|
SkillResult result = SkillResult();
|
|
SkillData* skill = &Abilities::SkillTable[skillId];
|
|
if(skill->drainType == SkillDrainType::PASSIVE) {
|
|
// apply buff
|
|
if(skill->targetType != SkillTargetType::PLAYERS) {
|
|
std::cout << "[WARN] weird skill type for egg " << eggId << " with skill " << skillId << ", should be " << (int)skill->targetType << std::endl;
|
|
}
|
|
|
|
int timeBuffId = Abilities::getCSTBFromST(skill->skillType);
|
|
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);
|
|
|
|
sSkillResult_Buff resultBuff{};
|
|
resultBuff.eCT = plr->getCharType();
|
|
resultBuff.iID = plr->getID();
|
|
resultBuff.bProtected = false;
|
|
resultBuff.iConditionBitFlag = plr->getCompositeCondition();
|
|
result = SkillResult(sizeof(sSkillResult_Buff), &resultBuff);
|
|
} else {
|
|
int value = plr->getMaxHP() * skill->values[0][0] / 1000;
|
|
sSkillResult_Damage resultDamage{};
|
|
sSkillResult_Heal_HP resultHeal{};
|
|
switch(skill->skillType)
|
|
{
|
|
case SkillType::DAMAGE:
|
|
resultDamage.bProtected = false;
|
|
resultDamage.eCT = plr->getCharType();
|
|
resultDamage.iID = plr->getID();
|
|
resultDamage.iDamage = plr->takeDamage(src, value);
|
|
resultDamage.iHP = plr->getCurrentHP();
|
|
result = SkillResult(sizeof(sSkillResult_Damage), &resultDamage);
|
|
break;
|
|
case SkillType::HEAL_HP:
|
|
resultHeal.eCT = plr->getCharType();
|
|
resultHeal.iID = plr->getID();
|
|
resultHeal.iHealHP = plr->heal(src, value);
|
|
resultHeal.iHP = plr->getCurrentHP();
|
|
result = SkillResult(sizeof(sSkillResult_Heal_HP), &resultHeal);
|
|
break;
|
|
default:
|
|
std::cout << "[WARN] oops, egg with active skill type " << (int)skill->skillType << " unhandled";
|
|
return;
|
|
}
|
|
}
|
|
|
|
// initialize response struct
|
|
size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + result.size;
|
|
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
|
memset(respbuf, 0, resplen);
|
|
|
|
sP_FE2CL_NPC_SKILL_HIT* pkt = (sP_FE2CL_NPC_SKILL_HIT*)respbuf;
|
|
pkt->iNPC_ID = eggId;
|
|
pkt->iSkillID = skillId;
|
|
pkt->eST = (int32_t)skill->skillType;
|
|
pkt->iTargetCnt = 1;
|
|
|
|
if(result.size > 0) {
|
|
void* attached = (void*)(pkt + 1);
|
|
memcpy(attached, result.payload, result.size);
|
|
}
|
|
|
|
NPCManager::sendToViewable(src.getEntity(), pkt, P_FE2CL_NPC_SKILL_HIT, resplen);
|
|
}
|
|
|
|
static void eggStep(CNServer* serv, time_t currTime) {
|
|
// check dead eggs and eggs in inactive chunks
|
|
for (auto npc : NPCManager::NPCs) {
|
|
if (npc.second->kind != EntityKind::EGG)
|
|
continue;
|
|
|
|
auto egg = (Egg*)npc.second;
|
|
if (!egg->dead || !Chunking::inPopulatedChunks(&egg->viewableChunks))
|
|
continue;
|
|
|
|
if (egg->deadUntil <= currTime) {
|
|
// respawn it
|
|
egg->dead = false;
|
|
egg->deadUntil = 0;
|
|
egg->hp = 400;
|
|
|
|
Chunking::addEntityToChunks(Chunking::getViewableChunks(egg->chunkPos), {npc.first});
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void Eggs::npcDataToEggData(int x, int y, int z, sNPCAppearanceData* npc, sShinyAppearanceData* egg) {
|
|
egg->iX = x;
|
|
egg->iY = y;
|
|
egg->iZ = z;
|
|
// client doesn't care about egg->iMapNum
|
|
egg->iShinyType = npc->iNPCType;
|
|
egg->iShiny_ID = npc->iNPC_ID;
|
|
}
|
|
|
|
static void eggPickup(CNSocket* sock, CNPacketData* data) {
|
|
auto pickup = (sP_CL2FE_REQ_SHINY_PICKUP*)data->buf;
|
|
Player* plr = PlayerManager::getPlayer(sock);
|
|
|
|
EntityRef eggRef = {pickup->iShinyID};
|
|
|
|
if (!eggRef.isValid()) {
|
|
std::cout << "[WARN] Player tried to open non existing egg?!" << std::endl;
|
|
return;
|
|
}
|
|
auto egg = (Egg*)eggRef.getEntity();
|
|
if (egg->kind != EntityKind::EGG) {
|
|
std::cout << "[WARN] Player tried to open something other than an?!" << std::endl;
|
|
return;
|
|
}
|
|
|
|
if (egg->dead) {
|
|
std::cout << "[WARN] Player tried to open a dead egg?!" << std::endl;
|
|
return;
|
|
}
|
|
|
|
/* this has some issues with position desync, leaving it out for now
|
|
if (abs(egg->x - plr->x)>500 || abs(egg->y - plr->y) > 500) {
|
|
std::cout << "[WARN] Player tried to open an egg isn't nearby?!" << std::endl;
|
|
return;
|
|
}
|
|
*/
|
|
|
|
int typeId = egg->type;
|
|
if (EggTypes.find(typeId) == EggTypes.end()) {
|
|
std::cout << "[WARN] Egg Type " << typeId << " not found!" << std::endl;
|
|
return;
|
|
}
|
|
|
|
EggType* type = &EggTypes[typeId];
|
|
|
|
/*
|
|
* SHINY_PICKUP_SUCC is only causing a GUI effect in the client
|
|
* (buff icon pops up in the bottom of the screen)
|
|
* so we don't send it for non-effect
|
|
*/
|
|
if (type->effectId != 0) {
|
|
eggBuffPlayer(sock, type->effectId, eggRef.id, type->duration);
|
|
INITSTRUCT(sP_FE2CL_REP_SHINY_PICKUP_SUCC, resp);
|
|
resp.iSkillID = type->effectId;
|
|
|
|
// in general client finds correct icon on it's own,
|
|
// but for damage we have to supply correct CSTB
|
|
if (resp.iSkillID == 183)
|
|
resp.eCSTB = ECSB_INFECTION;
|
|
|
|
sock->sendPacket(resp, P_FE2CL_REP_SHINY_PICKUP_SUCC);
|
|
}
|
|
|
|
// drop
|
|
if (type->dropCrateId != 0) {
|
|
const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward);
|
|
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
|
|
// we know it's only one trailing struct, so we can skip full validation
|
|
|
|
uint8_t respbuf[resplen]; // not a variable length array, don't worry
|
|
sP_FE2CL_REP_REWARD_ITEM* reward = (sP_FE2CL_REP_REWARD_ITEM*)respbuf;
|
|
sItemReward* item = (sItemReward*)(respbuf + sizeof(sP_FE2CL_REP_REWARD_ITEM));
|
|
|
|
// don't forget to zero the buffer!
|
|
memset(respbuf, 0, resplen);
|
|
|
|
// send back player's stats
|
|
reward->m_iCandy = plr->money;
|
|
reward->m_iFusionMatter = plr->fusionmatter;
|
|
reward->m_iBatteryN = plr->batteryN;
|
|
reward->m_iBatteryW = plr->batteryW;
|
|
reward->iFatigue = 100; // prevents warning message
|
|
reward->iFatigue_Level = 1;
|
|
reward->iItemCnt = 1; // remember to update resplen if you change this
|
|
|
|
int slot = Items::findFreeSlot(plr);
|
|
|
|
// no space for drop
|
|
if (slot != -1) {
|
|
|
|
// item reward
|
|
item->sItem.iType = 9;
|
|
item->sItem.iOpt = 1;
|
|
item->sItem.iID = type->dropCrateId;
|
|
item->iSlotNum = slot;
|
|
item->eIL = 1; // Inventory Location. 1 means player inventory.
|
|
|
|
// update player
|
|
plr->Inven[slot] = item->sItem;
|
|
sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen);
|
|
}
|
|
}
|
|
|
|
if (egg->summoned)
|
|
NPCManager::destroyNPC(eggRef.id);
|
|
else {
|
|
Chunking::removeEntityFromChunks(Chunking::getViewableChunks(egg->chunkPos), eggRef);
|
|
egg->dead = true;
|
|
egg->deadUntil = getTime() + (time_t)type->regen * 1000;
|
|
egg->hp = 0;
|
|
}
|
|
}
|
|
|
|
void Eggs::init() {
|
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_SHINY_PICKUP, eggPickup);
|
|
|
|
REGISTER_SHARD_TIMER(eggStep, 1000);
|
|
}
|