mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2026-03-28 15:40:03 +00:00
Compare commits
2 Commits
9a62ec61c9
...
06d1ccc17e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06d1ccc17e | ||
|
|
113bc0bc1b |
31
config.ini
31
config.ini
@@ -43,10 +43,6 @@ simulatemobs=true
|
||||
# little message players see when they enter the game
|
||||
motd=Welcome to OpenFusion!
|
||||
|
||||
# The following are the default locations of the JSON files the server
|
||||
# requires to run. You can override them by changing their values and
|
||||
# uncommenting them (removing the leading # character from that line).
|
||||
|
||||
# Should drop fixes be enabled?
|
||||
# This will add drops to (mostly Academy-specific) mobs that don't have drops
|
||||
# and rearrange drop tables that are either unassigned or stranded in difficult to reach mobs
|
||||
@@ -54,6 +50,30 @@ motd=Welcome to OpenFusion!
|
||||
# This is a polish option that is slightly inauthentic to the original game.
|
||||
#dropfixesenabled=true
|
||||
|
||||
# Should groups have to divide up gained Taros / FM among themselves?
|
||||
# Taros is divided up, FM gets diminished per group member, roughly -12.5% per group member
|
||||
# Original game worked like this. Uncomment below to disable this behavior.
|
||||
#lesstarofmingroupdisabled=true
|
||||
|
||||
# General reward percentages
|
||||
# You can change the rate of taro and fusion matter gains for all players.
|
||||
# The numbers are in percentages, i.e. 1000 is 1000%. You should only use whole numbers, no decimals.
|
||||
# Uncomment and change below to your desired rate. Defaults are 100%, regular gain rates.
|
||||
#tarorate=100
|
||||
#fusionmatterrate=100
|
||||
|
||||
# Should expired items in the bank disappear automatically?
|
||||
# Original game let you kep expired items in the bank until you take them out.
|
||||
# Uncomment below to enable this behavior.
|
||||
#removeexpireditemsfrombank=true
|
||||
|
||||
# Should there be a score cap for infected zone races?
|
||||
#izracescorecapped=true
|
||||
|
||||
# The following are the default locations of the JSON files the server
|
||||
# requires to run. You can override them by changing their values and
|
||||
# uncommenting them (removing the leading # character from that line).
|
||||
|
||||
# location of the tabledata folder
|
||||
#tdatadir=tdata/
|
||||
# location of the patch folder
|
||||
@@ -79,9 +99,6 @@ motd=Welcome to OpenFusion!
|
||||
# location of the database
|
||||
#dbpath=database.db
|
||||
|
||||
# should there be a score cap for infected zone races?
|
||||
#izracescorecapped=true
|
||||
|
||||
# should tutorial flags be disabled off the bat?
|
||||
disablefirstuseflag=true
|
||||
|
||||
|
||||
@@ -170,11 +170,11 @@ static SkillResult handleSkillBatteryDrain(SkillData* skill, int power, ICombata
|
||||
if(!blocked) {
|
||||
boostDrain = (int)(skill->values[0][power] * scalingFactor);
|
||||
if(boostDrain > plr->batteryW) boostDrain = plr->batteryW;
|
||||
plr->batteryW -= boostDrain;
|
||||
plr->subtractCapped(CappedValueType::BATTERY_W, boostDrain);
|
||||
|
||||
potionDrain = (int)(skill->values[1][power] * scalingFactor);
|
||||
if(potionDrain > plr->batteryN) potionDrain = plr->batteryN;
|
||||
plr->batteryN -= potionDrain;
|
||||
plr->subtractCapped(CappedValueType::BATTERY_N, potionDrain);
|
||||
}
|
||||
|
||||
sSkillResult_BatteryDrain result{};
|
||||
@@ -364,7 +364,7 @@ void Abilities::useNPCSkill(EntityRef npc, int skillID, std::vector<ICombatant*>
|
||||
ICombatant* src = nullptr;
|
||||
if(npc.kind == EntityKind::COMBAT_NPC || npc.kind == EntityKind::MOB)
|
||||
src = dynamic_cast<ICombatant*>(entity);
|
||||
|
||||
|
||||
SkillData* skill = &SkillTable[skillID];
|
||||
|
||||
std::vector<SkillResult> results = handleSkill(skill, 0, src, affected);
|
||||
@@ -443,7 +443,7 @@ std::vector<ICombatant*> Abilities::matchTargets(ICombatant* src, SkillData* ski
|
||||
}
|
||||
}
|
||||
|
||||
return targets;
|
||||
return targets;
|
||||
}
|
||||
|
||||
/* ripped from client (enums emplaced) */
|
||||
|
||||
@@ -75,30 +75,22 @@ static void setValuePlayer(CNSocket* sock, CNPacketData* data) {
|
||||
case CN_GM_SET_VALUE_TYPE__HP:
|
||||
response.iSetValue = plr->HP = setData->iSetValue;
|
||||
break;
|
||||
case CN_GM_SET_VALUE_TYPE__WEAPON_BATTERY :
|
||||
plr->batteryW = setData->iSetValue;
|
||||
|
||||
// caps
|
||||
if (plr->batteryW > 9999)
|
||||
plr->batteryW = 9999;
|
||||
|
||||
case CN_GM_SET_VALUE_TYPE__WEAPON_BATTERY:
|
||||
plr->setCapped(CappedValueType::BATTERY_W, setData->iSetValue);
|
||||
response.iSetValue = plr->batteryW;
|
||||
break;
|
||||
case CN_GM_SET_VALUE_TYPE__NANO_BATTERY:
|
||||
plr->batteryN = setData->iSetValue;
|
||||
|
||||
// caps
|
||||
if (plr->batteryN > 9999)
|
||||
plr->batteryN = 9999;
|
||||
|
||||
plr->setCapped(CappedValueType::BATTERY_N, setData->iSetValue);
|
||||
response.iSetValue = plr->batteryN;
|
||||
break;
|
||||
case CN_GM_SET_VALUE_TYPE__FUSION_MATTER:
|
||||
Missions::updateFusionMatter(sock, setData->iSetValue - plr->fusionmatter);
|
||||
plr->setCapped(CappedValueType::FUSIONMATTER, setData->iSetValue);
|
||||
Missions::updateFusionMatter(sock);
|
||||
response.iSetValue = plr->fusionmatter;
|
||||
break;
|
||||
case CN_GM_SET_VALUE_TYPE__CANDY:
|
||||
response.iSetValue = plr->money = setData->iSetValue;
|
||||
plr->setCapped(CappedValueType::TAROS, setData->iSetValue);
|
||||
response.iSetValue = plr->money;
|
||||
break;
|
||||
case CN_GM_SET_VALUE_TYPE__SPEED:
|
||||
case CN_GM_SET_VALUE_TYPE__JUMP:
|
||||
@@ -148,6 +140,60 @@ static void setGMSpecialOnOff(CNSocket *sock, CNPacketData *data) {
|
||||
// this is only used for muting players, so no need to update the client since that logic is server-side
|
||||
}
|
||||
|
||||
static void setGMRewardRate(CNSocket *sock, CNPacketData *data) {
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
// access check
|
||||
if (plr->accountLevel > 30)
|
||||
return;
|
||||
|
||||
auto req = (sP_CL2FE_GM_REQ_REWARD_RATE*)data->buf;
|
||||
|
||||
if (req->iGetSet != 0) {
|
||||
double *rate = nullptr;
|
||||
|
||||
switch (req->iRewardType) {
|
||||
case REWARD_TYPE_TAROS:
|
||||
rate = plr->rateT;
|
||||
break;
|
||||
case REWARD_TYPE_FUSIONMATTER:
|
||||
rate = plr->rateF;
|
||||
break;
|
||||
}
|
||||
|
||||
if (rate == nullptr) {
|
||||
std::cout << "[WARN] Invalid reward type for setGMRewardRate(): " << req->iRewardType << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
if (req->iSetRateValue < 0 || req->iSetRateValue > 1000) {
|
||||
std::cout << "[WARN] Invalid rate value for setGMRewardRate(): " << req->iSetRateValue << " (must be between 0 and 1000)" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (req->iRewardRateIndex) {
|
||||
case RATE_SLOT_ALL:
|
||||
for (int i = 0; i < 5; i++)
|
||||
rate[i] = req->iSetRateValue / 100.0;
|
||||
break;
|
||||
case RATE_SLOT_COMBAT:
|
||||
case RATE_SLOT_MISSION:
|
||||
case RATE_SLOT_EGG:
|
||||
case RATE_SLOT_RACING:
|
||||
rate[req->iRewardRateIndex] = req->iSetRateValue / 100.0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
INITSTRUCT(sP_FE2CL_GM_REP_REWARD_RATE_SUCC, resp);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
// double to float
|
||||
resp.afRewardRate_Taros[i] = plr->rateT[i];
|
||||
resp.afRewardRate_FusionMatter[i] = plr->rateF[i];
|
||||
}
|
||||
sock->sendPacket(resp, P_FE2CL_GM_REP_REWARD_RATE_SUCC);
|
||||
}
|
||||
|
||||
static void locatePlayer(CNSocket *sock, CNPacketData *data) {
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
@@ -371,6 +417,7 @@ void BuiltinCommands::init() {
|
||||
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_GM_REQ_PC_SPECIAL_STATE_SWITCH, setGMSpecialSwitchPlayer);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_GM_REQ_TARGET_PC_SPECIAL_STATE_ONOFF, setGMSpecialOnOff);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_GM_REQ_REWARD_RATE, setGMRewardRate);
|
||||
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_GM_REQ_PC_LOCATION, locatePlayer);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_GM_REQ_KICK_PLAYER, kickPlayer);
|
||||
|
||||
@@ -300,7 +300,7 @@ EntityRef CombatNPC::getRef() {
|
||||
}
|
||||
|
||||
void CombatNPC::step(time_t currTime) {
|
||||
|
||||
|
||||
if(stateHandlers.find(state) != stateHandlers.end())
|
||||
stateHandlers[state](this, currTime);
|
||||
else {
|
||||
@@ -441,11 +441,7 @@ static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) {
|
||||
damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, (plr->batteryW > 6 + difficulty),
|
||||
Nanos::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty);
|
||||
|
||||
if (plr->batteryW >= 6 + difficulty)
|
||||
plr->batteryW -= 6 + difficulty;
|
||||
else
|
||||
plr->batteryW = 0;
|
||||
|
||||
plr->subtractCapped(CappedValueType::BATTERY_W, 6 + difficulty);
|
||||
damage.first = mob->takeDamage(sock, damage.first);
|
||||
|
||||
respdata[i].iID = mob->id;
|
||||
@@ -690,10 +686,7 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) {
|
||||
Nanos::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty);
|
||||
}
|
||||
|
||||
if (plr->batteryW >= 6 + plr->level)
|
||||
plr->batteryW -= 6 + plr->level;
|
||||
else
|
||||
plr->batteryW = 0;
|
||||
plr->subtractCapped(CappedValueType::BATTERY_W, 6 + plr->level);
|
||||
|
||||
damage.first = target->takeDamage(sock, damage.first);
|
||||
|
||||
@@ -742,7 +735,7 @@ static int8_t addBullet(Player* plr, bool isGrenade) {
|
||||
toAdd.weaponBoost = plr->batteryW > 0;
|
||||
if (toAdd.weaponBoost) {
|
||||
int boostCost = Rand::rand(11) + 20;
|
||||
plr->batteryW = boostCost > plr->batteryW ? 0 : plr->batteryW - boostCost;
|
||||
plr->subtractCapped(CappedValueType::BATTERY_W, boostCost);
|
||||
}
|
||||
|
||||
Bullets[plr->iID][findId] = toAdd;
|
||||
|
||||
@@ -75,7 +75,7 @@ static void emailReceiveTaros(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
Database::EmailData email = Database::getEmail(plr->iID, pkt->iEmailIndex);
|
||||
// money transfer
|
||||
plr->money += email.Taros;
|
||||
plr->addCapped(CappedValueType::TAROS, email.Taros);
|
||||
email.Taros = 0;
|
||||
// update Taros in email
|
||||
Database::updateEmailContent(&email);
|
||||
@@ -274,7 +274,7 @@ static void emailSend(CNSocket* sock, CNPacketData* data) {
|
||||
}
|
||||
|
||||
int cost = pkt->iCash + 50 + 20 * attachments.size(); // attached taros + postage
|
||||
plr->money -= cost;
|
||||
plr->subtractCapped(CappedValueType::TAROS, cost);
|
||||
Database::EmailData email = {
|
||||
(int)pkt->iTo_PCUID, // PlayerId
|
||||
Database::getNextEmailIndex(pkt->iTo_PCUID), // MsgIndex
|
||||
@@ -291,7 +291,7 @@ static void emailSend(CNSocket* sock, CNPacketData* data) {
|
||||
};
|
||||
|
||||
if (!Database::sendEmail(&email, attachments, plr)) {
|
||||
plr->money += cost; // give money back
|
||||
plr->addCapped(CappedValueType::TAROS, cost); // give money back
|
||||
// give items back
|
||||
while (!attachments.empty()) {
|
||||
sItemBase attachment = attachments.back();
|
||||
|
||||
115
src/Entities.cpp
115
src/Entities.cpp
@@ -117,6 +117,121 @@ sPCAppearanceData Player::getAppearanceData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
bool Player::hasQuestBoost() const {
|
||||
if (AEQUIP_COUNT < AEQUIP_COUNT_WITH_BOOSTERS)
|
||||
return false;
|
||||
|
||||
const sItemBase& booster = Equip[10];
|
||||
return booster.iID == 153 && booster.iOpt > 0;
|
||||
}
|
||||
|
||||
bool Player::hasHunterBoost() const {
|
||||
if (AEQUIP_COUNT < AEQUIP_COUNT_WITH_BOOSTERS)
|
||||
return false;
|
||||
|
||||
const sItemBase& booster = Equip[11];
|
||||
return booster.iID == 154 && booster.iOpt > 0;
|
||||
}
|
||||
|
||||
bool Player::hasRacerBoost() const {
|
||||
if (AEQUIP_COUNT < AEQUIP_COUNT_WITH_BOOSTERS)
|
||||
return false;
|
||||
|
||||
const sItemBase& booster = Equip[9];
|
||||
return booster.iID == 155 && booster.iOpt > 0;
|
||||
}
|
||||
|
||||
bool Player::hasSuperBoost() const {
|
||||
return Player::hasQuestBoost() && Player::hasHunterBoost() && Player::hasRacerBoost();
|
||||
}
|
||||
|
||||
static int32_t getCap(CappedValueType type) {
|
||||
switch (type) {
|
||||
case CappedValueType::TAROS:
|
||||
return PC_CANDY_MAX;
|
||||
case CappedValueType::FUSIONMATTER:
|
||||
return PC_FUSIONMATTER_MAX;
|
||||
case CappedValueType::BATTERY_W:
|
||||
return PC_BATTERY_MAX;
|
||||
case CappedValueType::BATTERY_N:
|
||||
return PC_BATTERY_MAX;
|
||||
case CappedValueType::TAROS_IN_TRADE:
|
||||
return PC_CANDY_MAX;
|
||||
default:
|
||||
return INT32_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t *getCappedValue(Player *player, CappedValueType type) {
|
||||
switch (type) {
|
||||
case CappedValueType::TAROS:
|
||||
return &player->money;
|
||||
case CappedValueType::FUSIONMATTER:
|
||||
return &player->fusionmatter;
|
||||
case CappedValueType::BATTERY_W:
|
||||
return &player->batteryW;
|
||||
case CappedValueType::BATTERY_N:
|
||||
return &player->batteryN;
|
||||
case CappedValueType::TAROS_IN_TRADE:
|
||||
return &player->moneyInTrade;
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Player::addCapped(CappedValueType type, int32_t diff) {
|
||||
if (diff <= 0)
|
||||
return;
|
||||
|
||||
int32_t max = getCap(type);
|
||||
int32_t *value = getCappedValue(this, type);
|
||||
|
||||
if (value == nullptr)
|
||||
return;
|
||||
|
||||
if (diff > max)
|
||||
diff = max;
|
||||
|
||||
if (*value + diff > max)
|
||||
*value = max;
|
||||
else
|
||||
*value += diff;
|
||||
}
|
||||
|
||||
void Player::subtractCapped(CappedValueType type, int32_t diff) {
|
||||
if (diff <= 0)
|
||||
return;
|
||||
|
||||
int32_t max = getCap(type);
|
||||
int32_t *value = getCappedValue(this, type);
|
||||
|
||||
if (value == nullptr)
|
||||
return;
|
||||
|
||||
if (diff > max)
|
||||
diff = max;
|
||||
|
||||
if (*value - diff < 0)
|
||||
*value = 0;
|
||||
else
|
||||
*value -= diff;
|
||||
}
|
||||
|
||||
void Player::setCapped(CappedValueType type, int32_t value) {
|
||||
int32_t max = getCap(type);
|
||||
int32_t *valToSet = getCappedValue(this, type);
|
||||
|
||||
if (valToSet == nullptr)
|
||||
return;
|
||||
|
||||
if (value < 0)
|
||||
value = 0;
|
||||
else if (value > max)
|
||||
value = max;
|
||||
|
||||
*valToSet = value;
|
||||
}
|
||||
|
||||
// TODO: this is less effiecient than it was, because of memset()
|
||||
void Player::enterIntoViewOf(CNSocket *sock) {
|
||||
INITSTRUCT(sP_FE2CL_PC_NEW, pkt);
|
||||
|
||||
381
src/Items.cpp
381
src/Items.cpp
@@ -33,6 +33,19 @@ std::map<int32_t, int32_t> Items::EventToDropMap;
|
||||
std::map<int32_t, int32_t> Items::MobToDropMap;
|
||||
std::map<int32_t, ItemSet> Items::ItemSets;
|
||||
|
||||
// 1 week
|
||||
#define NANOCOM_BOOSTER_DURATION 604800
|
||||
|
||||
// known general item ids
|
||||
#define GENERALITEM_GUMBALL_ADAPTIUM 119
|
||||
#define GENERALITEM_GUMBALL_BLASTONS 120
|
||||
#define GENERALITEM_GUMBALL_COSMIX 121
|
||||
|
||||
#define GENERALITEM_FUSION_HUNTER_BOOSTER 153
|
||||
#define GENERALITEM_IZ_RACER_BOOSTER 154
|
||||
#define GENERALITEM_QUESTER_BOOSTER 155
|
||||
#define GENERALITEM_SUPER_BOOSTER_DX 156
|
||||
|
||||
#ifdef ACADEMY
|
||||
std::map<int32_t, int32_t> Items::NanoCapsules; // crate id -> nano id
|
||||
|
||||
@@ -322,14 +335,14 @@ static void itemMoveHandler(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
// if equipping an item, validate that it's of the correct type for the slot
|
||||
if ((SlotType)itemmove->eTo == SlotType::EQUIP) {
|
||||
if (fromItem->iType == 10 && itemmove->iToSlotNum != 8)
|
||||
if (fromItem->iType == 10 && itemmove->iToSlotNum != EQUIP_SLOT_VEHICLE)
|
||||
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
|
||||
else if (itemmove->iToSlotNum >= AEQUIP_COUNT_MINUS_BOOSTERS)
|
||||
return; // boosters can't be equipped via move packet
|
||||
}
|
||||
|
||||
// save items to response
|
||||
@@ -386,7 +399,7 @@ static void itemMoveHandler(CNSocket* sock, CNPacketData* data) {
|
||||
}
|
||||
|
||||
// unequip vehicle if equip slot 8 is 0
|
||||
if (plr->Equip[8].iID == 0 && plr->iPCState & 8) {
|
||||
if (plr->Equip[EQUIP_SLOT_VEHICLE].iID == 0 && plr->iPCState & 8) {
|
||||
INITSTRUCT(sP_FE2CL_PC_VEHICLE_OFF_SUCC, response);
|
||||
sock->sendPacket(response, P_FE2CL_PC_VEHICLE_OFF_SUCC);
|
||||
|
||||
@@ -430,30 +443,19 @@ static void itemDeleteHandler(CNSocket* sock, CNPacketData* data) {
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_ITEM_DELETE_SUCC);
|
||||
}
|
||||
|
||||
static void itemUseHandler(CNSocket* sock, CNPacketData* data) {
|
||||
static void useGumball(CNSocket* sock, CNPacketData* data) {
|
||||
auto request = (sP_CL2FE_REQ_ITEM_USE*)data->buf;
|
||||
Player* player = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (request->iSlotNum < 0 || request->iSlotNum >= AINVEN_COUNT)
|
||||
return; // sanity check
|
||||
|
||||
// gumball can only be used from inventory, so we ignore eIL
|
||||
sItemBase gumball = player->Inven[request->iSlotNum];
|
||||
sNano nano = player->Nanos[player->equippedNanos[request->iNanoSlot]];
|
||||
|
||||
// sanity check, check if gumball exists
|
||||
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);
|
||||
sock->sendPacket(response, P_FE2CL_REP_PC_ITEM_USE_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
// sanity check, check if gumball type matches nano style
|
||||
int nanoStyle = Nanos::nanoStyle(nano.iID);
|
||||
if (!((gumball.iID == 119 && nanoStyle == 0) ||
|
||||
( gumball.iID == 120 && nanoStyle == 1) ||
|
||||
( gumball.iID == 121 && nanoStyle == 2))) {
|
||||
if (!((gumball.iID == GENERALITEM_GUMBALL_ADAPTIUM && nanoStyle == 0) ||
|
||||
( gumball.iID == GENERALITEM_GUMBALL_BLASTONS && nanoStyle == 1) ||
|
||||
( gumball.iID == GENERALITEM_GUMBALL_COSMIX && nanoStyle == 2))) {
|
||||
std::cout << "[WARN] Gumball type doesn't match nano type" << std::endl;
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_USE_FAIL, response);
|
||||
sock->sendPacket(response, P_FE2CL_REP_PC_ITEM_USE_FAIL);
|
||||
@@ -472,11 +474,8 @@ static void itemUseHandler(CNSocket* sock, CNPacketData* data) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (gumball.iOpt == 0)
|
||||
gumball = {};
|
||||
|
||||
uint8_t respbuf[CN_PACKET_BODY_SIZE];
|
||||
memset(respbuf, 0, CN_PACKET_BODY_SIZE);
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
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));
|
||||
@@ -515,6 +514,128 @@ static void itemUseHandler(CNSocket* sock, CNPacketData* data) {
|
||||
player->Inven[resp->iSlotNum] = resp->RemainItem;
|
||||
}
|
||||
|
||||
static void useNanocomBooster(CNSocket* sock, CNPacketData* data) {
|
||||
// Guard against using nanocom boosters in before and including 0104
|
||||
// either path should be optimized by the compiler, effectively a no-op
|
||||
if (AEQUIP_COUNT < AEQUIP_COUNT_WITH_BOOSTERS) {
|
||||
std::cout << "[WARN] Nanocom Booster use not supported in this version" << std::endl;
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_USE_FAIL, respFail);
|
||||
sock->sendPacket(respFail, P_FE2CL_REP_PC_ITEM_USE_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
auto request = (sP_CL2FE_REQ_ITEM_USE*)data->buf;
|
||||
Player* player = PlayerManager::getPlayer(sock);
|
||||
sItemBase item = player->Inven[request->iSlotNum];
|
||||
|
||||
// decide on the booster to activate
|
||||
std::vector<int16_t> boosterIDs;
|
||||
switch(item.iID) {
|
||||
case GENERALITEM_FUSION_HUNTER_BOOSTER:
|
||||
case GENERALITEM_IZ_RACER_BOOSTER:
|
||||
case GENERALITEM_QUESTER_BOOSTER:
|
||||
boosterIDs.push_back(item.iID);
|
||||
break;
|
||||
case GENERALITEM_SUPER_BOOSTER_DX:
|
||||
boosterIDs.push_back(GENERALITEM_FUSION_HUNTER_BOOSTER);
|
||||
boosterIDs.push_back(GENERALITEM_IZ_RACER_BOOSTER);
|
||||
boosterIDs.push_back(GENERALITEM_QUESTER_BOOSTER);
|
||||
break;
|
||||
}
|
||||
|
||||
// consume item
|
||||
item.iOpt -= 1;
|
||||
if (item.iOpt == 0)
|
||||
item = {};
|
||||
|
||||
// client wants to subtract server time in seconds from the time limit for display purposes
|
||||
int32_t timeLimitDisplayed = (getTime() / 1000UL) + NANOCOM_BOOSTER_DURATION;
|
||||
// in actuality we will use the timestamp of booster activation to the item time limit similar to vehicles
|
||||
// and this is how it will be saved to the database
|
||||
int32_t timeLimit = getTimestamp() + NANOCOM_BOOSTER_DURATION;
|
||||
|
||||
// give item(s) to inv slots
|
||||
for (int16_t itemID : boosterIDs) {
|
||||
sItemBase boosterItem = { 7, itemID, 1, timeLimitDisplayed };
|
||||
|
||||
// quester 155 -> 9, hunter 153 -> 10, racer 154 -> 11
|
||||
int slot = 9 + ((itemID - GENERALITEM_FUSION_HUNTER_BOOSTER + 1) % 3);
|
||||
|
||||
// give item to the equip slot
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_GIVE_ITEM_SUCC, resp);
|
||||
resp.eIL = (int)SlotType::EQUIP;
|
||||
resp.iSlotNum = slot;
|
||||
resp.Item = boosterItem;
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_GIVE_ITEM_SUCC);
|
||||
|
||||
// inform client of equip change (non visible so it's okay to just send to the player)
|
||||
INITSTRUCT(sP_FE2CL_PC_EQUIP_CHANGE, equipChange);
|
||||
equipChange.iPC_ID = player->iID;
|
||||
equipChange.iEquipSlotNum = slot;
|
||||
equipChange.EquipSlotItem = boosterItem;
|
||||
sock->sendPacket(equipChange, P_FE2CL_PC_EQUIP_CHANGE);
|
||||
|
||||
boosterItem.iTimeLimit = timeLimit;
|
||||
// should replace existing booster in slot if it exists, i.e. you can refresh your boosters
|
||||
player->Equip[slot] = boosterItem;
|
||||
}
|
||||
|
||||
// send item use success packet
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_USE_SUCC, respUse);
|
||||
respUse.iPC_ID = player->iID;
|
||||
respUse.eIL = (int)SlotType::INVENTORY;
|
||||
respUse.iSlotNum = request->iSlotNum;
|
||||
respUse.RemainItem = item;
|
||||
sock->sendPacket(respUse, P_FE2CL_REP_PC_ITEM_USE_SUCC);
|
||||
|
||||
// update inventory serverside
|
||||
player->Inven[request->iSlotNum] = item;
|
||||
}
|
||||
|
||||
static void itemUseHandler(CNSocket* sock, CNPacketData* data) {
|
||||
auto request = (sP_CL2FE_REQ_ITEM_USE*)data->buf;
|
||||
Player* player = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (request->iSlotNum < 0 || request->iSlotNum >= AINVEN_COUNT)
|
||||
return; // sanity check
|
||||
|
||||
sItemBase item = player->Inven[request->iSlotNum];
|
||||
|
||||
// sanity check, check the item exists and has correct iType
|
||||
if (!(item.iOpt > 0 && item.iType == 7)) {
|
||||
std::cout << "[WARN] General item not found" << std::endl;
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_USE_FAIL, response);
|
||||
sock->sendPacket(response, P_FE2CL_REP_PC_ITEM_USE_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: In the XDT, there are subtypes for general-use items
|
||||
* (m_pGeneralItemTable -> m_pItemData-> m_iItemType) that
|
||||
* determine their behavior. It would be better to load these
|
||||
* and use them in this switch, rather than hardcoding by IDs.
|
||||
*/
|
||||
|
||||
switch(item.iID) {
|
||||
case GENERALITEM_GUMBALL_ADAPTIUM:
|
||||
case GENERALITEM_GUMBALL_BLASTONS:
|
||||
case GENERALITEM_GUMBALL_COSMIX:
|
||||
useGumball(sock, data);
|
||||
break;
|
||||
case GENERALITEM_FUSION_HUNTER_BOOSTER:
|
||||
case GENERALITEM_IZ_RACER_BOOSTER:
|
||||
case GENERALITEM_QUESTER_BOOSTER:
|
||||
case GENERALITEM_SUPER_BOOSTER_DX:
|
||||
useNanocomBooster(sock, data);
|
||||
break;
|
||||
default:
|
||||
std::cout << "[INFO] General item "<< item.iID << " is unimplemented." << std::endl;
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_USE_FAIL, response);
|
||||
sock->sendPacket(response, P_FE2CL_REP_PC_ITEM_USE_FAIL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void itemBankOpenHandler(CNSocket* sock, CNPacketData* data) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
@@ -598,7 +719,7 @@ static void chestOpenHandler(CNSocket *sock, CNPacketData *data) {
|
||||
// if we failed to open a crate, at least give the player a gumball (suggested by Jade)
|
||||
if (failing) {
|
||||
item->sItem.iType = 7;
|
||||
item->sItem.iID = 119 + Rand::rand(3);
|
||||
item->sItem.iID = GENERALITEM_GUMBALL_ADAPTIUM + Rand::rand(3);
|
||||
item->sItem.iOpt = 1;
|
||||
|
||||
std::cout << "[WARN] Crate open failed, giving a Gumball..." << std::endl;
|
||||
@@ -632,39 +753,89 @@ Item* Items::getItemData(int32_t id, int32_t type) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Items::checkItemExpire(CNSocket* sock, Player* player) {
|
||||
if (player->toRemoveVehicle.eIL == 0 && player->toRemoveVehicle.iSlotNum == 0)
|
||||
return;
|
||||
size_t Items::checkAndRemoveExpiredItems(CNSocket* sock, Player* player) {
|
||||
int32_t currentTime = getTimestamp();
|
||||
|
||||
/* 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
|
||||
*/
|
||||
// if there are expired items in bank just remove them silently
|
||||
if (settings::REMOVEEXPIREDITEMSFROMBANK) {
|
||||
for (int i = 0; i < ABANK_COUNT; i++) {
|
||||
if (player->Bank[i].iTimeLimit < currentTime && player->Bank[i].iTimeLimit != 0) {
|
||||
memset(&player->Bank[i], 0, sizeof(sItemBase));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const size_t resplen = sizeof(sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM) + sizeof(sTimeLimitItemDeleteInfo2CL);
|
||||
// collect items to remove and data for the packet
|
||||
std::vector<sItemBase*> toRemove;
|
||||
std::vector<sTimeLimitItemDeleteInfo2CL> itemData;
|
||||
|
||||
// equipped items
|
||||
for (int i = 0; i < AEQUIP_COUNT; i++) {
|
||||
if (player->Equip[i].iOpt > 0 && player->Equip[i].iTimeLimit < currentTime && player->Equip[i].iTimeLimit != 0) {
|
||||
toRemove.push_back(&player->Equip[i]);
|
||||
itemData.push_back({ (int)SlotType::EQUIP, i });
|
||||
}
|
||||
}
|
||||
// inventory
|
||||
for (int i = 0; i < AINVEN_COUNT; i++) {
|
||||
if (player->Inven[i].iTimeLimit < currentTime && player->Inven[i].iTimeLimit != 0) {
|
||||
toRemove.push_back(&player->Inven[i]);
|
||||
itemData.push_back({ (int)SlotType::INVENTORY, i });
|
||||
}
|
||||
}
|
||||
|
||||
if (itemData.empty())
|
||||
return 0;
|
||||
|
||||
// prepare packet containing all expired items to delete
|
||||
// this is expected for academy
|
||||
// pre-academy only checks the first item in the packet
|
||||
const size_t resplen = sizeof(sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM) + sizeof(sTimeLimitItemDeleteInfo2CL) * itemData.size();
|
||||
|
||||
// 8 bytes * 262 items = 2096 bytes, in total this shouldn't exceed 2500 bytes
|
||||
assert(resplen < CN_PACKET_BODY_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
|
||||
auto packet = (sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM*)respbuf;
|
||||
sTimeLimitItemDeleteInfo2CL* itemData = (sTimeLimitItemDeleteInfo2CL*)(respbuf + sizeof(sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM));
|
||||
memset(respbuf, 0, resplen);
|
||||
uint8_t respbuf[CN_PACKET_BODY_SIZE];
|
||||
memset(respbuf, 0, CN_PACKET_BODY_SIZE);
|
||||
|
||||
auto packet = (sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM*)respbuf;
|
||||
|
||||
for (size_t i = 0; i < itemData.size(); i++) {
|
||||
auto itemToDeletePtr = (sTimeLimitItemDeleteInfo2CL*)(
|
||||
respbuf + sizeof(sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM) + sizeof(sTimeLimitItemDeleteInfo2CL) * i
|
||||
);
|
||||
itemToDeletePtr->eIL = itemData[i].eIL;
|
||||
itemToDeletePtr->iSlotNum = itemData[i].iSlotNum;
|
||||
packet->iItemListCount++;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// delete serverside
|
||||
if (player->toRemoveVehicle.eIL == 0)
|
||||
memset(&player->Equip[8], 0, sizeof(sItemBase));
|
||||
else
|
||||
memset(&player->Inven[player->toRemoveVehicle.iSlotNum], 0, sizeof(sItemBase));
|
||||
// delete items serverside and send unequip packets
|
||||
for (size_t i = 0; i < itemData.size(); i++) {
|
||||
sItemBase* item = toRemove[i];
|
||||
memset(item, 0, sizeof(sItemBase));
|
||||
|
||||
player->toRemoveVehicle.eIL = 0;
|
||||
player->toRemoveVehicle.iSlotNum = 0;
|
||||
// send item delete success packet
|
||||
// required for pre-academy builds
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_DELETE_SUCC, itemDelete);
|
||||
itemDelete.eIL = itemData[i].eIL;
|
||||
itemDelete.iSlotNum = itemData[i].iSlotNum;
|
||||
sock->sendPacket(itemDelete, P_FE2CL_REP_PC_ITEM_DELETE_SUCC);
|
||||
|
||||
// also update item equips if needed
|
||||
if (itemData[i].eIL == (int)SlotType::EQUIP) {
|
||||
INITSTRUCT(sP_FE2CL_PC_EQUIP_CHANGE, equipChange);
|
||||
equipChange.iPC_ID = player->iID;
|
||||
equipChange.iEquipSlotNum = itemData[i].iSlotNum;
|
||||
sock->sendPacket(equipChange, P_FE2CL_PC_EQUIP_CHANGE);
|
||||
}
|
||||
}
|
||||
|
||||
// exit vehicle if player no longer has one equipped (function checks pcstyle)
|
||||
if (player->Equip[EQUIP_SLOT_VEHICLE].iID == 0)
|
||||
PlayerManager::exitPlayerVehicle(sock, nullptr);
|
||||
|
||||
return itemData.size();
|
||||
}
|
||||
|
||||
void Items::setItemStats(Player* plr) {
|
||||
@@ -711,7 +882,69 @@ static void getMobDrop(sItemBase* reward, const std::vector<int>& weights, const
|
||||
reward->iID = crateIds[chosenIndex];
|
||||
}
|
||||
|
||||
static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRoll& rolled) {
|
||||
static int32_t calculateTaroReward(Player* plr, int baseAmount, int groupSize) {
|
||||
double bonus = plr->hasBuff(ECSB_REWARD_CASH) ? (Nanos::getNanoBoost(plr) ? 1.23 : 1.2) : 1.0;
|
||||
double groupEffect = settings::LESSTAROFMINGROUPDISABLED ? 1.0 : 1.0 / groupSize;
|
||||
return baseAmount * plr->rateT[RATE_SLOT_COMBAT] * bonus * groupEffect;
|
||||
}
|
||||
|
||||
static int32_t calculateFMReward(Player* plr, int baseAmount, int levelDiff, int groupSize) {
|
||||
double scavenge = plr->hasBuff(ECSB_REWARD_BLOB) ? (Nanos::getNanoBoost(plr) ? 1.23 : 1.2) : 1.0;
|
||||
double boosterEffect = plr->hasHunterBoost() ? (plr->hasQuestBoost() && plr->hasRacerBoost() ? 1.75 : 1.5) : 1.0;
|
||||
|
||||
// if player is within 1 level of the mob, FM is untouched
|
||||
double levelEffect = 1.0;
|
||||
// otherwise, follow the table below
|
||||
switch (std::clamp(levelDiff, -3, 6)) {
|
||||
case 6:
|
||||
// if player is 6 or more levels above mob, no FM is dropped
|
||||
levelEffect = 0.0;
|
||||
break;
|
||||
case 5:
|
||||
levelEffect = 0.25;
|
||||
break;
|
||||
case 4:
|
||||
levelEffect = 0.5;
|
||||
break;
|
||||
case 3:
|
||||
levelEffect = 0.75;
|
||||
break;
|
||||
case 2:
|
||||
levelEffect = 0.899;
|
||||
break;
|
||||
case -2:
|
||||
levelEffect = 1.1;
|
||||
break;
|
||||
case -3:
|
||||
// if player is 3 or more levels below mob, FM is 1.2x
|
||||
levelEffect = 1.2;
|
||||
break;
|
||||
}
|
||||
|
||||
// if no group, FM is untouched
|
||||
double groupEffect = 1.0;
|
||||
// otherwise, follow the table below
|
||||
if (!settings::LESSTAROFMINGROUPDISABLED) {
|
||||
switch (groupSize) {
|
||||
case 2:
|
||||
groupEffect = 0.875;
|
||||
break;
|
||||
case 3:
|
||||
groupEffect = 0.75;
|
||||
break;
|
||||
case 4:
|
||||
// this case is more lenient
|
||||
groupEffect = 0.688;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t amount = baseAmount * plr->rateF[RATE_SLOT_COMBAT] * scavenge * levelEffect * groupEffect;
|
||||
amount *= boosterEffect;
|
||||
return amount;
|
||||
}
|
||||
|
||||
static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRoll& rolled, int groupSize) {
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward);
|
||||
@@ -763,43 +996,20 @@ static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRo
|
||||
MiscDropType& miscDropType = Items::MiscDropTypes[drop.miscDropTypeId];
|
||||
|
||||
if (rolled.taros % miscDropChance.taroDropChanceTotal < miscDropChance.taroDropChance) {
|
||||
plr->money += miscDropType.taroAmount;
|
||||
// money nano boost
|
||||
if (plr->hasBuff(ECSB_REWARD_CASH)) {
|
||||
int boost = 0;
|
||||
if (Nanos::getNanoBoost(plr)) // for gumballs
|
||||
boost = 1;
|
||||
plr->money += miscDropType.taroAmount * (5 + boost) / 25;
|
||||
}
|
||||
int32_t taros = calculateTaroReward(plr, miscDropType.taroAmount, groupSize);
|
||||
plr->addCapped(CappedValueType::TAROS, taros);
|
||||
}
|
||||
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
|
||||
if (plr->hasBuff(ECSB_REWARD_BLOB)) {
|
||||
int boost = 0;
|
||||
if (Nanos::getNanoBoost(plr)) // for gumballs
|
||||
boost = 1;
|
||||
fm += fm * (5 + boost) / 25;
|
||||
}
|
||||
|
||||
Missions::updateFusionMatter(sock, fm);
|
||||
int32_t fm = calculateFMReward(plr, miscDropType.fmAmount, levelDifference, groupSize);
|
||||
plr->addCapped(CappedValueType::FUSIONMATTER, fm);
|
||||
Missions::updateFusionMatter(sock);
|
||||
}
|
||||
|
||||
if (rolled.potions % miscDropChance.potionDropChanceTotal < miscDropChance.potionDropChance)
|
||||
plr->batteryN += miscDropType.potionAmount;
|
||||
plr->addCapped(CappedValueType::BATTERY_N, miscDropType.potionAmount);
|
||||
if (rolled.boosts % miscDropChance.boostDropChanceTotal < miscDropChance.boostDropChance)
|
||||
plr->batteryW += miscDropType.boostAmount;
|
||||
|
||||
// caps
|
||||
if (plr->batteryW > 9999)
|
||||
plr->batteryW = 9999;
|
||||
if (plr->batteryN > 9999)
|
||||
plr->batteryN = 9999;
|
||||
plr->addCapped(CappedValueType::BATTERY_W, miscDropType.boostAmount);
|
||||
|
||||
// simple rewards
|
||||
reward->m_iCandy = plr->money;
|
||||
@@ -830,7 +1040,7 @@ static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRo
|
||||
}
|
||||
}
|
||||
|
||||
void Items::giveMobDrop(CNSocket *sock, Mob* mob, const DropRoll& rolled, const DropRoll& eventRolled) {
|
||||
void Items::giveMobDrop(CNSocket *sock, Mob* mob, const DropRoll& rolled, const DropRoll& eventRolled, int groupSize) {
|
||||
// sanity check
|
||||
if (Items::MobToDropMap.find(mob->type) == Items::MobToDropMap.end()) {
|
||||
std::cout << "[WARN] Mob ID " << mob->type << " has no drops assigned" << std::endl;
|
||||
@@ -839,7 +1049,7 @@ void Items::giveMobDrop(CNSocket *sock, Mob* mob, const DropRoll& rolled, const
|
||||
// find mob drop id
|
||||
int mobDropId = Items::MobToDropMap[mob->type];
|
||||
|
||||
giveSingleDrop(sock, mob, mobDropId, rolled);
|
||||
giveSingleDrop(sock, mob, mobDropId, rolled, groupSize);
|
||||
|
||||
if (settings::EVENTMODE != 0) {
|
||||
// sanity check
|
||||
@@ -850,14 +1060,13 @@ void Items::giveMobDrop(CNSocket *sock, Mob* mob, const DropRoll& rolled, const
|
||||
// find mob drop id
|
||||
int eventMobDropId = Items::EventToDropMap[settings::EVENTMODE];
|
||||
|
||||
giveSingleDrop(sock, mob, eventMobDropId, eventRolled);
|
||||
giveSingleDrop(sock, mob, eventMobDropId, eventRolled, groupSize);
|
||||
}
|
||||
}
|
||||
|
||||
void Items::init() {
|
||||
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);
|
||||
|
||||
@@ -113,11 +113,11 @@ namespace Items {
|
||||
void init();
|
||||
|
||||
// mob drops
|
||||
void giveMobDrop(CNSocket *sock, Mob *mob, const DropRoll& rolled, const DropRoll& eventRolled);
|
||||
void giveMobDrop(CNSocket *sock, Mob *mob, const DropRoll& rolled, const DropRoll& eventRolled, int groupSize);
|
||||
|
||||
int findFreeSlot(Player *plr);
|
||||
Item* getItemData(int32_t id, int32_t type);
|
||||
void checkItemExpire(CNSocket* sock, Player* player);
|
||||
size_t checkAndRemoveExpiredItems(CNSocket* sock, Player* player);
|
||||
void setItemStats(Player* plr);
|
||||
void updateEquips(CNSocket* sock, Player* plr);
|
||||
|
||||
|
||||
@@ -12,6 +12,20 @@ std::map<int32_t, Reward*> Missions::Rewards;
|
||||
std::map<int32_t, TaskData*> Missions::Tasks;
|
||||
nlohmann::json Missions::AvatarGrowth[37];
|
||||
|
||||
static int32_t calculateTaroReward(Player* plr, int32_t baseAmount) {
|
||||
double bonus = plr->hasBuff(ECSB_REWARD_CASH) ? (Nanos::getNanoBoost(plr) ? 1.23 : 1.2) : 1.0;
|
||||
return baseAmount * plr->rateT[RATE_SLOT_MISSION] * bonus;
|
||||
}
|
||||
|
||||
static int32_t calculateFMReward(Player* plr, int32_t baseAmount) {
|
||||
double scavenge = plr->hasBuff(ECSB_REWARD_BLOB) ? (Nanos::getNanoBoost(plr) ? 1.23 : 1.2) : 1.0;
|
||||
double missionBoost = plr->hasQuestBoost() ? (plr->hasHunterBoost() && plr->hasRacerBoost() ? 1.75 : 1.5) : 1.0;
|
||||
|
||||
int32_t reward = baseAmount * plr->rateF[RATE_SLOT_MISSION] * scavenge;
|
||||
reward *= missionBoost;
|
||||
return reward;
|
||||
}
|
||||
|
||||
static void saveMission(Player* player, int missionId) {
|
||||
// sanity check missionID so we don't get exceptions
|
||||
if (missionId < 0 || missionId > 1023) {
|
||||
@@ -148,7 +162,7 @@ static int giveMissionReward(CNSocket *sock, int task, int choice=0) {
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
plr->Inven[slots[i]] = { 999, 999, 999, 0 }; // temp item; overwritten later
|
||||
}
|
||||
|
||||
@@ -162,21 +176,12 @@ static int giveMissionReward(CNSocket *sock, int task, int choice=0) {
|
||||
memset(respbuf, 0, CN_PACKET_BODY_SIZE);
|
||||
|
||||
// update player
|
||||
plr->money += reward->money;
|
||||
if (plr->hasBuff(ECSB_REWARD_CASH)) { // nano boost for taros
|
||||
int boost = 0;
|
||||
if (Nanos::getNanoBoost(plr)) // for gumballs
|
||||
boost = 1;
|
||||
plr->money += reward->money * (5 + boost) / 25;
|
||||
}
|
||||
int32_t money = calculateTaroReward(plr, reward->money);
|
||||
plr->addCapped(CappedValueType::TAROS, money);
|
||||
|
||||
if (plr->hasBuff(ECSB_REWARD_BLOB)) { // nano boost for fm
|
||||
int boost = 0;
|
||||
if (Nanos::getNanoBoost(plr)) // for gumballs
|
||||
boost = 1;
|
||||
updateFusionMatter(sock, reward->fusionmatter * (30 + boost) / 25);
|
||||
} else
|
||||
updateFusionMatter(sock, reward->fusionmatter);
|
||||
int32_t fusionMatter = calculateFMReward(plr, reward->fusionmatter);
|
||||
plr->addCapped(CappedValueType::FUSIONMATTER, fusionMatter);
|
||||
Missions::updateFusionMatter(sock);
|
||||
|
||||
// simple rewards
|
||||
resp->m_iCandy = plr->money;
|
||||
@@ -511,17 +516,13 @@ void Missions::quitTask(CNSocket* sock, int32_t taskNum, bool manual) {
|
||||
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_STOP_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_STOP_SUCC));
|
||||
}
|
||||
|
||||
void Missions::updateFusionMatter(CNSocket* sock, int fusion) {
|
||||
void Missions::updateFusionMatter(CNSocket* sock) {
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
plr->fusionmatter += fusion;
|
||||
|
||||
// there's a much lower FM cap in the Future
|
||||
int fmCap = AvatarGrowth[plr->level]["m_iFMLimit"];
|
||||
if (plr->fusionmatter > fmCap)
|
||||
plr->fusionmatter = fmCap;
|
||||
else if (plr->fusionmatter < 0) // if somehow lowered too far
|
||||
plr->fusionmatter = 0;
|
||||
|
||||
// don't run nano mission logic at level 36
|
||||
if (plr->level >= 36)
|
||||
@@ -551,7 +552,7 @@ void Missions::updateFusionMatter(CNSocket* sock, int fusion) {
|
||||
response.iTaskNum = AvatarGrowth[plr->level]["m_iNanoQuestTaskID"];
|
||||
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_START_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_START_SUCC));
|
||||
#else
|
||||
plr->fusionmatter -= (int)Missions::AvatarGrowth[plr->level]["m_iReqBlob_NanoCreate"];
|
||||
plr->subtractCapped(CappedValueType::FUSIONMATTER, (int)Missions::AvatarGrowth[plr->level]["m_iReqBlob_NanoCreate"]);
|
||||
plr->level++;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_CHANGE_LEVEL_SUCC, response);
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace Missions {
|
||||
bool startTask(Player* plr, int TaskID);
|
||||
|
||||
// checks if player doesn't have n/n quest items
|
||||
void updateFusionMatter(CNSocket* sock, int fusion);
|
||||
void updateFusionMatter(CNSocket* sock);
|
||||
|
||||
void mobKilled(CNSocket *sock, int mobid, std::map<int, int>& rolls);
|
||||
|
||||
|
||||
@@ -808,18 +808,17 @@ void MobAI::onDeath(CombatNPC* npc, EntityRef src) {
|
||||
Items::DropRoll rolled;
|
||||
Items::DropRoll eventRolled;
|
||||
std::map<int, int> qitemRolls;
|
||||
std::vector<EntityRef> playersInRange;
|
||||
std::vector<Player*> playerRefs;
|
||||
|
||||
if (plr->group == nullptr) {
|
||||
playerRefs.push_back(plr);
|
||||
Combat::genQItemRolls(playerRefs, qitemRolls);
|
||||
Items::giveMobDrop(src.sock, self, rolled, eventRolled);
|
||||
Items::giveMobDrop(src.sock, self, rolled, eventRolled, 1);
|
||||
Missions::mobKilled(src.sock, self->type, qitemRolls);
|
||||
}
|
||||
else {
|
||||
auto players = plr->group->filter(EntityKind::PLAYER);
|
||||
for (EntityRef pRef : players) playerRefs.push_back(PlayerManager::getPlayer(pRef.sock));
|
||||
Combat::genQItemRolls(playerRefs, qitemRolls);
|
||||
for (int i = 0; i < players.size(); i++) {
|
||||
CNSocket* sockTo = players[i].sock;
|
||||
Player* otherPlr = PlayerManager::getPlayer(sockTo);
|
||||
@@ -829,7 +828,14 @@ void MobAI::onDeath(CombatNPC* npc, EntityRef src) {
|
||||
if (dist > 5000)
|
||||
continue;
|
||||
|
||||
Items::giveMobDrop(sockTo, self, rolled, eventRolled);
|
||||
playersInRange.push_back(players[i]);
|
||||
}
|
||||
|
||||
for (EntityRef pRef : playersInRange) playerRefs.push_back(PlayerManager::getPlayer(pRef.sock));
|
||||
Combat::genQItemRolls(playerRefs, qitemRolls);
|
||||
for (int i = 0; i < playersInRange.size(); i++) {
|
||||
CNSocket* sockTo = playersInRange[i].sock;
|
||||
Items::giveMobDrop(sockTo, self, rolled, eventRolled, playersInRange.size());
|
||||
Missions::mobKilled(sockTo, self->type, qitemRolls);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,8 +33,10 @@ void Nanos::addNano(CNSocket* sock, int16_t nanoID, int16_t slot, bool spendfm)
|
||||
*/
|
||||
plr->level = level;
|
||||
|
||||
if (spendfm)
|
||||
Missions::updateFusionMatter(sock, -(int)Missions::AvatarGrowth[plr->level-1]["m_iReqBlob_NanoCreate"]);
|
||||
if (spendfm) {
|
||||
plr->subtractCapped(CappedValueType::FUSIONMATTER, (int)Missions::AvatarGrowth[plr->level-1]["m_iReqBlob_NanoCreate"]);
|
||||
Missions::updateFusionMatter(sock);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Send to client
|
||||
@@ -143,7 +145,7 @@ static void setNanoSkill(CNSocket* sock, sP_CL2FE_REQ_NANO_TUNE* skill) {
|
||||
return;
|
||||
#endif
|
||||
|
||||
plr->fusionmatter -= (int)Missions::AvatarGrowth[plr->level]["m_iReqBlob_NanoTune"];
|
||||
plr->subtractCapped(CappedValueType::FUSIONMATTER, (int)Missions::AvatarGrowth[plr->level]["m_iReqBlob_NanoTune"]);
|
||||
|
||||
int reqItemCount = NanoTunings[skill->iTuneID].reqItemCount;
|
||||
int reqItemID = NanoTunings[skill->iTuneID].reqItems;
|
||||
@@ -190,7 +192,7 @@ int Nanos::nanoStyle(int nanoID) {
|
||||
}
|
||||
|
||||
bool Nanos::getNanoBoost(Player* plr) {
|
||||
for (int i = 0; i < 3; i++)
|
||||
for (int i = 0; i < 3; i++)
|
||||
if (plr->equippedNanos[i] == plr->activeNano)
|
||||
if (plr->hasBuff(ECSB_STIMPAKSLOT1 + i))
|
||||
return true;
|
||||
@@ -355,7 +357,7 @@ static void nanoPotionHandler(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
sock->sendPacket(response, P_FE2CL_REP_CHARGE_NANO_STAMINA);
|
||||
// now update serverside
|
||||
player->batteryN -= difference;
|
||||
player->subtractCapped(CappedValueType::BATTERY_N, difference);
|
||||
player->Nanos[nano.iID].iStamina += difference;
|
||||
|
||||
}
|
||||
|
||||
@@ -15,6 +15,14 @@ struct BuffStack;
|
||||
|
||||
#define PC_MAXHEALTH(level) (925 + 75 * (level))
|
||||
|
||||
enum class CappedValueType {
|
||||
TAROS,
|
||||
FUSIONMATTER,
|
||||
BATTERY_W,
|
||||
BATTERY_N,
|
||||
TAROS_IN_TRADE,
|
||||
};
|
||||
|
||||
struct Player : public Entity, public ICombatant {
|
||||
int accountId = 0;
|
||||
int accountLevel = 0; // permission level (see CN_ACCOUNT_LEVEL enums)
|
||||
@@ -65,8 +73,6 @@ struct Player : public Entity, public ICombatant {
|
||||
sItemBase QInven[AQINVEN_COUNT] = {};
|
||||
int32_t CurrentMissionID = 0;
|
||||
|
||||
sTimeLimitItemDeleteInfo2CL toRemoveVehicle = {};
|
||||
|
||||
Group* group = nullptr;
|
||||
|
||||
bool notify = false;
|
||||
@@ -84,7 +90,14 @@ struct Player : public Entity, public ICombatant {
|
||||
time_t lastShot = 0;
|
||||
std::vector<sItemBase> buyback = {};
|
||||
|
||||
Player() { kind = EntityKind::PLAYER; }
|
||||
double rateF[5] = { 1.0, 1.0, 1.0, 1.0, 1.0 };
|
||||
double rateT[5] = { 1.0, 1.0, 1.0, 1.0, 1.0 };
|
||||
|
||||
Player() {
|
||||
kind = EntityKind::PLAYER;
|
||||
std::fill_n(rateF, 5, (double)settings::FUSIONMATTERRATE / 100.0);
|
||||
std::fill_n(rateT, 5, (double)settings::TARORATE / 100.0);
|
||||
}
|
||||
|
||||
virtual void enterIntoViewOf(CNSocket *sock) override;
|
||||
virtual void disappearFromViewOf(CNSocket *sock) override;
|
||||
@@ -111,4 +124,11 @@ struct Player : public Entity, public ICombatant {
|
||||
|
||||
sNano* getActiveNano();
|
||||
sPCAppearanceData getAppearanceData();
|
||||
bool hasQuestBoost() const;
|
||||
bool hasHunterBoost() const;
|
||||
bool hasRacerBoost() const;
|
||||
bool hasSuperBoost() const;
|
||||
void addCapped(CappedValueType type, int32_t diff);
|
||||
void subtractCapped(CappedValueType type, int32_t diff);
|
||||
void setCapped(CappedValueType type, int32_t value);
|
||||
};
|
||||
|
||||
@@ -243,9 +243,19 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
// client doesnt read this, it gets it from charinfo
|
||||
// response.PCLoadData2CL.PCStyle2 = plr->PCStyle2;
|
||||
// inventory
|
||||
for (int i = 0; i < AEQUIP_COUNT; i++)
|
||||
|
||||
// equipment (except nanocom boosters)
|
||||
for (int i = 0; i < AEQUIP_COUNT_MINUS_BOOSTERS; i++)
|
||||
response.PCLoadData2CL.aEquip[i] = plr->Equip[i];
|
||||
// equipment (nanocom boosters, loop only runs if boosters are available)
|
||||
int32_t serverTime = getTime() / 1000UL;
|
||||
int32_t timestamp = getTimestamp();
|
||||
for (int i = AEQUIP_COUNT_MINUS_BOOSTERS; i < AEQUIP_COUNT; i++) {
|
||||
response.PCLoadData2CL.aEquip[i] = plr->Equip[i];
|
||||
// client subtracts server time, then adds local timestamp to the item to print expiration time
|
||||
response.PCLoadData2CL.aEquip[i].iTimeLimit = std::max(0, plr->Equip[i].iTimeLimit - timestamp + serverTime);
|
||||
}
|
||||
// inventory
|
||||
for (int i = 0; i < AINVEN_COUNT; i++)
|
||||
response.PCLoadData2CL.aInven[i] = plr->Inven[i];
|
||||
// quest inventory
|
||||
@@ -384,7 +394,7 @@ static void loadPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
Chat::sendServerMessage(sock, settings::MOTDSTRING); // MOTD
|
||||
Missions::failInstancedMissions(sock); // auto-fail missions
|
||||
Buddies::sendBuddyList(sock); // buddy list
|
||||
Items::checkItemExpire(sock, plr); // vehicle expiration
|
||||
Items::checkAndRemoveExpiredItems(sock, plr); // vehicle and booster expiration
|
||||
|
||||
plr->initialLoadDone = true;
|
||||
}
|
||||
@@ -495,7 +505,6 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) {
|
||||
resp2.PCRegenDataForOtherPC.iAngle = plr->angle;
|
||||
|
||||
if (plr->group != nullptr) {
|
||||
|
||||
resp2.PCRegenDataForOtherPC.iConditionBitFlag = plr->getCompositeCondition();
|
||||
resp2.PCRegenDataForOtherPC.iPCState = plr->iPCState;
|
||||
resp2.PCRegenDataForOtherPC.iSpecialState = plr->iSpecialState;
|
||||
@@ -517,9 +526,7 @@ static void enterPlayerVehicle(CNSocket* sock, CNPacketData* data) {
|
||||
if (plr->instanceID != 0)
|
||||
return;
|
||||
|
||||
bool expired = plr->Equip[8].iTimeLimit < getTimestamp() && plr->Equip[8].iTimeLimit != 0;
|
||||
|
||||
if (plr->Equip[8].iID > 0 && !expired) {
|
||||
if (plr->Equip[EQUIP_SLOT_VEHICLE].iID > 0) {
|
||||
INITSTRUCT(sP_FE2CL_PC_VEHICLE_ON_SUCC, response);
|
||||
sock->sendPacket(response, P_FE2CL_PC_VEHICLE_ON_SUCC);
|
||||
|
||||
@@ -533,30 +540,6 @@ static void enterPlayerVehicle(CNSocket* sock, CNPacketData* data) {
|
||||
} else {
|
||||
INITSTRUCT(sP_FE2CL_PC_VEHICLE_ON_FAIL, response);
|
||||
sock->sendPacket(response, P_FE2CL_PC_VEHICLE_ON_FAIL);
|
||||
|
||||
// check if vehicle didn't expire
|
||||
if (expired) {
|
||||
plr->toRemoveVehicle.eIL = 0;
|
||||
plr->toRemoveVehicle.iSlotNum = 8;
|
||||
Items::checkItemExpire(sock, plr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void exitPlayerVehicle(CNSocket* sock, CNPacketData* data) {
|
||||
Player* plr = getPlayer(sock);
|
||||
|
||||
if (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;
|
||||
|
||||
sendToViewable(sock, response2, P_FE2CL_PC_STATE_CHANGE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -585,7 +568,7 @@ static void changePlayerGuide(CNSocket *sock, CNPacketData *data) {
|
||||
}
|
||||
|
||||
// start Blossom nano mission if applicable
|
||||
Missions::updateFusionMatter(sock, 0);
|
||||
Missions::updateFusionMatter(sock);
|
||||
}
|
||||
// save it on player
|
||||
plr->mentor = pkt->iMentor;
|
||||
@@ -607,6 +590,23 @@ static void setFirstUseFlag(CNSocket* sock, CNPacketData* data) {
|
||||
}
|
||||
|
||||
#pragma region Helper methods
|
||||
void PlayerManager::exitPlayerVehicle(CNSocket* sock, CNPacketData* data) {
|
||||
Player* plr = getPlayer(sock);
|
||||
|
||||
if (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;
|
||||
|
||||
sendToViewable(sock, response2, P_FE2CL_PC_STATE_CHANGE);
|
||||
}
|
||||
}
|
||||
|
||||
Player *PlayerManager::getPlayer(CNSocket* key) {
|
||||
if (players.find(key) != players.end())
|
||||
return players[key];
|
||||
|
||||
@@ -14,6 +14,8 @@ namespace PlayerManager {
|
||||
extern std::map<CNSocket*, Player*> players;
|
||||
void init();
|
||||
|
||||
void exitPlayerVehicle(CNSocket* sock, CNPacketData* data);
|
||||
|
||||
void removePlayer(CNSocket* key);
|
||||
|
||||
void updatePlayerPosition(CNSocket* sock, int X, int Y, int Z, uint64_t I, int angle);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Missions.hpp"
|
||||
#include "Items.hpp"
|
||||
#include "Nanos.hpp"
|
||||
|
||||
using namespace Racing;
|
||||
|
||||
@@ -14,6 +15,15 @@ std::map<int32_t, EPInfo> Racing::EPData;
|
||||
std::map<CNSocket*, EPRace> Racing::EPRaces;
|
||||
std::map<int32_t, std::pair<std::vector<int>, std::vector<int>>> Racing::EPRewards;
|
||||
|
||||
static int32_t calculateFMReward(Player* plr, int32_t baseAmount) {
|
||||
double scavenge = plr->hasBuff(ECSB_REWARD_BLOB) ? (Nanos::getNanoBoost(plr) ? 1.23 : 1.2) : 1.0;
|
||||
double raceBoost = plr->hasRacerBoost() ? (plr->hasHunterBoost() && plr->hasQuestBoost() ? 1.75 : 1.5) : 1.0;
|
||||
|
||||
int32_t reward = baseAmount * plr->rateF[RATE_SLOT_RACING] * scavenge;
|
||||
reward *= raceBoost;
|
||||
return reward;
|
||||
}
|
||||
|
||||
static void racingStart(CNSocket* sock, CNPacketData* data) {
|
||||
auto req = (sP_CL2FE_REQ_EP_RACE_START*)data->buf;
|
||||
|
||||
@@ -155,7 +165,9 @@ static void racingEnd(CNSocket* sock, CNPacketData* data) {
|
||||
resp.iEPRaceMode = EPRaces[sock].mode;
|
||||
resp.iEPRewardFM = fm;
|
||||
|
||||
Missions::updateFusionMatter(sock, resp.iEPRewardFM);
|
||||
int32_t fmReward = calculateFMReward(plr, resp.iEPRewardFM);
|
||||
plr->addCapped(CappedValueType::FUSIONMATTER, fmReward);
|
||||
Missions::updateFusionMatter(sock);
|
||||
|
||||
resp.iFusionMatter = plr->fusionmatter;
|
||||
resp.iFatigue = 50;
|
||||
|
||||
@@ -230,7 +230,7 @@ static void tradeConfirm(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
Player* plr2 = PlayerManager::getPlayer(otherSock);
|
||||
|
||||
|
||||
if (!(plr->isTrading && plr2->isTrading)) { // both players must be trading
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TRADE_CONFIRM_ABORT, resp);
|
||||
resp.iID_Request = plr2->iID;
|
||||
@@ -273,14 +273,16 @@ static void tradeConfirm(CNSocket* sock, CNPacketData* data) {
|
||||
resp2.iID_Request = pacdat->iID_Request;
|
||||
resp2.iID_From = pacdat->iID_From;
|
||||
resp2.iID_To = pacdat->iID_To;
|
||||
plr->money = plr->money + plr2->moneyInTrade - plr->moneyInTrade;
|
||||
plr->subtractCapped(CappedValueType::TAROS, plr->moneyInTrade);
|
||||
plr->addCapped(CappedValueType::TAROS, plr2->moneyInTrade);
|
||||
resp2.iCandy = plr->money;
|
||||
memcpy(resp2.Item, plr2->Trade, sizeof(plr2->Trade));
|
||||
memcpy(resp2.ItemStay, plr->Trade, sizeof(plr->Trade));
|
||||
|
||||
sock->sendPacket((void*)&resp2, P_FE2CL_REP_PC_TRADE_CONFIRM_SUCC, sizeof(sP_FE2CL_REP_PC_TRADE_CONFIRM_SUCC));
|
||||
|
||||
plr2->money = plr2->money + plr->moneyInTrade - plr2->moneyInTrade;
|
||||
plr2->subtractCapped(CappedValueType::TAROS, plr2->moneyInTrade);
|
||||
plr2->addCapped(CappedValueType::TAROS, plr->moneyInTrade);
|
||||
resp2.iCandy = plr2->money;
|
||||
memcpy(resp2.Item, plr->Trade, sizeof(plr->Trade));
|
||||
memcpy(resp2.ItemStay, plr2->Trade, sizeof(plr2->Trade));
|
||||
@@ -358,7 +360,7 @@ static void tradeRegisterItem(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
// since you can spread items like gumballs over multiple slots, we need to count them all
|
||||
// to make sure the inventory shows the right value during trade.
|
||||
int count = 0;
|
||||
int count = 0;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (plr->Trade[i].iInvenNum == pacdat->Item.iInvenNum)
|
||||
count += plr->Trade[i].iOpt;
|
||||
@@ -410,7 +412,7 @@ static void tradeUnregisterItem(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
// since you can spread items like gumballs over multiple slots, we need to count them all
|
||||
// to make sure the inventory shows the right value during trade.
|
||||
int count = 0;
|
||||
int count = 0;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (plr->Trade[i].iInvenNum == resp.InvenItem.iInvenNum)
|
||||
count += plr->Trade[i].iOpt;
|
||||
@@ -451,7 +453,7 @@ static void tradeRegisterCash(CNSocket* sock, CNPacketData* data) {
|
||||
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_CASH_REGISTER_SUCC, sizeof(sP_FE2CL_REP_PC_TRADE_CASH_REGISTER_SUCC));
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_CASH_REGISTER_SUCC, sizeof(sP_FE2CL_REP_PC_TRADE_CASH_REGISTER_SUCC));
|
||||
|
||||
plr->moneyInTrade = pacdat->iCandy;
|
||||
plr->setCapped(CappedValueType::TAROS_IN_TRADE, pacdat->iCandy);
|
||||
plr->isTradeConfirm = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ static void transportWarpHandler(CNSocket* sock, CNPacketData* data) {
|
||||
}
|
||||
|
||||
TransportRoute route = Routes[req->iTransporationID];
|
||||
plr->money -= route.cost;
|
||||
plr->subtractCapped(CappedValueType::TAROS, route.cost);
|
||||
|
||||
TransportLocation* target = nullptr;
|
||||
switch (route.type) {
|
||||
@@ -143,7 +143,7 @@ static void transportWarpHandler(CNSocket* sock, CNPacketData* data) {
|
||||
}
|
||||
|
||||
// refund and send alert packet
|
||||
plr->money += route.cost;
|
||||
plr->addCapped(CappedValueType::TAROS, route.cost);
|
||||
INITSTRUCT(sP_FE2CL_ANNOUNCE_MSG, alert);
|
||||
alert.iAnnounceType = 0; // don't think this lets us make a confirm dialog
|
||||
alert.iDuringTime = 3;
|
||||
|
||||
@@ -72,7 +72,7 @@ static void vendorBuy(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_SUCC, resp);
|
||||
|
||||
plr->money = plr->money - itemCost;
|
||||
plr->subtractCapped(CappedValueType::TAROS, itemCost);
|
||||
plr->Inven[slot] = req->Item;
|
||||
|
||||
resp.iCandy = plr->money;
|
||||
@@ -117,7 +117,7 @@ static void vendorSell(CNSocket* sock, CNPacketData* data) {
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_SUCC, resp);
|
||||
|
||||
// increment taros
|
||||
plr->money += itemData->sellPrice * req->iItemCnt;
|
||||
plr->addCapped(CappedValueType::TAROS, itemData->sellPrice * req->iItemCnt);
|
||||
|
||||
// modify item
|
||||
if (item->iOpt - req->iItemCnt > 0) { // selling part of a stack
|
||||
@@ -209,7 +209,7 @@ static void vendorBuyback(CNSocket* sock, CNPacketData* data) {
|
||||
std::cout << "[WARN] Client and server disagree on bought item slot (" << req->iInvenSlotNum << " vs " << slot << ")" << std::endl;
|
||||
}
|
||||
|
||||
plr->money = plr->money - itemCost;
|
||||
plr->subtractCapped(CappedValueType::TAROS, itemCost);
|
||||
plr->Inven[slot] = item;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_SUCC, resp);
|
||||
@@ -273,7 +273,7 @@ static void vendorBuyBattery(CNSocket* sock, CNPacketData* data) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
int cost = req->Item.iOpt * 100;
|
||||
if ((req->Item.iID == 3 ? (plr->batteryW >= 9999) : (plr->batteryN >= 9999)) || plr->money < cost || req->Item.iOpt < 0) { // sanity check
|
||||
if ((req->Item.iID == 3 ? (plr->batteryW >= PC_BATTERY_MAX) : (plr->batteryN >= PC_BATTERY_MAX)) || plr->money < cost || req->Item.iOpt < 0) { // sanity check
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_BATTERY_BUY_FAIL, failResp);
|
||||
failResp.iErrorCode = 0;
|
||||
sock->sendPacket(failResp, P_FE2CL_REP_PC_VENDOR_BATTERY_BUY_FAIL);
|
||||
@@ -281,17 +281,11 @@ static void vendorBuyBattery(CNSocket* sock, CNPacketData* data) {
|
||||
}
|
||||
|
||||
cost = plr->batteryW + plr->batteryN;
|
||||
plr->batteryW += req->Item.iID == 3 ? req->Item.iOpt * 100 : 0;
|
||||
plr->batteryN += req->Item.iID == 4 ? req->Item.iOpt * 100 : 0;
|
||||
|
||||
// caps
|
||||
if (plr->batteryW > 9999)
|
||||
plr->batteryW = 9999;
|
||||
if (plr->batteryN > 9999)
|
||||
plr->batteryN = 9999;
|
||||
plr->addCapped(CappedValueType::BATTERY_W, req->Item.iID == 3 ? req->Item.iOpt * 100 : 0);
|
||||
plr->addCapped(CappedValueType::BATTERY_N, req->Item.iID == 4 ? req->Item.iOpt * 100 : 0);
|
||||
|
||||
cost = plr->batteryW + plr->batteryN - cost;
|
||||
plr->money -= cost;
|
||||
plr->subtractCapped(CappedValueType::TAROS, cost);
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_BATTERY_BUY_SUCC, resp);
|
||||
|
||||
@@ -364,7 +358,7 @@ static void vendorCombineItems(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
float rolled = Rand::randFloat(100.0f); // success chance out of 100
|
||||
//std::cout << rolled << " vs " << successChance << std::endl;
|
||||
plr->money -= cost;
|
||||
plr->subtractCapped(CappedValueType::TAROS, cost);
|
||||
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_COMBINATION_SUCC, resp);
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
// wrapper for U16toU8
|
||||
#define ARRLEN(x) (sizeof(x)/sizeof(*x))
|
||||
#define AUTOU8(x) std::string((char*)x, ARRLEN(x))
|
||||
#define AUTOU16TOU8(x) U16toU8(x, ARRLEN(x))
|
||||
#define AUTOU16TOU8(x) U16toU8(x, ARRLEN(x))
|
||||
|
||||
// TODO: rewrite U16toU8 & U8toU16 to not use codecvt
|
||||
|
||||
@@ -67,4 +67,7 @@ void terminate(int);
|
||||
#error Invalid PROTOCOL_VERSION
|
||||
#endif
|
||||
|
||||
#define AEQUIP_COUNT_MINUS_BOOSTERS 9
|
||||
#define AEQUIP_COUNT_WITH_BOOSTERS 12
|
||||
|
||||
sSYSTEMTIME timeStampToStruct(uint64_t time);
|
||||
|
||||
@@ -225,6 +225,15 @@ enum {
|
||||
SIZEOF_NANO_TUNE_NEED_ITEM_SLOT = 10,
|
||||
VALUE_ATTACK_MISS = 1,
|
||||
|
||||
REWARD_TYPE_TAROS = 0,
|
||||
REWARD_TYPE_FUSIONMATTER = 1,
|
||||
|
||||
RATE_SLOT_ALL = 0,
|
||||
RATE_SLOT_COMBAT = 1,
|
||||
RATE_SLOT_MISSION = 2,
|
||||
RATE_SLOT_EGG = 3,
|
||||
RATE_SLOT_RACING = 4,
|
||||
|
||||
MSG_ONLINE = 1,
|
||||
MSG_BUSY = 2,
|
||||
MSG_OFFLINE = 0,
|
||||
|
||||
@@ -2,40 +2,6 @@
|
||||
|
||||
// Loading and saving players to/from the DB
|
||||
|
||||
static void removeExpiredVehicles(Player* player) {
|
||||
int32_t currentTime = getTimestamp();
|
||||
|
||||
// if there are expired vehicles in bank just remove them silently
|
||||
for (int i = 0; i < ABANK_COUNT; i++) {
|
||||
if (player->Bank[i].iType == 10 && player->Bank[i].iTimeLimit < currentTime && player->Bank[i].iTimeLimit != 0) {
|
||||
memset(&player->Bank[i], 0, sizeof(sItemBase));
|
||||
}
|
||||
}
|
||||
|
||||
// we want to leave only 1 expired vehicle on player to delete it with the client packet
|
||||
std::vector<sItemBase*> toRemove;
|
||||
|
||||
// equipped vehicle
|
||||
if (player->Equip[8].iOpt > 0 && player->Equip[8].iTimeLimit < currentTime && player->Equip[8].iTimeLimit != 0) {
|
||||
toRemove.push_back(&player->Equip[8]);
|
||||
player->toRemoveVehicle.eIL = 0;
|
||||
player->toRemoveVehicle.iSlotNum = 8;
|
||||
}
|
||||
// inventory
|
||||
for (int i = 0; i < AINVEN_COUNT; i++) {
|
||||
if (player->Inven[i].iType == 10 && player->Inven[i].iTimeLimit < currentTime && player->Inven[i].iTimeLimit != 0) {
|
||||
toRemove.push_back(&player->Inven[i]);
|
||||
player->toRemoveVehicle.eIL = 1;
|
||||
player->toRemoveVehicle.iSlotNum = i;
|
||||
}
|
||||
}
|
||||
|
||||
// delete all but one vehicles, leave last one for ceremonial deletion
|
||||
for (int i = 0; i < (int)toRemove.size()-1; i++) {
|
||||
memset(toRemove[i], 0, sizeof(sItemBase));
|
||||
}
|
||||
}
|
||||
|
||||
void Database::getPlayer(Player* plr, int id) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
@@ -93,13 +59,13 @@ void Database::getPlayer(Player* plr, int id) {
|
||||
plr->angle = sqlite3_column_int(stmt, 15);
|
||||
plr->HP = sqlite3_column_int(stmt, 16);
|
||||
plr->accountLevel = sqlite3_column_int(stmt, 17);
|
||||
plr->fusionmatter = sqlite3_column_int(stmt, 18);
|
||||
plr->money = sqlite3_column_int(stmt, 19);
|
||||
plr->setCapped(CappedValueType::FUSIONMATTER, sqlite3_column_int(stmt, 18));
|
||||
plr->setCapped(CappedValueType::TAROS, sqlite3_column_int(stmt, 19));
|
||||
|
||||
memcpy(plr->aQuestFlag, sqlite3_column_blob(stmt, 20), sizeof(plr->aQuestFlag));
|
||||
|
||||
plr->batteryW = sqlite3_column_int(stmt, 21);
|
||||
plr->batteryN = sqlite3_column_int(stmt, 22);
|
||||
plr->setCapped(CappedValueType::BATTERY_W, sqlite3_column_int(stmt, 21));
|
||||
plr->setCapped(CappedValueType::BATTERY_N, sqlite3_column_int(stmt, 22));
|
||||
plr->mentor = sqlite3_column_int(stmt, 23);
|
||||
plr->iWarpLocationFlag = sqlite3_column_int(stmt, 24);
|
||||
|
||||
@@ -160,8 +126,6 @@ void Database::getPlayer(Player* plr, int id) {
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
removeExpiredVehicles(plr);
|
||||
|
||||
// get quest inventory
|
||||
sql = R"(
|
||||
SELECT Slot, ID, Opt
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "MobAI.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "TableData.hpp" // for flush()
|
||||
#include "Items.hpp" // for checkAndRemoveExpiredItems()
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
@@ -23,6 +24,7 @@ CNShardServer::CNShardServer(uint16_t p) {
|
||||
pHandler = &CNShardServer::handlePacket;
|
||||
REGISTER_SHARD_TIMER(keepAliveTimer, 4000);
|
||||
REGISTER_SHARD_TIMER(periodicSaveTimer, settings::DBSAVEINTERVAL*1000);
|
||||
REGISTER_SHARD_TIMER(periodicItemExpireTimer, 60000);
|
||||
init();
|
||||
|
||||
if (settings::MONITORENABLED)
|
||||
@@ -88,6 +90,22 @@ void CNShardServer::periodicSaveTimer(CNServer* serv, time_t currTime) {
|
||||
std::cout << "[INFO] Done." << std::endl;
|
||||
}
|
||||
|
||||
void CNShardServer::periodicItemExpireTimer(CNServer* serv, time_t currTime) {
|
||||
size_t playersWithExpiredItems = 0;
|
||||
size_t itemsRemoved = 0;
|
||||
|
||||
for (const auto& [sock, player] : PlayerManager::players) {
|
||||
// check and remove expired items
|
||||
size_t removed = Items::checkAndRemoveExpiredItems(sock, player);
|
||||
itemsRemoved += removed;
|
||||
playersWithExpiredItems += (removed == 0 ? 0 : 1);
|
||||
}
|
||||
|
||||
if (playersWithExpiredItems > 0) {
|
||||
std::cout << "[INFO] Removed " << itemsRemoved << " expired items from " << playersWithExpiredItems << " players." << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
bool CNShardServer::checkExtraSockets(int i) {
|
||||
return Monitor::acceptConnection(fds[i].fd, fds[i].revents);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ private:
|
||||
|
||||
static void keepAliveTimer(CNServer*, time_t);
|
||||
static void periodicSaveTimer(CNServer* serv, time_t currTime);
|
||||
static void periodicItemExpireTimer(CNServer* serv, time_t currTime);
|
||||
|
||||
public:
|
||||
static std::map<uint32_t, PacketHandler> ShardPackets;
|
||||
|
||||
@@ -77,6 +77,16 @@ bool settings::IZRACESCORECAPPED = true;
|
||||
// drop fixes enabled
|
||||
bool settings::DROPFIXESENABLED = false;
|
||||
|
||||
// less taro / fm while in a group
|
||||
bool settings::LESSTAROFMINGROUPDISABLED = false;
|
||||
|
||||
// general reward percentages
|
||||
int settings::TARORATE = 100;
|
||||
int settings::FUSIONMATTERRATE = 100;
|
||||
|
||||
// should expired items in the bank disappear automatically?
|
||||
bool settings::REMOVEEXPIREDITEMSFROMBANK = false;
|
||||
|
||||
void settings::init() {
|
||||
INIReader reader("config.ini");
|
||||
|
||||
@@ -121,11 +131,15 @@ void settings::init() {
|
||||
PATCHDIR = reader.Get("shard", "patchdir", PATCHDIR);
|
||||
ENABLEDPATCHES = reader.Get("shard", "enabledpatches", ENABLEDPATCHES);
|
||||
DROPFIXESENABLED = reader.GetBoolean("shard", "dropfixesenabled", DROPFIXESENABLED);
|
||||
LESSTAROFMINGROUPDISABLED = reader.GetBoolean("shard", "lesstarofmingroupdisabled", LESSTAROFMINGROUPDISABLED);
|
||||
TARORATE = reader.GetInteger("shard", "tarorate", TARORATE);
|
||||
FUSIONMATTERRATE = reader.GetInteger("shard", "fusionmatterrate", FUSIONMATTERRATE);
|
||||
ACCLEVEL = reader.GetInteger("shard", "accountlevel", ACCLEVEL);
|
||||
EVENTMODE = reader.GetInteger("shard", "eventmode", EVENTMODE);
|
||||
DISABLEFIRSTUSEFLAG = reader.GetBoolean("shard", "disablefirstuseflag", DISABLEFIRSTUSEFLAG);
|
||||
ANTICHEAT = reader.GetBoolean("shard", "anticheat", ANTICHEAT);
|
||||
IZRACESCORECAPPED = reader.GetBoolean("shard", "izracescorecapped", IZRACESCORECAPPED);
|
||||
REMOVEEXPIREDITEMSFROMBANK = reader.GetBoolean("shard", "removeexpireditemsfrombank", REMOVEEXPIREDITEMSFROMBANK);
|
||||
MONITORENABLED = reader.GetBoolean("monitor", "enabled", MONITORENABLED);
|
||||
MONITORPORT = reader.GetInteger("monitor", "port", MONITORPORT);
|
||||
MONITORLISTENIP = reader.Get("monitor", "listenip", MONITORLISTENIP);
|
||||
|
||||
@@ -46,6 +46,10 @@ namespace settings {
|
||||
extern bool DISABLEFIRSTUSEFLAG;
|
||||
extern bool IZRACESCORECAPPED;
|
||||
extern bool DROPFIXESENABLED;
|
||||
extern bool LESSTAROFMINGROUPDISABLED;
|
||||
extern int TARORATE;
|
||||
extern int FUSIONMATTERRATE;
|
||||
extern bool REMOVEEXPIREDITEMSFROMBANK;
|
||||
|
||||
void init();
|
||||
}
|
||||
|
||||
2
tdata
2
tdata
Submodule tdata updated: d6183b484e...fed031b972
Reference in New Issue
Block a user