implemented basic eggs functionality

This commit is contained in:
Kamil 2020-10-24 21:48:55 +02:00 committed by Gent
parent de99522340
commit 674d5112f3
8 changed files with 257 additions and 13 deletions

View File

@ -7,6 +7,7 @@
#include "NanoManager.hpp"
#include <cmath>
#define M_PI 3.14159265358979323846
#include <algorithm>
#include <list>
#include <fstream>
@ -21,6 +22,8 @@ std::map<int32_t, WarpLocation> NPCManager::Warps;
std::vector<WarpLocation> NPCManager::RespawnPoints;
/// sock, CBFlag -> until
std::map<std::pair<CNSocket*, int32_t>, time_t> NPCManager::EggBuffs;
std::unordered_map<int, EggType> NPCManager::EggTypes;
std::unordered_map<int, Egg*> NPCManager::Eggs;
nlohmann::json NPCManager::NPCData;
/*
@ -43,8 +46,10 @@ void NPCManager::init() {
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_ITEM_RESTORE_BUY, npcVendorBuyback);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VENDOR_BATTERY_BUY, npcVendorBuyBattery);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ITEM_COMBINATION, npcCombineItems);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_SHINY_SUMMON, eggSummon);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_SHINY_PICKUP, eggPickup);
REGISTER_SHARD_TIMER(buffStep, 1000);
REGISTER_SHARD_TIMER(eggStep, 1000);
}
void NPCManager::removeNPC(std::vector<Chunk*> viewableChunks, int32_t id) {
@ -63,6 +68,17 @@ void NPCManager::removeNPC(std::vector<Chunk*> viewableChunks, int32_t id) {
}
}
break;
case NPC_EGG:
INITSTRUCT(sP_FE2CL_SHINY_EXIT, exitEggData);
exitEggData.iShinyID = id;
for (Chunk* chunk : viewableChunks) {
for (CNSocket* sock : chunk->players) {
// send to socket
sock->sendPacket((void*)&exitEggData, P_FE2CL_SHINY_EXIT, sizeof(sP_FE2CL_SHINY_EXIT));
}
}
break;
default:
// create struct
INITSTRUCT(sP_FE2CL_NPC_EXIT, exitData);
@ -94,6 +110,17 @@ void NPCManager::addNPC(std::vector<Chunk*> viewableChunks, int32_t id) {
}
}
break;
case NPC_EGG:
INITSTRUCT(sP_FE2CL_SHINY_ENTER, enterEggData);
npcDataToEggData(&npc->appearanceData, &enterEggData.ShinyAppearanceData);
for (Chunk* chunk : viewableChunks) {
for (CNSocket* sock : chunk->players) {
// send to socket
sock->sendPacket((void*)&enterEggData, P_FE2CL_SHINY_ENTER, sizeof(sP_FE2CL_SHINY_ENTER));
}
}
break;
default:
// create struct
INITSTRUCT(sP_FE2CL_NPC_ENTER, enterData);
@ -135,6 +162,10 @@ void NPCManager::destroyNPC(int32_t id) {
if (MobManager::Mobs.find(id) != MobManager::Mobs.end())
MobManager::Mobs.erase(id);
// remove from eggs
if (Eggs.find(id) != Eggs.end())
Eggs.erase(id);
// finally, remove it from the map and free it
NPCs.erase(id);
delete entity;
@ -654,7 +685,6 @@ BaseNPC* NPCManager::getNearestNPC(std::vector<Chunk*> chunks, int X, int Y, int
return npc;
}
int NPCManager::eggBuffPlayer(CNSocket* sock, int skillId, int duration) {
Player* plr = PlayerManager::getPlayer(sock);
@ -702,12 +732,12 @@ int NPCManager::eggBuffPlayer(CNSocket* sock, int skillId, int duration) {
return 0;
}
void NPCManager::buffStep(CNServer* serv, time_t currTime) {
void NPCManager::eggStep(CNServer* serv, time_t currTime) {
// tick buffs
auto it = EggBuffs.begin();
while (it != EggBuffs.end()) {
// check remaining time
if (it->second > getTimestamp())
if (it->second > currTime)
it++;
// if time reached 0
@ -747,4 +777,148 @@ void NPCManager::buffStep(CNServer* serv, time_t currTime) {
}
}
// check dead eggs
for (auto egg : Eggs) {
if (!egg.second->dead)
return;
if (egg.second->deadUntil <= currTime) {
// respawn it
addNPC(egg.second->currentChunks, egg.first);
egg.second->dead = false;
}
}
}
void NPCManager::npcDataToEggData(sNPCAppearanceData* npc, sShinyAppearanceData* egg) {
egg->iX = npc->iX;
egg->iY = npc->iY;
egg->iZ = npc->iZ;
// client doesn't care about egg->iMapNum
egg->iShinyType = npc->iNPCType;
egg->iShiny_ID = npc->iNPC_ID;
}
void NPCManager::eggSummon(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_SHINY_SUMMON))
return; // malformed packet
sP_CL2FE_REQ_SHINY_SUMMON* summon = (sP_CL2FE_REQ_SHINY_SUMMON*)data->buf;
assert(NPCManager::nextId < INT32_MAX);
int id = NPCManager::nextId++;
Player* plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return;
/*
* the packet sends us player position with a random offset,
* instead we're using some math to place the egg right in front of the player
*/
int addX = -500.0f * sin(plr->angle / 180.0f * M_PI);
int addY = -500.0f * cos(plr->angle / 180.0f * M_PI);
Egg* egg = new Egg (plr->x + addX, plr->y + addY, plr->z, plr->instanceID, summon->iShinyType, id, false);
NPCManager::NPCs[id] = egg;
NPCManager::Eggs[id] = egg;
NPCManager::updateNPCPosition(id, plr->x + addX, plr->y + addY, plr->z, plr->instanceID);
}
void NPCManager::eggPickup(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_SHINY_PICKUP))
return; // malformed packet
sP_CL2FE_REQ_SHINY_PICKUP* pickup = (sP_CL2FE_REQ_SHINY_PICKUP*)data->buf;
Player* plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return;
int eggId = pickup->iShinyID;
if (Eggs.find(eggId) == Eggs.end()) {
std::cout << "[WARN] Player tried to open non existing egg?!" << std::endl;
return;
}
Egg* egg = Eggs[eggId];
if (egg->dead) {
std::cout << "[WARN] Player tried to open a dead egg?!" << std::endl;
return;
}
int typeId = egg->appearanceData.iNPCType;
if (EggTypes.find(typeId) == EggTypes.end()) {
if (egg->npcClass != NPCClass::NPC_EGG) {
std::cout << "[WARN] Egg Type "<< typeId <<" not found!" << std::endl;
return;
}
}
EggType* type = &EggTypes[typeId];
// response packet
INITSTRUCT(sP_FE2CL_REP_SHINY_PICKUP_SUCC, resp);
resp.iSkillID = type->effectId;
if (resp.iSkillID == 0)
resp.iSkillID = -1;
// this packet is only responsible for showing UI effect stuff
sock->sendPacket((void*)&resp, P_FE2CL_REP_SHINY_PICKUP_SUCC, sizeof(sP_FE2CL_REP_SHINY_PICKUP_SUCC));
// buff
if (type->effectId != 0)
eggBuffPlayer(sock, type->effectId, type->duration);
// 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 = ItemManager::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)
destroyNPC(eggId);
else {
removeNPC(egg->currentChunks, eggId);
egg->dead = true;
egg->deadUntil = getTimestamp() + type->regen;
}
}

