2021-03-16 22:29:13 +00:00
|
|
|
#include "Items.hpp"
|
2022-07-16 23:19:40 +00:00
|
|
|
|
|
|
|
#include "servers/CNShardServer.hpp"
|
|
|
|
|
|
|
|
#include "Player.hpp"
|
2020-08-21 02:10:14 +00:00
|
|
|
#include "PlayerManager.hpp"
|
2021-03-16 22:29:13 +00:00
|
|
|
#include "Nanos.hpp"
|
2021-03-13 20:22:29 +00:00
|
|
|
#include "Abilities.hpp"
|
2021-03-17 21:28:24 +00:00
|
|
|
#include "Eggs.hpp"
|
2022-07-16 23:19:40 +00:00
|
|
|
#include "MobAI.hpp"
|
|
|
|
#include "Missions.hpp"
|
2022-07-17 06:33:57 +00:00
|
|
|
#include "Buffs.hpp"
|
2020-08-21 02:10:14 +00:00
|
|
|
|
2020-10-14 21:15:02 +00:00
|
|
|
#include <string.h> // for memset()
|
2020-08-28 16:25:03 +00:00
|
|
|
#include <assert.h>
|
2022-07-16 23:19:40 +00:00
|
|
|
#include <numeric>
|
2020-08-28 16:25:03 +00:00
|
|
|
|
2021-03-16 22:29:13 +00:00
|
|
|
using namespace Items;
|
2021-03-16 21:06:10 +00:00
|
|
|
|
2021-03-16 22:29:13 +00:00
|
|
|
std::map<std::pair<int32_t, int32_t>, Items::Item> Items::ItemData;
|
|
|
|
std::map<int32_t, CrocPotEntry> Items::CrocPotTable;
|
2021-03-28 20:57:43 +00:00
|
|
|
std::map<int32_t, std::vector<int32_t>> Items::RarityWeights;
|
2021-03-16 22:29:13 +00:00
|
|
|
std::map<int32_t, Crate> Items::Crates;
|
2021-03-29 06:22:23 +00:00
|
|
|
std::map<int32_t, ItemReference> Items::ItemReferences;
|
2021-03-16 22:29:13 +00:00
|
|
|
std::map<std::string, std::vector<std::pair<int32_t, int32_t>>> Items::CodeItems;
|
functional crates (no more plungers) (#133)
* FM, Taros and Boosts awards from killing mobs should be pretty
accurate now. A temporary formula for adjusting player/mob level gap is
implemented, but it will probably need to be adjusted in the future
* Mobs now drop correct crates
* Crates can be opened and give you correct items This includes
regular mob crates, world boss crates, mission crates, IZ race crates,
E.G.G.E.R.s, golden Eggs, and Event Crates. Keep in mind that neither
IZ races or golden Eggs are implemented, but if you spawn such a crate
it can be opened.
* All data is read from a json file, for which I'm going to release a
tool soon so it's easily adjustable
* There is a new setting for enabling events, which enables dropping
extra event crates These are Knishmas, Halloween and Easter
2020-10-10 17:18:47 +00:00
|
|
|
|
2021-03-28 20:57:43 +00:00
|
|
|
std::map<int32_t, CrateDropChance> Items::CrateDropChances;
|
|
|
|
std::map<int32_t, std::vector<int32_t>> Items::CrateDropTypes;
|
|
|
|
std::map<int32_t, MiscDropChance> Items::MiscDropChances;
|
|
|
|
std::map<int32_t, MiscDropType> Items::MiscDropTypes;
|
2021-03-16 22:29:13 +00:00
|
|
|
std::map<int32_t, MobDrop> Items::MobDrops;
|
2021-04-04 10:45:57 +00:00
|
|
|
std::map<int32_t, int32_t> Items::EventToDropMap;
|
2021-04-03 06:34:20 +00:00
|
|
|
std::map<int32_t, int32_t> Items::MobToDropMap;
|
2021-03-30 02:48:31 +00:00
|
|
|
std::map<int32_t, ItemSet> Items::ItemSets;
|
2021-03-16 18:41:20 +00:00
|
|
|
|
2020-12-16 15:27:18 +00:00
|
|
|
#ifdef ACADEMY
|
2021-03-16 22:29:13 +00:00
|
|
|
std::map<int32_t, int32_t> Items::NanoCapsules; // crate id -> nano id
|
2020-12-16 15:27:18 +00:00
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
static void nanoCapsuleHandler(CNSocket* sock, int slot, sItemBase *chest) {
|
2021-03-13 02:09:36 +00:00
|
|
|
Player* plr = PlayerManager::getPlayer(sock);
|
2021-03-16 21:06:10 +00:00
|
|
|
int32_t nanoId = NanoCapsules[chest->iID];
|
2021-03-13 02:09:36 +00:00
|
|
|
|
|
|
|
// chest opening acknowledgement packet
|
|
|
|
INITSTRUCT(sP_FE2CL_REP_ITEM_CHEST_OPEN_SUCC, resp);
|
|
|
|
resp.iSlotNum = slot;
|
|
|
|
|
|
|
|
// in order to remove capsule form inventory, we have to send item reward packet with empty item
|
|
|
|
const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward);
|
2024-10-29 02:50:35 +00:00
|
|
|
assert(resplen < CN_PACKET_BODY_SIZE);
|
2021-03-13 02:09:36 +00:00
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
|
|
|
// maintain stats
|
|
|
|
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
|
|
|
|
reward->m_iBatteryN = plr->batteryN;
|
|
|
|
reward->m_iBatteryW = plr->batteryW;
|
|
|
|
|
|
|
|
item->iSlotNum = slot;
|
|
|
|
item->eIL = 1;
|
2021-03-28 20:57:43 +00:00
|
|
|
|
2021-03-13 02:09:36 +00:00
|
|
|
// update player serverside
|
|
|
|
plr->Inven[slot] = item->sItem;
|
|
|
|
|
|
|
|
// transmit item
|
|
|
|
sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen);
|
|
|
|
|
|
|
|
// transmit chest opening acknowledgement packet
|
2021-03-20 20:19:48 +00:00
|
|
|
sock->sendPacket(resp, P_FE2CL_REP_ITEM_CHEST_OPEN_SUCC);
|
2021-03-13 02:09:36 +00:00
|
|
|
|
|
|
|
// check if player doesn't already have this nano
|
|
|
|
if (plr->Nanos[nanoId].iID != 0) {
|
|
|
|
INITSTRUCT(sP_FE2CL_GM_REP_PC_ANNOUNCE, msg);
|
|
|
|
msg.iDuringTime = 4;
|
2021-03-20 20:19:48 +00:00
|
|
|
std::string text = "You have already acquired this nano!";
|
2021-03-13 02:09:36 +00:00
|
|
|
U8toU16(text, msg.szAnnounceMsg, sizeof(text));
|
2021-03-20 20:19:48 +00:00
|
|
|
sock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
|
2021-03-13 02:09:36 +00:00
|
|
|
return;
|
|
|
|
}
|
2021-03-16 22:29:13 +00:00
|
|
|
Nanos::addNano(sock, nanoId, -1, false);
|
2020-08-21 02:10:14 +00:00
|
|
|
}
|
2021-03-13 02:09:36 +00:00
|
|
|
#endif
|
2020-08-21 02:10:14 +00:00
|
|
|
|
2021-03-28 20:57:43 +00:00
|
|
|
static int choice(const std::vector<int>& weights, int rolled) {
|
|
|
|
int total = std::accumulate(weights.begin(), weights.end(), 0);
|
|
|
|
int randValue = rolled % total;
|
|
|
|
int currentIndex = -1;
|
|
|
|
|
|
|
|
do {
|
|
|
|
currentIndex++;
|
|
|
|
randValue -= weights[currentIndex];
|
|
|
|
} while (randValue >= 0);
|
|
|
|
|
|
|
|
return currentIndex;
|
|
|
|
}
|
|
|
|
|
2021-03-30 02:48:31 +00:00
|
|
|
static int getRarity(int crateId, int itemSetId) {
|
2021-03-28 20:57:43 +00:00
|
|
|
Crate& crate = Items::Crates[crateId];
|
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
// find rarity ratio
|
2021-03-28 20:57:43 +00:00
|
|
|
if (Items::RarityWeights.find(crate.rarityWeightId) == Items::RarityWeights.end()) {
|
|
|
|
std::cout << "[WARN] Rarity Weight " << crate.rarityWeightId << " not found!" << std::endl;
|
2021-03-16 21:06:10 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2021-03-28 20:57:43 +00:00
|
|
|
std::vector<int>& rarityWeights = Items::RarityWeights[crate.rarityWeightId];
|
2021-04-01 10:02:38 +00:00
|
|
|
ItemSet& itemSet = Items::ItemSets[itemSetId];
|
2021-03-16 21:06:10 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* First we have to check if specified item set contains items with all specified rarities,
|
|
|
|
* and if not eliminate them from the draw
|
|
|
|
* it is simpler to do here than to fix individually in the file
|
|
|
|
*/
|
|
|
|
|
|
|
|
// remember that rarities start from 1!
|
2021-03-28 20:57:43 +00:00
|
|
|
std::set<int> rarityIndices;
|
2021-04-01 10:02:38 +00:00
|
|
|
|
|
|
|
for (int itemReferenceId : itemSet.itemReferenceIds) {
|
2021-03-29 06:22:23 +00:00
|
|
|
if (Items::ItemReferences.find(itemReferenceId) == Items::ItemReferences.end())
|
2021-03-28 20:57:43 +00:00
|
|
|
continue;
|
|
|
|
|
2021-04-01 10:02:38 +00:00
|
|
|
// alter rarity
|
|
|
|
int itemRarity = (itemSet.alterRarityMap.find(itemReferenceId) == itemSet.alterRarityMap.end())
|
|
|
|
? Items::ItemReferences[itemReferenceId].rarity
|
|
|
|
: itemSet.alterRarityMap[itemReferenceId];
|
|
|
|
|
|
|
|
rarityIndices.insert(itemRarity - 1);
|
2021-03-16 21:06:10 +00:00
|
|
|
|
2021-03-28 20:57:43 +00:00
|
|
|
// shortcut
|
|
|
|
if (rarityIndices.size() == rarityWeights.size())
|
|
|
|
break;
|
|
|
|
}
|
2021-03-16 21:06:10 +00:00
|
|
|
|
2021-03-28 20:57:43 +00:00
|
|
|
if (rarityIndices.empty()) {
|
2021-03-30 02:48:31 +00:00
|
|
|
std::cout << "[WARN] Item Set " << crate.itemSetId << " has no valid items assigned?!" << std::endl;
|
2021-03-16 21:06:10 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2021-04-01 10:02:38 +00:00
|
|
|
// retain the weights of rarities that actually exist in the itemset
|
2021-04-01 06:34:36 +00:00
|
|
|
std::vector<int> relevantWeights(rarityWeights.size(), 0);
|
|
|
|
for (int index : rarityIndices) {
|
2021-04-01 10:02:38 +00:00
|
|
|
// check for out of bounds and rarity 0 items
|
2021-04-01 06:34:36 +00:00
|
|
|
if (index >= 0 && index < rarityWeights.size())
|
|
|
|
relevantWeights[index] = rarityWeights[index];
|
|
|
|
}
|
2021-03-16 21:06:10 +00:00
|
|
|
|
2021-03-28 20:57:43 +00:00
|
|
|
// now return a random rarity number (starting from 1)
|
2021-04-01 10:02:38 +00:00
|
|
|
// if relevantWeights is empty or all zeros, we default to giving a common (1) item
|
|
|
|
// rarity 0 items will appear in the drop pool regardless of this roll
|
2021-04-01 06:34:36 +00:00
|
|
|
return Rand::randWeighted(relevantWeights) + 1;
|
2021-03-16 21:06:10 +00:00
|
|
|
}
|
|
|
|
|
2021-03-30 02:48:31 +00:00
|
|
|
static int getCrateItem(sItemBase* result, int itemSetId, int rarity, int playerGender) {
|
|
|
|
ItemSet& itemSet = Items::ItemSets[itemSetId];
|
2021-03-16 21:06:10 +00:00
|
|
|
|
2021-03-30 02:48:31 +00:00
|
|
|
// collect valid items that match the rarity and gender (if not ignored)
|
2021-03-29 06:22:23 +00:00
|
|
|
std::vector<std::pair<int, ItemReference*>> validItems;
|
2021-03-28 20:57:43 +00:00
|
|
|
|
2021-03-30 02:48:31 +00:00
|
|
|
for (int itemReferenceId : itemSet.itemReferenceIds) {
|
2021-03-29 06:22:23 +00:00
|
|
|
if (Items::ItemReferences.find(itemReferenceId) == Items::ItemReferences.end()) {
|
|
|
|
std::cout << "[WARN] Item reference " << itemReferenceId << " in item set type "
|
2021-03-30 02:48:31 +00:00
|
|
|
<< itemSetId << " was not found, skipping..." << std::endl;
|
2021-03-28 20:57:43 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-03-29 06:22:23 +00:00
|
|
|
ItemReference* item = &Items::ItemReferences[itemReferenceId];
|
2021-03-28 20:57:43 +00:00
|
|
|
|
2021-03-30 02:48:31 +00:00
|
|
|
// alter rarity
|
2021-04-01 10:02:38 +00:00
|
|
|
int itemRarity = (itemSet.alterRarityMap.find(itemReferenceId) == itemSet.alterRarityMap.end())
|
|
|
|
? item->rarity
|
|
|
|
: itemSet.alterRarityMap[itemReferenceId];
|
2021-03-30 02:48:31 +00:00
|
|
|
|
|
|
|
// if rarity doesn't match the selected one, exclude item
|
|
|
|
// rarity 0 bypasses this step for an individual item
|
|
|
|
if (!itemSet.ignoreRarity && itemRarity != 0 && itemRarity != rarity)
|
2021-03-28 20:57:43 +00:00
|
|
|
continue;
|
|
|
|
|
2021-03-30 02:48:31 +00:00
|
|
|
// alter rarity
|
2021-04-01 10:02:38 +00:00
|
|
|
int itemGender = (itemSet.alterGenderMap.find(itemReferenceId) == itemSet.alterGenderMap.end())
|
|
|
|
? item->gender
|
|
|
|
: itemSet.alterGenderMap[itemReferenceId];
|
2021-03-30 02:48:31 +00:00
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
// if gender is incorrect, exclude item
|
2021-03-30 02:48:31 +00:00
|
|
|
// gender 0 bypasses this step for an individual item
|
|
|
|
if (!itemSet.ignoreGender && itemGender != 0 && itemGender != playerGender)
|
2021-03-16 21:06:10 +00:00
|
|
|
continue;
|
2021-03-28 20:57:43 +00:00
|
|
|
|
2021-03-30 02:48:31 +00:00
|
|
|
validItems.push_back(std::make_pair(itemReferenceId, item));
|
2021-03-16 21:06:10 +00:00
|
|
|
}
|
|
|
|
|
2021-03-28 20:57:43 +00:00
|
|
|
if (validItems.empty()) {
|
2021-03-30 02:48:31 +00:00
|
|
|
std::cout << "[WARN] Set ID " << itemSetId << " Rarity " << rarity << " contains no valid items" << std::endl;
|
2021-03-16 21:06:10 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2022-02-11 17:27:00 +00:00
|
|
|
// initialize all weights as the default weight for all item slots
|
2021-03-30 02:48:31 +00:00
|
|
|
std::vector<int> itemWeights(validItems.size(), itemSet.defaultItemWeight);
|
2021-03-28 20:57:43 +00:00
|
|
|
|
2021-03-30 02:48:31 +00:00
|
|
|
if (!itemSet.alterItemWeightMap.empty()) {
|
2021-03-28 20:57:43 +00:00
|
|
|
for (int i = 0; i < validItems.size(); i++) {
|
2021-03-30 02:48:31 +00:00
|
|
|
int itemReferenceId = validItems[i].first;
|
2021-03-28 20:57:43 +00:00
|
|
|
|
2021-03-30 02:48:31 +00:00
|
|
|
if (itemSet.alterItemWeightMap.find(itemReferenceId) == itemSet.alterItemWeightMap.end())
|
2021-03-28 20:57:43 +00:00
|
|
|
continue;
|
|
|
|
|
2021-03-30 02:48:31 +00:00
|
|
|
int weight = itemSet.alterItemWeightMap[itemReferenceId];
|
2021-03-28 20:57:43 +00:00
|
|
|
// allow 0 weights for convenience
|
|
|
|
if (weight > -1)
|
|
|
|
itemWeights[i] = weight;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int chosenIndex = Rand::randWeighted(itemWeights);
|
2021-03-29 06:22:23 +00:00
|
|
|
ItemReference* item = validItems[chosenIndex].second;
|
2021-03-28 20:57:43 +00:00
|
|
|
|
|
|
|
result->iID = item->itemId;
|
|
|
|
result->iType = item->type;
|
|
|
|
result->iOpt = 1;
|
2021-03-16 21:06:10 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-03-28 20:57:43 +00:00
|
|
|
static int getValidCrateId(int crateId) {
|
|
|
|
// find the crate
|
|
|
|
if (Items::Crates.find(crateId) == Items::Crates.end()) {
|
|
|
|
std::cout << "[WARN] Crate " << crateId << " not found!" << std::endl;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return crateId;
|
|
|
|
}
|
|
|
|
|
2021-03-30 02:48:31 +00:00
|
|
|
static int getValidItemSetId(int crateId) {
|
2021-03-28 20:57:43 +00:00
|
|
|
Crate& crate = Items::Crates[crateId];
|
|
|
|
|
|
|
|
// find item set type
|
2021-03-30 02:48:31 +00:00
|
|
|
if (Items::ItemSets.find(crate.itemSetId) == Items::ItemSets.end()) {
|
2021-03-28 20:57:43 +00:00
|
|
|
std::cout << "[WARN] Crate " << crateId << " was assigned item set "
|
2021-03-30 02:48:31 +00:00
|
|
|
<< crate.itemSetId << " which is invalid!" << std::endl;
|
2021-03-28 20:57:43 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2021-03-30 02:48:31 +00:00
|
|
|
return crate.itemSetId;
|
2021-03-16 21:06:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void itemMoveHandler(CNSocket* sock, CNPacketData* data) {
|
2021-03-20 20:19:48 +00:00
|
|
|
auto itemmove = (sP_CL2FE_REQ_ITEM_MOVE*)data->buf;
|
2020-08-23 00:26:18 +00:00
|
|
|
INITSTRUCT(sP_FE2CL_PC_ITEM_MOVE_SUCC, resp);
|
2020-09-14 13:53:48 +00:00
|
|
|
|
2020-11-17 23:16:16 +00:00
|
|
|
Player* plr = PlayerManager::getPlayer(sock);
|
2020-09-14 13:53:48 +00:00
|
|
|
|
2020-09-14 14:03:30 +00:00
|
|
|
// sanity check
|
2021-01-05 12:17:59 +00:00
|
|
|
if (itemmove->iToSlotNum < 0 || itemmove->iFromSlotNum < 0)
|
2020-09-14 13:53:48 +00:00
|
|
|
return;
|
2021-01-05 12:17:59 +00:00
|
|
|
// NOTE: sending a no-op, "move in-place" packet is not necessary
|
2020-09-14 13:53:48 +00:00
|
|
|
|
2021-03-08 21:29:21 +00:00
|
|
|
if (plr->isTrading) {
|
|
|
|
std::cout << "[WARN] Player attempted to move item while trading" << std::endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-09-14 14:03:30 +00:00
|
|
|
// get the fromItem
|
2020-09-09 20:42:55 +00:00
|
|
|
sItemBase *fromItem;
|
2021-03-16 21:06:10 +00:00
|
|
|
switch ((SlotType)itemmove->eFrom) {
|
|
|
|
case SlotType::EQUIP:
|
2021-01-05 12:17:59 +00:00
|
|
|
if (itemmove->iFromSlotNum >= AEQUIP_COUNT)
|
2020-09-21 19:43:53 +00:00
|
|
|
return;
|
2021-01-05 12:17:59 +00:00
|
|
|
|
|
|
|
fromItem = &plr->Equip[itemmove->iFromSlotNum];
|
|
|
|
break;
|
2021-03-16 21:06:10 +00:00
|
|
|
case SlotType::INVENTORY:
|
2021-01-05 12:17:59 +00:00
|
|
|
if (itemmove->iFromSlotNum >= AINVEN_COUNT)
|
|
|
|
return;
|
|
|
|
|
|
|
|
fromItem = &plr->Inven[itemmove->iFromSlotNum];
|
|
|
|
break;
|
2021-03-16 21:06:10 +00:00
|
|
|
case SlotType::BANK:
|
2021-01-05 12:17:59 +00:00
|
|
|
if (itemmove->iFromSlotNum >= ABANK_COUNT)
|
|
|
|
return;
|
|
|
|
|
|
|
|
fromItem = &plr->Bank[itemmove->iFromSlotNum];
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
std::cout << "[WARN] MoveItem submitted unknown Item Type?! " << itemmove->eFrom << std::endl;
|
|
|
|
return;
|
2020-08-21 02:10:14 +00:00
|
|
|
}
|
2020-08-21 20:09:52 +00:00
|
|
|
|
2020-09-14 14:03:30 +00:00
|
|
|
// get the toItem
|
2020-09-09 20:42:55 +00:00
|
|
|
sItemBase* toItem;
|
2021-03-16 21:06:10 +00:00
|
|
|
switch ((SlotType)itemmove->eTo) {
|
|
|
|
case SlotType::EQUIP:
|
2021-01-05 12:17:59 +00:00
|
|
|
if (itemmove->iToSlotNum >= AEQUIP_COUNT)
|
|
|
|
return;
|
|
|
|
|
2020-11-17 23:16:16 +00:00
|
|
|
toItem = &plr->Equip[itemmove->iToSlotNum];
|
2020-09-09 20:42:55 +00:00
|
|
|
break;
|
2021-03-16 21:06:10 +00:00
|
|
|
case SlotType::INVENTORY:
|
2021-01-05 12:17:59 +00:00
|
|
|
if (itemmove->iToSlotNum >= AINVEN_COUNT)
|
|
|
|
return;
|
|
|
|
|
2020-11-17 23:16:16 +00:00
|
|
|
toItem = &plr->Inven[itemmove->iToSlotNum];
|
2020-09-09 20:42:55 +00:00
|
|
|
break;
|
2021-03-16 21:06:10 +00:00
|
|
|
case SlotType::BANK:
|
2021-01-05 12:17:59 +00:00
|
|
|
if (itemmove->iToSlotNum >= ABANK_COUNT)
|
|
|
|
return;
|
|
|
|
|
2020-12-03 23:58:10 +00:00
|
|
|
toItem = &plr->Bank[itemmove->iToSlotNum];
|
|
|
|
break;
|
2020-09-21 19:43:53 +00:00
|
|
|
default:
|
|
|
|
std::cout << "[WARN] MoveItem submitted unknown Item Type?! " << itemmove->eTo << std::endl;
|
|
|
|
return;
|
2020-08-21 02:10:14 +00:00
|
|
|
}
|
2020-08-21 03:25:39 +00:00
|
|
|
|
2021-01-05 12:17:59 +00:00
|
|
|
// if equipping an item, validate that it's of the correct type for the slot
|
2021-03-16 21:06:10 +00:00
|
|
|
if ((SlotType)itemmove->eTo == SlotType::EQUIP) {
|
2021-01-05 12:17:59 +00:00
|
|
|
if (fromItem->iType == 10 && itemmove->iToSlotNum != 8)
|
|
|
|
return; // vehicle in wrong slot
|
|
|
|
else if (fromItem->iType != 10
|
|
|
|
&& !(fromItem->iType == 0 && itemmove->iToSlotNum == 7)
|
|
|
|
&& fromItem->iType != itemmove->iToSlotNum)
|
|
|
|
return; // something other than a vehicle or a weapon in a non-matching slot
|
|
|
|
else if (itemmove->iToSlotNum >= AEQUIP_COUNT) // TODO: reject slots >= 9?
|
|
|
|
return; // invalid slot
|
|
|
|
}
|
|
|
|
|
2020-09-14 14:03:30 +00:00
|
|
|
// save items to response
|
2021-01-05 12:17:59 +00:00
|
|
|
resp.eTo = itemmove->eFrom;
|
|
|
|
resp.eFrom = itemmove->eTo;
|
2020-09-09 20:42:55 +00:00
|
|
|
resp.ToSlotItem = *toItem;
|
|
|
|
resp.FromSlotItem = *fromItem;
|
|
|
|
|
2020-09-30 17:29:56 +00:00
|
|
|
// swap/stack items in session
|
2021-03-16 21:06:10 +00:00
|
|
|
Item* itemDat = getItemData(toItem->iID, toItem->iType);
|
|
|
|
Item* itemDatFrom = getItemData(fromItem->iID, fromItem->iType);
|
2021-03-04 16:22:01 +00:00
|
|
|
if (itemDat != nullptr && itemDatFrom != nullptr && itemDat->stackSize > 1 && itemDat == itemDatFrom && fromItem->iOpt < itemDat->stackSize && toItem->iOpt < itemDat->stackSize) {
|
2020-09-30 20:23:46 +00:00
|
|
|
// items are stackable, identical, and not maxed, so run stacking logic
|
2020-09-30 17:29:56 +00:00
|
|
|
|
|
|
|
toItem->iOpt += fromItem->iOpt; // sum counts
|
|
|
|
fromItem->iOpt = 0; // deplete from item
|
|
|
|
if (toItem->iOpt > itemDat->stackSize) {
|
|
|
|
// handle overflow
|
|
|
|
fromItem->iOpt += (toItem->iOpt - itemDat->stackSize); // add overflow to fromItem
|
|
|
|
toItem->iOpt = itemDat->stackSize; // set toItem count to max
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fromItem->iOpt == 0) { // from item count depleted
|
|
|
|
// delete item
|
|
|
|
fromItem->iID = 0;
|
|
|
|
fromItem->iType = 0;
|
|
|
|
fromItem->iTimeLimit = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
resp.iFromSlotNum = itemmove->iFromSlotNum;
|
|
|
|
resp.iToSlotNum = itemmove->iToSlotNum;
|
|
|
|
resp.FromSlotItem = *fromItem;
|
|
|
|
resp.ToSlotItem = *toItem;
|
|
|
|
} else {
|
|
|
|
// items not stackable; just swap them
|
|
|
|
sItemBase temp = *toItem;
|
|
|
|
*toItem = *fromItem;
|
|
|
|
*fromItem = temp;
|
|
|
|
resp.iFromSlotNum = itemmove->iToSlotNum;
|
|
|
|
resp.iToSlotNum = itemmove->iFromSlotNum;
|
|
|
|
}
|
2020-08-21 20:09:52 +00:00
|
|
|
|
2020-09-14 14:03:30 +00:00
|
|
|
// send equip change to viewable players
|
2021-03-16 21:06:10 +00:00
|
|
|
if (itemmove->eFrom == (int)SlotType::EQUIP || itemmove->eTo == (int)SlotType::EQUIP) {
|
2020-08-23 00:26:18 +00:00
|
|
|
INITSTRUCT(sP_FE2CL_PC_EQUIP_CHANGE, equipChange);
|
2020-08-22 23:31:09 +00:00
|
|
|
|
2020-11-17 23:16:16 +00:00
|
|
|
equipChange.iPC_ID = plr->iID;
|
2021-03-16 21:06:10 +00:00
|
|
|
if (itemmove->eTo == (int)SlotType::EQUIP) {
|
2020-08-22 23:31:09 +00:00
|
|
|
equipChange.iEquipSlotNum = itemmove->iToSlotNum;
|
2020-09-09 20:42:55 +00:00
|
|
|
equipChange.EquipSlotItem = resp.FromSlotItem;
|
2020-10-21 05:24:51 +00:00
|
|
|
} else {
|
|
|
|
equipChange.iEquipSlotNum = itemmove->iFromSlotNum;
|
|
|
|
equipChange.EquipSlotItem = resp.ToSlotItem;
|
2020-08-22 23:31:09 +00:00
|
|
|
}
|
2020-09-14 13:53:48 +00:00
|
|
|
|
2020-08-27 02:35:13 +00:00
|
|
|
// unequip vehicle if equip slot 8 is 0
|
2021-04-25 04:55:13 +00:00
|
|
|
if (plr->Equip[8].iID == 0 && plr->iPCState & 8) {
|
|
|
|
INITSTRUCT(sP_FE2CL_PC_VEHICLE_OFF_SUCC, response);
|
|
|
|
sock->sendPacket(response, P_FE2CL_PC_VEHICLE_OFF_SUCC);
|
|
|
|
|
|
|
|
// send to other players
|
|
|
|
plr->iPCState &= ~8;
|
|
|
|
INITSTRUCT(sP_FE2CL_PC_STATE_CHANGE, response2);
|
|
|
|
response2.iPC_ID = plr->iID;
|
|
|
|
response2.iState = plr->iPCState;
|
|
|
|
|
|
|
|
PlayerManager::sendToViewable(sock, response2, P_FE2CL_PC_STATE_CHANGE);
|
|
|
|
}
|
2020-08-22 23:31:09 +00:00
|
|
|
|
|
|
|
// send equip event to other players
|
2021-03-20 20:19:48 +00:00
|
|
|
PlayerManager::sendToViewable(sock, equipChange, P_FE2CL_PC_EQUIP_CHANGE);
|
2020-10-05 00:03:13 +00:00
|
|
|
|
2020-09-27 01:53:03 +00:00
|
|
|
// set equipment stats serverside
|
2021-03-16 21:06:10 +00:00
|
|
|
setItemStats(plr);
|
2020-08-21 03:25:39 +00:00
|
|
|
}
|
2020-08-21 20:09:52 +00:00
|
|
|
|
2020-09-14 14:03:30 +00:00
|
|
|
// send response
|
2021-03-20 20:19:48 +00:00
|
|
|
sock->sendPacket(resp, P_FE2CL_PC_ITEM_MOVE_SUCC);
|
2020-08-21 20:09:52 +00:00
|
|
|
}
|
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
static void itemDeleteHandler(CNSocket* sock, CNPacketData* data) {
|
2021-03-20 20:19:48 +00:00
|
|
|
auto itemdel = (sP_CL2FE_REQ_PC_ITEM_DELETE*)data->buf;
|
2020-08-23 00:26:18 +00:00
|
|
|
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_DELETE_SUCC, resp);
|
2020-08-21 20:09:52 +00:00
|
|
|
|
2020-11-17 23:16:16 +00:00
|
|
|
Player* plr = PlayerManager::getPlayer(sock);
|
2020-08-21 20:09:52 +00:00
|
|
|
|
2023-12-18 10:41:53 +00:00
|
|
|
if (itemdel->iSlotNum < 0 || itemdel->iSlotNum >= AINVEN_COUNT)
|
|
|
|
return; // sanity check
|
|
|
|
|
2020-08-22 23:31:09 +00:00
|
|
|
resp.eIL = itemdel->eIL;
|
|
|
|
resp.iSlotNum = itemdel->iSlotNum;
|
2020-08-21 20:09:52 +00:00
|
|
|
|
|
|
|
// so, im not sure what this eIL thing does since you always delete items in inventory and not equips
|
2020-11-17 23:16:16 +00:00
|
|
|
plr->Inven[itemdel->iSlotNum].iID = 0;
|
|
|
|
plr->Inven[itemdel->iSlotNum].iType = 0;
|
|
|
|
plr->Inven[itemdel->iSlotNum].iOpt = 0;
|
2020-08-21 20:09:52 +00:00
|
|
|
|
2021-03-20 20:19:48 +00:00
|
|
|
sock->sendPacket(resp, P_FE2CL_REP_PC_ITEM_DELETE_SUCC);
|
2020-08-21 20:09:52 +00:00
|
|
|
}
|
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
static void itemUseHandler(CNSocket* sock, CNPacketData* data) {
|
2021-03-20 20:19:48 +00:00
|
|
|
auto request = (sP_CL2FE_REQ_ITEM_USE*)data->buf;
|
2020-09-25 05:35:27 +00:00
|
|
|
Player* player = PlayerManager::getPlayer(sock);
|
|
|
|
|
2020-12-01 19:18:01 +00:00
|
|
|
if (request->iSlotNum < 0 || request->iSlotNum >= AINVEN_COUNT)
|
2020-10-25 22:33:02 +00:00
|
|
|
return; // sanity check
|
2020-09-28 18:11:13 +00:00
|
|
|
|
2020-10-19 17:26:14 +00:00
|
|
|
// gumball can only be used from inventory, so we ignore eIL
|
2020-09-25 05:35:27 +00:00
|
|
|
sItemBase gumball = player->Inven[request->iSlotNum];
|
|
|
|
sNano nano = player->Nanos[player->equippedNanos[request->iNanoSlot]];
|
|
|
|
|
2020-10-19 17:26:14 +00:00
|
|
|
// sanity check, check if gumball exists
|
2020-09-25 05:35:27 +00:00
|
|
|
if (!(gumball.iOpt > 0 && gumball.iType == 7 && gumball.iID>=119 && gumball.iID<=121)) {
|
|
|
|
std::cout << "[WARN] Gumball not found" << std::endl;
|
|
|
|
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_USE_FAIL, response);
|
2021-03-20 20:19:48 +00:00
|
|
|
sock->sendPacket(response, P_FE2CL_REP_PC_ITEM_USE_FAIL);
|
2020-09-25 05:35:27 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-10-19 17:26:14 +00:00
|
|
|
// sanity check, check if gumball type matches nano style
|
2021-03-16 22:29:13 +00:00
|
|
|
int nanoStyle = Nanos::nanoStyle(nano.iID);
|
2020-09-25 05:35:27 +00:00
|
|
|
if (!((gumball.iID == 119 && nanoStyle == 0) ||
|
|
|
|
( gumball.iID == 120 && nanoStyle == 1) ||
|
|
|
|
( gumball.iID == 121 && nanoStyle == 2))) {
|
|
|
|
std::cout << "[WARN] Gumball type doesn't match nano type" << std::endl;
|
|
|
|
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_USE_FAIL, response);
|
2021-03-20 20:19:48 +00:00
|
|
|
sock->sendPacket(response, P_FE2CL_REP_PC_ITEM_USE_FAIL);
|
2020-09-25 05:35:27 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
gumball.iOpt -= 1;
|
|
|
|
if (gumball.iOpt == 0)
|
|
|
|
gumball = {};
|
|
|
|
|
2020-11-08 00:26:44 +00:00
|
|
|
size_t resplen = sizeof(sP_FE2CL_REP_PC_ITEM_USE_SUCC) + sizeof(sSkillResult_Buff);
|
|
|
|
|
2020-11-23 23:42:34 +00:00
|
|
|
// validate response packet
|
2020-11-08 00:26:44 +00:00
|
|
|
if (!validOutVarPacket(sizeof(sP_FE2CL_REP_PC_ITEM_USE_SUCC), 1, sizeof(sSkillResult_Buff))) {
|
|
|
|
std::cout << "[WARN] bad sP_FE2CL_REP_PC_ITEM_USE_SUCC packet size" << std::endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-11-13 23:06:18 +00:00
|
|
|
if (gumball.iOpt == 0)
|
|
|
|
gumball = {};
|
|
|
|
|
2024-10-29 02:50:35 +00:00
|
|
|
uint8_t respbuf[CN_PACKET_BODY_SIZE];
|
|
|
|
memset(respbuf, 0, CN_PACKET_BODY_SIZE);
|
2020-11-08 00:26:44 +00:00
|
|
|
|
|
|
|
sP_FE2CL_REP_PC_ITEM_USE_SUCC *resp = (sP_FE2CL_REP_PC_ITEM_USE_SUCC*)respbuf;
|
|
|
|
sSkillResult_Buff *respdata = (sSkillResult_Buff*)(respbuf+sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC));
|
|
|
|
resp->iPC_ID = player->iID;
|
|
|
|
resp->eIL = 1;
|
|
|
|
resp->iSlotNum = request->iSlotNum;
|
|
|
|
resp->RemainItem = gumball;
|
|
|
|
resp->iTargetCnt = 1;
|
2022-11-27 22:36:47 +00:00
|
|
|
resp->eST = (int32_t)SkillType::NANOSTIMPAK;
|
2020-11-17 03:30:01 +00:00
|
|
|
resp->iSkillID = 144;
|
2020-09-25 05:35:27 +00:00
|
|
|
|
2022-07-17 06:33:57 +00:00
|
|
|
int eCSB = ECSB_STIMPAKSLOT1 + request->iNanoSlot;
|
2020-09-25 05:35:27 +00:00
|
|
|
|
2020-11-08 00:26:44 +00:00
|
|
|
respdata->eCT = 1;
|
|
|
|
respdata->iID = player->iID;
|
2022-07-17 06:33:57 +00:00
|
|
|
respdata->iConditionBitFlag = CSB_FROM_ECSB(eCSB);
|
|
|
|
|
|
|
|
int durationMilliseconds = Abilities::SkillTable[144].durationTime[0] * 100;
|
|
|
|
BuffStack gumballBuff = {
|
|
|
|
durationMilliseconds / MS_PER_PLAYER_TICK,
|
|
|
|
0,
|
|
|
|
sock,
|
|
|
|
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);
|
2020-11-08 00:26:44 +00:00
|
|
|
|
|
|
|
sock->sendPacket((void*)&respbuf, P_FE2CL_REP_PC_ITEM_USE_SUCC, resplen);
|
|
|
|
// update inventory serverside
|
|
|
|
player->Inven[resp->iSlotNum] = resp->RemainItem;
|
2020-09-25 05:35:27 +00:00
|
|
|
}
|
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
static void itemBankOpenHandler(CNSocket* sock, CNPacketData* data) {
|
2020-11-17 23:16:16 +00:00
|
|
|
Player* plr = PlayerManager::getPlayer(sock);
|
|
|
|
|
2020-09-14 14:03:30 +00:00
|
|
|
// just send bank inventory
|
2020-09-09 20:42:55 +00:00
|
|
|
INITSTRUCT(sP_FE2CL_REP_PC_BANK_OPEN_SUCC, resp);
|
|
|
|
for (int i = 0; i < ABANK_COUNT; i++) {
|
2020-12-03 23:58:10 +00:00
|
|
|
resp.aBank[i] = plr->Bank[i];
|
2020-09-09 20:42:55 +00:00
|
|
|
}
|
|
|
|
resp.iExtraBank = 1;
|
2021-03-20 20:19:48 +00:00
|
|
|
sock->sendPacket(resp, P_FE2CL_REP_PC_BANK_OPEN_SUCC);
|
2020-09-09 20:42:55 +00:00
|
|
|
}
|
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
static void chestOpenHandler(CNSocket *sock, CNPacketData *data) {
|
2021-03-20 20:19:48 +00:00
|
|
|
auto pkt = (sP_CL2FE_REQ_ITEM_CHEST_OPEN *)data->buf;
|
2020-08-28 16:25:03 +00:00
|
|
|
|
2020-12-16 15:27:18 +00:00
|
|
|
// sanity check
|
2021-03-08 21:29:21 +00:00
|
|
|
if (pkt->eIL != 1 || pkt->iSlotNum < 0 || pkt->iSlotNum >= AINVEN_COUNT)
|
|
|
|
return;
|
|
|
|
|
|
|
|
Player *plr = PlayerManager::getPlayer(sock);
|
|
|
|
|
|
|
|
sItemBase *chest = &plr->Inven[pkt->iSlotNum];
|
|
|
|
// we could reject the packet if the client thinks the item is different, but eh
|
|
|
|
|
|
|
|
if (chest->iType != 9) {
|
2020-12-16 15:27:18 +00:00
|
|
|
std::cout << "[WARN] Player tried to open a crate with incorrect iType ?!" << std::endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef ACADEMY
|
|
|
|
// check if chest isn't a nano capsule
|
2021-03-16 21:06:10 +00:00
|
|
|
if (NanoCapsules.find(chest->iID) != NanoCapsules.end())
|
2021-03-08 21:29:21 +00:00
|
|
|
return nanoCapsuleHandler(sock, pkt->iSlotNum, chest);
|
2020-12-16 15:27:18 +00:00
|
|
|
#endif
|
|
|
|
|
2020-10-17 23:19:05 +00:00
|
|
|
// chest opening acknowledgement packet
|
|
|
|
INITSTRUCT(sP_FE2CL_REP_ITEM_CHEST_OPEN_SUCC, resp);
|
2021-03-08 21:29:21 +00:00
|
|
|
resp.iSlotNum = pkt->iSlotNum;
|
2020-10-17 23:19:05 +00:00
|
|
|
|
2020-08-28 16:25:03 +00:00
|
|
|
// item giving packet
|
|
|
|
const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward);
|
2024-10-29 02:50:35 +00:00
|
|
|
assert(resplen < CN_PACKET_BODY_SIZE);
|
2020-08-28 16:25:03 +00:00
|
|
|
// 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);
|
|
|
|
|
2020-09-09 21:00:13 +00:00
|
|
|
// maintain stats
|
2020-10-17 23:19:05 +00:00
|
|
|
reward->m_iCandy = plr->money;
|
|
|
|
reward->m_iFusionMatter = plr->fusionmatter;
|
2020-08-28 16:25:03 +00:00
|
|
|
reward->iFatigue = 100; // prevents warning message
|
|
|
|
reward->iFatigue_Level = 1;
|
|
|
|
reward->iItemCnt = 1; // remember to update resplen if you change this
|
2020-11-17 18:48:20 +00:00
|
|
|
reward->m_iBatteryN = plr->batteryN;
|
|
|
|
reward->m_iBatteryW = plr->batteryW;
|
2020-08-28 16:25:03 +00:00
|
|
|
|
2021-03-08 21:29:21 +00:00
|
|
|
item->iSlotNum = pkt->iSlotNum;
|
|
|
|
item->eIL = 1;
|
2020-08-28 16:25:03 +00:00
|
|
|
|
2021-03-30 02:48:31 +00:00
|
|
|
int validItemSetId = -1, rarity = -1, ret = -1;
|
2020-08-28 16:25:03 +00:00
|
|
|
|
2021-03-28 20:57:43 +00:00
|
|
|
int validCrateId = getValidCrateId(chest->iID);
|
|
|
|
bool failing = (validCrateId == -1);
|
|
|
|
|
|
|
|
if (!failing)
|
2021-03-30 02:48:31 +00:00
|
|
|
validItemSetId = getValidItemSetId(validCrateId);
|
|
|
|
failing = (validItemSetId == -1);
|
2020-08-28 16:25:03 +00:00
|
|
|
|
2020-10-17 23:19:05 +00:00
|
|
|
if (!failing)
|
2021-03-30 02:48:31 +00:00
|
|
|
rarity = getRarity(validCrateId, validItemSetId);
|
2021-03-28 20:57:43 +00:00
|
|
|
failing = (rarity == -1);
|
2020-08-28 16:25:03 +00:00
|
|
|
|
2020-10-17 23:19:05 +00:00
|
|
|
if (!failing)
|
2021-03-30 02:48:31 +00:00
|
|
|
ret = getCrateItem(&item->sItem, validItemSetId, rarity, plr->PCStyle.iGender);
|
2021-03-28 20:57:43 +00:00
|
|
|
failing = (ret == -1);
|
2020-10-17 23:19:05 +00:00
|
|
|
|
|
|
|
// if we failed to open a crate, at least give the player a gumball (suggested by Jade)
|
|
|
|
if (failing) {
|
|
|
|
item->sItem.iType = 7;
|
2021-03-28 20:57:43 +00:00
|
|
|
item->sItem.iID = 119 + Rand::rand(3);
|
2020-10-17 23:19:05 +00:00
|
|
|
item->sItem.iOpt = 1;
|
2021-03-28 20:57:43 +00:00
|
|
|
|
|
|
|
std::cout << "[WARN] Crate open failed, giving a Gumball..." << std::endl;
|
functional crates (no more plungers) (#133)
* FM, Taros and Boosts awards from killing mobs should be pretty
accurate now. A temporary formula for adjusting player/mob level gap is
implemented, but it will probably need to be adjusted in the future
* Mobs now drop correct crates
* Crates can be opened and give you correct items This includes
regular mob crates, world boss crates, mission crates, IZ race crates,
E.G.G.E.R.s, golden Eggs, and Event Crates. Keep in mind that neither
IZ races or golden Eggs are implemented, but if you spawn such a crate
it can be opened.
* All data is read from a json file, for which I'm going to release a
tool soon so it's easily adjustable
* There is a new setting for enabling events, which enables dropping
extra event crates These are Knishmas, Halloween and Easter
2020-10-10 17:18:47 +00:00
|
|
|
}
|
2020-10-17 23:19:05 +00:00
|
|
|
// update player
|
2021-03-08 21:29:21 +00:00
|
|
|
plr->Inven[pkt->iSlotNum] = item->sItem;
|
functional crates (no more plungers) (#133)
* FM, Taros and Boosts awards from killing mobs should be pretty
accurate now. A temporary formula for adjusting player/mob level gap is
implemented, but it will probably need to be adjusted in the future
* Mobs now drop correct crates
* Crates can be opened and give you correct items This includes
regular mob crates, world boss crates, mission crates, IZ race crates,
E.G.G.E.R.s, golden Eggs, and Event Crates. Keep in mind that neither
IZ races or golden Eggs are implemented, but if you spawn such a crate
it can be opened.
* All data is read from a json file, for which I'm going to release a
tool soon so it's easily adjustable
* There is a new setting for enabling events, which enables dropping
extra event crates These are Knishmas, Halloween and Easter
2020-10-10 17:18:47 +00:00
|
|
|
|
2020-10-17 23:19:05 +00:00
|
|
|
// transmit item
|
|
|
|
sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen);
|
|
|
|
|
|
|
|
// transmit chest opening acknowledgement packet
|
|
|
|
std::cout << "opening chest..." << std::endl;
|
2021-03-20 20:19:48 +00:00
|
|
|
sock->sendPacket(resp, P_FE2CL_REP_ITEM_CHEST_OPEN_SUCC);
|
functional crates (no more plungers) (#133)
* FM, Taros and Boosts awards from killing mobs should be pretty
accurate now. A temporary formula for adjusting player/mob level gap is
implemented, but it will probably need to be adjusted in the future
* Mobs now drop correct crates
* Crates can be opened and give you correct items This includes
regular mob crates, world boss crates, mission crates, IZ race crates,
E.G.G.E.R.s, golden Eggs, and Event Crates. Keep in mind that neither
IZ races or golden Eggs are implemented, but if you spawn such a crate
it can be opened.
* All data is read from a json file, for which I'm going to release a
tool soon so it's easily adjustable
* There is a new setting for enabling events, which enables dropping
extra event crates These are Knishmas, Halloween and Easter
2020-10-10 17:18:47 +00:00
|
|
|
}
|
|
|
|
|
2021-03-16 22:29:13 +00:00
|
|
|
// TODO: use this in cleaned up Items
|
|
|
|
int Items::findFreeSlot(Player *plr) {
|
2020-08-28 16:25:03 +00:00
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < AINVEN_COUNT; i++)
|
2020-09-12 00:25:45 +00:00
|
|
|
if (plr->Inven[i].iType == 0 && plr->Inven[i].iID == 0 && plr->Inven[i].iOpt == 0)
|
2020-08-28 16:25:03 +00:00
|
|
|
return i;
|
|
|
|
|
|
|
|
// not found
|
|
|
|
return -1;
|
|
|
|
}
|
2020-09-14 04:25:14 +00:00
|
|
|
|
2021-03-16 22:29:13 +00:00
|
|
|
Item* Items::getItemData(int32_t id, int32_t type) {
|
2020-12-04 18:57:08 +00:00
|
|
|
if(ItemData.find(std::make_pair(id, type)) != ItemData.end())
|
|
|
|
return &ItemData[std::make_pair(id, type)];
|
2020-09-14 14:27:52 +00:00
|
|
|
return nullptr;
|
2020-09-14 04:25:14 +00:00
|
|
|
}
|
2020-09-22 11:16:09 +00:00
|
|
|
|
2021-03-16 22:29:13 +00:00
|
|
|
void Items::checkItemExpire(CNSocket* sock, Player* player) {
|
2020-09-22 11:16:09 +00:00
|
|
|
if (player->toRemoveVehicle.eIL == 0 && player->toRemoveVehicle.iSlotNum == 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* prepare packet
|
|
|
|
* yes, this is a varadic packet, however analyzing client behavior and code
|
|
|
|
* it only checks takes the first item sent into account
|
|
|
|
* yes, this is very stupid
|
|
|
|
* therefore, we delete all but 1 expired vehicle while loading player
|
|
|
|
* to delete the last one here so player gets a notification
|
|
|
|
*/
|
|
|
|
|
|
|
|
const size_t resplen = sizeof(sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM) + sizeof(sTimeLimitItemDeleteInfo2CL);
|
2024-10-29 02:50:35 +00:00
|
|
|
assert(resplen < CN_PACKET_BODY_SIZE);
|
2020-09-22 11:16:09 +00:00
|
|
|
// we know it's only one trailing struct, so we can skip full validation
|
2020-10-05 00:03:13 +00:00
|
|
|
uint8_t respbuf[resplen]; // not a variable length array, don't worry
|
2021-03-20 20:19:48 +00:00
|
|
|
auto packet = (sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM*)respbuf;
|
2020-09-22 11:16:09 +00:00
|
|
|
sTimeLimitItemDeleteInfo2CL* itemData = (sTimeLimitItemDeleteInfo2CL*)(respbuf + sizeof(sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM));
|
|
|
|
memset(respbuf, 0, resplen);
|
|
|
|
|
|
|
|
packet->iItemListCount = 1;
|
|
|
|
itemData->eIL = player->toRemoveVehicle.eIL;
|
|
|
|
itemData->iSlotNum = player->toRemoveVehicle.iSlotNum;
|
|
|
|
sock->sendPacket((void*)&respbuf, P_FE2CL_PC_DELETE_TIME_LIMIT_ITEM, resplen);
|
2020-10-05 00:03:13 +00:00
|
|
|
|
2020-09-23 09:21:32 +00:00
|
|
|
// delete serverside
|
2020-09-22 11:16:09 +00:00
|
|
|
if (player->toRemoveVehicle.eIL == 0)
|
|
|
|
memset(&player->Equip[8], 0, sizeof(sItemBase));
|
|
|
|
else
|
|
|
|
memset(&player->Inven[player->toRemoveVehicle.iSlotNum], 0, sizeof(sItemBase));
|
2020-09-23 09:21:32 +00:00
|
|
|
|
|
|
|
player->toRemoveVehicle.eIL = 0;
|
|
|
|
player->toRemoveVehicle.iSlotNum = 0;
|
2020-09-22 11:16:09 +00:00
|
|
|
}
|
2020-09-27 01:53:03 +00:00
|
|
|
|
2021-03-16 22:29:13 +00:00
|
|
|
void Items::setItemStats(Player* plr) {
|
2020-09-27 01:53:03 +00:00
|
|
|
|
|
|
|
plr->pointDamage = 8 + plr->level * 2;
|
|
|
|
plr->groupDamage = 8 + plr->level * 2;
|
2020-12-31 05:54:57 +00:00
|
|
|
plr->fireRate = 0;
|
2020-09-27 01:53:03 +00:00
|
|
|
plr->defense = 16 + plr->level * 4;
|
|
|
|
|
|
|
|
Item* itemStatsDat;
|
|
|
|
|
|
|
|
for (int i = 0; i < 4; i++) {
|
2021-03-16 21:06:10 +00:00
|
|
|
itemStatsDat = getItemData(plr->Equip[i].iID, plr->Equip[i].iType);
|
2020-10-14 21:15:02 +00:00
|
|
|
if (itemStatsDat == nullptr) {
|
|
|
|
std::cout << "[WARN] setItemStats(): getItemData() returned NULL" << std::endl;
|
|
|
|
continue;
|
|
|
|
}
|
2020-09-27 01:53:03 +00:00
|
|
|
plr->pointDamage += itemStatsDat->pointDamage;
|
|
|
|
plr->groupDamage += itemStatsDat->groupDamage;
|
2020-12-31 05:54:57 +00:00
|
|
|
plr->fireRate += itemStatsDat->fireRate;
|
2020-09-27 01:53:03 +00:00
|
|
|
plr->defense += itemStatsDat->defense;
|
|
|
|
}
|
2020-10-05 00:03:13 +00:00
|
|
|
}
|
2020-10-04 23:54:08 +00:00
|
|
|
|
|
|
|
// HACK: work around the invisible weapon bug
|
2021-03-16 21:06:10 +00:00
|
|
|
// TODO: I don't think this makes a difference at all? Check and remove, if necessary.
|
2021-03-16 22:29:13 +00:00
|
|
|
void Items::updateEquips(CNSocket* sock, Player* plr) {
|
2020-10-04 23:54:08 +00:00
|
|
|
for (int i = 0; i < 4; i++) {
|
|
|
|
INITSTRUCT(sP_FE2CL_PC_EQUIP_CHANGE, resp);
|
|
|
|
|
|
|
|
resp.iPC_ID = plr->iID;
|
|
|
|
resp.iEquipSlotNum = i;
|
|
|
|
resp.EquipSlotItem = plr->Equip[i];
|
2020-10-05 00:03:13 +00:00
|
|
|
|
2021-03-20 20:19:48 +00:00
|
|
|
PlayerManager::sendToViewable(sock, resp, P_FE2CL_PC_EQUIP_CHANGE);
|
2020-10-04 23:54:08 +00:00
|
|
|
}
|
2020-10-05 00:03:13 +00:00
|
|
|
}
|
2020-12-16 15:27:18 +00:00
|
|
|
|
2021-03-28 20:57:43 +00:00
|
|
|
static void getMobDrop(sItemBase* reward, const std::vector<int>& weights, const std::vector<int>& crateIds, int rolled) {
|
|
|
|
int chosenIndex = choice(weights, rolled);
|
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
reward->iType = 9;
|
|
|
|
reward->iOpt = 1;
|
2021-03-28 20:57:43 +00:00
|
|
|
reward->iID = crateIds[chosenIndex];
|
2021-03-16 21:06:10 +00:00
|
|
|
}
|
|
|
|
|
2021-04-04 10:45:57 +00:00
|
|
|
static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRoll& rolled) {
|
2021-03-16 18:41:20 +00:00
|
|
|
Player *plr = PlayerManager::getPlayer(sock);
|
|
|
|
|
|
|
|
const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward);
|
2024-10-29 02:50:35 +00:00
|
|
|
assert(resplen < CN_PACKET_BODY_SIZE);
|
2021-03-16 18:41:20 +00:00
|
|
|
// 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);
|
|
|
|
|
|
|
|
// sanity check
|
2021-04-04 10:45:57 +00:00
|
|
|
if (Items::MobDrops.find(mobDropId) == Items::MobDrops.end()) {
|
|
|
|
std::cout << "[WARN] Drop Type " << mobDropId << " was not found" << std::endl;
|
2021-03-16 18:41:20 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
// find correct mob drop
|
2021-04-04 10:45:57 +00:00
|
|
|
MobDrop& drop = Items::MobDrops[mobDropId];
|
2021-03-16 18:41:20 +00:00
|
|
|
|
2021-03-28 20:57:43 +00:00
|
|
|
// use the keys to fetch data from other maps
|
|
|
|
// sanity check
|
|
|
|
if (Items::CrateDropChances.find(drop.crateDropChanceId) == Items::CrateDropChances.end()) {
|
|
|
|
std::cout << "[WARN] Crate Drop Chance Object " << drop.crateDropChanceId << " was not found" << std::endl;
|
|
|
|
return;
|
|
|
|
}
|
2021-03-28 22:40:39 +00:00
|
|
|
CrateDropChance& crateDropChance = Items::CrateDropChances[drop.crateDropChanceId];
|
2021-03-28 20:57:43 +00:00
|
|
|
|
|
|
|
// sanity check
|
|
|
|
if (Items::CrateDropTypes.find(drop.crateDropTypeId) == Items::CrateDropTypes.end()) {
|
|
|
|
std::cout << "[WARN] Crate Drop Type Object " << drop.crateDropTypeId << " was not found" << std::endl;
|
|
|
|
return;
|
|
|
|
}
|
2021-03-28 22:40:39 +00:00
|
|
|
std::vector<int>& crateDropType = Items::CrateDropTypes[drop.crateDropTypeId];
|
2021-03-28 20:57:43 +00:00
|
|
|
|
|
|
|
// sanity check
|
|
|
|
if (Items::MiscDropChances.find(drop.miscDropChanceId) == Items::MiscDropChances.end()) {
|
|
|
|
std::cout << "[WARN] Misc Drop Chance Object " << drop.miscDropChanceId << " was not found" << std::endl;
|
|
|
|
return;
|
|
|
|
}
|
2021-03-28 22:40:39 +00:00
|
|
|
MiscDropChance& miscDropChance = Items::MiscDropChances[drop.miscDropChanceId];
|
2021-03-28 20:57:43 +00:00
|
|
|
|
|
|
|
// sanity check
|
|
|
|
if (Items::MiscDropTypes.find(drop.miscDropTypeId) == Items::MiscDropTypes.end()) {
|
|
|
|
std::cout << "[WARN] Misc Drop Type Object " << drop.miscDropTypeId << " was not found" << std::endl;
|
|
|
|
return;
|
|
|
|
}
|
2021-03-28 22:40:39 +00:00
|
|
|
MiscDropType& miscDropType = Items::MiscDropTypes[drop.miscDropTypeId];
|
2021-03-28 20:57:43 +00:00
|
|
|
|
2021-04-04 10:45:57 +00:00
|
|
|
if (rolled.taros % miscDropChance.taroDropChanceTotal < miscDropChance.taroDropChance) {
|
|
|
|
plr->money += miscDropType.taroAmount;
|
|
|
|
// money nano boost
|
2022-07-17 06:33:57 +00:00
|
|
|
if (plr->hasBuff(ECSB_REWARD_CASH)) {
|
2021-04-04 10:45:57 +00:00
|
|
|
int boost = 0;
|
|
|
|
if (Nanos::getNanoBoost(plr)) // for gumballs
|
|
|
|
boost = 1;
|
|
|
|
plr->money += miscDropType.taroAmount * (5 + boost) / 25;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (rolled.fm % miscDropChance.fmDropChanceTotal < miscDropChance.fmDropChance) {
|
|
|
|
// formula for scaling FM with player/mob level difference
|
|
|
|
// TODO: adjust this better
|
|
|
|
int levelDifference = plr->level - mob->level;
|
|
|
|
int fm = miscDropType.fmAmount;
|
|
|
|
if (levelDifference > 0)
|
|
|
|
fm = levelDifference < 10 ? fm - (levelDifference * fm / 10) : 0;
|
|
|
|
// scavenger nano boost
|
2022-07-17 06:33:57 +00:00
|
|
|
if (plr->hasBuff(ECSB_REWARD_BLOB)) {
|
2021-04-04 10:45:57 +00:00
|
|
|
int boost = 0;
|
|
|
|
if (Nanos::getNanoBoost(plr)) // for gumballs
|
|
|
|
boost = 1;
|
|
|
|
fm += fm * (5 + boost) / 25;
|
|
|
|
}
|
|
|
|
|
|
|
|
Missions::updateFusionMatter(sock, fm);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rolled.potions % miscDropChance.potionDropChanceTotal < miscDropChance.potionDropChance)
|
2021-03-28 20:57:43 +00:00
|
|
|
plr->batteryN += miscDropType.potionAmount;
|
2021-04-04 10:45:57 +00:00
|
|
|
if (rolled.boosts % miscDropChance.boostDropChanceTotal < miscDropChance.boostDropChance)
|
2021-03-28 20:57:43 +00:00
|
|
|
plr->batteryW += miscDropType.boostAmount;
|
|
|
|
|
2021-03-16 18:41:20 +00:00
|
|
|
// caps
|
|
|
|
if (plr->batteryW > 9999)
|
|
|
|
plr->batteryW = 9999;
|
|
|
|
if (plr->batteryN > 9999)
|
|
|
|
plr->batteryN = 9999;
|
|
|
|
|
|
|
|
// simple rewards
|
|
|
|
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
|
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
int slot = findFreeSlot(plr);
|
2021-03-16 18:41:20 +00:00
|
|
|
|
|
|
|
// no drop
|
2021-04-04 10:45:57 +00:00
|
|
|
if (slot == -1 || rolled.crate % crateDropChance.dropChanceTotal >= crateDropChance.dropChance) {
|
2021-03-16 18:41:20 +00:00
|
|
|
// no room for an item, but you still get FM and taros
|
|
|
|
reward->iItemCnt = 0;
|
|
|
|
sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, sizeof(sP_FE2CL_REP_REWARD_ITEM));
|
|
|
|
} else {
|
|
|
|
// item reward
|
2021-04-04 10:45:57 +00:00
|
|
|
getMobDrop(&item->sItem, crateDropChance.crateTypeDropWeights, crateDropType, rolled.crateType);
|
2021-03-16 18:41:20 +00:00
|
|
|
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);
|
|
|
|
}
|
2021-04-04 10:45:57 +00:00
|
|
|
}
|
2021-03-16 18:41:20 +00:00
|
|
|
|
2021-04-04 10:45:57 +00:00
|
|
|
void Items::giveMobDrop(CNSocket *sock, Mob* mob, const DropRoll& rolled, const DropRoll& eventRolled) {
|
|
|
|
// sanity check
|
2021-06-20 18:37:37 +00:00
|
|
|
if (Items::MobToDropMap.find(mob->type) == Items::MobToDropMap.end()) {
|
|
|
|
std::cout << "[WARN] Mob ID " << mob->type << " has no drops assigned" << std::endl;
|
2021-04-04 10:45:57 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
// find mob drop id
|
2021-06-20 18:37:37 +00:00
|
|
|
int mobDropId = Items::MobToDropMap[mob->type];
|
2021-04-04 10:45:57 +00:00
|
|
|
|
|
|
|
giveSingleDrop(sock, mob, mobDropId, rolled);
|
|
|
|
|
|
|
|
if (settings::EVENTMODE != 0) {
|
|
|
|
// sanity check
|
|
|
|
if (Items::EventToDropMap.find(settings::EVENTMODE) == Items::EventToDropMap.end()) {
|
|
|
|
std::cout << "[WARN] Event " << settings::EVENTMODE << " has no mob drop assigned" << std::endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// find mob drop id
|
|
|
|
int eventMobDropId = Items::EventToDropMap[settings::EVENTMODE];
|
|
|
|
|
|
|
|
giveSingleDrop(sock, mob, eventMobDropId, eventRolled);
|
|
|
|
}
|
2021-03-16 18:41:20 +00:00
|
|
|
}
|
|
|
|
|
2021-03-16 22:29:13 +00:00
|
|
|
void Items::init() {
|
2021-03-13 02:09:36 +00:00
|
|
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_ITEM_MOVE, itemMoveHandler);
|
|
|
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ITEM_DELETE, itemDeleteHandler);
|
|
|
|
// this one is for gumballs
|
|
|
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_ITEM_USE, itemUseHandler);
|
|
|
|
// Bank
|
|
|
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_BANK_OPEN, itemBankOpenHandler);
|
|
|
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_ITEM_CHEST_OPEN, chestOpenHandler);
|
2021-03-13 20:22:29 +00:00
|
|
|
}
|