diff --git a/.vimrc b/.vimrc index c5c65b2..32e2a85 100644 --- a/.vimrc +++ b/.vimrc @@ -1,9 +1,9 @@ " vim configuration file -" you will need to put 'set exrc' and 'set secure' into your main .vimrc file, +" You will need to put 'set exrc' and 'set secure' into your main .vimrc file, " in which case this file will be loaded automatically, but *only* if you -" start vim in this dir, or you can just load it directly with ':so .vimrc' -" every time. +" start vim in this dir. Alternatively you can just load it directly with +" ':so .vimrc' every time. set tabstop=4 set shiftwidth=4 set expandtab diff --git a/Makefile b/Makefile index 97e18b6..0c8b5a5 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ CC=clang CXX=clang++ # -w suppresses all warnings (the part that's commented out helps me find memory leaks, it ruins performance though!) -CCFLAGS=-O3 #-g3 -fsanitize=address +CFLAGS=-O3 #-g3 -fsanitize=address CXXFLAGS=-Wall -std=c++17 -O3 -DPROTOCOL_VERSION=$(PROTOCOL_VERSION) #-g3 -fsanitize=address LDFLAGS=-lpthread -ldl # specifies the name of our exectuable @@ -14,22 +14,23 @@ PROTOCOL_VERSION?=104 # Windows-specific WIN_CC=x86_64-w64-mingw32-gcc WIN_CXX=x86_64-w64-mingw32-g++ -WIN_CCFLAGS=-O3 #-g3 -fsanitize=address +WIN_CFLAGS=-O3 #-g3 -fsanitize=address WIN_CXXFLAGS=-Wall -std=c++17 -O3 -DPROTOCOL_VERSION=$(PROTOCOL_VERSION) #-g3 -fsanitize=address WIN_LDFLAGS=-static -lws2_32 -lwsock32 WIN_SERVER=bin/winfusion.exe -CC_SRC=\ +CSRC=\ src/contrib/bcrypt/bcrypt.c\ src/contrib/bcrypt/crypt_blowfish.c\ src/contrib/bcrypt/crypt_gensalt.c\ src/contrib/bcrypt/wrapper.c\ src/contrib/sqlite/sqlite3.c\ -CXX_SRC=\ +CXXSRC=\ src/contrib/sqlite/sqlite3pp.cpp\ src/contrib/sqlite/sqlite3ppext.cpp\ src/ChatManager.cpp\ + src/CombatManager.cpp\ src/CNLoginServer.cpp\ src/CNProtocol.cpp\ src/CNShardServer.cpp\ @@ -47,7 +48,7 @@ CXX_SRC=\ src/settings.cpp\ # headers (for timestamp purposes) -CC_HDR=\ +CHDR=\ src/contrib/bcrypt/bcrypt.h\ src/contrib/bcrypt/crypt_blowfish.h\ src/contrib/bcrypt/crypt_gensalt.h\ @@ -56,13 +57,14 @@ CC_HDR=\ src/contrib/sqlite/sqlite3.h\ src/contrib/sqlite/sqlite3ext.h\ -CXX_HDR=\ +CXXHDR=\ src/contrib/bcrypt/BCrypt.hpp\ src/contrib/sqlite/sqlite3pp.h\ src/contrib/sqlite/sqlite3ppext.h\ src/contrib/INIReader.hpp\ src/contrib/JSON.hpp\ src/ChatManager.hpp\ + src/CombatManager.hpp\ src/CNLoginServer.hpp\ src/CNProtocol.hpp\ src/CNShardServer.hpp\ @@ -80,10 +82,10 @@ CXX_HDR=\ src/PlayerManager.hpp\ src/settings.hpp\ -CC_OBJ=$(CC_SRC:.c=.o) -CXX_OBJ=$(CXX_SRC:.cpp=.o) +COBJ=$(CSRC:.c=.o) +CXXOBJ=$(CXXSRC:.cpp=.o) -OBJ=$(CC_OBJ) $(CXX_OBJ) +OBJ=$(COBJ) $(CXXOBJ) all: $(SERVER) @@ -92,22 +94,24 @@ windows: $(SERVER) # assign Windows-specific values if targeting Windows windows : CC=$(WIN_CC) windows : CXX=$(WIN_CXX) -windows : CCFLAGS=$(WIN_CCFLAGS) +windows : CFLAGS=$(WIN_CFLAGS) windows : CXXFLAGS=$(WIN_CXXFLAGS) windows : LDFLAGS=$(WIN_LDFLAGS) windows : SERVER=$(WIN_SERVER) -$(CC_OBJ): %.o: %.c $(CC_HDR) - $(CC) -c $(CCFLAGS) -o $@ $< +.SUFFIX: .o .c .cpp .hpp -$(CXX_OBJ): %.o: %.cpp $(CXX_HDR) +.c.o: $(CHDR) + $(CC) -c $(CFLAGS) -o $@ $< + +.cpp.o: $(CXXHDR) $(CXX) -c $(CXXFLAGS) -o $@ $< -$(SERVER): $(OBJ) $(CC_HDR) $(CXX_HDR) +$(SERVER): $(OBJ) $(CHDR) $(CXXHDR) mkdir -p bin $(CXX) $(OBJ) $(LDFLAGS) -o $(SERVER) .PHONY: all windows clean clean: - rm -f $(OBJ) $(SERVER) $(WIN_SERVER) \ No newline at end of file + rm -f $(OBJ) $(SERVER) $(WIN_SERVER) diff --git a/README.md b/README.md index aa23791..7b7dc10 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ tl;dr: From then on, any time you want to run the "game": -3. Run `OpenFusionRelease/winfusion.exe` +3. Run `Server/winfusion.exe` 4. Run `FreeClient/OpenFusionClient.exe` Currently the client by default connects to a public server hosted by Cake. Change the loginInfo.php to point to your own server if you want to host your own. @@ -61,7 +61,7 @@ When the player clicks "ENTER THE GAME" (or completes the tutorial), the login s ## Configuration -You can change the ports the FusionFall server listens on in `OpenFusion/config.ini`. Make sure the login server port is in sync with `loginInfo.php`. +You can change the ports the FusionFall server listens on in `Server/config.ini`. Make sure the login server port is in sync with `loginInfo.php`. The shard port needs no such synchronization. You can also configure the distance at which you'll be able to see other players, though by default it's already as high as you'll want it. @@ -103,11 +103,11 @@ To make your landwalking experience more pleasant, you can make use of a few adm * `/goto` is useful for more precise teleportation (ie. for getting into Infected Zones, etc.). ### Item commands -* /itemN [type] [itemId] [amount] +* `/itemN [type] [itemId] [amount]` (Refer to the [item list](https://docs.google.com/spreadsheets/d/1mpoJ9iTHl_xLI4wQ_9UvIDYNcsDYscdkyaGizs43TCg/)) ### Nano commands -* /nano [id] (1-36) -* /nano_equip [id] (1-36) [slot] (0-2) -* /nano_unequip [slot] (0-2) -* /nano_active [slot] (0-2) +* `/nano [id] (1-36)` +* `/nano_equip [id] (1-36) [slot] (0-2)` +* `/nano_unequip [slot] (0-2)` +* `/nano_active [slot] (0-2)` diff --git a/config.ini b/config.ini index cdb6bcf..56e7550 100644 --- a/config.ini +++ b/config.ini @@ -23,9 +23,9 @@ npcdistance=16000 # little message players see when they enter the game motd=Welcome to OpenFusion! # NPC json data -npcdata=NPCs.json +npcdata=data/NPCs.json # warp target json data -warpdata=warps.json +warpdata=data/warps.json # is everyone a GM? gm=true diff --git a/NPCs.json b/data/NPCs.json similarity index 100% rename from NPCs.json rename to data/NPCs.json diff --git a/mobs.json b/data/mobs.json similarity index 100% rename from mobs.json rename to data/mobs.json diff --git a/warps.json b/data/warps.json similarity index 100% rename from warps.json rename to data/warps.json diff --git a/src/CNProtocol.cpp b/src/CNProtocol.cpp index 1c8c1a1..fd568b8 100644 --- a/src/CNProtocol.cpp +++ b/src/CNProtocol.cpp @@ -123,23 +123,25 @@ void CNSocket::sendPacket(void* buf, uint32_t type, size_t size) { if (!alive) return; - int tmpSize = size + sizeof(uint32_t); - uint8_t* tmpBuf = (uint8_t*)xmalloc(tmpSize); + size_t bodysize = size + sizeof(uint32_t); + uint8_t* fullpkt = (uint8_t*)xmalloc(bodysize+4); + uint8_t* body = fullpkt+4; + memcpy(fullpkt, (void*)&bodysize, 4); // copy packet type to the front of the buffer & then the actual buffer - memcpy(tmpBuf, (void*)&type, sizeof(uint32_t)); - memcpy(tmpBuf+sizeof(uint32_t), buf, size); + memcpy(body, (void*)&type, sizeof(uint32_t)); + memcpy(body+sizeof(uint32_t), buf, size); // encrypt the packet switch (activeKey) { case SOCKETKEY_E: - CNSocketEncryption::encryptData((uint8_t*)tmpBuf, (uint8_t*)(&EKey), tmpSize); + CNSocketEncryption::encryptData((uint8_t*)body, (uint8_t*)(&EKey), bodysize); break; case SOCKETKEY_FE: - CNSocketEncryption::encryptData((uint8_t*)tmpBuf, (uint8_t*)(&FEKey), tmpSize); + CNSocketEncryption::encryptData((uint8_t*)body, (uint8_t*)(&FEKey), bodysize); break; default: { - free(tmpBuf); + free(fullpkt); DEBUGLOG( std::cout << "[WARN]: UNSET KEYTYPE FOR SOCKET!! ABORTING SEND" << std::endl; ) @@ -147,15 +149,11 @@ void CNSocket::sendPacket(void* buf, uint32_t type, size_t size) { } } - // send packet size - if (!sendData((uint8_t*)&tmpSize, sizeof(uint32_t))) - kill(); - // send packet data! - if (alive && !sendData(tmpBuf, tmpSize)) + if (alive && !sendData(fullpkt, bodysize+4)) kill(); - free(tmpBuf); // free tmp buffer + free(fullpkt); } void CNSocket::setActiveKey(ACTIVEKEY key) { @@ -172,7 +170,7 @@ void CNSocket::step() { // we got out packet size!!!! readSize = *((int32_t*)readBuffer); // sanity check - if (readSize > MAX_PACKETSIZE) { + if (readSize > CN_PACKET_BUFFER_SIZE) { kill(); return; } diff --git a/src/CNProtocol.hpp b/src/CNProtocol.hpp index d2f9843..cc47ece 100644 --- a/src/CNProtocol.hpp +++ b/src/CNProtocol.hpp @@ -1,6 +1,5 @@ #pragma once -#define MAX_PACKETSIZE 8192 #define DEBUGLOG(x) if (settings::VERBOSITY) {x}; #include @@ -56,7 +55,8 @@ [4 bytes] - size of packet including the 4 byte packet type [size bytes] - Encrypted packet (byte swapped && xor'd with 8 byte key; see CNSocketEncryption) [4 bytes] - packet type (which is a combination of the first 4 bytes of the packet and a checksum in some versions) - [structure] + [structure] - one member contains length of trailing data (expressed in packet-dependant structures) + [trailing data] - optional variable-length data that only some packets make use of */ // error checking calloc wrapper @@ -71,6 +71,42 @@ inline void* xmalloc(size_t sz) { return res; } +// overflow-safe validation of variable-length packets +// for outbound packets +inline bool validOutVarPacket(size_t base, int32_t npayloads, size_t plsize) { + // check for multiplication overflow + if (npayloads > 0 && CN_PACKET_BUFFER_SIZE / (size_t)npayloads < plsize) + return false; + + // it's safe to multiply + size_t trailing = npayloads * plsize; + + // does it fit in a packet? + if (base + trailing > CN_PACKET_BUFFER_SIZE) + return false; + + // everything is a-ok! + return true; +} + +// for inbound packets +inline bool validInVarPacket(size_t base, int32_t npayloads, size_t plsize, size_t datasize) { + // check for multiplication overflow + if (npayloads > 0 && CN_PACKET_BUFFER_SIZE / (size_t)npayloads < plsize) + return false; + + // it's safe to multiply + size_t trailing = npayloads * plsize; + + // make sure size is exact + // datasize has already been validated against CN_PACKET_BUFFER_SIZE + if (datasize != base + trailing) + return false; + + // everything is a-ok! + return true; +} + namespace CNSocketEncryption { // you won't believe how complicated they made it in the client :facepalm: static constexpr const char* defaultKey = "m@rQn~W#"; @@ -104,7 +140,7 @@ private: uint64_t EKey; uint64_t FEKey; int32_t readSize = 0; - uint8_t readBuffer[MAX_PACKETSIZE]; + uint8_t readBuffer[CN_PACKET_BUFFER_SIZE]; int readBufferIndex = 0; bool activelyReading = false; bool alive = true; diff --git a/src/CombatManager.cpp b/src/CombatManager.cpp new file mode 100644 index 0000000..bb77b75 --- /dev/null +++ b/src/CombatManager.cpp @@ -0,0 +1,131 @@ +#include "CombatManager.hpp" +#include "PlayerManager.hpp" +#include "NPCManager.hpp" +#include "ItemManager.hpp" + +#include + +void CombatManager::init() { + REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ATTACK_NPCs, pcAttackNpcs); + + REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_COMBAT_BEGIN, combatBegin); + REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_COMBAT_END, combatEnd); + REGISTER_SHARD_PACKET(P_CL2FE_DOT_DAMAGE_ONOFF, dotDamageOnOff); +} + +void CombatManager::pcAttackNpcs(CNSocket *sock, CNPacketData *data) { + sP_CL2FE_REQ_PC_ATTACK_NPCs* pkt = (sP_CL2FE_REQ_PC_ATTACK_NPCs*)data->buf; + Player *plr = PlayerManager::getPlayer(sock); + + // sanity check + if (!validInVarPacket(sizeof(sP_CL2FE_REQ_PC_ATTACK_NPCs), pkt->iNPCCnt, sizeof(int32_t), data->size)) { + std::cout << "[WARN] bad sP_CL2FE_REQ_PC_ATTACK_NPCs packet size\n"; + return; + } + + int32_t *pktdata = (int32_t*)((uint8_t*)data->buf + sizeof(sP_CL2FE_REQ_PC_ATTACK_NPCs)); + + /* + * Due to the possibility of multiplication overflow (and regular buffer overflow), + * both incoming and outgoing variable-length packets must be validated, at least if + * the number of trailing structs isn't well known (ie. it's from the client). + */ + if (!validOutVarPacket(sizeof(sP_FE2CL_PC_ATTACK_NPCs_SUCC), pkt->iNPCCnt, sizeof(sAttackResult))) { + std::cout << "[WARN] bad sP_FE2CL_PC_ATTACK_NPCs_SUCC packet size\n"; + return; + } + + // initialize response struct + size_t resplen = sizeof(sP_FE2CL_PC_ATTACK_NPCs_SUCC) + pkt->iNPCCnt * sizeof(sAttackResult); + uint8_t respbuf[4096]; + + memset(respbuf, 0, resplen); + + sP_FE2CL_PC_ATTACK_NPCs_SUCC *resp = (sP_FE2CL_PC_ATTACK_NPCs_SUCC*)respbuf; + sAttackResult *respdata = (sAttackResult*)(respbuf+sizeof(sP_FE2CL_PC_ATTACK_NPCs_SUCC)); + + resp->iNPCCnt = pkt->iNPCCnt; + + for (int i = 0; i < pkt->iNPCCnt; i++) { + if (NPCManager::NPCs.find(pktdata[i]) == NPCManager::NPCs.end()) { + // not sure how to best handle this + std::cout << "[WARN] pcAttackNpcs: mob ID not found" << std::endl; + return; + } + BaseNPC& mob = NPCManager::NPCs[pktdata[i]]; + + mob.appearanceData.iHP -= 100; + + if (mob.appearanceData.iHP <= 0) + giveReward(sock); + // TODO: despawn mobs when they die + + respdata[i].iID = mob.appearanceData.iNPC_ID; + respdata[i].iDamage = 100; + respdata[i].iHP = mob.appearanceData.iHP; + respdata[i].iHitFlag = 2; + } + + sock->sendPacket((void*)respbuf, P_FE2CL_PC_ATTACK_NPCs_SUCC, resplen); + + // a bit of a hack: these are the same size, so we can reuse the output packet + assert(sizeof(sP_FE2CL_PC_ATTACK_NPCs_SUCC) == sizeof(sP_FE2CL_PC_ATTACK_NPCs)); + sP_FE2CL_PC_ATTACK_NPCs *resp1 = (sP_FE2CL_PC_ATTACK_NPCs*)respbuf; + + resp1->iPC_ID = plr->iID; + + // send to other players + for (CNSocket *s : PlayerManager::players[sock].viewable) { + if (s == sock) + continue; + + s->sendPacket((void*)respbuf, P_FE2CL_PC_ATTACK_NPCs, resplen); + } +} + +void CombatManager::combatBegin(CNSocket *sock, CNPacketData *data) {} // stub +void CombatManager::combatEnd(CNSocket *sock, CNPacketData *data) {} // stub +void CombatManager::dotDamageOnOff(CNSocket *sock, CNPacketData *data) {} // stub + +void CombatManager::giveReward(CNSocket *sock) { + Player *plr = PlayerManager::getPlayer(sock); + + const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward); + assert(resplen < CN_PACKET_BUFFER_SIZE); + // 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); + + // update player + plr->money += 50; + plr->fusionmatter += 70; + + // simple rewards + reward->m_iCandy = plr->money; + reward->m_iFusionMatter = plr->fusionmatter; + 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); + if (slot == -1) { + // no room for an item, but you still get FM and taros + sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, sizeof(sP_FE2CL_REP_REWARD_ITEM)); + } else { + // item reward + item->sItem.iType = 9; + item->sItem.iID = 1; + 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); + } +} diff --git a/src/CombatManager.hpp b/src/CombatManager.hpp new file mode 100644 index 0000000..5d321ac --- /dev/null +++ b/src/CombatManager.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "CNProtocol.hpp" +#include "CNShared.hpp" +#include "CNShardServer.hpp" + +namespace CombatManager { + void init(); + + void pcAttackNpcs(CNSocket *sock, CNPacketData *data); + void combatBegin(CNSocket *sock, CNPacketData *data); + void combatEnd(CNSocket *sock, CNPacketData *data); + void dotDamageOnOff(CNSocket *sock, CNPacketData *data); + + void giveReward(CNSocket *sock); +} diff --git a/src/Defines.cpp b/src/Defines.cpp index e0ce9ce..8552a43 100644 --- a/src/Defines.cpp +++ b/src/Defines.cpp @@ -5,7 +5,7 @@ #define STRINGIFY(x) PacketMap(x, #x) /* - * Turns out there's not better way to do this... + * Turns out there isn't better way to do this... * We'll only support CL2* packets for now, since we only * need to print those. */ diff --git a/src/ItemManager.cpp b/src/ItemManager.cpp index ad11e55..d43817a 100644 --- a/src/ItemManager.cpp +++ b/src/ItemManager.cpp @@ -4,6 +4,9 @@ #include "PlayerManager.hpp" #include "Player.hpp" +#include // for memset() and memcmp() +#include + void ItemManager::init() { REGISTER_SHARD_PACKET(P_CL2FE_REQ_ITEM_MOVE, itemMoveHandler); REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ITEM_DELETE, itemDeleteHandler); @@ -18,6 +21,7 @@ void ItemManager::init() { REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TRADE_ITEM_UNREGISTER, itemTradeUnregisterItemHandler); REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TRADE_CASH_REGISTER, itemTradeRegisterCashHandler); REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TRADE_EMOTES_CHAT, itemTradeChatHandler); + REGISTER_SHARD_PACKET(P_CL2FE_REQ_ITEM_CHEST_OPEN, chestOpenHandler); } void ItemManager::itemMoveHandler(CNSocket* sock, CNPacketData* data) { @@ -664,4 +668,64 @@ void ItemManager::itemTradeChatHandler(CNSocket* sock, CNPacketData* data) { sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_EMOTES_CHAT, sizeof(sP_FE2CL_REP_PC_TRADE_EMOTES_CHAT)); otherSock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_EMOTES_CHAT, sizeof(sP_FE2CL_REP_PC_TRADE_EMOTES_CHAT)); -} \ No newline at end of file +} + +void ItemManager::chestOpenHandler(CNSocket *sock, CNPacketData *data) { + if (data->size != sizeof(sP_CL2FE_REQ_ITEM_CHEST_OPEN)) + return; // ignore the malformed packet + + sP_CL2FE_REQ_ITEM_CHEST_OPEN *pkt = (sP_CL2FE_REQ_ITEM_CHEST_OPEN *)data->buf; + Player *plr = PlayerManager::getPlayer(sock); + + // item giving packet + const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward); + assert(resplen < CN_PACKET_BUFFER_SIZE); + // 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); + + // simple rewards + reward->iFatigue = 100; // prevents warning message + reward->iFatigue_Level = 1; + reward->iItemCnt = 1; // remember to update resplen if you change this + + // item reward + item->sItem.iType = 0; + item->sItem.iID = 96; + item->iSlotNum = pkt->iSlotNum; + item->eIL = pkt->eIL; + + // update player + plr->Inven[pkt->iSlotNum] = item->sItem; + + // transmit item + sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen); + + // chest opening acknowledgement packet + INITSTRUCT(sP_FE2CL_REP_ITEM_CHEST_OPEN_SUCC, resp); + + resp.iSlotNum = pkt->iSlotNum; + + std::cout << "opening chest..." << std::endl; + sock->sendPacket((void*)&resp, P_FE2CL_REP_ITEM_CHEST_OPEN_SUCC, sizeof(sP_FE2CL_REP_ITEM_CHEST_OPEN_SUCC)); +} + +// TODO: use this in cleaned up ItemManager +int ItemManager::findFreeSlot(Player *plr) { + int i; + sItemBase free; + + memset((void*)&free, 0, sizeof(sItemBase)); + + for (i = 0; i < AINVEN_COUNT; i++) + if (memcmp((void*)&plr->Inven[i], (void*)&free, sizeof(sItemBase)) == 0) + return i; + + // not found + return -1; +} diff --git a/src/ItemManager.hpp b/src/ItemManager.hpp index 44cf281..4531095 100644 --- a/src/ItemManager.hpp +++ b/src/ItemManager.hpp @@ -1,9 +1,11 @@ #pragma once #include "CNShardServer.hpp" +#include "Player.hpp" namespace ItemManager { void init(); + void itemMoveHandler(CNSocket* sock, CNPacketData* data); void itemDeleteHandler(CNSocket* sock, CNPacketData* data); void itemGMGiveHandler(CNSocket* sock, CNPacketData* data); @@ -17,4 +19,7 @@ namespace ItemManager { void itemTradeUnregisterItemHandler(CNSocket* sock, CNPacketData* data); void itemTradeRegisterCashHandler(CNSocket* sock, CNPacketData* data); void itemTradeChatHandler(CNSocket* sock, CNPacketData* data); + void chestOpenHandler(CNSocket* sock, CNPacketData* data); + + int findFreeSlot(Player *plr); } diff --git a/src/NPCManager.cpp b/src/NPCManager.cpp index 9988f1d..1e34704 100644 --- a/src/NPCManager.cpp +++ b/src/NPCManager.cpp @@ -37,7 +37,7 @@ void NPCManager::init() { // load temporary mob dump try { - std::ifstream inFile("mobs.json"); // not in settings, since it's temp + std::ifstream inFile("data/mobs.json"); // not in settings, since it's temp nlohmann::json npcData; // read file into json diff --git a/src/Player.hpp b/src/Player.hpp index e60bdf8..3d780d1 100644 --- a/src/Player.hpp +++ b/src/Player.hpp @@ -15,6 +15,7 @@ struct Player { int HP; int slot; // player slot, not nano slot int32_t money; + int32_t fusionmatter; sPCStyle PCStyle; sPCStyle2 PCStyle2; sNano Nanos[37]; // acquired nanos diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index 2b1ad85..7f87515 100644 --- a/src/PlayerManager.cpp +++ b/src/PlayerManager.cpp @@ -32,6 +32,7 @@ void PlayerManager::init() { REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_SPECIAL_STATE_SWITCH, PlayerManager::setSpecialSwitchPlayer); REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VEHICLE_ON, PlayerManager::enterPlayerVehicle); REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VEHICLE_OFF, PlayerManager::exitPlayerVehicle); + REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_CHANGE_MENTOR, PlayerManager::changePlayerGuide); } void PlayerManager::addPlayer(CNSocket* key, Player plr) { @@ -651,6 +652,21 @@ void PlayerManager::setSpecialSwitchPlayer(CNSocket* sock, CNPacketData* data) { sock->sendPacket((void*)&response, P_FE2CL_REP_PC_SPECIAL_STATE_SWITCH_SUCC, sizeof(sP_FE2CL_REP_PC_SPECIAL_STATE_SWITCH_SUCC)); } +void PlayerManager::changePlayerGuide(CNSocket *sock, CNPacketData *data) { + if (data->size != sizeof(sP_CL2FE_REQ_PC_CHANGE_MENTOR)) + return; + + sP_CL2FE_REQ_PC_CHANGE_MENTOR *pkt = (sP_CL2FE_REQ_PC_CHANGE_MENTOR*)data->buf; + INITSTRUCT(sP_FE2CL_REP_PC_CHANGE_MENTOR_SUCC, resp); + Player *plr = getPlayer(sock); + + resp.iMentor = pkt->iMentor; + resp.iMentorCnt = 1; + resp.iFusionMatter = plr->fusionmatter; // no cost + + sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_CHANGE_MENTOR_SUCC, sizeof(sP_FE2CL_REP_PC_CHANGE_MENTOR_SUCC)); +} + #pragma region Helper methods Player *PlayerManager::getPlayer(CNSocket* key) { return players[key].plr; diff --git a/src/PlayerManager.hpp b/src/PlayerManager.hpp index 78f904b..52eb95a 100644 --- a/src/PlayerManager.hpp +++ b/src/PlayerManager.hpp @@ -45,6 +45,7 @@ namespace PlayerManager { void exitGame(CNSocket* sock, CNPacketData* data); void setSpecialSwitchPlayer(CNSocket* sock, CNPacketData* data); + void changePlayerGuide(CNSocket *sock, CNPacketData *data); void enterPlayerVehicle(CNSocket* sock, CNPacketData* data); void exitPlayerVehicle(CNSocket* sock, CNPacketData* data); diff --git a/src/main.cpp b/src/main.cpp index cd4f46d..1a1990b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,6 +2,7 @@ #include "CNShardServer.hpp" #include "PlayerManager.hpp" #include "ChatManager.hpp" +#include "CombatManager.hpp" #include "ItemManager.hpp" #include "MissionManager.hpp" #include "NanoManager.hpp" @@ -17,10 +18,21 @@ #endif #include +CNShardServer *shardServer; +std::thread *shardThread; + void startShard(CNShardServer* server) { server->start(); } +// terminate gracefully on SIGINT (for gprof) +void terminate(int arg) { + std::cout << "OpenFusion: terminating" << std::endl; + shardServer->kill(); + shardThread->join(); + exit(0); +} + int main() { #ifdef _WIN32 WSADATA wsaData; @@ -31,12 +43,14 @@ int main() { #else // tell the OS to not kill us if you use a broken pipe, just let us know thru recv() or send() signal(SIGPIPE, SIG_IGN); + signal(SIGINT, terminate); // TODO: use sigaction() instead #endif settings::init(); std::cout << "[INFO] Protocol version: " << PROTOCOL_VERSION << std::endl; std::cout << "[INFO] Intializing Packet Managers..." << std::endl; PlayerManager::init(); ChatManager::init(); + CombatManager::init(); ItemManager::init(); MissionManager::init(); NanoManager::init(); @@ -46,14 +60,14 @@ int main() { std::cout << "[INFO] Starting Server Threads..." << std::endl; CNLoginServer loginServer(settings::LOGINPORT); - CNShardServer shardServer(settings::SHARDPORT); + shardServer = new CNShardServer(settings::SHARDPORT); - std::thread shardThread(startShard, (CNShardServer*)&shardServer); + shardThread = new std::thread(startShard, (CNShardServer*)shardServer); loginServer.start(); - shardServer.kill(); - shardThread.join(); + shardServer->kill(); + shardThread->join(); #ifdef _WIN32 WSACleanup(); diff --git a/src/settings.cpp b/src/settings.cpp index 976836e..07d0bc4 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -18,8 +18,8 @@ int settings::SPAWN_X = 179213; int settings::SPAWN_Y = 268451; int settings::SPAWN_Z = -4210; std::string settings::GMPASS = "pass"; -std::string settings::NPCJSON = "NPCs.json"; -std::string settings::WARPJSON = "warps.json"; +std::string settings::NPCJSON = "data/NPCs.json"; +std::string settings::WARPJSON = "data/warps.json"; std::string settings::MOTDSTRING = "Welcome to OpenFusion!"; bool settings::GM = false;