View File

@ -7,6 +7,7 @@
#include "contrib/JSON.hpp"
#include <map>
#include <unordered_map>
#include <vector>
#define RESURRECT_HEIGHT 400
@ -16,11 +17,32 @@ struct WarpLocation {
int x, y, z, instanceID, isInstance, limitTaskID, npcID;
};
struct Egg : public BaseNPC {
bool summoned;
bool dead = false;
time_t deadUntil;
Egg(int x, int y, int z, uint64_t iID, int type, int32_t id, bool summon)
: BaseNPC(x, y, z, 0, iID, type, id) {
summoned = summon;
npcClass = NPCClass::NPC_EGG;
}
};
struct EggType {
int dropCrateId;
int effectId;
int duration;
int regen;
};
namespace NPCManager {
extern std::map<int32_t, BaseNPC*> NPCs;
extern std::map<int32_t, WarpLocation> Warps;
extern std::vector<WarpLocation> RespawnPoints;
extern std::vector<WarpLocation> RespawnPoints;
extern std::unordered_map<int, Egg*> Eggs;
extern std::map<std::pair<CNSocket*, int32_t>, time_t> EggBuffs;
extern std::unordered_map<int, EggType> EggTypes;
extern nlohmann::json NPCData;
extern int32_t nextId;
void init();
@ -54,5 +76,8 @@ namespace NPCManager {
/// returns -1 on fail
int eggBuffPlayer(CNSocket* sock, int skillId, int duration);
void buffStep(CNServer* serv, time_t currTime);
void eggStep(CNServer* serv, time_t currTime);
void npcDataToEggData(sNPCAppearanceData* npc, sShinyAppearanceData* egg);
void eggSummon(CNSocket* sock, CNPacketData* data);
void eggPickup(CNSocket* sock, CNPacketData* data);
}

View File

@ -23,15 +23,15 @@ std::set<int> SleepPowers = {28, 30, 32, 49, 70, 71, 81, 85, 94};
// passive powers
std::set<int> ScavengePowers = {3, 50, 99};
std::set<int> RunPowers = {4, 68, 86};
std::set<int> RunPowers = {4, 68, 86, 132};
std::set<int> GroupRunPowers = {8, 62, 73};
std::set<int> BonusPowers = {6, 54, 104};
std::set<int> GuardPowers = {9, 57, 76};
std::set<int> RadarPowers = {11, 67, 95};
std::set<int> AntidotePowers = {14, 58, 102};
std::set<int> FreedomPowers = {31, 39, 107};
std::set<int> GuardPowers = {9, 57, 76, 157};
std::set<int> RadarPowers = {11, 67, 95, 156};
std::set<int> AntidotePowers = {14, 58, 102, 159};
std::set<int> FreedomPowers = {31, 39, 107, 158};
std::set<int> GroupFreedomPowers = {15, 55, 77};
std::set<int> JumpPowers = {16, 44, 88};
std::set<int> JumpPowers = {16, 44, 88, 154};
std::set<int> GroupJumpPowers = {35, 60, 100};
std::set<int> SelfRevivePowers = {22, 48, 84};
std::set<int> SneakPowers = {29, 72, 80};

View File

@ -117,6 +117,11 @@ bool PlayerManager::removePlayerFromChunks(std::vector<Chunk*> chunks, CNSocket*
exitBusData.iT_ID = id;
sock->sendPacket((void*)&exitBusData, P_FE2CL_TRANSPORTATION_EXIT, sizeof(sP_FE2CL_TRANSPORTATION_EXIT));
break;
case NPC_EGG:
INITSTRUCT(sP_FE2CL_SHINY_EXIT, exitEggData);
exitEggData.iShinyID = id;
sock->sendPacket((void*)&exitEggData, P_FE2CL_SHINY_EXIT, sizeof(sP_FE2CL_SHINY_EXIT));
break;
default:
INITSTRUCT(sP_FE2CL_NPC_EXIT, exitData);
exitData.iNPC_ID = id;
@ -151,6 +156,11 @@ void PlayerManager::addPlayerToChunks(std::vector<Chunk*> chunks, CNSocket* sock
enterBusData.AppearanceData = { 3, npc->appearanceData.iNPC_ID, npc->appearanceData.iNPCType, npc->appearanceData.iX, npc->appearanceData.iY, npc->appearanceData.iZ };
sock->sendPacket((void*)&enterBusData, P_FE2CL_TRANSPORTATION_ENTER, sizeof(sP_FE2CL_TRANSPORTATION_ENTER));
break;
case NPC_EGG:
INITSTRUCT(sP_FE2CL_SHINY_ENTER, enterEggData);
NPCManager::npcDataToEggData(&npc->appearanceData, &enterEggData.ShinyAppearanceData);
sock->sendPacket((void*)&enterEggData, P_FE2CL_SHINY_ENTER, sizeof(sP_FE2CL_SHINY_ENTER));
break;
default:
INITSTRUCT(sP_FE2CL_NPC_ENTER, enterData);
enterData.NPCAppearanceData = NPCManager::NPCs[id]->appearanceData;

View File

@ -223,6 +223,8 @@ void TableData::init() {
loadDrops();
loadEggs();
loadPaths(&nextId); // load paths
loadGruntwork(&nextId);
@ -433,6 +435,35 @@ void TableData::loadDrops() {
}
}
void TableData::loadEggs() {
try {
std::ifstream inFile(settings::EGGSJSON);
nlohmann::json eggData;
// read file into json
inFile >> eggData;
// EggTypes
nlohmann::json eggTypes = eggData["EggTypes"];
for (nlohmann::json::iterator _eggType = eggTypes.begin(); _eggType != eggTypes.end(); _eggType++) {
auto eggType = _eggType.value();
EggType toAdd = {};
toAdd.dropCrateId = (int)eggType["DropCrateId"];
toAdd.effectId = (int)eggType["EffectId"];
toAdd.duration = (int)eggType["Duration"];
toAdd.regen= (int)eggType["Regen"];
NPCManager::EggTypes[(int)eggType["Id"]] = toAdd;
}
std::cout << "[INFO] Loaded Egg Data" <<std::endl;
}
catch (const std::exception& err) {
std::cerr << "[FATAL] Malformed eggs.json file! Reason:" << err.what() << std::endl;
terminate(0);
}
}
/*
* Create a full and properly-paced path by interpolating between keyframes.
*/

View File

@ -18,6 +18,7 @@ namespace TableData {
int getItemType(int);
void loadPaths(int*);
void loadDrops();
void loadEggs();
void constructPathSkyway(nlohmann::json::iterator);
void constructPathSlider(nlohmann::json, int, int);
void constructPathNPC(nlohmann::json::iterator, int id=0);

View File

@ -25,6 +25,7 @@ std::string settings::XDTJSON = "tdata/xdt.json";
std::string settings::MOBJSON = "tdata/mobs.json";
std::string settings::PATHJSON = "tdata/paths.json";
std::string settings::DROPSJSON = "tdata/drops.json";
std::string settings::EGGSJSON = "tdata/eggs.json";
std::string settings::GRUNTWORKJSON = "tdata/gruntwork.json";
std::string settings::MOTDSTRING = "Welcome to OpenFusion!";
int settings::ACCLEVEL = 1;
@ -62,6 +63,7 @@ void settings::init() {
XDTJSON = reader.Get("shard", "xdtdata", XDTJSON);
MOBJSON = reader.Get("shard", "mobdata", MOBJSON);
DROPSJSON = reader.Get("shard", "dropdata", DROPSJSON);
EGGSJSON = reader.Get("shard", "eggdata", EGGSJSON);
PATHJSON = reader.Get("shard", "pathdata", PATHJSON);
GRUNTWORKJSON = reader.Get("shard", "gruntwork", GRUNTWORKJSON);
MOTDSTRING = reader.Get("shard", "motd", MOTDSTRING);

View File

@ -21,6 +21,7 @@ namespace settings {
extern std::string MOBJSON;
extern std::string PATHJSON;
extern std::string DROPSJSON;
extern std::string EGGSJSON;
extern std::string GRUNTWORKJSON;
extern int EVENTMODE;
extern int EVENTCRATECHANCE;